Skip to main content

Engine

Struct Engine 

Source
pub struct Engine { /* private fields */ }
Expand description

The runtime mixer: owns patch resources and their live instances, and serves their mixed-down stereo through AudioSource::fill.

use tono_core::prelude::*;
use tono_core::dsl::Node;

let doc = SoundDoc::new("blip", Node::Sine { freq: 880.0.into() });
let mut engine = Engine::new(48_000);
let blip = engine.load(&doc);          // a reusable resource…
let _voice = engine.play(blip);        // …spawning independent instances

let mut out = vec![0.0f32; 512];       // interleaved stereo L,R,L,R…
engine.fill(&mut out);
assert!(out.iter().any(|s| s.abs() > 0.0), "the blip is sounding");

§Threading

Mutating calls (play, set_param, stop, …) render the whole document synchronously — O(duration), with allocations — so they must never run on an audio callback thread. Real-time hosts use split to render on a pump thread joined to the callback by a wait-free ring. Note that set_param re-renders the whole doc per call with no coalescing — rate-limit parameter automation on the control side.

Implementations§

Source§

impl Engine

Source

pub fn new(sample_rate: u32) -> Self

A fresh engine that renders at sample_rate.

Examples found in repository?
examples/runtime_mixer.rs (line 25)
21fn main() {
22    let sr = 48_000;
23
24    // --- Direct (single-threaded) use -------------------------------------
25    let mut engine = Engine::new(sr);
26    let patch = engine.load(&blip());
27
28    // One patch → several independent instances, each controlled by handle.
29    let a = engine.play_looping(patch);
30    let b = engine.play_looping(patch);
31    engine.set_gain(b, 0.5, Tween::ms(20.0, sr));
32    engine.set_pan(a, -0.7, Tween::ms(20.0, sr));
33    engine.set_pan(b, 0.7, Tween::ms(20.0, sr));
34
35    let mut block = vec![0.0f32; 512 * 2];
36    let mut p = 0.0f32;
37    for _ in 0..20 {
38        engine.fill(&mut block);
39        p = p.max(peak(&block));
40    }
41    println!("direct mix: {} instances, peak {p:.3}", engine.active());
42
43    // --- Split (control thread + audio thread) ----------------------------
44    let mut engine = Engine::new(sr);
45    let patch = engine.load(&blip());
46    let (mut control, mut audio) = engine.split(2048);
47    control.play_looping(patch); // Deref → Engine::play_looping
48
49    control.pump(2048); // control thread: keep the ring fed
50    let mut out = vec![0.0f32; 512 * 2];
51    let frames = audio.fill(&mut out); // audio thread: drain in the callback
52    println!("split mix: pulled {frames} frames, peak {:.3}", peak(&out));
53}
Source

pub fn set_max_voices(&mut self, max: usize)

Cap the number of concurrently sounding instances. Once at the budget, a new play steals the lowest-Priority sounding instance (declicked, not hard-cut) — or is denied if every instance outranks it. Unset by default (unlimited). A max of 0 is treated as 1.

Source

pub fn max_voices(&self) -> Option<usize>

The current polyphony budget, or None if unlimited.

Source

pub fn sample_rate(&self) -> u32

The engine’s sample rate.

Source

pub fn load(&mut self, doc: &SoundDoc) -> PatchId

Load a bare document as a patch resource (no named parameters).

Examples found in repository?
examples/runtime_mixer.rs (line 26)
21fn main() {
22    let sr = 48_000;
23
24    // --- Direct (single-threaded) use -------------------------------------
25    let mut engine = Engine::new(sr);
26    let patch = engine.load(&blip());
27
28    // One patch → several independent instances, each controlled by handle.
29    let a = engine.play_looping(patch);
30    let b = engine.play_looping(patch);
31    engine.set_gain(b, 0.5, Tween::ms(20.0, sr));
32    engine.set_pan(a, -0.7, Tween::ms(20.0, sr));
33    engine.set_pan(b, 0.7, Tween::ms(20.0, sr));
34
35    let mut block = vec![0.0f32; 512 * 2];
36    let mut p = 0.0f32;
37    for _ in 0..20 {
38        engine.fill(&mut block);
39        p = p.max(peak(&block));
40    }
41    println!("direct mix: {} instances, peak {p:.3}", engine.active());
42
43    // --- Split (control thread + audio thread) ----------------------------
44    let mut engine = Engine::new(sr);
45    let patch = engine.load(&blip());
46    let (mut control, mut audio) = engine.split(2048);
47    control.play_looping(patch); // Deref → Engine::play_looping
48
49    control.pump(2048); // control thread: keep the ring fed
50    let mut out = vec![0.0f32; 512 * 2];
51    let frames = audio.fill(&mut out); // audio thread: drain in the callback
52    println!("split mix: pulled {frames} frames, peak {:.3}", peak(&out));
53}
Source

pub fn load_patch(&mut self, patch: &Patch) -> PatchId

Load a parametric Patch as a resource; its named params become ParamIds via Engine::param.

Source

pub fn param(&self, patch: PatchId, name: &str) -> Option<ParamId>

Resolve a named parameter of a patch to a typed handle (once, off the hot path). None if the patch has no such param.

Source

pub fn layer(&self, patch: PatchId, name: &str) -> Option<LayerId>

Resolve a named mixer layer (a tracks entry) to a typed handle. None if the patch’s graph has no track with that id.

Source

pub fn play(&mut self, patch: PatchId) -> InstanceHandle

Spawn a one-shot instance of a patch (plays once, then culls itself) at Priority::NORMAL.

Source

pub fn play_looping(&mut self, patch: PatchId) -> InstanceHandle

Spawn a looping instance of a patch (plays until stopped) at Priority::NORMAL.

Examples found in repository?
examples/runtime_mixer.rs (line 29)
21fn main() {
22    let sr = 48_000;
23
24    // --- Direct (single-threaded) use -------------------------------------
25    let mut engine = Engine::new(sr);
26    let patch = engine.load(&blip());
27
28    // One patch → several independent instances, each controlled by handle.
29    let a = engine.play_looping(patch);
30    let b = engine.play_looping(patch);
31    engine.set_gain(b, 0.5, Tween::ms(20.0, sr));
32    engine.set_pan(a, -0.7, Tween::ms(20.0, sr));
33    engine.set_pan(b, 0.7, Tween::ms(20.0, sr));
34
35    let mut block = vec![0.0f32; 512 * 2];
36    let mut p = 0.0f32;
37    for _ in 0..20 {
38        engine.fill(&mut block);
39        p = p.max(peak(&block));
40    }
41    println!("direct mix: {} instances, peak {p:.3}", engine.active());
42
43    // --- Split (control thread + audio thread) ----------------------------
44    let mut engine = Engine::new(sr);
45    let patch = engine.load(&blip());
46    let (mut control, mut audio) = engine.split(2048);
47    control.play_looping(patch); // Deref → Engine::play_looping
48
49    control.pump(2048); // control thread: keep the ring fed
50    let mut out = vec![0.0f32; 512 * 2];
51    let frames = audio.fill(&mut out); // audio thread: drain in the callback
52    println!("split mix: pulled {frames} frames, peak {:.3}", peak(&out));
53}
Source

pub fn play_prioritized( &mut self, patch: PatchId, priority: Priority, ) -> InstanceHandle

Spawn a one-shot instance with an explicit Priority. Under a voice cap, a higher priority survives a steal; a voice outranked by every sounding instance is denied (returns an inert handle — is_active is false).

Source

pub fn play_looping_prioritized( &mut self, patch: PatchId, priority: Priority, ) -> InstanceHandle

Spawn a looping instance with an explicit Priority — e.g. music at Priority::CRITICAL so it is never stolen by lower one-shots.

Source

pub fn set_priority(&mut self, h: InstanceHandle, priority: Priority)

Change a live instance’s Priority (no-op for an unknown handle).

Source

pub fn set_gain(&mut self, h: InstanceHandle, gain: f32, tw: Tween)

Set an instance’s linear gain, smoothed over tw (no-op if finished).

Examples found in repository?
examples/runtime_mixer.rs (line 31)
21fn main() {
22    let sr = 48_000;
23
24    // --- Direct (single-threaded) use -------------------------------------
25    let mut engine = Engine::new(sr);
26    let patch = engine.load(&blip());
27
28    // One patch → several independent instances, each controlled by handle.
29    let a = engine.play_looping(patch);
30    let b = engine.play_looping(patch);
31    engine.set_gain(b, 0.5, Tween::ms(20.0, sr));
32    engine.set_pan(a, -0.7, Tween::ms(20.0, sr));
33    engine.set_pan(b, 0.7, Tween::ms(20.0, sr));
34
35    let mut block = vec![0.0f32; 512 * 2];
36    let mut p = 0.0f32;
37    for _ in 0..20 {
38        engine.fill(&mut block);
39        p = p.max(peak(&block));
40    }
41    println!("direct mix: {} instances, peak {p:.3}", engine.active());
42
43    // --- Split (control thread + audio thread) ----------------------------
44    let mut engine = Engine::new(sr);
45    let patch = engine.load(&blip());
46    let (mut control, mut audio) = engine.split(2048);
47    control.play_looping(patch); // Deref → Engine::play_looping
48
49    control.pump(2048); // control thread: keep the ring fed
50    let mut out = vec![0.0f32; 512 * 2];
51    let frames = audio.fill(&mut out); // audio thread: drain in the callback
52    println!("split mix: pulled {frames} frames, peak {:.3}", peak(&out));
53}
Source

pub fn set_pan(&mut self, h: InstanceHandle, pan: f32, tw: Tween)

Set an instance’s stereo balance (−1 left … +1 right), smoothed over tw.

Examples found in repository?
examples/runtime_mixer.rs (line 32)
21fn main() {
22    let sr = 48_000;
23
24    // --- Direct (single-threaded) use -------------------------------------
25    let mut engine = Engine::new(sr);
26    let patch = engine.load(&blip());
27
28    // One patch → several independent instances, each controlled by handle.
29    let a = engine.play_looping(patch);
30    let b = engine.play_looping(patch);
31    engine.set_gain(b, 0.5, Tween::ms(20.0, sr));
32    engine.set_pan(a, -0.7, Tween::ms(20.0, sr));
33    engine.set_pan(b, 0.7, Tween::ms(20.0, sr));
34
35    let mut block = vec![0.0f32; 512 * 2];
36    let mut p = 0.0f32;
37    for _ in 0..20 {
38        engine.fill(&mut block);
39        p = p.max(peak(&block));
40    }
41    println!("direct mix: {} instances, peak {p:.3}", engine.active());
42
43    // --- Split (control thread + audio thread) ----------------------------
44    let mut engine = Engine::new(sr);
45    let patch = engine.load(&blip());
46    let (mut control, mut audio) = engine.split(2048);
47    control.play_looping(patch); // Deref → Engine::play_looping
48
49    control.pump(2048); // control thread: keep the ring fed
50    let mut out = vec![0.0f32; 512 * 2];
51    let frames = audio.fill(&mut out); // audio thread: drain in the callback
52    println!("split mix: pulled {frames} frames, peak {:.3}", peak(&out));
53}
Source

pub fn set_param( &mut self, h: InstanceHandle, param: ParamId, value: f32, tw: Tween, )

Set a named parameter on a live instance, crossfading over tw for a click-free swap. param must come from the same patch the instance plays.

Source

pub fn set_layer_gain( &mut self, h: InstanceHandle, layer: LayerId, gain: f32, tw: Tween, )

Set a named layer’s gain on a live instance, crossfading over tw.

Source

pub fn stop(&mut self, h: InstanceHandle, fade: Tween)

Stop an instance with a declick fade-out; it is culled once silent. A zero-length fade is bumped to a short default so stops never click.

Source

pub fn active(&self) -> usize

Number of live instances.

Examples found in repository?
examples/runtime_mixer.rs (line 41)
21fn main() {
22    let sr = 48_000;
23
24    // --- Direct (single-threaded) use -------------------------------------
25    let mut engine = Engine::new(sr);
26    let patch = engine.load(&blip());
27
28    // One patch → several independent instances, each controlled by handle.
29    let a = engine.play_looping(patch);
30    let b = engine.play_looping(patch);
31    engine.set_gain(b, 0.5, Tween::ms(20.0, sr));
32    engine.set_pan(a, -0.7, Tween::ms(20.0, sr));
33    engine.set_pan(b, 0.7, Tween::ms(20.0, sr));
34
35    let mut block = vec![0.0f32; 512 * 2];
36    let mut p = 0.0f32;
37    for _ in 0..20 {
38        engine.fill(&mut block);
39        p = p.max(peak(&block));
40    }
41    println!("direct mix: {} instances, peak {p:.3}", engine.active());
42
43    // --- Split (control thread + audio thread) ----------------------------
44    let mut engine = Engine::new(sr);
45    let patch = engine.load(&blip());
46    let (mut control, mut audio) = engine.split(2048);
47    control.play_looping(patch); // Deref → Engine::play_looping
48
49    control.pump(2048); // control thread: keep the ring fed
50    let mut out = vec![0.0f32; 512 * 2];
51    let frames = audio.fill(&mut out); // audio thread: drain in the callback
52    println!("split mix: pulled {frames} frames, peak {:.3}", peak(&out));
53}
Source

pub fn is_active(&self, h: InstanceHandle) -> bool

Whether a handle still refers to a live instance.

Source§

impl Engine

Source

pub fn split(self, ring_frames: usize) -> (Controller, Renderer)

Split into a Controller (control thread) and a Renderer (audio thread) joined by a wait-free ring ring_frames deep. Pump the controller off the audio thread; the renderer drains it in the callback.

Examples found in repository?
examples/runtime_mixer.rs (line 46)
21fn main() {
22    let sr = 48_000;
23
24    // --- Direct (single-threaded) use -------------------------------------
25    let mut engine = Engine::new(sr);
26    let patch = engine.load(&blip());
27
28    // One patch → several independent instances, each controlled by handle.
29    let a = engine.play_looping(patch);
30    let b = engine.play_looping(patch);
31    engine.set_gain(b, 0.5, Tween::ms(20.0, sr));
32    engine.set_pan(a, -0.7, Tween::ms(20.0, sr));
33    engine.set_pan(b, 0.7, Tween::ms(20.0, sr));
34
35    let mut block = vec![0.0f32; 512 * 2];
36    let mut p = 0.0f32;
37    for _ in 0..20 {
38        engine.fill(&mut block);
39        p = p.max(peak(&block));
40    }
41    println!("direct mix: {} instances, peak {p:.3}", engine.active());
42
43    // --- Split (control thread + audio thread) ----------------------------
44    let mut engine = Engine::new(sr);
45    let patch = engine.load(&blip());
46    let (mut control, mut audio) = engine.split(2048);
47    control.play_looping(patch); // Deref → Engine::play_looping
48
49    control.pump(2048); // control thread: keep the ring fed
50    let mut out = vec![0.0f32; 512 * 2];
51    let frames = audio.fill(&mut out); // audio thread: drain in the callback
52    println!("split mix: pulled {frames} frames, peak {:.3}", peak(&out));
53}

Trait Implementations§

Source§

impl AudioSource for Engine

Source§

fn fill(&mut self, out: &mut [f32]) -> usize

Fill out with the next block of interleaved-stereo audio.
Source§

fn reset(&mut self)

Rewind the source to its start (playback position / phase to zero). Defaults to a no-op; a looping source overrides it so a transport can restart it from the top. AdaptiveMusic::reset calls this on each layer.

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> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more