Files
pytheory/docs/guide/effects.rst
kennethreitz b239e9a997 Add warm closing paragraphs to all 11 guide pages
Every page now ends on prose instead of a code block.
Chords, tones, scales, effects, drums, CLI, cookbook,
fretboard, playback, systems, theory — each with a
sentence that ties the page together.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:17:36 -04:00

598 lines
18 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Effects
=======
Effects are how recorded music gets its character. A guitar without
reverb sounds like it's being played in a closet. A vocal without
compression sounds thin and amateur. A synth without filtering sounds
like a test signal. Effects are the difference between "notes" and
"music" -- they put the sound in a space, give it texture, and make it
feel alive.
Every record you've ever loved was shaped by effects. The cavernous
reverb on a Phil Collins drum hit. The tape delay on a reggae vocal.
The distortion on a Hendrix guitar. The chorus on an 80s synth pad.
These aren't decorations added after the fact; they're fundamental to
the sound itself.
Each part in a Score can have its own effects chain. Effects are set at
part creation and applied per-part before mixing, so every voice gets
independent processing.
Signal Chain
------------
The order of effects matters -- a lot. Distortion before a lowpass
filter means you're generating all those rich, crunchy harmonics and
then sculpting them with the filter. That's warm, controllable,
musical. Filter before distortion means you're distorting the already-
filtered signal -- a different, often harsher character. The fixed
order in PyTheory matches classic analog synth architecture, the same
signal path used by the Moog, the TB-303, and most hardware synths.
It's a well-tested order that sounds good by default.
Effects are applied in this fixed order::
Signal --> Distortion --> Chorus --> Lowpass Filter --> Delay --> Reverb --> Mix
- **Distortion** first: drives the raw signal before filtering (like
plugging a guitar into a fuzz pedal before the amp).
- **Chorus** second: thickens the distorted signal.
- **Lowpass** third: shapes the tone (like a tone knob on an amp).
- **Delay** fourth: echoes the shaped signal (tap delay / tape echo).
- **Reverb** last: places everything in a space (room / hall).
Distortion
----------
You know what distortion sounds like -- it's the sound of rock and roll.
An electric guitar through a cranked amplifier. But at lower levels,
distortion is subtler: it adds warmth, presence, and harmonic richness.
This is why producers run clean signals through tape machines and tube
preamps. A little saturation makes everything sound more "real."
Soft-clip waveshaping using ``tanh`` -- models the warm saturation of an
overdriven tube amplifier. At low drive levels it adds harmonic warmth;
at high levels it becomes an aggressive fuzz.
Parameters:
- ``distortion``: Wet/dry mix, 0.0--1.0.
- ``distortion_drive``: Gain before clipping (default 3.0).
- 0.5--2 = subtle warmth (tube preamp)
- 3--8 = overdrive (cranked amp)
- 10+ = fuzz
.. code-block:: python
# Warm tube saturation on a bass
bass = score.part(
"bass",
synth="sine",
envelope="pluck",
distortion=0.3,
distortion_drive=2.0,
)
# Heavy fuzz on a lead
lead = score.part(
"lead",
synth="saw",
envelope="staccato",
distortion=0.8,
distortion_drive=10.0,
)
Chorus
------
That shimmery, wide, slightly-out-of-focus sound that defined the
1980s? That's chorus. Think of the intro to "Come As You Are" by
Nirvana, or literally any synth pad from 1983 to 1989. It makes one
instrument sound like two or three playing together, slightly out of
tune with each other -- which is exactly how a real string section or
choir sounds rich and full.
A slightly detuned, LFO-modulated delayed copy mixed back in. Thickens
the sound like two musicians playing the same part -- the signature
effect of the Roland Juno synthesizers.
Parameters:
- ``chorus``: Wet/dry mix, 0.0--1.0.
- ``chorus_rate``: LFO speed in Hz. 0.5--1 = slow shimmer, 2--4 = vibrato.
- ``chorus_depth``: Modulation depth in seconds (default 0.003).
.. code-block:: python
# Juno-style pad chorus
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
chorus=0.5,
chorus_rate=1.5,
chorus_depth=0.003,
)
# Subtle thickening on a clean lead
lead = score.part(
"lead",
synth="triangle",
envelope="pluck",
chorus=0.2,
chorus_rate=0.8,
)
Lowpass Filter
--------------
You know that sound when a DJ turns the knob and everything goes
underwater? That's a lowpass filter closing down. It removes
high-frequency content, leaving only the warm, round, bassy
frequencies below the cutoff point. The lowpass filter is arguably the
most important effect in all of electronic music -- it's the entire
sound of acid house, the "wah" in auto-wah, and the reason analog
synths sound warm instead of harsh.
A 12 dB/octave biquad lowpass filter with resonance -- the sound of
analog synthesizers. Removes frequencies above the cutoff; the resonance
(Q) parameter adds a peak at the cutoff frequency for that classic
"acid squelch."
Parameters:
- ``lowpass``: Cutoff frequency in Hz (0 = off). Reference points:
- 200--400 Hz = deep sub bass
- 800--1500 Hz = warm / muffled
- 2000--4000 Hz = present lead
- 5000+ Hz = subtle rolloff
- ``lowpass_q``: Resonance / Q factor (default 0.707 = Butterworth flat).
- 1.0 = slight peak
- 2.0 = pronounced
- 5.0+ = aggressive acid squelch
.. code-block:: python
# Round bass with gentle filtering
bass = score.part(
"bass",
synth="sine",
envelope="pluck",
lowpass=400,
lowpass_q=1.5,
)
# Acid squelch on a saw lead
acid = score.part(
"acid",
synth="saw",
envelope="staccato",
lowpass=1500,
lowpass_q=5.0,
legato=True,
glide=0.03,
)
Delay
-----
Delay is echo. Literally. The Edge from U2 built his entire guitar
sound around dotted-eighth-note delays. Dub reggae producers like Lee
"Scratch" Perry and King Tubby turned delay into an art form, feeding
echoes back into themselves until they spiraled into infinity. At short
times with low feedback, delay adds rhythmic interest. At long times
with high feedback, it creates cascading, psychedelic soundscapes.
Tempo-synced echoes with feedback. Each repeat feeds back into the
delay line, creating rhythmic echo trails. High feedback values produce
the cascading, self-oscillating echoes of dub reggae.
Parameters:
- ``delay``: Wet/dry mix, 0.0--1.0.
- ``delay_time``: Time between echoes in seconds. Musically useful
values at 120 bpm: 0.25 (8th note), 0.375 (dotted 8th),
0.5 (quarter note).
- ``delay_feedback``: How much each echo feeds back (0.0--1.0).
- 0.3 = a few repeats
- 0.5 = many repeats
- 0.7+ = runaway (dub style)
.. code-block:: python
# Dotted-eighth slapback on a lead
lead = score.part(
"lead",
synth="triangle",
envelope="strings",
delay=0.3,
delay_time=0.375,
delay_feedback=0.4,
)
# Dub-style runaway echoes
melodica = score.part(
"melodica",
synth="triangle",
envelope="pluck",
delay=0.5,
delay_time=0.66,
delay_feedback=0.55,
)
Reverb
------
Everyone knows what reverb sounds like, even if they don't know the
word -- it's the sound of singing in the shower, or clapping in a
cathedral. It's the natural echo of a space. Without reverb, sounds
feel uncomfortably close and dry, like someone whispering directly into
your ear. With it, sounds feel like they exist in a real place. Reverb
is the most universally used effect in all of recorded music.
PyTheory offers two reverb engines: a fast **algorithmic** reverb for
general use, and **convolution** reverb for photorealistic acoustic
spaces.
Algorithmic Reverb
~~~~~~~~~~~~~~~~~~
A Schroeder reverb using 4 parallel comb filters and 2 series allpass
filters. Fast, lightweight, and good for general-purpose room
simulation.
Parameters:
- ``reverb``: Wet/dry mix, 0.0--1.0.
- 0.2--0.4 = subtle space
- 0.5--0.8 = ambient / dub
- ``reverb_decay``: Tail length in seconds.
- 0.5 = small room
- 1.5 = hall
- 3.0+ = cathedral / dub
.. code-block:: python
# Jazz club ambience
rhodes = score.part(
"rhodes",
synth="fm",
envelope="piano",
reverb=0.4,
reverb_decay=1.8,
)
Convolution Reverb
~~~~~~~~~~~~~~~~~~
Convolution reverb works by convolving your audio with an *impulse
response* -- a recording (or simulation) of a real acoustic space.
Where algorithmic reverb approximates the math of reflections,
convolution reverb *is* the space. You hear every surface, every
angle, every material.
PyTheory generates synthetic impulse responses that model the acoustic
properties of real spaces: early reflection patterns, exponential
decay envelopes, frequency-dependent absorption (high frequencies die
faster in stone), diffusion density, and subtle pitch modulation from
irregular surfaces. The result is dramatically more realistic than
algorithmic reverb, especially for long tails and large spaces.
Set ``reverb_type`` to any preset name instead of ``"algorithmic"``:
- ``"taj_mahal"`` -- Massive marble dome. 12-second tail, bright early
reflections, enormously dense and diffuse. The most dramatic verb
you've ever heard.
- ``"cathedral"`` -- Gothic stone cathedral. 6 seconds, strong early
reflections off parallel walls, dark reverberant tail.
- ``"plate"`` -- EMT 140 plate reverb. 4 seconds, dense, bright, smooth.
The studio classic that defined pop records from the 60s onward.
- ``"spring"`` -- Spring reverb tank. 3 seconds, metallic, boingy, lo-fi.
The sound of surf rock and guitar amps.
- ``"cave"`` -- Natural cave. 8 seconds, very dark, irregular reflections.
High frequencies are aggressively absorbed by rock.
- ``"parking_garage"`` -- Concrete box. 3 seconds, bright, flutter echoes
from parallel hard walls.
- ``"canyon"`` -- Open canyon. 5 seconds, sparse discrete echoes (the
walls are far apart) dissolving into a diffuse tail.
Parameters:
- ``reverb``: Wet/dry mix, 0.0--1.0.
- ``reverb_type``: Preset name (default ``"algorithmic"``).
.. code-block:: python
# FM flute through the Taj Mahal
flute = score.part(
"flute",
synth="fm",
envelope="bell",
reverb=0.85,
reverb_type="taj_mahal",
delay=0.65,
delay_time=0.375,
delay_feedback=0.55,
)
# Cathedral wash for ambient pads
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
reverb=0.7,
reverb_type="cathedral",
)
# Classic plate on a vocal-style lead
lead = score.part(
"lead",
synth="triangle",
envelope="strings",
reverb=0.5,
reverb_type="plate",
)
# Algorithmic reverb still works as before
rhodes = score.part(
"rhodes",
synth="fm",
envelope="piano",
reverb=0.4,
reverb_decay=1.8,
)
You can switch reverb types mid-song with automation:
.. code-block:: python
lead = score.part("lead", synth="fm", envelope="bell",
reverb=0.5, reverb_type="plate")
lead.add("C5", Duration.WHOLE)
# Switch to cathedral for the big section
lead.set(reverb_type="cathedral", reverb=0.8)
lead.add("E5", Duration.WHOLE)
Combining Effects
-----------------
Effects stack naturally. Here are some real-world combinations:
Dub
~~~
Distortion warmth into filtered delay into deep reverb:
.. code-block:: python
melodica = score.part(
"melodica",
synth="triangle",
envelope="pluck",
distortion=0.2,
distortion_drive=2.0,
lowpass=2000,
lowpass_q=1.2,
delay=0.5,
delay_time=0.66,
delay_feedback=0.55,
reverb=0.4,
reverb_decay=2.5,
)
Acid
~~~~
Resonant lowpass with distortion and delay:
.. code-block:: python
acid = score.part(
"acid",
synth="saw",
envelope="staccato",
lowpass=1500,
lowpass_q=3.0,
distortion=0.4,
distortion_drive=4.0,
delay=0.3,
delay_time=0.242,
delay_feedback=0.4,
)
Ambient
~~~~~~~
Wide chorus, long reverb, gentle delay:
.. code-block:: python
ambient = score.part(
"ambient",
synth="supersaw",
envelope="pad",
chorus=0.4,
chorus_rate=0.5,
delay=0.3,
delay_time=0.5,
delay_feedback=0.5,
reverb=0.7,
reverb_decay=4.0,
)
808 Bass
~~~~~~~~
Subtle saturation and deep filtering for hip-hop sub bass:
.. code-block:: python
bass = score.part(
"bass",
synth="sine",
envelope="pluck",
lowpass=200,
lowpass_q=1.8,
distortion=0.4,
distortion_drive=2.0,
)
Sidechain Compression
---------------------
If you've ever heard a house track where the pad *breathes* — gets
quiet every time the kick hits and swells back up between beats —
that's sidechain compression. It's the pumping effect that defines
modern electronic music. The kick drum triggers a compressor on
another part, ducking its volume in rhythm with the beat.
In PyTheory, the drum hits are the trigger. Any part with
``sidechain > 0`` gets ducked whenever the kick (or any drum) hits:
.. code-block:: python
# Classic EDM pump — pad ducks hard on every kick
pad = score.part(
"pad",
synth="supersaw",
envelope="pad",
sidechain=0.85,
sidechain_release=0.15,
)
# Bass breathes with the kick too, but less aggressively
bass = score.part(
"bass",
synth="sine",
lowpass=250,
sidechain=0.7,
sidechain_release=0.1,
)
Parameters:
- ``sidechain``: How much to duck, 0.01.0 (default 0, off).
0.5 = subtle pump, 0.7 = noticeable, 0.85 = classic EDM, 1.0 = full silence on hits.
- ``sidechain_release``: How fast the volume comes back, in seconds
(default 0.1). Shorter = tighter, longer = more dramatic pump.
The lead stays above the pump — don't sidechain everything or the
whole mix will gasp for air:
.. code-block:: python
# Lead cuts through — no sidechain
lead = score.part(
"lead",
synth="saw",
envelope="pluck",
delay=0.2,
)
Automation
----------
Static effects are fine for a loop, but music breathes. The filter
*opens* during the chorus. The reverb *swells* before the drop. The
distortion *kicks in* when the guitar solo starts. Automation is what
makes a track feel alive instead of robotic -- it's the difference
between a static loop and a piece of music that has dynamics, tension,
and release. If you've ever felt a song "build" toward something,
you're hearing automation at work.
``Part.set()`` changes effect parameters mid-song at the current beat
position. The renderer splits the audio at automation points and
processes each section independently:
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=400, lowpass_q=3.0)
# Verse: filtered and clean
lead.arpeggio("Cm", bars=4, pattern="up", octaves=2)
# Chorus: filter opens, chorus kicks in
lead.set(lowpass=2000, chorus=0.3)
lead.arpeggio("Fm", bars=4, pattern="updown", octaves=2)
# Drop: full send
lead.set(lowpass=4000, distortion=0.7, reverb=0.3)
lead.arpeggio("Gm", bars=4, pattern="updown", octaves=2)
Any parameter can be automated: ``lowpass``, ``lowpass_q``, ``reverb``,
``reverb_decay``, ``delay``, ``delay_time``, ``delay_feedback``,
``distortion``, ``distortion_drive``, ``chorus``, ``volume``.
LFO Automation
--------------
An LFO -- Low Frequency Oscillator -- is just automation that repeats.
Instead of manually setting parameter changes, you let a wave shape do
it for you, cycling back and forth continuously. You already know what
LFOs sound like, even if you don't know the term. The wobble bass in
dubstep? That's an LFO on the filter cutoff. Tremolo on a guitar amp?
LFO on volume. Auto-wah? LFO on filter cutoff with resonance cranked
up. Vibrato? LFO on pitch. It's one simple concept that produces a
huge range of effects.
``Part.lfo()`` automates a parameter with a low-frequency oscillator,
generating smooth sweeps over time. This is how filter sweeps, tremolo,
and auto-wah effects work.
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=400)
# Slow filter sweep: 400 -> 3000 Hz over 8 bars
lead.lfo("lowpass", rate=0.125, min=400, max=3000, bars=8)
lead.arpeggio("Cm", bars=8, pattern="up", octaves=2)
Parameters:
- ``param``: Parameter name to modulate (``"lowpass"``, ``"reverb"``,
``"distortion"``, ``"volume"``, ``"chorus"``, ``"delay"``).
- ``rate``: LFO speed in cycles per bar (default 0.5 = one sweep
every 2 bars). 0.25 = very slow, 1 = once per bar, 4 = four times
per bar.
- ``min`` / ``max``: Parameter value range.
- ``bars``: Number of bars to run the LFO over (default 4).
- ``shape``: Waveform shape.
- ``"sine"`` -- smooth, natural sweep
- ``"triangle"`` -- linear up/down
- ``"saw"`` -- ramp up, snap back
- ``"square"`` -- abrupt on/off
- ``resolution``: How often to insert automation points, in beats
(default 0.25 = every 16th note). Lower values = smoother curves.
Stacking Multiple LFOs
~~~~~~~~~~~~~~~~~~~~~~~
Call ``.lfo()`` multiple times to modulate different parameters
simultaneously:
.. code-block:: python
lead = score.part("lead", synth="saw", lowpass=800, reverb=0.1)
# Filter opens over 8 bars
lead.lfo("lowpass", rate=0.125, min=400, max=4000, bars=8)
# Reverb swells in and out every 2 bars
lead.lfo("reverb", rate=0.5, min=0.1, max=0.6, bars=8, shape="triangle")
# Volume tremolo
lead.lfo("volume", rate=2, min=0.3, max=0.6, bars=8, shape="sine")
lead.arpeggio("Cm", bars=8, pattern="updown", octaves=2)
Effects are what turn notes into music -- the space, the movement, the character. A dry signal is just information; reverb, delay, and filtering are what make it feel like something. Experiment freely, trust your ears.