SUPERB
Examples

midi_route

examples/primitives/midi_route.rs, the whole program

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

//! Note 69, 440 Hz, goes only to synth A. It starts at frame 240 of a 480-frame
//! block and stops at frame 119 of the next one. Synth B stays at 0.
//!
//! ```text
//! cargo run --example midi_route
//! ```

use superb::primitives::buffers::PlanarBuffer;
use superb::primitives::graph::nodes::{MonoSynthNode, PassThroughNode};
use superb::primitives::graph::{AudioGraph, MidiPort};
use superb::primitives::io::Engine;
use superb::primitives::midi::timing::us_to_frames;
use superb::primitives::midi::{MidiChannel, MidiMessage, TimestampedMessage};
use superb::primitives::transport::Transport;
use superb::primitives::{Result, SampleRate, SuperbError};

fn main() -> Result<()> {
    let sample_rate = SampleRate::HZ_48K;
    // 480 frames at 48 kHz is 10 ms.
    let block = 480;
    assert_eq!(sample_rate.seconds_to_samples(0.01), block as i64);

    let mut graph = AudioGraph::new();
    // Both synths sum into the sink. The only MIDI edge is external → A,
    // so B does not hear the note.
    let synth_a = graph.add_node(Box::new(MonoSynthNode::new(1)));
    let synth_b = graph.add_node(Box::new(MonoSynthNode::new(1)));
    let sink = graph.add_node(Box::new(PassThroughNode::new(1)));
    graph.connect(synth_a, sink)?;
    graph.connect(synth_b, sink)?;
    graph.set_output(sink)?;
    graph.connect_midi(MidiPort::External, synth_a)?;

    let mut engine = Engine::new(graph, Transport::new(sample_rate, 120.0));
    engine.prepare(sample_rate, block)?;
    engine.transport_mut().play();

    // Note 69 is 440 Hz. Velocity 127 is amplitude 1. Stamped 5 ms ahead,
    // which is frame 240. The stream clock is still 0.
    let (mut tx, rx) = superb::primitives::realtime::spsc::channel::<TimestampedMessage>(16);
    engine.set_midi_input(Some(rx));
    let channel = MidiChannel::new(0).unwrap();
    let note = 69;
    assert!((MonoSynthNode::note_to_hz(note) - 440.0).abs() < 1e-9);
    assert_eq!(engine.stream_clock_us(), 0);
    assert_eq!(us_to_frames(5_000, sample_rate), 240);
    let note_on_us = engine.stream_clock_us() + 5_000;
    assert_eq!(note_on_us, 5_000);
    tx.push(TimestampedMessage {
        timestamp_us: note_on_us,
        message: MidiMessage::NoteOn {
            channel,
            note,
            velocity: 127,
        },
    })
    .map_err(|_| SuperbError::Message("midi queue full".into()))?;

    let mut out = PlanarBuffer::new(1, block);
    engine.render(&mut out)?;

    // Frames 0..240 are silent and 240..480 sound. A peaks at 1.000, B at 0,
    // and the mix matches A.
    let silent_before = out.channel(0)[..240].iter().all(|&s| s == 0.0);
    let sounding_after = out.channel(0)[240..].iter().any(|&s| s != 0.0);
    assert!(silent_before);
    assert!(sounding_after);
    println!(
        "block 0: note-on stamped {note_on_us} us -> frame 240: silent before = {silent_before}, sounding after = {sounding_after}"
    );
    let (peak_a, peak_b) = {
        let graph = engine.processor();
        (
            graph.node_output(synth_a)?.peak(),
            graph.node_output(synth_b)?.peak(),
        )
    };
    let peak_mix = out.peak();
    assert_eq!(format!("{peak_a:.3}"), "1.000");
    assert_eq!(peak_b.to_bits(), 0);
    assert_eq!(format!("{peak_mix:.3}"), "1.000");
    println!(
        "  synth A peak {:.3}, synth B peak {:.3}, mix peak {:.3}",
        peak_a, peak_b, peak_mix
    );

    // The first block advanced the clock 10 ms. Note-off 2.5 ms later is
    // frame 120, so the last sounding sample is 119.
    assert_eq!(engine.stream_clock_us(), 10_000);
    assert_eq!(us_to_frames(2_500, sample_rate), 120);
    let note_off_us = engine.stream_clock_us() + 2_500;
    assert_eq!(note_off_us, 12_500);
    tx.push(TimestampedMessage {
        timestamp_us: note_off_us,
        message: MidiMessage::NoteOff {
            channel,
            note,
            velocity: 0,
        },
    })
    .map_err(|_| SuperbError::Message("midi queue full".into()))?;
    engine.render(&mut out)?;
    let last_sound = out.channel(0).iter().rposition(|&s| s != 0.0);
    assert_eq!(last_sound, Some(119));
    println!("block 1: note-off stamped {note_off_us} us -> last non-zero sample at frame {last_sound:?} (expected Some(119))");
    let stats = engine.stats();
    assert_eq!(stats.blocks, 2);
    assert_eq!(stats.segments, 2);
    assert_eq!(stats.midi_dropped, 0);
    assert_eq!(stats.capture_dropped, 0);
    println!("engine stats: {:?}", engine.stats());
    Ok(())
}