Mixer & Effect Chain
All timeline audio goes through one function, vv_audio::mixer::mix_range. The preview calls it from the cpal callback and the export calls it on its own thread, so both produce the same mix.
Signal chain

| Constant | Value and why |
|---|---|
PROJECT_SAMPLE_RATE | 48 kHz for every mix. |
MIX_BLOCK_FRAMES | 512: the mix goes track by track through scratch buffers of this size. |
GAIN_BLOCK_FRAMES | 800 (~60 Hz): keyframed clip gain is evaluated per block, not per sample, at the block start so the way a mix is split into calls never changes it. |
Strips and inserts
Every audio track and the master have a ChannelStrip (vv-core): fader in dB, balance, and an ordered list of effects. Before mixing, each strip becomes a Channel: one Insert per effect, then the bus gain. Balance is not a pan law: the centre leaves both channels at unity, moving to one side only attenuates the other.
| Effect | Implementation |
|---|---|
| Normalize | A gain toward a target, from a level measured through the effects before it: sample peak, true peak, or integrated loudness (EBU R128 / BS.1770, loudness.rs). On a track, Relative applies one gain to all clips; Independent gives each clip its own gain (Insert::ClipGains). Levels are measured in the background (LevelAnalysis); until a new reading arrives the previous one keeps playing. |
| Multiband compressor | Linkwitz-Riley crossovers into 3 bands, one stereo-linked compressor per band, soft knee (dynamics.rs). |
| Equalizer | 6 biquad bands in series (eq.rs). |
| Mono | Every channel gets the average of all channels. |
Compressor and EQ keep filter state between blocks in a MixState. A call that does not continue from the previous position resets it. Meters (per-track and master peaks, compressor band activity, spectra for the EQ and compressor panels) are atomics that only the metered call writes.
Preview: from the UI thread to the callback

- One stream for the whole session.
Mixer::newopens a singlecpaloutput stream at 48 kHz with the device's native channel count. Asking for a different count makes PipeWire insert a remix that adds latency. - Immutable snapshots.
TimelineAudio(vv-app) rebuilds aMixSnapshotwhen the history generation changes or a buffer arrives, then publishes it throughMixer::set_state. - Real-time safe. The callback only uses
try_lock, so it never blocks. If it cannot take a lock it plays silence for that buffer. Effect state is allocated on the UI thread, and states the callback dropped are freed there too (retained). - Clock. The mixer position, in timeline samples, is the playhead. A gap in the timeline is just silence while the clock keeps running. Without an output device the clock is wall time.
- Partial buffers.
mix_buffers.rsdecodes and resamples each (file, stream) on a worker and publishes the buffer while it grows, so the start of a track plays before decoding finishes. - Compound clips. Their audio is the mixdown of the nested timeline (
compound_mixdown), cached only when complete. - Scrub. Scrubbing plays an 80 ms snippet of the mix at the new position.
No comments to display
No comments to display