lyris 0.0.12

A real-time safe audio processing framework
Documentation
use crate::core::types::*;
use std::marker::PhantomData;
use std::any::TypeId;
use crate::Runtime;
use crate::Builder;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};

/// The `#[processor]` attribute macro transforms a function definition into a complete
/// component processor for the audio processing runtime.
/// 
/// # How it works
/// 
/// When you write:
/// ```rust
/// #[processor]
/// fn filter(
///     audio_in: Input,
///     cutoff: Input,
///     audio_out: UseOutput,
///     z1: UseF32,
/// ) {
///     // Process audio here
/// }
/// ```
/// 
/// The macro generates:
/// 
/// 1. **A struct type** - `Filter` struct with the appropriate fields
/// 2. **Processor trait implementation** - Provides compile-time info about buffer/slot counts
/// 3. **ProcessorName methods** - Type-safe buffer handle constructors
/// 4. **Context methods** - Type-safe buffer accessors for each field
/// 
/// # Component Functions
/// 
/// Components are pure functions that process audio with declared dependencies:
/// ```rust
/// #[processor]
/// fn filter(
///     audio_in: Input,
///     cutoff: Input,
///     audio_out: UseOutput,
///     z1: UseF32,
/// ) {
///     for (i, &sample) in audio_in.unwrap_or(&[]).enumerate() {
///         let filtered = sample * cutoff.unwrap_or(&[0.5])[0] + *z1 * 0.9;
///         audio_out[i] = filtered;
///         *z1 = filtered;
///     }
/// }
/// ```
/// 
/// # Routing with BufferHandles
/// 
/// The BufferHandle approach provides a fluent, type-safe routing API:
/// ```rust
/// router.route(
///     Filter::named("filter_1").audio_out(),
///     Filter::named("filter_2").audio_in()
/// );
/// ```
/// 
/// BufferHandles carry:
/// - The component instance name
/// - The field index within the processor
/// - The TypeId of the processor (for direct HashMap lookup)
/// - The marker type (Input/UseOutput/UseF32)
/// 
/// # Runtime Flow
/// 
/// 1. **Build Phase**: Components are registered with their instance names
/// 2. **Routing Phase**: BufferHandles are connected through the Router
/// 3. **Execution Phase**: Runtime processes updates and executes components
/// 
/// # Buffer Sharing
/// 
/// The Clerk uses (TypeId, field_idx) pairs to identify buffers:
/// - Direct HashMap lookup instead of string matching
/// - Connected buffers share the same PhysicalBuffer
/// - Topological sort ensures correct execution order
/// 
/// # Thread Safety
/// 
/// - Router is cloneable and can be shared between threads
/// - Routing updates are serialized through a Mutex
/// - Runtime updates use lock-free channels
/// - Audio processing runs without locks

// Example Filter component with function processor:

// #[processor]
fn filter(
    audio_in: Input,     // Option<&[f32]>
    cutoff: Input,       // Option<&[f32]>
    audio_out: UseOutput,   // &mut [f32]
    z1: UseF32,             // &mut f32
) {
    let default_silence = vec![0.0; audio_out.len()];
    let default_cutoff = vec![0.5; audio_out.len()];
    
    let audio_in = audio_in.unwrap_or(&default_silence);
    let cutoff = cutoff.unwrap_or(&default_cutoff);
    let z1 = z1;
    
    // Simple one-pole lowpass filter
    let a = cutoff[0].min(0.99).max(0.0);
    
    for (i, &sample) in audio_in.iter().enumerate() {
        let filtered = sample * a + *z1 * (1.0 - a);
        audio_out[i] = filtered;
        *z1 = filtered;
    }
}

// ============= GENERATED BY MACRO =============

struct Filter {
    audio_in: Input,
    cutoff: Input,
    audio_out: UseOutput,
    z1: UseF32,
}

impl Processor for Filter {
    fn buffers_count() -> usize { 3 }
    fn slot_count() -> usize { 1 }
    fn call<E: Clone + Copy>(runtime: &Runtime<E>, handle: ContextHandle) {
        let ctx = runtime.get_ctx::<Filter>(handle);
        
        let default_silence = vec![0.0; ctx.buffer_size()];
        let default_cutoff = vec![0.5; ctx.buffer_size()];
        let mut default_output = vec![0.0; ctx.buffer_size()];
        
        let audio_in = ctx.audio_in().unwrap_or(&default_silence);
        let cutoff = ctx.cutoff().unwrap_or(&default_cutoff);
        let audio_out = ctx.audio_out().unwrap_or(&mut default_output);
        let z1 = ctx.z1();
        
        // Call the user's function body
        filter(audio_in, cutoff, audio_out, z1);
    }
}

impl Filter {
    pub const fn named(name: &'static str) -> ProcessorName<Filter> {
        ProcessorName::<Filter> { name, _phantom: PhantomData }
    }
}

impl ProcessorName<Filter> {
    pub fn audio_in(self) -> PortHandle<Input> {
        BufferHandle::new(self.name, 0, TypeId::of::<Filter>())
    }

    pub fn cutoff(self) -> PortHandle<Input> {
        BufferHandle::new(self.name, 1, TypeId::of::<Filter>())
    }

    pub fn audio_out(self) -> PortHandle<UseOutput> {
        BufferHandle::new(self.name, 2, TypeId::of::<Filter>())
    }
}

impl<'a, E: Clone + Copy> Context<'a, Filter, E> {
    pub fn audio_in(&self) -> Option<&[f32]> {
        let buffer_idx = self.handle.buffer_ids_start.0 + 0;
        if let Some(buffer_id) = self.runtime.buffer_ids[buffer_idx]{
            unsafe {
                let buffer_cell = self.runtime.buffers
                    .get(&buffer_id)
                    .expect("Buffer not found");
                let res = &*buffer_cell.get();
                return Some(res)
            }
        }
        None
    }
    
    pub fn cutoff(&self) -> Option<&[f32]> {
        let buffer_idx = self.handle.buffer_ids_start.0 + 1;
        if let Some(buffer_id) = self.runtime.buffer_ids[buffer_idx]{
            unsafe {
                let buffer_cell = self.runtime.buffers
                    .get(&buffer_id)
                    .expect("Buffer not found");
                let res = &*buffer_cell.get();
                return Some(res)
            }
        }
        None
    }

    pub fn audio_out(&self) -> Option<&mut [f32]> {
        let buffer_idx = self.handle.buffer_ids_start.0 + 2;
        if let Some(buffer_id) = self.runtime.buffer_ids[buffer_idx]{
            unsafe {
                let buffer_cell = self.runtime.buffers
                    .get(&buffer_id)
                    .expect("Buffer not found");
                let res = &mut *buffer_cell.get();
                return Some(res)
            }
        }
        None
    }
    
    pub fn z1(&'a self) -> &'a mut f32 {
        let slot_idx = self.handle.slot_ids_start + 0;
        unsafe {
            let slot_cell = &self.runtime.slots[slot_idx];
            &mut *slot_cell.get()
        }
    }
}

// Example saw oscillator
// #[processor]
fn saw_osc(
    audio_out: UseOutput,
    phase: UseF32,
) {
    let frequency = 440.0;
    let sample_rate = 44100.0;
    let phase_increment = frequency / sample_rate;
    
    for sample in audio_out.iter_mut() {
        *sample = (*phase * 2.0 - 1.0) * 0.1;
        *phase += phase_increment;
        if *phase >= 1.0 {
            *phase -= 1.0;
        }
    }
}

pub fn test_main() -> Result<(), Box<dyn std::error::Error>> {
    // Set up the runtime
    let (mut runtime, router) = Builder::<()>::new()
        .add_processor::<SawOsc>("saw", saw_component)
        .buffer_length(1024)
        .build();
    
    let _ = router.route(
        SawOsc::named("saw").audio_out(),
        output()
    );

    runtime.tick();
    
    
    // Set up cpal
    let host = cpal::default_host();
    let device = host.default_output_device()
        .ok_or("No output device available")?;
    
    let config = device.default_output_config()?;
    println!("Using config: {:?}", config);
    
    let stream = match config.sample_format() {
        cpal::SampleFormat::F32 => {
            device.build_output_stream(
                &config.into(),
                move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
                    runtime.tick();
                    runtime.write_to(data);
                },
                |err| eprintln!("Audio stream error: {}", err),
                None,
            )?
        }
        _ => return Err("Unsupported sample format".into()),
    };
    
    stream.play()?;
    
    println!("Playing 440Hz sawtooth wave! Press Enter to stop...");
    let mut input = String::new();
    std::io::stdin().read_line(&mut input)?;
    
    Ok(())
}