Primitives
primitives::automation
Parameter envelopes and DAW-style automation modes.
Purpose
primitives::automation depends only on the primitives root and
primitives::realtime, and drives the same Arc<AtomicF32> handles that
strip/node parameters already expose — no new parameter system required.
Key types
Envelope/EnvelopePoint/CurveShape::{Hold, Linear, Curve}— sorted breakpoints; sample-accurateevaluate/fill_block.AutomationMode::{Off, Read, Touch, Latch, Write}plusAutomationState— touch/latch state machine.AutomationLane— one param: envelope + mode + boundArc<AtomicF32>. Callapply_block(block_start, frames, playing, scratch)once per audio block.AutomationBank— several lanes applied together.ParamTarget— implemented forAtomicF32andArc<AtomicF32>.
Usage
use std::sync::Arc;
use superb::primitives::automation::{AutomationLane, AutomationMode, EnvelopePoint};
use superb::primitives::{IdGenerator, TimePosition};
use superb::primitives::realtime::AtomicF32;
let ids = IdGenerator::new();
let gain = Arc::new(AtomicF32::new(1.0));
let mut lane = AutomationLane::new(ids.next(), Arc::clone(&gain));
lane.envelope_mut()
.upsert(EnvelopePoint::linear(TimePosition::ZERO, 0.0));
lane.envelope_mut()
.upsert(EnvelopePoint::linear(TimePosition::from_samples(100), 1.0));
lane.set_mode(AutomationMode::Read);
lane.apply_block(TimePosition::from_samples(50), 16, true, None);
assert!((gain.get() - 0.5).abs() < 1e-5);Write / touch / latch:
# use std::sync::Arc;
# use superb::primitives::automation::{AutomationLane, AutomationMode};
# use superb::primitives::{IdGenerator, TimePosition};
# use superb::primitives::realtime::AtomicF32;
# let ids = IdGenerator::new();
# let gain = Arc::new(AtomicF32::new(1.0));
# let mut lane = AutomationLane::new(ids.next(), Arc::clone(&gain));
lane.set_mode(AutomationMode::Touch);
lane.begin_touch(); // UI grabbed the fader
gain.set(0.25); // UI writes the live value
lane.apply_block(TimePosition::from_samples(0), 64, true, None);
lane.end_touch(); // back to reading the envelopeReal-time notes
- Envelope edits (
upsert,clear,recordinserts) allocate — keep them off the audio thread, or throttle write-mode withset_write_interval_samplesand accept that recording happens on a non-RT apply path / control thread. - The read path of
apply_blockonly evaluates math and stores into atomics / a caller-provided scratch slice — no heap traffic. - Nodes that read their param once per block see the value at
block_start. For true per-sample automation, pass a scratch buffer and have the node consume it.
Modes (while playing)
| Mode | Envelope → param | Param → envelope |
|---|---|---|
| Off | no | no |
| Read | yes | no |
| Touch | yes until touched; write while held | while touched |
| Latch | yes until first touch; then write until stop | after first touch |
| Write | no | continuously |
When not playing, every mode is a no-op for both directions.
See examples/automation_gain_ramp for an offline ramp printed to stdout.