Skip to main content

CompiledVoice

Struct CompiledVoice 

Source
pub struct CompiledVoice {
    pub patch: Patch,
    pub pitch: Arc<AtomicF64>,
    pub gate: Arc<AtomicF64>,
    pub params: HashMap<String, ParamHandle>,
    pub warnings: Vec<String>,
    pub taps: HashMap<String, (String, PortId)>,
}
Expand description

A compiled, playable voice: the patch plus its external control handles.

Fields§

§patch: Patch

The compiled quiver patch (output already selected and compiled).

§pitch: Arc<AtomicF64>

Pitch control, V/Oct (0 V = C4). Shared with the patch.

§gate: Arc<AtomicF64>

Gate control (≥ 2.5 V = on). Shared with the patch.

§params: HashMap<String, ParamHandle>

Live parameter handles, keyed by the knob’s trace address (node/0#cut, amp#attack, node/0#table, node/0#oct, …). Everything the panel can move without a recompile.

§warnings: Vec<String>

Signal-kind warnings accumulated while wiring (Warn mode).

§taps: HashMap<String, (String, PortId)>

Where each term node’s audio leaves it: trace key → the name of the quiver node carrying its output, and the port id on that node.

Named rather than NodeId-keyed because that is what StateObserver::add_subscriptions takes, and because a name survives the patch being rebuilt while a NodeId does not — the rack asks for a tap by the key of the module the player is looking at, and the answer has to still mean something after the next swap.

Every tap here already has a consumer, which is why nothing needs StateObserver::sync_output_keepalive: the genome is a typed tree, so each module’s output feeds exactly one parent and quiver is already producing it. That call exists for ports nothing reads, and it dirties the patch — a recompile the audio thread would have to be staged around. Metering here costs no recompile at all.

Implementations§

Source§

impl CompiledVoice

Source

pub fn env_phase(&self) -> f64

Where the amp envelope is right now, 0..1 — quiver’s 0–10 V env output scaled back down.

Reads the routing’s last computed value, so it is meaningful after the voice has ticked at least once and zero before that.

Source

pub fn seed_env_phase(&mut self, level: f64) -> bool

Fast-forward this voice’s amp envelope to level (0..1), with the gate already high, so a note carried across a patch swap resumes where it was instead of re-attacking from silence.

§Why this is a pre-roll and not a setter

The plan asked for an envelope-phase getter and setter on this type. The getter is above and is honest. The setter cannot be: quiver’s Adsr keeps stage and level private and exposes no parameter, state-serialization or introspection surface for them (GraphModule gives it port_spec/tick/reset/set_sample_rate/type_id and nothing else), so there is no way to write a level into it from outside. The alternative was to fork the ADSR into this crate to gain two accessors, which would put a hand-copy of quiver’s envelope arithmetic — Libm transcendentals and all — on the critical path of every patch the instrument has ever rendered. That is a rendered-audio change dressed as a refactor.

So the envelope is driven to the level the same way the player would: the attack and decay CVs are pinned to their fastest (1 ms) settings, the patch is ticked in silence until env arrives, and the CVs are put back. It costs a few hundred ticks — well inside the swap’s silent window, which already budgets a whole voice compile per quantum — and it leaves the envelope in the stage the level implies, which is the part that actually matters:

  • below sustain, only Attack can be there, so the rise stops on arrival and the envelope goes on attacking at its real rate;
  • at or above sustain, the note is in Decay or Sustain, so the rise runs to the peak (which is what puts quiver’s stage machine into Decay) and then falls to the level.

A side effect worth naming: the pre-roll is real audio, so it also primes the new voice’s filters and delay lines with ~15 ms of its own signal rather than handing the fade-in an empty reverb. Tails still do not transfer across a rewire — that is [R3] and is accepted.

Returns whether anything was seeded.

[R3]: the panel plan’s §4 risk register.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,