Files
pytheory/docs/guide/cookbook.rst
kennethreitz 33b0636cc4 docs: comprehensive refresh for v0.57.x + sphinx-design hero
A full freshness + quality pass across the documentation, with every code
example verified by actually running it against v0.57.8.

Content / accuracy:
- Fixed stale and broken examples throughout: SymPy-based "symbolic pitch"
  (SymPy was removed), the case-sensitive progression parser (I/V/vi/IV vs
  i/iv), analog_drift -> analog, Key.relative/parallel returning Key objects,
  Tone.from_string validation needing a system, stale tab()/scale_diagram()
  output, and many counts (74 drum sounds, 100 patterns, 37 fills, 19 chord
  types, 16 systems, 25 fretboard instruments, 56 waveforms, 83 presets).
- Documented major recent features that were missing: Maqam (quarter-tone
  Arabic maqamat) and Raga (Hindustani + Carnatic, shruti just intonation)
  in the systems guide and the CLI; SVG/PNG diagram export; progression /
  cadence / secondary-dominant analysis and reharmonization; notation export
  to LilyPond/MusicXML/ABC incl. lyrics; from_wav transcription; render_scores
  batch rendering; CLI raga/maqam and `analyze song.mid`.

Navigation / presentation:
- Added sphinx-design + sphinx-copybutton to the docs deps and conf.
- Rebuilt the homepage "Why would I want this?" into a sphinx-design persona
  card grid with CTA buttons and a badge row; wired the brand logo into the
  sidebar (and suppressed the now-redundant project-name text).
- Added API-reference pages for the public Raga and Maqam classes.

Also: removed a duplicate `jati` attribute line from the Raga docstring so
autodoc no longer double-documents it.

Sphinx build is clean (0 warnings).
2026-06-28 18:12:45 -04:00

766 lines
22 KiB
ReStructuredText

Cookbook
========
You know what you want to do; you just want the code. Each recipe
below is ready to paste into a Python session — grab one, run it, and
adapt it. The composition recipes build on a shared set of imports,
shown at the top of that section.
.. contents:: On this page
:local:
:depth: 2
Harmony and Analysis
--------------------
Analyze a Song
~~~~~~~~~~~~~~
Take the chord progression from "Let It Be" (C G Am F) and analyze it
in the key of C major:
.. code-block:: pycon
>>> from pytheory import Chord, Key
>>> C = Chord.from_name("C")
>>> G = Chord.from_name("G")
>>> Am = Chord.from_name("Am")
>>> F = Chord.from_name("F")
>>> [c.identify() for c in [C, G, Am, F]]
['C major', 'G major', 'A minor', 'F major']
>>> [c.analyze("C") for c in [C, G, Am, F]]
['I', 'V', 'vi', 'IV']
>>> key = Key("C", "major")
>>> [c.identify() for c in key.progression("I", "V", "vi", "IV")]
['C major', 'G major', 'A minor', 'F major']
Write a 12-Bar Blues
~~~~~~~~~~~~~~~~~~~~
The `12-bar blues <https://en.wikipedia.org/wiki/Twelve-bar_blues>`_ is
built from the I, IV, and V chords. Here it is in the key of A:
.. code-block:: pycon
>>> from pytheory import Key, Chord
>>> key = Key("A", "major")
>>> [c.identify() for c in key.progression("I", "IV", "V")]
['A major', 'D major', 'E major']
>>> bars = ["I","I","I","I", "IV","IV","I","I", "V","IV","I","V"]
>>> [c.identify() for c in key.progression(*bars)]
['A major', 'A major', 'A major', 'A major', 'D major', 'D major', 'A major', 'A major', 'E major', 'D major', 'A major', 'E major']
>>> Chord.from_name("A7").identify()
'A dominant 7th'
>>> Chord.from_name("D7").identify()
'D dominant 7th'
>>> Chord.from_name("E7").identify()
'E dominant 7th'
Find Chords in a Key
~~~~~~~~~~~~~~~~~~~~
The :class:`~pytheory.scales.Key` class builds diatonic chords for any
key and lets you pull progressions by Roman numeral or Nashville number:
.. code-block:: pycon
>>> from pytheory import Key
>>> key = Key("G", "major")
>>> key.chords
['G major', 'A minor', 'B minor', 'C major', 'D major', 'E minor', 'F# diminished']
>>> [c.identify() for c in key.progression("I", "V", "vi", "IV")]
['G major', 'D major', 'E minor', 'C major']
>>> [c.identify() for c in key.nashville(1, 5, 6, 4)]
['G major', 'D major', 'E minor', 'C major']
Voice Leading Between Chords
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Find the smoothest path from one chord to the next — each voice moves
the minimum distance:
.. code-block:: pycon
>>> from pytheory import Chord
>>> c_maj = Chord.from_tones("C", "E", "G")
>>> f_maj = Chord.from_tones("F", "A", "C")
>>> for src, dst, motion in c_maj.voice_leading(f_maj):
... print(f"{src} -> {dst} ({motion:+d} semitones)")
G4 -> A4 (+2 semitones)
E4 -> F4 (+1 semitones)
C4 -> C4 (+0 semitones)
Measure Harmonic Tension
~~~~~~~~~~~~~~~~~~~~~~~~
Quantify how much a chord "wants to resolve." Dominant 7ths have
the most tension — the tritone between the 3rd and 7th pulls toward
resolution:
.. code-block:: pycon
>>> from pytheory import Chord
>>> for name in ["C", "Am", "G7", "Cmaj7"]:
... ch = Chord.from_name(name)
... t = ch.tension
... print(f"{name:6s} tension={t['score']:.2f} tritones={t['tritones']} dominant={t['has_dominant_function']}")
C tension=0.00 tritones=0 dominant=False
Am tension=0.00 tritones=0 dominant=False
G7 tension=0.60 tritones=1 dominant=True
Cmaj7 tension=0.15 tritones=0 dominant=False
Tritone Substitution (Jazz)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Replace any dominant chord with the one a
`tritone <https://en.wikipedia.org/wiki/Tritone_substitution>`_ away —
they share the same tritone interval:
.. code-block:: pycon
>>> from pytheory import Chord
>>> g7 = Chord.from_name("G7")
>>> g7.tritone_sub().identify()
'C# dominant 7th'
>>> # ii-V-I with tritone sub:
>>> # Dm7 -> G7 -> Cmaj7 (standard)
>>> # Dm7 -> Db7 -> Cmaj7 (chromatic bass line!)
Borrowed Chords and Secondary Dominants
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Add color by borrowing from the parallel key or building secondary
dominants that approach other scale degrees:
.. code-block:: pycon
>>> from pytheory import Key
>>> c = Key("C", "major")
>>> c.borrowed_chords[:4]
['C minor', 'D diminished', 'Eb major', 'F minor']
>>> c.secondary_dominant(5).identify()
'D dominant 7th'
>>> c.secondary_dominant(2).identify()
'A dominant 7th'
>>> c.secondary_dominant(6).identify()
'E dominant 7th'
Scales and Keys
---------------
Compare Scales
~~~~~~~~~~~~~~
Play the same tonic through different scales to hear how each mode
reshapes the palette. The western modes share the same notes but start
on different degrees; the blues scale adds the "blue note" (flat 5th):
.. code-block:: pycon
>>> from pytheory import TonedScale
>>> c = TonedScale(tonic="C4")
>>> c["major"].note_names
['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']
>>> c["minor"].note_names
['C', 'D', 'Eb', 'F', 'G', 'Ab', 'Bb', 'C']
>>> c["dorian"].note_names
['C', 'D', 'Eb', 'F', 'G', 'A', 'Bb', 'C']
>>> c["mixolydian"].note_names
['C', 'D', 'E', 'F', 'G', 'A', 'Bb', 'C']
>>> c_blues = TonedScale(tonic="C4", system="blues")
>>> c_blues["blues"].note_names
['C', 'Eb', 'F', 'Gb', 'G', 'Bb', 'C']
Key Signatures and Detection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
View the accidentals in any key, or detect the key from a set of notes:
.. code-block:: pycon
>>> from pytheory import Key
>>> Key("C", "major").signature
{'sharps': 0, 'flats': 0, 'accidentals': []}
>>> Key("G", "major").signature
{'sharps': 1, 'flats': 0, 'accidentals': ['F#']}
>>> Key("D", "major").signature
{'sharps': 2, 'flats': 0, 'accidentals': ['F#', 'C#']}
>>> Key.detect("C", "E", "G", "A", "D")
<Key C major>
Relative and Parallel Keys
~~~~~~~~~~~~~~~~~~~~~~~~~~
Every major key has a **relative minor** (same notes, different root)
and a **parallel minor** (same root, different notes). Both come back
as :class:`~pytheory.scales.Key` objects, so you can keep working with
them:
.. code-block:: pycon
>>> from pytheory import Key
>>> c = Key("C", "major")
>>> c.relative
<Key A minor>
>>> c.parallel
<Key C minor>
>>> c.relative.chords[:4]
['A minor', 'B diminished', 'C major', 'D minor']
World Scales
~~~~~~~~~~~~
Explore scales from Indian, Arabic, and Japanese traditions:
.. code-block:: pycon
>>> from pytheory import TonedScale
>>> indian = TonedScale(tonic="Sa", system="indian")
>>> indian["bhairav"].note_names
['Sa', 'komal Re', 'Ga', 'Ma', 'Pa', 'komal Dha', 'Ni', 'Sa']
>>> arabic = TonedScale(tonic="Do", system="arabic")
>>> arabic["hijaz"].note_names
['Do', 'Reb', 'Mi', 'Fa', 'Sol', 'Solb', 'Sib', 'Do']
>>> japanese = TonedScale(tonic="C4", system="japanese")
>>> japanese["hirajoshi"].note_names
['C', 'D', 'Eb', 'G', 'Ab', 'C']
Those are 12-TET approximations — what a piano can play. For the real
thing, reach for the dedicated :class:`~pytheory.maqam.Maqam` (Arabic,
true quarter tones) and :class:`~pytheory.ragas.Raga` (Indian, shruti
just intonation) classes:
.. code-block:: pycon
>>> from pytheory import Maqam, Raga
>>> rast = Maqam.get("rast")
>>> rast.degree_names() # ↓ marks the quarter-flat notes
['Do', 'Re', 'Mi↓', 'Fa', 'Sol', 'La', 'Si↓']
>>> rast.note_names("C") # nearest 12-TET names
['C', 'D', 'E', 'F', 'G', 'A', 'Bb']
>>> bhairav = Raga.get("bhairav")
>>> bhairav.note_names("C")
['C', 'C#', 'E', 'F', 'G', 'G#', 'B']
>>> bhairav.aroha_swaras() # ascending line
['S', 'r', 'G', 'm', 'P', 'd', 'N', "S'"]
Both classes carry the real tuning: ``rast.maqam_table("C")`` lists each
degree's just ratio and how many cents it sits off the piano, and
``rast.play(tonic="C4")`` / ``bhairav.play(sa="C4")`` sound them out with
the quarter tones and shrutis intact. See :doc:`systems` for the full
catalogue of maqamat, ragas, and tuning systems.
Guitar
------
Guitar Chord Chart
~~~~~~~~~~~~~~~~~~
Generate fingerings for guitar and ukulele with
:class:`~pytheory.tones.Fretboard`:
.. code-block:: pycon
>>> from pytheory import Fretboard
>>> fb = Fretboard.guitar()
>>> fb.chord("C")
Fingering(E=x, A=3, D=2, G=0, B=1, e=0)
>>> fb.chord("G")
Fingering(E=3, A=2, D=0, G=0, B=0, e=3)
>>> fb.chord("Am")
Fingering(E=x, A=0, D=2, G=2, B=1, e=0)
>>> fb.chord("D")
Fingering(E=x, A=x, D=0, G=2, B=3, e=2)
>>> uke = Fretboard.ukulele()
>>> uke.chord("C")
Fingering(G=0, C=0, E=0, A=3)
>>> uke.chord("G")
Fingering(G=0, C=2, E=3, A=2)
Visualize a Scale on Guitar
~~~~~~~~~~~~~~~~~~~~~~~~~~~
See where the notes fall across the fretboard — E minor pentatonic,
the most-played scale in rock:
.. code-block:: pycon
>>> from pytheory import Fretboard, Scale
>>> fb = Fretboard.guitar()
>>> pent = Scale(tonic="E4", system="blues")["minor pentatonic"]
>>> print(fb.scale_diagram(pent, frets=12))
0 1 2 3 4 5 6 7 8 9 10 11 12
E| E | - | - | G | - | A | - | B | - | - | D | - | E |
A| A | - | B | - | - | D | - | E | - | - | G | - | A |
D| D | - | E | - | - | G | - | A | - | B | - | - | D |
G| G | - | A | - | B | - | - | D | - | E | - | - | G |
B| B | - | - | D | - | E | - | - | G | - | A | - | B |
E| E | - | - | G | - | A | - | B | - | - | D | - | E |
Tones and Physics
-----------------
Explore an Interval
~~~~~~~~~~~~~~~~~~~
Start from A4 (440 Hz) and walk through intervals, checking names and
frequency ratios:
.. code-block:: pycon
>>> from pytheory import Tone
>>> a4 = Tone.from_string("A4", system="western")
>>> a4.frequency
440.0
>>> minor_3rd = a4 + 3
>>> a4.interval_to(minor_3rd)
'minor 3rd'
>>> p5 = a4 + 7
>>> a4.interval_to(p5)
'perfect 5th'
>>> round(p5.frequency / a4.frequency, 4)
1.4983
>>> octave = a4 + 12
>>> a4.interval_to(octave)
'octave'
>>> round(octave.frequency / a4.frequency, 4)
2.0
Walk the Circle of Fifths
~~~~~~~~~~~~~~~~~~~~~~~~~
The `circle of fifths <https://en.wikipedia.org/wiki/Circle_of_fifths>`_
is the backbone of Western harmony — each step adds one sharp or flat:
.. code-block:: pycon
>>> from pytheory import Tone
>>> c = Tone.from_string("C4", system="western")
>>> [t.name for t in c.circle_of_fifths()]
['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'D#', 'A#', 'F']
>>> g = Tone.from_string("G4", system="western")
>>> [t.name for t in g.circle_of_fifths()]
['G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'D#', 'A#', 'F', 'C']
The Overtone Series
~~~~~~~~~~~~~~~~~~~
Every musical tone contains a stack of harmonics — the physics behind
why intervals sound consonant:
.. code-block:: pycon
>>> from pytheory import Tone
>>> a4 = Tone.from_string("A4", system="western")
>>> [round(f, 1) for f in a4.overtones(6)]
[440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0]
>>> # Harmonic 2 = octave (2:1)
>>> # Harmonic 3 = perfect 5th + octave (3:1)
>>> # Harmonic 5 = major 3rd + two octaves (5:1)
Enharmonic Spellings
~~~~~~~~~~~~~~~~~~~~
Find the alternate name for any sharp or flat:
.. code-block:: pycon
>>> from pytheory import Tone
>>> for name in ["C#4", "D#4", "F#4", "G#4"]:
... t = Tone.from_string(name, system="western")
... print(f"{t.name} = {t.enharmonic}")
C# = Db
D# = Eb
F# = Gb
G# = Ab
Composition Recipes
-------------------
These recipes go beyond theory into actual music-making. They all share
the same handful of imports:
.. code-block:: python
from pytheory import Score, Pattern, Duration, Chord, Key
from pytheory.play import play_score
Acid House Track
~~~~~~~~~~~~~~~~
303-style acid with sidechain pump:
.. code-block:: python
score = Score("4/4", bpm=132)
score.drums("house", repeats=8, fill="house", fill_every=8)
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
reverb=0.4,
chorus=0.3,
sidechain=0.85,
)
acid = score.part(
"acid",
synth="saw",
envelope="pad",
legato=True,
glide=0.03,
distortion=0.8,
distortion_drive=8.0,
lowpass=1000,
lowpass_q=5.0,
)
acid.lfo("lowpass", rate=0.5, min=600, max=2500, bars=8)
for sym in ["Cm", "Fm", "Abm", "Gm"]:
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
pad.add(Chord.from_symbol(sym), Duration.WHOLE)
acid.arpeggio(sym, bars=2, pattern="up", octaves=2)
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sparse notes into infinite echo:
.. code-block:: python
score = Score("4/4", bpm=72)
score.drums("dub", repeats=8)
melodica = score.part(
"melodica",
synth="triangle",
envelope="pluck",
delay=0.5,
delay_time=0.66,
delay_feedback=0.55,
reverb=0.4,
reverb_type="cathedral",
)
bass = score.part("bass", synth="sine", lowpass=400, lowpass_q=1.5)
# Play almost nothing — let the delay do the work
melodica.add("A4", 2).rest(6)
melodica.add("E5", 1.5).rest(6.5)
melodica.add("D5", 1).add("C5", 1).add("A4", 2).rest(4)
for n in ["A1"] * 16:
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/dub_reggae.wav" type="audio/wav"></audio>
Jazz Ballad with Humanize
~~~~~~~~~~~~~~~~~~~~~~~~~
The difference between a robot and a musician:
.. code-block:: python
score = Score("4/4", bpm=72, swing=0.5)
score.drums("jazz", repeats=8)
rhodes = score.part(
"rhodes",
synth="fm",
envelope="piano",
reverb=0.4,
reverb_type="plate",
humanize=0.3,
)
lead = score.part(
"lead",
synth="triangle",
envelope="strings",
delay=0.25,
reverb=0.3,
humanize=0.35,
)
key = Key("Bb", "major")
for chord in key.progression("I", "vi", "ii", "V") * 2:
rhodes.add(chord, Duration.WHOLE)
for n, d in [("D5", 1.5), ("F5", 0.5), ("Bb5", 2), (None, 4),
("A5", 1), ("G5", 1), ("F5", 2), (None, 4)]:
lead.rest(d) if n is None else lead.add(n, d)
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
~~~~~~~~~~~~~~~~~~
Define once, arrange freely:
.. code-block:: python
score = Score("4/4", bpm=120)
score.drums("rock", repeats=16, fill="rock", fill_every=4)
chords = score.part("chords", synth="saw", envelope="pad")
lead = score.part("lead", synth="triangle", envelope="pluck")
score.section("verse")
for sym in ["Am", "F", "C", "G"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
lead.add("A4", 1).add("C5", 1).add("E5", 1).rest(1)
lead.add("F5", 1).add("E5", 1).add("C5", 2)
score.section("chorus")
lead.set(reverb=0.4, lowpass=5000)
for sym in ["F", "G", "Am", "C"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
lead.add("C6", 2).add("A5", 1).add("G5", 1)
lead.add("F5", 2).add("E5", 2)
score.end_section()
score.repeat("verse")
score.repeat("chorus", times=2)
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>
Lead Sheet with Lyrics
~~~~~~~~~~~~~~~~~~~~~~~
Attach words to a vocal line with the ``lyric=`` argument — one syllable
per sounding note — and they flow straight into the notation. Here a
melody becomes an ABC lead sheet you can paste into any ABC viewer:
.. code-block:: pycon
>>> from pytheory import Score, Duration
>>> score = Score("4/4", bpm=120)
>>> vox = score.part("vocal", synth="sine", envelope="pad")
>>> words = [("Hap-", "C5"), ("py", "C5"), ("birth-", "D5"),
... ("day", "C5"), ("to", "F5"), ("you", "E5")]
>>> for syllable, note in words:
... vox.add(note, Duration.QUARTER, lyric=syllable)
>>> print(score.to_abc(title="Birthday", key="C"))
X:1
T:Birthday
M:4/4
Q:1/4=120
L:1/8
K:C
c2 c2 d2 c2 | f2 e2 |
w: Hap\- py birth\- day | to you
The same lyrics render to LilyPond (``score.to_lilypond(...)``) and
MusicXML (``score.to_musicxml(...)``) too, so you can open the lead
sheet in MuseScore, Sibelius, or Finale.
Shape the *performance* with ``articulation=````"staccato"``,
``"accent"``, ``"marcato"``, ``"tenuto"``, or ``"fermata"``. Each one
changes how the note is played *and* prints the matching mark in the
score:
.. code-block:: python
lead = score.part("lead", synth="triangle")
lead.add("C5", Duration.QUARTER, articulation="staccato")
lead.add("E5", Duration.QUARTER, articulation="tenuto")
lead.add("G5", Duration.HALF, articulation="fermata")
See :doc:`playback` and :doc:`effects` for the full performance toolkit.
Export Everything to MIDI
~~~~~~~~~~~~~~~~~~~~~~~~~
The whole point — sketch fast, finish in your DAW:
.. code-block:: python
# Any Score can be saved as MIDI
score.save_midi("track.mid")
# Simple progressions too
from pytheory import save_midi
chords = Key("C", "major").progression("I", "V", "vi", "IV")
save_midi(chords, "pop.mid", t=500, bpm=120)
# ...and read MIDI back in — same-onset notes regroup into chords
from pytheory import Score
sketch = Score.from_midi("track.mid", synth="sine", envelope="pluck")
Already have a MIDI file you want to understand? The CLI detects the key
and prints a Roman-numeral chord timeline (add ``--json`` to pipe it
elsewhere):
.. code-block:: console
$ pytheory analyze song.mid
Listening Recipes
-----------------
These start from a recording instead of code — see :doc:`listening`
for the full guide.
Voice Memo → Sheet Music
~~~~~~~~~~~~~~~~~~~~~~~~
You hummed something into your phone and want it on a staff. The
whole pipeline — recording to engraved PDF:
.. code-block:: python
# 1. Transcribe it — .m4a voice memos load directly
from pytheory import Score
score = Score.from_wav(
"hum.m4a",
quantize=0.25, # snap to sixteenths for a clean chart
fmin=100, # floor above vocal fry — kills the creaky
fmax=350, # sub-bass blips at note starts and ends
) # tempo is estimated; pass bpm= to pin it
# 2. Sanity-check what it heard
print(f"estimated tempo: {score.bpm}")
for n in score.parts["melody"].notes:
print(n.tone or "rest", n.beats)
# 3. Engrave it — three formats, pick the tool you live in
with open("hum.ly", "w") as f: # LilyPond → engraved PDF
f.write(score.to_lilypond(title="My Hum", key="G", mode="major"))
with open("hum.musicxml", "w") as f: # opens in MuseScore/Sibelius/Finale
f.write(score.to_musicxml(title="My Hum"))
with open("hum.abc", "w") as f: # plain-text, easy to paste and share
f.write(score.to_abc(title="My Hum", key="G"))
# ...and keep the MIDI for your DAW while you're at it
score.save_midi("hum.mid")
.. code-block:: bash
# 4. Compile to PDF (brew install lilypond)
lilypond --pdf hum.ly
A real hum is messy — breath noise, fry, scoops between notes. If
the transcription has stray notes, the fix is almost always the
pitch range: print the detected notes, find the artifacts (very low,
very quiet, at phrase edges), and tighten ``fmin``/``fmax`` around
the actual melody. Two or three runs gets it clean.
What Chords Is This Song Playing?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You're trying to learn a song by ear. Let the chromagram do the
listening — it reads the chord progression (including 7ths, sus
chords, and inversions like ``C/E``) straight off the audio:
.. code-block:: python
from pytheory.audio import load_wav, detect_chords, estimate_tempo
samples, sr = load_wav("song.m4a")
bpm = estimate_tempo(samples, sr) or 120
for start, dur, symbol in detect_chords(samples, sr, bpm=bpm):
print(f"beat {start:5g} {symbol:8s} ({dur:g} beats)")
Or get the whole arrangement — chords plus melody, bassline, drums,
and the key — as an editable Score, and hear PyTheory's cover of it:
.. code-block:: python
from pytheory import Score
from pytheory.play import play_score
score = Score.from_wav("song.m4a", split=True, quantize=0.5)
print(score.detected_key) # e.g. <Key C major>
play_score(score) # instant cover version
Playing Live
------------
Jam in Sync with Ableton Link
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Someone in the room is running Ableton Live (or an iOS drum app, or
anything else that speaks `Ableton Link <https://www.ableton.com/link/>`_).
Join their session — tempo, beat grid, and transport sync
automatically, peer-to-peer (requires ``pip install "pytheory[link]"``):
.. code-block:: python
from pytheory.live import LiveEngine
engine = LiveEngine()
engine.channel(1, instrument="electric_piano", reverb=0.3)
engine.channel(2, instrument="synth_bass", lowpass=800)
engine.drums("house", volume=0.5) # locks to the shared beat grid
engine.enable_link() # find the session on the network
engine.start() # play your MIDI keyboard in time
Or as one command, with the TUI dashboard::
$ pytheory live --link
These are all starting points. Change the key, swap the chords, layer in your own ideas -- the best way to learn is to take something that works and make it yours.