rill-patchbay 0.5.0-beta.2

The world where Automata live - control system for Rill
Documentation
use rill_core::queues::MpscQueue;
use rill_core::NodeId;
use rill_patchbay::{
    ControlEvent, EventPattern, FunctionAutomaton, LfoWaveform, Mapping, ParameterMapping,
    PatchbayControl, Servo, Target, Transform,
};
use std::sync::Arc;

fn param(name: &str) -> String {
    name.to_string()
}

#[test]
fn test_control_creation() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let control = PatchbayControl::new(queue);

    assert_eq!(control.mappings().len(), 0);
    assert!((control.current_time() - 0.0) < 1e-9);
}

#[test]
fn test_add_lfo_servo() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue.clone());

    control.add_lfo(
        "lfo1",
        2.0,
        0.3,
        0.5,
        LfoWaveform::Sine,
        NodeId(1),
        "gain",
        0.0,
        1.0,
    );

    assert!(control.get_servo("lfo1").is_some());
}

#[test]
fn test_add_envelope_servo() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue.clone());

    control.add_envelope("env1", 0.1, 0.2, 0.7, 0.3, NodeId(1), "gain", 0.0, 1.0);

    assert!(control.get_servo("env1").is_some());
}

#[test]
fn test_add_custom_servo() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue.clone());

    let sine = FunctionAutomaton::new("Sine", |t| (t * 2.0).sin() * 0.5 + 0.5);
    let servo = Servo::new(
        "custom",
        sine,
        NodeId(1),
        param("param"),
        ParameterMapping::Linear,
        0.0,
        1.0,
    );
    control.add_servo(servo);

    assert!(control.get_servo("custom").is_some());
}

#[test]
fn test_remove_servo() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue);

    control.add_lfo(
        "lfo1",
        1.0,
        0.2,
        0.5,
        LfoWaveform::Sine,
        NodeId(1),
        "gain",
        0.0,
        1.0,
    );
    control.add_lfo(
        "lfo2",
        2.0,
        0.3,
        0.3,
        LfoWaveform::Sine,
        NodeId(1),
        "pan",
        0.0,
        1.0,
    );

    assert!(control.remove_servo("lfo1"));
    assert!(control.get_servo("lfo1").is_none());
    assert!(control.get_servo("lfo2").is_some());
    assert!(!control.remove_servo("nonexistent"));
}

#[test]
fn test_clear_servos() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue);

    control.add_lfo(
        "lfo1",
        1.0,
        0.2,
        0.5,
        LfoWaveform::Sine,
        NodeId(1),
        "gain",
        0.0,
        1.0,
    );
    control.add_lfo(
        "lfo2",
        2.0,
        0.3,
        0.3,
        LfoWaveform::Sine,
        NodeId(1),
        "pan",
        0.0,
        1.0,
    );
    control.add_lfo(
        "lfo3",
        0.5,
        0.1,
        0.7,
        LfoWaveform::Sine,
        NodeId(1),
        "cutoff",
        0.0,
        1.0,
    );

    control.clear();
    assert!(control.get_servo("lfo1").is_none());
    assert!(control.get_servo("lfo2").is_none());
    assert!(control.get_servo("lfo3").is_none());
}

#[test]
fn test_servo_updates() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue.clone());

    control.add_lfo(
        "lfo1",
        1.0,
        0.2,
        0.5,
        LfoWaveform::Sine,
        NodeId(1),
        "gain",
        0.0,
        1.0,
    );

    for _ in 1..=3 {
        control.update(0.1);
    }

    let mut count = 0;
    while queue.pop().is_some() {
        count += 1;
    }
    assert!(count > 0, "No signals were sent");
}

#[test]
fn test_multiple_servos() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue);

    control.add_lfo(
        "lfo1",
        1.0,
        0.2,
        0.5,
        LfoWaveform::Sine,
        NodeId(1),
        "gain",
        0.0,
        1.0,
    );
    control.add_lfo(
        "lfo2",
        2.0,
        0.3,
        0.3,
        LfoWaveform::Sine,
        NodeId(1),
        "pan",
        0.0,
        1.0,
    );
    control.add_lfo(
        "lfo3",
        0.5,
        0.1,
        0.7,
        LfoWaveform::Sine,
        NodeId(1),
        "cutoff",
        0.0,
        1.0,
    );

    assert!(control.get_servo("lfo1").is_some());
    assert!(control.get_servo("lfo2").is_some());
    assert!(control.get_servo("lfo3").is_some());
}

#[test]
fn test_disable_servo() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue.clone());

    control.add_lfo(
        "lfo1",
        0.25,
        0.5,
        0.5,
        LfoWaveform::Sine,
        NodeId(1),
        "gain",
        0.0,
        1.0,
    );

    control.update(0.1);
    let initial_count = drain_count(&queue);

    if let Some(servo) = control.get_servo_mut("lfo1") {
        servo.set_enabled(false);
    }

    for _ in 0..3 {
        control.update(0.1);
    }

    let disabled_count = drain_count(&queue);
    assert_eq!(disabled_count, 0, "Signals were sent while disabled");

    if let Some(servo) = control.get_servo_mut("lfo1") {
        servo.set_enabled(true);
    }

    control.update(0.1);
    let after_enable = drain_count(&queue);
    assert!(
        after_enable > 0 || initial_count > 0,
        "Should produce some signals"
    );
}

fn drain_count(queue: &Arc<MpscQueue<rill_core::queues::SetParameter>>) -> usize {
    let mut count = 0;
    while queue.pop().is_some() {
        count += 1;
    }
    count
}

#[test]
fn test_different_servo_types() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue);

    control.add_lfo(
        "lfo",
        1.0,
        0.5,
        0.5,
        LfoWaveform::Sine,
        NodeId(1),
        "float_param",
        0.0,
        1.0,
    );
    control.add_envelope("env", 0.1, 0.2, 0.7, 0.3, NodeId(1), "int_param", 0.0, 1.0);

    let square =
        FunctionAutomaton::new("Square", |t| if (t * 0.2).sin() > 0.0 { 1.0 } else { 0.0 });
    let servo = Servo::new(
        "gate",
        square,
        NodeId(1),
        "bool_param",
        ParameterMapping::Linear,
        0.0,
        1.0,
    );
    control.add_servo(servo);

    assert!(control.get_servo("lfo").is_some());
    assert!(control.get_servo("env").is_some());
    assert!(control.get_servo("gate").is_some());
}

#[test]
fn test_midi_mapping() {
    let node = NodeId(1);
    let mapping = Mapping::new(
        EventPattern::MidiControl {
            channel: Some(1),
            controller: 7,
        },
        Target {
            node_id: node,
            param_name: "volume".to_string(),
            min: 0.0,
            max: 1.0,
        },
        Transform::Linear,
    );

    let event = ControlEvent::MidiControl {
        channel: 1,
        controller: 7,
        value: 64,
        normalized: 0.5,
    };

    assert!(mapping.matches(&event));

    let cmd = mapping.apply(&event).unwrap();
    assert_eq!(cmd.node_id, node);
    assert_eq!(cmd.param, "volume");
    assert!((cmd.value - 0.5).abs() < 1e-6);
}

#[test]
fn test_mapping_in_control() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue.clone());

    control
        .add_mapping_str("midi:1:7", NodeId(1), "volume", 0.0, 1.0, Transform::Linear)
        .unwrap();

    assert_eq!(control.mappings().len(), 1);

    let event = ControlEvent::MidiControl {
        channel: 1,
        controller: 7,
        value: 64,
        normalized: 0.5,
    };
    control.handle_event(event);

    let cmd = queue.pop();
    assert!(cmd.is_some());
    assert!((cmd.unwrap().value - 0.5).abs() < 1e-6);
}

#[test]
fn test_reset_time() {
    let queue = Arc::new(MpscQueue::with_capacity(64));
    let mut control = PatchbayControl::new(queue);

    control.update(1.0);
    control.update(2.0);
    assert!((control.current_time() - 3.0).abs() < 1e-9);

    control.reset_time();
    assert!((control.current_time() - 0.0).abs() < 1e-9);
}