Skip to main content

Scheduler

Struct Scheduler 

Source
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: bool

Implementations§

Source§

impl Scheduler

Source

pub fn new(sample_rate: f32) -> Self

Creates a new scheduler with the given sample rate.

§Arguments
  • sample_rate - Sample rate in Hz (typically 44100.0 or 48000.0)
§Example
use aether_core::scheduler::Scheduler;

let sched = Scheduler::new(48_000.0);
assert_eq!(sched.sample_rate, 48_000.0);
Source

pub fn process_block<C>(&mut self, cmd_consumer: &mut C, output: &mut [f32])
where C: Consumer<Item = Command>,

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 thread
  • output - 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
Source

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

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

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
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.