Merge pull request #48 from kennethreitz/docs-audio

Audio samples in documentation
This commit is contained in:
2026-03-29 05:51:38 -04:00
committed by GitHub
27 changed files with 506 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
<audio controls style="width: 100%; margin: 0.5em 0 1.5em 0;">
<source src="{{ pathto('_static/audio/' + file, 1) }}" type="audio/wav">
Your browser does not support the audio element.
</audio>
+440
View File
@@ -0,0 +1,440 @@
"""Generate audio samples for documentation.
Renders code examples from the docs as WAV files so they can be
embedded as <audio> players on the website.
Usage:
uv run python docs/generate_audio.py
"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from pytheory import Score, Duration, Key, Chord, Fretboard, DrumSound
from pytheory.play import render_score, SAMPLE_RATE
import numpy
import struct
AUDIO_DIR = os.path.join(os.path.dirname(__file__), "_static", "audio")
os.makedirs(AUDIO_DIR, exist_ok=True)
def save_wav(buf, path):
"""Save a float32 buffer as 16-bit WAV."""
# Normalize
peak = numpy.abs(buf).max()
if peak > 0:
buf = buf / peak * 0.9
samples = (buf * 32767).astype(numpy.int16)
with open(path, "wb") as f:
n = len(samples)
f.write(b"RIFF")
f.write(struct.pack("<I", 36 + n * 2))
f.write(b"WAVE")
f.write(b"fmt ")
f.write(struct.pack("<IHHIIHH", 16, 1, 1, SAMPLE_RATE, SAMPLE_RATE * 2, 2, 16))
f.write(b"data")
f.write(struct.pack("<I", n * 2))
f.write(samples.tobytes())
print(f" {os.path.basename(path)} ({len(buf)/SAMPLE_RATE:.1f}s)")
def render(name, score):
"""Render a score to WAV in the audio directory."""
buf = render_score(score)
save_wav(buf, os.path.join(AUDIO_DIR, f"{name}.wav"))
# ── Piano hold (polyphonic overlap) ──────────────────────────────────────
def gen_piano_hold():
score = Score("4/4", bpm=85)
piano = score.part("piano", instrument="piano", reverb=0.3)
piano.hold("C3", Duration.WHOLE * 2, velocity=60)
piano.hold("E3", Duration.WHOLE * 2, velocity=55)
piano.hold("G3", Duration.WHOLE * 2, velocity=55)
for n in ["E4", "G4", "C5", "G4", "E4", "D4", "C4", "E4"]:
piano.add(n, Duration.QUARTER, velocity=80)
render("piano_hold", score)
# ── Articulations ────────────────────────────────────────────────────────
def gen_articulations():
score = Score("4/4", bpm=90)
piano = score.part("piano", instrument="piano", reverb=0.25)
for n in ["C4", "E4", "G4", "C5"]:
piano.add(n, Duration.QUARTER, velocity=80)
for n in ["C4", "E4", "G4", "C5"]:
piano.add(n, Duration.QUARTER, velocity=80, articulation="staccato")
for n in ["C5", "G4", "E4", "C4"]:
piano.add(n, Duration.QUARTER, velocity=80, articulation="legato")
for n in ["C4", "E4", "G4", "C5"]:
piano.add(n, Duration.QUARTER, velocity=80, articulation="marcato")
render("articulations", score)
# ── Dynamic curves ───────────────────────────────────────────────────────
def gen_dynamics():
score = Score("4/4", bpm=90)
piano = score.part("piano", instrument="piano", reverb=0.3)
piano.crescendo(["C4", "D4", "E4", "F4", "G4", "A4", "B4", "C5"],
Duration.QUARTER, start_vel=30, end_vel=110)
piano.decrescendo(["C5", "B4", "A4", "G4", "F4", "E4", "D4", "C4"],
Duration.QUARTER, start_vel=110, end_vel=30)
render("dynamics", score)
# ── Filter ramp ──────────────────────────────────────────────────────────
def gen_filter_ramp():
score = Score("4/4", bpm=130)
score.drums("house", repeats=8, fill="house", fill_every=8)
score.set_drum_effects(volume=0.4)
acid = score.part("acid", synth="saw", volume=0.6,
lowpass=300, lowpass_q=8.0, distortion=0.3,
legato=True, glide=0.03)
acid.ramp(over=Duration.WHOLE * 4, curve="ease_in", lowpass=5000)
for _ in range(4):
for n in ["C2", "C3", "C2", "Eb2", "C2", "G2", "Bb2", "C2"]:
acid.add(n, Duration.EIGHTH, velocity=90)
acid.ramp(over=Duration.WHOLE * 4, curve="ease_out", lowpass=300)
for _ in range(4):
for n in ["C2", "C3", "C2", "Eb2", "C2", "G2", "Bb2", "C2"]:
acid.add(n, Duration.EIGHTH, velocity=88)
render("filter_ramp", score)
# ── Rock beat ────────────────────────────────────────────────────────────
def gen_rock_beat():
score = Score("4/4", bpm=120)
score.drums("rock", repeats=4, fill="rock", fill_every=4)
render("rock_beat", score)
# ── Bossa nova ───────────────────────────────────────────────────────────
def gen_bossa_nova():
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
rhodes = score.part("rhodes", synth="fm", envelope="piano", volume=0.3,
reverb=0.4, reverb_decay=1.8)
for sym in ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]:
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
render("bossa_nova", score)
# ── Djembe ───────────────────────────────────────────────────────────────
def gen_djembe():
score = Score("4/4", bpm=110)
score.drums("tiriba", repeats=4, fill="djembe call", fill_every=4)
score.set_drum_effects(reverb=0.2)
render("djembe", score)
# ── Tabla ────────────────────────────────────────────────────────────────
def gen_tabla():
score = Score("4/4", bpm=80)
score.drums("teental", repeats=2)
score.drums("chakradar", repeats=1)
score.set_drum_effects(reverb=0.2)
render("tabla", score)
# ── Marching snare ───────────────────────────────────────────────────────
def gen_march_snare():
score = Score("4/4", bpm=120)
p = score.part("snare", synth="sine", volume=0.8, reverb=0.15)
S = DrumSound.MARCH_SNARE
R = DrumSound.MARCH_RIMSHOT
C = DrumSound.MARCH_CLICK
for _ in range(4):
p.hit(C, Duration.QUARTER, velocity=95)
for _ in range(4):
p.hit(R, Duration.SIXTEENTH, velocity=118)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.hit(S, Duration.SIXTEENTH, velocity=28)
p.hit(R, Duration.SIXTEENTH, velocity=115)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=28)
p.hit(S, Duration.SIXTEENTH, velocity=32)
p.hit(R, Duration.SIXTEENTH, velocity=118)
p.hit(S, Duration.SIXTEENTH, velocity=35)
p.hit(S, Duration.SIXTEENTH, velocity=28)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(R, Duration.SIXTEENTH, velocity=120)
p.hit(S, Duration.SIXTEENTH, velocity=30)
p.hit(S, Duration.SIXTEENTH, velocity=28)
p.hit(S, Duration.SIXTEENTH, velocity=32)
render("march_snare", score)
# ── Ensemble comparison ──────────────────────────────────────────────────
def gen_ensemble():
score = Score("4/4", bpm=120)
S = DrumSound.MARCH_SNARE
R = DrumSound.MARCH_RIMSHOT
# Solo first
solo = score.part("solo", synth="sine", volume=0.7, reverb=0.15)
for _ in range(2):
solo.hit(R, Duration.SIXTEENTH, velocity=118)
solo.hit(S, Duration.SIXTEENTH, velocity=30)
solo.hit(S, Duration.SIXTEENTH, velocity=32)
solo.hit(S, Duration.SIXTEENTH, velocity=28)
solo.hit(R, Duration.SIXTEENTH, velocity=115)
solo.hit(S, Duration.SIXTEENTH, velocity=30)
solo.hit(S, Duration.SIXTEENTH, velocity=28)
solo.hit(S, Duration.SIXTEENTH, velocity=32)
solo.hit(R, Duration.SIXTEENTH, velocity=118)
solo.hit(S, Duration.SIXTEENTH, velocity=35)
solo.hit(S, Duration.SIXTEENTH, velocity=28)
solo.hit(S, Duration.SIXTEENTH, velocity=30)
solo.hit(R, Duration.SIXTEENTH, velocity=120)
solo.hit(S, Duration.SIXTEENTH, velocity=30)
solo.hit(S, Duration.SIXTEENTH, velocity=28)
solo.hit(S, Duration.SIXTEENTH, velocity=32)
# Then ensemble
line = score.part("line", synth="sine", volume=0.7, reverb=0.15, ensemble=8)
for _ in range(8):
line.rest(Duration.QUARTER)
for _ in range(4):
line.hit(R, Duration.SIXTEENTH, velocity=118)
line.hit(S, Duration.SIXTEENTH, velocity=30)
line.hit(S, Duration.SIXTEENTH, velocity=32)
line.hit(S, Duration.SIXTEENTH, velocity=28)
line.hit(R, Duration.SIXTEENTH, velocity=115)
line.hit(S, Duration.SIXTEENTH, velocity=30)
line.hit(S, Duration.SIXTEENTH, velocity=28)
line.hit(S, Duration.SIXTEENTH, velocity=32)
line.hit(R, Duration.SIXTEENTH, velocity=118)
line.hit(S, Duration.SIXTEENTH, velocity=35)
line.hit(S, Duration.SIXTEENTH, velocity=28)
line.hit(S, Duration.SIXTEENTH, velocity=30)
line.hit(R, Duration.SIXTEENTH, velocity=120)
line.hit(S, Duration.SIXTEENTH, velocity=30)
line.hit(S, Duration.SIXTEENTH, velocity=28)
line.hit(S, Duration.SIXTEENTH, velocity=32)
render("ensemble", score)
# ── Guitar strum ─────────────────────────────────────────────────────────
def gen_strum():
score = Score("4/4", bpm=100)
guitar = score.part("guitar", instrument="acoustic_guitar",
fretboard=Fretboard.guitar())
for ch in ["G", "D", "Em", "C"] * 2:
guitar.strum(ch, duration=Duration.WHOLE, velocity=75)
render("strum", score)
# ── Swell ────────────────────────────────────────────────────────────────
def gen_swell():
score = Score("4/4", bpm=80)
strings = score.part("strings", instrument="string_ensemble",
reverb=0.4, ensemble=12)
strings.swell(["C4", "D4", "E4", "F4", "G4", "F4", "E4", "D4",
"C4", "D4", "E4", "F4", "G4", "A4", "G4", "E4"],
Duration.QUARTER, low_vel=30, peak_vel=100)
render("swell", score)
# ── Generate all ─────────────────────────────────────────────────────────
# ── Cookbook: Acid House ───────────────────────────────────────────────────
def gen_acid_house():
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)
render("acid_house", score)
# ── Cookbook: Dub Reggae ──────────────────────────────────────────────────
def gen_dub_reggae():
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)
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 _ in range(16):
bass.add("A1", Duration.HALF)
render("dub_reggae", score)
# ── Cookbook: Jazz Ballad ─────────────────────────────────────────────────
def gen_jazz_ballad():
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)
render("jazz_ballad", score)
# ── Quickstart example ───────────────────────────────────────────────────
def gen_quickstart():
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
chords = score.part("chords", synth="sine", envelope="pad",
reverb=0.4, volume=0.3)
lead = score.part("lead", synth="saw", envelope="pluck",
lowpass=2000, lowpass_q=3.0, distortion=0.8,
legato=True, glide=0.03, volume=0.4)
bass = score.part("bass", synth="sine", lowpass=500)
key = Key("A", "minor")
for chord in key.progression("i", "iv", "V", "i"):
chords.add(chord, Duration.WHOLE)
chords.add(chord, Duration.WHOLE)
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)
for n in ["A2", "E2", "A2", "C3"] * 4:
bass.add(n, Duration.QUARTER)
render("quickstart", score)
# ── Sequencing complete example (bossa nova) ─────────────────────────────
def gen_sequencing_bossa():
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
rhodes = score.part("rhodes", synth="fm", envelope="piano", volume=0.3,
reverb=0.4, reverb_decay=1.8)
lead = score.part("lead", synth="triangle", envelope="pluck", volume=0.45,
delay=0.25, delay_time=0.32, delay_feedback=0.35, reverb=0.2)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.45,
lowpass=600)
for sym in ["Am", "Am", "Dm", "Dm", "E7", "E7", "Am", "Am"]:
rhodes.add(Chord.from_symbol(sym), Duration.WHOLE)
for n, d in [("E5", 0.67), ("D5", 0.33), ("C5", 0.67), ("B4", 0.33),
("A4", 1), ("C5", 0.67), ("E5", 0.33), ("D5", 0.67),
("C5", 0.33), ("A4", 1)]:
lead.add(n, d)
for n in ["A2", "E2", "A2", "C3", "D2", "A2", "D2", "F2"]:
bass.add(n, Duration.QUARTER)
render("sequencing_bossa", score)
# ── Drums layering (salsa) ───────────────────────────────────────────────
def gen_salsa_layered():
score = Score("4/4", bpm=180)
score.drums("salsa", repeats=4, fill="salsa", fill_every=4)
pads = score.part("pads", synth="sine", envelope="pad", volume=0.3)
lead = score.part("lead", synth="saw", envelope="pluck", volume=0.4)
bass = score.part("bass", synth="sine", envelope="pluck", volume=0.45)
for chord in Key("D", "minor").progression("ii", "V", "i", "i") * 2:
pads.add(chord, Duration.WHOLE)
lead.add("A5", 0.67).add("G5", 0.33).add("F5", 0.67).add("E5", 0.33)
for n in ["D2", "A2", "D2", "F2"] * 2:
bass.add(n, Duration.QUARTER)
render("salsa_layered", score)
# ── Playback basic ───────────────────────────────────────────────────────
def gen_playback_basic():
score = Score("4/4", bpm=140)
score.drums("bossa nova", repeats=4)
chords = score.part("chords", synth="sine", envelope="pad")
for sym in ["Am", "Dm", "E7", "Am"]:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
render("playback_basic", score)
# ── Cookbook: song with sections ──────────────────────────────────────────
def gen_song_sections():
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)
render("song_sections", score)
GENERATORS = [
gen_piano_hold,
gen_articulations,
gen_dynamics,
gen_filter_ramp,
gen_rock_beat,
gen_bossa_nova,
gen_djembe,
gen_tabla,
gen_march_snare,
gen_ensemble,
gen_strum,
gen_swell,
gen_acid_house,
gen_dub_reggae,
gen_jazz_ballad,
gen_quickstart,
gen_sequencing_bossa,
gen_salsa_layered,
gen_playback_basic,
gen_song_sections,
]
if __name__ == "__main__":
print("Generating audio samples for docs...")
print()
for gen in GENERATORS:
gen()
print()
print(f"Done. {len(GENERATORS)} files in {AUDIO_DIR}")
+16
View File
@@ -411,6 +411,10 @@ Acid House Track
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -443,6 +447,10 @@ Sparse notes into infinite echo:
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
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -480,6 +488,10 @@ The difference between a robot and a musician:
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
~~~~~~~~~~~~~~~~~~~
@@ -513,6 +525,10 @@ Define once, arrange freely:
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>
Export Everything to MIDI
~~~~~~~~~~~~~~~~~~~~~~~~~~
+16
View File
@@ -249,6 +249,10 @@ Playing Patterns
play_pattern(Pattern.preset("salsa"), repeats=4, bpm=180)
play_pattern(Pattern.preset("afrobeat"), repeats=8, bpm=110)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/rock_beat.wav" type="audio/wav"></audio>
Fills
-----
@@ -339,6 +343,10 @@ drum pattern and all named parts are mixed together by ``play_score()``:
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/salsa_layered.wav" type="audio/wav"></audio>
World Percussion
----------------
@@ -458,6 +466,10 @@ West African-style break).
score = Score("4/4", bpm=120)
score.drums("djembe", repeats=8, fill="djembe call", fill_every=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/djembe.wav" type="audio/wav"></audio>
Metal Kit
~~~~~~~~~
@@ -548,6 +560,10 @@ voice with per-player timing tendencies and micro pitch drift.
# Or use patterns
score.drums("drumline", repeats=4)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/march_snare.wav" type="audio/wav"></audio>
**Sympathetic resonance:** The marching snare builds up snare wire
buzz as hits accumulate, and the buzz decays during rests — just like
a real drum.
+4
View File
@@ -66,6 +66,10 @@ the mix louder and punchier:
chords.add(Chord.from_symbol(sym), Duration.WHOLE)
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/playback_basic.wav" type="audio/wav"></audio>
The render pipeline respects the Score's ``temperament`` and
``reference_pitch`` settings, so Baroque or microtonal scores play back
at the correct tuning:
+4
View File
@@ -185,6 +185,10 @@ chords, melody, bass, each with their own synth and effects:
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/quickstart.wav" type="audio/wav"></audio>
Export to Your DAW
------------------
+22
View File
@@ -392,6 +392,10 @@ rhodes, triangle lead, and filtered bass:
play_score(score)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/sequencing_bossa.wav" type="audio/wav"></audio>
Velocity
--------
@@ -431,6 +435,10 @@ Pass ``articulation=`` to ``Part.add()``:
piano.add("G4", Duration.QUARTER, articulation="accent") # louder
piano.add("C5", Duration.HALF, articulation="fermata") # held longer
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/articulations.wav" type="audio/wav"></audio>
What each articulation does:
- **staccato** — plays ~40% of the note duration with a quick fade-out. Short and detached.
@@ -467,6 +475,10 @@ of notes instead of setting each one manually.
piano.dynamics(["C4","E4","G4","C5"], Duration.QUARTER,
velocities=[50, 80, 110, 90])
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/dynamics.wav" type="audio/wav"></audio>
Four methods:
- **crescendo()** — linear velocity ramp from ``start_vel`` to ``end_vel``.
@@ -548,6 +560,12 @@ Each ensemble voice gets a consistent timing personality (some rush,
some drag) plus small per-note wobble, and slightly different tuning.
The result sounds like a real section — together but alive.
Solo snare, then an 8-player section plays the same pattern:
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/ensemble.wav" type="audio/wav"></audio>
Swing and Groove
----------------
@@ -663,6 +681,10 @@ Four interpolation curves:
# Smooth reverb wash fading in and settling
pad.ramp(over=Duration.WHOLE * 4, curve="ease_in_out", reverb=0.6)
.. raw:: html
<audio controls style="width:100%;margin:0.5em 0 1.5em"><source src="../_static/audio/filter_ramp.wav" type="audio/wav"></audio>
``ramp()`` generates automation points every quarter-beat by default.
Set ``resolution=0.125`` for smoother curves (every 32nd note), or
``resolution=1.0`` for lighter automation (every beat).