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`).

Datos de origen

Repositorio
kennethreitz/pytheory
Última actividad en el origen
17 de junio de 2026 a las 01:23
Idioma detectado de SKILL.md
inglés
Estrellas
1659
Forks
84

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
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
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub