Compare commits

...

210 Commits

Author SHA1 Message Date
kennethreitz 63362df697 Saxophone synth overhaul: reed clipping, formants, breath noise — v0.40.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:38:10 -04:00
kennethreitz 755b33a63b Fix test: update Synth enum count 42 → 46
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:12:43 -04:00
kennethreitz 40901d603d Multi-stage distortion: preamp, power amp, asymmetric clipping — v0.40.4
Single tanh was too mild. Now chains preamp gain → power amp clip →
asymmetric rectifier sag for proper overdrive/fuzz character.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:08:10 -04:00
kennethreitz 9b3cbd9065 Add crotales, tingsha, rain stick, ocean drum, cabasa, wind chimes, finger cymbal — v0.40.3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 14:50:03 -04:00
kennethreitz 0911947971 v0.40.2 — dial back master compressor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 04:43:53 -04:00
kennethreitz c2f748d5f3 Dial back master compressor: raise threshold, cap makeup gain
Threshold 0.5 → 0.7 so more dynamics survive. Makeup gain capped
at 3x so sparse arrangements (solo singing bowl, etc.) don't get
over-amplified to clipping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 04:42:52 -04:00
kennethreitz 7a6942c8e4 Add singing bowl synth (strike + ring) — v0.40.1
Two variants modeling Himalayan singing bowl acoustics:
- Strike: mallet hit with chirp from inharmonic partials, long ring
- Ring: rim-rubbed sustained tone with slow build and beating modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 04:32:16 -04:00
kennethreitz db7fabf985 Fully restore original ge_bend synth — only keep dispatch override
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 02:10:59 -04:00
kennethreitz a07b7e7cea Restore original ge_bend synth, keep only the envelope fix
Reverted all frequency/harmonic changes — original thump, metal, sub,
click all restored. Only change from original: envelope uses exp(-0.8*t)
instead of _exp_decay(n_samples, 6) so the sweep sustains long enough
to be audible. Dispatch override for sound_value 108 kept to bypass
stale closure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 02:04:40 -04:00
kennethreitz 7245cd0e51 Fix tabla ge_bend: bypass stale dispatch closure, improved sweep synth
The dispatch dict lambda was holding a stale function reference that
survived module reloads. Added explicit override for sound_value 108
to always call the current _synth_tabla_ge_bend directly.

Synth improvements:
- Sweep: 50→450Hz with slow exp(-1.5t) so ear tracks the bend
- Sustain: exp(-0.8t) envelope gives ~1.2s of audible signal
- Removed static sub/metal that masked the sweep
- Added 2nd harmonic for richness

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 02:01:01 -04:00
kennethreitz 9e85a48d0e Fix ge_bend decay — envelope was killing the sweep before it was audible
Replaced _exp_decay (which uses sample-count-based decay and was too fast)
with a direct time-based exponential: exp(-0.8*t) giving ~1.2s of signal.
The sweep from 50→450Hz is now actually hearable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 01:50:49 -04:00
kennethreitz 95b7bd830c Fix tabla ge_bend synth — wider sweep, longer sustain, actually audible
- Sweep range: 50→450Hz (was 60→240Hz)
- Slower sweep rate: exp(-1.5t) so ear can track it (was -4t)
- Longer sustain: decay rate 2.5 (was 6) — bend lives long enough to hear
- Removed static sub and metal that masked the sweep
- Added 2nd harmonic for richness
- Louder body (1.2 gain)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 01:48:20 -04:00
kennethreitz 150c57ed3d Remove live extras from pytheory — split to pytheory-live repo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 01:00:30 -04:00
kennethreitz d35d2b12f3 Add pytheory-live CLI entry point
pytheory-live is now a proper command after pip install pytheory[live].
TUI moved to pytheory/live_tui.py, registered as console script.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:18:58 -04:00
kennethreitz 2084473788 CLI args: --channels, --port, --drums, --buffer
python test_live.py --channels 4 --drums trap --port OP-XY
python test_live.py 4217 -c 16 -d house -b 256

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:16:29 -04:00
kennethreitz 970c730012 Hold-to-sustain keyboard: ignore repeats, release on key-up timeout
Tracks held keys. OS keyboard repeat is ignored (same key refreshes
timer). Note releases 150ms after last repeat stops — approximates
key-up detection. All notes released on Esc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:31:29 -04:00
kennethreitz 5f94e1939b Full keyboard mapping: all letters, numbers, punctuation
Piano-style layout across the full QWERTY keyboard:
- Bottom rows (ZXCVBNM + ASDFGHJKL): lower octave, white+black keys
- Top rows (QWERTYUIOP + 1234567890): upper octave, white+black keys
- Every letter mapped, ~3.5 octaves total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:28:20 -04:00
kennethreitz b649b2e659 Extended keyboard mapping: , . / ; ' [ ] and more keys
Lower row extends past M: comma=C5 L=C#5 .=D5 ;=D#5 /=E5 '=F5
Upper row extends past U: I=C6 9=C#6 O=D6 0=D#6 P=E6 [=F6 ]=G6

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:26:54 -04:00
kennethreitz ed6ba2ab9f Audio stream starts without MIDI — keyboard mode works standalone
MIDI port is now optional. If no device found or port fails,
the audio stream still starts so keyboard mode works. Cleanup
handles missing MIDI gracefully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:25:09 -04:00
kennethreitz fd317f9cfd Fix: capture engine output, track stream ready state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:23:03 -04:00
kennethreitz c57e29fe28 Debug: check stream active state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:21:43 -04:00
kennethreitz 938024bfa2 More debug: vol, level, wavetable peak
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:19:59 -04:00
kennethreitz acc92f9a60 Debug keyboard: cache check + voice count logging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:18:43 -04:00
kennethreitz 0d340dad30 Debug keyboard mode: log key presses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:17:12 -04:00
kennethreitz 1762500108 Fix self.self.kbd_active typo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:12:18 -04:00
kennethreitz ac2801d07d Keyboard modal, VU meter fix, play_recording.py
- Keyboard mode is now a proper modal: kbd enters, Esc exits
- All keys go to MIDI while in keyboard mode, Up/Down change octave
- Header shows KBD and REC indicators
- VU meters use ASCII-safe characters
- play_recording.py: render MIDI through full engine

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:06:57 -04:00
kennethreitz c49ec27b1b Next level: stereo pan, VU meters, keyboard MIDI, record, save/load
Live engine:
- Stereo panning per channel (constant power)
- VU meter level tracking per channel
- Computer keyboard as MIDI controller (QWERTY layout)
- Record mode: capture MIDI events with timestamps
- Export recording to MIDI file via pytheory Score
- Save/load channel config to JSON
- All effect params supported (volume, pan, lowpass, reverb,
  chorus, detune, spread, analog, distortion, delay, tremolo,
  saturation, phaser, sub_osc, noise_mix)

TUI:
- Live VU meters in config panel
- REC indicator, keyboard mode indicator
- Commands: kbd, rec, stop, export, save, load, pan, octave
- Tab completion for all new commands

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:03:24 -04:00
kennethreitz 5f4070c4a7 TUI: tab completion, cursor movement, fx command
- Tab completes commands, instruments, patterns, fx params
- Left/Right arrows move cursor, insert mid-line
- Home/Ctrl-A, End/Ctrl-E for jump to start/end
- fx <ch> <param> <val> for live effect tweaking
- fx alone lists all params, fx <ch> shows current values

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:59:14 -04:00
kennethreitz 8735393aaa Effects support in live engine + fx command in TUI
_Channel now stores and applies: chorus, detune, distortion,
saturation, tremolo, analog, delay, phaser, sub_osc, noise_mix.
TUI fx command: fx <ch> <param> <val> to tweak any effect live.
fx alone lists all available params. fx <ch> shows current values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:51:03 -04:00
kennethreitz 12f15d5138 Fix BPM: average up to 240 ticks, round to integer, update every beat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:36:37 -04:00
kennethreitz 20fc5e40b8 More accurate BPM: average over 96 ticks (4 quarter notes)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:34:12 -04:00
kennethreitz 91d16595b7 Fix BPM calculation: 24 ticks = 1 quarter note, not per-tick
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:29:01 -04:00
kennethreitz 54659d39b1 Make python-rtmidi optional (pip install pytheory[live])
Fixes CI failure — rtmidi needs ALSA headers on Linux which
aren't available in the test runner. Now optional: import is
lazy with clear error message if missing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:17:15 -04:00
kennethreitz 7cb2c166f9 Address all CodeRabbit review issues
- Channel validation: ch must be int 1-16, raises ValueError
- Port validation: string port raises ValueError if not found
- Exception-safe MIDI: open_port wrapped in try/except, cleanup on failure
- Reverb CC clears cache (was missing)
- stop() uses _stop_event to unblock start()
- _all_notes_off clears drum channel too
- Sorted __slots__
- Fixed en-dash in docstring
- Documented 3-second wavetable limitation
- Unused loop var fixed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:06:10 -04:00
kennethreitz ba2038d7ff Pitch bend support in live engine
MIDI pitch bend (0xE0) adjusts playback rate of active voices
via linear interpolation through the wavetable. ±2 semitone range.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:58:46 -04:00
kennethreitz 198fded20e Enable MIDI clock/transport reception (ignore_types timing=False)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:56:27 -04:00
kennethreitz 51159e309a MIDI clock sync, drum patterns, wavetable pre-rendering
- MIDI clock (0xF8) tracking for BPM detection
- Start/Stop/Continue transport handling
- engine.drums("rock") plays pattern synced to MIDI clock
- Pre-render all wavetables (MIDI 36-96) on startup for zero-glitch playback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:55:33 -04:00
kennethreitz 54df949089 Handle MIDI start/stop/continue, all-notes-off on stop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:53:27 -04:00
kennethreitz 30c70da468 Add reverb to live engine (baked into wavetable)
Simple feedback delay network reverb applied during wavetable
rendering. 3 early reflection taps + 6-pass feedback loop for tail.
Extended wavetable to 3 seconds for reverb room.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:51:29 -04:00
kennethreitz c633bd6f61 Add CC mapping to live engine
engine.cc(0, "lowpass", min_val=300, max_val=8000) maps any MIDI CC
to any channel parameter. Supports per-channel or global mapping.
Invalidates synth cache when filter params change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:46:30 -04:00
kennethreitz bc652c37d0 Live engine: real-time MIDI-to-audio synthesis
LiveEngine listens for MIDI input and synthesizes audio in real-time.
Each MIDI channel maps to a pytheory instrument with its own synth,
envelope, and effects. Supports polyphony, voice stealing, and
GM drum channel (10).

Adds python-rtmidi as a dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:29:51 -04:00
kennethreitz 417d9a6908 10x faster ensemble: render once + duplicate with time shifts
Ensemble rendering no longer re-synthesizes every note N times.
Renders the part once, then creates N copies with per-player
time shifts and velocity variation (cheap buffer ops).

Benchmarks:
- Heavy (ens=10+effects): 12.7s → 3.0s (4.2x faster)
- Ensemble=20: 4.3s → 0.43s (10x faster)

Also: vectorized strings_wave body_response, synth output cache.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:27:03 -04:00
kennethreitz f7d8f08446 Add Wurlitzer, vibraphone, pipe organ, choir synths
- Wurlitzer: reed-based, nasal, biting — bark on hard hits
- Vibraphone: aluminum bars with motor tremolo (spinning disc)
- Pipe organ: multi-rank (8'+4'+2'), constant air, wind chiff
- Choir: formant-filtered glottal source, vowel control via lyric=,
  no vibrato (ensemble handles voice variation)
- All four with instrument presets, audio demos, and docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:21:12 -04:00
kennethreitz b8d1fe5e81 v0.40.0: Rhodes synth, 74 audio demos, improved percussion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:22:43 -04:00
kennethreitz 2612444146 Louder snares in marching demo (volume 1.5 to compensate ensemble/8)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:59:11 -04:00
kennethreitz 48a954d063 Cajón slap: pure wood by default, snare wires optional
CAJON_SLAP is now dry wood crack (no wires). New CAJON_SLAP_SNARE
has the buzzy version for cajóns with snare wires engaged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:55:09 -04:00
kennethreitz 8a07be23e6 Cajón bass: fleshy palm impact + thuddy box resonance
Added hand-on-wood transient (lowpassed noise for soft palm feel)
before the box resonance kicks in. Deeper sub, longer air cavity
thump. You hear the hand, then the box.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:53:00 -04:00
kennethreitz d771117d5c Full drumline marching demo: 8 snares, 4 quads, 5 basses with ensemble
Click count-off → snare groove with quad sweeps and bass splits →
flams and diddles → buzz roll into big unison hit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:51:41 -04:00
kennethreitz 1ae9404f07 Add room reverb to cajón demo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:50:53 -04:00
kennethreitz 2e5b18de2e Boxy cajón: box resonance modes, plywood character, hollow mids
Bass has 180/320Hz box resonance modes. Slap has wood panel
resonance under the wire buzz. Tap has hollow finger-on-plywood.
Everything sounds like hitting a wooden box now.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:48:55 -04:00
kennethreitz 248594fb21 Extend metal gallop+triplet section to 4 bars
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:47:22 -04:00
kennethreitz 3ce890c54c Hall reverb on all tabla demos to match keherwa
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:46:01 -04:00
kennethreitz 499c49b6eb Metal demo: groove → gallop → blast → double kick with fills
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:44:53 -04:00
kennethreitz f85504b456 Tabla demos: add bayan pitch bends (ge_bend) to all three examples
Teental ends with bayan fill. Keherwa has ge_bend accents throughout
plus a bend-heavy final bar. Chakradar preceded by bayan showcase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:42:04 -04:00
kennethreitz d0624f8b78 Thunderous dhol dagga: deeper sub, longer sustain, more body
Dagga now booms at 30Hz with sustained sub, wider thump band,
and heavier stick impact. The kind of hit you feel in your chest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:40:18 -04:00
kennethreitz fb37a7c27b Regenerate all 74 audio samples with choke-free renderer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:38:44 -04:00
kennethreitz fb36e75a42 Remove melodic note choke, fix banjo to 16th notes
The melodic renderer was choking all signal at each new note,
killing strum/hold resonance. Removed — notes now naturally
overlap and ring out. Banjo lick now uses 16th notes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:35:06 -04:00
kennethreitz 81b54d2394 Mandolin demo: tremolo rolls + melody
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:31:39 -04:00
kennethreitz fa6a3090cb Banjo demo: strum + bluegrass picking lick
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:31:22 -04:00
kennethreitz 58286ddb69 Bagpipe demo: drone + chanter melody like Highland pipes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:30:44 -04:00
kennethreitz 6d137be9f5 Accordion demo: waltz chords with held bass
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:29:08 -04:00
kennethreitz 383802a1e1 Timpani: even longer fundamental sustain (0.35/s decay)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:27:59 -04:00
kennethreitz 7f2aeb2395 Timpani: long fundamental sustain for resonance buildup in rolls
Fundamental now decays at 0.6/s instead of 1.5/s — rings long
enough that rapid roll hits stack into a singing 'oooh' resonance.
Upper modes still die fast for the initial thump character.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:27:08 -04:00
kennethreitz 16b4c7d1fa Faster timpani roll: 140bpm + 32nd notes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:24:57 -04:00
kennethreitz b9ee5c9cde Timpani demo: crescendo roll + accent hit, matches doc example
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:23:21 -04:00
kennethreitz 7375d58209 Lower timpani demo to octave 2-3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:21:57 -04:00
kennethreitz 6316a6c910 Hybrid harp: KS pluck transient + pure additive sustain
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:20:49 -04:00
kennethreitz 237cfe171c Rewrite harp synth: pure additive tone instead of Karplus-Strong
Clean harmonics with dominant fundamental, gentle upper partials,
and soft finger pluck transient. Much purer, singing tone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:18:50 -04:00
kennethreitz 1751f97617 Update changelog for v0.39.4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:08:32 -04:00
kennethreitz 943a12b3bb Add dedicated Rhodes electric piano synth
Tine + tonebar + electromagnetic pickup model with bell-like
harmonics, metallic attack transient, and pickup nonlinearity.
electric_piano instrument preset now uses rhodes_synth instead of FM.
FM section updated to show bells. Audio demos for both.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:08:10 -04:00
kennethreitz 04d2de3e70 Add pulse, noise, PWM slow, PWM fast audio demos
Every waveform on the synths page now has an audio player.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:04:13 -04:00
kennethreitz ead42751ef Trim trailing silence from all audio exports
Detects silence below -60dB threshold and trims with 0.2s tail
for natural decay. 69 audio files regenerated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 15:59:34 -04:00
kennethreitz 8dee0d00d8 Raw waveform demos (no envelope), open up bass lowpass to 1200
Basic waveforms now play without envelope shaping so you hear
the raw timbre. Complete Example bass at 1200 instead of 600.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 15:57:39 -04:00
kennethreitz de112e0d9f Idiomatic synth demos: strum, hold, drone, ensemble per instrument
Harp: arpeggiated hold. Harpsichord: baroque runs. Guitars: strum
chords. Piano: hold + melody. Cello: bowed ensemble=3. Sitar: drone
under melody. Banjo/uke: strum. Mandolin: tremolo. Strings: ensemble=8.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:36:30 -04:00
kennethreitz f469ad90f8 Add granular synth audio demo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:33:05 -04:00
kennethreitz bab7f39304 Fix bass guitar demo: play in octave 2-3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:29:21 -04:00
kennethreitz 62557ba534 Fix upright bass demo: play in octave 2-3 not 4-5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:28:50 -04:00
kennethreitz 8b50a9c325 Audio demos for every synth: 34 waveform/instrument players
Every synth section in the docs now has an audio player.
Classic waveforms, FM, supersaw, and all 31 instrument synths
each play a C major arpeggio for easy comparison.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:27:56 -04:00
kennethreitz 7e9caac70b Update homepage + quickstart to rock example, match code to audio
Both pages now show the same rock beat example in G major with
piano, saw lead, triangle bass. Code and audio are in sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:15:27 -04:00
kennethreitz a0756b3172 v0.39.3: Audio samples in docs, numpy vectorization
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:11:23 -04:00
kennethreitz e3dd706032 Remove stale sequencing_bossa.wav (replaced by complete_rock.wav)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:05:19 -04:00
kennethreitz 9b412906bc Fix acid example, add basic chords audio, regenerate all 34 samples
All audio files: stereo, normalized, no issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:04:51 -04:00
kennethreitz 54e0421997 Fix acid legato example: drop pad envelope, add filter + distortion
The pad envelope has slow attack — wrong for fast acid lines.
Updated both the docs code and the audio generator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:57:47 -04:00
kennethreitz 109343ad30 Replace bossa nova with rock in Complete Example, add arpeggio audio
Complete Example now uses rock beat with piano/saw/bass in G major.
Added audio player for the arpeggiator code example.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:53:06 -04:00
kennethreitz 28e84de566 Add legato/glide audio example to sequencing docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:50:55 -04:00
kennethreitz d353d64298 Add Polyphonic Hold section with audio example to sequencing docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:49:49 -04:00
kennethreitz 7ee02e7ed2 Fix audio samples to stereo WAV
save_wav was writing mono — now properly writes stereo from
render_score's (n_samples, 2) output. All 31 files regenerated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:48:51 -04:00
kennethreitz a5c9a46eb2 Add ensemble= to strings, cellos, choir, pads in songs.py
String ensembles: 6-10 players. Cellos: 3 players.
Choir: 6 voices. Cathedral siren pad: 4 voices.
Makes everything sound fuller and more alive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:39:51 -04:00
kennethreitz f9c63ec360 Add audio player to homepage, remove save_midi from example
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:37:18 -04:00
kennethreitz b9e88b77d8 Add audio for all world percussion, metal, cajón sections
28 audio samples total. Tabla (teental, keherwa, chakradar at fast
tempos), dhol, dholak, mridangam, metal blast, cajón. No labels
on stacked players.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 06:04:30 -04:00
kennethreitz 1910b09132 Add individual audio samples for all 4 Playing Patterns examples
Rock, bossa nova, salsa, and afrobeat each get their own audio player.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 05:55:11 -04:00
kennethreitz 0c5287450b Merge pull request #48 from kennethreitz/docs-audio
Audio samples in documentation
2026-03-29 05:51:38 -04:00
kennethreitz 5ac1873d83 Audio samples for all play_score() examples in docs
20 WAV files covering quickstart, sequencing, drums, playback,
and cookbook examples. Audio players embedded after every code
block that calls play_score().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 05:51:25 -04:00
kennethreitz 9fafca2b08 Add audio samples for documentation
- docs/generate_audio.py renders 12 code examples as WAV files
- Audio players embedded in sequencing and drums docs via raw HTML
- Covers: piano hold, articulations, dynamics, filter ramp, rock,
  bossa nova, djembe, tabla, marching snare, ensemble, strum, swell
- WAV files gitignored — generated at build time

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 05:42:34 -04:00
kennethreitz af044f68ca Numpy vectorization: cached helpers, vectorized piano harmonics
- Cache time arrays and exp_decay envelopes (avoid reallocation)
- Cache drum hit renders (same sound at same length = same output)
- Vectorize piano_wave harmonic synthesis: 30 sin() calls in a
  Python loop → one 2D numpy.sin() operation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 05:25:52 -04:00
kennethreitz 60f697f846 Add song #33: Ensemble Showcase
Acid bass (ensemble=3), 16-player strings, tabla with solo and tihai,
8-player snare line, 6-player lead synth. Showcases ensemble, ramp,
LFO, sidechain, articulations, and dynamic curves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 23:07:23 -04:00
kennethreitz 7d678e364e v0.39.2: Marching drumline, ensemble rendering, rudiments
Full marching percussion: snare, quads, pitched bass drums.
Part.flam(), Part.diddle(), Part.cheese() rudiment methods.
Part ensemble= for multi-player rendering with timing tendencies.
Sympathetic snare resonance. Updated docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 23:01:18 -04:00
kennethreitz 3a8d829010 Quads, pitched bass drums, full drumline cadence
- 5 quad/tenor drum sounds (4 pitched + spock rim)
- 5 pitched marching bass drums (high to low, more beater sound)
- 6 patterns: quad sweep, quad groove, bass split, bass unison, drumline
- Louder snares, more beater on bass drums
- Song #32 rewritten as full drumline cadence with ensemble
  (8 snares, 4 quads, 5 basses)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 22:59:08 -04:00
kennethreitz 2a67906937 Update changelog for v0.40.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 22:49:57 -04:00
kennethreitz b9dcad0454 Marching snare, ensemble, flam/diddle/cheese, resonance buildup
- 3 marching percussion sounds: snare, rimshot, stick click
- 4 marching patterns: march, cadence, paradiddle, roll
- Part.flam(), Part.diddle(), Part.cheese() rudiment methods
- Part ensemble= parameter: duplicate voices with per-player timing
  tendencies and micro pitch drift (works on any Part)
- Sympathetic resonance: marching snare buzz builds up with repeated hits
- Song #32: Snare Cadence (16 bars with triplets, 32nds, flams, cheese)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 22:48:37 -04:00
kennethreitz db9726168a Add chakradar tabla pattern, simplify sprunki.py
16-beat chakradar: tihai of tihais with 3 escalating phrases
and crescendo triplet finale landing on sam.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:22:34 -04:00
kennethreitz 26af923789 Simplify sprunki.py to clean melody reference
Just the notes transcribed from MIDI, no arrangement.
Base for future iterations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:18:40 -04:00
kennethreitz 72e18a9bec Add Sprunki Simon Phase 1 arrangement (examples/sprunki.py)
Full arrangement with three-octave square wave lead, drums, bass,
supersaw pads, and a saw solo section. Melody transcribed from MIDI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:39:46 -04:00
kennethreitz 7d56ed7a2c Add render_score to __all__, 19 tests for new features
Tests for articulations, dynamic curves, Part.hit(), Part.ramp(),
djembe/cajón/metal patterns and fills. 882 tests total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:08:03 -04:00
kennethreitz 6efa4f18ce v0.39.0: Articulations, ramp(), drop numeral, djembe expansion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:51:29 -04:00
kennethreitz 06fc4cabb7 Drop numeral dependency, inline Roman numeral helpers
Replace the numeral package with ~30 lines of int2roman()/roman2int()
in _statics.py. Reduces supply chain surface. Fixes #47.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:48:22 -04:00
kennethreitz d3a93c18b3 Add song #31: Acid Tabla (303 + tabla fusion)
Showcases ramp(), articulations, Part.hit(), filter automation,
and cross-genre fusion. 303 acid bass with tabla entering at the
peak and riding through the outro.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:26:20 -04:00
kennethreitz 0e10359236 v0.38.2: Part.ramp() for smooth parameter automation
Smoothly sweep any parameter (lowpass, reverb, distortion, etc.)
from current value to target with linear, ease_in, ease_out, or
ease_in_out interpolation curves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:12:32 -04:00
kennethreitz df00c3436d Docs: articulations, dynamic curves, Part.hit(), Duration arithmetic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:24:21 -04:00
kennethreitz 2f02df15b8 v0.38.1: Dynamic curves (crescendo, decrescendo, swell, dynamics)
Part.crescendo(), Part.decrescendo(), Part.swell(), and Part.dynamics()
for velocity ramps and custom curves across note sequences.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:23:16 -04:00
kennethreitz a2740b8d57 v0.38.0: Articulations, Part.hit(), djembe expansion, cross-choke
Articulations (staccato, legato, marcato, tenuto, accent, fermata)
on Part.add() and Part.hold(). Part.hit() for placing individual
drum sounds with articulation support. 5 new djembe patterns,
3 djembe fills, cross-choke damping, improved djembe slap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:14:21 -04:00
kennethreitz 840bfcc36c v0.37.0: Djembe expansion and cross-choke drum damping
5 new djembe patterns (dununba, tiriba, yankadi, djansa, mendiani),
3 djembe fills, cross-choke damping across drum families, and
improved djembe slap synthesis.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:32:14 -04:00
kennethreitz 938c1cc132 v0.36.6: Cajón and metal drum fills
Add 6 new drum fills: cajon flam, cajon rumble, cajon breakdown,
metal triplet, metal blast, metal cascade. 27 fills total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:25:52 -04:00
kennethreitz 9dc22db4b2 v0.36.5: Duration arithmetic support
Duration enum now supports multiplication, division, and addition
so expressions like `Duration.WHOLE * 2` work instead of raising TypeError.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:19:28 -04:00
kennethreitz f570e226cd v0.36.4: Harmonium, doumbek, tabla fills, Part.hold()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:11:06 -04:00
kennethreitz 0c5c3abedc Harmonium synth, doumbek drums (3 sounds, 4 patterns, 2 fills)
- Harmonium: single free reed, nasal midrange, bellows swell.
  The sound of kirtan and qawwali.
- Doumbek (darbuka): dum (center bass), tek (edge sharp), ka (muted).
  4 patterns: maqsoum, baladi, saidi, ayoub.
  2 fills: doumbek roll, doumbek accent.
- 42 synth waveforms total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 12:05:46 -04:00
kennethreitz 35d07b984b Docs: add tabla fills to drums.rst
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:57:08 -04:00
kennethreitz aec7723ee6 5 tabla fills: tihai, chakkardar, tiri kita, bayan, tabla call
Tihai (3x crescendo landing on sam), chakkardar (32nd triplet
cascade), tiri kita (rapid 16th dayan burst), bayan (bass bends),
tabla call (dayan/bayan call-and-response).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:56:19 -04:00
kennethreitz b98a40297b v0.36.3: Part.hold() polyphony, strum fix, 30 songs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:46:45 -04:00
kennethreitz 9117568b74 Strum uses hold() — leading string plays simultaneously with chord
No more timing gaps. The leading string is held at 15% velocity
at the same beat position as the full chord via hold(), adding
strum texture without stealing time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:42:36 -04:00
kennethreitz 11e4417c62 Part.hold() — polyphonic overlap on a single part
hold() adds a note without advancing the beat position, so the
next note starts at the same time. Enables: piano sustain (bass
rings while melody plays), drone notes under melody, held chords
with moving lines.

Two lines in the renderer: skip beat_pos advance when _hold is set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:38:41 -04:00
kennethreitz 4edf1d983d Remove strum grace notes — clean chord hit only
Grace notes created audible gap before chord and sounded like
separate plucks. Pure chord hit sounds better.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:30:18 -04:00
kennethreitz 74b07b1a8a Song #29: Pop Rock (I-V-vi-IV) — the progression that launched 1000 hits
G-D-Em-C at 120 BPM. Picked intro, strummed verse, electric lead
melody, strings swell, rock drums. The most popular chord progression
in pop history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:22:08 -04:00
kennethreitz c9437209a7 Song #28: Descent (generative — different every time)
Random key, tempo, reverb space, instruments, and melodies.
Melodies walk the scale stepwise (not random jumps), arpeggios
follow chord tones in order, piano walks up/down. Tabla solo
always closes with random strokes. No seed — truly unique each play.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:18:19 -04:00
kennethreitz 92cb855a49 Song #27: Ascent (Deep → Sky → Theremin Solo → Tabla Solo)
Didgeridoo drone throughout, granular abyss, kalimba light,
cello surfacing, piano + quiet uke, pedal steel + theremin solo
(searching → building → soaring peak), strings/flute/harp/timpani
at the peak, 4-part tabla solo finale (whisper → ghosts → 9-tuplet
call-response → 32nd triplet cascade + grand tihai).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 23:09:34 -04:00
kennethreitz f06c6f77d1 Comprehensive docs sweep: all 9 guide pages updated
- index.rst: 16 systems, 60+ presets, 41 waveforms, full feature list
- synths.rst: 31 dedicated synths, 60+ presets, complete instrument list
- drums.rst: 51 drum sounds, cajón section, bayan pitch bend
- effects.rst: cabinet/analog_drift in automatable params
- playback.rst: temperament, reference_pitch, KeyboardInterrupt
- systems.rst: 16 systems, full microtonal section (shruti JI,
  maqam Zalzalian, slendro, pelog, thai, makam, carnatic, 19/31-TET,
  Bohlen-Pierce), TET factory, int tone names, System.tone()
- sequencing.rst: Score tuning params documented
- tones.rst: enharmonics (Cb/Fb/E#/B#, double sharps/flats, unicode),
  B#/Cb octave fix, tone validation
- chords.rst: enharmonic support cross-reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:41:08 -04:00
kennethreitz 51bd63658f Docs: update synths.rst — 41 waveforms, all 24 dedicated synths
Added: pedal steel, theremin, kalimba, steel drum, accordion,
didgeridoo, bagpipe, banjo, mandolin, ukulele. Updated counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:31:31 -04:00
kennethreitz 92ade3ee3d v0.36.2: REPL updates, 862 tests, improved songs, Ctrl-C handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:28:49 -04:00
kennethreitz 833867329e REPL: new commands, all instruments, updated autocomplete
New commands: strum, roll, bend, temperament, reference, instruments
Updated autocomplete: 41 synths, 50+ instruments, bowed/mallet
envelopes, all drum patterns (tabla, dhol, djembe, cajón, metal)
Part command supports instrument= keyword
Status shows temperament and reference pitch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:27:41 -04:00
kennethreitz 93b9fe9ced 25 new tests: all new synths, vocal, cajón, bends, rolls, int tones
862 tests total. Covers: 11 new synth waveforms, vocal synth with
lyrics, all instrument presets, cajón drums/patterns, pitch bend
rendering (3 types), roll velocity ramp, int tone names + wrapping,
B#/Cb octave fix, note choking, Score system/temperament/ref_pitch,
synth enum count (41).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:27:06 -04:00
kennethreitz 88a1171bbe Fix Theremin Noir: granular pad → strings pad (less noisy)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:20:01 -04:00
kennethreitz 3ca0842b7a Improve songs 1-16, 19: humanize, reverb, velocity dynamics
- humanize=0.2 added to all melodic parts (leads, basses, bells)
- Subtle reverb (0.1-0.2) on bass parts that had none
- Per-note velocity dynamics on all leads (was static)
- Blues lead changed from trumpet to saxophone (more fitting)
- Songs 17-18, 20-26 left untouched (already well-crafted)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:19:22 -04:00
kennethreitz 00de5eb354 Catch KeyboardInterrupt in all playback functions
play(), play_score(), _play_for() now catch Ctrl-C and stop
cleanly instead of crashing with a traceback. CLI demo also
wrapped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:18:43 -04:00
kennethreitz d2b0c6f329 v0.36.1: 7 new synths, 9 new demo moods
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:15:22 -04:00
kennethreitz 76612682f1 9 new demo moods: theremin noir, caribbean, accordion waltz, kalimba
dreams, outback drone, highland, nashville tears, tabla fusion

All new synths represented in pytheory demo random rotation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:13:11 -04:00
kennethreitz ce480858e9 7 new synths: pedal steel, theremin, kalimba, steel drum, accordion, didgeridoo, bagpipe
- Pedal steel: singing harmonics, slow vibrato, spring reverb
- Theremin: pure sine with hand wobble, legato+glide preset
- Kalimba: inharmonic metal tine modes, wooden body, bell-like
- Steel drum: hammered metal partials, bright Caribbean ring
- Accordion: musette-tuned doubled reeds, bellows pressure swell
- Didgeridoo: deep cylindrical drone, shifting formant overtones
- Bagpipe: bright chanter reed, constant bag pressure
- 41 synth waveforms total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:11:32 -04:00
kennethreitz 70efb0ad40 v0.36.0: Banjo, mandolin, ukulele, cajón, vocal synth, granular
34 synth waveforms, 26 songs, vocal/formant synthesis with choir
preset, granular engine, banjo/mandolin/ukulele physical models,
cajón drum with 3 patterns, strum sweep on fretboard instruments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:54:08 -04:00
kennethreitz bf6deaab64 Mandolin synth, cajón drums, Song #26 Acoustic Ensemble
- Mandolin: paired steel strings (natural chorus from doubled
  courses), bright body resonance (500/1000/2000Hz)
- Cajón: bass (woody box thump), slap (snare wire buzz), tap
  (ghost note). 3 patterns: cajon, cajon rumba, cajon folk
- Song #26: guitar + uke + mandolin + cajón — humanized strumming,
  stereo panned, plate reverb
- Mandola preset (mandolin with lowpass for darker tone)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:46:15 -04:00
kennethreitz 7c792c0a2a Ukulele synth + strum sweep on all fretboard instruments
- Ukulele: nylon string KS with small body resonance (350/700/1200Hz),
  faster decay than guitar, mid-heavy character
- Strum sweep: 2 quiet grace notes (25% vel) before the chord hit,
  gives audible strum feel without choppiness
- Default strum_time 0.08 → 0.05 for tighter feel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:36:29 -04:00
kennethreitz bf8d4b9a77 Epic Bhairav: musical polyrhythm section, fix reverb levels
Polyrhythm section uses musical phrases (ti-ra-ki-ta patterns)
in 5-groups, 7-groups, and 9-groups rather than mechanical grid
overlays. Reverb pulled back to 0.4 across the song.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:12:31 -04:00
kennethreitz d2d5115c8a Song #25: Epic Bhairav + vocal synth merged to master
Orchestral piece in 22-shruti JI with choir vowel pads, timpani
rolls, bansuri, cello, sitar, strings, harp, djembe→tabla→extended
tabla solo finale (whisper→ghosts→call/response→9-tuplets→32nd
triplet cascades→grand tihai→slam).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:25:05 -04:00
kennethreitz 3cdd98b158 Merge vocal/formant synth: LF glottal model, 5 formants, choir
Formant synthesis with LF glottal pulse, 5 Peterson & Barney
formant peaks, jitter/shimmer, consonant onsets, click-free
transitions. Presets: vocal, choir.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:22:36 -04:00
kennethreitz 751d5a49b8 Cleaner vocal synth: less static, click-free note transitions
- Jitter reduced (0.3% → 0.1%), shimmer reduced (2% → 0.8%)
- Breath noise halved (0.08 → 0.04), mix 85/15 → 92/8
- 10ms fade in/out on every vocal note prevents clicks
- Smoother syllable transitions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:17:12 -04:00
kennethreitz 6a836dd891 Overhaul vocal synth: LF glottal model, 5 formants, jitter/shimmer
- LF glottal pulse: asymmetric open/close phase (not sines)
- 5 parallel formant filters per vowel (Peterson & Barney data)
- Jitter (0.3% pitch irregularity) + shimmer (2% amplitude)
- Much more voice-like than previous version
- Consonant onsets preserved

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:13:50 -04:00
kennethreitz 1f888e2b21 Vocal/formant synth with choir preset
Formant synthesis: glottal buzz source through parallel bandpass
filters at vowel resonance frequencies. Supports 5 vowels (A E I O U)
with consonant onsets (plosives, sibilants, nasals, fricatives,
liquids, aspirates, glides). Per-note lyrics via Part.add(lyric=).

Best for choir pads — vowel sounds with cathedral reverb and detune.
Consonant synthesis is rudimentary (noise bursts, not real speech).

Presets: vocal (solo), choir (detuned ensemble).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:10:54 -04:00
kennethreitz fb923f6c76 v0.35.1: Granular synthesis engine
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:50:32 -04:00
kennethreitz 59e3338892 Granular synthesis engine with presets
Grain cloud synthesis: source waveform chopped into tiny overlapping
grains (40ms, 50/sec) with Hanning windows, random scatter, and
per-grain pitch variation. Creates textures impossible with other
synthesis. Two presets: granular_pad, granular_texture.
30 synth waveforms total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:47:52 -04:00
kennethreitz 8cf4145c15 Docs: timpani, saxophone, Part.roll(), update waveform counts
- Add timpani and saxophone synth sections to synths.rst
- Add rolls section to sequencing.rst with examples
- Update waveform count: 27 → 29

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:38:46 -04:00
kennethreitz b3885b2c15 v0.35.0: JI ratios, 8.5x faster import, timpani, saxophone, rolls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:34:34 -04:00
kennethreitz ae04fa60cc Reduce vibrato across all instruments to 0.001
Strings, cello, trumpet, clarinet, oboe all cut to 0.001 depth.
Much subtler in ensemble context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:29:48 -04:00
kennethreitz 6c411e43f8 Part.roll() for crescendo/decrescendo rolls, reedier sax, timpani reverb
- roll(tone, duration, velocity_start, velocity_end, speed) — rapid
  repeated notes with velocity ramp. Works on any instrument.
- Saxophone reed noise boosted and bandpass filtered for more bite
- Timpani preset: cathedral reverb at 0.4

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:26:28 -04:00
kennethreitz e0427af3cc Timpani and saxophone synths, 4 sax presets
- Timpani: inharmonic membrane modes (1.0, 1.5, 1.99, 2.44),
  felt mallet attack, copper kettle resonance, two-stage decay
- Saxophone: conical bore (all harmonics), strong mids, reed buzz,
  brass body warmth. 4 presets: saxophone, alto_sax, tenor_sax, bari_sax
- 29 synth waveforms total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:22:47 -04:00
kennethreitz 552836ae5b Drop pytuning/sympy, lazy-load scipy: import 0.48s → 0.05s (fixes #44)
- Replace pytuning with 30-line native implementations of EDO,
  Pythagorean, and quarter-comma meantone scale generators
- Lazy-load scipy.signal (337ms) — only imported when audio rendering
  is actually used, not on theory-only imports
- Removes pytuning and sympy from dependencies entirely

Import time: 0.479s → 0.056s (8.5x faster)

Closes #44

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:17:52 -04:00
kennethreitz 0fe53fcdeb Merge pull request #46 from kennethreitz/fix/accidental-octave-wrap
Fix B#/Cb octave boundary crossing
2026-03-27 11:11:43 -04:00
kennethreitz f6fb2a2cd6 Fix B#/Cb octave boundary crossing (fixes #45)
B#4 now correctly resolves to C5 (523.25 Hz), not C4 (261.63 Hz).
Cb4 now correctly resolves to B3 (246.94 Hz), not B4 (493.88 Hz).

When an accidental crosses the B/C octave boundary, the octave is
adjusted: sharps crossing B→C increment, flats crossing C→B decrement.
Also handles double sharps (B##→C#5) and double flats (Cbb→Bb3).

Closes #45

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:11:08 -04:00
kennethreitz 70d6e6b8ce Reduce flute vibrato further (0.0015 → 0.0008)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:05:55 -04:00
kennethreitz aec9a999cb Arabic maqam JI ratios: Zalzalian neutral third (27/22)
Maqam system now uses just intonation ratios instead of 24-TET:
- Quarter-tone positions use Zalzalian (11-limit) ratios
- Mi↓ (the defining Rast note) is exactly 27/22 from Do
- Standard JI intervals for chromatic positions
- Septimal ratios (7-limit) for other quarter-tone positions

Research confirmed: Turkish 53-TET and Thai 7-TET are already
correct as equal temperaments. Gamelan has no universal ratios
(each ensemble is unique), so TET remains the best default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:00:16 -04:00
kennethreitz 3acde86028 Int tone names, wrapping, System.tone(), proper shruti JI ratios
- Tone(0, system=edo22) works alongside Tone("0", ...)
- Tone(22, system=edo22) wraps to tone 0, octave+1
- Tone(-1) wraps to last tone, octave-1
- System.tone(name, octave) convenience method
- Shruti system now uses 5-limit just intonation ratios instead
  of 22-TET approximation. Based on Pythagorean/harmonic ratios
  from traditional Indian musicology. Pa is a pure 3/2, Ga is a
  pure 5/4.
- System.ratios attribute overrides equal temperament when set

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 10:54:28 -04:00
kennethreitz aa405702a9 Fix Journey: EDM parts start after tabla solo ends
Calculated edm_start from actual section lengths so pad/sub/sitar2
don't bleed into the tabla solo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 10:44:42 -04:00
kennethreitz b7c018fb94 Expanded tabla solo: 4 sections, 9-tuplets, polyrhythm, grand tihai
Solo now has 4 distinct parts:
1. Whisper — single hits with space, breath
2. Ghosts emerge — 16th note ghost fills between accents
3. Call and response — dayan vs bayan, 9-tuplet break
4. Blazing — 32nd triplet cascades, rapid alternating hands,
   9-against-4 polyrhythm, grand tihai (3x, each louder), slam

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 03:06:59 -04:00
kennethreitz 07a52a3a25 Add tabla solo section to Journey, louder sitar in EDM drop
Tabla solo with ghost notes, 32nd triplet cascade, tihai, then
slams into house beat. Sitar volume 0.22 → 0.4 in EDM section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 03:03:57 -04:00
kennethreitz e12cb9003b Song #24: Journey (Piano → World → Sitar EDM)
Single score, one reverb space (Taj Mahal), tanpura drone throughout.
Piano arpeggios alone → cello joins → harp/oboe/flute with djembe →
sitar over tabla → EDM section with sitar, synth pad, 808 sub, house
drums. 28 bars, 5 movements.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 03:01:36 -04:00
kennethreitz 28968a1b5c Docs: strumming, pitch bends, tuning systems, fix instrument count
- Add guitar strumming section to sequencing.rst
- Add pitch bends section with three bend types
- Add tuning systems section (temperament, reference_pitch, TET)
- Fix index.rst: 25 → 49 instrument presets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:49:24 -04:00
kennethreitz 8a4a2df1aa Song #23: Tabla Solo in Raga Yaman (22-shruti)
Tanpura drone intro, quiet sitar Yaman phrases, tabla solo building
from gentle theka through ghost notes to blazing tiri kita with
bayan pitch bends, tihai, dramatic silence, slam finish. Taj Mahal
reverb throughout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:37:34 -04:00
kennethreitz f4a90637db Note choking: new hits fade out previous tails on same sound
Drums and melodic notes now choke previous resonance with a quick
fade when a new hit/note starts. Prevents muddy buildup at fast
tempos. Added bayan pitch bend drum sound (TABLA_GE_BEND).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:32:34 -04:00
kennethreitz 90a1a31049 Fix pitch bends: resampling preserves instrument timbre
Render at base pitch using the actual synth, then variable-rate
resample to shift pitch over time. No more sine wave fallback or
retriggering artifacts. Three bend types: smooth (log), linear, late.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:22:03 -04:00
kennethreitz 33b2e82594 v0.34.1: Pitch bends, updated docs and songs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:15:32 -04:00
kennethreitz 9f8dd0006d Pitch bends, updated docs, songs with new instruments
- Pitch bend: part.add("C4", bend=2, bend_type="smooth") bends up
  a whole step. Three types: smooth (log/perceptual), linear, late
  (hold then bend — blues style).
- Updated songs.py: use dedicated instrument synths (piano_synth,
  flute_synth, trumpet_synth, etc.) instead of generic waveforms
- Updated docs: synths.rst (27 waveforms, instrument synths section),
  effects.rst (cabinet sim, analog drift, updated signal chain),
  drums.rst (world percussion: tabla, dhol, dholak, mridangam,
  djembe, metal kit), index.rst (feature counts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:14:45 -04:00
kennethreitz 417f7f74a3 19 new tests: instrument synths, cabinet, analog, strumming, world drums
Tests for: all 14 dedicated synth waveforms, piano brightness scaling,
cabinet sim frequency reduction, analog drift rendering, strum with/
without fretboard, strum direction, all 6 tabla sounds, dhol/mridangam/
djembe/metal kit sounds, 20 world drum pattern presets, guitar preset
cabinet sim. 838 tests total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:11:48 -04:00
kennethreitz cd6f814049 Update changelog with vibrato tuning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:08:37 -04:00
kennethreitz 83fcdb0a09 Reduce vibrato depth on flute, oboe, trumpet, cello
All cut from 0.003-0.004 to 0.0015-0.002 — less wobbly in
ensemble context, more natural.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:08:17 -04:00
kennethreitz aa21bf0f2a v0.34.0: 27 synth waveforms, world drums, guitar strumming
16 dedicated instrument synths, speaker cab sim, analog drift,
strumming with fretboard lookup, dhol/dholak/mridangam/djembe/
metal kit with 22 patterns, 5 new demo moods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:00:57 -04:00
kennethreitz e7e35ad4e4 5 more dedicated synths: oboe, harpsichord, cello, harp, upright bass
- Oboe: double reed buzz + conical bore (all harmonics, peaked 3-5)
- Harpsichord: KS with quill chiff, bright metallic pluck
- Cello: deep bowed string with 250/500Hz body resonance
- Harp: soft KS pluck with large soundboard bloom
- Upright bass: thick string pizzicato with wooden body resonance
- 27 synth waveforms total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:59:20 -04:00
kennethreitz 503dbce937 6 dedicated instrument synths: piano, bass, flute, trumpet, clarinet, marimba
- Piano: hammer strike + detuned strings + inharmonicity + soundboard
- Bass guitar: heavy KS with thick string damping + low-mid pickup
- Flute: breath noise + tube resonance + developing vibrato
- Trumpet: lip buzz harmonics + brass bell resonance + vibrato
- Clarinet: odd harmonics (cylindrical bore) + reed noise
- Marimba: inharmonic bar modes (1x, 4x, 9.2x) + resonator tube
- 22 synth waveforms total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:44:00 -04:00
kennethreitz c6bbfae7e6 Acoustic guitar synth with body resonance, fix strum
- New acoustic_guitar_synth: Karplus-Strong with wooden body
  resonance (3 formant peaks at 110/250/500 Hz), warmer initial
  noise, gentle rolloff. Sounds woody, not harsh.
- Strum renders as a single chord hit — no more exposed grace
  notes that sounded digital. Clean, full chord sound.
- 16 synth waveforms total

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:38:35 -04:00
kennethreitz 64ef7f0803 Add analog oscillator drift for synth warmth
Per-note random pitch wobble (gaussian, ±cents scaled by analog param)
simulates analog oscillator instability. Applied to synth_lead (0.3),
synth_pad (0.4), synth_bass (0.2), acid_bass (0.3), electric_piano
(0.2), organ (0.15). Subtle enough to add life without sounding
out of tune.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:32:03 -04:00
kennethreitz 406e5d7e54 Electric guitar synth, cab sim, strumming, world drums, metal kit
- Electric guitar: Karplus-Strong + magnetic pickup comb filter
- Cabinet simulation: speaker rolloff + presence bump (tames fizz)
- 6 guitar presets: clean, crunch, distorted, orange, metal
- Part.strum(): fretboard fingering lookup with down/up strumming
- Sitar synth: jawari buzz + chikari sympathetic strings
- Dhol, dholak, mridangam, djembe synthesis (membrane noise)
- Metal drum kit (kick click, bright snare, tight hats)
- 11 world patterns + 4 metal patterns + 7 tabla patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 01:25:53 -04:00
kennethreitz 267b7284ba Add dhol, dholak, mridangam, djembe drums + 11 world patterns
Drum synthesis:
- Dhol: dagga (heavy bass), tilli (treble crack), both
- Dholak: ge (bass palm), na (treble fingers), tit (light tap)
- Mridangam: tham (clay body bass), nam (rich overtone ring),
  din (both heads), tha (muted)
- Djembe: bass (center palm), tone (edge ring), slap (sharp crack)
All with bandpass-filtered membrane noise for drum head character.

Patterns:
- Dhol: bhangra, dhol chaal
- Dholak: qawwali, dholak folk
- Mridangam: adi talam, mridangam korvai
- Djembe: djembe (standard), kuku, soli

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:59:27 -04:00
kennethreitz 9b62b56120 Sitar synth, tabla drums with wood/metal shells, 7 tabla patterns
- Sitar synth: Karplus-Strong with gentle jawari bridge buzz,
  variable damping (bright attack fades to warm sustain), chikari
  sympathetic string shimmer
- Tabla: 6 synthesized strokes (Na, Tin, Ge, Dha, Tit, Ke) with
  goatskin membrane noise (bandpass filtered), wooden shell resonance
  on dayan, copper/metal shell resonance on bayan
- 7 tabla patterns: teental (16 beats), jhaptaal (10), rupak (7),
  dadra (6), keherwa (8), tabla solo, tiri kita (fast 16th-note)
- Sitar instrument preset with proper lowpass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:54:19 -04:00
kennethreitz 4fe7771d83 v0.33.0: Microtonal systems, historical tuning, Bohlen-Pierce
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:34:59 -04:00
kennethreitz 57079a43ac Merge feature/non-12-tet: microtonal systems, historical tuning
11 microtonal systems, Bohlen-Pierce tritave, just intonation,
reference pitch, Score(system=, temperament=, reference_pitch=).
TET(n) factory for any equal temperament. 819 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:34:12 -04:00
kennethreitz 1d07b06968 Add Greensleeves (Renaissance lute, meantone A=415) to songs.py
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:33:50 -04:00
kennethreitz 9887b59cfb Add reference_pitch to Score and playback pipeline
Score(reference_pitch=415.0, temperament="meantone") renders an
entire piece at Baroque pitch with historical tuning. Flows through
to all .pitch() calls in both normal and legato renderers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:32:11 -04:00
kennethreitz 9850a8016e Bohlen-Pierce, just intonation, temperament in Score/playback
- Bohlen-Pierce (13-TET tritave): period=3.0 support in pitch(),
  System, and TET factory. 13 equal divisions of the 3:1 ratio.
- Just intonation temperament: 5-limit JI ratios (pure 3/2 fifths,
  5/4 thirds). Use temperament="just" anywhere.
- Score(temperament="just") flows through to playback — all .pitch()
  calls in the render pipeline use the Score's temperament.
- Carnatic 72-TET system with 10 melakartas.
- Fix c_index for Indian, Arabic, and Gamelan 12-TET systems.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:24:03 -04:00
kennethreitz 35f5f35dc5 Carnatic 72-TET, Score system param, 22 microtonal tests
- Carnatic (72-TET): 10 melakartas including shankarabharanam,
  kalyani, mayamalavagowla, kharaharapriya, etc.
- Score(system=) param passes tuning system to all parts, so
  Part.add("Sa") resolves through the correct system
- 22 new tests covering all microtonal systems: TET factory,
  19/31-TET, shruti, maqam, slendro, pelog, thai, makam,
  carnatic, circle of fifths, from_frequency, Score integration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:18:09 -04:00
kennethreitz 47ca94111f Add gamelan slendro/pelog, Thai 7-TET, Turkish 53-TET makam
- Slendro (5-TET): true equal 5-tone gamelan tuning, 240 cents/step
- Pelog (9-TET): 7-of-9 gamelan tuning with pathet nem/lima/barang
- Thai classical (7-TET): 7 equal divisions (~171 cents each)
- Turkish makam (53-TET): Arel-Ezgi-Uzdilek system with 9 makams
  (rast, hicaz, ussak, nihavend, huseyni, kurdi, segah, saba, huzzam)
- Fix octave parser to only match trailing digits (not "Mib+3")
- Fix _index to use _name_to_index (avoid creating Tone objects)
- Fix _math to use per-system c_index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:11:19 -04:00
kennethreitz 62cfbb2591 Add 22-shruti Indian and 24-TET Arabic maqam systems
- "shruti" system: 22 named shrutis with proper microtonal intervals
  for all 10 thaats (bilawal, bhairav, todi, etc.) and pentatonic
  scales (bhupali, malkauns, durga). Captures the 2-shruti vs 3-shruti
  distinctions that 12-TET approximations lose.

- "maqam" system: 24-TET with quarter-tone positions (↑/↓ notation).
  True maqam Rast with quarter-flat E and B. Bayati, Saba, Sikah,
  Hijaz, and 6 more maqamat with exact quarter-tone intervals.

- 12-TET "indian" and "arabic" systems preserved for backwards compat.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:04:27 -04:00
kennethreitz de855a3fe6 Add non-12-TET support: TET() factory, 19-TET, 31-TET
- TET(n) factory creates N-tone equal temperament systems
- Built-in named systems: "19-tet" and "31-tet" with proper note
  names and scale definitions (major, minor, harmonic minor, pentatonic)
- Per-system c_index replaces global C_INDEX constant
- Fix 6 hardcoded '12's in tones.py: from_frequency, from_midi,
  interval_to, midi property, circle_of_fifths/fourths
- Numbered pitch classes for custom EDOs: TET(17) uses "0"-"16"
- Octave parser skips numeric-only names (fixes "0" being eaten)

Refs #38

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:58:33 -04:00
kennethreitz dc9f7b3342 Update changelog for 0.32.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:50:52 -04:00
kennethreitz 60fdff6d36 Merge pull request #43 from kennethreitz/fix/enharmonics-and-double-accidentals
Support enharmonic spellings and double accidentals
2026-03-26 23:50:33 -04:00
kennethreitz f42d38d1fd Support Cb, Fb, E#, B#, double sharps/flats, unicode symbols
- Cb, Fb, E#, B# resolve to their enharmonic equivalents (fixes #40)
- C##, Dbb, etc. resolve via semitone arithmetic (fixes #41)
- Unicode symbols accepted: ♯ ♭ 𝄪 𝄫
- 'x'/'X' accepted as double sharp (Bach notation): Fx = F##
- resolve_name handles all accidentals dynamically

Closes #40, closes #41

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:48:42 -04:00
kennethreitz 5a4122d61f Merge pull request #42 from kennethreitz/fix/tone-validate-early
Validate tone name at construction time
2026-03-26 23:43:41 -04:00
kennethreitz 3e4ba54a32 Validate tone name at construction time (fixes #39)
Tone("X") now raises ValueError immediately instead of silently
storing an invalid name and only failing on .frequency access.

Closes #39

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:43:00 -04:00
kennethreitz 5dd1c5e15d v0.32.0: 8 new synth features, highpass filter, preset overhaul
Filter envelope, velocity→brightness, sub-oscillator, tremolo,
saturation, noise layer, phaser, configurable FM. Highpass filter.
Bowed and mallet envelopes. Improved strings_synth with additive
synthesis. All 38 instrument presets sanity-checked and enhanced.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 22:00:49 -04:00
kennethreitz e46732fb5a Improved strings_synth, highpass filter, bowed envelope
- Rewrite strings_wave with additive synthesis: natural 1/n harmonic
  rolloff shaped by body resonance curve, per-harmonic phase
  randomization, delayed vibrato onset, bow pressure variation
- Add highpass filter (12dB/oct biquad) to signal chain and Part API
- Add BOWED envelope (40ms attack with bite) for string instruments
- Update string presets to use strings_synth + bowed envelope

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:38:15 -04:00
kennethreitz 833ab56857 Fix solo string instruments: clean triangle, no detune
Solo violin/viola/cello/contrabass now use triangle + strings envelope
(clean, clear). String ensemble keeps strings_synth + detune for
thick ensemble textures. Solo instruments need clarity, not width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:26:35 -04:00
kennethreitz 6b2b1e201e Update index.rst with 13 synths, 38 instrument presets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:20:46 -04:00
kennethreitz f9c81fe05f v0.31.0: 3 new synths, 38 instrument presets
- Karplus-Strong pluck (physical modeling for guitar/harp/koto)
- Hammond organ (additive drawbar synthesis)
- String ensemble (filtered saw with body resonance formants)
- 38 instrument presets: score.part("lead", instrument="violin")
- Demo updated with pluck_synth, organ_synth, strings_synth

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:18:27 -04:00
kennethreitz 931ec905c3 Add 3 new synths + 38 instrument presets
New synths:
- pluck_synth: Karplus-Strong physical modeling (guitar, harp, koto)
- organ_synth: Hammond-style additive drawbar synthesis
- strings_synth: Filtered saw with body resonance formants

38 instrument presets across 7 categories: keys, strings, woodwinds,
brass, plucked, synth, percussion/mallet. Each preset combines synth,
envelope, and effects to approximate real instruments.

score.part("lead", instrument="violin")
Score.list_instruments()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:15:56 -04:00
kennethreitz 799ffbdac9 Add MIT LICENSE file
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 21:02:44 -04:00
kennethreitz b29b33524f v0.30.0: Drums as Parts, split drums, kick-only sidechain, MIDI import
- Drums are real Parts with full effects pipeline
- split=True creates kick/snare/hats/toms/cymbals/percussion Parts
- Sidechain triggers on kick only
- Score.from_midi() imports Standard MIDI Files
- Document split drums workflow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:27:10 -04:00
kennethreitz 25f25c1f23 Split drums into separate Parts: kick, snare, hats, toms, cymbals, percussion
score.drums("rock", split=True) creates independent Parts per group.
Each gets its own effects chain. set_drum_effects() applies to all.
Sidechain triggers on kick only. Render loop handles multiple drum Parts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:26:08 -04:00
kennethreitz 3f1d632285 Sidechain triggers on kick only, not all drum hits
Hi-hats and snares no longer duck the pad — only the kick does.
This is how sidechain compression works in real mixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:22:20 -04:00
kennethreitz 1938037458 Update changelog: drums as Part
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:20:05 -04:00
kennethreitz f7c05e1b31 Drums are now a real Part — same effects pipeline, zero duplication
_drum_hits and _drum_pattern_beats proxy through score.parts['drums'].
Drum Part goes through _apply_part_effects like any other Part.
set_drum_effects() is now sugar over the Part's attributes.
All 789 tests pass with no API changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:17:05 -04:00
kennethreitz c375785bb9 Update changelog for drum bus effects
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:12:06 -04:00
kennethreitz 9ebd54b7fc Add drum bus effects — same engine as parts, zero duplication
score.set_drum_effects(reverb=0.2, reverb_type="plate", lowpass=8000)
Uses _apply_effects_with_params on each stereo channel.
Supports all effects: reverb, delay, lowpass, distortion, chorus.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:11:40 -04:00
kennethreitz ce68ad8f19 Update changelog for v0.29.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:09:00 -04:00
kennethreitz f402e76480 Rename song.py → songs.py, polish all 20 songs with effects
Every song now has: stereo panning, convolution reverb (plate/cathedral),
humanize (0.2), detune (8-12) on pads, sidechain on electronic tracks,
lowpass on bass, delay on leads. No melodies changed — just better sound.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:03:15 -04:00
kennethreitz 4d3c7e0d6c v0.29.0: MIDI import — Score.from_midi()
Load any Standard MIDI File into a Score. Zero-dependency parser
handles Type 0 and Type 1 files. Each channel becomes a Part,
channel 10 becomes drum hits. Roundtrip with save_midi works.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:25:19 -04:00
123 changed files with 18281 additions and 1528 deletions
+343
View File
@@ -2,6 +2,349 @@
All notable changes to PyTheory are documented here.
## 0.40.5
- **Saxophone synth overhaul** — reed nonlinearity (asymmetric soft clipping),
conical bore formant resonances, breath noise with attack envelope, separate
reed buzz, key click transient, and sub-harmonic warmth. Vibrato dialed back
to subtle, delayed onset.
## 0.40.4
- **Distortion overhaul** — multi-stage clipping (preamp → power amp →
asymmetric rectifier) replaces single-stage tanh. Crunch, distorted,
orange crunch, and metal guitar presets now sound properly driven.
## 0.40.3
- **Crotales synth** — tuned bronze discs with long ring and bright harmonics
- **Tingsha synth** — paired Tibetan cymbals with beating from two detuned discs
- **Rain stick** — cascading pebbles (steep and slow/shallow variants)
- **Ocean drum** — steel beads rolling inside a frame drum, surf wash
- **Cabasa** — metal bead chain on cylinder, bright metallic scrape
- **Wind chimes** — multiple suspended metal tubes ringing at random offsets
- **Finger cymbal** — single zill tap, bright metallic ping
- `crotales`, `tingsha`, `singing_bowl`, `singing_bowl_ring` instrument presets
- Audio demos in docs for all new sounds
## 0.40.2
- **Master compressor dialed back** — threshold raised from 0.5 to 0.7,
makeup gain capped at 3x. Sparse arrangements no longer get
over-amplified to clipping.
## 0.40.1
- **Singing bowl synth** — two variants: strike (mallet hit with chirp
and long decay) and ring (rim-rubbed sustained tone with slow build).
Inharmonic partials beat against near-degenerate mode pairs for
authentic Himalayan bowl shimmer.
- `singing_bowl` and `singing_bowl_ring` instrument presets
- Audio demos in docs for both variants
## 0.40.0
- **Rhodes electric piano synth** — tine + tonebar + electromagnetic
pickup model. `electric_piano` preset now uses dedicated `rhodes_synth`
instead of FM
- **73 audio demos in docs** — every synth, every drum pattern, every
code example with `play_score()` now has an embedded audio player
- Idiomatic demos: harp arpeggiates, guitars strum, cello bows, sitar
drones, strings use ensemble
- Trailing silence trimming on all audio exports
- Raw waveform demos (no envelope) for classic waveforms
## 0.39.3
- **33 audio samples in documentation** — every `play_score()` example
now has an embedded stereo audio player. Covers quickstart, sequencing,
drums (all world percussion), playback, and cookbook.
- **`docs/generate_audio.py`** — renders all doc examples to WAV
- Numpy vectorization: cached time arrays, decay envelopes, drum hits;
vectorized piano harmonic synthesis
- Fixed acid legato example (removed pad envelope, added proper 303 recipe)
## 0.39.2
- **Marching percussion** — snare, rimshot, and stick click sounds with
high-tension kevlar synthesis and woody-metallic rimshot crack
- **`Part.flam()`**, **`Part.diddle()`**, **`Part.cheese()`** — marching
rudiment methods for any drum sound
- **`Part ensemble=`** — duplicate voices with per-player timing tendencies
and micro pitch drift. Works on any Part (drumline, string section, choir).
`ensemble=20` for a full snare line, `ensemble=4` for a string quartet.
- **Sympathetic resonance** — marching snare buzz builds up with repeated
hits, decays during rests (like real snare wire response)
- **4 marching patterns** — march, cadence, paradiddle, roll
- **Chakradar tabla pattern** — 16-beat tihai of tihais composition
- Song #32: Snare Cadence (flams, diddles, cheese, triplets, 32nds)
## 0.39.1
- **Chakradar tabla pattern** — 16-beat tihai of tihais composition with
3 escalating phrases and a crescendo triplet finale
## 0.39.0
- **Dropped `numeral` dependency** — Roman numeral helpers inlined,
reducing supply chain surface (#47)
- **`Part.ramp()`** — smooth parameter automation with 4 interpolation
curves (linear, ease_in, ease_out, ease_in_out)
- **Articulations** — staccato, legato, marcato, tenuto, accent, fermata
- **Dynamic curves** — crescendo(), decrescendo(), swell(), dynamics()
- **`Part.hit()`** — individual drum sounds with articulation support
- **Cross-choke drum damping** — djembe, hi-hats, cajón, doumbek
- **5 new djembe patterns** + 3 djembe fills (30 fills total)
- **6 new drum fills** — 3 cajón, 3 metal
- **Duration arithmetic** — multiply, divide, add
- **Improved djembe slap** synthesis
- Song #31: Acid Tabla
## 0.38.2
- **`Part.ramp()`** — smooth parameter automation from current value to
target over a duration. Works for lowpass, reverb, distortion, chorus,
delay, volume, and any `.set()` parameter. Four interpolation curves:
linear, ease_in, ease_out, ease_in_out.
## 0.38.1
- **Dynamic curves** — `Part.crescendo()`, `Part.decrescendo()`,
`Part.swell()`, and `Part.dynamics()` for velocity ramps and custom
curves across a sequence of notes
## 0.38.0
- **Articulations** — `staccato`, `legato`, `marcato`, `tenuto`, `accent`,
`fermata` via `articulation=` on `Part.add()` and `Part.hold()`
- **`Part.hit()`** — place individual drum sounds in a Part's note stream
with articulation, velocity, and effects support
- **5 new djembe patterns** — dununba, tiriba, yankadi, djansa, mendiani
- **3 new djembe fills** — djembe call, djembe roll, djembe break (30 fills total)
- **Cross-choke drum damping** — striking one sound fades out related sounds
(djembe, hi-hats, cajón, doumbek)
- **Improved djembe slap** — dry goatskin pop instead of snare-like noise
## 0.37.0
- **5 new djembe patterns** — dununba, tiriba, yankadi, djansa, mendiani
- **3 new djembe fills** — djembe call, djembe roll, djembe break (30 fills total)
- **Cross-choke drum damping** — striking one sound on a hand drum fades
out the ring of related sounds (djembe slap kills bass resonance, closed
hat chokes open hat, cajón slap dampens bass, doumbek tek dampens dum)
- **Improved djembe slap** — dry, high-pitched goatskin pop instead of
snare-like noise rattle
## 0.36.6
- **6 new drum fills** — 3 cajón (flam, rumble, breakdown) and 3 metal
(triplet, blast, cascade). 27 fills total.
- Updated drums documentation with fill lists and examples
## 0.36.5
- **Duration arithmetic** — `Duration.WHOLE * 2`, `Duration.HALF + Duration.QUARTER`,
division, and reverse multiply all work now (previously raised TypeError)
## 0.36.3
- **`Part.hold()`** — polyphonic overlap on a single part. Add notes
without advancing the beat position so they play simultaneously.
Enables: piano sustain, sitar drone under melody, guitar strum texture.
- **Strum uses hold()** — leading string plays simultaneously with chord,
no more timing gaps or choppiness
- **Improved songs** 1-16: humanize, velocity dynamics, reverb, saxophone
for blues
- **Ctrl-C handling** — clean stop on all playback functions
- **REPL updates** — strum, roll, bend, temperament, reference commands
- Song #28 Descent (generative), #29 Pop Rock, #30 Sitar Drone
- 862 tests
## 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.
`import pytheory` now takes ~50ms instead of ~480ms (#44)
- **Proper shruti JI ratios** — 22 positions with 5-limit just intonation
(pure 3/2 fifths, 5/4 thirds), not 22-TET approximation
- **Arabic maqam JI ratios** — Zalzalian 11-limit ratios.
Mi↓ (the Rast third) is exactly 27/22 from Do
- **B#/Cb octave boundary fix** — B#4 = C5, Cb4 = B3 (#45)
- **Int tone names** — `Tone(0, system=TET(22))` works alongside strings.
Wrapping: `Tone(22)` → tone 0, octave+1. `System.tone()` convenience.
- **Timpani synth** — inharmonic membrane modes, felt mallet, copper kettle
resonance, cathedral reverb
- **Saxophone synth** — conical bore, reed buzz, brass body warmth.
4 presets: saxophone, alto_sax, tenor_sax, bari_sax
- **Part.roll()** — rapid repeated notes with velocity ramp for crescendo/
decrescendo rolls on any instrument
- **Vibrato tuning** — all instruments reduced to 0.001 depth for cleaner
ensemble sound
- **Granular synthesis** — grain cloud engine with scatter, pitch
variation, and Hanning-windowed grains. Two presets: granular_pad,
granular_texture.
- 30 synth waveforms, 838 tests
## 0.34.0
- **16 dedicated instrument synths** — physical modeling and specialized
synthesis for: piano (hammer + steel strings + soundboard), bass guitar
(thick KS + pickup), flute (breath + tube resonance), trumpet (lip buzz
+ bell), clarinet (odd harmonics + reed), oboe (double reed + conical
bore), marimba (inharmonic bar modes), harpsichord (quill pluck),
cello (deep bowed + body), harp (soft pluck + soundboard bloom),
upright bass (pizzicato + wooden body), acoustic guitar (KS + body
resonance), electric guitar (KS + pickup comb filter), sitar (jawari
+ chikari), plus organ and bowed strings
- **Speaker cabinet simulation** — tames distorted guitar fizz
- **Guitar strumming** — `Part.strum("Am")` with fretboard lookup
- **Analog oscillator drift** — subtle per-note pitch wobble on synth presets
- **World percussion:** dhol, dholak, mridangam, djembe, metal kit
with 22 new drum patterns
- **Piano improvements:** brightness scales with pitch, two-stage decay,
hammer impact with felt character
- **Vibrato tuning:** reduced across flute, oboe, trumpet, cello for
smoother ensemble sound
- 27 synth waveforms, 10 envelopes, 40+ instrument presets, 80+ drum patterns
## 0.33.1
- **Electric guitar synth** — Karplus-Strong with magnetic pickup comb filter
simulation (single-coil honk, proper sustain)
- **Speaker cabinet simulation** — steep rolloff above 4-5kHz with presence
bump. Makes distorted guitar sound warm instead of fizzy.
- **6 guitar presets:** electric_guitar, clean_guitar, crunch_guitar,
distorted_guitar, orange_crunch, metal_guitar — all with proper cab sim
- **Sitar synth** — Karplus-Strong with jawari bridge buzz, chikari
sympathetic strings, variable damping
- **Guitar strumming** — `Part.strum("Am", Duration.HALF)` with
fretboard fingering lookup, down/up direction, adjustable strum speed
- **World drums:** dhol (bhangra, chaal), dholak (qawwali, folk),
mridangam (adi talam, korvai), djembe (standard, kuku, soli)
— all with bandpass-filtered membrane noise for realistic drum head sound
- **Metal drum kit** — clicky kick, bright snare, tight hats
with 4 patterns (double kick, metal blast, metal groove, metal gallop)
- 15 synth waveforms, 10 envelopes, 40+ instrument presets
## 0.33.0
- **Non-12-TET support** — `TET(n)` factory creates any equal temperament
- **11 microtonal systems:**
- `"shruti"` (22-TET Indian, 10 thaats with proper shruti intervals)
- `"maqam"` (24-TET Arabic, quarter-tone Rast/Bayati/Hijaz + 7 more)
- `"slendro"` (5-TET gamelan), `"pelog"` (9-TET gamelan with 3 pathet)
- `"thai"` (7-TET, 171 cents/step)
- `"makam"` (53-TET Turkish Arel-Ezgi-Uzdilek, 9 makams)
- `"carnatic"` (72-TET, 10 melakartas)
- `"19-tet"`, `"31-tet"` (historical Western)
- `"bohlen-pierce"` (13 divisions of the tritave 3:1 — non-octave!)
- **Just intonation** — `temperament="just"` for pure 5-limit ratios
- **Historical pitch** — `Score(reference_pitch=415.0)` for Baroque A=415
- **`Score(system=, temperament=, reference_pitch=)`** flows through to all playback
- Per-system `c_index` and `period` replace hardcoded constants
- Fixed all hardcoded `12`s in tone arithmetic
- Song #22: Greensleeves (Renaissance lute, meantone, A=415)
- 22 new microtonal tests (819 total)
## 0.32.1
- `Tone("X")` now raises `ValueError` immediately instead of silently accepting invalid names (#39)
- Support enharmonic spellings: `Cb`, `Fb`, `E#`, `B#` resolve correctly (#40)
- Support double sharps (`C##`, `Fx`) and double flats (`Dbb`) via semitone arithmetic (#41)
- Accept unicode music symbols: `♯` `♭` `𝄪` `𝄫`
## 0.32.0
- **8 new synth engine features:**
- Filter envelope: per-note lowpass sweep (`filter_amount`, `filter_attack`, `filter_decay`, `filter_sustain`)
- Velocity → brightness: harder notes = brighter filter (`vel_to_filter`)
- Sub-oscillator: octave-below sine for bass weight (`sub_osc`)
- Tremolo: amplitude LFO modulation (`tremolo_depth`, `tremolo_rate`)
- Saturation: even-harmonic tape/tube warmth (`saturation`)
- Noise layer: per-note breath/air texture (`noise_mix`)
- Phaser: swept allpass filter chain (`phaser`, `phaser_rate`)
- Configurable FM: `fm_ratio` and `fm_index` params
- **Highpass filter** (12 dB/oct biquad) on any part
- **2 new envelopes:** `bowed` (bow attack with sustain), `mallet` (strike with ringing sustain)
- **Improved `strings_synth`:** additive synthesis with body resonance curve, per-harmonic phase randomization, delayed vibrato onset, bow pressure variation
- **Instrument preset overhaul:** every preset sanity-checked against real instrument behavior
- Mallet instruments (vibraphone, celesta, music box, glockenspiel, tubular bells) now ring properly
- Trumpet uses sustaining envelope instead of pluck
- Woodwinds have breath noise, brass has velocity brightness
- Bass instruments have sub-oscillators, synth presets have filter envelopes
- Piano has velocity-to-brightness and subtle hammer noise
- Signal chain: saturation → tremolo → distortion → chorus → phaser → highpass → lowpass → delay → reverb
- Song #21: Cinematic Showcase (Orchestral)
## 0.31.0
- 3 new synth engines: Karplus-Strong pluck, Hammond organ, string ensemble with body formants
- 38 instrument presets: `score.part("lead", instrument="violin")`
- Keys, strings, woodwinds, brass, plucked, synth, and mallet categories
- 13 total synth waveforms
## 0.30.0
- Drums are a real Part — same effects pipeline as any voice
- `score.drums("rock", split=True)` splits kit into kick/snare/hats/toms/cymbals/percussion Parts
- Each split Part gets independent effects (reverb on snare, LP on hats, etc.)
- `set_drum_effects()` applies to all drum Parts (split or not)
- Sidechain triggers on kick only — hats and snare don't duck the pad
- MIDI import via `Score.from_midi(path)`
## 0.29.3
- Drums are now a real Part — same effects pipeline as any other voice, zero code duplication
- `score.parts["drums"]` is a standard Part with reverb, delay, lowpass, etc.
- `set_drum_effects()` is sugar over the Part's attributes
## 0.29.2
- Add `score.set_drum_effects()` — reverb, delay, lowpass, distortion, chorus on the drum bus
- Same effects engine as parts, zero code duplication
## 0.29.1
- Rename song.py → songs.py
- Polish all 20 example songs with stereo, convolution reverb, humanize, detune, sidechain
## 0.29.0
- Add `Score.from_midi(path)` — import any Standard MIDI File into a Score
- Minimal zero-dependency MIDI parser (Type 0 and Type 1)
- Each channel becomes a named Part, channel 10 becomes drum hits
- Tempo, time signature, velocities, and note durations preserved
- Roundtrip: save_midi → from_midi works
## 0.28.3
- Rewrite `pytheory demo` — 8 moods with stereo, effects, humanize, convolution reverb, sidechain
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Kenneth Reitz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
<audio controls style="width: 100%; margin: 0.5em 0 1.5em 0;">
<source src="{{ pathto('_static/audio/' + file, 1) }}" type="audio/wav">
Your browser does not support the audio element.
</audio>
File diff suppressed because it is too large Load Diff
+8
View File
@@ -322,6 +322,14 @@ against 17 known chord types (triads, 7ths, 9ths, sus, power chords).
>>> Chord.from_tones("Bb", "D", "F").identify()
'Bb major'
Enharmonic spellings are fully supported — Cb, Fb, E#, B#, double
sharps/flats, and unicode symbols (see :doc:`tones` for details):
.. code-block:: pycon
>>> Chord.from_tones("Cb", "Eb", "Gb").identify()
'B minor'
You can also access the root and quality separately:
.. code-block:: pycon
+16
View File
@@ -411,6 +411,10 @@ Acid House Track
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/acid_house.wav" type="audio/wav"></audio>
Dub Reggae with Delay Madness
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -443,6 +447,10 @@ Sparse notes into infinite echo:
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dub_reggae.wav" type="audio/wav"></audio>
Jazz Ballad with Humanize
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -480,6 +488,10 @@ The difference between a robot and a musician:
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/jazz_ballad.wav" type="audio/wav"></audio>
Song with Sections
~~~~~~~~~~~~~~~~~~~
@@ -513,6 +525,10 @@ Define once, arrange freely:
play_score(score)
score.save_midi("my_song.mid")
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/song_sections.wav" type="audio/wav"></audio>
Export Everything to MIDI
~~~~~~~~~~~~~~~~~~~~~~~~~~
+349 -12
View File
@@ -9,8 +9,8 @@ in Atlanta. Over a dancehall pattern, you're in Kingston. The drums ARE
the genre -- they tell the listener's body how to move before a single
melodic note is played.
PyTheory includes a complete drum system -- 27 synthesized percussion
sounds, 58 pattern presets across dozens of genres, and 21 fill presets.
PyTheory includes a complete drum system -- 51 synthesized percussion
sounds, 95+ pattern presets across dozens of genres, and 30 fill presets.
Every sound is generated from waveforms; no samples needed.
Drum Sounds
@@ -29,6 +29,50 @@ Score:
The default is 0.15 — just enough to feel alive without sounding loose.
Drums Are Parts
~~~~~~~~~~~~~~~~
Drums are a real Part — the same as any melodic voice. You can set
effects on them the same way:
.. code-block:: python
score.drums("rock", repeats=4)
score.parts["drums"].reverb_mix = 0.2
score.parts["drums"].reverb_type = "plate"
Or use the shorthand:
.. code-block:: python
score.set_drum_effects(reverb=0.2, reverb_type="plate", lowpass=8000)
Split Drums
~~~~~~~~~~~
For maximum control, split the kit into separate Parts — kick, snare,
hats, toms, cymbals, and percussion — each with independent effects:
.. code-block:: python
score.drums("rock", repeats=4, split=True)
# Now each group is its own Part
score.parts["snare"].reverb_mix = 0.3
score.parts["snare"].reverb_type = "plate"
score.parts["hats"].lowpass = 7000
score.parts["kick"] # dry, no effects
# set_drum_effects still works — applies to all drum Parts
score.set_drum_effects(reverb=0.1)
This is how real studios work — the snare gets its own reverb send,
the hats get their own EQ, the kick stays dry and punchy. Now you
can do the same thing in Python.
Sidechain compression triggers on kick hits only — hi-hats and snares
don't duck the pad.
Every drum sound is stereo-panned like a real kit — kick and snare
center, hi-hat right, crash left, toms spread across the field,
percussion instruments placed naturally. Put on headphones and you'll
@@ -47,7 +91,7 @@ The ``DrumSound`` enum maps to General MIDI percussion note numbers:
>>> DrumSound.CLOSED_HAT.value
42
All 27 sounds, organized by type:
All 51 sounds, organized by type:
**Kicks:** KICK (36)
@@ -62,7 +106,32 @@ All 27 sounds, organized by type:
**Percussion:** COWBELL (56), CLAVE (75), SHAKER (70), TAMBOURINE (54),
CONGA_HIGH (63), CONGA_LOW (64), BONGO_HIGH (60), BONGO_LOW (61),
TIMBALE_HIGH (65), TIMBALE_LOW (66), AGOGO_HIGH (67), AGOGO_LOW (68),
GUIRO (73), MARACAS (70)
GUIRO (73)
**Tabla:** TABLA_NA (86), TABLA_TIN (87), TABLA_GE (88), TABLA_DHA (89),
TABLA_TIT (90), TABLA_KE (91), TABLA_GE_BEND (108 -- bayan with upward
pitch bend from palm pressing into the head)
**Dhol:** DHOL_DAGGA (92), DHOL_TILLI (93), DHOL_BOTH (94)
**Dholak:** DHOLAK_GE (95), DHOLAK_NA (96), DHOLAK_TIT (97)
**Mridangam:** MRIDANGAM_THAM (98), MRIDANGAM_NAM (99), MRIDANGAM_DIN (100),
MRIDANGAM_THA (101)
**Djembe:** DJEMBE_BASS (102), DJEMBE_TONE (103), DJEMBE_SLAP (104)
**Cajón:** CAJON_BASS (108), CAJON_SLAP (109), CAJON_TAP (110)
**Metal Kit:** METAL_KICK (105), METAL_SNARE (106), METAL_HAT (107)
**Marching Snare:** MARCH_SNARE (115), MARCH_RIMSHOT (116), MARCH_CLICK (118)
**Quads (Tenors):** QUAD_1 (119), QUAD_2 (120), QUAD_3 (121), QUAD_4 (122),
QUAD_SPOCK (123)
**Marching Bass:** BASS_1 (124), BASS_2 (125), BASS_3 (126), BASS_4 (127),
BASS_5 (80)
Drum Synthesis
--------------
@@ -101,8 +170,8 @@ Each sound has a dedicated synthesizer:
Pattern Presets
---------------
58 patterns spanning genres from rock to Afro-Cuban to electronic.
Load them with ``Pattern.preset()``:
80+ patterns spanning genres from rock to Afro-Cuban to electronic to
world percussion. Load them with ``Pattern.preset()``:
.. code-block:: pycon
@@ -149,9 +218,16 @@ adds syncopation.
rattling hi-hats of trap, the breakneck tempo of drum and bass. These
patterns were born in drum machines and they still live there.
**Metal/Punk:** metal, blast beat, punk -- Speed and aggression.
The blast beat is both feet and both hands going as fast as humanly
possible. Punk strips everything to its essentials.
**Metal/Punk:** metal, blast beat, punk, double kick, metal blast,
metal groove, metal gallop -- Speed and aggression. The blast beat is
both feet and both hands going as fast as humanly possible. Punk strips
everything to its essentials. The metal kit adds 3 dedicated sounds
(double kick, china cymbal, stack) and 4 patterns for extreme metal
subgenres.
**World Percussion:** tabla, dhol, dholak, mridangam, djembe, cajón --
Deep traditions from across the globe, each with authentic sound sets and
idiomatic patterns. See the World Percussion section below for details.
**Other:** funk, hip hop, bo diddley, second line, new orleans, waltz,
12/8 blues, country, gospel, flamenco -- Everything else. The syncopated
@@ -173,6 +249,13 @@ Playing Patterns
play_pattern(Pattern.preset("salsa"), repeats=4, bpm=180)
play_pattern(Pattern.preset("afrobeat"), repeats=8, bpm=110)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/rock_beat.wav" type="audio/wav"></audio>
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/bossa_nova_pattern.wav" type="audio/wav"></audio>
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/salsa_pattern.wav" type="audio/wav"></audio>
<audio controls style="width:100%;margin:0.3em 0 1.5em"><source src="../_static/audio/afrobeat_pattern.wav" type="audio/wav"></audio>
Fills
-----
@@ -184,14 +267,17 @@ ending and a new one is about to begin. Without fills, a drum pattern
just loops. With them, it breathes and has structure.
``Pattern.fill()`` loads a 1-bar drum fill -- a short break that
transitions between sections. 21 fill presets are available:
transitions between sections. 30 fill presets are available:
.. code-block:: pycon
>>> Pattern.list_fills()
['afrobeat', 'blast', 'bossa nova', 'breakdown', 'buildup',
'cumbia', 'disco', 'funk', 'highlife', 'hip hop', 'house',
'jazz', 'jazz brush', 'metal', 'reggae', 'rock', 'rock crash',
'cajon breakdown', 'cajon flam', 'cajon rumble',
'cumbia', 'disco', 'djembe break', 'djembe call', 'djembe roll',
'funk', 'highlife', 'hip hop', 'house',
'jazz', 'jazz brush', 'metal', 'metal blast', 'metal cascade',
'metal triplet', 'reggae', 'rock', 'rock crash',
'salsa', 'samba', 'second line', 'trap']
>>> fill = Pattern.fill("rock")
@@ -260,6 +346,257 @@ drum pattern and all named parts are mixed together by ``play_score()``:
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/salsa_layered.wav" type="audio/wav"></audio>
World Percussion
----------------
PyTheory includes dedicated sound sets and pattern presets for
traditional percussion instruments from around the world. Each
instrument has its own synthesized sounds that capture the timbral
character of the real instrument, plus idiomatic rhythmic patterns
drawn from their musical traditions.
Tabla
~~~~~
The tabla is a pair of hand drums from the Indian subcontinent -- the
smaller, higher-pitched *dayan* and the larger, bass *bayan*. It is
the rhythmic backbone of Hindustani classical music, and one of the
most expressive percussion instruments ever created. A single tabla
player can produce an astonishing range of tones by varying finger
placement, pressure, and striking technique.
**7 sounds** -- covering the primary tabla strokes (na, tin, tun, ge,
dha, ke, tit) plus a bayan pitch bend sound (TABLA_GE_BEND) that
models the technique of pressing the palm into the bayan head to bend
the pitch upward.
**7 patterns:** teental (16 beats, the most common taal), jhaptaal
(10 beats), rupak (7 beats), dadra (6 beats), keherwa (8 beats, folk
and light classical), tabla solo, and tiri kita (fast ornamental
pattern).
**5 fills:** tihai (3x crescendo landing on sam), chakkardar (32nd
triplet cascade into slam), tiri kita (rapid 16th-note dayan burst),
bayan (deep bass bends showcase), tabla call (dayan/bayan call-and-response).
.. code-block:: python
score.drums("teental", repeats=4, fill="tihai")
score.drums("keherwa", repeats=4, fill="chakkardar")
.. code-block:: python
score = Score("4/4", bpm=80)
score.drums("teental", repeats=4)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/tabla_teental.wav" type="audio/wav"></audio>
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/tabla_keherwa.wav" type="audio/wav"></audio>
<audio controls style="width:100%;margin:0.3em 0 1.5em"><source src="../_static/audio/tabla_chakradar.wav" type="audio/wav"></audio>
Dhol
~~~~
The dhol is a double-headed barrel drum from Punjab, played with
sticks. It is the driving force behind bhangra music -- loud,
energetic, and physically impossible to sit still to.
**3 sounds** -- bass stroke, treble stroke, and rimshot.
**2 patterns:** bhangra (the classic bhangra groove) and dhol chaal
(a processional rhythm).
.. code-block:: python
score = Score("4/4", bpm=160)
score.drums("bhangra", repeats=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dhol.wav" type="audio/wav"></audio>
Dholak
~~~~~~
The dholak is a smaller, lighter two-headed drum used across South
Asia in folk music, qawwali, and Bollywood. Played with bare hands,
it produces a warm, melodic tone.
**3 sounds** -- bass, treble, and slap.
**2 patterns:** qawwali (the rhythmic foundation of Sufi devotional
music) and dholak folk (a general folk groove).
.. code-block:: python
score = Score("4/4", bpm=120)
score.drums("qawwali", repeats=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dholak.wav" type="audio/wav"></audio>
Mridangam
~~~~~~~~~
The mridangam is a double-headed drum from South India, the
rhythmic anchor of Carnatic classical music. Its tuning system is
extraordinarily precise, and its rhythmic vocabulary is among the
most mathematically complex in the world.
**4 sounds** -- tha, thom, nam, and din.
**2 patterns:** adi talam (the most common Carnatic talam, 8 beats)
and mridangam korvai (a rhythmic cadence pattern).
.. code-block:: python
score = Score("4/4", bpm=90)
score.drums("adi talam", repeats=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/mridangam.wav" type="audio/wav"></audio>
Djembe
~~~~~~
The djembe is a rope-tuned goblet drum from West Africa, capable of
producing a wide range of tones from deep bass to sharp slaps. It is
central to the drum ensemble traditions of Mali, Guinea, and Senegal.
**3 sounds** -- bass (open center strike), tone (edge strike), and
slap (sharp edge strike).
**8 patterns:** djembe (basic accompanying rhythm), kuku (Guinean harvest
dance), soli (powerful Mandinka rhythm), dununba (heavy bass-driven),
tiriba (joyful Susu rhythm), yankadi (gentle greeting/welcome), djansa
(fast Malinke dance), mendiani (women's celebratory dance).
**3 fills:** djembe call (bass-tone-slap conversation building to climax),
djembe roll (rapid slaps accelerating into bass), djembe break (syncopated
West African-style break).
.. code-block:: python
score = Score("4/4", bpm=120)
score.drums("djembe", repeats=8, fill="djembe call", fill_every=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/djembe.wav" type="audio/wav"></audio>
Metal Kit
~~~~~~~~~
A dedicated percussion kit for extreme metal subgenres, with
specialized sounds and patterns that go beyond the standard drum kit.
**3 sounds** -- double kick (triggered, tight attack), china cymbal,
and stack (a short, trashy cymbal choke).
**4 patterns:** double kick (relentless double bass drum pattern),
metal blast (blast beat with china cymbal accents), metal groove (a
half-time groove with double kick fills), and metal gallop (the
classic triplet-feel gallop rhythm).
**4 fills:** metal (double kick 16ths with descending toms), metal triplet
(double kick triplets with snare accents), metal blast (alternating
snare/kick 32nds into half-time crash), metal cascade (descending snare
roll → kick roll → alternating → crash ending).
.. code-block:: python
score = Score("4/4", bpm=200)
score.drums("metal blast", repeats=8, fill="metal cascade", fill_every=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/metal_blast.wav" type="audio/wav"></audio>
Cajón
~~~~~
The cajón is a box-shaped percussion instrument from Peru, now
ubiquitous in acoustic and unplugged settings worldwide. Players sit
on the box and strike the front face with their hands.
**3 sounds** -- bass (deep center thump), slap (sharp, snare-like edge
hit with wire buzz), and tap (light finger tap).
**3 patterns:** cajon (basic groove), cajon rumba (flamenco-style rumba),
and cajon folk (folk/acoustic pattern).
**3 fills:** cajon flam (slaps accelerating into bass hits), cajon rumble
(fast taps building to slap accents), cajon breakdown (syncopated
bass-slap groove).
.. code-block:: python
score = Score("4/4", bpm=100)
score.drums("cajon", repeats=8, fill="cajon flam", fill_every=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/cajon.wav" type="audio/wav"></audio>
Marching Percussion
~~~~~~~~~~~~~~~~~~~
A full drumline — snare, quads (tenors), and pitched bass drums.
Every sound is synthesized: kevlar snare heads, aluminum shell ting
on the quads, felt-beater thwack on the basses.
**Snare** -- 3 sounds: MARCH_SNARE (tight kevlar tap), MARCH_RIMSHOT
(woody-metallic crack), MARCH_CLICK (stick click for count-offs).
**Quads** -- 5 sounds: QUAD_1 through QUAD_4 (high to low pitched
tenors) plus QUAD_SPOCK (rim click on the shell).
**Bass drums** -- 5 pitched drums: BASS_1 (highest/smallest) through
BASS_5 (lowest/biggest), each with a prominent felt-beater thwack.
**6 patterns:** march (basic 4/4), cadence (8-beat street beat),
march paradiddle, march roll (buzz crescendo), quad sweep (run across
all 4 drums), quad groove, bass split (cascading across the line),
bass unison (all 5 hit together), drumline (snare + quads + bass).
**Rudiment methods:** ``Part.flam()``, ``Part.diddle()``, and
``Part.cheese()`` for marching rudiments on any drum sound.
**Ensemble rendering:** ``ensemble=N`` on any Part duplicates the
voice with per-player timing tendencies and micro pitch drift.
``ensemble=8`` for a snare line, ``ensemble=20`` for a massive section.
.. code-block:: python
# Full drumline with ensemble
snares = score.part("snares", synth="sine", volume=0.9,
reverb=0.2, ensemble=8)
quads = score.part("quads", synth="sine", volume=0.5,
reverb=0.2, ensemble=4)
basses = score.part("basses", synth="sine", volume=0.55,
reverb=0.2, ensemble=5)
snares.flam(DrumSound.MARCH_SNARE, Duration.QUARTER, velocity=120)
snares.diddle(DrumSound.MARCH_SNARE, Duration.EIGHTH, velocity=60)
# Or use patterns
score.drums("drumline", repeats=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/march_snare.wav" type="audio/wav"></audio>
**Sympathetic resonance:** The marching snare builds up snare wire
buzz as hits accumulate, and the buzz decays during rests — just like
a real drum.
MIDI Export
-----------
+324 -9
View File
@@ -32,13 +32,27 @@ It's a well-tested order that sounds good by default.
Effects are applied in this fixed order::
Signal --> Distortion --> Chorus --> Lowpass Filter --> Delay --> Reverb --> Mix
Signal --> Saturation --> Tremolo --> Distortion --> Cabinet --> Chorus
--> Phaser --> Highpass --> Lowpass --> Delay --> Reverb --> Mix
- **Distortion** first: drives the raw signal before filtering (like
plugging a guitar into a fuzz pedal before the amp).
- **Chorus** second: thickens the distorted signal.
- **Lowpass** third: shapes the tone (like a tone knob on an amp).
- **Delay** fourth: echoes the shaped signal (tap delay / tape echo).
Additionally, these per-note effects are applied before the part effects chain:
- **Sub-oscillator**: octave-below sine mixed in at the oscillator stage
- **Noise layer**: filtered noise mixed per-note for breath/transients
- **Filter envelope**: per-note lowpass sweep (attack/decay/sustain)
- **Velocity → brightness**: harder velocity = brighter filter cutoff
Part-level effects:
- **Saturation** first: subtle even-harmonic warmth (tape/tube color).
- **Tremolo** second: amplitude LFO modulation.
- **Distortion** third: drives the signal before filtering.
- **Cabinet** fourth: speaker cab simulation (rolloff + presence bump).
- **Chorus** fifth: thickens the signal.
- **Phaser** sixth: swept allpass notches.
- **Highpass** seventh: removes low-frequency mud.
- **Lowpass** eighth: shapes the tone (like a tone knob on an amp).
- **Delay** ninth: echoes the shaped signal (tap delay / tape echo).
- **Reverb** last: places everything in a space (room / hall).
Distortion
@@ -83,6 +97,89 @@ Parameters:
distortion_drive=10.0,
)
Cabinet Simulation
------------------
A real guitar amp doesn't just distort the signal -- the speaker
cabinet shapes the tone dramatically. A 12-inch speaker in a closed
cabinet rolls off the harsh high frequencies above 5 kHz and adds a
presence bump around 2--3 kHz that gives the sound its "in the room"
quality. Without a cabinet, distortion sounds thin and fizzy. With
one, it sounds like a real amp.
PyTheory's cabinet simulation applies a speaker rolloff curve (lowpass
at ~5 kHz) combined with a presence resonance bump, placed in the
signal chain immediately after distortion -- exactly where it sits in
a real amp.
Parameters:
- ``cabinet``: Wet/dry mix, 0.0--1.0 (default 0, off).
- 0.3--0.5 = subtle speaker coloring
- 0.6--0.8 = classic amp-in-a-room
- 1.0 = full cabinet, no dry signal
.. code-block:: python
# Classic rock amp tone: distortion into cabinet
guitar = score.part(
"guitar",
synth="saw",
envelope="pluck",
distortion=0.6,
distortion_drive=5.0,
cabinet=0.8,
)
# Clean amp with just cabinet warmth (no distortion)
clean = score.part(
"clean",
synth="triangle",
envelope="pluck",
cabinet=0.5,
)
Analog Drift
------------
Real analog synthesizers are never perfectly in tune. The voltage-
controlled oscillators drift slightly over time as components warm up
and temperature fluctuates. This imperfection is actually a big part
of why vintage analog synths sound so appealing -- the subtle pitch
wandering gives each note a unique, living quality that static digital
oscillators lack.
The ``analog_drift`` parameter adds slow, random pitch variation to
each oscillator, modeling this vintage behavior.
Parameters:
- ``analog_drift``: Drift amount, 0.0--1.0 (default 0, off).
- 0.05--0.1 = subtle warmth (studio-grade analog)
- 0.15--0.25 = noticeable drift (vintage gear warming up)
- 0.3+ = unstable, wobbly (broken tape machine)
.. code-block:: python
# Warm vintage pad
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
analog_drift=0.1,
chorus=0.3,
)
# Lo-fi detuned lead
lead = score.part(
"lead",
synth="saw",
envelope="pluck",
analog_drift=0.25,
)
Chorus
------
@@ -498,6 +595,221 @@ whole mix will gasp for air:
delay=0.2,
)
Saturation
----------
Saturation is the warm, subtle harmonic enhancement of analog tape
machines and tube preamps. Unlike distortion (which uses ``tanh`` and
adds harsh odd harmonics), saturation uses a polynomial waveshaper
that adds even harmonics -- 2nd and 4th -- which the ear perceives as
warmth and fullness. It's why records mixed through a Neve console
sound "bigger" than the same mix done in the box.
Parameters:
- ``saturation``: Amount, 0.0--1.0 (default 0, off).
- 0.05--0.15 = subtle analog warmth (tape machine)
- 0.2--0.4 = noticeable color (tube preamp)
- 0.5+ = heavy coloring
.. code-block:: python
# Warm up a bass
bass = score.part("bass", synth="saw", saturation=0.2)
# Glue a string ensemble
strings = score.part("strings", instrument="string_ensemble",
saturation=0.1)
Tremolo
-------
Amplitude modulation by a sine LFO. The classic vibrating-amp sound.
Essential for vibraphone (the rotating discs in the resonator tubes),
Rhodes electric piano, and surf guitar. Not to be confused with
vibrato (pitch modulation).
Parameters:
- ``tremolo_depth``: Modulation depth, 0.0--1.0 (default 0, off).
- ``tremolo_rate``: LFO speed in Hz (default 5.0).
- 3--5 Hz = classic tremolo
- 5--7 Hz = vibraphone motor speed
- 8+ Hz = ring-mod territory
.. code-block:: python
# Classic Fender amp tremolo
guitar = score.part("guitar", synth="saw", envelope="pluck",
tremolo_depth=0.3, tremolo_rate=4.0)
# Vibraphone with motor
vib = score.part("vib", instrument="vibraphone") # built in
Phaser
------
A chain of allpass filters whose center frequencies are swept by an
LFO, creating moving notches in the spectrum. The classic "jet
engine" or "underwater" effect. Think Small Stone, MXR Phase 90, or
the intro to "Eruption." Different from chorus -- chorus adds a
detuned copy, phaser cancels specific frequencies.
Parameters:
- ``phaser``: Wet/dry mix, 0.0--1.0 (default 0, off).
- ``phaser_rate``: LFO sweep speed in Hz (default 0.5).
- 0.1--0.3 = slow, lush sweep
- 0.5--1.0 = classic phaser
- 2.0+ = fast, Leslie-like
.. code-block:: python
# Slow sweep on a pad
pad = score.part("pad", synth="supersaw", envelope="pad",
phaser=0.4, phaser_rate=0.2)
# Leslie sim on organ (built in)
organ = score.part("organ", instrument="organ")
Highpass Filter
---------------
The opposite of lowpass -- removes low-frequency content below the
cutoff. Useful for cleaning up mud from pads, keeping multiple bass
parts from masking each other, or thinning out a sound to sit better
in a mix.
Parameters:
- ``highpass``: Cutoff frequency in Hz (0 = off).
- 80--150 Hz = clean up sub rumble
- 200--400 Hz = thin out a pad
- 500+ Hz = telephone / radio effect
- ``highpass_q``: Resonance / Q factor (default 0.707).
.. code-block:: python
# Clean up sub rumble from a pad
pad = score.part("pad", synth="supersaw", highpass=120)
# Thin out rhythm guitar to leave room for bass
rhythm = score.part("rhythm", synth="saw", highpass=250)
Filter Envelope
---------------
A per-note lowpass filter whose cutoff sweeps over time. This is the
core of subtractive synthesis -- the reason a Moog bass goes "bwow"
instead of "boop." The filter opens on the attack and closes during
decay, giving each note a distinctive timbral shape.
Parameters:
- ``filter_amount``: Sweep range in Hz (0 = off). How far the filter
opens above the base cutoff.
- ``filter_attack``: Time to reach peak cutoff, in seconds (default 0.01).
- ``filter_decay``: Time to fall to sustain level (default 0.3).
- ``filter_sustain``: Sustain level as fraction of amount, 0.0--1.0
(default 0.0 = filter closes completely after decay).
.. code-block:: python
# Classic synth bass "bwow"
bass = score.part("bass", instrument="synth_bass") # built in
# Acid squelch
acid = score.part("acid", instrument="acid_bass") # built in
# Custom filter sweep on a lead
lead = score.part("lead", synth="saw",
filter_amount=4000, filter_attack=0.01,
filter_decay=0.4, filter_sustain=0.1)
Velocity to Brightness
~~~~~~~~~~~~~~~~~~~~~~
Real instruments get brighter when played harder. ``vel_to_filter``
maps note velocity to filter cutoff boost, so louder notes have more
high-frequency content.
- ``vel_to_filter``: Cutoff boost in Hz at max velocity (default 0, off).
.. code-block:: python
# Piano: soft = mellow, loud = bright
piano = score.part("piano", instrument="piano") # built in
# Manual: custom velocity mapping on a lead
lead = score.part("lead", synth="saw", vel_to_filter=3000)
Sub-Oscillator
--------------
An octave-below sine wave mixed in with the main oscillator. Adds
low-end weight without muddiness -- the sub fills in the fundamental
while the main oscillator provides harmonic character above.
- ``sub_osc``: Mix level, 0.0--1.0 (default 0, off).
- 0.1--0.2 = subtle weight (tuba, bass guitar)
- 0.3--0.5 = heavy sub (808, synth bass)
.. code-block:: python
# Fat 808 kick-bass
bass = score.part("bass", instrument="808_bass") # built in
# Add weight to any part
lead = score.part("lead", synth="saw", sub_osc=0.3)
Noise Layer
-----------
White noise mixed into each note, following the same amplitude
envelope. Adds breath for woodwinds, hammer/felt noise for piano,
bow rosin for strings, and attack transients for percussion.
- ``noise_mix``: Mix level, 0.0--1.0 (default 0, off).
- 0.02--0.04 = subtle texture (strings, piano)
- 0.05--0.08 = noticeable breath (woodwinds)
- 0.1+ = heavy air/texture
.. code-block:: python
# Breathy flute
flute = score.part("flute", instrument="flute") # noise_mix=0.08
# Add air to any synth
pad = score.part("pad", synth="supersaw", noise_mix=0.05)
Configurable FM
---------------
The FM synth now accepts ``fm_ratio`` and ``fm_index`` parameters,
letting you dial in specific FM timbres instead of using the defaults.
- ``fm_ratio``: Modulator frequency as multiple of carrier (default 2.0).
Integer ratios = harmonic timbres; non-integer = metallic/inharmonic.
- ``fm_index``: Modulation depth (default 3.0). Higher = more harmonics.
.. code-block:: python
# Warm electric piano (low ratio, low index)
ep = score.part("ep", synth="fm", fm_ratio=1.0, fm_index=1.5)
# Bright metallic bell (high ratio, high index)
bell = score.part("bell", synth="fm", fm_ratio=3.5, fm_index=5.0)
# Glockenspiel
glock = score.part("glock", instrument="glockenspiel") # built in
Automation
----------
@@ -528,9 +840,12 @@ processes each section independently:
lead.set(lowpass=4000, distortion=0.7, reverb=0.3)
lead.arpeggio("Gm", bars=4, pattern="updown", octaves=2)
Any parameter can be automated: ``lowpass``, ``lowpass_q``, ``reverb``,
``reverb_decay``, ``delay``, ``delay_time``, ``delay_feedback``,
``distortion``, ``distortion_drive``, ``chorus``, ``volume``.
Any parameter can be automated: ``lowpass``, ``lowpass_q``, ``highpass``,
``reverb``, ``reverb_decay``, ``reverb_type``, ``delay``, ``delay_time``,
``delay_feedback``, ``distortion``, ``distortion_drive``, ``chorus``,
``phaser``, ``phaser_rate``, ``saturation``, ``tremolo_depth``,
``tremolo_rate``, ``cabinet``, ``cabinet_brightness``, ``analog_drift``,
``volume``.
LFO Automation
--------------
+46 -1
View File
@@ -66,6 +66,21 @@ the mix louder and punchier:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/playback_basic.wav" type="audio/wav"></audio>
The render pipeline respects the Score's ``temperament`` and
``reference_pitch`` settings, so Baroque or microtonal scores play back
at the correct tuning:
.. code-block:: python
score = Score("4/4", bpm=80, temperament="meantone", reference_pitch=415.0)
Press **Ctrl+C** at any time during playback to stop — PyTheory catches
``KeyboardInterrupt`` and stops audio cleanly.
See :doc:`sequencing` for how to build scores and parts.
render_score() -- Headless Rendering
@@ -153,7 +168,7 @@ Play a drum pattern through the speakers:
play_pattern(Pattern.preset("rock"), repeats=4, bpm=120)
play_pattern(Pattern.preset("bossa nova"), repeats=4, bpm=140)
See :doc:`drums` for the full list of 58 presets and 21 fills.
See :doc:`drums` for the full list of 80+ presets and 21 fills.
play_progression() -- Quick Chord Playback
------------------------------------------
@@ -177,3 +192,33 @@ Optional synth, envelope, and gap parameters:
play_progression(chords, t=2000, envelope=Envelope.PAD)
That's the workflow: hear it, tweak it, hear it again. When it sounds right, export to WAV or MIDI and take it somewhere bigger.
MIDI Import
-----------
Load any Standard MIDI File into a Score — then play it through
PyTheory's synth engine with effects, or analyze the theory:
.. code-block:: python
from pytheory import Score
from pytheory.play import play_score
score = Score.from_midi("song.mid")
# See what's inside
for name, part in score.parts.items():
print(f"{name}: {len(part.notes)} notes")
# Change the synth and add effects
score.parts["ch1"].synth = "saw"
score.parts["ch1"].reverb_mix = 0.3
play_score(score)
Each MIDI channel becomes a named Part (``ch1``, ``ch2``, etc.).
Channel 10 (drums) becomes drum hits. Tempo, time signature,
note durations, and velocities are all preserved.
Download any MIDI file from the internet, load it, play it through
the synth engine with reverb and delay. That's the whole idea.
+20 -34
View File
@@ -143,48 +143,34 @@ chords, melody, bass, each with their own synth and effects:
.. code-block:: python
from pytheory import Score, Pattern, Key, Duration, Chord
from pytheory import Score, Key, Duration
from pytheory.play import play_score
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
score = Score("4/4", bpm=120)
score.drums("rock", repeats=8, fill="rock", fill_every=4)
chords = score.part(
"chords",
synth="fm",
envelope="pad",
reverb=0.4,
)
lead = score.part(
"lead",
synth="saw",
envelope="pluck",
delay=0.3,
lowpass=3000,
humanize=0.2,
)
bass = score.part(
"bass",
synth="sine",
lowpass=500,
)
piano = score.part("piano", instrument="piano", reverb=0.3)
lead = score.part("lead", synth="saw", envelope="pluck",
delay=0.2, reverb=0.2, lowpass=4000)
bass = score.part("bass", synth="triangle", lowpass=900)
key = Key("A", "minor")
for chord in key.progression("i", "iv", "V", "i"):
chords.add(chord, Duration.WHOLE)
chords.add(chord, Duration.WHOLE)
for chord in Key("G", "major").progression("I", "V", "vi", "IV") * 2:
piano.add(chord, Duration.WHOLE)
lead.arpeggio("Am", bars=2, pattern="updown", octaves=2)
lead.arpeggio("Dm", bars=2, pattern="updown", octaves=2)
lead.set(lowpass=5000, reverb=0.3)
lead.arpeggio("E7", bars=2, pattern="up", octaves=2)
lead.arpeggio("Am", bars=2, pattern="updown", octaves=2)
lead.add("D5", 1).add("B4", 0.5).add("D5", 0.5)
lead.add("G5", 1).add("E5", 1)
lead.add("D5", 0.5).add("B4", 0.5).add("A4", 1)
lead.add("G4", 2).rest(2)
for n in ["A2", "E2", "A2", "C3"] * 4:
bass.add(n, Duration.QUARTER)
for n in ["G2", "G2", "D2", "D2", "E2", "E2", "C2", "C2"] * 2:
bass.add(n, Duration.HALF)
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/quickstart.wav" type="audio/wav"></audio>
Export to Your DAW
------------------
@@ -233,7 +219,7 @@ drum voices with stereo panning.
mandolin family, violin family, banjo, harp, oud, sitar, erhu, and
more) with chord fingering generation and scale diagrams.
**Output** — stereo playback, WAV export, MIDI export.
**Output** — stereo playback, WAV export, MIDI import/export.
**Interface** — REPL with tab completion (``pytheory repl``), CLI with
15 commands. ``pytheory demo``, ``pytheory key``, ``pytheory chord``,
+391 -41
View File
@@ -47,6 +47,18 @@ A ``Duration`` represents a note length in beats (quarter note = 1 beat):
>>> Duration.TRIPLET_QUARTER.value
0.6666666666666666
Duration supports arithmetic — multiply, divide, and add to create
compound durations:
.. code-block:: pycon
>>> Duration.WHOLE * 2
8.0
>>> Duration.HALF + Duration.QUARTER
3.0
>>> Duration.WHOLE / 2
2.0
Time Signatures
---------------
@@ -149,6 +161,10 @@ Chords work just like tones — pass any ``Chord`` object:
for chord in chords:
score.add(chord, Duration.WHOLE)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/chords_basic.wav" type="audio/wav"></audio>
.. code-block:: pycon
>>> score.measures
@@ -232,6 +248,31 @@ Chords and Tone objects work the same way:
for note in ["A2", "C3", "E3", "A2", "D2", "F2", "A2", "D2"]:
bass.add(note, Duration.QUARTER)
Polyphonic Hold
---------------
``Part.hold()`` adds a note without advancing the beat position —
the next note starts at the *same* time. This enables polyphonic
overlap on a single part: piano sustain, sitar drone under melody,
guitar strum texture.
.. code-block:: python
piano = score.part("piano", instrument="piano", reverb=0.3)
# Hold a C major chord for 8 beats
piano.hold("C3", Duration.WHOLE * 2, velocity=60)
piano.hold("E3", Duration.WHOLE * 2, velocity=55)
piano.hold("G3", Duration.WHOLE * 2, velocity=55)
# Melody plays simultaneously on top
for n in ["E4", "G4", "C5", "G4", "E4", "D4", "C4", "E4"]:
piano.add(n, Duration.QUARTER, velocity=80)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/piano_hold.wav" type="audio/wav"></audio>
Arpeggiator
------------
@@ -280,6 +321,10 @@ Chain arpeggios through a progression:
for sym in ["Cm", "Fm", "Abm", "Gm"]:
lead.arpeggio(sym, bars=2, pattern="updown", octaves=2)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/arpeggio.wav" type="audio/wav"></audio>
Combined with legato, glide, distortion, and a resonant lowpass, this
produces the classic acid/trance arpeggiator sound.
@@ -310,12 +355,18 @@ portamento (pitch slides between notes):
acid = score.part(
"acid",
synth="saw",
envelope="pad",
legato=True,
glide=0.04,
lowpass=3000,
lowpass_q=6.0,
distortion=0.3,
)
acid.add("C2", 0.25).add("C3", 0.25).add("G2", 0.25).add("C2", 0.25)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/legato_glide.wav" type="audio/wav"></audio>
- ``legato``: If True, no envelope retrigger between notes (default False).
- ``glide``: Portamento time in seconds (default 0, instant).
0.03--0.05 = quick 303 slide, 0.1--0.2 = slow glide.
@@ -323,63 +374,51 @@ portamento (pitch slides between notes):
Complete Example
----------------
A full multi-part arrangement built from scratch — bossa nova with FM
rhodes, triangle lead, and filtered bass:
A full multi-part arrangement — rock beat with piano chords, saw
lead, and filtered bass:
.. code-block:: python
from pytheory import Score, Pattern, Key, Duration, Chord
from pytheory import Score, Key, Duration, Chord
from pytheory.play import play_score
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
score = Score("4/4", bpm=120)
score.drums("rock", repeats=8, fill="rock", fill_every=4)
# FM rhodes with reverb
rhodes = score.part(
"rhodes",
synth="fm",
envelope="piano",
volume=0.3,
reverb=0.4,
reverb_decay=1.8,
)
# Piano chords with reverb
piano = score.part("piano", instrument="piano", volume=0.4, reverb=0.3)
# Triangle lead with delay
# Saw lead with delay
lead = score.part(
"lead",
synth="triangle",
envelope="pluck",
volume=0.45,
delay=0.25,
delay_time=0.32,
delay_feedback=0.35,
reverb=0.2,
"lead", synth="saw", envelope="pluck", volume=0.4,
delay=0.2, delay_time=0.33, reverb=0.2, lowpass=3000,
)
# Filtered bass
bass = score.part(
"bass",
synth="sine",
envelope="pluck",
volume=0.45,
lowpass=600,
)
bass = score.part("bass", synth="triangle", envelope="pluck",
volume=0.45, lowpass=1200)
for sym in ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]:
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
for chord in Key("G", "major").progression("I", "V", "vi", "IV") * 2:
piano.add(chord, Duration.WHOLE)
for n, d in [
("E5", 0.67), ("D5", 0.33), ("C5", 0.67), ("B4", 0.33),
("A4", 1), ("C5", 0.67), ("E5", 0.33), ("D5", 0.67), ("C5", 0.33),
("A4", 1),
]:
lead.add(n, d)
lead.add("D5", 1).add("B4", 0.5).add("D5", 0.5)
lead.add("G5", 1).add("E5", 1)
lead.add("D5", 0.5).add("B4", 0.5).add("A4", 1)
lead.add("G4", 2).rest(2)
lead.add("D5", 1).add("B4", 0.5).add("D5", 0.5)
lead.add("G5", 1).add("A5", 1)
lead.add("G5", 0.5).add("E5", 0.5).add("D5", 1)
lead.add("B4", 2).rest(2)
for n in ["A2", "E2", "A2", "C3", "D2", "A2", "D2", "F2"]:
bass.add(n, Duration.QUARTER)
for n in ["G2", "G2", "D2", "D2", "E2", "E2", "C2", "C2"] * 2:
bass.add(n, Duration.HALF)
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/complete_rock.wav" type="audio/wav"></audio>
Velocity
--------
@@ -399,6 +438,157 @@ The arpeggiator also accepts velocity:
lead.arpeggio("Am", bars=2, pattern="up", velocity=80)
Articulations
-------------
Articulations change *how* a note is played — its attack, duration, and
weight. A staccato note is short and bouncy. A marcato note hits hard.
A legato note melts into the next one. This is the difference between
a melody that sounds like a MIDI file and one that sounds like a
musician played it.
Pass ``articulation=`` to ``Part.add()``:
.. code-block:: python
piano.add("C4", Duration.QUARTER, articulation="staccato") # short, bouncy
piano.add("D4", Duration.QUARTER, articulation="legato") # smooth, overlaps
piano.add("E4", Duration.QUARTER, articulation="marcato") # heavy accent
piano.add("F4", Duration.QUARTER, articulation="tenuto") # held, soft attack
piano.add("G4", Duration.QUARTER, articulation="accent") # louder
piano.add("C5", Duration.HALF, articulation="fermata") # held longer
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/articulations.wav" type="audio/wav"></audio>
What each articulation does:
- **staccato** — plays ~40% of the note duration with a quick fade-out. Short and detached.
- **legato** — extends ~15% into the next note. Smooth and connected.
- **marcato** — 25% velocity boost + sharper attack. Heavy and accented.
- **tenuto** — full duration with a softer attack ramp. Held and deliberate.
- **accent** — 20% velocity boost, no duration change.
- **fermata** — stretches the note 50% longer.
Articulations work on ``Part.hold()`` and ``Part.hit()`` too.
Dynamic Curves
--------------
Real music breathes — phrases get louder, get quieter, swell and
recede. Dynamic curves let you shape the velocity across a sequence
of notes instead of setting each one manually.
.. code-block:: python
# Crescendo: quiet to loud
piano.crescendo(["C4","D4","E4","F4","G4","A4","B4","C5"],
Duration.QUARTER, start_vel=30, end_vel=110)
# Decrescendo: loud to quiet
piano.decrescendo(["C5","B4","A4","G4","F4","E4","D4","C4"],
Duration.QUARTER, start_vel=110, end_vel=30)
# Swell: up then back down (orchestral < > shape)
strings.swell(["C4","D4","E4","F4","G4","F4","E4","D4"],
Duration.QUARTER, low_vel=35, peak_vel=110)
# Custom curve: explicit velocity per note
piano.dynamics(["C4","E4","G4","C5"], Duration.QUARTER,
velocities=[50, 80, 110, 90])
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dynamics.wav" type="audio/wav"></audio>
Four methods:
- **crescendo()** — linear velocity ramp from ``start_vel`` to ``end_vel``.
- **decrescendo()** — same thing, but typically loud to quiet.
- **swell()** — ramps up to the midpoint, then back down. The classic
orchestral crescendo-decrescendo.
- **dynamics()** — the general form. Pass a ``(start, end)`` tuple for
a linear ramp, or a list of velocities for a custom curve.
All four accept ``articulation=`` to combine dynamics with articulations:
.. code-block:: python
# Staccato crescendo — bouncy notes getting louder
piano.crescendo(["C4","E4","G4","C5","E5","G5","C6","E6"],
Duration.EIGHTH, start_vel=40, end_vel=110,
articulation="staccato")
Part.hit() — Manual Drum Placement
-----------------------------------
The pattern system is great for grooves, but sometimes you want to
place individual drum hits with full control — articulations, effects,
and all. ``Part.hit()`` puts a drum sound into a Part's note stream:
.. code-block:: python
from pytheory import DrumSound
kit = score.part("kit", synth="sine", volume=0.7)
kit.hit(DrumSound.KICK, Duration.QUARTER, articulation="accent")
kit.hit(DrumSound.CLOSED_HAT, Duration.EIGHTH, velocity=60)
kit.hit(DrumSound.SNARE, Duration.EIGHTH, articulation="marcato")
Because hits go through the normal Part renderer, they get humanize,
effects, and articulations for free. Use this for custom beats that
don't fit a preset pattern, or for one-shot accent hits layered on
top of a pattern.
Rudiments — Flam, Diddle, Cheese
---------------------------------
Marching percussion rudiments as methods on any Part:
.. code-block:: python
from pytheory import DrumSound
p = score.part("snares", synth="sine", volume=0.9)
# Flam: grace note + main hit (gap controls tightness)
p.flam(DrumSound.MARCH_SNARE, Duration.QUARTER, velocity=120)
# Diddle: two equal strokes in one note duration
p.diddle(DrumSound.MARCH_SNARE, Duration.EIGHTH, velocity=60)
# Cheese: flam + diddle combined
p.cheese(DrumSound.MARCH_SNARE, Duration.QUARTER, velocity=120)
Ensemble
--------
Any Part can be rendered as an ensemble — multiple players with
per-player timing tendencies and micro pitch drift:
.. code-block:: python
# 8-player snare line
snares = score.part("snares", synth="sine", volume=0.9, ensemble=8)
# 20-player string section
strings = score.part("strings", instrument="string_ensemble", ensemble=20)
# Single player (default)
solo = score.part("solo", instrument="violin")
Each ensemble voice gets a consistent timing personality (some rush,
some drag) plus small per-note wobble, and slightly different tuning.
The result sounds like a real section — together but alive.
Solo snare, then an 8-player section plays the same pattern:
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/ensemble.wav" type="audio/wav"></audio>
Swing and Groove
----------------
@@ -478,6 +668,54 @@ integrate naturally with the rest of the automation system:
pad.rest(Duration.WHOLE)
pad.rest(Duration.WHOLE)
Parameter Ramps
---------------
Fades only control volume. ``Part.ramp()`` smoothly sweeps *any*
parameter from its current value to a target — filters, reverb,
distortion, chorus, delay, anything ``.set()`` accepts. This is how
you build filter sweeps, gradual effect sends, and EDM buildups.
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=200, lowpass_q=3.0)
# Open the filter over 8 bars
lead.ramp(over=Duration.WHOLE * 8, lowpass=8000)
# Ramp multiple params at once
pad.ramp(over=Duration.WHOLE * 4, reverb=0.5, chorus=0.3)
# Close the filter with distortion fading in
lead.ramp(over=Duration.WHOLE * 4, lowpass=400, distortion=0.5)
Four interpolation curves:
- **linear** — constant rate of change (default).
- **ease_in** — starts slow, accelerates. Good for buildups.
- **ease_out** — starts fast, decelerates. Good for releases.
- **ease_in_out** — slow at both ends. Smooth and natural.
.. code-block:: python
# EDM buildup: slow start, accelerating filter sweep
lead.ramp(over=Duration.WHOLE * 8, curve="ease_in", lowpass=8000)
# Smooth reverb wash fading in and settling
pad.ramp(over=Duration.WHOLE * 4, curve="ease_in_out", reverb=0.6)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/filter_ramp.wav" type="audio/wav"></audio>
``ramp()`` generates automation points every quarter-beat by default.
Set ``resolution=0.125`` for smoother curves (every 32nd note), or
``resolution=1.0`` for lighter automation (every beat).
Combine with ``lfo()`` for cyclic modulation and ``ramp()`` for
one-shot sweeps — together they cover the full range of parameter
automation.
Humanize
--------
@@ -574,3 +812,115 @@ Define sections with ``score.section()`` and repeat them with
Use any names you want — ``"intro"``, ``"verse"``, ``"chorus"``,
``"bridge"``, ``"drop"``, ``"breakdown"``, ``"outro"``, or anything
that makes sense for your song. The names are just labels.
Guitar Strumming
----------------
Any part with a fretboard can strum chords using real fingering
positions. The ``strum()`` method looks up the chord on the fretboard,
gets the correct voicing, and plays all strings as a chord.
.. code-block:: python
from pytheory import Fretboard
guitar = score.part("guitar", instrument="acoustic_guitar",
fretboard=Fretboard.guitar())
guitar.strum("Am", Duration.HALF, direction="down")
guitar.strum("G", Duration.HALF, direction="up")
guitar.strum("F", Duration.WHOLE)
Works with any fretboard instrument — guitar, ukulele, banjo, mandolin.
Works with any guitar preset — clean, crunch, distorted, orange, metal.
Pitch Bends
-----------
Bend a note's pitch up or down over its duration. Essential for guitar
bends, sitar meends, trombone slides, and vocal-style expression.
.. code-block:: python
# Guitar bend: D up to E (2 semitones)
guitar.add("D4", Duration.HALF, bend=2, bend_type="smooth")
# Release bend: E back down to D
guitar.add("E4", Duration.HALF, bend=-2)
# Blues curl: hold then bend at the end
guitar.add("C4", Duration.HALF, bend=1, bend_type="late")
Three bend types:
- ``"smooth"`` — logarithmic (default). Perceptually even pitch change.
- ``"linear"`` — linear frequency interpolation. Mechanical/synth feel.
- ``"late"`` — holds the starting pitch for 60%, bends in the last 40%.
The classic blues "curl."
Rolls
-----
Rapid repeated notes with a velocity ramp — perfect for timpani
rolls, snare rolls, tremolo on any instrument. The velocity ramps
from ``velocity_start`` to ``velocity_end`` for crescendo or
decrescendo effects.
.. code-block:: python
# Timpani crescendo roll
timp = score.part("timp", instrument="timpani")
timp.roll("C3", Duration.WHOLE, velocity_start=20, velocity_end=110)
timp.add("C3", Duration.HALF, velocity=127) # big accent
# Snare roll with 32nd notes
snare = score.part("snare", synth="noise", envelope="pluck")
snare.roll("C4", Duration.HALF, speed=0.125,
velocity_start=40, velocity_end=100)
# Decrescendo (loud to quiet)
timp.roll("G2", Duration.WHOLE, velocity_start=100, velocity_end=30)
Parameters:
- ``velocity_start``: Starting velocity (default 40).
- ``velocity_end``: Ending velocity (default 100).
- ``speed``: Note subdivision (default ``Duration.SIXTEENTH``).
Use ``0.125`` for 32nd notes, ``Duration.EIGHTH`` for 8th notes.
Tuning Systems
--------------
A Score can use any tuning system and temperament:
.. code-block:: python
# Baroque harpsichord — meantone tuning, A=415
score = Score("4/4", bpm=80, temperament="meantone",
reference_pitch=415.0)
# Indian classical — 22-shruti system
score = Score("4/4", bpm=75, system="shruti")
# Just intonation — pure intervals
score = Score("4/4", bpm=90, temperament="just")
The Score constructor accepts these tuning parameters:
- ``system``: Musical system name (default ``"western"``). Any system
from :doc:`systems` works — ``"indian"``, ``"shruti"``, ``"maqam"``,
``"carnatic"``, etc. Note strings in ``Part.add()`` are parsed against
this system.
- ``temperament``: Tuning temperament — ``"equal"`` (default),
``"pythagorean"``, ``"meantone"``, ``"just"``.
- ``reference_pitch``: Concert pitch in Hz (default 440.0). Use 415.0
for Baroque tuning, 432.0 for "Verdi tuning", etc.
Custom equal temperaments via the ``TET()`` factory:
.. code-block:: python
from pytheory import TET
edo19 = TET(19) # 19-tone equal temperament
score = Score("4/4", bpm=100, system=edo19)
+806 -7
View File
@@ -1,7 +1,7 @@
Synthesizers
============
PyTheory includes 10 built-in waveforms and 8 ADSR envelope presets.
PyTheory includes 41 built-in waveforms and 10 ADSR envelope presets.
Every sound is generated from scratch -- no samples or external audio
files needed.
@@ -37,6 +37,10 @@ building block of all other waveforms (Fourier's theorem).
tone = Tone.from_string("C4", system="western")
play(tone, synth=Synth.SINE)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_sine.wav" type="audio/wav"></audio>
Sawtooth
~~~~~~~~
@@ -50,6 +54,10 @@ Named for its ramp shape.
play(tone, synth=Synth.SAW)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_saw.wav" type="audio/wav"></audio>
Triangle
~~~~~~~~
@@ -63,6 +71,10 @@ described as "woody" or "hollow."
play(tone, synth=Synth.TRIANGLE)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_triangle.wav" type="audio/wav"></audio>
Square
~~~~~~
@@ -76,6 +88,10 @@ pulse wave with a 50% duty cycle.
play(tone, synth=Synth.SQUARE)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_square.wav" type="audio/wav"></audio>
Extended Waveforms
------------------
@@ -98,6 +114,10 @@ the classic NES-style buzzy tone.
lead = score.part("lead", synth="pulse", envelope="pluck")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_pulse.wav" type="audio/wav"></audio>
FM Synthesis
~~~~~~~~~~~~
@@ -109,18 +129,24 @@ the electric piano in every Whitney Houston ballad, the bass in every
Depeche Mode track, the bells in a thousand TV jingles. If you heard
pop music in the 80s, you heard FM synthesis.
**Use for:** electric piano (rhodes), bells, metallic leads, jazz chords.
**Use for:** bells, metallic leads, glassy pads, DX7-style sounds.
.. code-block:: python
rhodes = score.part(
"rhodes",
bells = score.part(
"bells",
synth="fm",
envelope="piano",
envelope="bell",
fm_ratio=3.0,
fm_index=5.0,
volume=0.3,
reverb=0.4,
)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_fm.wav" type="audio/wav"></audio>
Noise
-----
@@ -142,6 +168,10 @@ Useful as a texture layer, a percussion source, or a wind/ocean effect.
lowpass=2000,
)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_noise.wav" type="audio/wav"></audio>
Ensemble Waveforms
------------------
@@ -174,6 +204,10 @@ supersaw.
reverb=0.5,
)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_supersaw.wav" type="audio/wav"></audio>
PWM Slow
~~~~~~~~
@@ -195,6 +229,10 @@ from Boards of Canada to Drake? PWM with a slow LFO.
reverb=0.4,
)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_pwm_slow.wav" type="audio/wav"></audio>
PWM Fast
~~~~~~~~
@@ -207,6 +245,10 @@ produces a natural chorus/vibrato effect built into the waveform itself.
lead = score.part("lead", synth="pwm_fast", envelope="pluck", volume=0.5)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_pwm_fast.wav" type="audio/wav"></audio>
ADSR Envelopes
--------------
@@ -233,7 +275,7 @@ shapes the amplitude over time for natural-sounding notes:
- **Sustain** -- the held volume while the note is on.
- **Release** -- how quickly it fades to silence after the note ends.
PyTheory includes 8 presets:
PyTheory includes 10 presets:
.. code-block:: python
@@ -247,6 +289,8 @@ PyTheory includes 8 presets:
play(tone, envelope=Envelope.ORGAN) # Instant on/off, no shaping
play(tone, envelope=Envelope.BELL) # Instant attack, long ring
play(tone, envelope=Envelope.STRINGS) # Gradual bow attack
play(tone, envelope=Envelope.BOWED) # Bow bite into sustain
play(tone, envelope=Envelope.MALLET) # Strike with ringing sustain
play(tone, envelope=Envelope.STACCATO) # Short and punchy
play(tone, envelope=Envelope.NONE) # Raw waveform, no shaping
@@ -260,8 +304,10 @@ Name Character
``"pluck"`` Sharp attack, fast decay -- guitar pick, harp
``"pad"`` Slow fade in, lush sustain -- strings, synth pads
``"organ"`` Instant on/off -- Hammond organ, no shaping
``"bell"`` Instant attack, long ring -- vibraphone, tubular
``"bell"`` Instant attack, no sustain -- short metallic ring
``"strings"`` Gradual bow attack -- orchestral strings, slow
``"bowed"`` Bow bite into sustain -- solo strings, brass
``"mallet"`` Strike with ringing sustain -- vibraphone, celesta
``"staccato"`` Short and punchy -- funk stabs, percussive hits
``"none"`` Raw waveform, no amplitude shaping at all
=============== ================================================
@@ -341,6 +387,759 @@ Reverb is also stereo — the left and right channels get different
early reflection patterns, so the reverb tail occupies real space
in the stereo field rather than sitting dead center.
Physical Modeling
-----------------
Three synths go beyond traditional waveform synthesis into physical
modeling territory — they simulate how real instruments produce sound.
Karplus-Strong Pluck
~~~~~~~~~~~~~~~~~~~~
A burst of noise fed into a short delay line. The delay length sets
the pitch, the feedback filter models the string decaying. This is
how every physical modeling synth since 1983 does plucked strings.
It sounds genuinely like a real guitar, harp, or koto.
.. code-block:: python
guitar = score.part("guitar", synth="pluck_synth")
harp = score.part("harp", instrument="harp") # uses pluck_synth
Hammond Organ
~~~~~~~~~~~~~
Additive synthesis with drawbar harmonics — sine waves at the
fundamental plus 2nd, 3rd, 4th, 5th, 6th, and 8th harmonics mixed
at musical levels. Warm, round, unmistakably organ.
.. code-block:: python
organ = score.part("organ", synth="organ_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_organ.wav" type="audio/wav"></audio>
String Ensemble
~~~~~~~~~~~~~~~
Filtered sawtooth with body resonance formants at ~500 Hz and ~1500 Hz,
modeling the way a violin or cello body shapes the sound. Warmer and
more "wooden" than a raw saw wave.
.. code-block:: python
violin = score.part("violin", synth="strings_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_strings.wav" type="audio/wav"></audio>
Dedicated Instrument Synths
--------------------------
Beyond the classic and physical modeling waveforms, PyTheory includes
31 dedicated instrument synths. Each one uses tailored synthesis
techniques -- additive harmonics, formant shaping, body resonance
modeling, and specialized envelopes -- to capture the character of a
specific acoustic instrument. These are the waveforms that bring the
total count to 41.
Piano Synth
~~~~~~~~~~~
Hammer-strike envelope with body resonance and subtle inharmonicity.
Models the way a felt hammer excites steel strings inside a wooden
soundboard.
.. code-block:: python
piano = score.part("piano", synth="piano_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_piano.wav" type="audio/wav"></audio>
Rhodes Electric Piano
~~~~~~~~~~~~~~~~~~~~~
The Fender Rhodes — a rubber-tipped hammer strikes a steel tine
next to a tonebar, picked up by an electromagnetic pickup. Warm,
bell-like, with a bright metallic attack that mellows into a
singing sustain. The sound of jazz clubs, soul, and neo-soul.
.. code-block:: python
rhodes = score.part("rhodes", synth="rhodes_synth")
# Or use the instrument preset (adds tremolo + chorus)
rhodes = score.part("rhodes", instrument="electric_piano")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_rhodes.wav" type="audio/wav"></audio>
Wurlitzer Electric Piano
~~~~~~~~~~~~~~~~~~~~~~~~
The Wurlitzer uses a vibrating steel reed (not a tine like Rhodes)
picked up by an electrostatic pickup. More nasal, reedy, and biting
— it barks and growls when played hard. Think Supertramp, Ray Charles.
.. code-block:: python
wurli = score.part("wurli", synth="wurlitzer_synth")
wurli = score.part("wurli", instrument="wurlitzer")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_wurlitzer.wav" type="audio/wav"></audio>
Vibraphone Synth
~~~~~~~~~~~~~~~~
Struck aluminum bars with motor-driven tremolo discs. The spinning
motor modulates the sound through the resonator tubes, creating the
signature vibraphone shimmer. Inharmonic bar modes at 1x, 2.76x, 5.4x.
.. code-block:: python
vib = score.part("vib", synth="vibraphone_synth")
vib = score.part("vib", instrument="vibraphone")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_vibraphone.wav" type="audio/wav"></audio>
Pipe Organ Synth
~~~~~~~~~~~~~~~~
Multiple ranks of pipes — principal 8', octave 4', fifteenth 2'.
Constant air pressure means no dynamics. Wind chiff at the attack.
Best with cathedral reverb.
.. code-block:: python
organ = score.part("organ", synth="pipe_organ_synth")
organ = score.part("organ", instrument="pipe_organ")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_pipe_organ.wav" type="audio/wav"></audio>
Choir Synth
~~~~~~~~~~~
Voices singing vowels shaped by formant bandpass filters. The glottal
source is filtered through vocal tract resonances — F1, F2, F3, F4 —
which is what makes "ah" sound different from "oo". Use ``lyric=``
to control the vowel. Best with ``ensemble=`` for a full section.
.. code-block:: python
choir = score.part("choir", synth="choir_synth")
choir = score.part("choir", instrument="choir") # ensemble=6 + cathedral reverb
choir.add("C4", Duration.WHOLE, lyric="ah")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_choir.wav" type="audio/wav"></audio>
Bass Guitar Synth
~~~~~~~~~~~~~~~~~
Plucked string model with finger-damped harmonics and low-end warmth.
.. code-block:: python
bass = score.part("bass", synth="bass_guitar_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_bass_guitar.wav" type="audio/wav"></audio>
Flute Synth
~~~~~~~~~~~~
Breathy noise excitation through a resonant tube model, with
overblowing behavior at higher velocities.
.. code-block:: python
flute = score.part("flute", synth="flute_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_flute.wav" type="audio/wav"></audio>
Trumpet Synth
~~~~~~~~~~~~~
Brass lip-buzz model with spectral brightness that increases with
velocity, plus a characteristic brassy edge from shaped harmonics.
.. code-block:: python
trumpet = score.part("trumpet", synth="trumpet_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_trumpet.wav" type="audio/wav"></audio>
Clarinet Synth
~~~~~~~~~~~~~~
Cylindrical bore model producing mostly odd harmonics, giving the
characteristic hollow, woody tone.
.. code-block:: python
clarinet = score.part("clarinet", synth="clarinet_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_clarinet.wav" type="audio/wav"></audio>
Oboe Synth
~~~~~~~~~~~
Double-reed model with nasal formant shaping and a buzzy, penetrating
timbre.
.. code-block:: python
oboe = score.part("oboe", synth="oboe_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_oboe.wav" type="audio/wav"></audio>
Marimba Synth
~~~~~~~~~~~~~
Tuned bar model with a soft mallet attack and a warm, resonant decay
that emphasizes the fundamental.
.. code-block:: python
marimba = score.part("marimba", synth="marimba_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_marimba.wav" type="audio/wav"></audio>
Harpsichord Synth
~~~~~~~~~~~~~~~~~
Plucked-string model with a bright, immediate attack and rapid decay
-- the characteristic "plink" of a quill plucking a string.
.. code-block:: python
harpsi = score.part("harpsi", synth="harpsichord_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_harpsichord.wav" type="audio/wav"></audio>
Cello Synth
~~~~~~~~~~~
Bowed string model with body formants at cello resonance frequencies,
producing a rich, warm, sustained tone.
.. code-block:: python
cello = score.part("cello", synth="cello_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_cello.wav" type="audio/wav"></audio>
Harp Synth
~~~~~~~~~~
Plucked string with longer sustain and gentle high-frequency rolloff,
modeling nylon strings on a resonant frame.
.. code-block:: python
harp = score.part("harp", synth="harp_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_harp.wav" type="audio/wav"></audio>
Upright Bass Synth
~~~~~~~~~~~~~~~~~~
Pizzicato double bass with woody body resonance and a thumpy low end.
.. code-block:: python
bass = score.part("bass", synth="upright_bass_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_upright_bass.wav" type="audio/wav"></audio>
Acoustic Guitar Synth
~~~~~~~~~~~~~~~~~~~~~
Steel-string model with pick transient, body resonance, and natural
string decay.
.. code-block:: python
guitar = score.part("guitar", synth="acoustic_guitar_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_acoustic_guitar.wav" type="audio/wav"></audio>
Electric Guitar Synth
~~~~~~~~~~~~~~~~~~~~~
Magnetic pickup model with brighter harmonics and less body resonance
than the acoustic, ready for effects processing.
.. code-block:: python
eguitar = score.part("eguitar", synth="electric_guitar_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_electric_guitar.wav" type="audio/wav"></audio>
Sitar Synth
~~~~~~~~~~~~
Sympathetic string resonance with the characteristic buzzy "jawari"
bridge, producing a shimmering, metallic sustain.
.. code-block:: python
sitar = score.part("sitar", synth="sitar_synth")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_sitar.wav" type="audio/wav"></audio>
Timpani Synth
~~~~~~~~~~~~~
Large kettle drum with definite pitch. Inharmonic membrane modes
(1.0, 1.5, 1.99, 2.44), felt mallet attack, copper kettle resonance.
Use ``Part.roll()`` for crescendo timpani rolls.
.. code-block:: python
timp = score.part("timp", synth="timpani_synth")
timp.roll("C3", Duration.WHOLE, velocity_start=20, velocity_end=110)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_timpani.wav" type="audio/wav"></audio>
Saxophone Synth
~~~~~~~~~~~~~~~
Single reed through a conical brass bore. All harmonics with strong
mids, reed buzz, and brass body warmth. Four presets: ``saxophone``,
``alto_sax``, ``tenor_sax``, ``bari_sax``.
.. code-block:: python
sax = score.part("sax", instrument="tenor_sax")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_saxophone.wav" type="audio/wav"></audio>
Pedal Steel Synth
~~~~~~~~~~~~~~~~~
The Nashville crying sound — singing harmonics with slow vibrato
and long sustain. Pairs naturally with spring reverb.
.. code-block:: python
steel = score.part("steel", instrument="pedal_steel")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_pedal_steel.wav" type="audio/wav"></audio>
Theremin Synth
~~~~~~~~~~~~~~
Pure sine with natural hand wobble — the eerie sci-fi sound.
Best used with legato and glide for continuous pitch.
.. code-block:: python
theremin = score.part("theremin", instrument="theremin")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_theremin.wav" type="audio/wav"></audio>
Kalimba Synth
~~~~~~~~~~~~~
Metal tines on a wooden body. Bright, bell-like attack with
inharmonic overtones (modes at 1x, 2.92x, 5.4x).
.. code-block:: python
kalimba = score.part("kalimba", instrument="kalimba")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_kalimba.wav" type="audio/wav"></audio>
Steel Drum Synth
~~~~~~~~~~~~~~~~
Hammered metal pan with bright, ringing, tropical character.
Inharmonic partials at 2.0x, 3.01x, 4.1x, 5.3x.
.. code-block:: python
pan = score.part("pan", instrument="steel_drum")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_steel_drum.wav" type="audio/wav"></audio>
Accordion Synth
~~~~~~~~~~~~~~~
Musette-tuned doubled reeds — two slightly detuned reed sets
create natural beating. Bellows pressure swell modulates amplitude.
.. code-block:: python
acc = score.part("acc", instrument="accordion")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_accordion.wav" type="audio/wav"></audio>
Didgeridoo Synth
~~~~~~~~~~~~~~~~
Deep cylindrical drone with shifting formant overtones. The
overtone singing effect sweeps a resonant peak between 500-1500Hz.
Best with cave reverb.
.. code-block:: python
didg = score.part("didg", instrument="didgeridoo")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_didgeridoo.wav" type="audio/wav"></audio>
Bagpipe Synth
~~~~~~~~~~~~~
Bright chanter reed with constant bag pressure. All harmonics
peaked around 3-7 (the piercing brightness). No dynamics — always ff.
.. code-block:: python
pipes = score.part("pipes", instrument="bagpipe")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_bagpipe.wav" type="audio/wav"></audio>
Banjo Synth
~~~~~~~~~~~
Steel strings on a drum-head membrane body. The membrane gives
nasal, ringy resonance with faster decay than guitar.
.. code-block:: python
banjo = score.part("banjo", instrument="banjo")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_banjo.wav" type="audio/wav"></audio>
Mandolin Synth
~~~~~~~~~~~~~~
Paired steel strings in 4 courses — natural chorus from the
doubled unison strings. Bright, ringing, fast attack.
.. code-block:: python
mando = score.part("mando", instrument="mandolin")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_mandolin.wav" type="audio/wav"></audio>
Ukulele Synth
~~~~~~~~~~~~~
Nylon strings on a small body. Mid-heavy resonance (no deep bass),
softer attack than guitar, shorter sustain.
.. code-block:: python
uke = score.part("uke", instrument="ukulele")
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_ukulele.wav" type="audio/wav"></audio>
Granular Synth
~~~~~~~~~~~~~~
Grain cloud synthesis — chops a source waveform into tiny overlapping
grains (10-200ms), each windowed and optionally pitch/time scattered.
Creates textures impossible with other synthesis: frozen tones,
shimmering clouds, evolving pads, glitchy stutters.
.. code-block:: python
# Atmospheric granular pad
pad = score.part("pad", instrument="granular_pad")
# Granular with filter envelope sweep + resonance
texture = score.part("texture", synth="granular_synth", envelope="pad",
filter_amount=4000, filter_attack=0.5,
filter_decay=1.5, filter_sustain=0.3,
lowpass=600, lowpass_q=3.0,
reverb=0.5, reverb_type="taj_mahal")
Parameters (passed as synth kwargs):
- ``grain_size``: Duration per grain in seconds (default 0.04).
- ``density``: Grains per second (default 50). Higher = denser cloud.
- ``scatter``: Random position jitter 0-1 (default 0.5).
- ``pitch_var``: Per-grain pitch randomization in cents (default 12).
- ``source``: Base waveform — ``"saw"``, ``"sine"``, ``"triangle"``,
``"square"``, ``"noise"``.
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_granular.wav" type="audio/wav"></audio>
Crotales
~~~~~~~~
Small tuned bronze discs (antique cymbals) struck with brass mallets.
Bright, crystalline, bell-like tone with strong upper harmonics that
rings for a long time. Nearly harmonic partials give crotales their
penetrating brilliance — they cut through any orchestra.
.. code-block:: python
crotales = score.part("crotales", synth="crotales_synth", envelope="none",
reverb=0.3)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_crotales.wav" type="audio/wav"></audio>
Tingsha
~~~~~~~
Two small Tibetan cymbals joined by a cord, clashed together. Both discs
ring at slightly different frequencies, producing a bright ping with
pronounced beating — the wavering interference between the two is the
whole character of the sound.
.. code-block:: python
tingsha = score.part("tingsha", synth="tingsha_synth", envelope="none",
reverb=0.4)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_tingsha.wav" type="audio/wav"></audio>
Singing Bowl (Strike)
~~~~~~~~~~~~~~~~~~~~~
Tibetan/Himalayan singing bowl struck with a mallet. The impact excites
inharmonic partials that ring and slowly beat against each other as
near-degenerate mode pairs interfere. Higher modes fade quickly, leaving
the fundamental shimmering for seconds.
.. code-block:: python
bowl = score.part("bowl", synth="singing_bowl_strike_synth", envelope="none",
reverb=0.4)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_singing_bowl_strike.wav" type="audio/wav"></audio>
Singing Bowl (Ring)
~~~~~~~~~~~~~~~~~~~
Rim-rubbed singing bowl — the mallet traces the rim, slowly building the
fundamental into a sustained, pulsing tone. Upper harmonics shimmer in
and out as the bowl resonates.
.. code-block:: python
bowl = score.part("bowl", synth="singing_bowl_ring_synth", envelope="none",
reverb=0.4)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/synth_singing_bowl_ring.wav" type="audio/wav"></audio>
Rain Stick
~~~~~~~~~~
Cascading pebbles through a cactus tube with internal pins. Two variants:
steep angle (fast cascade) and shallow angle (slow trickle).
.. code-block:: python
p.hit(DrumSound.RAINSTICK, Duration.WHOLE * 3) # steep — fast cascade
p.hit(DrumSound.RAINSTICK_SLOW, Duration.WHOLE * 4) # shallow — gentle trickle
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/rainstick.wav" type="audio/wav"></audio>
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/rainstick_slow.wav" type="audio/wav"></audio>
Ocean Drum
~~~~~~~~~~
Steel beads rolling inside a frame drum — tilting produces a smooth surf wash.
.. code-block:: python
p.hit(DrumSound.OCEAN_DRUM, Duration.WHOLE * 3)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/ocean_drum.wav" type="audio/wav"></audio>
Cabasa
~~~~~~
Metal bead chain scraped against a textured cylinder — brighter and
more metallic than a shaker.
.. code-block:: python
p.hit(DrumSound.CABASA, Duration.EIGHTH)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/cabasa.wav" type="audio/wav"></audio>
Wind Chimes
~~~~~~~~~~~
Suspended metal tubes struck by hand or breeze. Each tube rings at
its own pitch with slight time offsets.
.. code-block:: python
p.hit(DrumSound.WIND_CHIMES, Duration.WHOLE * 3)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/wind_chimes.wav" type="audio/wav"></audio>
Finger Cymbal
~~~~~~~~~~~~~
Single small cymbal tap (zill) — bright metallic ping.
.. code-block:: python
p.hit(DrumSound.FINGER_CYMBAL, Duration.HALF)
.. raw:: html
<audio controls style="width:100%;margin:0.3em 0 0.5em"><source src="../_static/audio/finger_cymbal.wav" type="audio/wav"></audio>
Analog Oscillator Drift
~~~~~~~~~~~~~~~~~~~~~~~~
All waveform synths support the ``analog_drift`` parameter, which adds
subtle, slow random pitch variation to each oscillator -- modeling the
voltage instability of vintage analog circuits. This is what makes a
real Minimoog sound slightly different on every note, and why analog
synths feel "alive" compared to their digital counterparts.
.. code-block:: python
# Subtle vintage drift
pad = score.part("pad", synth="saw", analog_drift=0.1)
# More pronounced, wobbly analog character
lead = score.part("lead", synth="square", analog_drift=0.3)
Drift values:
- **0.05--0.1** = subtle warmth (studio-grade analog)
- **0.15--0.25** = noticeable drift (vintage gear warming up)
- **0.3+** = unstable, wobbly (broken tape machine)
Instrument Presets
------------------
Instead of choosing synth + envelope + effects manually, use an
instrument preset — 60+ predefined combinations that approximate real
instruments:
.. code-block:: python
piano = score.part("piano", instrument="piano")
violin = score.part("violin", instrument="violin")
guitar = score.part("guitar", instrument="acoustic_guitar")
organ = score.part("organ", instrument="organ")
bass = score.part("bass", instrument="upright_bass")
Available instruments:
**Keys**: piano, electric_piano, organ, harpsichord, celesta, music_box,
accordion
**Strings**: violin, viola, cello, contrabass, string_ensemble
**Woodwinds**: flute, clarinet, oboe, bassoon, saxophone, alto_sax,
tenor_sax, bari_sax
**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, koto, banjo, mandolin, mandola, ukulele
**World/Exotic**: pedal_steel, theremin, kalimba, steel_drum, didgeridoo,
bagpipe, singing_bowl, singing_bowl_ring, tingsha
**Synth**: synth_lead, synth_pad, synth_bass, acid_bass, 808_bass,
granular_pad, granular_texture, vocal, choir
**Percussion**: vibraphone, marimba, xylophone, glockenspiel, tubular_bells,
timpani, crotales
Explicit kwargs override preset defaults:
.. code-block:: python
# Piano with extra reverb
piano = score.part("piano", instrument="piano", reverb=0.5)
# Violin panned left
violin = score.part("v", instrument="violin", pan=-0.4)
Choosing Synth and Envelope Combos
----------------------------------

Some files were not shown because too many files have changed in this diff Show More