I’ve been writing command-line tools that send nonlinear and chaotic signals into the modular through the audio interface. These are the working notes: what shaping is, what chaos is, which curves are worth having, and which systems to build.
Shape is not chaos
A formula like x*x has no memory. Identical inputs always produce identical outputs. Chaos needs state that evolves — through feedback, delay, forcing, or coupled variables.
So there are two binaries, not one.
cv_transform is memoryless: one input sample, one selected function, one output sample. Square, cube, rectification, saturation, folding.
./cv_transform --mode square --input 1 --output 1
./cv_transform --mode cube --input 1 --output 1
./cv_transform --mode sine --drive 2.5 --input 1 --output 1
cv_chaos owns persistent state, integrators, delay buffers, parameter CV and multiple outputs.
./cv_chaos --engine logistic --output 1
./cv_chaos --engine duffing --input 1 --outputs 1,2
./cv_chaos --engine lorenz --outputs 1,2,3
Keeping them apart means a transform failure can’t destabilize the chaos engine, and the simple binary stays easy to verify. Device discovery, channel routing, clipping and error handling live in shared source files.
The transfer curve
Pick a transformation and move the drive. The left chart is the rule; the right one runs a bipolar sine through it, sample by sample.
Smooth odd functions are friendliest in feedback. Even functions create positive DC. Discontinuous functions make the most high-frequency energy and aliasing.
| Function | Shape | CV effect | Audio effect | Role |
|---|---|---|---|---|
x² | even / unipolar | rectification, LFO doubling | DC and even harmonics | shaper |
x³ | odd / bipolar | gentle center, strong extremes | odd harmonics, no DC | feedback shaper |
x − x³ | odd / non-monotonic | competing regions with feedback | turning-point overtones | restoring force |
tanh(gx) | odd / bounded | smooth runaway control | rounded peak limiting | limiter |
sin(gx) | periodic folds | repeating control zones | bright folded spectrum | feedback map |
sign(x) | discontinuous | gate extraction | square-wave spectrum | switch |
One equation, different regimes
The logistic map feeds its output back into itself: x[n+1] = r·x[n]·(1 − x[n]). As r rises, one stable value splits into two, four, eight, and eventually a dense chaotic set.
Below r = 3 it settles on one value. Between 3 and about 3.57 it doubles. Above that it’s mostly chaos, interrupted by periodic windows.
Four engines
Each keeps state between PortAudio callbacks. The same engine can be slow CV or an audio oscillator — change the update rate or the integration time scale, not the mathematical structure. Use double internally and float at the I/O boundary, and allocate every buffer before PortAudio starts.
float raw = engine_tick(&engine, input[i]);
float y = (float)(raw * output_scale);
/* Protect the physical output, not the internal state. */
if (y > 1.0f) y = 1.0f;
if (y < -1.0f) y = -1.0f;
output[i] = y;
No allocation, file access, locks or printing inside engine_tick(). Detect non-finite state there, but report it from the main thread.
Logistic map — one state, discrete
Cycles, doubling, digital chaos. Clocked slowly it’s stepped deterministic modulation; at sample rate it’s bright digital noise, and dividing the update rate gives pitch.
Store x, r, the update rate and a phase accumulator. When the accumulator crosses one update, run the map once; otherwise hold.
phase += update_rate / sample_rate;
if (phase >= 1.0) {
phase -= 1.0;
x = r * x * (1.0 - x);
}
out = 2.0 * x - 1.0;
Initial x 0.417, r 3.90, CV update 1–20 Hz. Input CV maps r into roughly 2.5–4.0; the output is the bipolar map state. Never seed exactly 0 or 1.
Duffing — two states plus drive
The one I want to build next. It moves from tone to subharmonics to noisy transitions and chaotic motion; as CV, position and velocity are related wandering controls.
Store position, velocity and forcing phase. Calculate both derivatives from one consistent state and integrate with RK4.
dx = v;
dv = x - x*x*x
- damping*v
+ drive*cos(phase)
+ input_gain*input;
dt = simulation_speed / sample_rate;
Damping 0.20, drive 0.30, omega 1.20, initial x,v 0.1 and 0. Input 1 is external force; output 1 scaled position, output 2 scaled velocity. More inputs later for damping, drive and speed.
Lorenz — three coupled states
Three related but distinct outputs: pitch, timbre, spatial modulation. Scaled into audio rates it’s turbulent oscillation.
dx = sigma * (y - x);
dy = x * (rho - z) - y;
dz = x * y - beta * z;
RK4 has to advance the three together — don’t update one and use its new value for the next. Sigma 10, rho 28, beta 8/3, initial x,y,z 0.1, 0, 0. Outputs 1–3 are separately scaled x, y, z; a CV input can modulate rho. Never clip the internal states.
Mackey–Glass — one state plus history
Chaos from delayed feedback. A long delay gives smooth aperiodic contours; a short one gives pitched feedback that breaks into texture.
Allocate the circular history buffer before PortAudio starts. Each update reads the value from delay_samples ago, advances x, then writes the new state.
delayed = history[read_index];
dx = beta*delayed
/ (1.0 + pow(delayed, exponent))
- gamma*x;
x += dt * dx;
history[write_index] = x;
Beta 0.20, gamma 0.10, exponent 10, delay 17. Changing the delay continuously needs fractional-delay interpolation.
Feedback through the rack
Send the chaotic state out, run it through attenuation, bias, filtering and a VCA, and bring it back in. The rack becomes part of the equation and the USB round-trip becomes its delay.
Start with heavy attenuation. Uncontrolled feedback usually hits clipping or a rail before it finds anything interesting. x*x also goes entirely positive, so signed functions — x - x*x*x, tanh, sin — work better in the loop.
Rules
Protect the output, not the attractor.
- Don’t clip state variables to ±1. That changes the equations and can destroy the attractor.
- Map state to interface range after updating it, then limit the physical output sample.
- Use RK4 or another tested integrator for Duffing, Lorenz and Rössler.
- Detect NaN or infinity, reset to a small nonzero seed, report outside the callback.
- Expect aliasing. Folds, switching and chaotic audio all put energy above Nyquist.
- Test stable, periodic and chaotic parameter sets, not just the arithmetic.
Also: normalized samples are not volts. Measure and calibrate the interface before trusting any exact voltage relationship.
Build order
Extract the square processor into cv_transform and add cube, double-well, tanh and sine-fold. Pull device discovery, routing, clipping and error handling into shared I/O. Then cv_chaos with a clean stateful engine interface and the logistic map. Then Duffing with RK4, force input, position and velocity outputs. Then multi-channel input mapping. Lorenz and Mackey–Glass come after the multi-output and delay-buffer tests are in place.
Rössler and Chua after that.