mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92ade3ee3d | |||
| 833867329e | |||
| 93b9fe9ced | |||
| 88a1171bbe | |||
| 3ca0842b7a | |||
| 00de5eb354 | |||
| d2b0c6f329 | |||
| 76612682f1 | |||
| ce480858e9 | |||
| 70efb0ad40 | |||
| bf6deaab64 | |||
| 7c792c0a2a | |||
| bf8d4b9a77 | |||
| d2d5115c8a | |||
| 3cdd98b158 |
@@ -2,6 +2,36 @@
|
||||
|
||||
All notable changes to PyTheory are documented here.
|
||||
|
||||
## 0.36.1
|
||||
|
||||
- **7 new instrument synths:** pedal steel guitar, theremin, kalimba/thumb
|
||||
piano, steel drum/pan, accordion (musette reeds), didgeridoo (drone +
|
||||
shifting formants), bagpipes (chanter reed)
|
||||
- **9 new demo moods** in ``pytheory demo``: Theremin Noir, Caribbean,
|
||||
Accordion Waltz, Kalimba Dreams, Outback Drone, Highland, Nashville
|
||||
Tears, Tabla Fusion
|
||||
- Improved existing songs with dedicated instrument synths
|
||||
- 41 synth waveforms, 26+ songs, 21 demo moods
|
||||
|
||||
## 0.36.0
|
||||
|
||||
- **Banjo synth** — steel strings on drum-head body, nasal twang,
|
||||
fast decay with membrane resonance
|
||||
- **Mandolin synth** — paired steel strings (natural chorus from
|
||||
doubled courses), bright body resonance
|
||||
- **Ukulele synth** — nylon strings, small mid-heavy body, shorter
|
||||
sustain than guitar
|
||||
- **Cajón drums** — bass (woody box thump), slap (snare wire buzz),
|
||||
tap (ghost note). 3 patterns: cajon, cajon rumba, cajon folk
|
||||
- **Vocal/formant synth** — LF glottal model, 5 Peterson & Barney
|
||||
formant peaks, jitter/shimmer, consonant onsets, per-note lyrics.
|
||||
Presets: vocal, choir
|
||||
- **Granular synthesis** — grain cloud engine with scatter, pitch
|
||||
variation, Hanning windows. Presets: granular_pad, granular_texture
|
||||
- **Strum sweep** — subtle grace notes before chord hit for natural
|
||||
strum feel on all fretboard instruments
|
||||
- Mandola preset, 34 synth waveforms, 26 songs
|
||||
|
||||
## 0.35.0
|
||||
|
||||
- **8.5x faster import** — dropped pytuning/sympy, lazy-load scipy.
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ What's Inside
|
||||
numbers), scale recommendation, modulation, voice leading
|
||||
- **Sequencing** — Score, Parts, arpeggiator, legato/glide, velocity,
|
||||
swing, humanize, tempo changes, song sections with repeat
|
||||
- **Synthesis** — 30 waveforms (including Karplus-Strong pluck, Hammond organ,
|
||||
- **Synthesis** — 41 waveforms (including Karplus-Strong pluck, Hammond organ,
|
||||
bowed string, and 14 dedicated instrument synths), 10 envelopes, 40+
|
||||
instrument presets, configurable FM, sub-oscillator, noise layer, filter
|
||||
envelope, velocity-to-brightness, analog oscillator drift, detune, stereo
|
||||
|
||||
+487
-193
@@ -52,24 +52,26 @@ def bossa_nova_girl():
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.45, pan=0.3,
|
||||
delay=0.25, delay_time=0.32, delay_feedback=0.35,
|
||||
reverb=0.2, reverb_type="plate")
|
||||
reverb=0.2, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.45, pan=0.0, lowpass=600)
|
||||
volume=0.45, pan=0.0, lowpass=600,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]:
|
||||
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("E5",.67),("D5",.33),("C5",.67),("B4",.33),("A4",1),("C5",.67),("E5",.33),
|
||||
("D5",.67),("C5",.33),("A4",1),(None,1),
|
||||
("F5",.67),("E5",.33),("D5",.67),("C5",.33),("D5",1),("F5",.67),("A5",.33),
|
||||
("G5",.67),("F5",.33),("D5",1),(None,1),
|
||||
("G#5",.67),("F5",.33),("E5",.67),("D5",.33),("E5",1),(None,.5),("B4",.5),
|
||||
("D5",.67),("E5",.33),("G#4",1),(None,1),
|
||||
("A4",1),("C5",.67),("E5",.33),("A5",1.5),(None,.5),
|
||||
("G5",.67),("E5",.33),("C5",.67),("A4",.33),("A4",2),
|
||||
for n, d, v in [
|
||||
("E5",.67,85),("D5",.33,75),("C5",.67,80),("B4",.33,70),("A4",1,85),("C5",.67,78),("E5",.33,82),
|
||||
("D5",.67,78),("C5",.33,72),("A4",1,80),(None,1,0),
|
||||
("F5",.67,88),("E5",.33,78),("D5",.67,82),("C5",.33,72),("D5",1,80),("F5",.67,85),("A5",.33,90),
|
||||
("G5",.67,82),("F5",.33,75),("D5",1,78),(None,1,0),
|
||||
("G#5",.67,92),("F5",.33,78),("E5",.67,85),("D5",.33,75),("E5",1,82),(None,.5,0),("B4",.5,72),
|
||||
("D5",.67,78),("E5",.33,82),("G#4",1,75),(None,1,0),
|
||||
("A4",1,80),("C5",.67,78),("E5",.33,85),("A5",1.5,95),(None,.5,0),
|
||||
("G5",.67,82),("E5",.33,78),("C5",.67,75),("A4",.33,70),("A4",2,85),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["A2","E2","A2","C3","D2","A2","D2","F2",
|
||||
"E2","B2","E2","G#2","A2","E2","A2","C3",
|
||||
@@ -95,32 +97,34 @@ def bebop_in_bb():
|
||||
volume=0.4, pan=0.25,
|
||||
lowpass=4000, lowpass_q=1.1,
|
||||
delay=0.15, delay_time=0.19, delay_feedback=0.25,
|
||||
reverb=0.15, reverb_type="plate")
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.4, pan=0.0, lowpass=500)
|
||||
volume=0.4, pan=0.0, lowpass=500,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["Bb", "Gm", "Cm", "F7"] * 2:
|
||||
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("Bb4",.67),("D5",.33),("F5",.67),("D5",.33),
|
||||
("Bb4",.67),("C5",.33),("D5",.67),("F5",.33),
|
||||
("G5",.67),("F5",.33),("D5",.67),("Bb4",.33),
|
||||
("A4",.67),("Bb4",.33),("D5",.67),("G4",.33),
|
||||
("C5",.67),("Eb5",.33),("G5",.67),("Eb5",.33),
|
||||
("C5",.67),("D5",.33),("Eb5",.67),("F5",.33),
|
||||
("A5",.67),("G5",.33),("F5",.67),("Eb5",.33),
|
||||
("D5",.67),("C5",.33),("A4",.5),(None,.5),
|
||||
("Bb4",1),("D5",.67),("F5",.33),
|
||||
("G5",.67),("F5",.33),("D5",.67),("Bb4",.33),
|
||||
("Bb5",.67),("A5",.33),("G5",.67),("F5",.33),
|
||||
("Eb5",.67),("D5",.33),("Bb4",.67),("G4",.33),
|
||||
("C5",.5),(None,.5),("Eb5",.67),("G5",.33),
|
||||
("F5",.67),("Eb5",.33),("D5",.67),("C5",.33),
|
||||
("A4",.67),("C5",.33),("Eb5",.67),("F5",.33),
|
||||
("G5",.67),("A5",.33),("Bb5",1),
|
||||
for n, d, v in [
|
||||
("Bb4",.67,82),("D5",.33,75),("F5",.67,88),("D5",.33,78),
|
||||
("Bb4",.67,80),("C5",.33,72),("D5",.67,82),("F5",.33,85),
|
||||
("G5",.67,90),("F5",.33,78),("D5",.67,82),("Bb4",.33,72),
|
||||
("A4",.67,78),("Bb4",.33,72),("D5",.67,85),("G4",.33,70),
|
||||
("C5",.67,82),("Eb5",.33,78),("G5",.67,92),("Eb5",.33,80),
|
||||
("C5",.67,78),("D5",.33,75),("Eb5",.67,82),("F5",.33,85),
|
||||
("A5",.67,95),("G5",.33,82),("F5",.67,85),("Eb5",.33,78),
|
||||
("D5",.67,80),("C5",.33,72),("A4",.5,75),(None,.5,0),
|
||||
("Bb4",1,85),("D5",.67,80),("F5",.33,88),
|
||||
("G5",.67,90),("F5",.33,82),("D5",.67,78),("Bb4",.33,72),
|
||||
("Bb5",.67,95),("A5",.33,85),("G5",.67,88),("F5",.33,80),
|
||||
("Eb5",.67,82),("D5",.33,75),("Bb4",.67,78),("G4",.33,70),
|
||||
("C5",.5,78),(None,.5,0),("Eb5",.67,82),("G5",.33,88),
|
||||
("F5",.67,85),("Eb5",.33,78),("D5",.67,82),("C5",.33,75),
|
||||
("A4",.67,78),("C5",.33,80),("Eb5",.67,85),("F5",.33,88),
|
||||
("G5",.67,92),("A5",.33,88),("Bb5",1,95),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["Bb2","D3","F3","A3","G3","F3","D3","Bb2",
|
||||
"C3","Eb3","G3","Bb3","F3","A3","C4","Eb3",
|
||||
@@ -146,29 +150,31 @@ def salsa_descarga():
|
||||
lead = score.part("lead", instrument="trumpet",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.2, delay_time=0.167, delay_feedback=0.3,
|
||||
reverb=0.15, reverb_type="plate")
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="synth_bass",
|
||||
volume=0.45, pan=0.0, lowpass=500, lowpass_q=1.3)
|
||||
volume=0.45, pan=0.0, lowpass=500, lowpass_q=1.3,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["Em7b5", "A7", "Dm7", "Bbmaj7"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("E5",.67),("G5",.33),("Bb5",.67),("A5",.33),
|
||||
("G5",.67),("F5",.33),("E5",.67),("D5",.33),
|
||||
("C#5",.67),("D5",.33),("E5",.67),("G5",.33),
|
||||
("F5",.67),("E5",.33),("C#5",.5),(None,.5),
|
||||
("D5",.5),(None,.17),("F5",.67),("A5",.33),
|
||||
("G5",.67),("F5",.33),("E5",.67),("D5",.33),
|
||||
("Bb4",1),("D5",.67),("F5",.33),("A5",1),(None,1),
|
||||
("E5",.5),("F5",.5),("G5",.67),("A5",.33),
|
||||
("Bb5",.67),("A5",.33),("G5",.67),("E5",.33),
|
||||
("C#5",.67),("E5",.33),("A5",.67),("G5",.33),
|
||||
("F5",.67),("E5",.33),("C#5",.67),("A4",.33),
|
||||
("D5",1),("F5",.67),("A5",.33),("G5",.67),("F5",.33),("D5",1),(None,1),
|
||||
("Bb4",.67),("D5",.33),("F5",.67),("Bb5",.33),("A5",1.5),(None,.5),
|
||||
for n, d, v in [
|
||||
("E5",.67,85),("G5",.33,78),("Bb5",.67,92),("A5",.33,82),
|
||||
("G5",.67,85),("F5",.33,78),("E5",.67,82),("D5",.33,75),
|
||||
("C#5",.67,80),("D5",.33,75),("E5",.67,85),("G5",.33,80),
|
||||
("F5",.67,82),("E5",.33,78),("C#5",.5,75),(None,.5,0),
|
||||
("D5",.5,78),(None,.17,0),("F5",.67,85),("A5",.33,90),
|
||||
("G5",.67,88),("F5",.33,80),("E5",.67,82),("D5",.33,75),
|
||||
("Bb4",1,78),("D5",.67,82),("F5",.33,88),("A5",1,95),(None,1,0),
|
||||
("E5",.5,80),("F5",.5,82),("G5",.67,88),("A5",.33,85),
|
||||
("Bb5",.67,95),("A5",.33,85),("G5",.67,88),("E5",.33,78),
|
||||
("C#5",.67,80),("E5",.33,82),("A5",.67,92),("G5",.33,85),
|
||||
("F5",.67,82),("E5",.33,78),("C#5",.67,75),("A4",.33,70),
|
||||
("D5",1,82),("F5",.67,85),("A5",.33,92),("G5",.67,88),("F5",.33,80),("D5",1,78),(None,1,0),
|
||||
("Bb4",.67,75),("D5",.33,80),("F5",.67,88),("Bb5",.33,95),("A5",1.5,90),(None,.5,0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["E2","E3","A2","A3","D2","D3","Bb2","Bb3"] * 4:
|
||||
bass.add(n, Duration.QUARTER)
|
||||
@@ -192,23 +198,25 @@ def afrobeat_groove():
|
||||
volume=0.4, pan=0.3,
|
||||
lowpass=3000, lowpass_q=1.0,
|
||||
delay=0.2, delay_time=0.26, delay_feedback=0.3,
|
||||
reverb=0.15, reverb_type="plate")
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.5, pan=0.0, lowpass=500)
|
||||
volume=0.5, pan=0.0, lowpass=500,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["Em", "Am", "D", "C"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
riff = [("E5",.5),("G5",.5),("A5",.5),("G5",.5),
|
||||
("E5",.5),("D5",.5),("E5",1),
|
||||
("E5",.5),("G5",.5),("A5",.5),("B5",.5),
|
||||
("A5",.5),("G5",.5),("E5",1),
|
||||
(None,.5),("A5",.5),("G5",.5),("E5",.5),
|
||||
("D5",1),("E5",.5),("G5",.5),
|
||||
("A5",.5),("B5",.5),("A5",.5),("G5",.5),
|
||||
("E5",1.5),(None,.5)]
|
||||
for n, d in riff * 2:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
riff = [("E5",.5,82),("G5",.5,78),("A5",.5,88),("G5",.5,80),
|
||||
("E5",.5,78),("D5",.5,72),("E5",1,85),
|
||||
("E5",.5,80),("G5",.5,78),("A5",.5,88),("B5",.5,92),
|
||||
("A5",.5,85),("G5",.5,78),("E5",1,82),
|
||||
(None,.5,0),("A5",.5,85),("G5",.5,80),("E5",.5,75),
|
||||
("D5",1,78),("E5",.5,80),("G5",.5,82),
|
||||
("A5",.5,88),("B5",.5,92),("A5",.5,85),("G5",.5,80),
|
||||
("E5",1.5,82),(None,.5,0)]
|
||||
for n, d, v in riff * 2:
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["E2","E2","G2","A2","A2","G2","E2","D2",
|
||||
"D2","D2","F#2","A2","C3","C3","B2","G2"] * 2:
|
||||
@@ -234,24 +242,26 @@ def reggae_one_drop():
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.35, delay_time=0.5625, delay_feedback=0.45,
|
||||
reverb=0.3, reverb_type="cathedral")
|
||||
reverb=0.3, reverb_type="cathedral",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.55, pan=0.0, lowpass=400, lowpass_q=1.3)
|
||||
volume=0.55, pan=0.0, lowpass=400, lowpass_q=1.3,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["G", "C", "D", "C"] * 2:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("G5",1.5),(None,.5),("B5",1),("A5",1),
|
||||
("G5",2),("E5",1),("D5",1),
|
||||
("C5",1.5),(None,.5),("E5",1),("G5",1),
|
||||
("A5",1.5),(None,.5),("G5",2),
|
||||
("D5",1.5),(None,.5),("E5",1),("G5",1),
|
||||
("A5",2),("B5",1),("A5",1),
|
||||
("G5",1.5),(None,.5),("E5",1),("D5",1),
|
||||
("G4",3),(None,1),
|
||||
for n, d, v in [
|
||||
("G5",1.5,85),(None,.5,0),("B5",1,90),("A5",1,82),
|
||||
("G5",2,80),("E5",1,75),("D5",1,72),
|
||||
("C5",1.5,78),(None,.5,0),("E5",1,80),("G5",1,85),
|
||||
("A5",1.5,88),(None,.5,0),("G5",2,82),
|
||||
("D5",1.5,78),(None,.5,0),("E5",1,80),("G5",1,85),
|
||||
("A5",2,90),("B5",1,92),("A5",1,85),
|
||||
("G5",1.5,82),(None,.5,0),("E5",1,78),("D5",1,72),
|
||||
("G4",3,75),(None,1,0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["G2","G2","B2","D3","C3","C3","E3","G3",
|
||||
"D3","D3","F#3","A3","C3","C3","E3","G3",
|
||||
@@ -278,30 +288,32 @@ def funk_workout():
|
||||
volume=0.4, pan=0.35,
|
||||
lowpass=3500, lowpass_q=1.5,
|
||||
delay=0.15, delay_time=0.15, delay_feedback=0.25,
|
||||
reverb=0.1, reverb_type="plate")
|
||||
reverb=0.1, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="synth_bass",
|
||||
volume=0.5, pan=0.0, lowpass=600, lowpass_q=1.2)
|
||||
volume=0.5, pan=0.0, lowpass=600, lowpass_q=1.2,
|
||||
humanize=0.2, reverb=0.15)
|
||||
|
||||
for sym in ["Em", "Am", "D", "B7"] * 2:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("E5",.25),("E5",.25),(None,.25),("G5",.25),
|
||||
(None,.25),("A5",.25),("G5",.25),("E5",.25),
|
||||
("D5",.5),("E5",.5),(None,.5),("B4",.5),
|
||||
("E5",.25),("E5",.25),(None,.25),("G5",.25),
|
||||
(None,.25),("A5",.25),("B5",.25),("A5",.25),
|
||||
("G5",.5),("E5",.5),(None,1),
|
||||
("A4",.25),("C5",.25),("E5",.25),("A5",.25),
|
||||
("G5",.5),("E5",.5),(None,.5),("C5",.5),
|
||||
("A4",.5),("C5",.5),("D5",.5),("E5",.5),
|
||||
("E5",1),(None,1),
|
||||
("D5",.25),("F#5",.25),("A5",.25),("D5",.25),
|
||||
("F#5",.5),("D5",.5),("A4",.5),("D5",.5),
|
||||
("D#5",.5),("F#5",.5),("B4",.5),("D#5",.5),
|
||||
("F#5",1),(None,1),
|
||||
for n, d, v in [
|
||||
("E5",.25,90),("E5",.25,78),(None,.25,0),("G5",.25,85),
|
||||
(None,.25,0),("A5",.25,88),("G5",.25,82),("E5",.25,78),
|
||||
("D5",.5,80),("E5",.5,85),(None,.5,0),("B4",.5,72),
|
||||
("E5",.25,88),("E5",.25,78),(None,.25,0),("G5",.25,85),
|
||||
(None,.25,0),("A5",.25,90),("B5",.25,92),("A5",.25,85),
|
||||
("G5",.5,82),("E5",.5,78),(None,1,0),
|
||||
("A4",.25,80),("C5",.25,82),("E5",.25,88),("A5",.25,92),
|
||||
("G5",.5,85),("E5",.5,78),(None,.5,0),("C5",.5,75),
|
||||
("A4",.5,78),("C5",.5,80),("D5",.5,82),("E5",.5,85),
|
||||
("E5",1,82),(None,1,0),
|
||||
("D5",.25,82),("F#5",.25,85),("A5",.25,90),("D5",.25,78),
|
||||
("F#5",.5,85),("D5",.5,78),("A4",.5,75),("D5",.5,80),
|
||||
("D#5",.5,82),("F#5",.5,88),("B4",.5,78),("D#5",.5,82),
|
||||
("F#5",1,85),(None,1,0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["E2","E2","G2","E2","A2","A2","C3","A2",
|
||||
"D2","D2","F#2","D2","B1","B1","D#2","F#2"] * 2:
|
||||
@@ -313,7 +325,7 @@ def funk_workout():
|
||||
def blues_shuffle():
|
||||
"""12/8 blues in A — slow shuffle with wailing lead."""
|
||||
print(" 12/8 Blues Shuffle in A")
|
||||
print(" 12/8 blues drums | saw lead + reverb + delay | sine bass + LP 500Hz")
|
||||
print(" 12/8 blues drums | saxophone lead + reverb + delay | upright bass + LP 500Hz")
|
||||
|
||||
score = Score("12/8", bpm=70)
|
||||
score.drums("12/8 blues", repeats=6)
|
||||
@@ -321,32 +333,34 @@ def blues_shuffle():
|
||||
chords = score.part("chords", instrument="electric_piano",
|
||||
volume=0.3, pan=-0.3,
|
||||
reverb=0.3, reverb_decay=1.5, reverb_type="plate")
|
||||
lead = score.part("lead", instrument="trumpet",
|
||||
lead = score.part("lead", instrument="saxophone",
|
||||
volume=0.45, pan=0.25,
|
||||
reverb=0.3, reverb_decay=1.2, reverb_type="plate",
|
||||
delay=0.2, delay_time=0.43, delay_feedback=0.3,
|
||||
lowpass=3500)
|
||||
lowpass=3500,
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.5, pan=0.0, lowpass=500)
|
||||
volume=0.5, pan=0.0, lowpass=500,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["A", "A", "D", "D", "E7", "A"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
|
||||
chords.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
|
||||
|
||||
for n, d in [
|
||||
("A4",1),("C5",.67),("A4",.33),("E4",1),
|
||||
(None,.5),("A4",.5),("C5",.67),("D5",.33),
|
||||
("E5",1.5),("D5",.5),("C5",.5),("A4",.5),
|
||||
("E4",1.5),(None,.5),("A4",1),
|
||||
("D5",1),("F5",.67),("D5",.33),("A4",1),
|
||||
(None,1),("D5",.67),("F5",.33),("A5",1),
|
||||
("G5",.67),("F5",.33),("D5",1),(None,1),
|
||||
("E5",.67),("G#4",.33),("B4",.67),("E5",.33),
|
||||
("D5",1),("A4",1),(None,1),
|
||||
("A4",.67),("C5",.33),("E5",.67),("A5",.33),
|
||||
("A5",2),(None,1),
|
||||
for n, d, v in [
|
||||
("A4",1,80),("C5",.67,85),("A4",.33,75),("E4",1,78),
|
||||
(None,.5,0),("A4",.5,80),("C5",.67,85),("D5",.33,82),
|
||||
("E5",1.5,92),("D5",.5,80),("C5",.5,78),("A4",.5,75),
|
||||
("E4",1.5,72),(None,.5,0),("A4",1,80),
|
||||
("D5",1,85),("F5",.67,90),("D5",.33,78),("A4",1,80),
|
||||
(None,1,0),("D5",.67,82),("F5",.33,88),("A5",1,95),
|
||||
("G5",.67,88),("F5",.33,80),("D5",1,78),(None,1,0),
|
||||
("E5",.67,85),("G#4",.33,72),("B4",.67,80),("E5",.33,85),
|
||||
("D5",1,82),("A4",1,78),(None,1,0),
|
||||
("A4",.67,78),("C5",.33,82),("E5",.67,90),("A5",.33,95),
|
||||
("A5",2,92),(None,1,0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["A1","A1","E2","A2","A1","A1","E2","A2","A1","A1","E2","A2",
|
||||
"D2","D2","A2","D2","D2","D2","A2","D2",
|
||||
@@ -372,25 +386,27 @@ def samba_de_janeiro():
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.45, pan=0.3,
|
||||
delay=0.2, delay_time=0.176, delay_feedback=0.3,
|
||||
reverb=0.15, reverb_type="plate")
|
||||
reverb=0.15, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.45, pan=0.0, lowpass=500)
|
||||
volume=0.45, pan=0.0, lowpass=500,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["G", "Em", "Am", "D7"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("B5",.33),("A5",.33),("G5",.34),("F#5",.5),("E5",.5),
|
||||
("D5",.5),("G5",.5),("B5",.5),("A5",.5),
|
||||
("G5",.67),("E5",.33),("D5",.67),("B4",.33),("G4",1),(None,1),
|
||||
("E5",.5),("G5",.5),("B5",.5),("A5",.5),
|
||||
("G5",.67),("F#5",.33),("E5",.67),("D5",.33),("E5",1),(None,1),
|
||||
("A5",.5),("C6",.5),("B5",.5),("A5",.5),
|
||||
("G5",.67),("E5",.33),("C5",.67),("A4",.33),("A4",1),(None,1),
|
||||
("D5",.5),("F#5",.5),("A5",.5),("C5",.5),
|
||||
("B4",.67),("A4",.33),("G4",.67),("F#4",.33),("G4",2),(None,2),
|
||||
for n, d, v in [
|
||||
("B5",.33,88),("A5",.33,82),("G5",.34,78),("F#5",.5,82),("E5",.5,75),
|
||||
("D5",.5,72),("G5",.5,80),("B5",.5,88),("A5",.5,82),
|
||||
("G5",.67,85),("E5",.33,78),("D5",.67,75),("B4",.33,70),("G4",1,72),(None,1,0),
|
||||
("E5",.5,80),("G5",.5,85),("B5",.5,90),("A5",.5,82),
|
||||
("G5",.67,85),("F#5",.33,80),("E5",.67,78),("D5",.33,72),("E5",1,80),(None,1,0),
|
||||
("A5",.5,85),("C6",.5,92),("B5",.5,88),("A5",.5,82),
|
||||
("G5",.67,85),("E5",.33,78),("C5",.67,75),("A4",.33,70),("A4",1,72),(None,1,0),
|
||||
("D5",.5,78),("F#5",.5,85),("A5",.5,90),("C5",.5,75),
|
||||
("B4",.67,78),("A4",.33,72),("G4",.67,70),("F#4",.33,68),("G4",2,75),(None,2,0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["G2","B2","D3","G2","E2","G2","B2","E2",
|
||||
"A2","C3","E3","A2","D2","F#2","A2","D3"] * 2:
|
||||
@@ -413,26 +429,28 @@ def jazz_waltz():
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.25,
|
||||
reverb=0.3, reverb_decay=1.5, reverb_type="plate",
|
||||
delay=0.2, delay_time=0.4, delay_feedback=0.3)
|
||||
delay=0.2, delay_time=0.4, delay_feedback=0.3,
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.4, pan=0.0, lowpass=500)
|
||||
volume=0.4, pan=0.0, lowpass=500,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for _ in range(2):
|
||||
for sym in ["Fmaj7", "Gm", "C7", "Fmaj7"]:
|
||||
for _ in range(4):
|
||||
rhodes.add(Chord.from_symbol(sym), Duration.DOTTED_HALF)
|
||||
|
||||
for n, d in [
|
||||
("A5",1.5),("G5",.5),("F5",1),("E5",1),("C5",1),("F5",1),
|
||||
("A5",2),(None,1),("G5",2),(None,1),
|
||||
("Bb5",1),("A5",.5),("G5",.5),("F5",1),("D5",1),("G5",1),
|
||||
("Bb5",2),(None,1),("A5",1.5),("G5",.5),("F5",1),
|
||||
("E5",1),("G5",1),("Bb5",1),("A5",1.5),("G5",.5),("E5",1),
|
||||
("C5",2),(None,1),("E5",1),("G5",1),("C5",1),
|
||||
("F5",2),("A5",1),("C6",2),(None,1),
|
||||
("A5",1),("F5",1),("C5",1),("F5",3),
|
||||
for n, d, v in [
|
||||
("A5",1.5,85),("G5",.5,78),("F5",1,80),("E5",1,75),("C5",1,72),("F5",1,80),
|
||||
("A5",2,88),(None,1,0),("G5",2,82),(None,1,0),
|
||||
("Bb5",1,90),("A5",.5,82),("G5",.5,78),("F5",1,80),("D5",1,75),("G5",1,82),
|
||||
("Bb5",2,90),(None,1,0),("A5",1.5,85),("G5",.5,78),("F5",1,80),
|
||||
("E5",1,78),("G5",1,82),("Bb5",1,88),("A5",1.5,85),("G5",.5,78),("E5",1,75),
|
||||
("C5",2,72),(None,1,0),("E5",1,78),("G5",1,82),("C5",1,72),
|
||||
("F5",2,82),("A5",1,88),("C6",2,92),(None,1,0),
|
||||
("A5",1,85),("F5",1,80),("C5",1,72),("F5",3,82),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["F2","A2","C3","G2","Bb2","D3","C2","E2","G2","F2","A2","C3"] * 4:
|
||||
bass.add(n, Duration.QUARTER)
|
||||
@@ -461,7 +479,8 @@ def house_anthem():
|
||||
reverb=0.15, reverb_type="plate")
|
||||
bass = score.part("bass", instrument="808_bass",
|
||||
volume=0.55, pan=0.0,
|
||||
sidechain=0.5)
|
||||
sidechain=0.5,
|
||||
reverb=0.1)
|
||||
|
||||
for sym in ["Cm", "Ab", "Bb", "Cm"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -518,9 +537,11 @@ def dub_kingston():
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.45, delay_time=0.625, delay_feedback=0.5,
|
||||
reverb=0.35, reverb_decay=2.0, reverb_type="cathedral")
|
||||
reverb=0.35, reverb_decay=2.0, reverb_type="cathedral",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.6, pan=0.0, lowpass=400, lowpass_q=1.5)
|
||||
volume=0.6, pan=0.0, lowpass=400, lowpass_q=1.5,
|
||||
humanize=0.2)
|
||||
siren = score.part("siren", synth="pwm_slow", envelope="pad",
|
||||
volume=0.15, pan=0.5,
|
||||
reverb=0.7, reverb_decay=3.0, reverb_type="cathedral",
|
||||
@@ -529,13 +550,13 @@ def dub_kingston():
|
||||
for sym in ["Am", "Am", "Dm", "Dm", "Am", "Am", "Em", "Am"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("A4", 2), (None, 2), ("C5", 1.5), (None, 2.5),
|
||||
("D5", 1), ("C5", 1), ("A4", 2), (None, 4),
|
||||
("E5", 2), (None, 2), ("D5", 1.5), ("C5", 1.5), (None, 3),
|
||||
("A4", 1), ("G4", 1), ("A4", 3), (None, 3),
|
||||
for n, d, v in [
|
||||
("A4", 2, 78), (None, 2, 0), ("C5", 1.5, 82), (None, 2.5, 0),
|
||||
("D5", 1, 85), ("C5", 1, 78), ("A4", 2, 75), (None, 4, 0),
|
||||
("E5", 2, 88), (None, 2, 0), ("D5", 1.5, 82), ("C5", 1.5, 78), (None, 3, 0),
|
||||
("A4", 1, 75), ("G4", 1, 70), ("A4", 3, 78), (None, 3, 0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["A1","A1","A1","A1","D1","D1","D1","D1",
|
||||
"A1","A1","A1","A1","E1","E1","A1","A1"]:
|
||||
@@ -573,7 +594,8 @@ def techno_minimal():
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="808_bass",
|
||||
volume=0.55, pan=0.0,
|
||||
sidechain=0.5)
|
||||
sidechain=0.5,
|
||||
reverb=0.1)
|
||||
|
||||
for sym in ["Fm", "Db", "Eb", "Fm"] * 2:
|
||||
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -605,31 +627,33 @@ def gospel_shuffle():
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.2, delay_time=0.278, delay_feedback=0.3,
|
||||
reverb=0.2, reverb_type="plate")
|
||||
reverb=0.2, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="upright_bass",
|
||||
volume=0.45, pan=0.0, lowpass=500)
|
||||
volume=0.45, pan=0.0, lowpass=500,
|
||||
humanize=0.2, reverb=0.2)
|
||||
|
||||
for sym in ["C", "Am", "F", "G"] * 2:
|
||||
organ.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
for n, d in [
|
||||
("E5",.67),("G5",.33),("C6",1),("B5",.67),("A5",.33),
|
||||
("G5",1),(None,1),
|
||||
("A5",.67),("C6",.33),("E5",1),("D5",.67),("C5",.33),
|
||||
("A4",1),(None,1),
|
||||
("F5",.67),("A5",.33),("C6",1),("B5",.67),("A5",.33),
|
||||
("G5",.67),("E5",.33),("C5",1),(None,1),
|
||||
("D5",.67),("E5",.33),("G5",.67),("B5",.33),
|
||||
("C6",2),(None,2),
|
||||
for n, d, v in [
|
||||
("E5",.67,82),("G5",.33,78),("C6",1,92),("B5",.67,85),("A5",.33,80),
|
||||
("G5",1,82),(None,1,0),
|
||||
("A5",.67,85),("C6",.33,88),("E5",1,80),("D5",.67,78),("C5",.33,72),
|
||||
("A4",1,75),(None,1,0),
|
||||
("F5",.67,82),("A5",.33,85),("C6",1,92),("B5",.67,88),("A5",.33,82),
|
||||
("G5",.67,80),("E5",.33,75),("C5",1,72),(None,1,0),
|
||||
("D5",.67,78),("E5",.33,80),("G5",.67,85),("B5",.33,88),
|
||||
("C6",2,92),(None,2,0),
|
||||
# Second half: more intense
|
||||
("C6",.67),("B5",.33),("A5",.67),("G5",.33),
|
||||
("E5",1),("C5",.67),("E5",.33),
|
||||
("A5",1),("G5",.67),("E5",.33),("C5",1),(None,1),
|
||||
("F5",1),("A5",.67),("C6",.33),("E6",2),
|
||||
("D6",.67),("C6",.33),("B5",.67),("G5",.33),
|
||||
("C6",3),(None,1),
|
||||
("C6",.67,95),("B5",.33,88),("A5",.67,90),("G5",.33,82),
|
||||
("E5",1,85),("C5",.67,78),("E5",.33,82),
|
||||
("A5",1,90),("G5",.67,85),("E5",.33,78),("C5",1,75),(None,1,0),
|
||||
("F5",1,85),("A5",.67,90),("C6",.33,95),("E6",2,100),
|
||||
("D6",.67,92),("C6",.33,88),("B5",.67,90),("G5",.33,82),
|
||||
("C6",3,95),(None,1,0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
for n in ["C2","E2","G2","C3","A1","C2","E2","A2",
|
||||
"F2","A2","C3","F2","G2","B2","D3","G2"] * 2:
|
||||
@@ -664,7 +688,8 @@ def dub_delay_madness():
|
||||
reverb=0.7, reverb_decay=3.0, reverb_type="cathedral",
|
||||
lowpass=1200, detune=8, humanize=0.2)
|
||||
bass = score.part("bass", instrument="bass_guitar",
|
||||
volume=0.6, pan=0.0, lowpass=350, lowpass_q=1.5)
|
||||
volume=0.6, pan=0.0, lowpass=350, lowpass_q=1.5,
|
||||
humanize=0.2)
|
||||
siren = score.part("siren", synth="pwm_slow", envelope="pad",
|
||||
volume=0.12, pan=0.5,
|
||||
reverb=0.8, reverb_decay=4.0, reverb_type="cathedral",
|
||||
@@ -674,7 +699,8 @@ def dub_delay_madness():
|
||||
melodica = score.part("melodica", instrument="flute",
|
||||
volume=0.35, pan=0.3,
|
||||
delay=0.6, delay_time=0.66, delay_feedback=0.55,
|
||||
reverb=0.5, reverb_decay=2.5, reverb_type="cathedral")
|
||||
reverb=0.5, reverb_decay=2.5, reverb_type="cathedral",
|
||||
humanize=0.2)
|
||||
|
||||
for sym in ["Em", "Em", "Am", "Am", "Em", "Em", "Bm", "Em"]:
|
||||
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
@@ -684,13 +710,13 @@ def dub_delay_madness():
|
||||
bass.add(n, Duration.HALF)
|
||||
|
||||
# Melodica: very sparse — let the delay do the work
|
||||
for n, d in [
|
||||
("E5", 1.5), (None, 6.5),
|
||||
("G5", 1), ("A5", 1), (None, 6),
|
||||
(None, 4), ("B5", 2), (None, 6),
|
||||
("E5", 1), (None, 3), ("D5", 1.5), (None, 2.5),
|
||||
for n, d, v in [
|
||||
("E5", 1.5, 78), (None, 6.5, 0),
|
||||
("G5", 1, 82), ("A5", 1, 85), (None, 6, 0),
|
||||
(None, 4, 0), ("B5", 2, 88), (None, 6, 0),
|
||||
("E5", 1, 75), (None, 3, 0), ("D5", 1.5, 72), (None, 2.5, 0),
|
||||
]:
|
||||
melodica.rest(d) if n is None else melodica.add(n, d)
|
||||
melodica.rest(d) if n is None else melodica.add(n, d, velocity=v)
|
||||
|
||||
# Siren: long notes that disappear into the void
|
||||
for n, d in [
|
||||
@@ -718,25 +744,27 @@ def drum_and_bass():
|
||||
lead = score.part("lead", instrument="flute",
|
||||
volume=0.4, pan=0.3,
|
||||
delay=0.3, delay_time=0.172, delay_feedback=0.4,
|
||||
reverb=0.25, reverb_type="plate")
|
||||
reverb=0.25, reverb_type="plate",
|
||||
humanize=0.2)
|
||||
bass = score.part("bass", instrument="808_bass",
|
||||
volume=0.55, pan=0.0)
|
||||
volume=0.55, pan=0.0,
|
||||
reverb=0.1)
|
||||
|
||||
for sym in ["Am", "F", "C", "G"] * 2:
|
||||
pads.add(Chord.from_symbol(sym), Duration.WHOLE)
|
||||
|
||||
# Liquid melody — flowing, emotional
|
||||
for n, d in [
|
||||
("A5", 1), ("G5", .5), ("E5", .5), ("C5", 1), (None, 1),
|
||||
("D5", .5), ("E5", .5), ("G5", 1), ("A5", 1.5), (None, .5),
|
||||
("C6", 1), ("B5", .5), ("A5", .5), ("G5", 1), ("E5", 1),
|
||||
("F5", .5), ("G5", .5), ("A5", 1.5), (None, .5), ("G5", 1),
|
||||
("A5", 1), ("G5", .5), ("E5", .5), ("C5", 1), (None, 1),
|
||||
("E5", .5), ("G5", .5), ("B5", 1), ("A5", 2),
|
||||
("G5", 1), ("E5", .5), ("D5", .5), ("C5", 1.5), (None, .5),
|
||||
("E5", .5), ("G5", .5), ("A5", 2), (None, 1),
|
||||
for n, d, v in [
|
||||
("A5", 1, 85), ("G5", .5, 78), ("E5", .5, 72), ("C5", 1, 75), (None, 1, 0),
|
||||
("D5", .5, 72), ("E5", .5, 78), ("G5", 1, 82), ("A5", 1.5, 88), (None, .5, 0),
|
||||
("C6", 1, 92), ("B5", .5, 85), ("A5", .5, 80), ("G5", 1, 82), ("E5", 1, 75),
|
||||
("F5", .5, 78), ("G5", .5, 82), ("A5", 1.5, 88), (None, .5, 0), ("G5", 1, 80),
|
||||
("A5", 1, 85), ("G5", .5, 78), ("E5", .5, 72), ("C5", 1, 75), (None, 1, 0),
|
||||
("E5", .5, 78), ("G5", .5, 82), ("B5", 1, 90), ("A5", 2, 85),
|
||||
("G5", 1, 80), ("E5", .5, 75), ("D5", .5, 70), ("C5", 1.5, 72), (None, .5, 0),
|
||||
("E5", .5, 78), ("G5", .5, 82), ("A5", 2, 88), (None, 1, 0),
|
||||
]:
|
||||
lead.rest(d) if n is None else lead.add(n, d)
|
||||
lead.rest(d) if n is None else lead.add(n, d, velocity=v)
|
||||
|
||||
# Sub bass — half note roots, deep
|
||||
for n in ["A1","A1","F1","F1","C1","C1","G1","G1"] * 2:
|
||||
@@ -762,7 +790,8 @@ def drake_vibes():
|
||||
bells = score.part("bells", instrument="vibraphone",
|
||||
volume=0.3, pan=0.4,
|
||||
reverb=0.4, reverb_decay=2.0, reverb_type="plate",
|
||||
delay=0.25, delay_time=0.44, delay_feedback=0.35)
|
||||
delay=0.25, delay_time=0.44, delay_feedback=0.35,
|
||||
humanize=0.2)
|
||||
lead = score.part("lead", synth="pwm_slow", envelope="strings",
|
||||
volume=0.35, pan=-0.2,
|
||||
reverb=0.3, reverb_type="cathedral", lowpass=2000,
|
||||
@@ -994,10 +1023,12 @@ def dance_party():
|
||||
|
||||
bass = score.part("bass", instrument="synth_bass",
|
||||
volume=0.45, lowpass=500, lowpass_q=1.3,
|
||||
sidechain=0.75, sidechain_release=0.12)
|
||||
sidechain=0.75, sidechain_release=0.12,
|
||||
reverb=0.15)
|
||||
sparkle = score.part("sparkle", instrument="vibraphone",
|
||||
volume=0.3, pan=0.4, reverb=0.3, reverb_decay=1.5,
|
||||
delay=0.2, delay_time=0.234, delay_feedback=0.3)
|
||||
delay=0.2, delay_time=0.234, delay_feedback=0.3,
|
||||
humanize=0.2)
|
||||
chords_part = score.part("chords", instrument="synth_pad",
|
||||
volume=0.2,
|
||||
reverb=0.4, reverb_type="plate", sidechain=0.7)
|
||||
@@ -1315,13 +1346,13 @@ def journey():
|
||||
# ── Drone — runs the entire piece ──
|
||||
tanpura = score.part("tanpura", synth="strings_synth", envelope="pad",
|
||||
detune=3, lowpass=1000, volume=0.12,
|
||||
reverb=0.5, reverb_type=REV)
|
||||
reverb=0.6, reverb_type=REV)
|
||||
for _ in range(40):
|
||||
tanpura.add("A2", Duration.WHOLE)
|
||||
|
||||
# ── Bars 1-8: Piano alone, then cello ──
|
||||
piano = score.part("piano", instrument="piano", volume=0.35,
|
||||
reverb=0.35, reverb_type=REV)
|
||||
reverb=0.6, reverb_type=REV)
|
||||
for notes in [
|
||||
["A2","E3","A3","C4","E4","C4","A3","E3"],
|
||||
["F2","C3","F3","A3","C4","A3","F3","C3"],
|
||||
@@ -1336,7 +1367,7 @@ def journey():
|
||||
piano.add(n, Duration.EIGHTH, velocity=68)
|
||||
|
||||
cello = score.part("cello", instrument="cello", volume=0.2,
|
||||
reverb=0.4, reverb_type=REV)
|
||||
reverb=0.55, reverb_type=REV)
|
||||
cello.rest(Duration.WHOLE)
|
||||
for note, dur, vel in [
|
||||
("A3", 4.0, 55), ("C4", 4.0, 58),
|
||||
@@ -1347,11 +1378,11 @@ def journey():
|
||||
|
||||
# ── Bars 9-16: Harp + oboe + flute + djembe ──
|
||||
harp = score.part("harp", instrument="harp", volume=0.28,
|
||||
reverb=0.45, reverb_type=REV)
|
||||
reverb=0.6, reverb_type=REV)
|
||||
oboe = score.part("oboe", instrument="oboe", volume=0.22,
|
||||
reverb=0.4, reverb_type=REV)
|
||||
reverb=0.55, reverb_type=REV)
|
||||
flute = score.part("flute", instrument="flute", volume=0.18,
|
||||
reverb=0.4, reverb_type=REV)
|
||||
reverb=0.55, reverb_type=REV)
|
||||
for _ in range(8):
|
||||
harp.rest(Duration.WHOLE)
|
||||
for notes in [
|
||||
@@ -1383,7 +1414,7 @@ def journey():
|
||||
|
||||
# ── Bars 15-20: Sitar + tabla ──
|
||||
sitar = score.part("sitar", instrument="sitar", volume=0.2,
|
||||
reverb=0.35, reverb_type=REV)
|
||||
reverb=0.6, reverb_type=REV)
|
||||
for _ in range(14):
|
||||
sitar.rest(Duration.WHOLE)
|
||||
for note, dur, vel in [
|
||||
@@ -1406,7 +1437,7 @@ def journey():
|
||||
# Total bars before EDM: 8 piano + 6 harp + 6 djembe + 4 tabla + 9 solo = 33
|
||||
edm_start = 33
|
||||
pad = score.part("pad", instrument="synth_pad", volume=0.18,
|
||||
reverb=0.45, reverb_type=REV,
|
||||
reverb=0.6, reverb_type=REV,
|
||||
sidechain=0.6, sidechain_release=0.15)
|
||||
for _ in range(edm_start):
|
||||
pad.rest(Duration.WHOLE)
|
||||
@@ -1551,6 +1582,267 @@ def journey():
|
||||
play_song(score, "Journey — Piano → World → Sitar EDM (Taj Mahal)")
|
||||
|
||||
|
||||
def epic_bhairav():
|
||||
"""Epic Bhairav — orchestral + choir + tabla with extended solo finale."""
|
||||
shruti = SYSTEMS["shruti"]
|
||||
score = Score("4/4", bpm=90, system=shruti)
|
||||
REV = "taj_mahal"
|
||||
T3 = 1.0 / 12.0
|
||||
T9 = 1.0 / 9.0
|
||||
|
||||
ts = TonedScale(system=shruti, tonic=Tone("Sa", octave=4, system=shruti))
|
||||
bh = list(ts["bhairav"].tones)
|
||||
S, kR, G, M, P, kD, N, S2 = bh
|
||||
|
||||
NA = DrumSound.TABLA_NA
|
||||
DH = DrumSound.TABLA_DHA
|
||||
TT = DrumSound.TABLA_TIT
|
||||
KE = DrumSound.TABLA_KE
|
||||
GB = DrumSound.TABLA_GE_BEND
|
||||
GE = DrumSound.TABLA_GE
|
||||
DJB = DrumSound.DJEMBE_BASS
|
||||
DJT = DrumSound.DJEMBE_TONE
|
||||
DJS = DrumSound.DJEMBE_SLAP
|
||||
|
||||
# Tanpura
|
||||
tanpura = score.part("tanpura", synth="strings_synth", envelope="pad",
|
||||
detune=3, lowpass=900, volume=0.14, reverb=0.4, reverb_type=REV)
|
||||
tanpura_pa = score.part("tanpura_pa", synth="strings_synth", envelope="pad",
|
||||
detune=3, lowpass=1200, volume=0.1, reverb=0.4, reverb_type=REV)
|
||||
sa = Tone("Sa", octave=3, system=shruti)
|
||||
pa = Tone("Pa", octave=3, system=shruti)
|
||||
for _ in range(34):
|
||||
tanpura.add(sa, Duration.WHOLE)
|
||||
tanpura_pa.add(pa, Duration.WHOLE)
|
||||
|
||||
# Timpani
|
||||
timp = score.part("timp", instrument="timpani")
|
||||
timp.roll(Tone("Sa", octave=2, system=shruti), Duration.WHOLE,
|
||||
velocity_start=20, velocity_end=90, speed=0.125)
|
||||
timp.add(Tone("Sa", octave=2, system=shruti), Duration.HALF, velocity=105)
|
||||
timp.rest(Duration.HALF)
|
||||
for _ in range(8):
|
||||
timp.rest(Duration.WHOLE)
|
||||
timp.roll(Tone("Sa", octave=2, system=shruti), Duration.WHOLE,
|
||||
velocity_start=25, velocity_end=115, speed=0.125)
|
||||
timp.add(Tone("Sa", octave=2, system=shruti), Duration.HALF, velocity=120)
|
||||
timp.add(Tone("Pa", octave=2, system=shruti), Duration.HALF, velocity=115)
|
||||
|
||||
# Choir — bar 3
|
||||
choir = score.part("choir", synth="vocal_synth", envelope="pad",
|
||||
detune=8, spread=0.4, reverb=0.4, reverb_type=REV, volume=0.2)
|
||||
for _ in range(2):
|
||||
choir.rest(Duration.WHOLE)
|
||||
for tone, dur, lyric, vel in [
|
||||
(S, 4.0, "ah", 60), (M, 4.0, "oh", 62), (P, 4.0, "ah", 68),
|
||||
(S, 4.0, "ee", 65), (kD, 4.0, "oh", 70), (P, 4.0, "ah", 72),
|
||||
]:
|
||||
choir.add(tone, dur, velocity=vel, lyric=lyric)
|
||||
|
||||
# Bansuri — bar 5
|
||||
bansuri = score.part("bansuri", instrument="flute", volume=0.22,
|
||||
reverb=0.4, reverb_type=REV)
|
||||
for _ in range(4):
|
||||
bansuri.rest(Duration.WHOLE)
|
||||
for tone, dur, vel in [
|
||||
(P, 2.0, 58), (kD, 1.0, 50), (P, 1.0, 55),
|
||||
(M, 2.0, 55), (G, 1.0, 50), (kR, 1.0, 48), (S, 4.0, 58),
|
||||
]:
|
||||
bansuri.add(tone, dur, velocity=vel)
|
||||
|
||||
# Cello — bar 3
|
||||
cello = score.part("cello", instrument="cello", volume=0.22, reverb=0.4, reverb_type=REV)
|
||||
for _ in range(2):
|
||||
cello.rest(Duration.WHOLE)
|
||||
for name, dur, vel in [
|
||||
("Sa", 4.0, 55), ("Ma", 4.0, 52), ("Pa", 4.0, 58),
|
||||
("Sa", 4.0, 55), ("komal Dha", 4.0, 58), ("Pa", 4.0, 55),
|
||||
]:
|
||||
cello.add(Tone(name, octave=2, system=shruti), dur, velocity=vel)
|
||||
|
||||
# Sitar — bar 9
|
||||
sitar = score.part("sitar", instrument="sitar", volume=0.25, reverb=0.4, reverb_type=REV)
|
||||
for _ in range(8):
|
||||
sitar.rest(Duration.WHOLE)
|
||||
for tone, dur, vel in [
|
||||
(S, 1.0, 72), (kR, 0.5, 62), (S, 0.5, 68), (G, 2.0, 78),
|
||||
(M, 1.0, 72), (P, 2.0, 82), (kD, 0.5, 65), (P, 1.0, 75),
|
||||
(M, 0.5, 65), (G, 0.5, 68), (kR, 0.5, 60), (S, 2.0, 78),
|
||||
(kR, 0.25, 62), (G, 0.25, 65), (M, 0.25, 70), (P, 0.25, 75),
|
||||
(kD, 0.25, 70), (N, 0.25, 78), (S2, 0.5, 88),
|
||||
(N, 0.25, 68), (kD, 0.25, 62), (P, 0.5, 68),
|
||||
(M, 0.5, 62), (G, 0.5, 65), (kR, 0.5, 58), (S, 2.0, 80),
|
||||
]:
|
||||
sitar.add(tone, dur, velocity=vel)
|
||||
|
||||
# Strings — bar 13
|
||||
strings = score.part("strings", instrument="string_ensemble", volume=0.18,
|
||||
reverb=0.4, reverb_type=REV)
|
||||
for _ in range(12):
|
||||
strings.rest(Duration.WHOLE)
|
||||
for name, dur, vel in [("Sa", 4.0, 58), ("Ma", 4.0, 62), ("Pa", 4.0, 68), ("Sa", 4.0, 72)]:
|
||||
strings.add(Tone(name, octave=3, system=shruti), dur, velocity=vel)
|
||||
|
||||
# Harp — bar 14
|
||||
harp = score.part("harp", instrument="harp", volume=0.15, reverb=0.4, reverb_type=REV)
|
||||
for _ in range(13):
|
||||
harp.rest(Duration.WHOLE)
|
||||
for name in ["Sa", "komal Ga", "Pa", "Sa", "Pa", "komal Ga", "Sa", "Sa"]:
|
||||
oct = 4 if name == "Sa" and harp.total_beats > 55 else 3
|
||||
harp.add(Tone(name, octave=oct, system=shruti), Duration.EIGHTH, velocity=50)
|
||||
|
||||
# Drums
|
||||
silence = Pattern(name="s", time_signature="4/4", beats=16.0, hits=[])
|
||||
score.add_pattern(silence, repeats=1)
|
||||
p_dj = Pattern(name="dj", time_signature="4/4", beats=8.0, hits=[
|
||||
_Hit(DJB, 0.0, 45), _Hit(DJT, 1.0, 38), _Hit(DJT, 1.5, 32),
|
||||
_Hit(DJS, 2.0, 42), _Hit(DJT, 3.0, 38),
|
||||
_Hit(DJB, 4.0, 50), _Hit(DJT, 5.0, 42), _Hit(DJT, 5.5, 35),
|
||||
_Hit(DJS, 6.0, 48), _Hit(DJT, 6.5, 32), _Hit(DJS, 7.0, 45),
|
||||
])
|
||||
score.add_pattern(p_dj, repeats=2)
|
||||
p_tab = Pattern(name="tab", time_signature="4/4", beats=8.0, hits=[
|
||||
_Hit(DH, 0.0, 82), _Hit(TT, 0.5, 30), _Hit(NA, 1.0, 65),
|
||||
_Hit(NA, 2.0, 60), _Hit(DH, 3.0, 82),
|
||||
_Hit(DH, 4.0, 88), _Hit(TT, 4.25, 32), _Hit(TT, 4.5, 35),
|
||||
_Hit(NA, 5.0, 68), _Hit(TT, 5.5, 30), _Hit(NA, 6.0, 65),
|
||||
_Hit(DH, 7.0, 88),
|
||||
])
|
||||
score.add_pattern(p_tab, repeats=3)
|
||||
|
||||
# Extended tabla finale — whisper → ghosts → call/response → blazing
|
||||
p_f1 = Pattern(name="f1", time_signature="4/4", beats=8.0, hits=[
|
||||
_Hit(DH, 0.0, 78), _Hit(NA, 2.0, 55),
|
||||
_Hit(DH, 4.0, 82), _Hit(TT, 5.0, 30), _Hit(NA, 5.5, 52),
|
||||
_Hit(DH, 7.0, 78),
|
||||
])
|
||||
score.add_pattern(p_f1, repeats=1)
|
||||
p_f2 = Pattern(name="f2", time_signature="4/4", beats=8.0, hits=[
|
||||
_Hit(DH, 0.0, 95), _Hit(TT, 0.25, 35), _Hit(TT, 0.5, 38),
|
||||
_Hit(NA, 1.0, 70), _Hit(TT, 1.25, 30), _Hit(NA, 2.0, 65),
|
||||
_Hit(TT, 2.5, 35), _Hit(DH, 3.0, 90),
|
||||
_Hit(DH, 4.0, 98), _Hit(TT, 4.25, 38), _Hit(TT, 4.5, 42),
|
||||
_Hit(NA, 5.0, 75), _Hit(KE, 5.5, 40), _Hit(NA, 6.0, 70),
|
||||
_Hit(KE, 6.5, 42), _Hit(DH, 7.0, 100), _Hit(GB, 7.5, 92),
|
||||
])
|
||||
score.add_pattern(p_f2, repeats=1)
|
||||
p_f3 = Pattern(name="f3", time_signature="4/4", beats=8.0, hits=[
|
||||
_Hit(NA, 0.0, 112), _Hit(NA, 0.25, 58), _Hit(TT, 0.5, 40), _Hit(NA, 0.75, 105),
|
||||
_Hit(GE, 1.0, 105), _Hit(GE, 1.25, 52), _Hit(GB, 1.5, 95), _Hit(GE, 1.75, 48),
|
||||
_Hit(NA, 2.0, 115), _Hit(TT, 2.125, 32), _Hit(TT, 2.25, 38),
|
||||
_Hit(NA, 2.5, 108), _Hit(TT, 2.625, 35), _Hit(TT, 2.75, 42),
|
||||
_Hit(GB, 3.0, 115), _Hit(KE, 3.25, 52), _Hit(GE, 3.5, 70),
|
||||
_Hit(DH, 4.0, 118),
|
||||
*[_Hit(TT if i % 2 == 0 else KE, 5.0 + i * T9, 40 + i * 5) for i in range(9)],
|
||||
_Hit(DH, 7.0, 120),
|
||||
])
|
||||
score.add_pattern(p_f3, repeats=1)
|
||||
|
||||
# Part 3.5: polyrhythm — space and conversation, not density
|
||||
T5 = 4.0 / 5.0
|
||||
p_poly = Pattern(name="poly", time_signature="4/4", beats=16.0, hits=[
|
||||
# Bar 1: single Dha, let reverb ring. Bayan answers.
|
||||
_Hit(DH, 0.0, 95),
|
||||
_Hit(GB, 3.0, 88),
|
||||
# Bar 2: one 5-group phrase, then breathe
|
||||
_Hit(NA, 4.0, 75), _Hit(TT, 4.0 + T5, 42),
|
||||
_Hit(NA, 4.0 + 2*T5, 70), _Hit(TT, 4.0 + 3*T5, 40),
|
||||
_Hit(DH, 4.0 + 4*T5, 88),
|
||||
# Bar 3: bayan, pause, one floating 9-group
|
||||
_Hit(GB, 8.0, 100),
|
||||
_Hit(NA, 9.0, 62),
|
||||
*[_Hit(TT if i % 2 == 0 else KE, 10.0 + i * T9, 35 + i * 4)
|
||||
for i in range(9)],
|
||||
_Hit(DH, 11.0, 105),
|
||||
# Bar 4: simple question-answer into sam
|
||||
_Hit(DH, 12.0, 100), _Hit(NA, 12.5, 62),
|
||||
_Hit(GE, 13.0, 88),
|
||||
_Hit(NA, 14.0, 72), _Hit(TT, 14.25, 40), _Hit(NA, 14.5, 70),
|
||||
_Hit(DH, 15.0, 112), _Hit(GB, 15.5, 105),
|
||||
])
|
||||
score.add_pattern(p_poly, repeats=1)
|
||||
|
||||
p_f4 = Pattern(name="f4", time_signature="4/4", beats=12.0, hits=[
|
||||
*[_Hit(TT, 0.0 + i * T3, 38 + i * 2) for i in range(12)],
|
||||
_Hit(DH, 1.0, 118), _Hit(GB, 1.5, 110),
|
||||
_Hit(NA, 2.0, 112), _Hit(KE, 2.125, 48), _Hit(NA, 2.25, 108),
|
||||
_Hit(KE, 2.375, 50), _Hit(NA, 2.5, 110), _Hit(KE, 2.625, 52), _Hit(NA, 2.75, 115),
|
||||
_Hit(DH, 3.0, 120),
|
||||
*[_Hit(TT, 3.5 + i * T3, 30 + i * 4) for i in range(18)],
|
||||
_Hit(DH, 5.0, 122), _Hit(DH, 5.25, 118), _Hit(GB, 5.5, 115),
|
||||
_Hit(GE, 6.0, 90), _Hit(GE, 7.0, 88),
|
||||
*[_Hit(NA if i % 3 == 0 else TT, 6.0 + i * (2.0/9.0), 42 + (i%3)*15) for i in range(9)],
|
||||
_Hit(DH, 8.0, 110), _Hit(NA, 8.25, 75), _Hit(TT, 8.5, 50),
|
||||
_Hit(KE, 8.75, 55), _Hit(DH, 9.0, 105),
|
||||
_Hit(DH, 9.25, 115), _Hit(NA, 9.5, 80), _Hit(TT, 9.75, 55),
|
||||
_Hit(KE, 10.0, 60), _Hit(DH, 10.25, 110),
|
||||
_Hit(DH, 10.5, 122), _Hit(NA, 10.75, 85), _Hit(TT, 11.0, 60),
|
||||
_Hit(KE, 11.25, 65), _Hit(DH, 11.5, 127),
|
||||
_Hit(GB, 11.875, 127),
|
||||
])
|
||||
score.add_pattern(p_f4, repeats=1)
|
||||
score.set_drum_effects(reverb=0.4, reverb_type=REV)
|
||||
|
||||
play_song(score, "Epic Bhairav — Orchestra + Choir + Tabla (22-Shruti JI)")
|
||||
|
||||
|
||||
def acoustic_ensemble():
|
||||
"""Acoustic Ensemble — guitar, ukulele, mandolin, cajón."""
|
||||
import random
|
||||
from pytheory import Fretboard
|
||||
random.seed(7)
|
||||
score = Score("4/4", bpm=115)
|
||||
|
||||
fb_g = Fretboard.guitar()
|
||||
guitar = score.part("guitar", instrument="acoustic_guitar", fretboard=fb_g,
|
||||
reverb=0.3, reverb_type="plate", humanize=0.2, pan=-0.3)
|
||||
|
||||
fb_u = Fretboard.ukulele()
|
||||
uke = score.part("uke", instrument="ukulele", fretboard=fb_u,
|
||||
reverb=0.25, reverb_type="plate", humanize=0.25, pan=0.3)
|
||||
|
||||
fb_m = Fretboard.mandolin()
|
||||
mando = score.part("mando", instrument="mandolin", fretboard=fb_m,
|
||||
reverb=0.25, reverb_type="plate", humanize=0.2, pan=0.15)
|
||||
|
||||
for sym in ["C", "G", "Am", "F"] * 3:
|
||||
vd = random.randint(75, 95)
|
||||
vu = random.randint(58, 78)
|
||||
guitar.strum(sym, Duration.QUARTER, direction="down", velocity=vd)
|
||||
guitar.strum(sym, Duration.EIGHTH, direction="up", velocity=vu)
|
||||
guitar.strum(sym, Duration.EIGHTH, direction="down", velocity=vd - 8)
|
||||
guitar.strum(sym, Duration.QUARTER, direction="up", velocity=vu)
|
||||
guitar.strum(sym, Duration.QUARTER, direction="down", velocity=vd)
|
||||
|
||||
vd2 = random.randint(65, 88)
|
||||
vu2 = random.randint(50, 72)
|
||||
uke.rest(Duration.EIGHTH)
|
||||
uke.strum(sym, Duration.EIGHTH, direction="up", velocity=vu2)
|
||||
uke.strum(sym, Duration.QUARTER, direction="down", velocity=vd2)
|
||||
uke.strum(sym, Duration.EIGHTH, direction="up", velocity=vu2)
|
||||
uke.strum(sym, Duration.EIGHTH, direction="down", velocity=vd2 - 5)
|
||||
uke.strum(sym, Duration.QUARTER, direction="up", velocity=vu2)
|
||||
|
||||
mando.strum(sym, Duration.EIGHTH, direction="down",
|
||||
velocity=random.randint(65, 82))
|
||||
mando.strum(sym, Duration.EIGHTH, direction="up",
|
||||
velocity=random.randint(55, 72))
|
||||
mando.strum(sym, Duration.EIGHTH, direction="down",
|
||||
velocity=random.randint(65, 82))
|
||||
mando.rest(Duration.EIGHTH)
|
||||
mando.strum(sym, Duration.EIGHTH, direction="up",
|
||||
velocity=random.randint(55, 72))
|
||||
mando.strum(sym, Duration.EIGHTH, direction="down",
|
||||
velocity=random.randint(68, 85))
|
||||
mando.strum(sym, Duration.QUARTER, direction="down",
|
||||
velocity=random.randint(70, 85))
|
||||
|
||||
score.drums("cajon", repeats=6)
|
||||
score.set_drum_effects(reverb=0.15)
|
||||
|
||||
play_song(score, "Acoustic Ensemble — Guitar, Uke, Mandolin, Cajón")
|
||||
|
||||
|
||||
SONGS = {
|
||||
"1": ("Bossa Nova in A minor", bossa_nova_girl),
|
||||
"2": ("Bebop in Bb major", bebop_in_bb),
|
||||
@@ -1576,6 +1868,8 @@ SONGS = {
|
||||
"22": ("Greensleeves (Renaissance Lute)", greensleeves),
|
||||
"23": ("Tabla Solo (Raga Yaman)", tabla_solo_yaman),
|
||||
"24": ("Journey (Western → World → Indian)", journey),
|
||||
"25": ("Epic Bhairav (Orchestral + Tabla)", epic_bhairav),
|
||||
"26": ("Acoustic Ensemble (Guitar+Uke+Mando+Cajón)", acoustic_ensemble),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -1589,7 +1883,7 @@ if __name__ == "__main__":
|
||||
print(f" {key:>2}. {name}")
|
||||
|
||||
print()
|
||||
choice = input(" Pick a song (1-24, or 'all'): ").strip()
|
||||
choice = input(" Pick a song (1-26, or 'all'): ").strip()
|
||||
print()
|
||||
|
||||
if choice == "all":
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pytheory"
|
||||
version = "0.35.1"
|
||||
version = "0.36.2"
|
||||
description = "Music Theory for Humans"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""PyTheory: Music Theory for Humans."""
|
||||
|
||||
__version__ = "0.35.1"
|
||||
__version__ = "0.36.2"
|
||||
|
||||
from .tones import Tone, Interval
|
||||
from .systems import System, SYSTEMS, TET
|
||||
|
||||
+52
-1
@@ -299,6 +299,54 @@ def cmd_demo(args):
|
||||
"lead": ("trumpet_synth", "bowed", 0.3, 0.2),
|
||||
"pad": ("piano_synth", "none", -0.2),
|
||||
"bass_lp": 600, "reverb_type": "plate"},
|
||||
{"name": "Theremin Noir", "key": ("A", "minor"), "drums": "hip hop",
|
||||
"fill": "rock", "bpm": 85,
|
||||
"prog": ("i", "iv", "V", "i"),
|
||||
"lead": ("theremin_synth", "pad", 0.4, 0.0),
|
||||
"pad": ("strings_synth", "pad", 0.0),
|
||||
"bass_lp": 300, "reverb_type": "cave"},
|
||||
{"name": "Caribbean", "key": ("C", "major"), "drums": "reggae",
|
||||
"fill": "reggae", "bpm": 110,
|
||||
"prog": ("I", "IV", "V", "IV"),
|
||||
"lead": ("steel_drum_synth", "none", 0.25, 0.3),
|
||||
"pad": ("acoustic_guitar_synth", "none", -0.3),
|
||||
"bass_lp": 500, "reverb_type": "plate"},
|
||||
{"name": "Accordion Waltz", "key": ("D", "minor"), "drums": "waltz",
|
||||
"fill": "jazz", "bpm": 88,
|
||||
"prog": ("i", "iv", "V", "i"),
|
||||
"lead": ("accordion_synth", "organ", 0.2, 0.1),
|
||||
"pad": ("strings_synth", "pad", -0.2),
|
||||
"bass_lp": 500, "reverb_type": "plate"},
|
||||
{"name": "Kalimba Dreams", "key": ("G", "major"), "drums": "cajon folk",
|
||||
"fill": "bossa nova", "bpm": 95,
|
||||
"prog": ("I", "vi", "IV", "V"),
|
||||
"lead": ("kalimba_synth", "none", 0.35, 0.2),
|
||||
"pad": ("piano_synth", "none", -0.2),
|
||||
"bass_lp": 400, "reverb_type": "taj_mahal"},
|
||||
{"name": "Outback Drone", "key": ("E", "minor"), "drums": "djembe",
|
||||
"fill": "afrobeat", "bpm": 70,
|
||||
"prog": ("i", "iv", "i", "V"),
|
||||
"lead": ("didgeridoo_synth", "pad", 0.3, 0.0),
|
||||
"pad": ("granular_synth", "pad", 0.0),
|
||||
"bass_lp": 200, "reverb_type": "cave"},
|
||||
{"name": "Highland", "key": ("A", "minor"), "drums": "flamenco",
|
||||
"fill": "rock", "bpm": 95,
|
||||
"prog": ("i", "iv", "V", "i"),
|
||||
"lead": ("bagpipe_synth", "organ", 0.15, 0.0),
|
||||
"pad": ("strings_synth", "pad", -0.2),
|
||||
"bass_lp": 400, "reverb_type": "cathedral"},
|
||||
{"name": "Nashville Tears", "key": ("G", "major"), "drums": "country",
|
||||
"fill": "rock", "bpm": 85,
|
||||
"prog": ("I", "IV", "V", "IV"),
|
||||
"lead": ("pedal_steel_synth", "strings", 0.35, 0.2),
|
||||
"pad": ("piano_synth", "none", -0.3),
|
||||
"bass_lp": 500, "reverb_type": "spring"},
|
||||
{"name": "Tabla Fusion", "key": ("E", "minor"), "drums": "teental",
|
||||
"fill": "rock", "bpm": 120,
|
||||
"prog": ("i", "iv", "V", "i"),
|
||||
"lead": ("sitar_synth", "none", 0.3, 0.2),
|
||||
"pad": ("vocal_synth", "pad", 0.0),
|
||||
"bass_lp": 400, "reverb_type": "taj_mahal"},
|
||||
]
|
||||
|
||||
mood = random.choice(moods)
|
||||
@@ -375,7 +423,10 @@ def cmd_demo(args):
|
||||
print(f" {mood['drums']} | {lead_synth} lead | {pad_synth} pad | {mood['reverb_type']} reverb")
|
||||
print()
|
||||
|
||||
play_score(score)
|
||||
try:
|
||||
play_score(score)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
print(" ♫")
|
||||
|
||||
|
||||
|
||||
+449
-4
@@ -1130,6 +1130,359 @@ def granular_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE,
|
||||
return (peak * out).astype(numpy.int16)
|
||||
|
||||
|
||||
def pedal_steel_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Pedal steel guitar — the Nashville crying sound.
|
||||
|
||||
Sustained steel string with natural portamento character,
|
||||
very smooth, lots of harmonics, and a singing quality from
|
||||
the bar sliding on the strings.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
# Slow, singing vibrato — the bar wobbling on the strings
|
||||
vib = hz * 0.002 * numpy.sin(2 * numpy.pi * 4.0 * t)
|
||||
# Rich harmonics — steel bar gives a clear, singing tone
|
||||
wave = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for n in range(1, 12):
|
||||
f_n = hz * n
|
||||
if f_n >= SAMPLE_RATE / 2:
|
||||
break
|
||||
amp = 1.0 / n * numpy.exp(-0.08 * n)
|
||||
phase = rng.uniform(0, 2 * numpy.pi)
|
||||
wave += amp * numpy.sin(2 * numpy.pi * (f_n + vib * n) * t + phase)
|
||||
# Long sustain envelope
|
||||
wave *= numpy.exp(-0.8 * t)
|
||||
mx = numpy.abs(wave).max()
|
||||
if mx > 0:
|
||||
wave /= mx
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def theremin_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Theremin — pure sine with natural wobble.
|
||||
|
||||
The theremin's sound is a nearly pure sine wave with slight
|
||||
pitch instability from hand position. The eerie, sci-fi sound
|
||||
comes from this purity combined with continuous pitch.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
# Natural hand wobble — slightly irregular vibrato
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
wobble = hz * 0.004 * numpy.sin(2 * numpy.pi * 5.8 * t)
|
||||
wobble += hz * 0.001 * rng.normal(0, 1, n_samples)
|
||||
wave = numpy.sin(2 * numpy.pi * (hz + wobble) * t)
|
||||
# Slight 2nd harmonic — real theremins aren't perfectly pure
|
||||
wave += 0.08 * numpy.sin(2 * numpy.pi * (hz * 2 + wobble * 2) * t)
|
||||
mx = numpy.abs(wave).max()
|
||||
if mx > 0:
|
||||
wave /= mx
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def kalimba_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Kalimba/thumb piano — metal tines on a wooden body.
|
||||
|
||||
Bright, bell-like attack with inharmonic overtones from the
|
||||
metal tines. The wooden resonator gives warmth underneath.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
# Metal tine modes — slightly inharmonic like marimba
|
||||
wave = numpy.sin(2 * numpy.pi * hz * t) * 0.8
|
||||
wave += numpy.sin(2 * numpy.pi * hz * 2.92 * t) * 0.25 * numpy.exp(-12 * t)
|
||||
wave += numpy.sin(2 * numpy.pi * hz * 5.4 * t) * 0.1 * numpy.exp(-20 * t)
|
||||
# Two-stage decay: bright attack dies fast, fundamental rings
|
||||
decay = numpy.where(t < 0.1,
|
||||
numpy.exp(-3 * t),
|
||||
numpy.exp(-3 * 0.1) * numpy.exp(-1.5 * (t - 0.1)))
|
||||
wave *= decay
|
||||
# Wooden body resonance
|
||||
import scipy.signal as _sig
|
||||
for center, bw, gain in [(300, 100, 0.2), (600, 120, 0.15)]:
|
||||
lo, hi = max(20, center - bw), min(SAMPLE_RATE // 2 - 1, center + bw)
|
||||
if lo < hi:
|
||||
bp, ap = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
|
||||
wave += _sig.lfilter(bp, ap, wave) * gain
|
||||
mx = numpy.abs(wave).max()
|
||||
if mx > 0:
|
||||
wave /= mx
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def steel_drum_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Steel drum/pan — hammered metal with bright, ringing tone.
|
||||
|
||||
The steel pan has specific inharmonic partials from the
|
||||
hand-hammered notes. Bright, tropical, bell-like.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
# Steel pan modes — distinctly metallic
|
||||
wave = numpy.sin(2 * numpy.pi * hz * t) * 0.7
|
||||
wave += numpy.sin(2 * numpy.pi * hz * 2.0 * t) * 0.4 * numpy.exp(-5 * t)
|
||||
wave += numpy.sin(2 * numpy.pi * hz * 3.01 * t) * 0.25 * numpy.exp(-8 * t)
|
||||
wave += numpy.sin(2 * numpy.pi * hz * 4.1 * t) * 0.15 * numpy.exp(-12 * t)
|
||||
wave += numpy.sin(2 * numpy.pi * hz * 5.3 * t) * 0.08 * numpy.exp(-18 * t)
|
||||
# Mallet impact
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
hit_len = min(int(SAMPLE_RATE * 0.008), n_samples)
|
||||
hit = rng.uniform(-0.2, 0.2, hit_len) * numpy.exp(-numpy.linspace(0, 12, hit_len))
|
||||
wave[:hit_len] += hit
|
||||
# Two-stage decay
|
||||
decay = numpy.where(t < 0.15,
|
||||
numpy.exp(-4 * t),
|
||||
numpy.exp(-4 * 0.15) * numpy.exp(-1.2 * (t - 0.15)))
|
||||
wave *= decay
|
||||
mx = numpy.abs(wave).max()
|
||||
if mx > 0:
|
||||
wave /= mx
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def accordion_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Accordion — bellows-driven free reeds.
|
||||
|
||||
Two reeds per note slightly detuned (musette tuning) create
|
||||
the characteristic beating/tremolo. Rich in harmonics from
|
||||
the reed vibration.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
# Two reeds slightly detuned — musette beating
|
||||
detune_cents = 8
|
||||
hz2 = hz * (2 ** (detune_cents / 1200))
|
||||
# Reed harmonics — rich, like a square-ish wave but warmer
|
||||
wave = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for reed_hz in [hz, hz2]:
|
||||
for n in range(1, 10):
|
||||
f_n = reed_hz * n
|
||||
if f_n >= SAMPLE_RATE / 2:
|
||||
break
|
||||
# Odd harmonics stronger (reed character)
|
||||
amp = (1.0 / n) * (1.2 if n % 2 == 1 else 0.6)
|
||||
phase = rng.uniform(0, 2 * numpy.pi)
|
||||
wave += amp * numpy.sin(2 * numpy.pi * f_n * t + phase)
|
||||
wave *= 0.5 # normalize for two reeds
|
||||
# Bellows pressure variation — slow amplitude swell
|
||||
bellows = 0.85 + 0.15 * numpy.sin(2 * numpy.pi * 0.8 * t)
|
||||
wave *= bellows
|
||||
mx = numpy.abs(wave).max()
|
||||
if mx > 0:
|
||||
wave /= mx
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def didgeridoo_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Didgeridoo — circular breathing drone through a wooden tube.
|
||||
|
||||
Deep fundamental with strong odd harmonics from the cylindrical
|
||||
bore. The overtone singing technique creates shifting formants.
|
||||
Buzzy, droning, primal.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
# Lip buzz source — rich, raw
|
||||
phase = numpy.cumsum(2 * numpy.pi * hz / SAMPLE_RATE * numpy.ones(n_samples))
|
||||
buzz = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for n in range(1, 15):
|
||||
if hz * n >= SAMPLE_RATE / 2:
|
||||
break
|
||||
# Odd harmonics stronger (cylindrical bore, like clarinet)
|
||||
amp = (1.0 / n) * (1.0 if n % 2 == 1 else 0.4)
|
||||
buzz += amp * numpy.sin(phase * n + rng.uniform(0, 2 * numpy.pi))
|
||||
# Shifting formant — the overtone singing effect
|
||||
# Sweeps slowly between 500Hz and 1500Hz
|
||||
formant_center = 800 + 400 * numpy.sin(2 * numpy.pi * 0.3 * t)
|
||||
import scipy.signal as _sig
|
||||
# Block-process the formant sweep
|
||||
block = 2048
|
||||
out = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for i in range(0, n_samples, block):
|
||||
end = min(i + block, n_samples)
|
||||
fc = formant_center[(i + end) // 2]
|
||||
lo = max(20, int(fc - 300))
|
||||
hi = min(SAMPLE_RATE // 2 - 1, int(fc + 300))
|
||||
if lo < hi:
|
||||
bp, ap = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
|
||||
seg = _sig.lfilter(bp, ap, buzz[i:end])
|
||||
out[i:end] = buzz[i:end] * 0.5 + seg * 0.5
|
||||
else:
|
||||
out[i:end] = buzz[i:end]
|
||||
# Breath noise
|
||||
breath = rng.normal(0, 0.04, n_samples)
|
||||
out += breath
|
||||
mx = numpy.abs(out).max()
|
||||
if mx > 0:
|
||||
out /= mx
|
||||
return (peak * out).astype(numpy.int16)
|
||||
|
||||
|
||||
def bagpipe_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Bagpipes — chanter reed with constant drone pressure.
|
||||
|
||||
The chanter (melody pipe) uses a double reed like an oboe
|
||||
but with more buzz and brightness. The constant air pressure
|
||||
from the bag means no dynamics — always ff.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float64) / SAMPLE_RATE
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
# Chanter — all harmonics, bright and reedy
|
||||
wave = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for n in range(1, 18):
|
||||
f_n = hz * n
|
||||
if f_n >= SAMPLE_RATE / 2:
|
||||
break
|
||||
# Peaked around harmonics 3-7 (the piercing brightness)
|
||||
amp = (1.0 / n) * numpy.exp(-0.03 * (n - 5) ** 2)
|
||||
phase = rng.uniform(0, 2 * numpy.pi)
|
||||
wave += amp * numpy.sin(2 * numpy.pi * f_n * t + phase)
|
||||
# Reed buzz — more than oboe
|
||||
reed = rng.normal(0, 0.08, n_samples)
|
||||
import scipy.signal as _sig
|
||||
lo = max(20, int(hz * 2))
|
||||
hi = min(SAMPLE_RATE // 2 - 1, int(hz * 8))
|
||||
if lo < hi:
|
||||
br, ar = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
|
||||
reed = _sig.lfilter(br, ar, reed).astype(numpy.float64) * 1.5
|
||||
wave += reed
|
||||
# Bag pressure wobble — very subtle
|
||||
bag = 1.0 + 0.02 * numpy.sin(2 * numpy.pi * 1.5 * t)
|
||||
wave *= bag
|
||||
mx = numpy.abs(wave).max()
|
||||
if mx > 0:
|
||||
wave /= mx
|
||||
return (peak * wave).astype(numpy.int16)
|
||||
|
||||
|
||||
def banjo_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Banjo — steel strings on a drum-head body.
|
||||
|
||||
The banjo's distinctive twang comes from the membrane head
|
||||
(like a drum skin) instead of a wooden soundboard. This gives
|
||||
a sharp attack, bright tone, and fast decay with a nasal,
|
||||
metallic quality. The 5th string drone adds shimmer.
|
||||
"""
|
||||
period = int(SAMPLE_RATE / hz)
|
||||
if period < 2:
|
||||
period = 2
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
|
||||
# Steel string — bright, sharp attack
|
||||
buf = rng.uniform(-0.9, 0.9, period).astype(numpy.float64)
|
||||
# Minimal filtering — banjo keeps the brightness
|
||||
for k in range(period - 1):
|
||||
buf[k] = 0.7 * buf[k] + 0.3 * buf[k + 1]
|
||||
|
||||
out = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for i in range(n_samples):
|
||||
out[i] = buf[i % period]
|
||||
next_idx = (i + 1) % period
|
||||
# Moderate decay — drum head rings but shorter than guitar
|
||||
buf[i % period] = 0.5 * (buf[i % period] + buf[next_idx]) * 0.9988
|
||||
|
||||
# Drum-head resonance — nasal, ringy, mid-frequency peaks
|
||||
# The membrane head rings more than wood — that's the twang
|
||||
import scipy.signal as _sig
|
||||
for center, bw, gain in [(600, 200, 0.5), (1500, 300, 0.4), (3000, 500, 0.25)]:
|
||||
lo = max(20, center - bw)
|
||||
hi = min(SAMPLE_RATE // 2 - 1, center + bw)
|
||||
if lo < hi:
|
||||
bp, ap = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
|
||||
out += _sig.lfilter(bp, ap, out) * gain
|
||||
|
||||
mx = numpy.abs(out).max()
|
||||
if mx > 0:
|
||||
out /= mx
|
||||
return (peak * out).astype(numpy.int16)
|
||||
|
||||
|
||||
def mandolin_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Mandolin — paired steel strings, bright and ringing.
|
||||
|
||||
The mandolin has 4 courses of paired strings, tuned in unison.
|
||||
The doubled strings create natural chorus. Bright attack from
|
||||
the plectrum, small body with high-frequency resonance.
|
||||
"""
|
||||
period = int(SAMPLE_RATE / hz)
|
||||
if period < 2:
|
||||
period = 2
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
|
||||
# Two strings per course — slightly detuned for natural chorus
|
||||
buf1 = rng.uniform(-0.8, 0.8, period).astype(numpy.float64)
|
||||
period2 = max(2, period + rng.integers(-1, 2))
|
||||
buf2 = rng.uniform(-0.8, 0.8, period2).astype(numpy.float64)
|
||||
# Light filtering — steel is brighter than nylon
|
||||
for k in range(period - 1):
|
||||
buf1[k] = 0.65 * buf1[k] + 0.35 * buf1[k + 1]
|
||||
for k in range(period2 - 1):
|
||||
buf2[k] = 0.65 * buf2[k] + 0.35 * buf2[k + 1]
|
||||
|
||||
out = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for i in range(n_samples):
|
||||
s1 = buf1[i % period]
|
||||
s2 = buf2[i % period2]
|
||||
out[i] = s1 * 0.55 + s2 * 0.45
|
||||
next1 = (i + 1) % period
|
||||
buf1[i % period] = 0.5 * (s1 + buf1[next1]) * 0.9988
|
||||
next2 = (i + 1) % period2
|
||||
buf2[i % period2] = 0.5 * (s2 + buf2[next2]) * 0.9988
|
||||
|
||||
# Small bright body — higher resonance than guitar
|
||||
import scipy.signal as _sig
|
||||
for center, bw, gain in [(500, 120, 0.3), (1000, 200, 0.25), (2000, 300, 0.15)]:
|
||||
lo = max(20, center - bw)
|
||||
hi = min(SAMPLE_RATE // 2 - 1, center + bw)
|
||||
if lo < hi:
|
||||
bp, ap = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
|
||||
out += _sig.lfilter(bp, ap, out) * gain
|
||||
|
||||
mx = numpy.abs(out).max()
|
||||
if mx > 0:
|
||||
out /= mx
|
||||
return (peak * out).astype(numpy.int16)
|
||||
|
||||
|
||||
def ukulele_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Ukulele — nylon strings on a small resonant body.
|
||||
|
||||
Brighter and thinner than guitar, shorter sustain. The small
|
||||
body gives a mid-heavy resonance (no deep bass). Nylon strings
|
||||
have a softer, warmer attack than steel.
|
||||
"""
|
||||
period = int(SAMPLE_RATE / hz)
|
||||
if period < 2:
|
||||
period = 2
|
||||
rng = numpy.random.default_rng(int(hz * 100) % 2**31)
|
||||
|
||||
# Nylon string — soft noise
|
||||
buf = rng.uniform(-0.5, 0.5, period).astype(numpy.float64)
|
||||
for _ in range(5):
|
||||
for k in range(period - 1):
|
||||
buf[k] = 0.55 * buf[k] + 0.45 * buf[k + 1]
|
||||
|
||||
out = numpy.zeros(n_samples, dtype=numpy.float64)
|
||||
for i in range(n_samples):
|
||||
out[i] = buf[i % period]
|
||||
next_idx = (i + 1) % period
|
||||
buf[i % period] = 0.5 * (buf[i % period] + buf[next_idx]) * 0.998
|
||||
|
||||
# Small body resonance — mid-heavy, no deep bass
|
||||
import scipy.signal as _sig
|
||||
for center, bw, gain in [(350, 100, 0.35), (700, 150, 0.25), (1200, 200, 0.15)]:
|
||||
lo = max(20, center - bw)
|
||||
hi = min(SAMPLE_RATE // 2 - 1, center + bw)
|
||||
if lo < hi:
|
||||
bp, ap = _sig.butter(2, [lo, hi], btype='band', fs=SAMPLE_RATE)
|
||||
out += _sig.lfilter(bp, ap, out) * gain
|
||||
|
||||
bl, al = _sig.butter(2, min(6000, hz * 12), btype='low', fs=SAMPLE_RATE)
|
||||
out = _sig.lfilter(bl, al, out)
|
||||
|
||||
mx = numpy.abs(out).max()
|
||||
if mx > 0:
|
||||
out /= mx
|
||||
return (peak * out).astype(numpy.int16)
|
||||
|
||||
|
||||
def acoustic_guitar_wave(hz, peak=SAMPLE_PEAK, n_samples=SAMPLE_RATE):
|
||||
"""Acoustic guitar — Karplus-Strong with wooden body resonance.
|
||||
|
||||
@@ -1388,8 +1741,11 @@ def _play_for(sample_wave, ms):
|
||||
"""Play the given NumPy sample array through the speakers."""
|
||||
normalized_wave = sample_wave.astype(numpy.float32) / SAMPLE_PEAK
|
||||
_sd = _get_sd()
|
||||
_sd.play(normalized_wave, SAMPLE_RATE)
|
||||
_sd.wait()
|
||||
try:
|
||||
_sd.play(normalized_wave, SAMPLE_RATE)
|
||||
_sd.wait()
|
||||
except KeyboardInterrupt:
|
||||
_sd.stop()
|
||||
|
||||
|
||||
class Synth(Enum):
|
||||
@@ -1434,6 +1790,16 @@ class Synth(Enum):
|
||||
SAXOPHONE = "saxophone_synth"
|
||||
GRANULAR = "granular_synth"
|
||||
VOCAL = "vocal_synth"
|
||||
PEDAL_STEEL = "pedal_steel_synth"
|
||||
THEREMIN = "theremin_synth"
|
||||
KALIMBA = "kalimba_synth"
|
||||
STEEL_DRUM = "steel_drum_synth"
|
||||
ACCORDION = "accordion_synth"
|
||||
DIDGERIDOO = "didgeridoo_synth"
|
||||
BAGPIPE = "bagpipe_synth"
|
||||
BANJO = "banjo_synth"
|
||||
MANDOLIN = "mandolin_synth"
|
||||
UKULELE = "ukulele_synth"
|
||||
ACOUSTIC_GUITAR = "acoustic_guitar_synth"
|
||||
SITAR = "sitar_synth"
|
||||
ELECTRIC_GUITAR = "electric_guitar_synth"
|
||||
@@ -1457,6 +1823,12 @@ _SYNTH_FUNCTIONS = {
|
||||
"harp_synth": harp_wave, "upright_bass_synth": upright_bass_wave,
|
||||
"timpani_synth": timpani_wave, "saxophone_synth": saxophone_wave,
|
||||
"granular_synth": granular_wave, "vocal_synth": vocal_wave,
|
||||
"pedal_steel_synth": pedal_steel_wave, "theremin_synth": theremin_wave,
|
||||
"kalimba_synth": kalimba_wave, "steel_drum_synth": steel_drum_wave,
|
||||
"accordion_synth": accordion_wave, "didgeridoo_synth": didgeridoo_wave,
|
||||
"bagpipe_synth": bagpipe_wave,
|
||||
"banjo_synth": banjo_wave, "mandolin_synth": mandolin_wave,
|
||||
"ukulele_synth": ukulele_wave,
|
||||
"acoustic_guitar_synth": acoustic_guitar_wave,
|
||||
"sitar_synth": sitar_wave, "electric_guitar_synth": electric_guitar_wave,
|
||||
}
|
||||
@@ -2212,6 +2584,68 @@ def _synth_mridangam_tha(n_samples):
|
||||
return out
|
||||
|
||||
|
||||
def _synth_cajon_bass(n_samples):
|
||||
"""Cajón bass — palm strike on center of the face.
|
||||
|
||||
Deep woody thump. The box resonates like a bass drum but with
|
||||
a warmer, more wooden character.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Wooden box thump
|
||||
thump_len = min(int(SAMPLE_RATE * 0.06), n_samples)
|
||||
thump_raw = _noise(thump_len)
|
||||
import scipy.signal as _sig
|
||||
if thump_len > 20:
|
||||
bl, al = _sig.butter(2, [40, 200], btype='band', fs=SAMPLE_RATE)
|
||||
thump = _sig.lfilter(bl, al, numpy.pad(thump_raw, (0, max(0, n_samples - thump_len))))[:thump_len].astype(numpy.float32)
|
||||
else:
|
||||
thump = thump_raw
|
||||
thump *= _exp_decay(thump_len, 18) * 0.8
|
||||
body = numpy.sin(2 * numpy.pi * 70 * t) * _exp_decay(n_samples, 7) * 0.8
|
||||
sub = _sine_f32(45, n_samples) * _exp_decay(n_samples, 9) * 0.4
|
||||
click_len = min(200, n_samples)
|
||||
click = _noise(click_len) * _exp_decay(click_len, 45) * 0.3
|
||||
result = body + sub
|
||||
result[:thump_len] += thump
|
||||
result[:click_len] += click
|
||||
return numpy.tanh(result * 1.3).astype(numpy.float32)
|
||||
|
||||
|
||||
def _synth_cajon_slap(n_samples):
|
||||
"""Cajón slap — fingers near the top edge, snare wires buzz.
|
||||
|
||||
Bright crack with a buzzy rattle from the internal snare wires.
|
||||
The signature cajón sound — like a snare but woodier.
|
||||
"""
|
||||
t = numpy.arange(n_samples, dtype=numpy.float32) / SAMPLE_RATE
|
||||
# Snare wire buzz
|
||||
wire = _noise(n_samples) * _exp_decay(n_samples, 18) * 0.6
|
||||
import scipy.signal as _sig
|
||||
bl, al = _sig.butter(2, [1500, 6000], btype='band', fs=SAMPLE_RATE)
|
||||
wire = _sig.lfilter(bl, al, wire).astype(numpy.float32) * 1.2
|
||||
# Wood body
|
||||
body = numpy.sin(2 * numpy.pi * 200 * t) * _exp_decay(n_samples, 22) * 0.4
|
||||
# Sharp slap
|
||||
slap_len = min(int(SAMPLE_RATE * 0.008), n_samples)
|
||||
slap = _noise(slap_len) * _exp_decay(slap_len, 200) * 0.8
|
||||
result = body + wire
|
||||
result[:slap_len] += slap
|
||||
return numpy.tanh(result * 1.5).astype(numpy.float32)
|
||||
|
||||
|
||||
def _synth_cajon_tap(n_samples):
|
||||
"""Cajón tap — light fingertip on the face. Ghost note."""
|
||||
n = min(n_samples, int(SAMPLE_RATE * 0.04))
|
||||
t = numpy.arange(n, dtype=numpy.float32) / SAMPLE_RATE
|
||||
tap = numpy.sin(2 * numpy.pi * 300 * t) * _exp_decay(n, 35) * 0.3
|
||||
pop = _noise(min(50, n)) * _exp_decay(min(50, n), 250) * 0.5
|
||||
result = tap
|
||||
result[:min(50, n)] += pop
|
||||
out = numpy.zeros(n_samples, dtype=numpy.float32)
|
||||
out[:n] = numpy.tanh(result * 1.5)
|
||||
return out
|
||||
|
||||
|
||||
def _synth_metal_kick(n_samples):
|
||||
"""Metal kick — punchy with beater click. Double-bass ready.
|
||||
|
||||
@@ -2480,6 +2914,10 @@ def _render_drum_hit(sound_value, n_samples):
|
||||
DrumSound.DJEMBE_BASS.value: lambda n: _synth_djembe_bass(n),
|
||||
DrumSound.DJEMBE_TONE.value: lambda n: _synth_djembe_tone(n),
|
||||
DrumSound.DJEMBE_SLAP.value: lambda n: _synth_djembe_slap(n),
|
||||
# Cajon
|
||||
DrumSound.CAJON_BASS.value: lambda n: _synth_cajon_bass(n),
|
||||
DrumSound.CAJON_SLAP.value: lambda n: _synth_cajon_slap(n),
|
||||
DrumSound.CAJON_TAP.value: lambda n: _synth_cajon_tap(n),
|
||||
# Metal kit
|
||||
DrumSound.METAL_KICK.value: lambda n: _synth_metal_kick(n),
|
||||
DrumSound.METAL_SNARE.value: lambda n: _synth_metal_snare(n),
|
||||
@@ -4069,6 +4507,10 @@ def render_score(score):
|
||||
DrumSound.DJEMBE_BASS.value: 0.0,
|
||||
DrumSound.DJEMBE_TONE.value: 0.1,
|
||||
DrumSound.DJEMBE_SLAP.value: -0.1,
|
||||
# Cajon — centered (single instrument)
|
||||
DrumSound.CAJON_BASS.value: 0.0,
|
||||
DrumSound.CAJON_SLAP.value: 0.0,
|
||||
DrumSound.CAJON_TAP.value: 0.1,
|
||||
# Metal kit
|
||||
DrumSound.METAL_KICK.value: 0.0,
|
||||
DrumSound.METAL_SNARE.value: 0.0,
|
||||
@@ -4195,8 +4637,11 @@ def play_score(score):
|
||||
"""
|
||||
buf = render_score(score)
|
||||
_sd = _get_sd()
|
||||
_sd.play(buf, SAMPLE_RATE)
|
||||
_sd.wait()
|
||||
try:
|
||||
_sd.play(buf, SAMPLE_RATE)
|
||||
_sd.wait()
|
||||
except KeyboardInterrupt:
|
||||
_sd.stop()
|
||||
|
||||
|
||||
# ── MIDI export ─────────────────────────────────────────────────────────────
|
||||
|
||||
+176
-7
@@ -77,6 +77,7 @@ def cmd_help(session, args):
|
||||
Parts:
|
||||
part lead saw pluck score.part("lead", synth="saw", envelope="pluck")
|
||||
part bass sine score.part("bass", synth="sine")
|
||||
part lead instrument piano score.part("lead", instrument="piano")
|
||||
part list all parts
|
||||
|
||||
Notes (on active part):
|
||||
@@ -85,6 +86,12 @@ def cmd_help(session, args):
|
||||
rest 2 part.rest(2.0)
|
||||
arp Am updown 2 2 part.arpeggio("Am", pattern="updown", bars=2, octaves=2)
|
||||
prog I V vi IV part adds key.progression(...)
|
||||
strum Am 2 down part.strum("Am", 2, direction="down")
|
||||
strum G 2 up 0.1 lazy strum (strum_time=0.1)
|
||||
roll C3 4 part.roll("C3", 4) — timpani/tremolo
|
||||
roll C3 4 30 110 roll with velocity ramp
|
||||
bend C5 1 2 part.add("C5", 1, bend=2) — bend up 2 semitones
|
||||
bend C5 1 -1 bend down a half step
|
||||
|
||||
Effects (on active part):
|
||||
reverb 0.4 reverb=0.4
|
||||
@@ -110,6 +117,12 @@ def cmd_help(session, args):
|
||||
fingering Am guitar chord fingering
|
||||
diagram [mode] [frets] scale diagram on guitar
|
||||
|
||||
Tuning:
|
||||
temperament equal set temperament (equal/pythagorean/meantone/just)
|
||||
temperament show current temperament
|
||||
reference 432 set reference pitch (default 440)
|
||||
instruments list all available instruments
|
||||
|
||||
Session:
|
||||
show score info
|
||||
status current state
|
||||
@@ -197,12 +210,22 @@ def cmd_part(session, args):
|
||||
return
|
||||
|
||||
name = args[0]
|
||||
synth = args[1] if len(args) > 1 else "saw"
|
||||
envelope = args[2] if len(args) > 2 else "pluck"
|
||||
|
||||
if name not in session.parts:
|
||||
session.parts[name] = session.score.part(name, synth=synth, envelope=envelope)
|
||||
print(f" score.part(\"{name}\", synth=\"{synth}\", envelope=\"{envelope}\")")
|
||||
# Check if second arg is "instrument" keyword or an instrument name
|
||||
if len(args) > 1 and args[1] == "instrument" and len(args) > 2:
|
||||
instrument = args[2]
|
||||
session.parts[name] = session.score.part(name, instrument=instrument)
|
||||
print(f" score.part(\"{name}\", instrument=\"{instrument}\")")
|
||||
elif len(args) > 1 and args[1] in _INSTRUMENT_NAMES:
|
||||
instrument = args[1]
|
||||
session.parts[name] = session.score.part(name, instrument=instrument)
|
||||
print(f" score.part(\"{name}\", instrument=\"{instrument}\")")
|
||||
else:
|
||||
synth = args[1] if len(args) > 1 else "saw"
|
||||
envelope = args[2] if len(args) > 2 else "pluck"
|
||||
session.parts[name] = session.score.part(name, synth=synth, envelope=envelope)
|
||||
print(f" score.part(\"{name}\", synth=\"{synth}\", envelope=\"{envelope}\")")
|
||||
else:
|
||||
print(f" → {name}")
|
||||
session.current_part = session.parts[name]
|
||||
@@ -534,6 +557,97 @@ def cmd_identify(session, args):
|
||||
print(f" error: {e}")
|
||||
|
||||
|
||||
def cmd_strum(session, args):
|
||||
"""Strum a chord on a fretboard-equipped part."""
|
||||
if not args:
|
||||
print(" usage: strum Am [beats] [down|up] [strum_time]")
|
||||
return
|
||||
part = _require_part(session)
|
||||
chord_name = args[0]
|
||||
beats = float(args[1]) if len(args) > 1 else 1.0
|
||||
direction = args[2] if len(args) > 2 else "down"
|
||||
strum_time = float(args[3]) if len(args) > 3 else 0.05
|
||||
try:
|
||||
part.strum(chord_name, beats, direction=direction, strum_time=strum_time)
|
||||
print(f" .strum(\"{chord_name}\", {beats}, direction=\"{direction}\", "
|
||||
f"strum_time={strum_time})")
|
||||
except Exception as e:
|
||||
print(f" error: {e}")
|
||||
|
||||
|
||||
def cmd_roll(session, args):
|
||||
"""Play a roll (rapid repeated notes with velocity ramp)."""
|
||||
if not args:
|
||||
print(" usage: roll C3 [beats] [vel_start] [vel_end]")
|
||||
return
|
||||
part = _require_part(session)
|
||||
tone = args[0]
|
||||
beats = float(args[1]) if len(args) > 1 else 4.0
|
||||
vel_start = int(args[2]) if len(args) > 2 else 40
|
||||
vel_end = int(args[3]) if len(args) > 3 else 100
|
||||
try:
|
||||
part.roll(tone, beats, velocity_start=vel_start, velocity_end=vel_end)
|
||||
print(f" .roll(\"{tone}\", {beats}, velocity_start={vel_start}, "
|
||||
f"velocity_end={vel_end})")
|
||||
except Exception as e:
|
||||
print(f" error: {e}")
|
||||
|
||||
|
||||
def cmd_bend(session, args):
|
||||
"""Add a note with pitch bend."""
|
||||
if len(args) < 3:
|
||||
print(" usage: bend C5 1 2 (note, beats, semitones)")
|
||||
print(" bend C5 1 -1 (bend down)")
|
||||
return
|
||||
part = _require_part(session)
|
||||
note = args[0]
|
||||
beats = float(args[1])
|
||||
bend = float(args[2])
|
||||
bend_type = args[3] if len(args) > 3 else "smooth"
|
||||
try:
|
||||
part.add(note, beats, bend=bend, bend_type=bend_type)
|
||||
print(f" .add(\"{note}\", {beats}, bend={bend}, bend_type=\"{bend_type}\")")
|
||||
except Exception as e:
|
||||
print(f" error: {e}")
|
||||
|
||||
|
||||
def cmd_temperament(session, args):
|
||||
"""Set or show the tuning temperament."""
|
||||
if not args:
|
||||
temp = getattr(session.score, 'temperament', 'equal')
|
||||
ref = getattr(session.score, 'reference_pitch', 440.0)
|
||||
print(f" temperament={temp} reference={ref} Hz")
|
||||
print(f" available: equal, pythagorean, meantone, just")
|
||||
return
|
||||
temp = args[0]
|
||||
valid = ["equal", "pythagorean", "meantone", "just"]
|
||||
if temp not in valid:
|
||||
print(f" unknown temperament: {temp}")
|
||||
print(f" available: {', '.join(valid)}")
|
||||
return
|
||||
session.score.temperament = temp
|
||||
print(f" temperament={temp}")
|
||||
|
||||
|
||||
def cmd_reference(session, args):
|
||||
"""Set the reference pitch (A4 frequency)."""
|
||||
if not args:
|
||||
ref = getattr(session.score, 'reference_pitch', 440.0)
|
||||
print(f" reference={ref} Hz")
|
||||
return
|
||||
ref = float(args[0])
|
||||
session.score.reference_pitch = ref
|
||||
print(f" reference={ref} Hz")
|
||||
|
||||
|
||||
def cmd_instruments(session, args):
|
||||
"""List all available instruments."""
|
||||
cols = 3
|
||||
for i in range(0, len(_INSTRUMENT_NAMES), cols):
|
||||
row = _INSTRUMENT_NAMES[i:i + cols]
|
||||
print(" " + " ".join(f"{name:<22s}" for name in row))
|
||||
|
||||
|
||||
def cmd_circle(session, args):
|
||||
"""Show circle of fifths."""
|
||||
tonic = args[0] if args else session.key.tonic_name
|
||||
@@ -560,7 +674,10 @@ def cmd_clear(session, args):
|
||||
def cmd_status(session, args):
|
||||
parts = ", ".join(session.parts.keys()) if session.parts else "none"
|
||||
active = session.current_part.name if session.current_part else "none"
|
||||
temp = getattr(session.score, 'temperament', 'equal')
|
||||
ref = getattr(session.score, 'reference_pitch', 440.0)
|
||||
print(f" key={session.key} bpm={session.bpm} swing={session.swing}")
|
||||
print(f" temperament={temp} reference={ref} Hz")
|
||||
print(f" drums={session._drum_preset or 'none'} parts=[{parts}] active={active}")
|
||||
|
||||
|
||||
@@ -607,6 +724,12 @@ COMMANDS = {
|
||||
"interval": cmd_interval,
|
||||
"identify": cmd_identify, "id": cmd_identify,
|
||||
"circle": cmd_circle,
|
||||
"strum": cmd_strum,
|
||||
"roll": cmd_roll,
|
||||
"bend": cmd_bend,
|
||||
"temperament": cmd_temperament, "temp": cmd_temperament,
|
||||
"reference": cmd_reference, "ref": cmd_reference,
|
||||
"instruments": cmd_instruments,
|
||||
"clear": cmd_clear,
|
||||
"status": cmd_status,
|
||||
}
|
||||
@@ -653,9 +776,43 @@ def _prompt(session):
|
||||
# ── Tab completion ─────────────────────────────────────────────────────────
|
||||
|
||||
_SYNTH_NAMES = ["sine", "saw", "triangle", "square", "pulse", "fm",
|
||||
"noise", "supersaw", "pwm_slow", "pwm_fast"]
|
||||
"noise", "supersaw", "pwm_slow", "pwm_fast",
|
||||
"pedal_steel_synth", "theremin_synth", "kalimba_synth",
|
||||
"steel_drum_synth", "accordion_synth", "didgeridoo_synth",
|
||||
"bagpipe_synth", "banjo_synth", "mandolin_synth",
|
||||
"ukulele_synth", "vocal_synth", "granular_synth",
|
||||
"piano_synth", "organ_synth", "harpsichord_synth",
|
||||
"strings_synth", "cello_synth", "flute_synth",
|
||||
"clarinet_synth", "oboe_synth", "trumpet_synth",
|
||||
"acoustic_guitar_synth", "electric_guitar_synth",
|
||||
"bass_guitar_synth", "upright_bass_synth", "harp_synth",
|
||||
"sitar_synth", "pluck_synth", "saxophone_synth",
|
||||
"marimba_synth", "timpani_synth"]
|
||||
_INSTRUMENT_NAMES = [
|
||||
# Keys
|
||||
"piano", "electric_piano", "organ", "harpsichord", "celesta", "music_box",
|
||||
# Strings
|
||||
"violin", "viola", "cello", "contrabass", "string_ensemble",
|
||||
# Woodwinds
|
||||
"flute", "clarinet", "oboe", "bassoon",
|
||||
# Brass
|
||||
"trumpet", "trombone", "french_horn", "tuba", "brass_ensemble",
|
||||
# Plucked
|
||||
"acoustic_guitar", "electric_guitar", "clean_guitar", "crunch_guitar",
|
||||
"distorted_guitar", "orange_crunch", "metal_guitar", "bass_guitar",
|
||||
"upright_bass", "harp", "sitar", "pedal_steel", "theremin", "kalimba",
|
||||
"steel_drum", "accordion", "didgeridoo", "bagpipe", "banjo", "mandolin",
|
||||
"mandola", "ukulele", "koto",
|
||||
# Synth presets
|
||||
"synth_lead", "synth_pad", "synth_bass", "acid_bass",
|
||||
"granular_pad", "vocal", "choir", "granular_texture", "808_bass",
|
||||
# Percussion / Mallet
|
||||
"vibraphone", "marimba", "xylophone", "glockenspiel", "tubular_bells", "timpani",
|
||||
# Woodwinds (continued)
|
||||
"saxophone", "alto_sax", "tenor_sax", "bari_sax",
|
||||
]
|
||||
_ENVELOPE_NAMES = ["piano", "pluck", "pad", "organ", "bell", "strings",
|
||||
"staccato", "none"]
|
||||
"staccato", "bowed", "mallet", "none"]
|
||||
_ARP_PATTERNS = ["up", "down", "updown", "downup", "random"]
|
||||
_LFO_SHAPES = ["sine", "triangle", "saw", "square"]
|
||||
_SYSTEMS = ["western", "indian", "arabic", "japanese", "blues", "gamelan"]
|
||||
@@ -667,7 +824,7 @@ _CHORD_SUFFIXES = ["", "m", "7", "m7", "maj7", "dim", "aug", "sus2", "sus4",
|
||||
# Context-aware completions for the second word
|
||||
_ARG_COMPLETIONS = {
|
||||
"drums": lambda: Pattern.list_presets(),
|
||||
"part": lambda: _SYNTH_NAMES,
|
||||
"part": lambda: _SYNTH_NAMES + _INSTRUMENT_NAMES,
|
||||
"key": lambda: [f"{n}m" for n in _NOTE_NAMES[:12]] + _NOTE_NAMES[:12],
|
||||
"arp": lambda: [f"{n}{s}" for n in _NOTE_NAMES[:7] for s in _CHORD_SUFFIXES[:6]],
|
||||
"add": lambda: [f"{n}{o}" for n in _NOTE_NAMES[:12] for o in ["3", "4", "5"]],
|
||||
@@ -679,6 +836,12 @@ _ARG_COMPLETIONS = {
|
||||
"lowpass_q", "reverb_decay", "delay_time", "delay_feedback",
|
||||
"distortion_drive"],
|
||||
"identify": lambda: [f"{n}{s}" for n in _NOTE_NAMES[:7] for s in _CHORD_SUFFIXES[:6]],
|
||||
"strum": lambda: [f"{n}{s}" for n in _NOTE_NAMES[:7] for s in _CHORD_SUFFIXES[:6]],
|
||||
"roll": lambda: [f"{n}{o}" for n in _NOTE_NAMES[:12] for o in ["2", "3", "4", "5"]],
|
||||
"bend": lambda: [f"{n}{o}" for n in _NOTE_NAMES[:12] for o in ["3", "4", "5"]],
|
||||
"temperament": lambda: ["equal", "pythagorean", "meantone", "just"],
|
||||
"reference": lambda: ["440", "432", "415", "444"],
|
||||
"instruments": lambda: _INSTRUMENT_NAMES,
|
||||
}
|
||||
|
||||
|
||||
@@ -705,6 +868,12 @@ def _completer(text, state):
|
||||
elif cmd == "arp" and len(tokens) == 3:
|
||||
# Pattern for arp
|
||||
options = [p for p in _ARP_PATTERNS if p.startswith(text)]
|
||||
elif cmd == "strum" and len(tokens) == 4:
|
||||
# Direction for strum
|
||||
options = [d for d in ["down", "up"] if d.startswith(text)]
|
||||
elif cmd == "bend" and len(tokens) == 5:
|
||||
# Bend type
|
||||
options = [t for t in ["smooth", "linear", "late"] if t.startswith(text)]
|
||||
elif cmd == "lfo" and len(tokens) >= 7:
|
||||
# Shape for lfo
|
||||
options = [s for s in _LFO_SHAPES if s.startswith(text)]
|
||||
|
||||
+112
-2
@@ -195,6 +195,54 @@ INSTRUMENTS = {
|
||||
"detune": 12, "lowpass": 3000, "lowpass_q": 1.5,
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"pedal_steel": {
|
||||
"synth": "pedal_steel_synth", "envelope": "strings",
|
||||
"reverb": 0.3, "reverb_type": "spring",
|
||||
"humanize": 0.15,
|
||||
},
|
||||
"theremin": {
|
||||
"synth": "theremin_synth", "envelope": "pad",
|
||||
"legato": True, "glide": 0.05,
|
||||
"reverb": 0.3, "reverb_type": "plate",
|
||||
},
|
||||
"kalimba": {
|
||||
"synth": "kalimba_synth", "envelope": "none",
|
||||
"reverb": 0.35, "reverb_type": "plate",
|
||||
},
|
||||
"steel_drum": {
|
||||
"synth": "steel_drum_synth", "envelope": "none",
|
||||
"reverb": 0.3, "reverb_type": "plate",
|
||||
},
|
||||
"accordion": {
|
||||
"synth": "accordion_synth", "envelope": "organ",
|
||||
"humanize": 0.15,
|
||||
},
|
||||
"didgeridoo": {
|
||||
"synth": "didgeridoo_synth", "envelope": "pad",
|
||||
"lowpass": 1500,
|
||||
"reverb": 0.4, "reverb_type": "cave",
|
||||
},
|
||||
"bagpipe": {
|
||||
"synth": "bagpipe_synth", "envelope": "organ",
|
||||
"lowpass": 4000,
|
||||
},
|
||||
"banjo": {
|
||||
"synth": "banjo_synth", "envelope": "none",
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"mandolin": {
|
||||
"synth": "mandolin_synth", "envelope": "none",
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"mandola": {
|
||||
"synth": "mandolin_synth", "envelope": "none",
|
||||
"lowpass": 3000,
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"ukulele": {
|
||||
"synth": "ukulele_synth", "envelope": "none",
|
||||
"humanize": 0.2,
|
||||
},
|
||||
"koto": {
|
||||
"synth": "pluck_synth", "envelope": "none",
|
||||
"lowpass": 4000,
|
||||
@@ -479,6 +527,10 @@ class DrumSound(Enum):
|
||||
DJEMBE_BASS = 102 # open bass (center of head)
|
||||
DJEMBE_TONE = 103 # open tone (edge, fingers together)
|
||||
DJEMBE_SLAP = 104 # slap (edge, fingers spread, sharp crack)
|
||||
# Cajon sounds
|
||||
CAJON_BASS = 108 # center of face, deep thump
|
||||
CAJON_SLAP = 109 # top edge, snare wires buzz
|
||||
CAJON_TAP = 110 # light finger tap
|
||||
# Metal kit — tighter, punchier, more attack
|
||||
METAL_KICK = 105 # clicky, punchy, tight
|
||||
METAL_SNARE = 106 # crack, bright, cutting
|
||||
@@ -1510,6 +1562,50 @@ Pattern._PRESETS["tabla solo"] = dict(
|
||||
],
|
||||
)
|
||||
|
||||
# ── Cajón patterns ────────────────────────────────────────────────────────
|
||||
CB = DrumSound.CAJON_BASS
|
||||
CSL = DrumSound.CAJON_SLAP
|
||||
CT = DrumSound.CAJON_TAP
|
||||
|
||||
# Cajón flamenco — the classic acoustic percussion groove
|
||||
Pattern._PRESETS["cajon"] = dict(
|
||||
name="cajon",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(CB, 0.0, 85), _h(CT, 0.5, 35), _h(CT, 0.75, 38),
|
||||
_h(CSL, 1.0, 80), _h(CT, 1.5, 32),
|
||||
_h(CB, 2.0, 82), _h(CT, 2.5, 35), _h(CT, 2.75, 40),
|
||||
_h(CSL, 3.0, 82), _h(CT, 3.25, 30), _h(CT, 3.5, 35),
|
||||
],
|
||||
)
|
||||
|
||||
# Cajón rumba — Latin-flavored
|
||||
Pattern._PRESETS["cajon rumba"] = dict(
|
||||
name="cajon rumba",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(CB, 0.0, 88), _h(CT, 0.5, 38),
|
||||
_h(CSL, 1.0, 78), _h(CT, 1.25, 32), _h(CB, 1.5, 72),
|
||||
_h(CSL, 2.0, 82), _h(CT, 2.5, 35),
|
||||
_h(CB, 3.0, 75), _h(CSL, 3.5, 80), _h(CT, 3.75, 38),
|
||||
],
|
||||
)
|
||||
|
||||
# Cajón singer-songwriter — simple, supportive
|
||||
Pattern._PRESETS["cajon folk"] = dict(
|
||||
name="cajon folk",
|
||||
time_signature="4/4",
|
||||
beats=4.0,
|
||||
hits=[
|
||||
_h(CB, 0.0, 80),
|
||||
_h(CSL, 1.0, 72), _h(CT, 1.5, 30),
|
||||
_h(CB, 2.0, 78),
|
||||
_h(CSL, 3.0, 75),
|
||||
],
|
||||
)
|
||||
|
||||
# ── Metal kit patterns ────────────────────────────────────────────────────
|
||||
MK = DrumSound.METAL_KICK
|
||||
MS = DrumSound.METAL_SNARE
|
||||
@@ -2409,7 +2505,7 @@ class Part:
|
||||
|
||||
def strum(self, chord_name: str, duration=Duration.QUARTER, *,
|
||||
direction: str = "down", velocity: int = 100,
|
||||
strum_time: float = 0.08) -> "Part":
|
||||
strum_time: float = 0.05) -> "Part":
|
||||
"""Strum a chord using the part's fretboard fingering.
|
||||
|
||||
Looks up the chord on the fretboard, gets the fingering, and
|
||||
@@ -2477,7 +2573,21 @@ class Part:
|
||||
from .chords import Chord as ChordClass
|
||||
chord_obj = ChordClass(tones=strum_tones)
|
||||
|
||||
self.add(chord_obj, total_beats, velocity=velocity)
|
||||
# Strum sweep: quick individual string hits before the chord.
|
||||
# Only the first 2-3 strings get a tiny grace note, the rest
|
||||
# ring together as the full chord. Gives the strum feel without
|
||||
# sounding like separate plucks.
|
||||
n_strings = len(strum_tones)
|
||||
if strum_time > 0.02 and n_strings >= 3:
|
||||
n_grace = min(2, n_strings - 1)
|
||||
per_grace = strum_time / n_grace
|
||||
grace_vel = max(1, int(velocity * 0.25))
|
||||
for i in range(n_grace):
|
||||
self.add(strum_tones[i], per_grace, velocity=grace_vel)
|
||||
ring = max(0.1, total_beats - strum_time)
|
||||
self.add(chord_obj, ring, velocity=velocity)
|
||||
else:
|
||||
self.add(chord_obj, total_beats, velocity=velocity)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
+241
-2
@@ -5320,7 +5320,7 @@ def test_supersaw_wave():
|
||||
@needs_portaudio
|
||||
def test_all_synths_in_enum():
|
||||
from pytheory.play import Synth
|
||||
assert len(Synth) == 30
|
||||
assert len(Synth) == 41
|
||||
for s in Synth:
|
||||
wave = s(440, n_samples=1000)
|
||||
assert len(wave) == 1000
|
||||
@@ -6827,7 +6827,7 @@ def test_strum_direction():
|
||||
p = score.part("g", instrument="acoustic_guitar", fretboard=fb)
|
||||
p.strum("G", Duration.QUARTER, direction="down")
|
||||
p.strum("G", Duration.QUARTER, direction="up")
|
||||
assert len(p.notes) == 2
|
||||
assert len(p.notes) >= 2 # grace notes + chord per strum
|
||||
|
||||
|
||||
# ── World drums ──────────────────────────────────────────────────────────────
|
||||
@@ -6912,3 +6912,242 @@ def test_clean_guitar_preset():
|
||||
p = score.part("g", instrument="clean_guitar")
|
||||
assert p.synth == "electric_guitar_synth"
|
||||
assert p.cabinet > 0
|
||||
|
||||
|
||||
# ── New instrument synths (v0.36+) ──────────────────────────────────────────
|
||||
|
||||
def test_new_synths_render():
|
||||
"""All 7 new synths produce valid audio."""
|
||||
from pytheory.play import (pedal_steel_wave, theremin_wave, kalimba_wave,
|
||||
steel_drum_wave, accordion_wave,
|
||||
didgeridoo_wave, bagpipe_wave,
|
||||
banjo_wave, mandolin_wave, ukulele_wave,
|
||||
vocal_wave, SAMPLE_RATE)
|
||||
synths = [pedal_steel_wave, theremin_wave, kalimba_wave, steel_drum_wave,
|
||||
accordion_wave, didgeridoo_wave, bagpipe_wave,
|
||||
banjo_wave, mandolin_wave, ukulele_wave, vocal_wave]
|
||||
for fn in synths:
|
||||
wave = fn(440, n_samples=11025)
|
||||
assert len(wave) == 11025
|
||||
assert wave.dtype == numpy.int16
|
||||
assert numpy.abs(wave).max() > 0
|
||||
|
||||
|
||||
def test_vocal_synth_with_lyric():
|
||||
"""Vocal synth accepts lyric parameter."""
|
||||
from pytheory.play import vocal_wave
|
||||
for lyric in ["ah", "ee", "oh", "oo", "hi", "la"]:
|
||||
wave = vocal_wave(330, n_samples=11025, lyric=lyric)
|
||||
assert len(wave) == 11025
|
||||
assert numpy.abs(wave).max() > 0
|
||||
|
||||
|
||||
def test_vocal_different_vowels_differ():
|
||||
"""Different vowels should produce different waveforms."""
|
||||
from pytheory.play import vocal_wave
|
||||
ah = vocal_wave(330, n_samples=22050, lyric="ah")
|
||||
ee = vocal_wave(330, n_samples=22050, lyric="ee")
|
||||
# They should differ (different formant peaks)
|
||||
assert not numpy.array_equal(ah, ee)
|
||||
|
||||
|
||||
def test_all_instrument_presets_create():
|
||||
"""Every instrument preset in INSTRUMENTS should create a valid Part."""
|
||||
from pytheory import Score
|
||||
from pytheory.rhythm import INSTRUMENTS
|
||||
for name in INSTRUMENTS:
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("test", instrument=name)
|
||||
assert p.synth is not None
|
||||
|
||||
|
||||
def test_new_instrument_presets():
|
||||
"""New instrument presets have correct synths."""
|
||||
from pytheory import Score
|
||||
presets = {
|
||||
"pedal_steel": "pedal_steel_synth",
|
||||
"theremin": "theremin_synth",
|
||||
"kalimba": "kalimba_synth",
|
||||
"steel_drum": "steel_drum_synth",
|
||||
"accordion": "accordion_synth",
|
||||
"didgeridoo": "didgeridoo_synth",
|
||||
"bagpipe": "bagpipe_synth",
|
||||
"banjo": "banjo_synth",
|
||||
"mandolin": "mandolin_synth",
|
||||
"ukulele": "ukulele_synth",
|
||||
}
|
||||
for name, expected_synth in presets.items():
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("t", instrument=name)
|
||||
assert p.synth == expected_synth, f"{name} has {p.synth}, expected {expected_synth}"
|
||||
|
||||
|
||||
# ── Cajón drums ─────────────────────────────────────────────────────────────
|
||||
|
||||
def test_cajon_sounds_render():
|
||||
from pytheory.play import _render_drum_hit
|
||||
from pytheory.rhythm import DrumSound
|
||||
for sound in [DrumSound.CAJON_BASS, DrumSound.CAJON_SLAP, DrumSound.CAJON_TAP]:
|
||||
wave = _render_drum_hit(sound.value, 22050)
|
||||
assert len(wave) == 22050
|
||||
assert wave.dtype == numpy.float32
|
||||
|
||||
|
||||
def test_cajon_patterns():
|
||||
from pytheory.rhythm import Pattern
|
||||
for name in ["cajon", "cajon rumba", "cajon folk"]:
|
||||
p = Pattern.preset(name)
|
||||
assert p.beats > 0
|
||||
|
||||
|
||||
# ── Pitch bends ─────────────────────────────────────────────────────────────
|
||||
|
||||
def test_pitch_bend_renders():
|
||||
"""Pitch bend should produce valid audio without errors."""
|
||||
from pytheory import Score, Duration
|
||||
from pytheory.play import render_score
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("t", instrument="electric_guitar")
|
||||
p.add("A4", Duration.HALF, bend=2, bend_type="smooth")
|
||||
p.add("A4", Duration.HALF, bend=-1, bend_type="late")
|
||||
p.add("A4", Duration.HALF, bend=3, bend_type="linear")
|
||||
p.add("A4", Duration.HALF)
|
||||
buf = render_score(score)
|
||||
assert len(buf) > 0
|
||||
|
||||
|
||||
def test_pitch_bend_types():
|
||||
"""All three bend types should work."""
|
||||
from pytheory.rhythm import Note, Duration
|
||||
for bt in ["smooth", "linear", "late"]:
|
||||
n = Note(tone=None, duration=Duration.QUARTER, bend=2, bend_type=bt)
|
||||
assert n.bend_type == bt
|
||||
|
||||
|
||||
# ── Roll method ─────────────────────────────────────────────────────────────
|
||||
|
||||
def test_roll_adds_notes():
|
||||
from pytheory import Score, Duration
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("t", instrument="timpani")
|
||||
p.roll("C3", Duration.WHOLE, velocity_start=30, velocity_end=100)
|
||||
assert len(p.notes) > 4 # should be many 16th notes
|
||||
|
||||
|
||||
def test_roll_velocity_ramp():
|
||||
from pytheory import Score, Duration
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("t", instrument="timpani")
|
||||
p.roll("C3", Duration.WHOLE, velocity_start=20, velocity_end=100)
|
||||
velocities = [n.velocity for n in p.notes]
|
||||
# First should be quieter than last
|
||||
assert velocities[0] < velocities[-1]
|
||||
|
||||
|
||||
def test_roll_custom_speed():
|
||||
from pytheory import Score, Duration
|
||||
score = Score("4/4", bpm=120)
|
||||
p = score.part("t", synth="sine")
|
||||
p.roll("A4", Duration.WHOLE, speed=0.125) # 32nd notes
|
||||
# 4 beats / 0.125 = 32 notes
|
||||
assert len(p.notes) == 32
|
||||
|
||||
|
||||
# ── Int tone names ──────────────────────────────────────────────────────────
|
||||
|
||||
def test_int_tone_name():
|
||||
from pytheory import Tone, TET
|
||||
edo = TET(22)
|
||||
t = Tone(0, octave=4, system=edo)
|
||||
assert t.name == "0"
|
||||
assert t.frequency == pytest.approx(440.0, rel=1e-3)
|
||||
|
||||
|
||||
def test_int_tone_wrapping():
|
||||
from pytheory import Tone, TET
|
||||
edo = TET(22)
|
||||
t = Tone(22, octave=4, system=edo)
|
||||
assert t.name == "0"
|
||||
assert t.octave == 5
|
||||
assert t.frequency == pytest.approx(880.0, rel=1e-3)
|
||||
|
||||
|
||||
def test_int_tone_negative():
|
||||
from pytheory import Tone, TET
|
||||
edo = TET(22)
|
||||
t = Tone(-1, octave=4, system=edo)
|
||||
assert t.name == "21"
|
||||
assert t.octave == 3
|
||||
|
||||
|
||||
def test_system_tone_method():
|
||||
from pytheory import TET
|
||||
edo = TET(19)
|
||||
t = edo.tone(5, octave=4)
|
||||
assert t.name == "5"
|
||||
assert t.octave == 4
|
||||
|
||||
|
||||
# ── B#/Cb octave boundary ──────────────────────────────────────────────────
|
||||
|
||||
def test_b_sharp_octave():
|
||||
t = Tone("B#4")
|
||||
assert t.octave == 5
|
||||
assert t.frequency == pytest.approx(Tone("C5").frequency, rel=1e-3)
|
||||
|
||||
|
||||
def test_c_flat_octave():
|
||||
t = Tone("Cb4")
|
||||
assert t.octave == 3
|
||||
assert t.frequency == pytest.approx(Tone("B3").frequency, rel=1e-3)
|
||||
|
||||
|
||||
# ── Note choking ────────────────────────────────────────────────────────────
|
||||
|
||||
def test_note_choking_renders():
|
||||
"""Fast repeated notes should render without errors (choking active)."""
|
||||
from pytheory import Score, Duration
|
||||
from pytheory.play import render_score
|
||||
score = Score("4/4", bpm=200)
|
||||
p = score.part("t", instrument="piano")
|
||||
for _ in range(32):
|
||||
p.add("C4", Duration.SIXTEENTH)
|
||||
buf = render_score(score)
|
||||
assert len(buf) > 0
|
||||
|
||||
|
||||
# ── Score system/temperament ───────────────────────────────────────────────
|
||||
|
||||
def test_score_temperament():
|
||||
from pytheory import Score
|
||||
score = Score("4/4", bpm=120, temperament="just")
|
||||
assert score.temperament == "just"
|
||||
|
||||
|
||||
def test_score_reference_pitch():
|
||||
from pytheory import Score
|
||||
score = Score("4/4", bpm=120, reference_pitch=415.0)
|
||||
assert score.reference_pitch == 415.0
|
||||
|
||||
|
||||
def test_score_system_propagates():
|
||||
from pytheory import Score, SYSTEMS
|
||||
shruti = SYSTEMS["shruti"]
|
||||
score = Score("4/4", bpm=120, system=shruti)
|
||||
p = score.part("t", synth="sine")
|
||||
assert p._system is shruti
|
||||
|
||||
|
||||
# ── Synth enum count ────────────────────────────────────────────────────────
|
||||
|
||||
def test_synth_enum_count():
|
||||
from pytheory.play import Synth
|
||||
assert len(Synth) == 41
|
||||
|
||||
|
||||
def test_all_synths_render_and_enum_match():
|
||||
"""Every Synth enum member should render valid audio."""
|
||||
from pytheory.play import Synth
|
||||
for s in Synth:
|
||||
wave = s(440, n_samples=1000)
|
||||
assert len(wave) == 1000
|
||||
|
||||
Reference in New Issue
Block a user