Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion AUDIODSP_PIN
Original file line number Diff line number Diff line change
Expand Up @@ -470,5 +470,17 @@
# AirSpace's get_buffer digests are re-read. A sampler voice that sets none
# of the new keywords renders the bytes it did.
#
# Moved to 84b8b45 on 2026-11-10, for the level ramps (audiodsp#400): the
# Mixer's `level_ramp`, the 20 ms walk of a Mix, post_gain or makeup move in
# the Waveshaper, the Biquad, the AllPass, the Dynamics node and the
# FeedbackDelay, and `audioroute.Port.play(after=)`, which the effects use so
# a jump of Mix or a level walks instead of stepping. The walks
# change what a moved control renders and nothing else: a node's first value
# lands at once, so a fresh instance renders the bytes it did. What else
# moved beneath the components, by `git log 0c0824e..c0e7c61 -- src`: new
# audiograph nodes and Graph.carry and swap, audiograph's BIQUAD_RAMP,
# CircuitPython main's widened synthio biquad, MP3 decoding on CPython, type
# stubs for thirteen modules, and pump and build changes for boards.
#
# <ref> <commit> -- ref is what CI installs (git+https://...@<ref>)
0c0824e3966998613d8c5314cefb1d752bc70693 0c0824e3966998613d8c5314cefb1d752bc70693
84b8b45f5b838afe3529f53e1468abe7711b6bcc 84b8b45f5b838afe3529f53e1468abe7711b6bcc
6 changes: 3 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ way on every interpreter and board.

## Next

- Control moves that ramp instead of stepping: Mix, levels and depths over
20 ms in audiodsp's nodes, then filter corners, then crossfaded patch
changes. [The policy](docs/how-it-works/control-smoothing.md).
- Control moves that ramp instead of stepping where they still step: Fuzz's
way back from Mix 0 and Tremolo's Depth, then filter corners, then
crossfaded patch changes. [The policy](docs/how-it-works/control-smoothing.md).

## Later

Expand Down
59 changes: 34 additions & 25 deletions docs/how-it-works/control-smoothing.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
# Smoothing control moves

When a control jumps, the effects hand the new value to their nodes at once,
and the output steps. You hear a click. That happens on a patch change, on a
MIDI value that leaps, or when a host sets Mix from 0.3 to 1.0 in one call. A
knob turned by hand sends many small moves, so it sounds smooth already.

If you need a jump smooth, send it as small steps, one a block: the delays'
own tests show Mix moved in 127 steps, one a block, steps the output by less
than one and a half times the steepest step of the tone going through it.

The rest of this page is the policy planned for the effects, so that a jump
stops clicking by itself. None of it is built in the effects here yet.
audiodsp's `audiograph` `BIQUAD` node already walks to new coefficients over
`BIQUAD_RAMP` milliseconds, 20 by default, but these effects drive
`audiobiquad` directly. It is on the
When you jump a level or a Mix, these effects walk the output to the new
setting over 20 ms instead of stepping there, so the jump doesn't click. That
covers a patch change's levels, a MIDI value that leaps, and a host setting
Mix from 0.3 to 1.0 in one call: Bitcrusher, Chorus, CombFilter, Compressor,
DeEsser, Distortion, DynamicEQ, Exciter, Fuzz, MultibandCompressor, Overdrive,
Phaser, Saturation, Tremolo and Vibrato, with the exceptions below. The Mix
of DigitalDelay and PingPongDelay and SlapbackDelay's Level walk too, since
their delay node does.

A few moves still step. Fuzz coming back from Mix 0 cuts in, Tremolo's Depth
jumps, a filter corner moves at once, and so does a patch change that rebuilds
part of a graph. Until those walk too, send such a jump as small steps, one a
block: the delays' own tests show Mix moved in 127 steps, one a block, steps
the output by less than one and a half times the steepest step of the tone
going through it. The rest is on the
[roadmap](https://github.com/PyDevices/audioeffects/blob/main/ROADMAP.md).

## Which controls will ramp, and for how long
## Which controls ramp, and for how long

Every control that scales a signal will ramp: Mix, Level and Volume, a wet or dry
gain, and a modulation depth. A move will ramp in a straight line over 20 ms, the
time `audioecho.FeedbackDelay` already takes for its wow depth. That is short
enough to sound like the knob you turned. Whether it is long enough to take
the click out of every jump is the step property's to say (below), once the
ramp is built.
Every control that scales a signal ramps: Mix, Level, Volume, Output and
Makeup, a band's gain, a wet or dry gain, and a modulation depth. A move
walks in a straight line over 20 ms, the time `audioecho.FeedbackDelay`
already takes for its wow depth. That is short enough to sound like the knob
you turned, and the lifecycle matrix's step property (below) holds it to not
clicking.

Mix 0 is still your signal, byte for byte. An effect that hands you the
source itself there walks its graph to the dry first and switches once the
walk has landed, and walks back in from the dry when Mix leaves 0.

A delay's Time is not ramped here: it already glides, at the rate its Glide
control sets. A filter corner that jumps steps the output too, more quietly
than a level does; it comes after the levels, with the node moving its
coefficients across the same 20 ms.

A switch, or a patch change that rebuilds part of a graph, crossfades from the
old graph to the new over the same 20 ms instead of cutting.
A switch, or a patch change that rebuilds part of a graph, will crossfade from
the old graph to the new over the same 20 ms instead of cutting.

## Where the ramp lives

Expand All @@ -42,8 +47,12 @@ only if the host calls something every block. Built into the node, the ramp
costs a multiply a sample, runs on every interpreter and every board, and
reaches anyone using the bare nodes too, CircuitPython users included.

Mix comes first, because it is the control most likely to be jumped and the
loudest when it is.
The nodes that walk are `audiomixer.Mixer`'s voice levels (its `level_ramp`),
the Waveshaper's `mix` and `post_gain`, the Biquad's and the AllPass's `mix`,
the Dynamics node's makeup and the FeedbackDelay's `mix`. An
`audioroute.Port` told `play(source, after=frames)` keeps playing what it
plays until a walk has landed, which is how Mix 0 switches to the source
without a cut.

## When it is done

Expand Down
Loading
Loading