diff --git a/docs/guide/cli.rst b/docs/guide/cli.rst index b52c7ba..8c82cce 100644 --- a/docs/guide/cli.rst +++ b/docs/guide/cli.rst @@ -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 ----------- diff --git a/docs/guide/cookbook.rst b/docs/guide/cookbook.rst index 9c58793..c6757f6 100644 --- a/docs/guide/cookbook.rst +++ b/docs/guide/cookbook.rst @@ -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) diff --git a/docs/guide/quickstart.rst b/docs/guide/quickstart.rst index 2afe72f..833a8ee 100644 --- a/docs/guide/quickstart.rst +++ b/docs/guide/quickstart.rst @@ -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 `_: +For audio playback through your speakers, you'll also need +`PortAudio `_: - 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.from_midi(60) - + >>> Chord.from_symbol("Am7").identify() + 'A minor 7th' - >>> c4 + 4 - - >>> c4 + 7 - - - >>> 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") - + 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.from_name("Am7") - - >>> Chord.from_intervals("G", 4, 7, 10) - +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