pub struct Scheduler {
pub graph: DspGraph,
pub sample_rate: f32,
pub muted: bool,
}Expand description
Real-time audio scheduler.
The scheduler owns the DSP graph and processes audio in fixed-size blocks (64 samples by default). It executes nodes in topologically sorted order, with nodes at the same BFS level running in parallel via Rayon.
§Real-Time Safety
- ✅ No allocation in audio thread
- ✅ No locks in audio thread
- ✅ Bounded execution time
- ✅ Lock-free command processing via SPSC ring
§Example
use aether_core::scheduler::Scheduler;
use aether_core::node::DspNode;
use aether_core::param::ParamBlock;
use aether_core::{BUFFER_SIZE, MAX_INPUTS};
// Create a simple oscillator node
struct Oscillator {
frequency: f32,
phase: f32,
}
impl DspNode for Oscillator {
fn process(&mut self, _inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
output: &mut [f32; BUFFER_SIZE], _params: &mut ParamBlock, sample_rate: f32) {
let phase_inc = self.frequency / sample_rate;
for sample in output.iter_mut() {
*sample = (self.phase * std::f32::consts::TAU).sin() * 0.3;
self.phase = (self.phase + phase_inc).fract();
}
}
fn type_name(&self) -> &'static str { "Oscillator" }
}
// Create scheduler and add node
let mut sched = Scheduler::new(48_000.0);
let osc = Box::new(Oscillator { frequency: 440.0, phase: 0.0 });
let id = sched.graph.add_node(osc).unwrap();
sched.graph.set_output_node(id);
// Process one audio block
let mut output = vec![0.0f32; 128];
sched.process_block_simple(&mut output);§Performance
- Latency: 1.33ms @ 48kHz (64 samples)
- Throughput: 1000+ nodes @ <100µs processing time
- Memory: Pre-allocated arena + buffer pool
Fields§
§graph: DspGraph§sample_rate: f32§muted: boolImplementations§
Source§impl Scheduler
impl Scheduler
Sourcepub fn process_block<C>(&mut self, cmd_consumer: &mut C, output: &mut [f32])
pub fn process_block<C>(&mut self, cmd_consumer: &mut C, output: &mut [f32])
Processes one audio block with command draining.
Call this from your audio thread (e.g., CPAL stream callback).
It drains up to MAX_COMMANDS_PER_TICK commands from the ring buffer,
applies them to the graph, then processes all nodes in topological order.
§Arguments
cmd_consumer- SPSC consumer for control commands from UI/control threadoutput- Interleaved stereo output buffer (length = BUFFER_SIZE * 2)
§Real-Time Safety
This function is real-time safe:
- No allocations
- No locks (uses lock-free SPSC ring)
- Bounded execution time
- Parallel node execution within BFS levels
§Example
use aether_core::scheduler::Scheduler;
use aether_core::command::Command;
use ringbuf::{HeapRb, traits::Split};
let mut sched = Scheduler::new(48_000.0);
let (mut producer, mut consumer) = HeapRb::<Command>::new(1024).split();
// In audio thread callback:
let mut output = vec![0.0f32; 128]; // 64 frames * 2 channels
sched.process_block(&mut consumer, &mut output);§See Also
process_block_simple- Simplified version without command ring
Sourcepub fn process_block_simple(&mut self, output: &mut [f32])
pub fn process_block_simple(&mut self, output: &mut [f32])
Processes one audio block without command draining.
Simplified version of process_block that doesn’t
drain commands from a ring buffer. Use this when the scheduler is shared
via Arc<Mutex<>> and the control thread mutates it directly.
§Arguments
output- Interleaved stereo output buffer (length = BUFFER_SIZE * 2)
§Real-Time Safety
This function is real-time safe:
- No allocations
- No locks (assumes caller holds lock)
- Bounded execution time
§Example
use aether_core::scheduler::Scheduler;
use aether_core::BUFFER_SIZE;
let mut sched = Scheduler::new(48_000.0);
// Process one block
let mut output = vec![0.0f32; BUFFER_SIZE * 2];
sched.process_block_simple(&mut output);§See Also
process_block- Version with command ring buffer
Auto Trait Implementations§
impl Freeze for Scheduler
impl !RefUnwindSafe for Scheduler
impl Send for Scheduler
impl !Sync for Scheduler
impl Unpin for Scheduler
impl UnsafeUnpin for Scheduler
impl !UnwindSafe for Scheduler
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more