Last active
March 26, 2026 15:44
-
-
Save kennethreitz/009d56ad9325b710489874714cf20197 to your computer and use it in GitHub Desktop.
Mixing major and minor pentatonic over the same blues changes — PyTheory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """Mixing major and minor pentatonic over the same changes. | |
| "Once you start mixing major and minor pentatonic over the same changes | |
| it's a whole different world. That's where the magic lives." | |
| Uses PyTheory's blues scale system — the notes come from the scales, | |
| not hardcoded. Change the tonic and it works in any key. | |
| Requires: pip install pytheory | |
| brew install portaudio (macOS) | |
| """ | |
| import random | |
| from pytheory import Score, Pattern, Duration, Chord, TonedScale | |
| from pytheory.play import render_score, SAMPLE_RATE | |
| import sounddevice as sd | |
| # ── The two scales ────────────────────────────────────────────────── | |
| TONIC = "A" | |
| ts = TonedScale(tonic=f"{TONIC}4", system="blues") | |
| minor_pent = ts["minor pentatonic"] | |
| major_pent = ts["major pentatonic"] | |
| minor_notes = minor_pent.note_names[:-1] # A C D E G | |
| major_notes = major_pent.note_names[:-1] # A B C# E F# | |
| print(f" {TONIC} minor pentatonic: {' '.join(minor_notes)}") | |
| print(f" {TONIC} major pentatonic: {' '.join(major_notes)}") | |
| print() | |
| # ── Score ─────────────────────────────────────────────────────────── | |
| score = Score("4/4", bpm=90, drum_humanize=0.2) | |
| score.drums("shuffle", repeats=8) | |
| rhodes = score.part( | |
| "rhodes", synth="fm", envelope="piano", | |
| volume=0.25, pan=-0.3, | |
| reverb=0.3, reverb_type="plate", | |
| humanize=0.2, | |
| ) | |
| lead = score.part( | |
| "lead", synth="triangle", envelope="strings", | |
| volume=0.45, pan=0.2, | |
| legato=True, glide=0.06, | |
| reverb=0.25, reverb_type="plate", | |
| delay=0.15, delay_time=0.33, delay_feedback=0.3, | |
| humanize=0.2, | |
| ) | |
| bass = score.part( | |
| "bass", synth="sine", envelope="pluck", | |
| volume=0.4, lowpass=500, humanize=0.15, | |
| ) | |
| # ── Chords: simple blues in the tonic ── | |
| for sym in [f"{TONIC}7"] * 2 + [f"D7"] * 2 + [f"{TONIC}7"] * 2 + ["E7", f"{TONIC}7"]: | |
| rhodes.add(Chord.from_symbol(sym), Duration.WHOLE) | |
| # ── Bass: root-fifth from chord tones ── | |
| for sym in [f"{TONIC}7"] * 2 + ["D7"] * 2 + [f"{TONIC}7"] * 2 + ["E7", f"{TONIC}7"]: | |
| c = Chord.from_symbol(sym) | |
| r = c.root | |
| fifth = r.add(7) | |
| bass.add(f"{r.name}2", Duration.QUARTER, velocity=95) | |
| bass.add(f"{r.name}2", Duration.QUARTER, velocity=55) | |
| bass.add(f"{fifth.name}2", Duration.QUARTER, velocity=65) | |
| bass.add(f"{r.name}2", Duration.QUARTER, velocity=75) | |
| # ── Lead: phrase generator mixing both pentatonics ── | |
| def phrase(notes, octave, beats, style="melodic"): | |
| """Generate a phrase from a set of scale notes.""" | |
| result = [] | |
| remaining = beats | |
| while remaining > 0.5: | |
| if random.random() < 0.2: | |
| r = random.choice([0.5, 1.0]) | |
| r = min(r, remaining) | |
| result.append((None, 0, r)) | |
| remaining -= r | |
| else: | |
| n = random.choice(notes) | |
| d = random.choice([0.67, 1.0, 1.5, 2.0]) | |
| d = min(d, remaining) | |
| v = random.randint(60, 100) | |
| result.append((f"{n}{octave}", v, d)) | |
| remaining -= d | |
| return result | |
| # 8 bars: alternate between minor and major pent, then mix | |
| # Bars 1-2: pure minor (dark) | |
| for note_data in phrase(minor_notes, 5, 4) + phrase(minor_notes, 5, 4): | |
| n, v, d = note_data | |
| lead.rest(d) if n is None else lead.add(n, d, velocity=v) | |
| # Bars 3-4: pure major (bright) — hear the difference | |
| for note_data in phrase(major_notes, 5, 4) + phrase(major_notes, 5, 4): | |
| n, v, d = note_data | |
| lead.rest(d) if n is None else lead.add(n, d, velocity=v) | |
| # Bars 5-6: MIXED — this is where the magic lives | |
| mixed = minor_notes + major_notes # both scales in one pool | |
| for note_data in phrase(mixed, 5, 4) + phrase(mixed, 5, 4): | |
| n, v, d = note_data | |
| lead.rest(d) if n is None else lead.add(n, d, velocity=v) | |
| # Bars 7-8: mixed with longer notes to let it breathe | |
| for note_data in phrase(mixed, 5, 4, "melodic") + phrase(mixed, 4, 4): | |
| n, v, d = note_data | |
| lead.rest(d) if n is None else lead.add(n, d, velocity=v) | |
| # ── Play ──────────────────────────────────────────────────────────── | |
| buf = render_score(score) | |
| print(f" 🎵 {TONIC} Blues — Major + Minor Pentatonic") | |
| print(" " + "─" * 50) | |
| print(" bars 1-2: minor only (dark)") | |
| print(" bars 3-4: major only (bright)") | |
| print(" bars 5-8: mixed (the magic)") | |
| print() | |
| print(" change TONIC to any note and it works.") | |
| print() | |
| sd.play(buf, SAMPLE_RATE) | |
| sd.wait() | |
| print(" 🎵") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment