mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 23:00:20 +00:00
b239e9a997
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>
598 lines
18 KiB
ReStructuredText
598 lines
18 KiB
ReStructuredText
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.0–1.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.
|