SUPERB
Examples

live_graph_edit

examples/primitives/live_graph_edit.rs, the whole program

Run it with cargo run --example live_graph_edit (see the examples for the features some need).

//! Three graphs swap in between blocks: 440 Hz through a gain of 0.5,
//! then 440 Hz and 660 Hz through a gain of 0.125, then a constant 0.75.
//! The retired graph is dropped back on this thread.
//!
//! ```text
//! cargo run --example live_graph_edit
//! ```

use superb::primitives::graph::nodes::{ConstantNode, GainNode, PassThroughNode, SineNode};
use superb::primitives::graph::{live_pair, BlockTime, LiveGraph};
use superb::primitives::midi::MidiEventBuffer;
use superb::primitives::{Result, SampleRate};

fn main() -> Result<()> {
    let sample_rate = SampleRate::HZ_48K;
    assert_eq!(sample_rate.seconds_to_samples(1.0), 48_000);
    // Each process call is 256 frames. No MIDI in this render.
    let block = 256;
    let midi = MidiEventBuffer::with_capacity(0);

    let (mut editor, mut live) = live_pair();
    editor.configure(sample_rate, block);

    // Generation 1: 440 Hz at amplitude 1, stereo, into a gain of 0.5.
    let sine_node = SineNode::new(440.0, 1.0, 2);
    assert!((sine_node.frequency().get() - 440.0).abs() < 1e-6);
    assert!((sine_node.amplitude().get() - 1.0).abs() < 1e-6);
    let sine = editor.graph_mut().add_node(Box::new(sine_node));
    let gain_node = GainNode::new(0.5, 2);
    assert!((gain_node.gain().get() - 0.5).abs() < 1e-6);
    let gain = editor.graph_mut().add_node(Box::new(gain_node));
    editor.graph_mut().connect(sine, gain)?;
    editor.graph_mut().set_output(gain)?;
    editor.publish()?;
    println!("published generation 1 (sine -> gain 0.5)");

    let mut time = BlockTime {
        rolling: true,
        ..BlockTime::free_running()
    };

    let mut render = |live: &mut LiveGraph, label: &str, blocks: usize| -> Result<f32> {
        let mut peak = 0.0f32;
        for _ in 0..blocks {
            live.process(time, block, &midi, None)?;
            time = time.advanced_by(block, sample_rate);
            peak = peak.max(live.output().peak());
        }
        println!(
            "{label}: {blocks} blocks, peak {peak:.3}, active nodes {}, swaps {}",
            live.active_node_count(),
            live.swap_count()
        );
        Ok(peak)
    };

    // 20 blocks. Peak 0.500, 2 nodes, 1 swap.
    let peak = render(&mut live, "gen 1 running   ", 20)?;
    assert_eq!(format!("{peak:.3}"), "0.500");
    assert_eq!(live.active_node_count(), 2);
    assert_eq!(live.swap_count(), 1);

    // Generation 2 is drafted while generation 1 keeps running: 440 Hz and
    // 660 Hz, summed, through a gain of 0.125, then a pass-through.
    let sine_a_node = SineNode::new(440.0, 1.0, 2);
    let sine_b_node = SineNode::new(660.0, 1.0, 2);
    assert!((sine_a_node.frequency().get() - 440.0).abs() < 1e-6);
    assert!((sine_b_node.frequency().get() - 660.0).abs() < 1e-6);
    let sine_a = editor.graph_mut().add_node(Box::new(sine_a_node));
    let sine_b = editor.graph_mut().add_node(Box::new(sine_b_node));
    let gain_node = GainNode::new(0.125, 2);
    assert!((gain_node.gain().get() - 0.125).abs() < 1e-6);
    let gain = editor.graph_mut().add_node(Box::new(gain_node));
    let bus = editor
        .graph_mut()
        .add_node(Box::new(PassThroughNode::new(2)));
    editor.graph_mut().connect(sine_a, gain)?;
    editor.graph_mut().connect(sine_b, gain)?;
    editor.graph_mut().connect(gain, bus)?;
    editor.graph_mut().set_output(bus)?;
    // Still generation 1: 10 blocks, peak 0.500, 2 nodes, 1 swap.
    let peak = render(&mut live, "gen 1 while edit", 10)?;
    assert_eq!(format!("{peak:.3}"), "0.500");
    assert_eq!(live.active_node_count(), 2);
    assert_eq!(live.swap_count(), 1);
    editor.publish()?;
    // Published and not yet processed, so one graph is pending.
    assert_eq!(editor.pending_count(), 1);
    println!(
        "published generation 2 (two sines -> gain 0.125 -> bus); pending: {}",
        editor.pending_count()
    );

    // 20 blocks. The summed sines through 0.125 peak at 0.238, under the
    // 0.250 of a perfect stack. 4 nodes, 2 swaps.
    let peak = render(&mut live, "gen 2 running   ", 20)?;
    assert_eq!(format!("{peak:.3}"), "0.238");
    assert!(peak < 0.25);
    assert_eq!(live.active_node_count(), 4);
    assert_eq!(live.swap_count(), 2);

    // Generation 3: DC 0.75 on both channels.
    let constant = ConstantNode::new(0.75, 2);
    assert!((constant.value().get() - 0.75).abs() < 1e-6);
    let c = editor.graph_mut().add_node(Box::new(constant));
    editor.graph_mut().set_output(c)?;
    editor.publish()?;
    println!("published generation 3 (constant 0.75)");
    // 10 blocks. Peak 0.750, 1 node, 3 swaps.
    let peak = render(&mut live, "gen 3 running   ", 10)?;
    assert_eq!(format!("{peak:.3}"), "0.750");
    assert!((peak - 0.75).abs() < 1e-6);
    assert_eq!(live.active_node_count(), 1);
    assert_eq!(live.swap_count(), 3);

    // publish already dropped the earlier retired graphs. Generation 2
    // is the one left, so this reclaims 1. Applied 3, rejected 0.
    let reclaimed = editor.try_reclaim();
    assert_eq!(reclaimed, 1);
    assert_eq!(editor.applied_count(), 3);
    assert_eq!(editor.rejected_count(), 0);
    println!(
        "reclaimed {reclaimed} retired graph(s) on the control thread; applied {}, rejected {}",
        editor.applied_count(),
        editor.rejected_count()
    );
    Ok(())
}