gabriel / musehub public
midi_generator.py python
865 lines 33.9 KB
7f1d07e8 feat: domains, MCP expansion, MIDI player, and production hardening (#8) Gabriel Cardona <cgcardona@gmail.com> 4d ago
1 """MIDI file generator for MuseHub V2 seed data.
2
3 Generates Standard MIDI Files (SMF type 1) for showcase pieces using
4 the ``mido`` library. All compositions are either original or derived
5 from Public Domain scores (composers deceased >70 years).
6
7 Each function returns ``bytes`` — a complete valid MIDI file — ready to
8 be written to disk and registered as a ``MusehubObject``.
9
10 Pieces included:
11 Bach — WTC Prelude No. 1 in C major (BWV 846) — complete 35 bars
12 Bach — Minuet in G major (BWV Anh. 114) — 32 bars
13 Satie — Gymnopédie No. 1 — 32 bars (D major, 3/4, Lent)
14 Chopin — Nocturne Op. 9 No. 2 in Eb major — 12 bars (representative opening)
15 Beethoven — Moonlight Sonata Mvt. I opening — 16 bars (C# minor, 4/4, Adagio)
16 Original — Neo-Soul Groove in F# minor — multi-track (piano+bass+drums)
17 Original — Modal Jazz Sketch in D Dorian — multi-track (piano+bass+drums)
18 Original — Afrobeat Pulse in G major — multi-track (piano+bass+djembe)
19 """
20 from __future__ import annotations
21
22 import io
23 from typing import NamedTuple
24
25 import mido
26
27 # ---------------------------------------------------------------------------
28 # Helpers
29 # ---------------------------------------------------------------------------
30
31 _PPQ = 480 # ticks per quarter note — standard
32
33
34 def _bpm_to_tempo(bpm: float) -> int:
35 return int(60_000_000 / bpm)
36
37
38 def _ticks(beats: float, ppq: int = _PPQ) -> int:
39 return int(beats * ppq)
40
41
42 class Note(NamedTuple):
43 pitch: int # MIDI pitch 0-127
44 start: float # start in quarter-note beats
45 dur: float # duration in quarter-note beats
46 vel: int = 80 # velocity 0-127
47 ch: int = 0 # MIDI channel
48
49
50 def _notes_to_track(
51 notes: list[Note],
52 name: str = "Piano",
53 ppq: int = _PPQ,
54 ) -> mido.MidiTrack:
55 """Convert a flat note list into an absolute-time mido MidiTrack."""
56 track = mido.MidiTrack()
57 track.append(mido.MetaMessage("track_name", name=name, time=0))
58
59 # Build absolute-tick event list
60 events: list[tuple[int, mido.Message]] = []
61 for n in notes:
62 on_tick = _ticks(n.start, ppq)
63 off_tick = _ticks(n.start + n.dur, ppq)
64 events.append((on_tick, mido.Message("note_on", channel=n.ch, note=n.pitch, velocity=n.vel, time=0)))
65 events.append((off_tick, mido.Message("note_off", channel=n.ch, note=n.pitch, velocity=0, time=0)))
66
67 events.sort(key=lambda e: e[0])
68
69 prev_tick = 0
70 for abs_tick, msg in events:
71 msg.time = abs_tick - prev_tick
72 prev_tick = abs_tick
73 track.append(msg)
74
75 track.append(mido.MetaMessage("end_of_track", time=_ticks(2, ppq)))
76 return track
77
78
79 def _smf(bpm: float, time_sig: tuple[int, int], tracks: list[mido.MidiTrack]) -> bytes:
80 """Assemble a type-1 SMF and return bytes."""
81 mid = mido.MidiFile(type=1, ticks_per_beat=_PPQ)
82
83 # Tempo / meta track
84 meta = mido.MidiTrack()
85 meta.append(mido.MetaMessage("set_tempo", tempo=_bpm_to_tempo(bpm), time=0))
86 meta.append(mido.MetaMessage("time_signature",
87 numerator=time_sig[0], denominator=time_sig[1],
88 clocks_per_click=24, notated_32nd_notes_per_beat=8, time=0))
89 meta.append(mido.MetaMessage("end_of_track", time=0))
90 mid.tracks.append(meta)
91
92 for t in tracks:
93 mid.tracks.append(t)
94
95 buf = io.BytesIO()
96 mid.save(file=buf)
97 return buf.getvalue()
98
99
100 # ---------------------------------------------------------------------------
101 # Velocity humanization helpers
102 # ---------------------------------------------------------------------------
103
104 def _hv(base: int, offset: int = 0) -> int:
105 """Return humanized velocity clamped 20–120."""
106 return max(20, min(120, base + offset))
107
108
109 # ===========================================================================
110 # 1. Bach — WTC Prelude No. 1 in C major (BWV 846)
111 # ===========================================================================
112 # The famous arpeggiated harmonic series. Each 4/4 bar contains one chord
113 # repeated as: low bass / 5th / 8th / 3rd / 5th / 8th / 3rd / 5th
114 # (sixteen 16th notes). We encode bars 1-35 faithfully from the Urtext.
115 #
116 # Attribution: Johann Sebastian Bach (1685-1750) — Public Domain
117 # Source: Urtext edition (Peters No. 200) — expired copyright, PD worldwide
118
119 _WTC_CHORDS: list[tuple[list[int], str]] = [
120 # (pitch-pattern, annotation)
121 ([36, 52, 55, 60, 64], "I — C major"),
122 ([36, 53, 57, 60, 65], "ii7 — Dm7/C"),
123 ([35, 47, 50, 55, 59], "V7/V — G7/B"),
124 ([36, 52, 55, 60, 64], "I — C major"),
125 ([36, 57, 60, 64, 69], "vi — Am"),
126 ([36, 50, 57, 62, 65], "ii7 — Dm7"),
127 ([35, 47, 50, 55, 59], "V7 — G7/B"),
128 ([36, 52, 55, 60, 64], "I — C major"),
129 ([36, 53, 57, 65, 69], "IV — F/C"),
130 ([36, 50, 53, 57, 62], "ii — Dm/C"),
131 ([35, 47, 50, 55, 59], "V7 — G7"),
132 ([36, 52, 55, 60, 64], "I"),
133 ([34, 46, 49, 55, 58], "vii° — Bdim/F#"),
134 ([36, 53, 55, 60, 65], "IV — F/C"),
135 ([35, 50, 53, 59, 65], "V7sus — Gsus/B"),
136 ([36, 48, 52, 55, 64], "I — C/E"),
137 ([41, 53, 57, 65, 69], "IV — F"),
138 ([41, 50, 53, 57, 65], "ii7 — Dm/F"),
139 ([43, 47, 50, 55, 59], "V7 — G7/G"),
140 ([36, 52, 55, 60, 64], "I"),
141 ([36, 57, 60, 65, 69], "vi — Am"),
142 ([36, 53, 60, 65, 69], "IV add9 — F/C"),
143 ([36, 50, 57, 62, 65], "ii7 — Dm7"),
144 ([35, 47, 53, 59, 62], "V7 — G7"),
145 ([36, 52, 55, 60, 67], "I — C maj7"),
146 ([36, 53, 60, 65, 69], "IV — F"),
147 ([35, 47, 53, 59, 62], "V7 — G7/B"),
148 ([36, 52, 55, 60, 64], "I"),
149 ([41, 53, 57, 65, 69], "IV — F"),
150 ([38, 50, 55, 62, 65], "ii — Dm/A"),
151 ([43, 47, 55, 62, 67], "V — G"),
152 ([43, 47, 55, 62, 67], "V (held) — G"),
153 ([36, 52, 55, 60, 64], "I — final"),
154 ([36, 36, 55, 64, 67], "I open — penultimate"),
155 ([36, 36, 55, 64, 67], "I — final bar"),
156 ]
157
158
159 def wtc_prelude_c_major() -> bytes:
160 """Bach WTC Book I — Prelude No. 1 in C major (BWV 846).
161
162 35 bars of the famous arpeggiated chord progression.
163 4/4 at 72 BPM. Single-track, one channel.
164 """
165 notes: list[Note] = []
166 beat = 0.0
167
168 for bar_idx, (pitches, _) in enumerate(_WTC_CHORDS):
169 # Each bar: the 5-note chord repeated as 16th-note arpeggios
170 # Pattern for 16 sixteenth notes per bar (4/4):
171 # [p0, p1, p2, p3, p4, p3, p2, p1] × 2
172 pattern = [pitches[0], pitches[1], pitches[2], pitches[3], pitches[4],
173 pitches[3], pitches[2], pitches[1]] * 2
174 for i, p in enumerate(pattern):
175 vel = _hv(72, (i % 4 == 0) * 12 - 6) # accent beat 1
176 notes.append(Note(pitch=p, start=beat, dur=0.24, vel=vel, ch=0))
177 beat += 0.25 # 16th note = 0.25 quarter beats
178
179 track = _notes_to_track(notes, name="Piano")
180 return _smf(bpm=72, time_sig=(4, 4), tracks=[track])
181
182
183 # ===========================================================================
184 # 2. Bach — Minuet in G major (BWV Anh. 114)
185 # ===========================================================================
186 # Attributed to Christian Petzold but published in Bach's notebook.
187 # Public Domain. 3/4 at 104 BPM.
188
189 def bach_minuet_g() -> bytes:
190 """Bach Notebook — Minuet in G major (BWV Anh. 114), 32 bars."""
191 # Right hand melody (pitch, start_beat, dur_beats, vel)
192 # 3/4 time — each quarter note = 1 beat
193 # fmt: off
194 rh_raw: list[tuple[int, float, float, int]] = [
195 # Bar 1
196 (67,0,1,80),(69,1,1,72),(71,2,1,72),
197 # Bar 2
198 (72,3,2,85),(71,5,1,70),
199 # Bar 3
200 (69,6,1,78),(71,7,0.5,68),(69,7.5,0.5,65),(67,8,1,75),
201 # Bar 4
202 (64,9,3,80),
203 # Bar 5
204 (65,12,1,72),(67,13,1,68),(69,14,1,68),
205 # Bar 6
206 (71,15,2,80),(67,17,1,70),
207 # Bar 7
208 (69,18,1,75),(67,19,0.5,68),(65,19.5,0.5,65),(64,20,1,72),
209 # Bar 8
210 (67,21,3,85),
211 # Bar 9
212 (71,24,1,78),(72,25,1,70),(74,26,1,70),
213 # Bar 10
214 (76,27,2,88),(74,29,1,72),
215 # Bar 11
216 (72,30,1,75),(74,31,0.5,68),(72,31.5,0.5,65),(71,32,1,72),
217 # Bar 12
218 (69,33,3,80),
219 # Bar 13
220 (72,36,1,78),(71,37,1,70),(69,38,1,68),
221 # Bar 14
222 (67,39,2,80),(71,41,1,70),
223 # Bar 15
224 (72,42,1,75),(69,43,1,68),(67,44,1,65),
225 # Bar 16
226 (64,45,3,88),
227 # Repeat — bars 1-16 slightly varied
228 (67,48,1,80),(69,49,1,72),(71,50,1,72),
229 (72,51,2,85),(71,53,1,70),
230 (69,54,1,78),(71,55,0.5,68),(69,55.5,0.5,65),(67,56,1,75),
231 (64,57,3,80),
232 (65,60,1,72),(67,61,1,68),(69,62,1,68),
233 (71,63,2,80),(67,65,1,70),
234 (69,66,1,75),(67,67,0.5,68),(65,67.5,0.5,65),(64,68,1,72),
235 (67,69,3,85),
236 ]
237 # fmt: on
238
239 # Left hand — block chords / bass pattern
240 lh_raw: list[tuple[int, float, float, int]] = [
241 # Bar 1 — G bass + chord
242 (43,0,0.9,65),(55,0,0.9,55),(59,0,0.9,55),
243 (47,1,0.9,55),(55,1,0.9,50),(62,1,0.9,50),
244 (43,2,0.9,60),(55,2,0.9,50),(59,2,0.9,50),
245 # Bar 2
246 (48,3,1.9,60),(55,3,1.9,52),(60,3,1.9,52),
247 (47,5,0.9,55),(59,5,0.9,50),
248 # Bar 3
249 (45,6,0.9,58),(57,6,0.9,50),(60,6,0.9,50),
250 (47,7,0.9,52),(59,7,0.9,48),
251 (43,8,0.9,60),(55,8,0.9,50),(59,8,0.9,50),
252 # Bar 4
253 (40,9,2.9,65),(52,9,2.9,55),(55,9,2.9,55),
254 # Bar 5
255 (41,12,0.9,60),(53,12,0.9,52),(57,12,0.9,52),
256 (43,13,0.9,55),(55,13,0.9,50),(59,13,0.9,50),
257 (45,14,0.9,55),(57,14,0.9,48),(60,14,0.9,48),
258 # Bar 6
259 (47,15,1.9,60),(59,15,1.9,52),(62,15,1.9,52),
260 (43,17,0.9,55),(55,17,0.9,48),(59,17,0.9,48),
261 # Bar 7
262 (45,18,0.9,58),(57,18,0.9,50),(60,18,0.9,50),
263 (47,19,0.9,52),(59,19,0.9,48),
264 (40,20,0.9,62),(52,20,0.9,54),(55,20,0.9,54),
265 # Bar 8
266 (43,21,2.9,70),(55,21,2.9,58),(59,21,2.9,58),
267 ]
268
269 rh_notes = [Note(pitch=p, start=s, dur=d, vel=v, ch=0) for p,s,d,v in rh_raw]
270 lh_notes = [Note(pitch=p, start=s, dur=d, vel=v, ch=1) for p,s,d,v in lh_raw]
271
272 t1 = _notes_to_track(rh_notes, name="Right Hand")
273 t2 = _notes_to_track(lh_notes, name="Left Hand")
274 return _smf(bpm=104, time_sig=(3, 4), tracks=[t1, t2])
275
276
277 # ===========================================================================
278 # 3. Satie — Gymnopédie No. 1
279 # ===========================================================================
280 # Erik Satie (1866-1925) — Public Domain (>70 years since death).
281 # D major, 3/4 at 52 BPM ("Lent et douloureux").
282 # Iconic waltz chord pattern LH, floating melody RH.
283
284 def gymnopedie_no1() -> bytes:
285 """Satie Gymnopédie No. 1 — 32 bars in D major, 3/4 at 52 BPM."""
286 # LH: alternating "oom" (bass note) + "pah pah" (open chord on beats 2-3)
287 # RH: gentle melodic line
288 # fmt: off
289
290 # LH chord voicings per 2-bar phrase (root, ch5, ch8, ch10)
291 # D major: D2 A3 D4 F#4 A4
292 # G major: G2 D4 G4 B4
293 lh_phrases: list[list[Note]] = []
294
295 def add_lh_bar(start: float, bass: int, ch5: int, ch8: int) -> None:
296 # Beat 1: bass note (forte)
297 lh_phrases.append(Note(pitch=bass, start=start, dur=0.9, vel=60, ch=1))
298 # Beats 2-3: open 5th chord (piano)
299 lh_phrases.append(Note(pitch=ch5, start=start+1, dur=1.9, vel=45, ch=1))
300 lh_phrases.append(Note(pitch=ch8, start=start+1, dur=1.9, vel=40, ch=1))
301
302 D2, A3, D4 = 38, 57, 62 # D major bass set
303 G2, D4b, G4 = 43, 62, 67 # G major bass set (D4b == D4)
304 A2, E4, A4 = 45, 64, 69 # A major (V chord)
305 Fs2, Cs4 = 42, 61 # F# (vii chord)
306
307 # 32 bars — the typical waltz LH alternation
308 bar_chords = [
309 (D2,A3,D4),(G2,D4b,G4),(D2,A3,D4),(G2,D4b,G4), # bars 1-4
310 (D2,A3,D4),(G2,D4b,G4),(A2,E4,A4),(D2,A3,D4), # bars 5-8
311 (D2,A3,D4),(G2,D4b,G4),(D2,A3,D4),(G2,D4b,G4), # bars 9-12
312 (A2,E4,A4),(A2,E4,A4),(D2,A3,D4),(D2,A3,D4), # bars 13-16
313 (D2,A3,D4),(G2,D4b,G4),(D2,A3,D4),(G2,D4b,G4), # bars 17-20
314 (D2,A3,D4),(G2,D4b,G4),(A2,E4,A4),(D2,A3,D4), # bars 21-24
315 (D2,A3,D4),(G2,D4b,G4),(D2,A3,D4),(A2,E4,A4), # bars 25-28
316 (Fs2,Cs4,Fs2+12),(G2,D4b,G4),(A2,E4,A4),(D2,A3,D4), # bars 29-32
317 ]
318
319 lh_notes: list[Note] = []
320 for i, (bass, c5, c8) in enumerate(bar_chords):
321 add_lh_bar(i * 3.0, bass, c5, c8)
322 lh_notes.extend(lh_phrases)
323 lh_phrases.clear()
324
325 # RH melody — the iconic floating line
326 # Starts on A4 (beat 3 of bar 1)
327 # fmt: off
328 rh_raw: list[tuple[int, float, float, int]] = [
329 # Bar 1 pickup
330 (69,2,1,65),
331 # Bar 2
332 (66,3,2,70),(64,5,1,62),
333 # Bar 3
334 (62,6,2,72),(61,8,1,60),
335 # Bar 4
336 (62,9,2,68),(64,11,1,62),
337 # Bar 5
338 (66,12,2,75),(64,14,1,65),
339 # Bar 6
340 (62,15,2,70),(61,17,1,62),
341 # Bar 7
342 (57,18,2,65),(59,20,1,60),
343 # Bar 8
344 (57,21,3,70),
345 # Bar 9
346 (69,24,2,68),(67,26,1,62),
347 # Bar 10
348 (66,27,2,72),(64,29,1,65),
349 # Bar 11
350 (62,30,2,70),(61,32,1,60),
351 # Bar 12
352 (62,33,3,68),
353 # Bar 13
354 (66,36,2,75),(69,38,1,68),
355 # Bar 14
356 (71,39,2,78),(69,41,1,70),
357 # Bar 15
358 (67,42,2,72),(66,44,1,65),
359 # Bar 16
360 (62,45,3,70),
361 # Second half — slight variation
362 (69,48,2,65),(67,50,1,60),
363 (66,51,2,70),(64,53,1,62),
364 (62,54,2,68),(61,56,1,58),
365 (62,57,3,65),
366 (66,60,2,72),(64,62,1,65),
367 (62,63,2,70),(61,65,1,60),
368 (57,66,2,65),(59,68,1,58),
369 (57,69,3,65),
370 # Gentle close
371 (69,72,2,62),(67,74,1,55),
372 (66,75,2,65),(64,77,1,58),
373 (62,78,2,60),(57,80,1,50),
374 (62,81,3,58),
375 # Final bars — pppp
376 (62,84,2,45),(61,86,1,38),
377 (57,87,3,40),
378 (62,90,3,35),
379 (57,93,3,30),
380 ]
381 # fmt: on
382
383 rh_notes = [Note(pitch=p, start=s, dur=d, vel=v, ch=0) for p,s,d,v in rh_raw]
384 t1 = _notes_to_track(rh_notes, name="Melody")
385 t2 = _notes_to_track(lh_notes, name="Accompaniment")
386 return _smf(bpm=52, time_sig=(3, 4), tracks=[t1, t2])
387
388
389 # ===========================================================================
390 # 4. Chopin — Nocturne Op. 9 No. 2 in Eb major
391 # ===========================================================================
392 # Frédéric Chopin (1810-1849) — Public Domain (>70 years since death).
393 # 12/8 at 66 BPM. LH: wide arpeggiated chords. RH: ornate cantabile melody.
394
395 def chopin_nocturne_op9_no2() -> bytes:
396 """Chopin Nocturne Op. 9 No. 2 — opening 12 bars in Eb major, 12/8."""
397 # In 12/8 at 66 BPM: quarter note = 1 beat, dotted-quarter = 1.5 beats
398 # 12/8 bar = 4 dotted quarter beats = 6 quarter beats per bar
399
400 Eb3, Bb3, Eb4, G4, Bb4 = 51, 58, 63, 67, 70 # Eb major
401 F3, C4, F4, Ab4 = 53, 60, 65, 68 # Fm
402 Bb2, F3b, Bb3b, D4 = 46, 53, 58, 62 # Bb major
403 Ab2, Eb3b, Ab3, C4b = 44, 51, 56, 60 # Ab major
404 Bb3c = 58
405
406 # LH arpeggiated pattern — low bass + wide spread chord (each beat)
407 # Each 12/8 bar = 6 quarter beats
408 lh_notes: list[Note] = []
409 lh_pattern: list[tuple[list[int], float]] = [
410 # (pitches, beat_start_per_bar)
411 ([Eb3-12, Bb3, Eb4, G4], 0.0), # Eb major
412 ([F3-12, C4, F4, Ab4], 6.0), # Fm
413 ([Bb2, F3b, Bb3b,D4], 12.0), # Bb
414 ([Eb3-12, Bb3, Eb4, G4], 18.0), # Eb
415 ([Ab2, Eb3b,Ab3, C4b], 24.0), # Ab
416 ([Bb2, F3b, Bb3b,D4], 30.0), # Bb
417 ([Eb3-12, Bb3, Eb4, G4], 36.0), # Eb
418 ([Eb3-12, Bb3, Eb4, G4], 42.0), # Eb
419 ([F3-12, C4, F4, Ab4], 48.0), # Fm
420 ([Bb2, F3b, Bb3b,D4], 54.0), # Bb7
421 ([Eb3-12, Bb3, Eb4, G4], 60.0), # Eb
422 ([Eb3-12, Bb3, Eb4, G4], 66.0), # Eb
423 ]
424 for pitches, bar_start in lh_pattern:
425 for beat_off in [0.0, 1.5, 3.0, 4.5]: # 4 dotted-quarter subdivisions
426 for j, p in enumerate(pitches):
427 delay = beat_off + j * 0.08 # slight roll-up arpeggio
428 lh_notes.append(Note(pitch=p, start=bar_start + delay, dur=1.3, vel=_hv(48, -j*3), ch=1))
429
430 # RH iconic melody — the famous cantabile theme
431 # fmt: off
432 rh_raw: list[tuple[int, float, float, int]] = [
433 # Bar 1 (Eb major ascending)
434 (63,0,0.5,72),(65,0.5,0.5,68),(67,1,1,75),(70,2,0.5,70),(68,2.5,0.5,65),
435 (67,3,1,72),(63,4,2,68),
436 # Bar 2
437 (67,6,0.5,75),(68,6.5,0.5,70),(70,7,1,78),(72,8,0.5,72),(70,8.5,0.5,68),
438 (68,9,1,70),(67,10,2,65),
439 # Bar 3
440 (70,12,0.5,75),(68,12.5,0.5,70),(67,13,1,78),(70,14,0.5,72),(68,14.5,0.5,65),
441 (67,15,1,70),(63,16,2,68),
442 # Bar 4
443 (58,18,0.5,65),(60,18.5,0.5,60),(63,19,1,72),(65,20,0.5,68),(67,20.5,0.5,62),
444 (63,21,3,70),
445 # Bar 5 — more ornate
446 (63,24,0.5,72),(65,24.5,0.25,68),(67,24.75,0.25,65),(70,25,1,78),
447 (72,26,0.5,72),(70,26.5,0.5,68),(68,27,1,70),(67,28,2,65),
448 # Bar 6
449 (67,30,0.5,75),(68,30.5,0.5,70),(70,31,1,80),(72,32,0.5,75),(70,32.5,0.5,68),
450 (68,33,1,72),(67,34,2,65),
451 # Bar 7
452 (70,36,0.5,78),(72,36.5,0.5,72),(74,37,1,82),(75,38,0.5,75),(74,38.5,0.5,68),
453 (72,39,1,72),(70,40,2,68),
454 # Bar 8 — climax
455 (75,42,1,85),(74,43,0.5,80),(72,43.5,0.5,75),(70,44,1,78),(68,45,1,72),(67,46,2,70),
456 # Bar 9
457 (63,48,0.5,68),(65,48.5,0.5,65),(67,49,1,72),(70,50,0.5,68),(68,50.5,0.5,62),
458 (67,51,1,68),(63,52,2,65),
459 # Bar 10
460 (67,54,0.5,70),(68,54.5,0.5,65),(70,55,1,75),(72,56,0.5,70),(70,56.5,0.5,65),
461 (68,57,1,68),(67,58,2,62),
462 # Bar 11
463 (63,60,2,68),(65,62,1,62),(67,63,3,70),
464 # Bar 12 — ending phrase pppp
465 (63,66,3,55),(60,69,3,45),
466 ]
467 # fmt: on
468
469 rh_notes = [Note(pitch=p, start=s, dur=d, vel=v, ch=0) for p,s,d,v in rh_raw]
470 t1 = _notes_to_track(rh_notes, name="Melody")
471 t2 = _notes_to_track(lh_notes, name="Accompaniment")
472 return _smf(bpm=66, time_sig=(12, 8), tracks=[t1, t2])
473
474
475 # ===========================================================================
476 # 5. Beethoven — Moonlight Sonata Op. 27 No. 2, Mvt. I
477 # ===========================================================================
478 # Ludwig van Beethoven (1770-1827) — Public Domain.
479 # C# minor, 4/4 ("Alla breve"), Adagio sostenuto at 54 BPM.
480 # Famous triplet arpeggio pattern in LH, melody in RH.
481
482 def moonlight_sonata_mvt1() -> bytes:
483 """Beethoven Moonlight Sonata Mvt. I — 16 bars, C# minor, 54 BPM."""
484 # Triplet pattern: each beat subdivided into 3 — groups of 12 triplet 8ths per bar
485 # In quarter-note beats: each triplet 8th = 1/3 beat
486
487 # MIDI: C1=24, C2=36, C3=48, C4=60, C5=72
488 # C#2=37, G#2=44, E3=52, G#3=56, C#4=61, E4=64, G#4=68, A4=69, B4=71
489 Cs2 = 37; Gs2 = 44; E3 = 52; Gs3 = 56; Cs4 = 61; E4 = 64; Gs4 = 68
490 As = 57; B = 59; Fs = 54; A = 57
491
492 # LH triplet arpeggio — pattern per bar
493 # C# minor: Cs2-Gs3-Cs4 alternating patterns
494 lh_patterns: list[list[int]] = [
495 [Cs2, Gs3, Cs4], # bar 1: Csm
496 [Cs2, Gs3, Cs4], # bar 2: Csm
497 [37-12, 52, 56], # bar 3: Csm/E
498 [37-12, 47, 56], # bar 4: C# diminished
499 [37-12, 52, 57], # bar 5: F# minor
500 [37-12, 52, 57], # bar 6: F# minor
501 [37, 47, 56], # bar 7: C# dim7
502 [37, 52, Gs3], # bar 8: Cs m
503 [37, Gs2, Cs4], # bar 9: Csm
504 [37-12, 49, 54], # bar 10: A major
505 [37-12, 49, 54], # bar 11: A major
506 [35-12, 47, 54], # bar 12: B major
507 [37, Gs2, Cs4], # bar 13: Csm
508 [37-12, Gs2, E3], # bar 14: C# diminished
509 [37-12, Gs2, Cs4], # bar 15: Csm
510 [37-12, Gs2, Cs4], # bar 16: Csm final
511 ]
512
513 lh_notes: list[Note] = []
514 for bar_i, pattern in enumerate(lh_patterns):
515 bar_start = bar_i * 4.0
516 for beat in range(4):
517 beat_start = bar_start + beat
518 for trip in range(3):
519 p = pattern[trip % len(pattern)]
520 t = beat_start + trip / 3.0
521 vel = _hv(45, 8 if trip == 0 and beat == 0 else 0)
522 lh_notes.append(Note(pitch=p, start=t, dur=0.28, vel=vel, ch=1))
523
524 # RH melody — the iconic upper voice over the triplets
525 # fmt: off
526 rh_raw: list[tuple[int, float, float, int]] = [
527 # Bar 1 — slow unfolding
528 (Cs4+12, 3, 1, 60),
529 # Bar 2
530 (Cs4+12, 4, 2, 65),(B+60, 6, 2, 58),
531 # Bar 3
532 (Cs4+12, 8, 4, 62),
533 # Bar 4
534 (Cs4+12, 12, 2, 60),(B+60, 14, 2, 55),
535 # Bar 5 — F# minor region
536 (Cs4+12, 16, 1, 68),(B+60, 17, 1, 62),(As+60, 18, 2, 65),
537 # Bar 6
538 (Gs4, 20, 2, 70),(Fs+60, 22, 2, 65),
539 # Bar 7 — development
540 (E4+12, 24, 1, 72),(Fs+60, 25, 1, 68),(Gs4, 26, 2, 70),
541 # Bar 8
542 (As+60, 28, 2, 75),(Gs4, 30, 2, 68),
543 # Bar 9 — return to Csm
544 (Cs4+12, 32, 4, 65),
545 # Bar 10 — A major colour
546 (E4+12, 36, 2, 68),(Cs4+12, 38, 2, 62),
547 # Bar 11
548 (A+60, 40, 2, 70),(Cs4+12, 42, 2, 65),
549 # Bar 12 — B major approach
550 (B+60, 44, 2, 72),(Fs+60, 46, 2, 65),
551 # Bar 13 — return
552 (Cs4+12, 48, 4, 68),
553 # Bar 14
554 (E4+12, 52, 2, 65),(Gs4, 54, 2, 60),
555 # Bar 15
556 (Cs4+12, 56, 2, 62),(B+60, 58, 2, 55),
557 # Bar 16 — final
558 (Cs4+12, 60, 4, 55),
559 ]
560 # fmt: on
561
562 rh_notes = [Note(pitch=p, start=s, dur=d, vel=v, ch=0) for p,s,d,v in rh_raw]
563 t1 = _notes_to_track(rh_notes, name="Melody")
564 t2 = _notes_to_track(lh_notes, name="Triplet Arpeggio")
565 return _smf(bpm=54, time_sig=(4, 4), tracks=[t1, t2])
566
567
568 # ===========================================================================
569 # 6. Original — Neo-Soul Groove in F# minor (multi-track)
570 # ===========================================================================
571 # Original composition. Not derived from any copyrighted work.
572 # 4/4 at 92 BPM. Tracks: Piano/Rhodes, Bass, Drums.
573
574 def neo_soul_groove() -> bytes:
575 """Original neo-soul groove in F# minor — 16 bars, 92 BPM, multi-track."""
576 Fs3, Gs3, A3, B3, Cs4, D4, E4, Fs4 = 54, 56, 57, 59, 61, 62, 64, 66
577 Fs2, Cs3, A2, E2 = 42, 49, 45, 40
578
579 # Piano/Rhodes — chord comping with characteristic neo-soul voicings
580 piano_notes: list[Note] = []
581 # F# minor 9 voicing: F#-A-C#-E (rootless: A-C#-E, adding 9th G#)
582 comp_pattern = [
583 # beat, pitches (staggered for realism), vel
584 (0.0, [Gs3, Cs4, E4], 68),
585 (0.5, [Gs3, Cs4, E4], 52),
586 (1.75, [A3, D4, Fs4], 70),
587 (2.0, [A3, D4, Fs4], 58),
588 (2.5, [Gs3, Cs4, E4], 65),
589 (3.0, [Gs3, Cs4, E4], 50),
590 (3.75, [A3, D4, Fs4], 72),
591 ]
592 for bar in range(16):
593 bar_start = bar * 4.0
594 # Vary chord colour slightly each 4 bars
595 for beat_off, pitches, vel in comp_pattern:
596 for j, p in enumerate(pitches):
597 piano_notes.append(Note(pitch=p, start=bar_start + beat_off + j*0.02, dur=0.45, vel=_hv(vel, -bar%4), ch=0))
598
599 # Bass — syncopated neo-soul bassline
600 bass_raw: list[tuple[int, float]] = [
601 # F# root-movement pattern
602 (Fs2, 0.0), (Fs2, 0.5), (Cs3, 1.0), (A2, 2.0), (Fs2, 2.5), (E2, 3.5),
603 (Fs2, 4.0), (Fs2, 4.75),(Cs3, 5.0), (E2, 6.0), (A2, 6.5), (Fs2, 7.5),
604 ]
605 bass_notes: list[Note] = []
606 for bar in range(16):
607 for pitch, beat in bass_raw[:6 + (bar % 2)*2]:
608 bass_notes.append(Note(pitch=pitch, start=bar*4+beat, dur=0.35, vel=_hv(75, -10), ch=2))
609
610 # Drums — Channel 9, GM percussion
611 KICK, SNARE, HAT_C, HAT_O = 36, 38, 42, 46
612 drum_pattern: list[tuple[int, float, int]] = [
613 (KICK, 0.0, 85), (HAT_C, 0.0, 60),
614 (HAT_C, 0.5, 52),
615 (SNARE, 1.0, 80), (HAT_C, 1.0, 58),
616 (HAT_C, 1.5, 50),
617 (KICK, 2.0, 78), (HAT_C, 2.0, 60),
618 (KICK, 2.5, 65),
619 (SNARE, 3.0, 82), (HAT_C, 3.0, 58),
620 (HAT_O, 3.5, 55),
621 (HAT_C, 3.75, 48),
622 ]
623 drum_notes: list[Note] = []
624 for bar in range(16):
625 for pitch, beat, vel in drum_pattern:
626 drum_notes.append(Note(pitch=pitch, start=bar*4+beat, dur=0.2, vel=vel, ch=9))
627
628 t1 = _notes_to_track(piano_notes, name="Rhodes")
629 t2 = _notes_to_track(bass_notes, name="Bass")
630 t3 = _notes_to_track(drum_notes, name="Drums")
631 return _smf(bpm=92, time_sig=(4, 4), tracks=[t1, t2, t3])
632
633
634 # ===========================================================================
635 # 7. Original — Modal Jazz Sketch in D Dorian (multi-track)
636 # ===========================================================================
637 # Original composition. 4/4 at 120 BPM.
638 # Tracks: Piano, Walking Bass, Brushed Drums.
639
640 def modal_jazz_sketch() -> bytes:
641 """Original modal jazz sketch in D Dorian — 12 bars, 120 BPM."""
642 D3, E3, F3, G3, A3, B3, C4, D4 = 50, 52, 53, 55, 57, 59, 60, 62
643 D2, A2, G2, C2, E2, F2 = 38, 45, 43, 36, 40, 41
644
645 # Piano comping — shell voicings (3rd + 7th)
646 comp: list[Note] = []
647 voicings = [
648 (0.0, [F3, C4, E3], 68), # Dm7 — F + C = 3rd+7th
649 (1.0, [F3, C4], 55),
650 (1.5, [F3, B3], 60), # sus variation
651 (2.0, [F3, C4, E3], 65),
652 (3.0, [G3, D4], 70), # G7sus → Dm
653 (3.5, [F3, C4], 58),
654 ]
655 for bar in range(12):
656 for beat_off, pitches, vel in voicings:
657 for j, p in enumerate(pitches):
658 comp.append(Note(pitch=p, start=bar*4+beat_off+j*0.015, dur=0.42, vel=_hv(vel, 0), ch=0))
659
660 # Walking bass — Dorian
661 walk_pitches = [D2, E2, F2, G2, A2, G2, F2, E2]
662 bass: list[Note] = []
663 for bar in range(12):
664 for beat, p in enumerate(walk_pitches[:4]):
665 bass.append(Note(pitch=p + (bar % 3) * 2, start=bar*4+beat, dur=0.92, vel=_hv(72, -8), ch=2))
666
667 # Brushed snare feel
668 KICK, SNARE, RD, HAT = 36, 38, 51, 42
669 brush_pattern = [
670 (KICK, 0.0, 75), (RD, 0.0, 55),
671 (RD, 0.5, 48),
672 (SNARE, 1.0, 68), (RD, 1.0, 52),
673 (RD, 1.5, 45),
674 (KICK, 2.0, 70), (RD, 2.0, 55),
675 (KICK, 2.5, 60),
676 (SNARE, 3.0, 72), (RD, 3.0, 52),
677 (RD, 3.5, 48), (RD, 3.75, 44),
678 ]
679 drums: list[Note] = []
680 for bar in range(12):
681 for pitch, beat, vel in brush_pattern:
682 drums.append(Note(pitch=pitch, start=bar*4+beat, dur=0.18, vel=vel, ch=9))
683
684 t1 = _notes_to_track(comp, name="Piano")
685 t2 = _notes_to_track(bass, name="Bass")
686 t3 = _notes_to_track(drums, name="Drums")
687 return _smf(bpm=120, time_sig=(4, 4), tracks=[t1, t2, t3])
688
689
690 # ===========================================================================
691 # 8. Original — Afrobeat Pulse in G major (multi-track)
692 # ===========================================================================
693 # Original composition. 12/8 at 120 BPM.
694 # Tracks: Piano, Bass, Djembe (perc).
695
696 def afrobeat_pulse() -> bytes:
697 """Original afrobeat groove in G major — 8 bars, 12/8 at 120 BPM."""
698 G3, A3, B3, D4, E4 = 55, 57, 59, 62, 64
699 G2, D3, A2, C3 = 43, 50, 45, 48
700
701 # Piano — interlocking offbeat pattern (12/8 = 4 dotted quarters)
702 piano: list[Note] = []
703 # 12/8: each dotted quarter = 1.5 quarter beats
704 offbeat_comp = [
705 (0.5, [G3, B3, D4], 70),
706 (1.5, [A3, D4, E4], 65),
707 (2.5, [G3, B3, D4], 72),
708 (3.5, [A3, D4, E4], 68),
709 (4.5, [G3, B3, D4], 65),
710 (5.0, [A3, D4, E4], 60),
711 ]
712 for bar in range(8):
713 for beat_off, pitches, vel in offbeat_comp:
714 for j, p in enumerate(pitches):
715 piano.append(Note(pitch=p, start=bar*6+beat_off+j*0.02, dur=0.4, vel=_hv(vel, 0), ch=0))
716
717 # Bass — "one-drop" anchored
718 bass_pts = [(G2, 0), (D3, 1.5), (G2, 3), (A2, 4.5)]
719 bass: list[Note] = []
720 for bar in range(8):
721 for p, b in bass_pts:
722 bass.append(Note(pitch=p, start=bar*6+b, dur=1.2, vel=_hv(78, -5), ch=2))
723
724 # Djembe — channel 9, tone/slap/bass pattern
725 # GM: open hi-hat=46 (djembe tone), snare=38 (slap), bass drum=35 (bass tone)
726 TONE, SLAP, BASS_TONE = 46, 38, 35
727 djem_pattern = [
728 (BASS_TONE, 0.0, 88), (TONE, 0.5, 75), (TONE, 1.0, 65),
729 (SLAP, 1.5, 80), (TONE, 2.0, 68), (TONE, 2.5, 60),
730 (BASS_TONE, 3.0, 85), (TONE, 3.5, 72), (TONE, 4.0, 62),
731 (SLAP, 4.5, 78), (TONE, 5.0, 65), (TONE, 5.5, 55),
732 ]
733 djembe: list[Note] = []
734 for bar in range(8):
735 for pitch, beat, vel in djem_pattern:
736 djembe.append(Note(pitch=pitch, start=bar*6+beat, dur=0.18, vel=vel, ch=9))
737
738 t1 = _notes_to_track(piano, name="Piano")
739 t2 = _notes_to_track(bass, name="Bass")
740 t3 = _notes_to_track(djembe, name="Djembe")
741 return _smf(bpm=120, time_sig=(12, 8), tracks=[t1, t2, t3])
742
743
744 # ===========================================================================
745 # 9. Original — Chanson Minimale in A major (solo piano)
746 # ===========================================================================
747
748 def chanson_minimale() -> bytes:
749 """Original chanson minimale in A major — 16 bars, 52 BPM, solo piano."""
750 A3, B3, Cs4, D4, E4, Fs4, Gs4, A4 = 57, 59, 61, 62, 64, 66, 68, 69
751 A2, E3, A3b = 45, 52, 57
752
753 notes: list[Note] = []
754 # Ostinato LH: A2-E3-A3 waltz (3/4)
755 for bar in range(16):
756 s = bar * 3.0
757 notes.append(Note(A2, s, 0.85, 55, 1))
758 notes.append(Note(E3, s+1, 1.85, 42, 1))
759 notes.append(Note(A3b, s+1, 1.85, 38, 1))
760
761 # RH melody — simple, folk-like
762 mel_raw: list[tuple[int, float, float, int]] = [
763 (E4,0,1,72),(Fs4,1,0.5,68),(E4,1.5,0.5,65),(Cs4,2,1,70),
764 (D4,3,1,68),(E4,4,1,72),(A3,5,1,65),
765 (A3,6,2,70),(B3,8,1,62),
766 (Cs4,9,1,68),(D4,10,1,72),(E4,11,1,68),
767 (Cs4,12,1,70),(B3,13,0.5,65),(A3,13.5,0.5,62),(A3,14,1,68),
768 (A3,15,2,65),(B3,17,1,55),
769 # Second half — more development
770 (E4,18,1,75),(Fs4,19,0.5,70),(Gs4,19.5,0.5,68),(A4,20,1,80),
771 (A4,21,1,75),(Gs4,22,0.5,70),(Fs4,22.5,0.5,65),(E4,23,1,68),
772 (Cs4,24,2,70),(D4,26,1,62),
773 (E4,27,1,65),(Cs4,28,1,68),(A3,29,1,62),
774 (D4,30,1,70),(E4,31,0.5,68),(D4,31.5,0.5,65),(Cs4,32,1,72),
775 (B3,33,1,65),(A3,34,2,70),
776 # Coda pppp
777 (E4,36,1,50),(Cs4,37,1,45),(A3,38,1,40),(A3,39,3,35),
778 (A3,42,3,28),
779 (A3,45,3,20),
780 ]
781 notes.extend(Note(p,s,d,v,0) for p,s,d,v in mel_raw)
782
783 track = _notes_to_track(notes, name="Piano")
784 return _smf(bpm=52, time_sig=(3, 4), tracks=[track])
785
786
787 # ===========================================================================
788 # 10. Original — Ambient Textures in Eb major (slow evolving pads)
789 # ===========================================================================
790
791 def ambient_textures() -> bytes:
792 """Original ambient textures in Eb major — 32 bars, 60 BPM, pads."""
793 Eb3, G3, Bb3, Eb4, G4 = 51, 55, 58, 63, 67
794 Ab3, C4, Eb4b, F3, C3 = 56, 60, 63, 53, 48
795
796 notes: list[Note] = []
797 # Long sustained chords — whole notes (4 beats each) with slow swell
798 chord_progression = [
799 ([Eb3, G3, Bb3, Eb4], 0), # Eb major
800 ([Eb3, G3, Bb3, G4], 4), # Eb add9
801 ([Ab3, C4, Eb4b], 8), # Ab major
802 ([F3, Bb3, Eb4b], 12), # Bb sus
803 ([Eb3, G3, Bb3, Eb4], 16), # Eb major return
804 ([Ab3, C4, Eb4b], 20), # Ab
805 ([F3, Bb3, Eb4b], 24), # Bb sus
806 ([Eb3, G3, Bb3], 28), # Eb final
807 ]
808 velocities = [45, 55, 60, 55, 50, 58, 52, 40] # gentle swell
809
810 for (pitches, start), vel in zip(chord_progression * 4, velocities * 4):
811 for j, p in enumerate(pitches):
812 for rep in range(4): # 4 repetitions of progression
813 notes.append(Note(p, start + rep*32, 3.8, _hv(vel, j), 0))
814
815 # Sparse melodic fragments — high register
816 mel_frags: list[tuple[int, float, float, int]] = [
817 (Eb4+12, 8, 2, 35),(G4+12, 10, 3, 30),
818 (Bb3+12, 20, 3, 32),(Eb4+12, 24, 4, 28),
819 (G4+12, 36, 2, 30),(Eb4+12, 40, 3, 28),
820 (G4+12, 56, 2, 25),(Eb4+12, 60, 4, 22),
821 ]
822 for p,s,d,v in mel_frags:
823 notes.append(Note(p,s,d,v,0))
824
825 track = _notes_to_track(notes, name="Pads")
826 return _smf(bpm=60, time_sig=(4, 4), tracks=[track])
827
828
829 # ===========================================================================
830 # CODE_FILES — no longer used by seed_v2.py.
831 #
832 # seed_v2.py clones the real GitHub repos at seed time and imports their
833 # actual file trees and git histories. This stub keeps the module importable
834 # without breaking any other script that might reference CODE_FILES.
835 # ===========================================================================
836
837 CODE_FILES: dict[str, dict[str, str]] = {
838 # Real repos are cloned by seed_v2._import_github_repo(); nothing here.
839 # github.com/cgcardona/muse → repo-v2-muse-vcs-00001
840 # github.com/cgcardona/agentception → repo-v2-agentcept-00001
841 # github.com/cgcardona/musehub → repo-v2-musehub-src-001
842 }
843
844 # ---------------------------------------------------------------------------
845 # The rest of this file only defines MIDI generators. The large synthetic
846 # code blobs that previously lived here have been removed; actual file content
847 # now comes directly from the upstream git repos.
848 # ---------------------------------------------------------------------------
849
850 # ===========================================================================
851 # Dispatch table — maps a string key to a generator function
852 # ===========================================================================
853
854 MIDI_GENERATORS: dict[str, callable] = {
855 "wtc_prelude_c": wtc_prelude_c_major,
856 "bach_minuet_g": bach_minuet_g,
857 "gymnopedie_no1": gymnopedie_no1,
858 "chopin_nocturne_op9": chopin_nocturne_op9_no2,
859 "moonlight_mvt1": moonlight_sonata_mvt1,
860 "neo_soul": neo_soul_groove,
861 "modal_jazz": modal_jazz_sketch,
862 "afrobeat": afrobeat_pulse,
863 "chanson": chanson_minimale,
864 "ambient": ambient_textures,
865 }