Playback and Export¶
This is the output layer. You’ve built your theory, composed your arrangement, shaped your sounds – now you need to hear it. PyTheory gives you four ways to get your music out: speakers, WAV files, MIDI files, and sheet music.
Use speakers for immediate feedback while you’re sketching and experimenting. Use WAV export when you want to share actual audio – post it, send it, drop it into a video. Use MIDI export when you want to bring your sketch into a real DAW and finish it with professional instruments, mixing, and mastering. Use ABC notation export when you want sheet music – rendered in the browser or shared as plain text. Each output serves a different stage of the creative process.
PyTheory can play audio through your speakers, save to WAV, export to MIDI, or generate sheet music as ABC notation. Everything is synthesized from waveforms – no samples or external audio files needed.
Note
Audio playback requires PortAudio to be
installed on your system. On macOS: brew install portaudio.
On Ubuntu: apt install libportaudio2.
play() – Single Tones and Chords¶
The simplest way to hear something:
from pytheory import Tone, Chord, play
play(Tone.from_string("A4"), t=1_000) # A440 for 1 second
play(Chord.from_symbol("Am7"), t=2_000) # chord for 2 seconds
Optional parameters for synth, envelope, and temperament:
from pytheory import Synth, Envelope
play(Tone.from_string("C4"), synth=Synth.SAW, envelope=Envelope.PLUCK, t=1_000)
play(Tone.from_string("C4"), temperament="pythagorean", t=1_000)
Synth-specific parameters are passed through as keyword arguments:
# Mellotron with flute tape
play(Tone.from_string("C4"), synth=Synth.MELLOTRON, tape="choir", t=2_000)
# Hard sync with custom slave ratio
play(Tone.from_string("C4"), synth=Synth.HARD_SYNC, slave_ratio=2.5)
# Wavefolding with 4 folds
play(Tone.from_string("C4"), synth=Synth.WAVEFOLD, folds=4.0)
# Drift oscillator with square shape
play(Tone.from_string("C4"), synth=Synth.DRIFT, shape="square")
play_score() – Full Arrangements¶
Plays a Score with all its parts and drums mixed together.
Output is stereo — each part is panned according to its pan
setting, drums are stereo-panned like a real kit, and reverb tails
have natural stereo width. A master bus compressor/limiter (4:1
ratio, brick-wall at 0.95) is applied to prevent clipping and make
the mix louder and punchier:
from pytheory import Score, Duration, Chord
from pytheory.play import play_score
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
chords = score.part("chords", synth="sine", envelope="pad")
for sym in ["Am", "Dm", "E7", "Am"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
play_score(score)
The render pipeline respects the Score’s temperament and
reference_pitch settings, so Baroque or microtonal scores play back
at the correct tuning:
score = Score("4/4", bpm=80, temperament="meantone", reference_pitch=415.0)
Press Ctrl+C at any time during playback to stop — PyTheory catches
KeyboardInterrupt and stops audio cleanly.
See Sequencing for how to build scores and parts.
render_score() – Headless Rendering¶
Returns a raw audio buffer (numpy float32 array) without playing it. Useful for saving to WAV or further processing:
>>> from pytheory.play import render_score
>>> buf = render_score(score) # numpy float32 array
>>> len(buf)
604800
save() – WAV Export¶
Render tones or chords to a WAV file. Works without speakers or PortAudio:
from pytheory import save, Chord, Tone, Synth
save(Tone.from_string("A4"), "a440.wav", t=1_000)
save(Chord.from_name("Am7"), "am7.wav", t=2_000)
save(
Chord.from_name("C"),
"c_triangle.wav",
synth=Synth.TRIANGLE,
temperament="meantone",
t=3_000,
)
save_midi() – MIDI Export¶
MIDI export is probably the most useful feature here for working musicians. The idea is simple: sketch your ideas in Python – where iteration is fast, where you can use loops and randomness and music theory functions – and then export to MIDI. Open that MIDI file in Logic, Ableton, Reaper, FL Studio, or whatever you use, and now you’ve got your chord progressions, melodies, and bass lines on real tracks. Swap in your favorite soft synths, add real mixing, finish the track properly. Python is the sketchpad; the DAW is the canvas.
Export tones, chords, progressions, or full scores as Standard MIDI Files. MIDI files can be opened in any DAW, edited, transposed, and assigned to any instrument.
Simple export (single tone, chord, or progression):
from pytheory import save_midi, Key, Tone, Chord
save_midi(Tone.from_string("C4"), "middle_c.mid", t=1000)
save_midi(Chord.from_symbol("Am7"), "am7.mid")
chords = Key("C", "major").progression("I", "V", "vi", "IV")
save_midi(chords, "pop.mid", t=500, bpm=120)
Score-based export (with time signature, tempo, and parts):
from pytheory import Score, Duration, Key
score = Score("4/4", bpm=140)
for chord in Key("G", "major").progression("I", "IV", "V", "I"):
score.add(chord, Duration.WHOLE)
score.save_midi("progression.mid")
to_abc() – ABC Notation / Sheet Music¶
ABC notation is a human-readable text format for music that tools can turn into staff notation and MIDI. It’s widely used for folk tunes, lead sheets, and quick sketches. PyTheory can export any Score as ABC notation – and optionally wrap it in an HTML page that renders sheet music right in the browser using abcjs.
Basic export:
from pytheory import Score, Duration, Key
score = Score("4/4", bpm=120)
lead = score.part("lead")
for chord in Key("C", "major").progression("I", "V", "vi", "IV"):
lead.add(chord, Duration.WHOLE)
print(score.to_abc(title="Pop Chords", key="C"))
Output:
X:1
T:Pop Chords
M:4/4
Q:1/4=120
L:1/8
K:C
[CEG]8 | [GBd]8 | [Ace]8 | [FAc]8 |
Open sheet music in the browser with html=True:
html = score.to_abc(title="Pop Chords", key="C", html=True)
with open("chords.html", "w") as f:
f.write(html)
import webbrowser
webbrowser.open("chords.html")
This generates a self-contained HTML page with an embedded
<script> tag that loads abcjs from a CDN and renders the notation
as SVG – no build steps, no dependencies, just open the file.
Multi-part scores automatically get V: (voice) directives so each
instrument appears on its own staff. Bass parts (average note below C4)
get bass clef automatically. Drum-only parts are skipped. Notes longer
than one measure are split into tied notes across barlines.
Parameters:
title – Tune title for the
T:header (default"Untitled").key – ABC key signature string (default
"C"). Use"Am"for A minor,"Bb"for B-flat major,"F#m"for F-sharp minor, etc.html – If
True, return a full HTML document instead of raw ABC (defaultFalse).
to_lilypond() – LilyPond Export¶
LilyPond is the gold standard for
publication-quality music engraving. to_lilypond() generates
complete LilyPond source files that you can compile to PDF:
score = Score("4/4", bpm=120)
lead = score.part("lead")
for note in ["C4", "D4", "E4", "F4"]:
lead.add(note, Duration.QUARTER)
ly = score.to_lilypond(title="My Score", key="C", mode="major")
with open("score.ly", "w") as f:
f.write(ly)
Then compile with lilypond score.ly to get a PDF. Multi-part scores
get separate staves in a StaffGroup, bass clef is auto-detected,
and long notes are split with ties across barlines.
Parameters:
title – Title for the
\headerblock (default"Untitled").key – Key signature root (default
"C"). Use note names like"Bb","F#","Eb".mode – LilyPond mode string (default
"major"). Use"minor"for minor keys.
to_musicxml() – MusicXML Export¶
MusicXML is the interchange format for notation software. Export your score and open it in MuseScore, Sibelius, Finale, Dorico, or any other notation app:
xml = score.to_musicxml(title="My Score")
with open("score.musicxml", "w") as f:
f.write(xml)
The output is a complete MusicXML 4.0 partwise document with proper time signatures, tempo markings, clef detection, tied notes across barlines, and chord notation. No external dependencies needed.
to_tab() – Guitar/Bass Tablature¶
Generate ASCII tablature from any Part or Score:
lead = score.part("lead")
lead.add("E4", Duration.QUARTER)
lead.add("B3", Duration.QUARTER)
lead.add("G3", Duration.QUARTER)
lead.add("D3", Duration.QUARTER)
print(lead.to_tab())
Output:
e|---0---------|
B|------0------|
G|---------0---|
D|------------0|
A|-------------|
E|-------------|
Works on Score too – it picks the first melodic part automatically:
print(score.to_tab()) # auto-pick part
print(score.to_tab(part_name="bass")) # specific part
print(score.to_tab(tuning="bass")) # 4-string bass tab
print(score.to_tab(tuning="drop_d")) # drop D guitar
Supports "guitar" (6-string standard), "bass" (4-string),
"drop_d", or a custom list of MIDI note numbers for any tuning.
play_pattern() – Drum Patterns¶
Play a drum pattern through the speakers:
from pytheory import Pattern
from pytheory.play import play_pattern
play_pattern(Pattern.preset("rock"), repeats=4, bpm=120)
play_pattern(Pattern.preset("bossa nova"), repeats=4, bpm=140)
See Drums for the full list of 100 presets and 37 fills.
play_progression() – Quick Chord Playback¶
Play a chord progression in sequence with a single call:
from pytheory import Key, play_progression
chords = Key("C", "major").progression("I", "V", "vi", "IV")
play_progression(chords, t=800)
Optional synth, envelope, and gap parameters:
from pytheory import Synth, Envelope
play_progression(chords, t=1000, synth=Synth.TRIANGLE, gap=200)
play_progression(chords, t=2000, envelope=Envelope.PAD)
That’s the workflow: hear it, tweak it, hear it again. When it sounds right, export to WAV or MIDI and take it somewhere bigger.
MIDI Import¶
Load any Standard MIDI File into a Score — then play it through PyTheory’s synth engine with effects, or analyze the theory:
from pytheory import Score
from pytheory.play import play_score
score = Score.from_midi("song.mid")
# See what's inside
for name, part in score.parts.items():
print(f"{name}: {len(part.notes)} notes")
# Change the synth and add effects
score.parts["ch1"].synth = "saw"
score.parts["ch1"].reverb_mix = 0.3
play_score(score)
Each MIDI channel becomes a named Part (ch1, ch2, etc.).
Channel 10 (drums) becomes drum hits. Tempo, time signature,
note durations, and velocities are all preserved.
Download any MIDI file from the internet, load it, play it through the synth engine with reverb and delay. That’s the whole idea.
Audio Import — WAV → Score¶
The reverse direction: record yourself humming a melody, whistling a hook, or playing a bass line, and turn the recording back into notes you can edit, harmonize, quantize, export to MIDI, or print as sheet music:
from pytheory import Score
score = Score.from_wav("hum.wav", bpm=100, quantize=0.25)
for note in score.parts["melody"].notes:
print(note.tone, note.beats)
score.save_midi("hum.mid") # finish it in your DAW
print(score.to_abc(title="My Hum")) # or print the sheet music
Pitch tracking uses the YIN algorithm — the same approach as most monophonic tuners — implemented in pure numpy. Transcription is monophonic: one note at a time (voice, whistle, a single instrument line), not chords. Record melody and bass as separate takes.
For full mixes, pass split=True and you get a full arrangement
sketch back — four parts plus the key:
score = Score.from_wav("song.wav", split=True, quantize=0.5)
score.parts["melody"] # the lead line
score.parts["bass"] # the bassline
score.parts["chords"] # chord symbols (Am, F, C, G...)
score.parts["drums"] # kick / snare / hat hits
score.detected_key # e.g. <Key C major>
play_score(score) # an instant cover version
Under the hood: harmonic-percussive separation splits sustained
pitched material from drums (held notes are horizontal lines on a
spectrogram, drum hits vertical ones — median filters tell them
apart). The harmonic stem gets band-split YIN passes for bass and
melody, plus a chromagram — the spectrum folded into 12 pitch
classes — matched against major/minor triad templates per chord
window. The percussive stem gets onset detection with each hit
classified as kick, snare, or hat by where its energy lives. The
key falls out of everything pitched via Key.detect().
What to expect: chords and bassline come out well (a rendered Am-F-C-G test mix comes back exactly), the melody as well as it dominates the mix, and the drum groove reads correctly even when individual edge hits get fuzzy. It’s a sketch to edit, not a record deal — but it turns “what is this song?” into an editable Score in one line.
Tempo is estimated automatically when you don’t pass bpm= — the
onset pattern is autocorrelated to find the pulse (a rendered groove
comes back exact; pulse-free rubato humming falls back to 120).
Tips for good transcriptions:
Tighten the pitch range —
fmin=60, fmax=350for a bass line,fmin=150, fmax=1200for whistling. Less range to search means fewer octave mistakes.Raise the floor above vocal fry — voice recordings often carry low creaky artifacts at note starts and ends that transcribe as quiet sub-bass blips. If you see suspiciously low, low-velocity notes at phrase boundaries, set
fmin=100(or higher) and they disappear.Pin the tempo if you know it — auto-estimation reads the pulse from the onsets, but if you recorded against a metronome, pass that
bpm=and remove the guesswork.Quantize when you want a clean chart —
quantize=0.25snaps to sixteenths; leave it off to keep the timing as performed.
Phone voice memos (.m4a), mp3s, and other formats load directly —
they’re converted on the fly through afconvert (built into
macOS) or ffmpeg, whichever is on your PATH.
There’s a CLI command too:
$ pytheory transcribe hum.m4a hum.mid --quantize 0.25
$ pytheory transcribe bass.wav --fmin 60 --fmax 350 --play
$ pytheory transcribe song.wav --split
See the Cookbook for the full voice-memo-to-sheet-music recipe.
PyTheory Studio — the Browser Front Door¶
All of the above, without writing any code:
$ pytheory studio
opens a local web app: drop in a recording (.wav, voice memo, .mp3) and the transcription renders as sheet music right on the page (via abcjs). Press play to hear it through PyTheory’s synths, download the MIDI for your DAW, and there’s a tuner at the bottom. Check “full mix” for the four-part bass/melody/chords/drums split.
Everything runs on your machine — the only thing fetched from the internet is the notation renderer. Your audio never leaves localhost.