Skip to content

Ids, tracks and routing

  • Id is a u64. Project::alloc_id hands out next_id and increments it; the counter is saved, so ids are unique within a project and never reused. Tracks, regions, clips, plug-in instances (PluginSlot::uid), groups, markers and takes all draw from it.
  • MASTER_ID = 0 is reserved for Stereo Out (the Master struct, not a Track).
  • Plug-in instance ids restart in every project, which is why open clears all controllers before loading.

Track::kind is one of Audio, Instrument, Folder (folder stack), Summing (summing stack) or Aux. Stacks are expressed with Track::parent, pointing at the stack’s main track; collapsed hides a stack’s subtracks in the views.

Invariants, maintained by Project::normalize_tree and prune_routes:

  • project.tracks is in display order, and every stack’s subtracks sit directly below it (depth first). normalize_tree re-sorts the list that way, drops parent links to tracks that aren’t stacks (or to the track itself) and breaks cycles.
  • Auxes are never inside stacks; move_tracks refuses it and prune_routes clears it.
  • Every track has SEND_SLOTS (8) send slots and INSERT_SLOTS (8) insert slots.

Session::sync_tracks is the one call to make after tracks are added, removed, moved or restacked: it runs prune_routes, normalize_tree, sends Cmd::Order(process_order()) and then every track’s parameters.

Processing order (Project::process_order) is different from display order: every track comes before whatever it plays into (its summing stack, its output aux, the auxes it sends to). The base order is the tree with subtracks before their stack; a depth-first walk over the “who feeds me” graph then moves receivers after their senders. The engine processes tracks in exactly this order, so a bus’s input (sum_l/sum_r) is complete by the time the bus runs.

What the engine plays for a track is worked out by Session::track_params, which applies the stacks above it: a folder’s fader adds to its subtracks’ volume (stack_gain), a muted stack mutes them (stack_mute), solo reaches down from a soloed stack and up from a soloed subtrack, and solo follows routing (a soloed track keeps the auxes it feeds audible; a soloed aux keeps its feeders audible). Because one track’s change can reach others, stack members and solo changes resend every track (push_all_params).

VCA faders (Project::vcas, Track::vca; rules in vca.rs, edits in session/vca.rs) never reach the engine as objects. track_params adds the VCA chain’s offset (levels add up through nested VCAs; one at -96 dB silences) to the volume and to stack_gain, and its mute to mute and stack_mute, exactly like a folder’s fader. So the fader smoothing covers VCA moves, volume and mute automation of a member combine with the VCA (the engine multiplies the automated value by stack_gain), and every render path (bounce, export, Bounce in Place with the fader on) follows it. A freeze is rendered before the fader, so a frozen track’s VCA keeps working. VCA edits resend all tracks’ parameters without re-solving delay compensation (push_vca_params); undo needs nothing extra, since restore() ends with push_all_params. VCA levels can’t be automated: automation lanes belong to tracks, and a VCA has none.

session/routing.rs and model/project.rs:

  • Track::output: None is the default route (the nearest summing stack the track sits in, else Stereo Out), Some(MASTER_ID) forces Stereo Out (skipping the stack), Some(aux) plays into an aux. Project::output_of resolves it.
  • Track::sends: up to 8 TrackSend { bus, level_db, pre_fader, muted }. bus is an aux’s track id. Auxes are fed by “Bus N” (Track::bus), assigned by free_bus.
  • Project::feeds(id) is the set of tracks id plays into directly; reaches(from, to) is its transitive closure.
  • Loops are refused at edit time: check_route rejects a send or output to a non-aux, to itself, or to an aux that already reaches the track. prune_routes also drops dangling routes, routing on folders, and any edge that closes a loop in a loaded file, so process_order always terminates.
  • The engine re-resolves routes (Engine::resolve_routes) whenever the order or a track’s routing changes, storing the index of each destination (out_idx, send_idx). A route that points backwards in the list is ignored, and that track plays to Stereo Out.