mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
Rewrite quickstart, update CLI docs, add composition recipes to cookbook
Quickstart: zero to arrangement in 5 minutes, pytheory demo, MIDI export. CLI: add demo command docs at the top. Cookbook: acid house, dub reggae, jazz ballad, song sections, MIDI export recipes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-1
@@ -1,7 +1,20 @@
|
||||
Command-Line Interface
|
||||
======================
|
||||
|
||||
PyTheory includes a CLI for quick music theory lookups from the terminal.
|
||||
PyTheory includes a CLI for music theory lookups, composition, and
|
||||
playback — all from the terminal.
|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
The fastest way to hear what PyTheory can do. Generates and plays a
|
||||
random multi-part track — different every time::
|
||||
|
||||
$ pytheory demo
|
||||
♫ Jazz Club
|
||||
Bb major | 105 bpm
|
||||
Bb → Gm → Cm → F
|
||||
jazz drums | saw lead | fm pad
|
||||
|
||||
Tone Lookup
|
||||
-----------
|
||||
|
||||
@@ -364,3 +364,166 @@ the most-played scale in rock:
|
||||
D| D | - | E | - | - | G | - | A | - | B | - | - | D |
|
||||
A| A | - | B | - | - | D | - | E | - | - | G | - | A |
|
||||
E| E | - | - | G | - | A | - | B | - | - | D | - | E |
|
||||
|
||||
Composition Recipes
|
||||
-------------------
|
||||
|
||||
These recipes go beyond theory into actual music-making.
|
||||
|
||||
Acid House Track
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
303-style acid with sidechain pump:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import Score, Pattern, Duration, Chord
|
||||
from pytheory.play import play_score
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
+119
-155
@@ -1,6 +1,8 @@
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
From zero to a multi-part arrangement in 5 minutes.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
@@ -8,200 +10,162 @@ Installation
|
||||
|
||||
$ pip install pytheory
|
||||
|
||||
For audio playback, you'll also need `PortAudio <http://www.portaudio.com/>`_:
|
||||
For audio playback through your speakers, you'll also need
|
||||
`PortAudio <http://www.portaudio.com/>`_:
|
||||
|
||||
- macOS: ``brew install portaudio``
|
||||
- Ubuntu: ``apt install libportaudio2``
|
||||
- Windows: included with the ``sounddevice`` package
|
||||
|
||||
Tones
|
||||
-----
|
||||
PortAudio is only needed for live playback. MIDI export, WAV export,
|
||||
and all theory functions work without it.
|
||||
|
||||
A :class:`~pytheory.tones.Tone` is a single musical note:
|
||||
Hear Something Immediately
|
||||
--------------------------
|
||||
|
||||
::
|
||||
|
||||
$ pytheory demo
|
||||
|
||||
This generates and plays a random track — different every time. It's
|
||||
the fastest way to hear what PyTheory can do.
|
||||
|
||||
Explore Music Theory
|
||||
--------------------
|
||||
|
||||
The theory layer is where most people start. Every concept in Western
|
||||
music theory (and five other systems) has a clean Python API:
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Tone
|
||||
>>> from pytheory import Key, Chord, Tone
|
||||
|
||||
>>> a4 = Tone.from_string("A4", system="western")
|
||||
>>> a4.frequency
|
||||
440.0
|
||||
>>> key = Key("C", "major")
|
||||
>>> key.chords
|
||||
['C major', 'D minor', 'E minor', 'F major', 'G major', 'A minor', 'B diminished']
|
||||
|
||||
>>> c4 = Tone.from_string("C4", system="western")
|
||||
>>> c4.midi
|
||||
60
|
||||
>>> [c.symbol for c in key.progression("I", "V", "vi", "IV")]
|
||||
['C', 'G', 'Am', 'F']
|
||||
|
||||
>>> Tone.from_frequency(440)
|
||||
<Tone A4>
|
||||
>>> Tone.from_midi(60)
|
||||
<Tone C4>
|
||||
>>> Chord.from_symbol("Am7").identify()
|
||||
'A minor 7th'
|
||||
|
||||
>>> c4 + 4
|
||||
<Tone E4>
|
||||
>>> c4 + 7
|
||||
<Tone G4>
|
||||
|
||||
>>> g4 = c4 + 7
|
||||
>>> g4 - c4
|
||||
7
|
||||
>>> c4.interval_to(g4)
|
||||
>>> Tone.from_string("C4").interval_to(Tone.from_string("G4"))
|
||||
'perfect 5th'
|
||||
|
||||
>>> Tone.from_string("C#4", system="western").enharmonic
|
||||
'Db'
|
||||
>>> Key("C", "major").pivot_chords(Key("G", "major"))
|
||||
['A minor', 'B minor', 'C major', 'D major', 'E minor', 'G major']
|
||||
|
||||
Scales
|
||||
------
|
||||
|
||||
Build scales in any key and mode:
|
||||
|
||||
.. 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']
|
||||
|
||||
>>> major = c["major"]
|
||||
>>> major["tonic"]
|
||||
C4
|
||||
>>> major["dominant"]
|
||||
G4
|
||||
>>> major["V"]
|
||||
G4
|
||||
|
||||
Keys and Chords
|
||||
Compose a Track
|
||||
---------------
|
||||
|
||||
The :class:`~pytheory.scales.Key` class ties everything together —
|
||||
scales, chords, and progressions:
|
||||
This is where it gets fun. A ``Score`` is your arrangement — drums,
|
||||
chords, melody, bass, each with their own synth and effects:
|
||||
|
||||
.. code-block:: pycon
|
||||
.. code-block:: python
|
||||
|
||||
>>> from pytheory import Key
|
||||
from pytheory import Score, Pattern, Key, Duration, Chord
|
||||
from pytheory.play import play_score
|
||||
|
||||
>>> key = Key("G", "major")
|
||||
>>> key.note_names
|
||||
['G', 'A', 'B', 'C', 'D', 'E', 'F#', 'G']
|
||||
score = Score("4/4", bpm=140)
|
||||
score.drums("bossa nova", repeats=4)
|
||||
|
||||
>>> key.chords
|
||||
['G major', 'A minor', 'B minor', 'C major', 'D major', 'E minor', 'F# diminished']
|
||||
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,
|
||||
)
|
||||
|
||||
>>> chords = key.progression("I", "V", "vi", "IV")
|
||||
>>> [c.identify() for c in chords]
|
||||
['G major', 'D major', 'E minor', 'C major']
|
||||
key = Key("A", "minor")
|
||||
for chord in key.progression("i", "iv", "V", "i"):
|
||||
chords.add(chord, Duration.WHOLE)
|
||||
chords.add(chord, Duration.WHOLE)
|
||||
|
||||
>>> Key.detect("C", "E", "G", "A", "D")
|
||||
<Key C major>
|
||||
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)
|
||||
|
||||
Build chords directly:
|
||||
for n in ["A2", "E2", "A2", "C3"] * 4:
|
||||
bass.add(n, Duration.QUARTER)
|
||||
|
||||
.. code-block:: pycon
|
||||
play_score(score)
|
||||
|
||||
>>> from pytheory import Chord
|
||||
Export to Your DAW
|
||||
------------------
|
||||
|
||||
>>> Chord.from_tones("C", "E", "G")
|
||||
<Chord C major>
|
||||
>>> Chord.from_name("Am7")
|
||||
<Chord A minor 7th>
|
||||
>>> Chord.from_intervals("G", 4, 7, 10)
|
||||
<Chord G dominant 7th>
|
||||
The whole point: sketch in Python, finish in Logic / Ableton / Reaper.
|
||||
|
||||
>>> Chord.from_tones("Bb", "D", "F").identify()
|
||||
'Bb major'
|
||||
.. code-block:: python
|
||||
|
||||
>>> Chord.from_name("G7").analyze("C")
|
||||
'V7'
|
||||
score.save_midi("my_sketch.mid")
|
||||
|
||||
Guitar Fingerings
|
||||
Open that file in any DAW and you'll see all the notes laid out on
|
||||
the timeline, ready to assign to real instruments and mix.
|
||||
|
||||
You can also save rendered audio:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pytheory import save
|
||||
save(Chord.from_symbol("Am7"), "am7.wav", t=2_000)
|
||||
|
||||
What's in the Box
|
||||
-----------------
|
||||
|
||||
.. code-block:: pycon
|
||||
**Theory** — tones, scales (40+ across 6 musical systems), chords
|
||||
(17 types, Roman numeral analysis, tension scoring, voice leading),
|
||||
keys (detection, signatures, modulation paths, borrowed chords).
|
||||
|
||||
>>> from pytheory import Fretboard
|
||||
**Sequencing** — Score, Part, Duration, TimeSignature. Arpeggiator
|
||||
with 5 patterns. Legato with pitch glide. Per-note velocity. Swing.
|
||||
Tempo changes. Fade in/out. Song sections with repeat. Humanize.
|
||||
|
||||
>>> fb = Fretboard.guitar()
|
||||
**Synthesis** — 10 waveforms: sine, saw, triangle, square, pulse, FM,
|
||||
noise, supersaw, PWM slow, PWM fast. 8 ADSR envelopes.
|
||||
|
||||
>>> fb.chord("C")
|
||||
Fingering(e=0, B=1, G=0, D=2, A=3, E=x)
|
||||
**Effects** — distortion, chorus, lowpass filter (with resonance),
|
||||
delay, reverb (algorithmic + 7 convolution presets including
|
||||
Taj Mahal with 12-second tail). All per-part with automation and
|
||||
LFO modulation. Sidechain compression.
|
||||
|
||||
>>> fb.chord("C")['A']
|
||||
3
|
||||
**Drums** — 58 pattern presets (rock, jazz, salsa, bossa nova,
|
||||
afrobeat, house, trap, and 50+ more). 21 fill presets. 27 synthesized
|
||||
drum voices.
|
||||
|
||||
>>> fb.fingering(0, 0, 0, 2, 2, 0).identify()
|
||||
'E minor'
|
||||
**Instruments** — 25 presets (guitar with 8 tunings, bass, ukulele,
|
||||
mandolin family, violin family, banjo, harp, oud, sitar, erhu, and
|
||||
more) with chord fingering generation and scale diagrams.
|
||||
|
||||
>>> print(fb.tab("Am"))
|
||||
A minor
|
||||
e|--0--
|
||||
B|--1--
|
||||
G|--2--
|
||||
D|--2--
|
||||
A|--0--
|
||||
E|--x--
|
||||
**Export** — MIDI, WAV, real-time playback.
|
||||
|
||||
>>> from pytheory import Scale
|
||||
>>> pentatonic = Scale(tonic="A4", system="blues")["minor pentatonic"]
|
||||
>>> print(fb.scale_diagram(pentatonic, frets=5))
|
||||
0 1 2 3 4 5
|
||||
E| E | - | - | G | - | A |
|
||||
B| - | C | - | D | - | E |
|
||||
G| G | - | A | - | - | C |
|
||||
D| D | - | E | - | - | G |
|
||||
A| A | - | - | C | - | D |
|
||||
E| E | - | - | G | - | A |
|
||||
**CLI** — ``pytheory demo``, ``pytheory key``, ``pytheory chord``,
|
||||
``pytheory identify``, ``pytheory midi``, ``pytheory play``, and more.
|
||||
|
||||
Audio Playback
|
||||
--------------
|
||||
Where to Go Next
|
||||
-----------------
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> from pytheory import Tone, Chord, play, save, Synth
|
||||
|
||||
>>> play(Tone.from_string("A4"), t=1_000)
|
||||
|
||||
>>> play(Chord.from_name("Am7"), synth=Synth.TRIANGLE, t=2_000)
|
||||
|
||||
>>> save(Chord.from_name("C"), "c_major.wav", t=2_000)
|
||||
|
||||
Command Line
|
||||
------------
|
||||
|
||||
PyTheory also works from the terminal::
|
||||
|
||||
$ pytheory tone A4
|
||||
$ pytheory chord C E G
|
||||
$ pytheory key G major
|
||||
$ pytheory scale C dorian
|
||||
$ pytheory fingering Am
|
||||
$ pytheory progression C major I V vi IV
|
||||
$ pytheory detect C E G A D
|
||||
$ pytheory play Am7 --synth triangle
|
||||
|
||||
What's Included
|
||||
---------------
|
||||
|
||||
- **6 musical systems**: Western, Indian (Hindustani), Arabic (Maqam),
|
||||
Japanese, Blues/Pentatonic, Javanese Gamelan
|
||||
- **40+ scales**: major, minor, harmonic minor, 7 modes, 10 thaats,
|
||||
10 maqamat, 6 Japanese pentatonic scales, blues, pentatonic,
|
||||
slendro, pelog, and more
|
||||
- **Pitch calculation** in equal, Pythagorean, and meantone temperaments
|
||||
- **Chord identification**: name any chord from its notes, intervals, or
|
||||
MIDI numbers (17 chord types recognized)
|
||||
- **Chord charts** with 144 pre-built chords (12 roots x 12 qualities)
|
||||
- **Chord analysis**: consonance scoring, Plomp-Levelt dissonance,
|
||||
beat frequency calculation, harmonic tension, voice leading
|
||||
- **Key detection** and **Roman numeral analysis** (I-IV-V-I progressions)
|
||||
- **Fingering generation** for 25 instruments with labeled string names,
|
||||
including guitar (8 tunings), bass, ukulele, mandolin, and more
|
||||
- **Audio playback** with sine, sawtooth, and triangle wave synthesis
|
||||
- **WAV export** for saving rendered audio to disk
|
||||
- :doc:`theory` — music theory fundamentals
|
||||
- :doc:`tones` — working with individual notes
|
||||
- :doc:`scales` — scales, modes, and keys
|
||||
- :doc:`chords` — chord construction, analysis, and progressions
|
||||
- :doc:`sequencing` — composing multi-part arrangements
|
||||
- :doc:`synths` — the 10 waveforms and 8 envelopes
|
||||
- :doc:`effects` — reverb, delay, distortion, chorus, lowpass, automation
|
||||
- :doc:`drums` — 58 patterns, 21 fills, drum synthesis
|
||||
- :doc:`playback` — play, save, export
|
||||
|
||||
Reference in New Issue
Block a user