Compare commits

..

112 Commits

Author SHA1 Message Date
kennethreitz 665a6f5de5 Remove lowpass/vel_to_filter from sax presets, let wave shape its own tone — v0.40.6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:44:01 -04:00
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
113 changed files with 7694 additions and 392 deletions
+130
View File
@@ -2,6 +2,136 @@
All notable changes to PyTheory are documented here.
## 0.40.6
- **Saxophone presets cleaned up** — removed lowpass filters and vel_to_filter
from all sax instrument presets (saxophone, alto_sax, tenor_sax, bari_sax).
The saxophone wave function already shapes its own spectrum; the extra
filters were dulling the tone.
## 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
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
+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
~~~~~~~~~~~~~~~~~~~~~~~~~~
+103 -2
View File
@@ -10,7 +10,7 @@ the genre -- they tell the listener's body how to move before a single
melodic note is played.
PyTheory includes a complete drum system -- 51 synthesized percussion
sounds, 85+ pattern presets across dozens of genres, and 30 fill presets.
sounds, 95+ pattern presets across dozens of genres, and 30 fill presets.
Every sound is generated from waveforms; no samples needed.
Drum Sounds
@@ -121,10 +121,18 @@ MRIDANGAM_THA (101)
**Djembe:** DJEMBE_BASS (102), DJEMBE_TONE (103), DJEMBE_SLAP (104)
**Cajón:** CAJON_SLAP (109), CAJON_TAP (110)
**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
--------------
@@ -241,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
-----
@@ -331,6 +346,10 @@ 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
----------------
@@ -374,6 +393,12 @@ bayan (deep bass bends showcase), tabla call (dayan/bayan call-and-response).
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
~~~~
@@ -391,6 +416,10 @@ energetic, and physically impossible to sit still to.
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
~~~~~~
@@ -408,6 +437,10 @@ music) and dholak folk (a general folk groove).
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
~~~~~~~~~
@@ -426,6 +459,10 @@ and mridangam korvai (a rhythmic cadence pattern).
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
~~~~~~
@@ -450,6 +487,10 @@ West African-style break).
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
~~~~~~~~~
@@ -474,6 +515,10 @@ roll → kick roll → alternating → crash ending).
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
~~~~~
@@ -496,6 +541,62 @@ bass-slap groove).
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
-----------
+4
View File
@@ -66,6 +66,10 @@ 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:
+19 -33
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
------------------
+279 -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
--------
+385 -6
View File
@@ -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
--------------
@@ -375,6 +417,10 @@ at musical levels. Warm, round, unmistakably organ.
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
~~~~~~~~~~~~~~~
@@ -386,6 +432,10 @@ more "wooden" than a raw saw wave.
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
--------------------------
@@ -407,6 +457,94 @@ soundboard.
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
~~~~~~~~~~~~~~~~~
@@ -416,6 +554,10 @@ Plucked string model with finger-damped harmonics and low-end warmth.
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
~~~~~~~~~~~~
@@ -426,6 +568,10 @@ overblowing behavior at higher velocities.
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
~~~~~~~~~~~~~
@@ -436,6 +582,10 @@ velocity, plus a characteristic brassy edge from shaped harmonics.
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
~~~~~~~~~~~~~~
@@ -446,6 +596,10 @@ characteristic hollow, woody tone.
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
~~~~~~~~~~~
@@ -456,6 +610,10 @@ timbre.
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
~~~~~~~~~~~~~
@@ -466,6 +624,10 @@ that emphasizes the fundamental.
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
~~~~~~~~~~~~~~~~~
@@ -476,6 +638,10 @@ Plucked-string model with a bright, immediate attack and rapid decay
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
~~~~~~~~~~~
@@ -486,6 +652,10 @@ producing a rich, warm, sustained tone.
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
~~~~~~~~~~
@@ -496,6 +666,10 @@ modeling nylon strings on a resonant frame.
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
~~~~~~~~~~~~~~~~~~
@@ -505,6 +679,10 @@ Pizzicato double bass with woody body resonance and a thumpy low end.
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
~~~~~~~~~~~~~~~~~~~~~
@@ -515,6 +693,10 @@ string decay.
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
~~~~~~~~~~~~~~~~~~~~~
@@ -525,6 +707,10 @@ than the acoustic, ready for effects processing.
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
~~~~~~~~~~~~
@@ -535,6 +721,10 @@ bridge, producing a shimmering, metallic sustain.
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
~~~~~~~~~~~~~
@@ -547,6 +737,10 @@ Use ``Part.roll()`` for crescendo timpani rolls.
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
~~~~~~~~~~~~~~~
@@ -558,6 +752,10 @@ mids, reed buzz, and brass body warmth. Four presets: ``saxophone``,
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
~~~~~~~~~~~~~~~~~
@@ -568,6 +766,10 @@ and long sustain. Pairs naturally with spring reverb.
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
~~~~~~~~~~~~~~
@@ -578,6 +780,10 @@ Best used with legato and glide for continuous pitch.
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
~~~~~~~~~~~~~
@@ -588,6 +794,10 @@ inharmonic overtones (modes at 1x, 2.92x, 5.4x).
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
~~~~~~~~~~~~~~~~
@@ -598,6 +808,10 @@ Inharmonic partials at 2.0x, 3.01x, 4.1x, 5.3x.
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
~~~~~~~~~~~~~~~
@@ -608,6 +822,10 @@ create natural beating. Bellows pressure swell modulates amplitude.
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
~~~~~~~~~~~~~~~~
@@ -619,6 +837,10 @@ Best with cave reverb.
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
~~~~~~~~~~~~~
@@ -629,6 +851,10 @@ peaked around 3-7 (the piercing brightness). No dynamics — always ff.
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
~~~~~~~~~~~
@@ -639,6 +865,10 @@ nasal, ringy resonance with faster decay than guitar.
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
~~~~~~~~~~~~~~
@@ -649,6 +879,10 @@ doubled unison strings. Bright, ringing, fast attack.
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
~~~~~~~~~~~~~
@@ -659,6 +893,10 @@ softer attack than guitar, shorter sustain.
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
~~~~~~~~~~~~~~
@@ -688,6 +926,147 @@ Parameters (passed as synth kwargs):
- ``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
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -743,13 +1122,13 @@ 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
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
timpani, crotales
Explicit kwargs override preset defaults:
+20 -10
View File
@@ -46,23 +46,33 @@ it through your speakers, export MIDI, finish in your DAW:
.. 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)
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)
for chord in Key("A", "minor").progression("i", "iv", "V", "i"):
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=4, 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 ["G2", "G2", "D2", "D2", "E2", "E2", "C2", "C2"] * 2:
bass.add(n, Duration.HALF)
play_score(score)
score.save_midi("sketch.mid")
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="_static/audio/quickstart.wav" type="audio/wav"></audio>
Or hear a randomly generated track from the command line — different
every time::
+688 -10
View File
@@ -543,7 +543,7 @@ def dub_kingston():
volume=0.6, pan=0.0, lowpass=400, lowpass_q=1.5,
humanize=0.2)
siren = score.part("siren", synth="pwm_slow", envelope="pad",
volume=0.15, pan=0.5,
volume=0.15, pan=0.5, ensemble=4,
reverb=0.7, reverb_decay=3.0, reverb_type="cathedral",
lowpass=1200, detune=10)
@@ -1124,14 +1124,14 @@ def cinematic_showcase():
bells.rest(Duration.WHOLE)
# String ensemble — lush wide pad
strings = score.part("strings", instrument="string_ensemble",
strings = score.part("strings", instrument="string_ensemble", ensemble=8,
reverb=0.4, reverb_type="hall")
strings.rest(Duration.WHOLE)
for sym in ["Am", "F", "C", "G", "Dm", "Am", "E"]:
strings.add(Chord.from_symbol(sym), Duration.WHOLE)
# Cello — deep foundation
cello = score.part("cello", instrument="cello",
cello = score.part("cello", instrument="cello", ensemble=3,
reverb=0.3, reverb_type="hall")
cello.rest(Duration.WHOLE)
for n in ["A2", "F2", "C3", "G2", "D3", "A2", "E2"]:
@@ -1629,7 +1629,7 @@ def epic_bhairav():
timp.add(Tone("Pa", octave=2, system=shruti), Duration.HALF, velocity=115)
# Choir — bar 3
choir = score.part("choir", synth="vocal_synth", envelope="pad",
choir = score.part("choir", synth="vocal_synth", envelope="pad", ensemble=6,
detune=8, spread=0.4, reverb=0.4, reverb_type=REV, volume=0.2)
for _ in range(2):
choir.rest(Duration.WHOLE)
@@ -1651,7 +1651,7 @@ def epic_bhairav():
bansuri.add(tone, dur, velocity=vel)
# Cello — bar 3
cello = score.part("cello", instrument="cello", volume=0.22, reverb=0.4, reverb_type=REV)
cello = score.part("cello", instrument="cello", volume=0.22, reverb=0.4, reverb_type=REV, ensemble=3)
for _ in range(2):
cello.rest(Duration.WHOLE)
for name, dur, vel in [
@@ -1677,7 +1677,7 @@ def epic_bhairav():
# Strings — bar 13
strings = score.part("strings", instrument="string_ensemble", volume=0.18,
reverb=0.4, reverb_type=REV)
reverb=0.4, reverb_type=REV, ensemble=10)
for _ in range(12):
strings.rest(Duration.WHOLE)
for name, dur, vel in [("Sa", 4.0, 58), ("Ma", 4.0, 62), ("Pa", 4.0, 68), ("Sa", 4.0, 72)]:
@@ -1887,7 +1887,7 @@ def ascent():
# 3: SURFACING (5-8)
cello = score.part("cello", instrument="cello", volume=0.22,
reverb=0.4, reverb_type=REV)
reverb=0.4, reverb_type=REV, ensemble=3)
cello.rest(16.0)
for note, dur, vel in [("E2",4.0,52),("G2",4.0,55),("B2",4.0,58),("E3",4.0,62)]:
cello.add(note, dur, velocity=vel)
@@ -1943,7 +1943,7 @@ def ascent():
theremin.add(note, dur, velocity=vel)
strings = score.part("strings", instrument="string_ensemble", volume=0.15,
reverb=0.45, reverb_type=REV)
reverb=0.45, reverb_type=REV, ensemble=8)
strings.rest(40.0)
for sym, vel in [("Em",52),("C",55),("Am",58),("B",55),("Em",60),("C",62)]:
strings.add(Chord.from_symbol(sym), 4.0, velocity=vel)
@@ -2218,7 +2218,7 @@ def pop_rock():
cabinet=1.0, cabinet_brightness=0.6,
reverb=0.2, reverb_type="plate", pan=0.2)
strings = score.part("strings", instrument="string_ensemble", volume=0.12,
reverb=0.35, reverb_type="hall")
reverb=0.35, reverb_type="hall", ensemble=6)
prog = ["G", "D", "Em", "C"]
@@ -2324,6 +2324,681 @@ def sitar_drone():
play_song(score, "Sitar Drone — Raga Bhairav (22-Shruti JI, hold() polyphony)")
def acid_tabla():
"""Acid Tabla — 303 filter automation meets Indian percussion."""
score = Score("4/4", bpm=132)
# ── House drums ──
score.drums("house", repeats=20, fill="house", fill_every=8)
score.set_drum_effects(volume=0.45)
# ── 303 acid bass ──
acid = score.part("acid", synth="saw", volume=0.75,
legato=True, glide=0.035,
distortion=0.35, distortion_drive=4.5,
saturation=0.15, humanize=0.05)
# Intro (4 bars): filter closed, high resonance
acid.set(lowpass=600, lowpass_q=12.0)
for _ in range(4):
for n in ["C3","C3","C2","C3","Eb3","C2","G2","C3"]:
acid.add(n, Duration.EIGHTH)
# Build (4 bars): filter opens
acid.ramp(over=Duration.WHOLE * 4, curve="ease_in", lowpass=4500)
for _ in range(4):
for n in ["C2","G2","C3","Eb3","C2","Bb2","G2","C3"]:
acid.add(n, Duration.EIGHTH)
# Peak (4 bars): wide open, wilder pattern
acid.set(lowpass=7000, lowpass_q=7.0)
for _ in range(2):
for n in ["C2","C3","Eb3","G3","C2","Bb2","G2","Eb3"]:
acid.add(n, Duration.EIGHTH)
for _ in range(2):
for n in ["C2","Eb3","C3","G3","Bb2","C3","G2","C2"]:
acid.add(n, Duration.EIGHTH)
# Tabla section (4 bars): filter pulls back
acid.set(lowpass=3000, lowpass_q=5.0)
for _ in range(4):
for n in ["C2","G2","C3","C2","Eb2","G2","Bb2","C2"]:
acid.add(n, Duration.EIGHTH)
# Outro (4 bars): filter closes
acid.ramp(over=Duration.WHOLE * 4, curve="ease_out", lowpass=400, lowpass_q=15.0)
for _ in range(4):
for n in ["C3","G2","C2","C3","C2","G2","Eb2","C2"]:
acid.add(n, Duration.EIGHTH)
# ── Tabla: enters bar 9, rides through to the end ──
tabla = score.part("tabla", synth="sine", volume=0.55, reverb=0.15)
# 8 bars rest
for _ in range(64):
tabla.rest(Duration.EIGHTH)
# Bars 9-12: keherwa groove
for _ in range(4):
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=100, articulation="accent")
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=55)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=50)
tabla.hit(DrumSound.TABLA_NA, Duration.EIGHTH, velocity=88)
tabla.hit(DrumSound.TABLA_TIN, Duration.EIGHTH, velocity=82)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=52)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=95, articulation="accent")
tabla.hit(DrumSound.TABLA_GE, Duration.EIGHTH, velocity=78)
# Bars 13-14: busier with 16ths
for _ in range(2):
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=105, articulation="marcato")
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=48)
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=52)
tabla.hit(DrumSound.TABLA_NA, Duration.EIGHTH, velocity=90)
tabla.hit(DrumSound.TABLA_TIN, Duration.EIGHTH, velocity=85)
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=48)
tabla.hit(DrumSound.TABLA_NA, Duration.SIXTEENTH, velocity=58)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=100, articulation="accent")
tabla.hit(DrumSound.TABLA_GE_BEND, Duration.EIGHTH, velocity=88)
# Bars 15-16: tihai crescendo ending
for vel in [85, 90, 95]:
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=vel, articulation="accent")
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=int(vel * 0.6))
tabla.hit(DrumSound.TABLA_NA, Duration.SIXTEENTH, velocity=int(vel * 0.75))
for vel in [100, 105, 110]:
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=vel, articulation="marcato")
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=int(vel * 0.55))
tabla.hit(DrumSound.TABLA_NA, Duration.SIXTEENTH, velocity=int(vel * 0.7))
tabla.hit(DrumSound.TABLA_DHA, Duration.QUARTER, velocity=127, articulation="fermata")
tabla.hit(DrumSound.TABLA_GE_BEND, Duration.QUARTER, velocity=110)
tabla.rest(Duration.HALF)
# Bars 17-20: tabla continues through outro, lighter
for _ in range(4):
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=85, articulation="accent")
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=45)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=42)
tabla.hit(DrumSound.TABLA_NA, Duration.EIGHTH, velocity=75)
tabla.hit(DrumSound.TABLA_TIN, Duration.EIGHTH, velocity=70)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=42)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=80)
tabla.hit(DrumSound.TABLA_GE, Duration.EIGHTH, velocity=65)
# ── Pad: enters at peak, fades during outro ──
pad = score.part("pad", synth="supersaw", envelope="pad", volume=0.0,
reverb=0.4, chorus=0.2, detune=10, lowpass=2500)
for _ in range(32):
pad.rest(Duration.QUARTER)
pad.ramp(over=Duration.WHOLE * 2, volume=0.18)
for sym in ["Cm", "Ab", "Eb", "Bb"] * 3:
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
pad.ramp(over=Duration.WHOLE * 2, curve="ease_out", volume=0.0)
for sym in ["Cm", "Cm"]:
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
play_song(score, "Acid Tabla — 303 filter automation + tabla (ramp, articulations, Part.hit)")
def snare_cadence():
"""Snare Cadence — full drumline with ensemble, flams, diddles, cheese."""
score = Score("4/4", bpm=120)
S = DrumSound.MARCH_SNARE
R = DrumSound.MARCH_RIMSHOT
C = DrumSound.MARCH_CLICK
Q1 = DrumSound.QUAD_1
Q2 = DrumSound.QUAD_2
Q3 = DrumSound.QUAD_3
Q4 = DrumSound.QUAD_4
QS = DrumSound.QUAD_SPOCK
B1 = DrumSound.BASS_1
B2 = DrumSound.BASS_2
B3 = DrumSound.BASS_3
B4 = DrumSound.BASS_4
B5 = DrumSound.BASS_5
# Snare line — 8 players
p = score.part("snares", synth="sine", volume=0.9, reverb=0.2, ensemble=8)
# Quad line — 4 players
q = score.part("quads", synth="sine", volume=0.5, reverb=0.2, ensemble=4)
# Bass line — 5 players
b = score.part("basses", synth="sine", volume=0.55, reverb=0.2, ensemble=5)
_trip = 1.0 / 3
# Helper: bass split run (down or up)
def bass_down(dur=Duration.SIXTEENTH):
b.hit(B1, dur, velocity=95)
b.hit(B2, dur, velocity=90)
b.hit(B3, dur, velocity=85)
b.hit(B4, dur, velocity=90)
def bass_up(dur=Duration.SIXTEENTH):
b.hit(B4, dur, velocity=90)
b.hit(B3, dur, velocity=85)
b.hit(B2, dur, velocity=90)
b.hit(B1, dur, velocity=95)
def bass_hit(dur=Duration.QUARTER):
b.hit(B3, dur, velocity=100)
def quad_sweep_down():
q.hit(Q1, Duration.SIXTEENTH, velocity=95)
q.hit(Q2, Duration.SIXTEENTH, velocity=88)
q.hit(Q3, Duration.SIXTEENTH, velocity=82)
q.hit(Q4, Duration.SIXTEENTH, velocity=78)
def quad_sweep_up():
q.hit(Q4, Duration.SIXTEENTH, velocity=78)
q.hit(Q3, Duration.SIXTEENTH, velocity=82)
q.hit(Q2, Duration.SIXTEENTH, velocity=88)
q.hit(Q1, Duration.SIXTEENTH, velocity=95)
# ── Click count-off ──
for _ in range(4):
p.hit(C, Duration.QUARTER, velocity=95)
q.rest(Duration.QUARTER)
b.rest(Duration.QUARTER)
# ── Section 1: 16th groove — snares only (4 bars) ──
for _ in range(16):
q.rest(Duration.QUARTER)
b.rest(Duration.QUARTER)
for _ in range(2):
p.hit(R, Duration.SIXTEENTH, velocity=118)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.hit(S, Duration.SIXTEENTH, velocity=35)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(R, Duration.SIXTEENTH, velocity=115)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=28)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.hit(R, Duration.SIXTEENTH, velocity=118)
p.hit(S, Duration.SIXTEENTH, velocity=35)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.hit(R, Duration.SIXTEENTH, velocity=120)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=28)
p.hit(S, Duration.SIXTEENTH, velocity=32)
# Triplets mixed in
for _ in range(2):
p.hit(R, _trip, velocity=118)
p.hit(S, _trip, velocity=32)
p.hit(S, _trip, velocity=30)
p.hit(R, _trip, velocity=115)
p.hit(S, _trip, velocity=28)
p.hit(S, _trip, velocity=32)
p.hit(R, Duration.SIXTEENTH, velocity=118)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.hit(S, Duration.SIXTEENTH, velocity=28)
p.hit(R, _trip, velocity=118)
p.hit(S, _trip, velocity=30)
p.hit(S, _trip, velocity=35)
# ── Section 2: Quads + bass enter (4 bars) ──
for _ in range(2):
p.flam(S, Duration.QUARTER, velocity=118)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.hit(R, _trip, velocity=118)
p.hit(S, _trip, velocity=28)
p.hit(S, _trip, velocity=30)
p.flam(S, Duration.QUARTER, velocity=118)
quad_sweep_down()
q.hit(QS, Duration.QUARTER, velocity=100)
quad_sweep_up()
q.hit(QS, Duration.QUARTER, velocity=100)
bass_hit()
b.hit(B5, Duration.QUARTER, velocity=95)
bass_hit()
b.hit(B1, Duration.QUARTER, velocity=95)
for _ in range(2):
p.hit(S, _trip, velocity=35)
p.flam(S, _trip * 2, velocity=118)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.flam(S, Duration.QUARTER, velocity=118)
p.hit(S, _trip, velocity=28)
p.hit(R, _trip, velocity=122)
p.hit(S, _trip, velocity=35)
quad_sweep_down()
quad_sweep_up()
q.hit(Q1, Duration.EIGHTH, velocity=95)
q.hit(Q4, Duration.EIGHTH, velocity=85)
q.hit(QS, Duration.QUARTER, velocity=100)
bass_down()
bass_up()
b.hit(B3, Duration.HALF, velocity=100)
# ── Section 3: Flams + diddles + full line (4 bars) ──
for _ in range(2):
p.flam(S, Duration.QUARTER, velocity=120)
p.diddle(S, Duration.EIGHTH, velocity=45)
p.hit(S, _trip, velocity=30)
p.hit(S, _trip, velocity=32)
p.hit(S, _trip, velocity=28)
p.hit(R, Duration.EIGHTH, velocity=122)
p.diddle(S, Duration.EIGHTH, velocity=42)
q.hit(Q1, Duration.QUARTER, velocity=95)
q.hit(Q3, Duration.EIGHTH, velocity=55)
q.hit(Q2, _trip, velocity=55)
q.hit(Q3, _trip, velocity=55)
q.hit(Q4, _trip, velocity=55)
q.hit(QS, Duration.EIGHTH, velocity=100)
q.hit(Q1, Duration.EIGHTH, velocity=55)
bass_hit()
b.hit(B1, Duration.EIGHTH, velocity=90)
b.hit(B5, Duration.EIGHTH, velocity=95)
bass_hit()
b.hit(B5, Duration.EIGHTH, velocity=90)
b.hit(B1, Duration.EIGHTH, velocity=95)
for _ in range(2):
p.diddle(S, Duration.EIGHTH, velocity=45)
p.hit(R, _trip, velocity=120)
p.hit(S, _trip, velocity=30)
p.hit(S, _trip, velocity=32)
p.diddle(S, Duration.EIGHTH, velocity=48)
p.hit(R, _trip, velocity=118)
p.hit(S, _trip, velocity=28)
p.hit(S, _trip, velocity=30)
p.flam(S, Duration.EIGHTH, velocity=122)
p.hit(S, Duration.EIGHTH, velocity=35)
quad_sweep_down()
quad_sweep_up()
quad_sweep_down()
quad_sweep_up()
bass_down()
bass_up()
bass_down()
bass_up()
# ── Section 4: Cheese + 32nds (4 bars) ──
for _ in range(2):
p.cheese(S, Duration.QUARTER, velocity=120)
p.hit(S, 0.0625, velocity=30)
p.hit(S, 0.0625, velocity=32)
p.hit(S, 0.0625, velocity=35)
p.hit(S, 0.0625, velocity=30)
p.cheese(S, Duration.QUARTER, velocity=118)
p.diddle(S, Duration.EIGHTH, velocity=48)
p.hit(R, Duration.EIGHTH, velocity=125)
q.hit(QS, Duration.QUARTER, velocity=105)
q.hit(Q1, Duration.SIXTEENTH, velocity=55)
q.hit(Q2, Duration.SIXTEENTH, velocity=55)
q.hit(Q3, Duration.SIXTEENTH, velocity=55)
q.hit(Q4, Duration.SIXTEENTH, velocity=55)
q.hit(QS, Duration.QUARTER, velocity=105)
q.hit(Q4, Duration.EIGHTH, velocity=55)
q.hit(Q1, Duration.EIGHTH, velocity=90)
bass_hit()
b.hit(B1, Duration.EIGHTH, velocity=90)
b.hit(B3, Duration.EIGHTH, velocity=85)
b.hit(B5, Duration.EIGHTH, velocity=95)
b.hit(B3, Duration.EIGHTH, velocity=85)
bass_hit()
b.rest(Duration.QUARTER)
# All cheese
p.cheese(S, Duration.QUARTER, velocity=122)
p.cheese(S, Duration.QUARTER, velocity=120)
p.cheese(S, Duration.QUARTER, velocity=125)
p.cheese(S, Duration.QUARTER, velocity=122)
q.hit(QS, Duration.QUARTER, velocity=105)
q.hit(QS, Duration.QUARTER, velocity=105)
q.hit(QS, Duration.QUARTER, velocity=108)
q.hit(QS, Duration.QUARTER, velocity=105)
b.hit(B5, Duration.QUARTER, velocity=100)
b.hit(B3, Duration.QUARTER, velocity=100)
b.hit(B1, Duration.QUARTER, velocity=100)
b.hit(B3, Duration.QUARTER, velocity=100)
p.flam(S, Duration.EIGHTH, velocity=120)
p.diddle(S, Duration.EIGHTH, velocity=50)
p.flam(S, Duration.EIGHTH, velocity=122)
p.diddle(S, Duration.EIGHTH, velocity=52)
p.flam(S, Duration.EIGHTH, velocity=125)
p.diddle(S, Duration.EIGHTH, velocity=55)
p.hit(R, Duration.EIGHTH, velocity=127)
p.hit(S, Duration.EIGHTH, velocity=38)
quad_sweep_down()
quad_sweep_up()
quad_sweep_down()
quad_sweep_up()
bass_down()
bass_up()
bass_down()
bass_up()
# ── Section 5: 16ths + triplet 16ths + 32nds (4 bars) ──
_trip16 = 1.0 / 6
for _ in range(2):
for beat in range(4):
p.hit(R, _trip, velocity=118)
p.hit(S, _trip, velocity=35)
p.hit(S, _trip, velocity=32)
quad_sweep_down()
quad_sweep_up()
quad_sweep_down()
quad_sweep_up()
bass_hit()
b.hit(B5, Duration.QUARTER, velocity=95)
bass_hit()
b.hit(B1, Duration.QUARTER, velocity=95)
# 32nd run crescendo
for i in range(32):
p.hit(S, 0.0625, velocity=min(22 + i * 3, 92))
p.hit(R, Duration.EIGHTH, velocity=125)
p.hit(R, Duration.EIGHTH, velocity=127)
for _ in range(4):
q.hit(Q1, 0.0625, velocity=55)
q.hit(Q2, 0.0625, velocity=55)
q.hit(Q3, 0.0625, velocity=55)
q.hit(Q4, 0.0625, velocity=55)
q.hit(QS, Duration.QUARTER, velocity=108)
bass_down()
bass_up()
bass_down()
b.hit(B5, Duration.QUARTER, velocity=100)
b.hit(B1, Duration.QUARTER, velocity=100)
# Triplet 16ths — all sections
for _ in range(2):
for beat in range(4):
p.hit(R, _trip16, velocity=115)
p.hit(S, _trip16, velocity=30)
p.hit(S, _trip16, velocity=32)
p.hit(R, _trip16, velocity=112)
p.hit(S, _trip16, velocity=28)
p.hit(S, _trip16, velocity=30)
for beat in range(4):
q.hit(Q1, _trip16, velocity=90)
q.hit(Q2, _trip16, velocity=55)
q.hit(Q3, _trip16, velocity=55)
q.hit(Q4, _trip16, velocity=55)
q.hit(Q3, _trip16, velocity=55)
q.hit(Q2, _trip16, velocity=55)
bass_down()
bass_up()
bass_down()
bass_up()
# ── Section 6: Buzz roll climax (2 bars) ──
for i in range(64):
p.hit(S, 0.0625, velocity=min(20 + i * 1.5, 100))
p.hit(R, Duration.EIGHTH, velocity=127)
p.hit(R, Duration.EIGHTH, velocity=127)
for i in range(32):
q.hit([Q1, Q2, Q3, Q4][i % 4], 0.0625, velocity=min(40 + i * 2, 95))
q.hit(QS, Duration.QUARTER, velocity=110)
for i in range(16):
b.hit([B1, B2, B3, B4, B5, B4, B3, B2,
B1, B2, B3, B4, B5, B4, B3, B2][i], Duration.SIXTEENTH, velocity=90)
b.hit(B3, Duration.HALF, velocity=100)
b.hit(B3, Duration.HALF, velocity=100)
# ── Ending: big unison hits ──
p.hit(R, Duration.EIGHTH, velocity=127)
q.hit(QS, Duration.EIGHTH, velocity=110)
b.hit(B3, Duration.EIGHTH, velocity=100)
p.rest(Duration.QUARTER + Duration.EIGHTH)
q.rest(Duration.QUARTER + Duration.EIGHTH)
b.rest(Duration.QUARTER + Duration.EIGHTH)
p.hit(R, Duration.EIGHTH, velocity=127)
q.hit(QS, Duration.EIGHTH, velocity=110)
b.hit(B3, Duration.EIGHTH, velocity=100)
p.rest(Duration.QUARTER + Duration.EIGHTH)
q.rest(Duration.QUARTER + Duration.EIGHTH)
b.rest(Duration.QUARTER + Duration.EIGHTH)
# Flam into final CRACK — all sections
p.flam(S, Duration.EIGHTH, velocity=127)
q.hit(QS, Duration.EIGHTH, velocity=110)
b.hit(B3, Duration.EIGHTH, velocity=100)
p.rest(Duration.QUARTER + Duration.EIGHTH)
q.rest(Duration.QUARTER + Duration.EIGHTH)
b.rest(Duration.QUARTER + Duration.EIGHTH)
p.hit(R, Duration.QUARTER, velocity=127)
q.hit(QS, Duration.QUARTER, velocity=110)
b.hit(B3, Duration.QUARTER, velocity=100)
p.rest(Duration.HALF)
q.rest(Duration.HALF)
b.rest(Duration.HALF)
play_song(score, "Snare Cadence — full drumline (8 snares, 4 quads, 5 basses)")
def ensemble_showcase():
"""Ensemble Showcase — acid bass, tabla solo, strings, snare line."""
score = Score("4/4", bpm=128)
# ── Drums: house kit ──
score.drums("house", repeats=24, fill="house", fill_every=8)
score.set_drum_effects(volume=0.4, reverb=0.1)
# ── 303 Acid Bass: detuned, spread, LFO filter, ensemble=3 ──
acid = score.part("acid", synth="saw", volume=0.55, ensemble=3,
lowpass=400, lowpass_q=10.0, distortion=0.35,
distortion_drive=4.0, legato=True, glide=0.03,
detune=12, spread=0.4, sub_osc=0.15,
sidechain=0.5, sidechain_release=0.08)
acid.lfo("lowpass", rate=0.5, min=400, max=5000, bars=16, shape="sine")
for _ in range(8):
for n in ["C2", "C3", "C2", "Eb2", "C2", "G2", "Bb2", "C2"]:
acid.add(n, Duration.EIGHTH, velocity=90)
acid.ramp(over=Duration.WHOLE * 8, curve="ease_in", lowpass=6000)
for _ in range(8):
for n in ["C2", "Eb3", "C3", "G2", "Bb2", "C3", "G2", "C2"]:
acid.add(n, Duration.EIGHTH, velocity=95)
acid.ramp(over=Duration.WHOLE * 8, curve="ease_out", lowpass=500)
for _ in range(8):
for n in ["C2", "C3", "C2", "Eb2", "C2", "G2", "Bb2", "C2"]:
acid.add(n, Duration.EIGHTH, velocity=88)
# ── Strings: 16-player ensemble pad ──
strings = score.part("strings", instrument="string_ensemble", volume=0.0,
reverb=0.4, ensemble=16, detune=8, spread=0.5)
for _ in range(32):
strings.rest(Duration.QUARTER)
strings.ramp(over=Duration.WHOLE * 4, curve="ease_in", volume=0.18)
for ch in ["Cm", "Ab", "Eb", "Bb"] * 4:
strings.add(Chord.from_symbol(ch), Duration.WHOLE, velocity=55)
strings.ramp(over=Duration.WHOLE * 4, curve="ease_out", volume=0.0)
for ch in ["Cm", "Ab", "Eb", "Bb"]:
strings.add(Chord.from_symbol(ch), Duration.WHOLE, velocity=45)
for _ in range(16):
strings.rest(Duration.QUARTER)
# ── Tabla: ensemble=3, enters bar 9 ──
tabla = score.part("tabla", synth="sine", volume=0.0, reverb=0.15, ensemble=3)
for _ in range(32):
tabla.rest(Duration.QUARTER)
tabla.ramp(over=Duration.WHOLE * 2, volume=0.45)
# Keherwa groove — 8 bars
for _ in range(8):
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=95, articulation="accent")
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=50)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=48)
tabla.hit(DrumSound.TABLA_NA, Duration.EIGHTH, velocity=82)
tabla.hit(DrumSound.TABLA_TIN, Duration.EIGHTH, velocity=78)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=48)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=88, articulation="accent")
tabla.hit(DrumSound.TABLA_GE, Duration.EIGHTH, velocity=72)
# Tabla solo — getting busier
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=100, articulation="marcato")
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=45)
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=48)
tabla.hit(DrumSound.TABLA_NA, Duration.EIGHTH, velocity=85)
tabla.hit(DrumSound.TABLA_TIN, Duration.EIGHTH, velocity=80)
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=45)
tabla.hit(DrumSound.TABLA_NA, Duration.SIXTEENTH, velocity=55)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=95, articulation="accent")
tabla.hit(DrumSound.TABLA_GE_BEND, Duration.EIGHTH, velocity=82)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=105, articulation="marcato")
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=48)
tabla.hit(DrumSound.TABLA_NA, Duration.SIXTEENTH, velocity=55)
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=45)
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=48)
tabla.hit(DrumSound.TABLA_NA, Duration.EIGHTH, velocity=88)
tabla.hit(DrumSound.TABLA_DHA, Duration.SIXTEENTH, velocity=100)
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=50)
tabla.hit(DrumSound.TABLA_GE_BEND, Duration.EIGHTH, velocity=85)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=108, articulation="accent")
# Tihai crescendo
for vel in [85, 90, 95, 100, 105, 110]:
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=vel, articulation="accent")
tabla.hit(DrumSound.TABLA_TIT, Duration.SIXTEENTH, velocity=int(vel * 0.55))
tabla.hit(DrumSound.TABLA_NA, Duration.SIXTEENTH, velocity=int(vel * 0.7))
tabla.hit(DrumSound.TABLA_DHA, Duration.QUARTER, velocity=125, articulation="fermata")
tabla.hit(DrumSound.TABLA_GE_BEND, Duration.QUARTER, velocity=110)
# Groove out
for _ in range(4):
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=88, articulation="accent")
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=45)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=42)
tabla.hit(DrumSound.TABLA_NA, Duration.EIGHTH, velocity=75)
tabla.hit(DrumSound.TABLA_TIN, Duration.EIGHTH, velocity=70)
tabla.hit(DrumSound.TABLA_TIT, Duration.EIGHTH, velocity=42)
tabla.hit(DrumSound.TABLA_DHA, Duration.EIGHTH, velocity=80)
tabla.hit(DrumSound.TABLA_GE, Duration.EIGHTH, velocity=65)
# ── Snare line: 8-player ensemble, enters bar 17 ──
S = DrumSound.MARCH_SNARE
R = DrumSound.MARCH_RIMSHOT
snares = score.part("snares", synth="sine", volume=0.0, reverb=0.15, ensemble=8)
for _ in range(64):
snares.rest(Duration.QUARTER)
snares.ramp(over=Duration.WHOLE * 2, volume=0.7)
for _ in range(4):
snares.hit(R, Duration.SIXTEENTH, velocity=118)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(S, Duration.SIXTEENTH, velocity=32)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(R, Duration.SIXTEENTH, velocity=115)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(S, Duration.SIXTEENTH, velocity=32)
snares.hit(R, Duration.SIXTEENTH, velocity=118)
snares.hit(S, Duration.SIXTEENTH, velocity=35)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(R, Duration.SIXTEENTH, velocity=120)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(S, Duration.SIXTEENTH, velocity=32)
# Buzz roll finale
for i in range(64):
snares.hit(S, 0.0625, velocity=min(20 + i * 1.5, 100))
snares.hit(R, Duration.EIGHTH, velocity=127)
snares.hit(R, Duration.EIGHTH, velocity=127)
snares.ramp(over=Duration.WHOLE * 2, curve="ease_out", volume=0.0)
for _ in range(2):
snares.hit(R, Duration.SIXTEENTH, velocity=110)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(S, Duration.SIXTEENTH, velocity=32)
snares.hit(R, Duration.SIXTEENTH, velocity=108)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(S, Duration.SIXTEENTH, velocity=32)
snares.hit(R, Duration.SIXTEENTH, velocity=105)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(S, Duration.SIXTEENTH, velocity=32)
snares.hit(R, Duration.SIXTEENTH, velocity=100)
snares.hit(S, Duration.SIXTEENTH, velocity=30)
snares.hit(S, Duration.SIXTEENTH, velocity=28)
snares.hit(S, Duration.SIXTEENTH, velocity=32)
# ── Lead synth: 6-player ensemble, enters bar 5 ──
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.0,
lowpass=3500, detune=6, chorus=0.1, reverb=0.2,
delay=0.15, delay_time=0.33, delay_feedback=0.25,
ensemble=6)
for _ in range(16):
lead.rest(Duration.QUARTER)
lead.ramp(over=Duration.WHOLE * 2, volume=0.3)
for _ in range(2):
lead.add("Eb5", Duration.QUARTER, velocity=88)
lead.add("G5", Duration.QUARTER, velocity=92)
lead.add("Bb5", Duration.HALF, velocity=95, articulation="accent")
lead.add("Ab5", Duration.QUARTER, velocity=88)
lead.add("G5", Duration.QUARTER, velocity=85)
lead.add("Eb5", Duration.QUARTER, velocity=82)
lead.add("D5", Duration.QUARTER, velocity=80)
lead.swell(["Eb5", "G5", "Bb5", "C6", "Bb5", "G5", "Eb5", "D5"],
Duration.QUARTER, low_vel=75, peak_vel=105)
lead.decrescendo(["Eb5", "D5", "C5", "Bb4"], Duration.HALF,
start_vel=90, end_vel=60)
for _ in range(16):
lead.rest(Duration.QUARTER)
lead.ramp(over=Duration.WHOLE * 2, volume=0.35)
lead.crescendo(["C5", "Eb5", "G5", "Bb5", "C6", "Eb6", "C6", "Bb5"],
Duration.QUARTER, start_vel=80, end_vel=110)
lead.add("G5", Duration.HALF, velocity=105, bend=1, bend_type="smooth")
lead.add("Eb5", Duration.HALF, velocity=95)
lead.decrescendo(["C5", "Bb4", "G4", "Eb4"], Duration.WHOLE,
start_vel=85, end_vel=40)
play_song(score, "Ensemble Showcase — acid bass, tabla solo, 16-player strings, 8-player snare line")
SONGS = {
"1": ("Bossa Nova in A minor", bossa_nova_girl),
"2": ("Bebop in Bb major", bebop_in_bb),
@@ -2355,6 +3030,9 @@ SONGS = {
"28": ("Descent (Generative — different every time)", descent),
"29": ("Pop Rock (I-V-vi-IV)", pop_rock),
"30": ("Sitar Drone (Bhairav, hold() polyphony)", sitar_drone),
"31": ("Acid Tabla (303 + tabla, ramp, articulations)", acid_tabla),
"32": ("Snare Cadence (marching snare, flams, diddles)", snare_cadence),
"33": ("Ensemble Showcase (acid+tabla+strings+snare line)", ensemble_showcase),
}
if __name__ == "__main__":
@@ -2368,7 +3046,7 @@ if __name__ == "__main__":
print(f" {key:>2}. {name}")
print()
choice = input(" Pick a song (1-30, or 'all'): ").strip()
choice = input(" Pick a song (1-33, or 'all'): ").strip()
print()
if choice == "all":
+69
View File
@@ -0,0 +1,69 @@
"""Sprunki Simon Phase 1 — melody reference.
Notes transcribed from MIDI. Use as a base for arrangements.
Usage:
python examples/sprunki.py
"""
import sounddevice as sd
from pytheory import Score, Duration
from pytheory.play import render_score, SAMPLE_RATE
def sprunki_simon():
score = Score("4/4", bpm=200)
lead = score.part("lead", synth="square", envelope="pluck", volume=0.5,
lowpass=4500, detune=3, reverb=0.1)
# Phrase A
lead.add("E4", 1.0)
lead.add("G4", 1.0)
lead.rest(1.5)
lead.add("A4", 0.5)
lead.add("B4", 1.0)
lead.add("A4", 1.0)
lead.add("G4", 1.0)
lead.add("D4", 1.0)
# Phrase B
lead.add("E4", 1.0)
lead.add("G4", 1.0)
lead.rest(1.5)
lead.add("A4", 0.5)
lead.add("D4", 2.0)
lead.add("B3", 1.0)
lead.add("A3", 0.5)
lead.add("D4", 0.5)
# Phrase C
lead.add("E4", 1.0)
lead.add("G4", 1.0)
lead.rest(1.5)
lead.add("A4", 0.5)
lead.add("B4", 1.0)
lead.add("A4", 1.0)
lead.add("G4", 1.0)
lead.add("B4", 1.0)
# Phrase D
lead.add("A4", 2.0)
lead.add("G4", 1.0)
lead.add("E4", 1.0)
lead.add("B3", 2.0)
lead.add("D4", 2.0)
return score
if __name__ == "__main__":
score = sprunki_simon()
print(" Sprunki Simon Phase 1")
try:
buf = render_score(score)
sd.play(buf, SAMPLE_RATE)
sd.wait()
except KeyboardInterrupt:
sd.stop()

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