Music: real melody instead of percussion blips
Replaced the atonal note-burst loops with an actual tune - a soft, public-domain arrangement of Twinkle Twinkle Little Star in C (fits the star lanes), scheduled as proper notes with durations over an oom-pah bass line: - title: slow sine lullaby (~100 bpm) - play: bouncier triangle version with a brighter octave-up B section - verified the schedule spells out C C G G A A G F F E E D D C ... - no square waves or noise in the music; soft volumes
This commit is contained in:
@@ -163,6 +163,25 @@ class Synth:
|
|||||||
self._build_music()
|
self._build_music()
|
||||||
|
|
||||||
# ------------------------------------------------------------------ music
|
# ------------------------------------------------------------------ music
|
||||||
|
# A real melody! Public-domain "Twinkle Twinkle Little Star" (Mozart's
|
||||||
|
# variations), arranged as a gentle 4-bar loop with a soft bass line.
|
||||||
|
# Fits the star-lane theme; unmistakably a tune.
|
||||||
|
# Each bar is 8 eighth-note slots; attacks are (note, n_slots, octave).
|
||||||
|
MELODY = [
|
||||||
|
# Twinkle, twinkle, little star
|
||||||
|
[("C", 1, 4), ("C", 1, 4), ("G", 1, 4), ("G", 1, 4),
|
||||||
|
("A", 1, 4), ("A", 1, 4), ("G", 2, 4)],
|
||||||
|
# how I wonder what you are
|
||||||
|
[("F", 1, 4), ("F", 1, 4), ("E", 1, 4), ("E", 1, 4),
|
||||||
|
("D", 1, 4), ("D", 1, 4), ("C", 2, 4)],
|
||||||
|
# up above the world so high / like a diamond in the sky
|
||||||
|
[("G", 1, 4), ("G", 1, 4), ("F", 1, 4), ("F", 1, 4),
|
||||||
|
("E", 1, 4), ("E", 1, 4), ("D", 2, 4)],
|
||||||
|
[("G", 1, 4), ("G", 1, 4), ("F", 1, 4), ("F", 1, 4),
|
||||||
|
("E", 1, 4), ("E", 1, 4), ("D", 2, 4)],
|
||||||
|
]
|
||||||
|
BASS_ROOTS = ["C", "F", "C", "C"] # harmony for each of the 4 bars
|
||||||
|
|
||||||
def _music_buffer(self, tracks, total_s):
|
def _music_buffer(self, tracks, total_s):
|
||||||
"""Render time-placed notes (start_s, note, octave, dur_s, vol, shape)
|
"""Render time-placed notes (start_s, note, octave, dur_s, vol, shape)
|
||||||
into one softly-mixed, normalized buffer."""
|
into one softly-mixed, normalized buffer."""
|
||||||
@@ -179,53 +198,54 @@ class Synth:
|
|||||||
sc = 28000.0 / peak if peak > 28000 else 1.0
|
sc = 28000.0 / peak if peak > 28000 else 1.0
|
||||||
return array.array("h", (int(v * sc) for v in buf))
|
return array.array("h", (int(v * sc) for v in buf))
|
||||||
|
|
||||||
def _build_music(self):
|
def _render_loop(self, eighth_s, lead_vol, lead_shape, bass_vol,
|
||||||
"""Two short generative loops, deliberately sparse & soft: a calm menu
|
offset_s=0.0, bars=None, melody_oct=4, with_bass=True):
|
||||||
loop and a gentle bouncy play loop. (Square waves and hats are used in
|
"""Schedule the MELODY (per-bar eighth attacks) plus a soft oom-pah
|
||||||
SFX only - never in the music - to keep it from getting harsh.)"""
|
bass. `offset_s` shifts the start (used for B sections / echoes);
|
||||||
|
`bars` picks which MELODY bars to play in order."""
|
||||||
# ---- menu: dreamy, mostly rests, C - Am - F - G ------------------------
|
bars = bars if bars is not None else list(range(4))
|
||||||
seq = [
|
|
||||||
(("C", 2), ["C", "E", "G", "D"], "E"),
|
|
||||||
(("A", 2), ["A", "C", "E", "B"], "C"),
|
|
||||||
(("F", 2), ["F", "A", "C", "G"], "A"),
|
|
||||||
(("G", 2), ["G", "B", "D", "A"], "B"),
|
|
||||||
]
|
|
||||||
tracks = []
|
tracks = []
|
||||||
CH = 1.8
|
for idx, bi in enumerate(bars):
|
||||||
for ci, (bass, arp, twinkle) in enumerate(seq):
|
bar_start = offset_s + idx * 8 * eighth_s
|
||||||
t = ci * CH
|
if with_bass:
|
||||||
tracks.append((t, bass[0], bass[1], CH * 0.72, 0.12, "tri")) # bass
|
root = self.BASS_ROOTS[bi % len(self.BASS_ROOTS)]
|
||||||
for k in range(4): # arpeggio
|
fifth = "G" if root == "C" else "C"
|
||||||
oc = 4 if k < 2 else 5
|
tracks.append((bar_start, root, 2, eighth_s * 4.2, bass_vol, "tri"))
|
||||||
tracks.append((t + 0.35 + k * 0.26, arp[k], oc, 0.24, 0.08, "tri"))
|
tracks.append((bar_start + eighth_s * 4, fifth, 2,
|
||||||
tracks.append((t + 1.3, twinkle, 5, 0.55, 0.045, "tri")) # twinkle
|
eighth_s * 4.2, bass_vol, "tri"))
|
||||||
menu_total = 4 * CH + 0.8
|
slot = 0
|
||||||
|
for (nm, nslots, oc) in self.MELODY[bi % len(self.MELODY)]:
|
||||||
|
t = bar_start + slot * eighth_s
|
||||||
|
tracks.append((t + eighth_s * 0.08, nm, oc + melody_oct - 4,
|
||||||
|
eighth_s * nslots * 0.9, lead_vol, lead_shape))
|
||||||
|
slot += nslots
|
||||||
|
return tracks
|
||||||
|
|
||||||
|
def _build_music(self):
|
||||||
|
"""A real tune: public-domain "Twinkle Twinkle Little Star" arranged
|
||||||
|
in C - soft sine lullaby on the title, bouncy triangle version during
|
||||||
|
play. Gentle oom-pah bass, nothing harsh, no percussion."""
|
||||||
|
|
||||||
|
# ---- menu: slow lullaby ----------------------------------------------
|
||||||
|
e = 0.30 # one bar = 8 * 0.30 = 2.4 s
|
||||||
|
tracks = self._render_loop(e, 0.085, "sine", 0.055)
|
||||||
|
menu_total = 4 * 8 * e + 2.6 # 4 bars + tail
|
||||||
self._menu_music = self._music_buffer(tracks, menu_total)
|
self._menu_music = self._music_buffer(tracks, menu_total)
|
||||||
|
|
||||||
# ---- play: bouncy but gentle, 16 eighth-notes @ ~0.30s ----------------
|
# ---- play: bouncy version, with a brighter octave-up repeat ----------
|
||||||
step = 0.30
|
e2 = 0.225 # ~130 bpm eighth feel
|
||||||
bass_seq = [("C", 2), ("C", 2), ("G", 2), ("G", 2),
|
tracks = self._render_loop(e2, 0.115, "tri", 0.07)
|
||||||
("A", 2), ("A", 2), ("F", 2), ("G", 2),
|
# B section: same tune one octave up, lighter, then the loop restarts
|
||||||
("C", 2), ("C", 2), ("G", 2), ("G", 2),
|
tracks += self._render_loop(e2, 0.045, "sine", 0.0,
|
||||||
("A", 2), ("A", 2), ("G", 2), ("C", 2)]
|
offset_s=4 * 8 * e2, bars=[0, 1, 2, 3],
|
||||||
lead_seq = [None, None, ("E", 4), None, ("G", 4), None, ("E", 4), None,
|
melody_oct=5, with_bass=False)
|
||||||
("D", 4), None, ("F", 4), None, ("A", 4), None, ("G", 4), None]
|
play_total = 2 * 4 * 8 * e2 + 1.6 # two passes, ~16 s of music
|
||||||
tracks = []
|
|
||||||
for i in range(16):
|
|
||||||
t = i * step
|
|
||||||
if i % 2 == 0: # bass on the beat
|
|
||||||
bn, bo = bass_seq[i]
|
|
||||||
tracks.append((t, bn, bo, step * 1.25, 0.15, "tri"))
|
|
||||||
if lead_seq[i]: # light melody
|
|
||||||
nm, oc = lead_seq[i]
|
|
||||||
tracks.append((t + 0.02, nm, oc, step * 0.85, 0.085, "tri"))
|
|
||||||
play_total = 16 * step + 1.2
|
|
||||||
self._play_music = self._music_buffer(tracks, play_total)
|
self._play_music = self._music_buffer(tracks, play_total)
|
||||||
|
|
||||||
self._menu = pygame.mixer.Sound(buffer=self._menu_music.tobytes())
|
self._menu = pygame.mixer.Sound(buffer=self._menu_music.tobytes())
|
||||||
self._play = pygame.mixer.Sound(buffer=self._play_music.tobytes())
|
self._play = pygame.mixer.Sound(buffer=self._play_music.tobytes())
|
||||||
self._menu.set_volume(0.40)
|
self._menu.set_volume(0.42)
|
||||||
self._play.set_volume(0.30)
|
self._play.set_volume(0.34)
|
||||||
|
|
||||||
# ---- playback --------------------------------------------------------
|
# ---- playback --------------------------------------------------------
|
||||||
def play(self, name, vol=1.0, pitch_var=0):
|
def play(self, name, vol=1.0, pitch_var=0):
|
||||||
|
|||||||
Reference in New Issue
Block a user