Three new export methods on Score:
- to_lilypond() — complete LilyPond source files for PDF engraving
- to_musicxml() — MusicXML 4.0 for MuseScore/Sibelius/Finale
- to_tab() — ASCII guitar/bass tablature (also on Part)
All three handle multi-part scores, bass clef detection, tied notes
across barlines, chords, and drum tone filtering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Notes longer than one measure are split into tied pieces so abcjs
can render them correctly (e.g. 16-beat choir drone becomes four
tied whole notes).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parts with only drum tones or rests are excluded from ABC output.
Chords correctly recognized as pitched content.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New method converts scores to ABC notation with support for multi-voice,
chords, rests, accidentals, and all durations. Pass html=True for a
self-contained HTML page with abcjs sheet music rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supports multi-voice scores, chords, rests, accidentals, and all durations.
Pass html=True to get a self-contained page using abcjs for sheet music.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Five new synth waveforms: tape-replay Mellotron (strings/flute/choir
tapes with wow, flutter, saturation, 8s fadeout), hard sync oscillator,
ring modulation, wavefolding, and analog drift VCO with pitch
instability. 14 new instrument presets for Score.part(). Synth kwargs
now pass through play()/save()/_render(). 808 bass envelope fixed
from pluck to piano.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Note.beats now returns 0.0 for held notes (_hold=True), matching the
renderer which already skipped advancing the beat position. Previously
every hold() call added its full duration to the part's total, causing
duration reports to be 2-3x too long on tracks with drone notes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
_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>
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>
- 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>
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>