289 lines
8.4 KiB
Rust
289 lines
8.4 KiB
Rust
use crate::harness::{default_ctx, expect_outputs, forth};
|
|
use cagire::forth::{CcAccess, StepContext};
|
|
use cagire::midi::CcMemory;
|
|
|
|
|
|
#[allow(unused_imports)]
|
|
use cagire::forth::Value;
|
|
|
|
#[test]
|
|
fn test_midi_channel_set() {
|
|
let outputs = expect_outputs("60 note 100 velocity 3 chan m.", 1);
|
|
assert!(outputs[0].starts_with("/midi/note/60/vel/100/chan/2/dur/"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_note_default_channel() {
|
|
let outputs = expect_outputs("72 note 80 velocity m.", 1);
|
|
assert!(outputs[0].starts_with("/midi/note/72/vel/80/chan/0/dur/"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_cc() {
|
|
let outputs = expect_outputs("64 ccnum 127 ccout m.", 1);
|
|
assert!(outputs[0].contains("/midi/cc/64/127/chan/0"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_cc_with_channel() {
|
|
let outputs = expect_outputs("5 chan 1 ccnum 64 ccout m.", 1);
|
|
assert!(outputs[0].contains("/midi/cc/1/64/chan/4"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_ccval_returns_zero_without_cc_memory() {
|
|
let f = forth();
|
|
let ctx = default_ctx();
|
|
let outputs = f.evaluate("1 1 ccval", &ctx).unwrap();
|
|
assert!(outputs.is_empty());
|
|
let stack = f.stack();
|
|
assert_eq!(stack.len(), 1);
|
|
match &stack[0] {
|
|
cagire::forth::Value::Int(v, _) => assert_eq!(*v, 0),
|
|
_ => panic!("expected Int"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_ccval_reads_from_cc_memory() {
|
|
let cc_memory = CcMemory::new();
|
|
cc_memory.set_cc(0, 0, 1, 64); // device 0, channel 1 (0-indexed), CC 1, value 64
|
|
cc_memory.set_cc(0, 5, 74, 127); // device 0, channel 6 (0-indexed), CC 74, value 127
|
|
|
|
let f = forth();
|
|
let ctx = StepContext {
|
|
cc_access: Some(&cc_memory as &dyn CcAccess),
|
|
..default_ctx()
|
|
};
|
|
|
|
// Test CC 1 on channel 1 (user provides 1, internally 0)
|
|
f.evaluate("1 1 ccval", &ctx).unwrap();
|
|
let stack = f.stack();
|
|
assert_eq!(stack.len(), 1);
|
|
match &stack[0] {
|
|
cagire::forth::Value::Int(v, _) => assert_eq!(*v, 64),
|
|
_ => panic!("expected Int"),
|
|
}
|
|
f.clear_stack();
|
|
|
|
// Test CC 74 on channel 6 (user provides 6, internally 5)
|
|
f.evaluate("74 6 ccval", &ctx).unwrap();
|
|
let stack = f.stack();
|
|
assert_eq!(stack.len(), 1);
|
|
match &stack[0] {
|
|
cagire::forth::Value::Int(v, _) => assert_eq!(*v, 127),
|
|
_ => panic!("expected Int"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_channel_clamping() {
|
|
// Channel should be clamped 1-16, then converted to 0-15 internally
|
|
let outputs = expect_outputs("60 note 100 velocity 0 chan m.", 1);
|
|
assert!(outputs[0].contains("/chan/0")); // 0 clamped to 1, then -1 = 0
|
|
|
|
let outputs = expect_outputs("60 note 100 velocity 17 chan m.", 1);
|
|
assert!(outputs[0].contains("/chan/15")); // 17 clamped to 16, then -1 = 15
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_note_clamping() {
|
|
let outputs = expect_outputs("-1 note 100 velocity m.", 1);
|
|
assert!(outputs[0].contains("/note/0"));
|
|
|
|
let outputs = expect_outputs("200 note 100 velocity m.", 1);
|
|
assert!(outputs[0].contains("/note/127"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_velocity_clamping() {
|
|
let outputs = expect_outputs("60 note -10 velocity m.", 1);
|
|
assert!(outputs[0].contains("/vel/0"));
|
|
|
|
let outputs = expect_outputs("60 note 200 velocity m.", 1);
|
|
assert!(outputs[0].contains("/vel/127"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_defaults() {
|
|
// With only note specified, velocity defaults to 100 and channel to 0
|
|
let outputs = expect_outputs("60 note m.", 1);
|
|
assert!(outputs[0].starts_with("/midi/note/60/vel/100/chan/0/dur/"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_full_defaults() {
|
|
// With nothing specified, defaults to note=60, velocity=100, channel=0
|
|
let outputs = expect_outputs("m.", 1);
|
|
assert!(outputs[0].starts_with("/midi/note/60/vel/100/chan/0/dur/"));
|
|
}
|
|
|
|
// Pitch bend tests
|
|
#[test]
|
|
fn test_midi_bend_center() {
|
|
let outputs = expect_outputs("0.0 bend m.", 1);
|
|
// 0.0 -> 8192 (center)
|
|
assert!(
|
|
outputs[0].contains("/midi/bend/8191/chan/0")
|
|
|| outputs[0].contains("/midi/bend/8192/chan/0")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_bend_max() {
|
|
let outputs = expect_outputs("1.0 bend m.", 1);
|
|
// 1.0 -> 16383 (max)
|
|
assert!(outputs[0].contains("/midi/bend/16383/chan/0"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_bend_min() {
|
|
let outputs = expect_outputs("-1.0 bend m.", 1);
|
|
// -1.0 -> 0 (min)
|
|
assert!(outputs[0].contains("/midi/bend/0/chan/0"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_bend_with_channel() {
|
|
let outputs = expect_outputs("0.5 bend 3 chan m.", 1);
|
|
assert!(outputs[0].contains("/chan/2")); // channel 3 -> 2 (0-indexed)
|
|
assert!(outputs[0].contains("/midi/bend/"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_bend_clamping() {
|
|
let outputs = expect_outputs("2.0 bend m.", 1);
|
|
// 2.0 clamped to 1.0 -> 16383
|
|
assert!(outputs[0].contains("/midi/bend/16383/chan/0"));
|
|
|
|
let outputs = expect_outputs("-5.0 bend m.", 1);
|
|
// -5.0 clamped to -1.0 -> 0
|
|
assert!(outputs[0].contains("/midi/bend/0/chan/0"));
|
|
}
|
|
|
|
// Channel pressure (aftertouch) tests
|
|
#[test]
|
|
fn test_midi_pressure() {
|
|
let outputs = expect_outputs("64 pressure m.", 1);
|
|
assert!(outputs[0].contains("/midi/pressure/64/chan/0"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_pressure_with_channel() {
|
|
let outputs = expect_outputs("100 pressure 5 chan m.", 1);
|
|
assert!(outputs[0].contains("/midi/pressure/100/chan/4"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_pressure_clamping() {
|
|
let outputs = expect_outputs("-10 pressure m.", 1);
|
|
assert!(outputs[0].contains("/midi/pressure/0/chan/0"));
|
|
|
|
let outputs = expect_outputs("200 pressure m.", 1);
|
|
assert!(outputs[0].contains("/midi/pressure/127/chan/0"));
|
|
}
|
|
|
|
// Program change tests
|
|
#[test]
|
|
fn test_midi_program() {
|
|
let outputs = expect_outputs("0 program m.", 1);
|
|
assert!(outputs[0].contains("/midi/program/0/chan/0"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_program_with_channel() {
|
|
let outputs = expect_outputs("42 program 10 chan m.", 1);
|
|
assert!(outputs[0].contains("/midi/program/42/chan/9"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_program_clamping() {
|
|
let outputs = expect_outputs("-1 program m.", 1);
|
|
assert!(outputs[0].contains("/midi/program/0/chan/0"));
|
|
|
|
let outputs = expect_outputs("200 program m.", 1);
|
|
assert!(outputs[0].contains("/midi/program/127/chan/0"));
|
|
}
|
|
|
|
// MIDI real-time messages
|
|
#[test]
|
|
fn test_midi_clock() {
|
|
let outputs = expect_outputs("mclock", 1);
|
|
assert_eq!(outputs[0], "/midi/clock/dev/0");
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_start() {
|
|
let outputs = expect_outputs("mstart", 1);
|
|
assert_eq!(outputs[0], "/midi/start/dev/0");
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_stop() {
|
|
let outputs = expect_outputs("mstop", 1);
|
|
assert_eq!(outputs[0], "/midi/stop/dev/0");
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_continue() {
|
|
let outputs = expect_outputs("mcont", 1);
|
|
assert_eq!(outputs[0], "/midi/continue/dev/0");
|
|
}
|
|
|
|
// Test message type priority (first matching type wins)
|
|
#[test]
|
|
fn test_midi_message_priority_cc_over_note() {
|
|
// CC params take priority over note
|
|
let outputs = expect_outputs("60 note 1 ccnum 64 ccout m.", 1);
|
|
assert!(outputs[0].contains("/midi/cc/1/64"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_message_priority_bend_over_note() {
|
|
// bend takes priority over note (but not over CC)
|
|
let outputs = expect_outputs("60 note 0.5 bend m.", 1);
|
|
assert!(outputs[0].contains("/midi/bend/"));
|
|
}
|
|
|
|
// MIDI note duration tests
|
|
#[test]
|
|
fn test_midi_note_default_duration() {
|
|
// Default dur=1.0, with tempo=120 and speed=1.0, step_duration = 60/120/4/1 = 0.125
|
|
let outputs = expect_outputs("60 note m.", 1);
|
|
assert!(outputs[0].contains("/dur/0.125"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_note_explicit_duration() {
|
|
// dur=0.5 means half step duration = 0.0625 seconds
|
|
let outputs = expect_outputs("60 note 0.5 dur m.", 1);
|
|
assert!(outputs[0].contains("/dur/0.0625"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_note_long_duration() {
|
|
// dur=2.0 means two step durations = 0.25 seconds
|
|
let outputs = expect_outputs("60 note 2 dur m.", 1);
|
|
assert!(outputs[0].contains("/dur/0.25"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_note_duration_with_tempo() {
|
|
use crate::harness::{ctx_with, forth};
|
|
let f = forth();
|
|
// At tempo=60, step_duration = 60/60/4/1 = 0.25 seconds
|
|
let ctx = ctx_with(|c| c.tempo = 60.0);
|
|
let outputs = f.evaluate("60 note m.", &ctx).unwrap();
|
|
assert!(outputs[0].contains("/dur/0.25"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_midi_note_duration_with_speed() {
|
|
use crate::harness::{ctx_with, forth};
|
|
let f = forth();
|
|
// At tempo=120 speed=2, step_duration = 60/120/4/2 = 0.0625 seconds
|
|
let ctx = ctx_with(|c| c.speed = 2.0);
|
|
let outputs = f.evaluate("60 note m.", &ctx).unwrap();
|
|
assert!(outputs[0].contains("/dur/0.0625"));
|
|
}
|