Skip to main content

composing-with-pytheory

Compose music with PyTheory — chord progressions, melodies, basslines, drum grooves, and full multi-part arrangements written in pure Python and rendered to audio or MIDI. Use whenever the user wants to write, sketch, generate, or arrange music — "write me a bossa nova in G minor", "make a four-chord pop loop", "lay down a funk beat", "turn this progression into a song" — or export the result to WAV, MIDI, MusicXML, LilyPond, ABC, or guitar tab. Also covers the metronome / chord-practice click / tempo trainer (`pytheory metronome`).

Informations de source

Dépôt
kennethreitz/pytheory
Dernière activité de la source
17 juin 2026 à 01:23
Langue détectée de SKILL.md
anglais
Étoiles
1 657
Forks
84

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
composing-with-pytheory
description
Compose music with PyTheory — chord progressions, melodies, basslines, drum grooves, and full multi-part arrangements written in pure Python and rendered to audio or MIDI. Use whenever the user wants to write, sketch, generate, or arrange music — "write me a bossa nova in G minor", "make a four-chord pop loop", "lay down a funk beat", "turn this progression into a song" — or export the result to WAV, MIDI, MusicXML, LilyPond, ABC, or guitar tab. Also covers the metronome / chord-practice click / tempo trainer (`pytheory metronome`).
license
MIT
allowed-tools
Write, Read, Bash(python3:*), Bash(uv run:*)
# Composing with PyTheory PyTheory turns music theory into runnable Python. You build a `Score`, add parts (chords, melodies, basslines) and drums, then **render it to audio or MIDI** — every sound is synthesized from math, so there are no samples, plugins, or DAW to install. ## How to work 1. **Write a short Python script** that builds a `Score` and either plays it or saves it. Don't compose by chaining shell one-liners — a script is clearer, reproducible, and easy to iterate on. 2. **Run it** with `uv run python song.py` when uv is available, otherwise `python3 song.py`. 3. To let the user *hear* it, call `play_score(score)` (speakers) or render to a WAV they can open. To hand off to a DAW, save MIDI. If PyTheory isn't installed, prefer uv when available: `uv pip install pytheory` (or `uv add pytheory` in a uv project); otherwise `pip install pytheory`. For live MIDI input add the extra: `uv pip install "pytheory[live]"` (or `pip install "pytheory[live]"`). NumPy/SciPy ship as PyTheory dependencies, so the `scipy.io.wavfile` WAV export below needs no extra install. ## Core building blocks ```python from pytheory import Score, Key, Chord, Duration score = Score("4/4", bpm=120) # time signature + tempo # A part is a voice with its own synth/instrument and effects. piano = score.part("piano", instrument="piano", reverb=0.3) lead = score.part("lead", synth="saw", envelope="pluck", lowpass=4000) bass = score.part("bass", synth="triangle", lowpass=900) # Chords from a Key's progression (Roman numerals) or Chord.from_symbol. for chord in Key("G", "major").progression("I", "V", "vi", "IV"): piano.add(chord, Duration.WHOLE) # Melodies are note names + durations (a float = beats, or Duration.*). lead.add("D5", 1).add("B4", 0.5).add("D5", 0.5).add("G5", 2) lead.rest(2) for n in ["G2", "D2", "E2", "C2"]: bass.add(n, Duration.WHOLE) ``` - `part.add(note_or_chord, duration)` chains and returns the part. `part.rest(duration)` inserts silence. - `Duration.WHOLE / HALF / QUARTER / EIGHTH / SIXTEENTH` plus dotted variants (`DOTTED_HALF`, `DOTTED_QUARTER`, …), or pass a float in beats (`1`, `0.5`, `0.25`, `0.125`). - `Key(tonic, mode).progression("I", "V", "vi", "IV")` returns `Chord`s (lowercase numeral = minor, e.g. `"i"`, `"VI"`). `Key(...).chords` lists the diatonic chords; `Key(...).scale` gives the scale tones. `Chord.from_symbol("F#m7b5")` parses any symbol. - Note names are scientific pitch (`C4` = middle C). `Tone.from_string("A1")` builds an explicit pitch; `tone.add(12)`/`tone.add(-12)` shift octaves. ## Expression & dynamics `add()` takes keyword controls that make lines feel human and musical: ```python part.add("A4", Duration.HALF, velocity=90, bend=-0.5, articulation="accent") ``` - **`velocity`** (1–127) — loudness per note. Vary it; flat velocities sound robotic. Fades are just descending velocities: ```python for v in [100, 85, 70, 55, 40, 25]: part.add("E5", Duration.QUARTER, velocity=v) ``` - **`bend`** (semitones, float) — pitch bend over the note's duration. `bend=2` bends up a whole step, `bend=-0.5` slides down a quartertone. Great for sitar meends, guitar bends, theremin. `bend_type` = `"smooth"` (default), `"linear"`, or `"late"`. - **`articulation`** — `"accent"`, `"staccato"`, `"legato"`, `"marcato"`, `"tenuto"`, `"fermata"` (or `""`). - **`lyric`** — a syllable for vocal synths only (`synth="vocal_synth"` / `"choir_synth"`). Passing `lyric` to a non-vocal synth raises an error. - **`humanize`** (part kwarg, ~0.02–0.2) — subtle timing drift per note. ## Sound design palette Each part is either an **instrument** preset (realistic) or a raw **synth** waveform (shapeable), plus an **envelope** and an effects chain. - **Synths (56)** — `sine`, `saw`, `triangle`, `square`, `pulse`, `fm`, `noise`, `supersaw`, `pwm_slow/fast`, `hard_sync`, `ring_mod`, `wavefold`, `drift`, and many modeled instruments as `*_synth` (`rhodes_synth`, `sitar_synth`, `vocal_synth`, `mellotron_synth`, `singing_bowl_ring_synth`, …). `from pytheory.play import Synth; [s.value for s in Synth]` lists them. - **Instruments (83)** — `piano`, `electric_piano`, `acoustic_guitar`, `cello`, `violin`, `harp`, `flute`, `choir`, `vocal`, `sitar`, `koto`, `kalimba`, `timpani`, `pipe_organ`, `808_bass`, `acid_bass`, … `from pytheory import INSTRUMENTS; sorted(INSTRUMENTS)` lists them. - **Envelopes (10)** — `none`, `piano`, `organ`, `pluck`, `pad`, `strings`, `bowed`, `bell`, `mallet`, `staccato`. Effects are part kwargs (set once at creation, or change later with `.set()`): | Group | kwargs | | --- | --- | | Level / space | `volume`, `pan` (−1..1), `reverb` (0..1), `reverb_decay`, `reverb_type` | | Time | `delay`, `delay_time` (beats, e.g. `0.375` dotted-8th, `0.333` triplet), `delay_feedback` | | Filter | `lowpass`, `lowpass_q`, `highpass`, `highpass_q` (Hz / resonance) | | Drive | `distortion`, `distortion_drive`, `saturation` | | Width / pitch | `chorus`, `chorus_rate`, `chorus_depth`, `detune` (**cents**, keep 8–15), `spread` | | Synth body | `sub_osc` (0..1 sub-oscillator), `tremolo_depth`, `tremolo_rate`, `legato`, `glide` (portamento secs) | | Mix glue | `sidechain` (0..0.5 duck by the kick), `sidechain_release`, `humanize` | **`reverb_type`** picks a convolution space: `"algorithmic"` (default), `"taj_mahal"`, `"cathedral"`, `"hall"`, `"plate"`, `"spring"`, `"cave"`, `"parking_garage"`, `"canyon"`. **`score.ring_out()`** appends trailing silence so reverb/delay tails decay naturally instead of being clipped at the last beat (most audible on a final drum hit with a long reverb). Call it once before playing or exporting; the length auto-sizes to the longest effect tail, or pass `ring_out(seconds)`. Skip it for seamless loops, where you *want* the hard cut. ```python pad = score.part("pad", synth="supersaw", envelope="pad", reverb=0.5, reverb_type="cathedral", sidechain=0.2, sub_osc=0.3) acid = score.part("acid", synth="saw", envelope="pluck", legato=True, glide=0.05, lowpass=1500, lowpass_q=8, distortion=0.3, distortion_drive=2.0) ``` ## Movement: LFOs and mid-track changes - **`part.lfo(param, rate=, min=, max=, bars=, shape=)`** sweeps a parameter over time — the classic filter sweep / auto-wah / tremolo. `rate` is **cycles per bar** (`0.5` = one sweep every 2 bars, `1` = once per bar). `shape` = `"sine"`, `"triangle"`, `"saw"`, `"square"`. ```python pad.lfo("lowpass", rate=0.25, min=600, max=5000, bars=8, shape="triangle") ``` - **`part.set(**params)`** changes a part's settings partway through, for arrangement dynamics (drop the pad back, open the filter for a chorus): ```python pad.set(volume=0.5, reverb=0.4) ``` ## Drums ```python score.drums("hip hop", repeats=4) # by preset name score.drums("rock", repeats=8, fill="rock", fill_every=4) ``` - `score.drums(preset_or_pattern, repeats, fill=, fill_every=, split=, layer=)`. Presets include `rock`, `funk`, `jazz`, `hip hop`, `bossa nova`, `salsa`, `samba`, `reggae`, `waltz`, `tresillo`, `tabla solo`, … (`Pattern.list_presets()`). - **Custom beats** — build a `Pattern` from `Hit`s and pass it to `drums()`: ```python from pytheory import Pattern, Hit, DrumSound K, S, CH = DrumSound.KICK, DrumSound.SNARE, DrumSound.CLOSED_HAT beat = Pattern("my beat", [ Hit(K, 0.0), Hit(CH, 0.0), Hit(CH, 0.5), Hit(S, 1.0), Hit(CH, 1.0), Hit(CH, 1.5), Hit(K, 2.0), Hit(CH, 2.0), Hit(K, 2.5), Hit(CH, 2.5), Hit(S, 3.0), Hit(CH, 3.0), Hit(S, 1.75, velocity=35), # ghost ], beats=4.0) score.drums(beat, repeats=4) ``` `Hit(sound, position_in_beats, velocity=100)`; 74 `DrumSound` members (`print([d.name for d in DrumSound])`) including world kits (`TABLA_*`, `CAJON_*`, `DOUMBEK_*`, `DJEMBE_*`). - **Hand-programmed hits** — `part.hit(DrumSound.KICK, Duration.QUARTER, velocity=110, articulation="accent")` places a hit in any part's stream, with full per-hit control (good for tabla/cajon fills). - **Layering / polyrhythm** — repeated `drums()` calls play *in sequence*; pass `layer=True` to overlay (clave over a backbeat). For a true polyrhythm, put both voices in one `Pattern` at fractional positions. - **`split=True`** splits a kit into separate `kick`/`snare`/`hats`/`toms`/ `cymbals`/`percussion` parts so each takes its own effects (`score.parts["snare"].set(reverb=0.3)`). - **`score.set_drum_effects(reverb=, volume=, humanize=, …)`** applies to the whole kit at once. ## Arrangement & structure Real songs are built from repetition and dynamics. Useful idioms: - **Octave / voicing spread** — layer the same progression across registers: ```python for c in prog: low.add(c.transpose(-12), Duration.WHOLE) high.add(c.transpose(12), Duration.WHOLE) ``` `Chord.transpose(semitones)` and `Tone.transpose(semitones)` move pitches. - **Reusable phrases** — capture a motif as data and replay it with variation: ```python RIFF = [("A4", 0.5, 90), ("C5", 0.5, 80), ("E5", 1, 100)] def play_riff(part, vshift=0): for note, dur, vel in RIFF: part.add(note, dur, velocity=max(1, min(127, vel + vshift))) ``` - **Section boundaries** — rest parts in/out to create intros, breakdowns, drops: ```python def rest_bars(part, n): for _ in range(n): part.rest(Duration.WHOLE) ``` - **`score.section(name)` / `score.repeat(name, times)`** capture and repeat spans for structured arrangements. ## Alternate tunings PyTheory isn't limited to 12-tone equal temperament: ```python
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub