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
impl Engine
Sourcepub fn new(sample_rate: u32) -> Self
pub fn new(sample_rate: u32) -> Self
A fresh engine that renders at sample_rate.
Examples found in repository?
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}Sourcepub fn set_max_voices(&mut self, max: usize)
pub fn set_max_voices(&mut self, max: usize)
Sourcepub fn max_voices(&self) -> Option<usize>
pub fn max_voices(&self) -> Option<usize>
The current polyphony budget, or None if unlimited.
Sourcepub fn sample_rate(&self) -> u32
pub fn sample_rate(&self) -> u32
The engine’s sample rate.
Sourcepub fn load(&mut self, doc: &SoundDoc) -> PatchId
pub fn load(&mut self, doc: &SoundDoc) -> PatchId
Load a bare document as a patch resource (no named parameters).
Examples found in repository?
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}Sourcepub fn load_patch(&mut self, patch: &Patch) -> PatchId
pub fn load_patch(&mut self, patch: &Patch) -> PatchId
Load a parametric Patch as a resource; its named params become
ParamIds via Engine::param.
Sourcepub fn param(&self, patch: PatchId, name: &str) -> Option<ParamId>
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.
Sourcepub fn layer(&self, patch: PatchId, name: &str) -> Option<LayerId>
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.
Sourcepub fn play(&mut self, patch: PatchId) -> InstanceHandle
pub fn play(&mut self, patch: PatchId) -> InstanceHandle
Spawn a one-shot instance of a patch (plays once, then culls itself) at
Priority::NORMAL.
Sourcepub fn play_looping(&mut self, patch: PatchId) -> InstanceHandle
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?
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}Sourcepub fn play_prioritized(
&mut self,
patch: PatchId,
priority: Priority,
) -> InstanceHandle
pub fn play_prioritized( &mut self, patch: PatchId, priority: Priority, ) -> InstanceHandle
Sourcepub fn play_looping_prioritized(
&mut self,
patch: PatchId,
priority: Priority,
) -> InstanceHandle
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.
Sourcepub fn set_priority(&mut self, h: InstanceHandle, priority: Priority)
pub fn set_priority(&mut self, h: InstanceHandle, priority: Priority)
Change a live instance’s Priority (no-op for an unknown handle).
Sourcepub fn set_gain(&mut self, h: InstanceHandle, gain: f32, tw: Tween)
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?
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}Sourcepub fn set_pan(&mut self, h: InstanceHandle, pan: f32, tw: Tween)
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?
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}Sourcepub fn set_param(
&mut self,
h: InstanceHandle,
param: ParamId,
value: f32,
tw: Tween,
)
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.
Sourcepub fn set_layer_gain(
&mut self,
h: InstanceHandle,
layer: LayerId,
gain: f32,
tw: Tween,
)
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.
Sourcepub fn stop(&mut self, h: InstanceHandle, fade: Tween)
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.
Sourcepub fn active(&self) -> usize
pub fn active(&self) -> usize
Number of live instances.
Examples found in repository?
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}Sourcepub fn is_active(&self, h: InstanceHandle) -> bool
pub fn is_active(&self, h: InstanceHandle) -> bool
Whether a handle still refers to a live instance.
Source§impl Engine
impl Engine
Sourcepub fn split(self, ring_frames: usize) -> (Controller, Renderer)
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?
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
impl AudioSource for Engine
Source§fn fill(&mut self, out: &mut [f32]) -> usize
fn fill(&mut self, out: &mut [f32]) -> usize
out with the next block of interleaved-stereo audio.Source§fn reset(&mut self)
fn reset(&mut self)
AdaptiveMusic::reset
calls this on each layer.