Changelog

All notable changes to PyTheory are documented here.

0.48.1

  • Docs URL updated to pytheory.org in package metadata and README (the docs site moved from pytheory.kennethreitz.org).

0.48.0

  • PyTheory Studio — pytheory studio. A local web app that ties the whole listening pipeline together: drag in a recording (.wav, .m4a voice memo, .mp3) and the transcription renders as sheet music on the page (abcjs); press play to hear it through PyTheory’s synths; download the MIDI; tuner at the bottom of the page. “Full mix” mode runs the four-part bass/melody/chords/drums split. Everything runs on localhost — only the notation renderer comes from a CDN.

  • 7th chords in chord recognition. detect_chords (and therefore from_wav(split=True)) now matches dominant 7th, major 7th, and minor 7th templates alongside triads, with a small prior so plain triads aren’t promoted by passing melody notes. A rendered Dm7-G7-Cmaj7 progression comes back exactly; Am-F-C-G still comes back as triads.

  • Fix key detection with 7th-chord symbols — the chord-root extraction mishandled symbols like “Am7”.

0.47.0

  • Full-arrangement transcription. Score.from_wav(split=True) now returns four parts plus the key: "melody", "bass", "chords" (chromagram folded to 12 pitch classes, matched against major/minor triad templates per chord window — a rendered Am-F-C-G mix comes back exactly), and "drums" (onset detection on the percussive stem, each hit classified as kick/snare/hat by band energy and spectral centroid, calibrated against pytheory’s own drum synths — including kick+hat detected simultaneously). score.detected_key carries the key via Key.detect. Import a song, play_score(score), and you have an instant cover version.

  • Real-time tuner — pytheory tune. Microphone → YIN pitch tracking → note name and signed cents offset, 20 readings/second. Terminal needle by default; --serve opens a browser tuner page fed by a Server-Sent Events stream at /stream with CORS open, so any JavaScript app can consume PyTheory’s pitch detection with three lines of EventSource. --ref 442 for orchestras that tune high. Python API: pytheory.tuner.Tuner and analyze_frame().

  • New public audio analysis functionspytheory.audio.detect_chords and detect_drums.

  • Docs homepage now shows uv install instructions.

0.46.0

  • Full-mix transcription — Score.from_wav(split=True). Runs harmonic-percussive separation first (median-filtered spectrogram masks, pure scipy — held notes are horizontal lines on a spectrogram, drum hits vertical), then transcribes a "bass" part and a "melody" part from band-split passes. On a four-track test mix (drums/bass/Rhodes/lead) the melody came back 15/16 notes correct and the bass walked the right roots. Also pytheory transcribe song.wav --split.

  • Automatic tempo estimation. from_wav now estimates BPM from the recording’s onset pattern when bpm= isn’t given (autocorrelated spectral flux with a 120-BPM log-gaussian prior); a rendered 110 BPM groove estimates exactly 110. Pulse-free rubato falls back to 120. score.bpm carries the result.

  • Voice memos load directly. .m4a, .mp3, and other non-WAV formats convert on the fly through afconvert (macOS) or ffmpeg — no manual conversion step.

  • Fix stereo WAV normalization — stereo int16/int32 files were loaded without amplitude scaling (the dtype check ran after the channel mixdown converted to float). Transcription was unaffected (the pitch tracker is scale-invariant), but load_wav output is now correctly in [-1, 1].

0.45.0

  • Audio import — Score.from_wav(). Record yourself humming a melody, whistling a hook, or playing a bass line; load the WAV and get an editable Score back — notes, rests, durations, and velocities. Pitch tracking is a pure-numpy YIN implementation (no new dependencies); transcription is monophonic. Optional quantize= snaps timing to a grid, fmin/fmax tighten the search range for bass or whistle registers. Also available as pytheory transcribe in.wav out.mid. Round-trip verified against pytheory’s own synth output, including repeated-note splitting and the inharmonic piano timbre.

  • New piano. piano_wave is now a modal synthesis of stiff steel strings: genuinely inharmonic partials stretched by string stiffness (the 10th partial of middle C rings ~19 cents sharp, just like a real piano — verified spectrally), a hammer strike-position comb that carves the woody midrange, register-correct stringing (one wound bass string up to a beating three-string trichord), and frequency-dependent damping so every note darkens as it decays — treble notes are short, bass notes bloom. Up to 56 partials in the bass (previously 15), so low notes have actual body.

  • Live notes sustain forever. Sustaining instruments (organ, pads, strings, choir — any envelope holding a level ≥ 0.7 whose source is still ringing) now loop seamlessly inside their wavetables, so a held key rings for as long as you hold it. Percussive instruments (piano, plucks, mallets) still decay naturally and end. The loop seam is crossfaded, click-free, and works through pitch bends. This removes the documented “notes cut off after 3 seconds” limitation.

  • Live effects moved to per-channel buses. Lowpass, reverb, chorus, delay, tremolo, distortion, and saturation now stream per audio block with persistent state (filter memory, delay lines, LFO phases) instead of being baked into each note’s wavetable. MIDI CC sweeps and TUI fx commands apply on the next block (~3ms) with no wavetable re-rendering — turning a filter knob mid-phrase no longer stutters. The live reverb is now the same Schroeder comb/allpass topology as the offline renderer (block-size invariant, verified against the offline math), replacing the old multi-tap echo.

  • Live channels honor detune, sub_osc, and noise_mix — these Part parameters were silently ignored by the live engine.

  • pytheory live — the live TUI is now a first-class CLI command (pytheory live [seed] --port --channels --drums --buffer) instead of python -m pytheory.live_tui.

  • Choir vowel transitions. lyric="ah>oo" glides the formant filters from one vowel to the next across the note — a sung diphthong. Chains work too ("ah>ee>oo"): each vowel holds, then the vocal tract reshapes into the next. Works per-note through Part.add(..., lyric=...) and directly via choir_wave().

0.44.0

  • ~11x faster rendering. The DSP hot paths — Schroeder reverb, Karplus-Strong string synthesis, chorus, filter envelopes, the master compressor, and several one-pole filters — were per-sample Python loops; they now run in C via vectorized scipy.signal.lfilter recurrences and numpy indexing. A representative 35-second multi-part score dropped from 9.1s to 0.8s. Reverb and chorus output is bit-identical to 0.43; string synths match to float epsilon. The compressor’s envelope follower now runs at control rate (32-sample blocks with peak detection), which is inaudible but much faster. ensemble=20 parts and the live engine’s note-cache misses benefit the most.

  • Drum hits by name. Part.hit(), flam(), diddle(), and cheese() now accept drum names as plain strings — kit.hit("kick"), kit.hit("closed_hat") — as well as DrumSound members. Previously strings crashed at render time.

  • pip install "pytheory[live]" now actually works — the live extra (python-rtmidi) was referenced in error messages but missing from packaging.

  • Fix LiveEngine.export_recording() crashing with AttributeError when called on an engine without the TUI (it referenced a TUI-only attribute). Saving a recording from the live TUI works again.

  • Detune oscillators are now cached per pitch like main oscillators, speeding up detuned parts with repeated notes.

  • Documentation overhaul. New homepage organized around what you came for (theory, guitar, composing, live play) with guitar tabs and chord identification front and center; new Live Performance guide; new API reference pages for the sequencing layer (Score/Part/Pattern) and the live engine; custom beat programming and add_pattern() documented; stale feature counts corrected everywhere (56 waveforms, 100 drum patterns, 37 fills, 74 percussion sounds, 83 instrument presets); Sphinx build is now warning-free.

0.43.1

  • Fix Fretboard.scale_diagram() enharmonic matching. Scale notes spelled with flats (e.g. the Eb blue note in the blues scale) were silently omitted from the diagram, because the fretboard spells that pitch as D#. Notes are now matched enharmonically (by pitch) and displayed using the scale’s own spelling.

0.43.0

  • BREAKING — fingerings now read low-to-high by default. Fretboard string lists and Fingering positions/string-names now run from the lowest-pitched string first (e.g. standard guitar reads E A D G B E), matching how chord diagrams and tablature are conventionally written. Previously they ran high-to-low (E B G D A E). This affects Fretboard.tones, iteration over a fretboard, repr, chord(), tab(), chart(), and fingering() output.

    To restore the pre-0.43 high-to-low behavior, pass high_to_low=True to any fretboard constructor — Fretboard.guitar(high_to_low=True), Fretboard(tones=..., high_to_low=True), and likewise on every instrument preset (bass, ukulele, mandolin, … keyboard).

    The flip also applies to input: a custom tuning tuple passed to Fretboard.guitar(...) and manual fret positions passed to fingering(*positions) are now read in the board’s orientation (low-to-high by default).

    to_tab() and Part.strum() are unaffected — they sort by pitch internally and produce identical output regardless of orientation.

0.42.1

  • Fretboard tuning supportto_tab() now accepts Fretboard objects as the tuning parameter. Works with Fretboard.guitar(), Fretboard.bass(), Fretboard.ukulele(), Fretboard.mandolin(), Fretboard.banjo(), and any custom Fretboard with capo.

0.42.0

  • LilyPond exportScore.to_lilypond() generates complete LilyPond source files with multi-staff scores, key/time signatures, tempo markings, and automatic bass clef detection. Output can be compiled to publication-quality PDFs with LilyPond.

  • MusicXML exportScore.to_musicxml() generates MusicXML 4.0 documents that can be opened in MuseScore, Sibelius, Finale, and any notation software. Includes proper ties, chords, clef detection, and tempo/time signature metadata.

  • Guitar/bass tablaturePart.to_tab() and Score.to_tab() generate ASCII tablature. Supports guitar (6-string), bass (4-string), drop D, and custom tunings. Automatically maps notes to the best string/fret positions.

0.41.4

  • Fixto_abc() now ties long notes across barlines instead of emitting oversized durations that abcjs can’t render (e.g. 16-beat notes become four tied whole notes).

0.41.3

  • Fixto_abc() now skips parts with only drum tones or rests (no pitched notes), fixing “pitch is undefined” errors in abcjs. Chords are correctly recognized as pitched content.

0.41.2

  • Auto bass clefto_abc() detects low-register parts (808, bass, timpani) and assigns clef=bass automatically based on average note octave.

0.41.1

  • Fixto_abc() no longer crashes on parts containing drum tones.

0.41.0

  • ABC notation exportScore.to_abc() converts scores to ABC notation strings. Supports multi-voice scores (via V: directives), chords, rests, accidentals, and all standard durations. Pass html=True to get a self-contained HTML page that renders sheet music in the browser via abcjs.

0.40.9

  • Mellotron synth — tape-replay keyboard with wow/flutter, tape saturation, bandwidth limiting, hiss, and 8-second tape fadeout. Three tape banks via the tape parameter: "strings" (default), "flute", and "choir".

  • Analog oscillator synths — four new waveform generators for fat, alive, analog-style sounds:

    • Synth.HARD_SYNC — slave oscillator hard-synced to a master (Prophet-5 leads). slave_ratio parameter controls harmonic content.

    • Synth.RING_MOD — two oscillators multiplied for metallic, bell-like inharmonic tones. mod_ratio parameter.

    • Synth.WAVEFOLD — west coast wavefolding (Buchla-style). folds parameter sweeps from warm to gnarly.

    • Synth.DRIFT — analog VCO with pitch drift, jitter, and noise floor. shape parameter ("saw", "square", "triangle", "pulse") and drift_amount for instability level.

  • Synth kwargs passthroughplay(), save(), and _render() now accept **synth_kw for forwarding parameters to synth wave functions (e.g. play(tone, synth=Synth.MELLOTRON, tape="choir")).

  • 14 new instrument presetsmellotron, mellotron_strings, mellotron_flute, mellotron_choir, sync_lead, sync_lead_bright, ring_mod_bell, ring_mod_metallic, wavefold_warm, wavefold_gnarly, drift_saw, drift_square, analog_pad, analog_bass.

  • 808 bass envelope fix — changed from pluck (zero sustain, wrong for 808) to piano (sharp attack with long decay tail).

0.40.8

  • Fix hold() inflating durationNote.beats was returning the full duration for held notes (_hold=True), causing Part.total_beats and Score.duration_ms to overcount. A part with hold(Sa, WHOLE * 4) followed by add(Pa, QUARTER) would report 17 beats instead of 1. Now held notes return 0 beats, matching the renderer which already skipped advancing the timeline for held notes.

0.40.7

  • Expose missing Synth enum entries — rhodes, wurlitzer, vibraphone, pipe organ, and choir wave functions were already implemented but not accessible via the Synth enum. Now available as Synth.RHODES, Synth.WURLITZER, Synth.VIBRAPHONE, Synth.PIPE_ORGAN, Synth.CHOIR.

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 curvesPart.crescendo(), Part.decrescendo(), Part.swell(), and Part.dynamics() for velocity ramps and custom curves across a sequence of notes

0.38.0

  • Articulationsstaccato, legato, marcato, tenuto, accent, fermata via articulation= on Part.add() and Part.hold()

  • Part.hit() — place individual drum sounds in a Part’s note stream with articulation, velocity, and effects support

  • 5 new djembe patterns — dununba, tiriba, yankadi, djansa, mendiani

  • 3 new djembe fills — djembe call, djembe roll, djembe break (30 fills total)

  • Cross-choke drum damping — striking one sound fades out related sounds (djembe, hi-hats, cajón, doumbek)

  • Improved djembe slap — dry goatskin pop instead of snare-like noise

0.37.0

  • 5 new djembe patterns — dununba, tiriba, yankadi, djansa, mendiani

  • 3 new djembe fills — djembe call, djembe roll, djembe break (30 fills total)

  • Cross-choke drum damping — striking one sound on a hand drum fades out the ring of related sounds (djembe slap kills bass resonance, closed hat chokes open hat, cajón slap dampens bass, doumbek tek dampens dum)

  • Improved djembe slap — dry, high-pitched goatskin pop instead of snare-like noise rattle

0.36.6

  • 6 new drum fills — 3 cajón (flam, rumble, breakdown) and 3 metal (triplet, blast, cascade). 27 fills total.

  • Updated drums documentation with fill lists and examples

0.36.5

  • Duration arithmeticDuration.WHOLE * 2, Duration.HALF + Duration.QUARTER, division, and reverse multiply all work now (previously raised TypeError)

0.36.3

  • Part.hold() — polyphonic overlap on a single part. Add notes without advancing the beat position so they play simultaneously. Enables: piano sustain, sitar drone under melody, guitar strum texture.

  • Strum uses hold() — leading string plays simultaneously with chord, no more timing gaps or choppiness

  • Improved songs 1-16: humanize, velocity dynamics, reverb, saxophone for blues

  • Ctrl-C handling — clean stop on all playback functions

  • REPL updates — strum, roll, bend, temperament, reference commands

  • Song #28 Descent (generative), #29 Pop Rock, #30 Sitar Drone

  • 862 tests

0.36.1

  • 7 new instrument synths: pedal steel guitar, theremin, kalimba/thumb piano, steel drum/pan, accordion (musette reeds), didgeridoo (drone + shifting formants), bagpipes (chanter reed)

  • 9 new demo moods in pytheory demo: Theremin Noir, Caribbean, Accordion Waltz, Kalimba Dreams, Outback Drone, Highland, Nashville Tears, Tabla Fusion

  • Improved existing songs with dedicated instrument synths

  • 41 synth waveforms, 26+ songs, 21 demo moods

0.36.0

  • Banjo synth — steel strings on drum-head body, nasal twang, fast decay with membrane resonance

  • Mandolin synth — paired steel strings (natural chorus from doubled courses), bright body resonance

  • Ukulele synth — nylon strings, small mid-heavy body, shorter sustain than guitar

  • Cajón drums — bass (woody box thump), slap (snare wire buzz), tap (ghost note). 3 patterns: cajon, cajon rumba, cajon folk

  • Vocal/formant synth — LF glottal model, 5 Peterson & Barney formant peaks, jitter/shimmer, consonant onsets, per-note lyrics. Presets: vocal, choir

  • Granular synthesis — grain cloud engine with scatter, pitch variation, Hanning windows. Presets: granular_pad, granular_texture

  • Strum sweep — subtle grace notes before chord hit for natural strum feel on all fretboard instruments

  • Mandola preset, 34 synth waveforms, 26 songs

0.35.0

  • 8.5x faster import — dropped pytuning/sympy, lazy-load scipy. import pytheory now takes ~50ms instead of ~480ms (#44)

  • Proper shruti JI ratios — 22 positions with 5-limit just intonation (pure 3/2 fifths, 5/4 thirds), not 22-TET approximation

  • Arabic maqam JI ratios — Zalzalian 11-limit ratios. Mi↓ (the Rast third) is exactly 27/22 from Do

  • B#/Cb octave boundary fix — B#4 = C5, Cb4 = B3 (#45)

  • Int tone namesTone(0, system=TET(22)) works alongside strings. Wrapping: Tone(22) → tone 0, octave+1. System.tone() convenience.

  • Timpani synth — inharmonic membrane modes, felt mallet, copper kettle resonance, cathedral reverb

  • Saxophone synth — conical bore, reed buzz, brass body warmth. 4 presets: saxophone, alto_sax, tenor_sax, bari_sax

  • Part.roll() — rapid repeated notes with velocity ramp for crescendo/ decrescendo rolls on any instrument

  • Vibrato tuning — all instruments reduced to 0.001 depth for cleaner ensemble sound

  • Granular synthesis — grain cloud engine with scatter, pitch variation, and Hanning-windowed grains. Two presets: granular_pad, granular_texture.

  • 30 synth waveforms, 838 tests

0.34.0

  • 16 dedicated instrument synths — physical modeling and specialized synthesis for: piano (hammer + steel strings + soundboard), bass guitar (thick KS + pickup), flute (breath + tube resonance), trumpet (lip buzz

    • bell), clarinet (odd harmonics + reed), oboe (double reed + conical bore), marimba (inharmonic bar modes), harpsichord (quill pluck), cello (deep bowed + body), harp (soft pluck + soundboard bloom), upright bass (pizzicato + wooden body), acoustic guitar (KS + body resonance), electric guitar (KS + pickup comb filter), sitar (jawari

    • chikari), plus organ and bowed strings

  • Speaker cabinet simulation — tames distorted guitar fizz

  • Guitar strummingPart.strum("Am") with fretboard lookup

  • Analog oscillator drift — subtle per-note pitch wobble on synth presets

  • World percussion: dhol, dholak, mridangam, djembe, metal kit with 22 new drum patterns

  • Piano improvements: brightness scales with pitch, two-stage decay, hammer impact with felt character

  • Vibrato tuning: reduced across flute, oboe, trumpet, cello for smoother ensemble sound

  • 27 synth waveforms, 10 envelopes, 40+ instrument presets, 80+ drum patterns

0.33.1

  • Electric guitar synth — Karplus-Strong with magnetic pickup comb filter simulation (single-coil honk, proper sustain)

  • Speaker cabinet simulation — steep rolloff above 4-5kHz with presence bump. Makes distorted guitar sound warm instead of fizzy.

  • 6 guitar presets: electric_guitar, clean_guitar, crunch_guitar, distorted_guitar, orange_crunch, metal_guitar — all with proper cab sim

  • Sitar synth — Karplus-Strong with jawari bridge buzz, chikari sympathetic strings, variable damping

  • Guitar strummingPart.strum("Am", Duration.HALF) with fretboard fingering lookup, down/up direction, adjustable strum speed

  • World drums: dhol (bhangra, chaal), dholak (qawwali, folk), mridangam (adi talam, korvai), djembe (standard, kuku, soli) — all with bandpass-filtered membrane noise for realistic drum head sound

  • Metal drum kit — clicky kick, bright snare, tight hats with 4 patterns (double kick, metal blast, metal groove, metal gallop)

  • 15 synth waveforms, 10 envelopes, 40+ instrument presets

0.33.0

  • Non-12-TET supportTET(n) factory creates any equal temperament

  • 11 microtonal systems:

    • "shruti" (22-TET Indian, 10 thaats with proper shruti intervals)

    • "maqam" (24-TET Arabic, quarter-tone Rast/Bayati/Hijaz + 7 more)

    • "slendro" (5-TET gamelan), "pelog" (9-TET gamelan with 3 pathet)

    • "thai" (7-TET, 171 cents/step)

    • "makam" (53-TET Turkish Arel-Ezgi-Uzdilek, 9 makams)

    • "carnatic" (72-TET, 10 melakartas)

    • "19-tet", "31-tet" (historical Western)

    • "bohlen-pierce" (13 divisions of the tritave 3:1 — non-octave!)

  • Just intonationtemperament="just" for pure 5-limit ratios

  • Historical pitchScore(reference_pitch=415.0) for Baroque A=415

  • Score(system=, temperament=, reference_pitch=) flows through to all playback

  • Per-system c_index and period replace hardcoded constants

  • Fixed all hardcoded 12s in tone arithmetic

  • Song #22: Greensleeves (Renaissance lute, meantone, A=415)

  • 22 new microtonal tests (819 total)

0.32.1

  • Tone("X") now raises ValueError immediately instead of silently accepting invalid names (#39)

  • Support enharmonic spellings: Cb, Fb, E#, B# resolve correctly (#40)

  • Support double sharps (C##, Fx) and double flats (Dbb) via semitone arithmetic (#41)

  • Accept unicode music symbols: 𝄪 𝄫

0.32.0

  • 8 new synth engine features:

    • Filter envelope: per-note lowpass sweep (filter_amount, filter_attack, filter_decay, filter_sustain)

    • Velocity → brightness: harder notes = brighter filter (vel_to_filter)

    • Sub-oscillator: octave-below sine for bass weight (sub_osc)

    • Tremolo: amplitude LFO modulation (tremolo_depth, tremolo_rate)

    • Saturation: even-harmonic tape/tube warmth (saturation)

    • Noise layer: per-note breath/air texture (noise_mix)

    • Phaser: swept allpass filter chain (phaser, phaser_rate)

    • Configurable FM: fm_ratio and fm_index params

  • Highpass filter (12 dB/oct biquad) on any part

  • 2 new envelopes: bowed (bow attack with sustain), mallet (strike with ringing sustain)

  • Improved strings_synth: additive synthesis with body resonance curve, per-harmonic phase randomization, delayed vibrato onset, bow pressure variation

  • Instrument preset overhaul: every preset sanity-checked against real instrument behavior

    • Mallet instruments (vibraphone, celesta, music box, glockenspiel, tubular bells) now ring properly

    • Trumpet uses sustaining envelope instead of pluck

    • Woodwinds have breath noise, brass has velocity brightness

    • Bass instruments have sub-oscillators, synth presets have filter envelopes

    • Piano has velocity-to-brightness and subtle hammer noise

  • Signal chain: saturation → tremolo → distortion → chorus → phaser → highpass → lowpass → delay → reverb

  • Song #21: Cinematic Showcase (Orchestral)

0.31.0

  • 3 new synth engines: Karplus-Strong pluck, Hammond organ, string ensemble with body formants

  • 38 instrument presets: score.part("lead", instrument="violin")

  • Keys, strings, woodwinds, brass, plucked, synth, and mallet categories

  • 13 total synth waveforms

0.30.0

  • Drums are a real Part — same effects pipeline as any voice

  • score.drums("rock", split=True) splits kit into kick/snare/hats/toms/cymbals/percussion Parts

  • Each split Part gets independent effects (reverb on snare, LP on hats, etc.)

  • set_drum_effects() applies to all drum Parts (split or not)

  • Sidechain triggers on kick only — hats and snare don’t duck the pad

  • MIDI import via Score.from_midi(path)

0.29.3

  • Drums are now a real Part — same effects pipeline as any other voice, zero code duplication

  • score.parts["drums"] is a standard Part with reverb, delay, lowpass, etc.

  • set_drum_effects() is sugar over the Part’s attributes

0.29.2

  • Add score.set_drum_effects() — reverb, delay, lowpass, distortion, chorus on the drum bus

  • Same effects engine as parts, zero code duplication

0.29.1

  • Rename song.py → songs.py

  • Polish all 20 example songs with stereo, convolution reverb, humanize, detune, sidechain

0.29.0

  • Add Score.from_midi(path) — import any Standard MIDI File into a Score

  • Minimal zero-dependency MIDI parser (Type 0 and Type 1)

  • Each channel becomes a named Part, channel 10 becomes drum hits

  • Tempo, time signature, velocities, and note durations preserved

  • Roundtrip: save_midi → from_midi works

0.28.3

  • Rewrite pytheory demo — 8 moods with stereo, effects, humanize, convolution reverb, sidechain

  • Added Dub and Temple moods

0.28.2

  • Lower drum_humanize default to 0.15 — tighter, more professional feel

0.28.1

  • Humanize drum hits — random timing jitter and velocity variation (default 0.3)

  • Control via Score(drum_humanize=0.5) — 0.0 = quantized, 0.3 = natural, 0.5+ = loose

0.28.0

  • Add figured bass notation: Chord.figured_bass and Chord.analyze_figured() for classical inversion symbols

  • Add pitch class set theory: pitch_classes, normal_form, prime_form, forte_number on Chord

  • Add Scale.recommend() — ranked scale suggestions for a set of notes

  • Forte number catalog covers all trichords and tetrachords

0.27.1

  • Tab completion in REPL — context-aware for commands, drum presets, synths, envelopes, chords, notes, systems

0.27.0

  • Rewrite all 15 drum sounds for higher quality (inharmonic partials, proper transients, multi-mode resonance, saturation)

  • 19 example songs including Dance Party at the Reitz House

0.26.3

  • Stereo drum panning — each sound placed in the stereo field (hat right, crash left, toms spread, kick/snare center)

  • Stereo convolution reverb — different IR per L/R channel for all 7 presets

  • 2 new songs: Neon Grid (stereo acid), Glass and Silk (sine+triangle waltz)

0.26.2

  • Stereo convolution reverb — different IR per L/R channel for all 7 presets

  • Both algorithmic and convolution reverbs now output true stereo

0.26.1

  • Stereo reverb — L and R channels get different early reflection patterns for natural width

  • Effects chain now skips mono reverb in favor of stereo reverb in the mixer

0.26.0

  • Stereo output — render_score() now returns stereo (N, 2) arrays

  • Add pan parameter: -1.0 (left) to 1.0 (right), constant-power panning

  • Add spread parameter: detuned oscillators spread across L/R channels

  • Master bus compressor runs per-channel for stereo

  • All playback functions handle stereo natively

0.25.7

  • Add detune parameter — ±cents oscillator spread on any synth (3 oscillators per note)

  • Swing now applies to drum hits (offbeats shift with the groove)

  • Improved snare and hi-hat sounds (metallic harmonics, faster attack)

0.25.6

  • Swing now applies to drum hits — offbeats shift with the groove, everything locks into the same pocket

  • Improved snare: 220Hz body, transient click, tanh saturation

  • Improved hi-hats: metallic harmonics (6k+8.5k+12k Hz), crisper attack, shorter decay

0.25.5

  • Improved snare: 220Hz body, transient click, tanh saturation — snappier and more present

  • Improved hi-hats: metallic harmonics (6k+8.5k+12k Hz), shorter decay, crisper attack

0.25.4

  • Add master bus compressor/limiter — louder, punchier, more cohesive mixes

  • Feed-forward compression with configurable threshold, ratio, attack, release

  • Makeup gain restores loudness after compression

  • Brick-wall limiter at 0.95 prevents clipping

  • Replaces simple normalization in render_score()

0.25.3

  • Add pytheory repl — interactive music theory scratchpad and composition tool

  • Context-aware prompt shows key, bpm, drums, active part + effects

  • Theory commands: key, chords, modes, scales, circle, interval, identify, system

  • Composition: drums, part, add, rest, arp, prog, effects, automation, LFO

  • Guitar: fingering, scale diagram

  • 6 musical systems with correct default tonics

  • REPL guide documentation

0.25.1

  • Add pytheory demo CLI command — plays a randomly generated track, different every time

  • Rewrite README to showcase the full feature set (composition, effects, drums, MIDI export)

0.25.0

  • Add sidechain compression — kick ducks pad/bass for the classic EDM pump effect

  • Add song structure: score.section("verse"), score.section("chorus"), score.repeat("verse")

  • Punchier kick drum: 808-style with faster pitch sweep (200→45Hz), sub thump, and soft saturation

  • Section repeat copies all part notes, drum hits, and automation with proper offset

0.24.1

  • Add humanize parameter on Parts — random micro-timing and velocity variation

  • Makes programmed parts feel like a real player (0.1 = subtle, 0.3 = natural, 0.5+ = loose)

0.24.0

  • Add per-note velocity: lead.add("C5", Duration.QUARTER, velocity=90) — dynamics, accents, ghost notes

  • Add swing/groove: Score("4/4", bpm=120, swing=0.5) — shuffles every other note for human feel

  • Add tempo changes mid-song: score.set_tempo(140) — accelerando, ritardando, tempo drops

  • Add Part.fade_in(bars) and Part.fade_out(bars) — volume envelopes over sections

  • Arpeggiator supports velocity parameter

  • Per-part swing override (set independently from score swing)

  • Tempo map engine: beat-to-sample conversion handles variable BPM throughout a score

0.23.0

  • Add convolution reverb with 7 synthetic impulse responses: Taj Mahal, cathedral, plate, spring, cave, parking garage, canyon

  • Each IR models real acoustic properties: early reflections, frequency-dependent absorption, diffusion density, and modulation

  • FFT-based convolution via scipy.signal.fftconvolve for fast processing even with long tails (12s Taj Mahal)

  • Select via reverb_type parameter on Score.part() — drop-in alongside existing algorithmic reverb

  • IR cache for zero-cost reuse across parts

  • Automatable via Part.set(reverb_type="cathedral") mid-song

0.22.0

  • Add Part.lfo() for automated parameter modulation (filter sweeps, tremolo, auto-wah)

  • 4 LFO shapes: sine, triangle, saw, square

  • Configurable rate (cycles per bar), min/max range, duration, and resolution

  • Stack multiple LFOs on different parameters for complex modulation

0.21.0

  • Add Part.set() for mid-song effect automation (filter sweeps, reverb swells, distortion kicks)

  • Add chorus effect (LFO-modulated delay, Juno-style)

  • Renderer segments audio at automation points for per-section effect processing

  • Updated effect chain: distortion → chorus → lowpass → delay → reverb

  • Document automation, chorus, and updated signal chain

0.20.0

  • Add Part.arpeggio() — arpeggiator with up/down/updown/downup/random patterns, octave spanning

  • Fix Roman numeral parser to handle flat/sharp degree prefixes (bVI, bVII, bIII, #IV)

  • Add song_showoff.py — generative composition that’s different every time, uses every feature

  • 4 mood palettes (dark, bright, ethereal, aggressive) with matched keys, progressions, drums, and effects

0.19.1

  • Add Part.arpeggio() — arpeggiator with up/down/updown/downup/random patterns, octave spanning, and division control

  • Arpeggiator chains with legato + glide for classic acid/trance sequencer sound

  • Rename rhythm docs to “Sequencing: Rhythm and Scores”

  • Document arpeggiator, legato, and glide in rhythm guide

0.19.0

  • Add legato mode for parts — continuous waveform without retriggering envelope per note

  • Add glide/portamento — smooth pitch slides between consecutive notes (303-style)

  • Legato renders entire phrase as one oscillator with phase-accumulating frequency changes

  • Glide uses exponential interpolation for perceptually linear pitch slides

0.18.1

  • Add distortion effect (tanh soft-clip waveshaping) with drive and mix controls

  • 3 new example songs: Dub Delay Madness (separate delay snare), Liquid DnB (174bpm), Late Night Texts (Drake-style trap)

  • 16 total songs in the song player

0.18.0

  • Add per-part audio effects: reverb, delay, and lowpass filter

  • Reverb: Schroeder algorithm with configurable mix and decay

  • Delay: tempo-synced echoes with feedback control

  • Lowpass: 12 dB/octave biquad filter with resonance (Q) control

  • All effects set at part creation: score.part("lead", reverb=0.3, delay=0.25, lowpass=2000, lowpass_q=1.5)

  • Effects applied per-part before mixing for independent processing

0.17.0

  • Add 10 new groove presets: country, ska, dub, jungle, techno, gospel, swing, bolero, tango, flamenco (58 total)

  • Add 10 new fill presets: reggae, afrobeat, bossa nova, house, trap, hip hop, disco, cumbia, highlife, second line (21 total)

  • Every major genre family now has matching groove + fill presets

0.16.0

  • Add drum fill system with 11 genre-specific presets: rock, rock crash, jazz, jazz brush, salsa, samba, funk, metal, blast, buildup, breakdown

  • Pattern.fill("rock") returns a 1-bar fill pattern

  • Score.fill("rock") inserts a fill at the current position

  • Score.drums("rock", repeats=8, fill="rock", fill_every=4) auto-fills every Nth bar

  • Without fill_every, fill replaces only the last bar

0.15.1

  • Add Synth.PWM_SLOW and Synth.PWM_FAST — pulse width modulation with LFO sweep (Juno-style pads)

  • Add Score.drums() shorthand for score.add_pattern(Pattern.preset(...), repeats=...)

  • Update all docs to use score.drums() syntax and document all 10 synth waveforms

0.15.0

  • Add 5 new synth waveforms: Synth.SQUARE, Synth.PULSE, Synth.FM, Synth.NOISE, Synth.SUPERSAW

  • Square wave: classic chiptune / 8-bit sound (odd harmonics at 1/n)

  • Pulse wave: variable duty cycle for NES-style timbres (25%, 12.5%)

  • FM synthesis: DX7-style frequency modulation (electric piano, bells, brass, metallic)

  • Noise: white noise for percussion textures and effects

  • Supersaw: 7 detuned saw oscillators for trance/EDM pads

  • All 8 synths available in both the API (Synth.FM) and Part strings (synth="fm")

  • CLI play command supports all 8 waveforms

0.14.0

  • Add Part class for multi-voice Score arrangements (lead, bass, pads, etc.)

  • Score.part() creates named parts with independent synth, envelope, and volume

  • Score.add_pattern() for attaching drum patterns

  • render_score() exported for headless buffer rendering

  • Parts accept raw float beat values alongside Duration enums

  • All 10 example songs rewritten with drums + chords + lead + bass parts

0.13.1

  • Fix drum pattern repeats: hits now correctly offset across cycles instead of piling up on the first bar

0.13.0

  • Add drum synthesizer with 27 individual instrument voices (kick, snare, hat, conga, timbale, etc.)

  • Add play_pattern() for playing drum patterns through the speakers

  • Add play_score() for playing mixed drum patterns + chord progressions together

  • Every DrumSound has a dedicated synthesis algorithm (pitch sweeps, noise bursts, membrane resonance, metallic rings)

0.12.0

  • Add rhythm module: Duration, TimeSignature, Note, Rest, Score

  • Duration enum with 8 note lengths (whole through sixteenth, dotted, triplet)

  • TimeSignature with string parsing (“4/4”, “3/4”, “6/8”, “12/8”) and beats_per_measure

  • Score class with fluent .add() / .rest() chaining, measure counting, and save_midi() export

  • Measure-aware MIDI export with proper time signature and tempo meta events

  • Add DrumSound enum with 27 General MIDI percussion sounds

  • Add Pattern class with 48 drum pattern presets covering:

    • Rock/Pop: rock, half time, double time, disco, motown, train beat

    • Jazz: jazz, bebop, shuffle, linear, paradiddle

    • Latin: salsa, bossa nova, samba, cumbia, merengue, baiao, maracatu

    • Afro-Cuban: son clave 3-2/2-3, rumba clave 3-2/2-3, cascara, guaguanco, mozambique, nanigo, bembe, 6/8 afro-cuban, tresillo, habanera

    • African: afrobeat, highlife

    • Caribbean: reggae, dancehall

    • Electronic: house, trap, drum and bass, breakbeat

    • Metal/Punk: metal, blast beat, punk

    • Other: funk, hip hop, bo diddley, second line, new orleans, waltz, 12/8 blues

  • Pattern.to_score() renders drum patterns to Score for MIDI export

0.11.0

  • Add drop voicings: Chord.close_voicing(), Chord.open_voicing(), Chord.drop2(), Chord.drop3()

  • Add Key.modulation_path(target) for chord-by-chord modulation suggestions via pivot chords

  • Add Scale.degree_name(n) returning traditional names (tonic, dominant, leading tone, etc.)

  • Add Chord.extensions() to suggest available 9th/11th/13th extensions

  • Add Tone.solfege property for fixed-Do solfege syllables (Do, Re, Mi, Fi, etc.)

  • Add CLI identify command for full chord analysis from a symbol

  • Add CLI midi command for exporting progressions to Standard MIDI Files

  • Expand documentation: solfege, Helmholtz, cents, slash chords, drop voicings, chord extensions, borrowed chord analysis, ADSR envelopes, MIDI export, new CLI commands

0.10.0

  • Add Scale.fitness() to score how well a set of notes fits a scale (0.0–1.0)

  • Add Key.suggest_next(chord) for chord progression suggestions based on functional harmony

  • Add Tone.helmholtz and Tone.scientific properties for alternate pitch notation

  • Add Chord.slash(bass) and Chord.slash_name for slash chord notation (C/G, Am/E)

  • Add save_midi() for exporting tones, chords, and progressions as Standard MIDI Files

  • Add chord tone highlighting in Fretboard.scale_diagram() — chord tones uppercase, passing tones lowercase

  • Extend Chord.analyze() to recognize borrowed chords (bVI, bVII, bIII, etc.)

0.9.0

  • Add ADSR envelope system with 8 presets: Envelope.PIANO, ORGAN, PLUCK, PAD, STRINGS, BELL, STACCATO, NONE

  • Add Chord.from_symbol() parser — handles any standard chord symbol (e.g. “F#m7b5”, “Bbmaj9”, “Gsus4”) without lookup tables

  • Add Key.pivot_chords(target) for finding modulation pivot chords between two keys

  • Add Scale.parallel_modes() to show all modes sharing the same notes (C major → D dorian, E phrygian, etc.)

  • Add Tone.cents_difference(other) for measuring fine pitch differences in cents

  • Add --envelope flag to CLI play command

  • CLI play command now uses Chord.from_symbol() for broader chord parsing

  • Replace hardcoded c_index = 3 with named C_INDEX constant throughout

0.8.3

  • Add Chord.symbol property for standard shorthand notation (Cmaj7, Dm, G7, m7b5, etc.)

  • Add Key.common_progressions() to realize all named progressions in a key

  • Add CLI commands: modes, circle, progressions

0.8.2

  • Use flat spellings in CHARTS acceptable_tone_names (e.g. Bbm now shows Bb/Db/F instead of A#/C#/F)

0.8.1

  • Use musically correct flat spellings in flat keys (F major gives Bb, not A#)

0.8.0

  • Add Fretboard.scale_diagram() for visual scale layouts on any instrument

  • Add play_progression() for sequential chord playback with gaps

  • Add cookbook documentation page with practical recipes

  • Curated guitar fingering overrides for common open chords

  • Fingering memoization with bounded cache, barre detection, 4-fret span constraint

  • API ergonomics: Fretboard.chord(), convenience constructors, slow test markers

0.7.0

  • Add Fretboard.chord() method for named chord lookups

  • Improve fingering algorithm with better voicing selection

  • Rewrite all documentation in REPL style with verified output

0.6.1

  • Fix sawtooth and triangle wave generation

  • Add WAV export via save()

  • Add CLI tests and play module tests

  • Skip play module tests when PortAudio is not available

0.6.0

  • Support flat note names (Db, Bb, Eb, etc.) throughout the system

  • Add Fingering class for labeled chord fingerings

  • Add pytheory play CLI command for playing notes and chords

  • Add 12 example scripts showcasing pytheory features

  • Expand documentation with undocumented features and CLI guide

0.4.1

  • Add --temperament flag to CLI tone command

  • Add Symbolic Pitch section to tones docs

0.4.0

  • Add key signatures, scale diagrams, chord building, and progression analysis

  • Add CLI tool (pytheory tone, pytheory chord, pytheory key, etc.)

  • Add Jupyter notebook tutorial

  • Improve test coverage from 93% to 97% (476 tests)

  • Add type hints, docstrings, and property caching throughout

0.3.2

  • Add type hints and docstrings throughout the library

0.3.1

  • Add capo support, chord merging (+), tritone substitution

  • Add secondary dominants, Nashville number system

  • Add more common progressions (blues, jazz, flamenco, modal)

0.3.0

  • Add interval naming (Tone.interval_to())

  • Add MIDI conversion (Tone.midi, Tone.from_midi())

  • Add Tone.from_frequency(), Tone.transpose()

  • Add Chord.root, Chord.quality properties

  • Add Chord.from_name(), Chord.from_intervals(), Chord.from_midi_message()

  • Add Interval constants (MINOR_THIRD, PERFECT_FIFTH, etc.)

  • Add PROGRESSIONS dict with common named progressions

  • Add Tone.enharmonic property

  • Add inversions, harmonize, and Roman numeral progressions

  • Add Key class with detection, signatures, relative/parallel keys

  • Add Scale.detect() and Chord.from_tones() convenience constructors

  • Add 25 instrument presets (mandolin family, violin family, banjo, harp, world instruments, keyboard)

  • Add Tone.circle_of_fifths() and Tone.circle_of_fourths()

  • Add chord identification (17 types), voice leading, tension scoring

  • Add beat frequencies, Plomp-Levelt dissonance model, harmony scoring

0.2.0

  • Add Fretboard class for guitar fretboards

  • Add play() function with sine, sawtooth, and triangle wave synthesis

  • Add chord harmony and dissonance calculations

  • Modernize project structure (pyproject.toml, sounddevice)

0.1.0

  • Initial release

  • Western 12-tone system with tones, scales, and basic chord support

  • Temperament support (equal, Pythagorean, meantone)

  • Indian (Hindustani), Arabic, Japanese, Blues, and Gamelan systems