use crate::harness::{default_ctx, expect_outputs, forth}; use cagire::forth::{CcMemory, StepContext}; use std::sync::{Arc, Mutex}; #[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 = Arc::new(Mutex::new([[0u8; 128]; 16])); { let mut mem = cc_memory.lock().unwrap(); mem[0][1] = 64; // channel 1 (0-indexed), CC 1, value 64 mem[5][74] = 127; // channel 6 (0-indexed), CC 74, value 127 } let f = forth(); let ctx = StepContext { cc_memory: Some(Arc::clone(&cc_memory)), ..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"); } #[test] fn test_midi_start() { let outputs = expect_outputs("mstart", 1); assert_eq!(outputs[0], "/midi/start"); } #[test] fn test_midi_stop() { let outputs = expect_outputs("mstop", 1); assert_eq!(outputs[0], "/midi/stop"); } #[test] fn test_midi_continue() { let outputs = expect_outputs("mcont", 1); assert_eq!(outputs[0], "/midi/continue"); } // 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")); }