use crate::instruments::Instrument;
use crate::mixer::{Mixer, NUM_CHANNELS};
use crate::smf::{ChannelBody, ChannelMessage, Division, Event, MetaEvent, SmfFile};
pub const DEFAULT_TEMPO_USEC_PER_QUARTER: u32 = 500_000;
#[derive(Clone, Debug)]
struct AbsEvent {
tick: u64,
track: u16,
order: u32,
event: Event,
}
pub struct Scheduler {
events: Vec<AbsEvent>,
cursor: usize,
division: Division,
tempo_us_per_quarter: u32,
sample_rate: u32,
samples_elapsed: f64,
samples_per_tick: f64,
sample_clock: f64,
}
impl Scheduler {
pub fn new(smf: &SmfFile, sample_rate: u32) -> Self {
let mut events: Vec<AbsEvent> = Vec::new();
for (ti, track) in smf.tracks.iter().enumerate() {
let mut tick: u64 = 0;
for (oi, te) in track.events.iter().enumerate() {
tick = tick.saturating_add(te.delta as u64);
events.push(AbsEvent {
tick,
track: ti as u16,
order: oi as u32,
event: te.kind.clone(),
});
}
}
events.sort_by(|a, b| {
a.tick
.cmp(&b.tick)
.then_with(|| a.track.cmp(&b.track))
.then_with(|| a.order.cmp(&b.order))
});
let mut s = Self {
events,
cursor: 0,
division: smf.header.division,
tempo_us_per_quarter: DEFAULT_TEMPO_USEC_PER_QUARTER,
sample_rate: sample_rate.max(1),
samples_elapsed: 0.0,
samples_per_tick: 0.0,
sample_clock: 0.0,
};
s.recompute_samples_per_tick();
s
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn is_done(&self) -> bool {
self.cursor >= self.events.len()
}
pub fn event_count(&self) -> usize {
self.events.len()
}
pub fn next_event_sample(&self) -> Option<f64> {
self.events
.get(self.cursor)
.map(|e| e.tick as f64 * self.samples_per_tick)
}
pub fn step(&mut self, samples: usize, mixer: &mut Mixer, instrument: &dyn Instrument) -> bool {
let next_clock = self.sample_clock + samples as f64;
while self.cursor < self.events.len() {
let evt_tick = self.events[self.cursor].tick;
let last_tick = if self.cursor == 0 {
0
} else {
self.events[self.cursor - 1].tick
};
let dt_ticks = evt_tick.saturating_sub(last_tick);
let evt_sample = self.samples_elapsed + dt_ticks as f64 * self.samples_per_tick;
if evt_sample >= next_clock {
break;
}
self.samples_elapsed = evt_sample;
let event = self.events[self.cursor].event.clone();
self.dispatch(&event, mixer, instrument);
self.cursor += 1;
}
self.sample_clock = next_clock;
self.is_done()
}
fn dispatch(&mut self, evt: &Event, mixer: &mut Mixer, instrument: &dyn Instrument) {
match evt {
Event::Channel(ChannelMessage { channel, body }) => {
self.dispatch_channel(*channel, *body, mixer, instrument);
}
Event::Meta(MetaEvent::Tempo(us)) => {
if *us != 0 {
self.tempo_us_per_quarter = *us;
self.recompute_samples_per_tick();
}
}
Event::Meta(MetaEvent::EndOfTrack) => {
}
Event::Meta(_) => {}
Event::Sysex { escape, data } => {
if !escape {
dispatch_universal_sysex(data, mixer);
}
}
}
}
fn dispatch_channel(
&self,
channel: u8,
body: ChannelBody,
mixer: &mut Mixer,
instrument: &dyn Instrument,
) {
let ch = channel as usize % NUM_CHANNELS;
match body {
ChannelBody::NoteOn { key, velocity: 0 } => {
mixer.note_off(channel, key);
}
ChannelBody::NoteOn { key, velocity } => {
let program = mixer.channel_state(channel).program;
let _drum = ch == 9;
if let Ok(voice) = instrument.make_voice(program, key, velocity, self.sample_rate) {
mixer.note_on(channel, key, velocity, voice);
}
}
ChannelBody::NoteOff { key, velocity: _ } => {
mixer.note_off(channel, key);
}
ChannelBody::ProgramChange { program } => {
mixer.channel_state_mut(channel).program = program;
}
ChannelBody::ControlChange { controller, value } => match controller {
1 => mixer.set_mod_wheel(channel, value), 6 => mixer.set_data_entry(channel, value, true), 7 => mixer.channel_state_mut(channel).volume = value,
10 => mixer.channel_state_mut(channel).pan = value,
38 => mixer.set_data_entry(channel, value, false), 64 => mixer.set_sustain(channel, value),
74 => mixer.set_timbre(channel, value), 96 => mixer.data_inc_dec(channel, 1), 97 => mixer.data_inc_dec(channel, -1), 100 => mixer.set_rpn_byte(channel, value, false), 101 => mixer.set_rpn_byte(channel, value, true), 120 | 123 => {
mixer.all_notes_off();
}
_ => { }
},
ChannelBody::PolyAftertouch { key, pressure } => {
mixer.set_poly_pressure(channel, key, pressure);
}
ChannelBody::ChannelAftertouch { pressure } => {
mixer.set_channel_pressure(channel, pressure);
}
ChannelBody::PitchBend { value } => {
mixer.set_pitch_bend(channel, value);
}
}
}
fn recompute_samples_per_tick(&mut self) {
let sr = self.sample_rate as f64;
self.samples_per_tick = match self.division {
Division::TicksPerQuarter(div) => {
self.tempo_us_per_quarter as f64 * sr / (1_000_000.0 * div.max(1) as f64)
}
Division::Smpte {
frames_per_second,
ticks_per_frame,
} => {
let denom = (frames_per_second.max(1) as f64) * (ticks_per_frame.max(1) as f64);
sr / denom
}
};
}
pub fn estimated_total_samples(&self) -> u64 {
if let Some(last) = self.events.last() {
(last.tick as f64 * self.samples_per_tick).ceil() as u64
} else {
0
}
}
}
pub fn dispatch_universal_sysex(data: &[u8], mixer: &mut crate::mixer::Mixer) {
if data.len() < 3 {
return;
}
let payload = if *data.last().unwrap() == 0xF7 {
&data[..data.len() - 1]
} else {
data
};
match payload[0] {
0x7E => dispatch_universal_non_real_time(payload, mixer),
0x7F => dispatch_universal_real_time(payload, mixer),
_ => { }
}
}
fn dispatch_universal_non_real_time(payload: &[u8], mixer: &mut crate::mixer::Mixer) {
if payload.len() < 4 {
return;
}
let sub_id1 = payload[2];
let sub_id2 = payload[3];
if sub_id1 == 0x09 && matches!(sub_id2, 0x01..=0x03) {
mixer.all_notes_off();
mixer.set_master_volume_14(0x3FFF);
mixer.set_master_balance_14(0x2000); mixer.set_master_fine_tuning(0, 0x40); mixer.set_master_coarse_tuning(0, 0x40); mixer.reset_tuning(); mixer.reset_gm_effects(); } else if sub_id1 == 0x08 {
dispatch_mts(payload, mixer, false);
}
}
fn dispatch_universal_real_time(payload: &[u8], mixer: &mut crate::mixer::Mixer) {
if payload.len() < 4 {
return;
}
let sub_id1 = payload[2];
if sub_id1 == 0x08 {
dispatch_mts(payload, mixer, true);
return;
}
let sub_id2 = payload[3];
if sub_id1 != 0x04 || payload.len() < 6 {
return;
}
if sub_id2 == 0x05 {
dispatch_global_parameter_control(payload, mixer);
return;
}
match sub_id2 {
0x01 => {
let lsb = payload[4] & 0x7F;
let msb = payload[5] & 0x7F;
let combined = ((msb as u16) << 7) | (lsb as u16);
mixer.set_master_volume_14(combined);
}
0x02 => {
let lsb = payload[4] & 0x7F;
let msb = payload[5] & 0x7F;
let combined = ((msb as u16) << 7) | (lsb as u16);
mixer.set_master_balance_14(combined);
}
0x03 => {
mixer.set_master_fine_tuning(payload[4], payload[5]);
}
0x04 => {
mixer.set_master_coarse_tuning(payload[4], payload[5]);
}
_ => {}
}
}
fn dispatch_global_parameter_control(payload: &[u8], mixer: &mut crate::mixer::Mixer) {
if payload.len() < 7 {
return;
}
let sw = payload[4] as usize; let pw = payload[5] as usize; let vw = payload[6] as usize; if pw == 0 || vw == 0 {
return;
}
let mut p = 7;
let slot_path_end = p + sw * 2;
if slot_path_end > payload.len() {
return;
}
if sw != 1 {
return;
}
let slot_msb = payload[p];
let slot_lsb = payload[p + 1];
p = slot_path_end;
if slot_msb != 1 {
return;
}
while p + pw + vw <= payload.len() {
let mut pp: u32 = 0;
for &b in &payload[p..p + pw] {
pp = (pp << 7) | (b & 0x7F) as u32;
}
p += pw;
let mut vv: u32 = 0;
for (i, &b) in payload[p..p + vw].iter().enumerate() {
vv |= ((b & 0x7F) as u32) << (7 * i);
}
p += vw;
let pp8 = (pp & 0x7F) as u8;
let vv8 = (vv & 0x7F) as u8;
match slot_lsb {
0x01 => mixer.set_gm_reverb_param(pp8, vv8),
0x02 => mixer.set_gm_chorus_param(pp8, vv8),
_ => {}
}
}
}
fn dispatch_mts(payload: &[u8], mixer: &mut crate::mixer::Mixer, live: bool) {
if payload.len() < 4 {
return;
}
let sub2 = payload[3];
match sub2 {
0x02 => {
if payload.len() < 6 {
return;
}
let count = payload[5] as usize;
let mut p = 6;
for _ in 0..count {
if p + 4 > payload.len() {
break;
}
let key = payload[p] & 0x7F;
let word = [payload[p + 1], payload[p + 2], payload[p + 3]];
mixer.set_key_tuning_word(key, word, live);
p += 4;
}
}
0x07 => {
if payload.len() < 7 {
return;
}
let count = payload[6] as usize;
let mut p = 7;
for _ in 0..count {
if p + 4 > payload.len() {
break;
}
let key = payload[p] & 0x7F;
let word = [payload[p + 1], payload[p + 2], payload[p + 3]];
mixer.set_key_tuning_word(key, word, live);
p += 4;
}
}
0x08 => {
if payload.len() < 7 + 12 {
return;
}
let mask = crate::tuning::scale_octave_channel_mask(payload[4], payload[5], payload[6]);
let mut offsets = [0.0f32; 12];
for (pc, off) in offsets.iter_mut().enumerate() {
*off = crate::tuning::scale_octave_1byte_to_cents(payload[7 + pc]);
}
apply_scale_octave_to_mask(mixer, mask, offsets, live);
}
0x09 => {
if payload.len() < 7 + 24 {
return;
}
let mask = crate::tuning::scale_octave_channel_mask(payload[4], payload[5], payload[6]);
let mut offsets = [0.0f32; 12];
for (pc, off) in offsets.iter_mut().enumerate() {
let msb = payload[7 + pc * 2];
let lsb = payload[7 + pc * 2 + 1];
*off = crate::tuning::scale_octave_2byte_to_cents(msb, lsb);
}
apply_scale_octave_to_mask(mixer, mask, offsets, live);
}
_ => {}
}
}
fn apply_scale_octave_to_mask(
mixer: &mut crate::mixer::Mixer,
mask: u16,
offsets: [f32; 12],
live: bool,
) {
for ch in 0..16u8 {
if mask & (1 << ch) != 0 {
mixer.set_scale_octave_tuning(ch, offsets, live);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::instruments::tone::ToneInstrument;
use crate::smf::parse;
fn smf_with_events(division: u16, events: &[u8]) -> Vec<u8> {
let mut blob = Vec::new();
blob.extend_from_slice(b"MThd");
blob.extend_from_slice(&6u32.to_be_bytes());
blob.extend_from_slice(&0u16.to_be_bytes()); blob.extend_from_slice(&1u16.to_be_bytes()); blob.extend_from_slice(&division.to_be_bytes());
blob.extend_from_slice(b"MTrk");
blob.extend_from_slice(&(events.len() as u32).to_be_bytes());
blob.extend_from_slice(events);
blob
}
#[test]
fn samples_per_tick_default_tempo_120bpm_44100() {
let smf = parse(&smf_with_events(480, &[0x00, 0xFF, 0x2F, 0x00])).unwrap();
let s = Scheduler::new(&smf, 44_100);
assert!(
(s.samples_per_tick - 45.9375).abs() < 1e-6,
"got {}",
s.samples_per_tick,
);
}
#[test]
fn samples_per_tick_tempo_change_60bpm() {
let mut s = Scheduler::new(
&parse(&smf_with_events(480, &[0x00, 0xFF, 0x2F, 0x00])).unwrap(),
44_100,
);
s.tempo_us_per_quarter = 1_000_000;
s.recompute_samples_per_tick();
assert!((s.samples_per_tick - 91.875).abs() < 1e-6);
}
#[test]
fn samples_per_tick_smpte_division() {
let div = u16::from_be_bytes([0xE7, 0x28]);
let smf = parse(&smf_with_events(div, &[0x00, 0xFF, 0x2F, 0x00])).unwrap();
let s = Scheduler::new(&smf, 48_000);
assert!((s.samples_per_tick - 48.0).abs() < 1e-9);
}
#[test]
fn merges_multi_track_in_tick_order() {
let mut blob = Vec::new();
blob.extend_from_slice(b"MThd");
blob.extend_from_slice(&6u32.to_be_bytes());
blob.extend_from_slice(&1u16.to_be_bytes()); blob.extend_from_slice(&2u16.to_be_bytes()); blob.extend_from_slice(&480u16.to_be_bytes());
let t1: &[u8] = &[
0x00, 0xFF, 0x51, 0x03, 0x07, 0xA1, 0x20, 0x64, 0xFF, 0x2F, 0x00, ];
blob.extend_from_slice(b"MTrk");
blob.extend_from_slice(&(t1.len() as u32).to_be_bytes());
blob.extend_from_slice(t1);
let t2: &[u8] = &[
0x32, 0x90, 0x3C, 0x64, 0x32, 0xFF, 0x2F, 0x00, ];
blob.extend_from_slice(b"MTrk");
blob.extend_from_slice(&(t2.len() as u32).to_be_bytes());
blob.extend_from_slice(t2);
let smf = parse(&blob).unwrap();
let s = Scheduler::new(&smf, 44_100);
assert_eq!(s.events.len(), 4);
assert_eq!(s.events[0].tick, 0);
assert_eq!(s.events[1].tick, 50);
assert_eq!(s.events[2].tick, 100);
assert_eq!(s.events[3].tick, 100);
assert_eq!(s.events[2].track, 0);
assert_eq!(s.events[3].track, 1);
}
#[test]
fn step_dispatches_events_within_window() {
let events = &[
0x00, 0x90, 0x3C, 0x64, 0xC0, 0x00, 0x80, 0x3C, 0x40, 0x00, 0xFF, 0x2F, 0x00,
];
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0x90, 0x3C, 0x64]); ev.extend_from_slice(&[0x83, 0x60]); ev.extend_from_slice(&[0x80, 0x3C, 0x40]); ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]); let _ = events;
let smf = parse(&smf_with_events(480, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
let done = s.step(1024, &mut mixer, &inst);
assert!(!done);
assert_eq!(mixer.live_voice_count(), 1);
for _ in 0..30 {
s.step(1024, &mut mixer, &inst);
}
assert!(s.is_done());
}
#[test]
fn channel_state_program_change_takes_effect() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xC0, 0x18]); ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(4096, &mut mixer, &inst);
assert_eq!(mixer.channel_state(0).program, 24);
}
#[test]
fn cc_volume_and_pan_propagate_to_mixer() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xB0, 7, 50]); ev.extend_from_slice(&[0x00, 0xB0, 10, 0]); ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(4096, &mut mixer, &inst);
assert_eq!(mixer.channel_state(0).volume, 50);
assert_eq!(mixer.channel_state(0).pan, 0);
}
#[test]
fn pitch_bend_event_propagates_to_mixer() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0x90, 0x3C, 0x64]); ev.extend_from_slice(&[0x10, 0xE0, 0x7F, 0x7F]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.channel_state(0).pitch_bend, 0x3FFF);
}
#[test]
fn channel_pressure_event_propagates() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0x90, 0x3C, 0x64]); ev.extend_from_slice(&[0x10, 0xD0, 0x40]); ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.channel_state(0).channel_pressure, 0x40);
}
#[test]
fn rpn_zero_data_entry_changes_pitch_bend_range() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xB0, 101, 0]);
ev.extend_from_slice(&[0x00, 0xB0, 100, 0]);
ev.extend_from_slice(&[0x00, 0xB0, 6, 12]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(4096, &mut mixer, &inst);
assert_eq!(mixer.channel_state(0).pitch_bend_range_cents, 1200);
}
#[test]
fn data_increment_decrement_route_to_mixer() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xB0, 101, 0]);
ev.extend_from_slice(&[0x00, 0xB0, 100, 0]);
ev.extend_from_slice(&[0x00, 0xB0, 96, 0x7F]);
ev.extend_from_slice(&[0x00, 0xB0, 96, 0x00]);
ev.extend_from_slice(&[0x00, 0xB0, 97, 0x55]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(4096, &mut mixer, &inst);
assert_eq!(mixer.channel_state(0).pitch_bend_range_cents, 201);
}
#[test]
fn poly_aftertouch_event_propagates() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0x90, 0x3C, 0x64]);
ev.extend_from_slice(&[0x10, 0xA0, 0x3C, 0x50]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.live_voice_count(), 1);
}
#[test]
fn cc1_mod_wheel_propagates_via_scheduler() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xB0, 1, 100]); ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(4096, &mut mixer, &inst);
assert_eq!(mixer.channel_state(0).mod_wheel, 100);
}
#[test]
fn cc74_timbre_does_not_panic_via_scheduler() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0x90, 0x3C, 0x64]); ev.extend_from_slice(&[0x10, 0xB0, 74, 96]); ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.live_voice_count(), 1);
}
#[test]
fn universal_master_volume_sysex_routes_to_mixer() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x01, 0x00, 0x40, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.master_volume_14(), 0x2000);
}
#[test]
fn universal_master_balance_sysex_routes_centre() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x02, 0x00, 0x40, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.master_balance_14(), 0x2000);
}
#[test]
fn universal_master_balance_sysex_routes_hard_left() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x02, 0x00, 0x00, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.master_balance_14(), 0x0000);
let (l, r) = mixer.master_balance_gains();
assert_eq!(l, 1.0);
assert_eq!(r, 0.0);
}
#[test]
fn universal_master_balance_sysex_routes_hard_right() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x02, 0x7F, 0x7F, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.master_balance_14(), 0x3FFF);
let (l, r) = mixer.master_balance_gains();
assert_eq!(l, 0.0);
assert_eq!(r, 1.0);
}
#[test]
fn universal_master_fine_tuning_sysex_routes() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x03, 0x00, 0x60, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
let c = mixer.master_fine_tune_cents();
assert!((49..=50).contains(&c), "got {c}");
}
#[test]
fn universal_master_coarse_tuning_sysex_routes() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x04, 0x00, 0x4C, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.master_coarse_tune_semitones(), 12);
}
fn sysex_event(delta: u8, body: &[u8]) -> Vec<u8> {
let mut v = vec![delta, 0xF0, (body.len() + 1) as u8];
v.extend_from_slice(body);
v.push(0xF7);
v
}
#[test]
fn gm2_reverb_chorus_defaults() {
let mixer = Mixer::new();
let fx = mixer.gm_effects();
assert_eq!(fx.reverb_type, 4);
assert_eq!(fx.chorus_type, 2);
assert!((fx.reverb_time_s - ((24.0f32) * 0.025).exp()).abs() < 1e-4);
}
#[test]
fn global_parameter_control_reverb_type_and_time() {
let body = [
0x7F, 0x7F, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x01, 0x50,
];
let mut ev = sysex_event(0x00, &body);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
let fx = mixer.gm_effects();
assert_eq!(fx.reverb_type, 2);
assert!(
(fx.reverb_time_s - 1.0f32.exp()).abs() < 1e-4,
"{}",
fx.reverb_time_s
);
}
#[test]
fn global_parameter_control_chorus_params() {
let body = [
0x7F, 0x7F, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x02, 0x00, 0x03, 0x01, 0x0A, 0x02, 0x0F, 0x03, 0x40, 0x04, 0x32, ];
let mut ev = sysex_event(0x00, &body);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
let fx = mixer.gm_effects();
assert_eq!(fx.chorus_type, 3);
assert!((fx.chorus_mod_rate_hz - 10.0 * 0.122).abs() < 1e-5);
assert!((fx.chorus_mod_depth_ms - (15.0 + 1.0) / 3.2).abs() < 1e-5);
assert!((fx.chorus_feedback_pct - 64.0 * 0.763).abs() < 1e-4);
assert!((fx.chorus_send_to_reverb_pct - 50.0 * 0.787).abs() < 1e-4);
}
#[test]
fn global_parameter_control_non_gm2_slot_ignored() {
let body = [
0x7F, 0x7F, 0x04, 0x05, 0x01, 0x01, 0x01, 0x02, 0x01, 0x00, 0x00, ];
let mut ev = sysex_event(0x00, &body);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.gm_effects().reverb_type, 4);
}
#[test]
fn global_parameter_control_unknown_param_ignored() {
let body = [
0x7F, 0x7F, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x09, 0x7F, 0x00, 0x01, ];
let mut ev = sysex_event(0x00, &body);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.gm_effects().reverb_type, 1);
}
#[test]
fn gm_on_resets_gm2_effects() {
let mut ev = Vec::new();
let gpc = [
0x7F, 0x7F, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
];
ev.extend_from_slice(&sysex_event(0x00, &gpc));
ev.extend_from_slice(&[0x10, 0xF0, 0x05, 0x7E, 0x7F, 0x09, 0x01, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.gm_effects().reverb_type, 4);
assert_eq!(mixer.gm_effects().chorus_type, 2);
}
#[test]
fn universal_gm_on_sysex_resets_state() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x01, 0x00, 0x20, 0xF7]);
ev.extend_from_slice(&[0x00, 0xF0, 0x07, 0x7F, 0x7F, 0x04, 0x02, 0x00, 0x00, 0xF7]);
ev.extend_from_slice(&[0x10, 0xF0, 0x05, 0x7E, 0x7F, 0x09, 0x01, 0xF7]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
assert_eq!(mixer.master_volume_14(), 0x3FFF);
assert_eq!(mixer.master_balance_14(), 0x2000);
assert_eq!(mixer.master_fine_tune_cents(), 0);
assert_eq!(mixer.master_coarse_tune_semitones(), 0);
}
#[test]
fn mcm_via_smf_data_entry_assigns_mpe_zone() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xB0, 0x65, 0x00]);
ev.extend_from_slice(&[0x00, 0xB0, 0x64, 0x06]);
ev.extend_from_slice(&[0x00, 0xB0, 0x06, 0x04]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(96, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(8192, &mut mixer, &inst);
let zone = mixer
.mpe_zone(crate::mixer::MpeZoneKind::Lower)
.expect("MCM must have created the lower zone");
assert_eq!(zone.members, 4);
assert!(mixer.channel_state(0).mpe_role.is_manager());
}
#[test]
fn mts_single_note_real_time_routes_to_mixer() {
let data = [
0x7F, 0x7F, 0x08, 0x02, 0x00, 0x01, 0x3C, 0x3D, 0x00, 0x00, 0xF7,
];
let mut mixer = Mixer::new();
dispatch_universal_sysex(&data, &mut mixer);
let off = mixer.tuning().offset_cents(0, 60);
assert!((off - 100.0).abs() < 1e-3, "off {off}");
}
#[test]
fn mts_single_note_bank_form_routes() {
let data = [
0x7F, 0x7F, 0x08, 0x07, 0x00, 0x00, 0x01, 0x40, 0x41, 0x00, 0x00, 0xF7,
];
let mut mixer = Mixer::new();
dispatch_universal_sysex(&data, &mut mixer);
let off = mixer.tuning().offset_cents(0, 64);
assert!((off - 100.0).abs() < 1e-3, "off {off}");
}
#[test]
fn mts_scale_octave_1byte_routes_to_selected_channel() {
let mut data = vec![0x7F, 0x7F, 0x08, 0x08, 0x00, 0x00, 0x01];
data.push(0x7F); data.resize(data.len() + 11, 0x40); data.push(0xF7);
let mut mixer = Mixer::new();
dispatch_universal_sysex(&data, &mut mixer);
assert!((mixer.tuning().offset_cents(0, 60) - 63.0).abs() < 1e-3);
assert!(mixer.tuning().offset_cents(0, 61).abs() < 1e-3);
assert!(mixer.tuning().offset_cents(1, 60).abs() < 1e-3);
}
#[test]
fn mts_scale_octave_2byte_routes() {
let mut data = vec![0x7F, 0x7F, 0x08, 0x09, 0x00, 0x00, 0x01];
data.extend_from_slice(&[0x7F, 0x7F]); for _ in 0..11 {
data.extend_from_slice(&[0x40, 0x00]); }
data.push(0xF7);
let mut mixer = Mixer::new();
dispatch_universal_sysex(&data, &mut mixer);
let c = mixer.tuning().offset_cents(0, 60);
assert!((c - 99.988).abs() < 0.05, "C offset {c}");
}
#[test]
fn mts_non_real_time_single_note_does_not_retune_sounding_note() {
let data = [
0x7E, 0x7F, 0x08, 0x07, 0x00, 0x00, 0x01, 0x40, 0x41, 0x00, 0x00, 0xF7,
];
let mut mixer = Mixer::new();
dispatch_universal_sysex(&data, &mut mixer);
let off = mixer.tuning().offset_cents(0, 64);
assert!((off - 100.0).abs() < 1e-3, "off {off}");
}
#[test]
fn mts_multi_change_message_sets_several_keys() {
let data = [
0x7F, 0x7F, 0x08, 0x02, 0x00, 0x02, 0x3C, 0x3D, 0x00, 0x00, 0x40, 0x40, 0x40, 0x00, 0xF7,
];
let mut mixer = Mixer::new();
dispatch_universal_sysex(&data, &mut mixer);
assert!((mixer.tuning().offset_cents(0, 60) - 100.0).abs() < 1e-3);
assert!((mixer.tuning().offset_cents(0, 64) - 50.0).abs() < 1e-3);
}
#[test]
fn gm_system_on_resets_mts_tuning() {
let mut mixer = Mixer::new();
dispatch_universal_sysex(
&[
0x7F, 0x7F, 0x08, 0x02, 0x00, 0x01, 0x3C, 0x3D, 0x00, 0x00, 0xF7,
],
&mut mixer,
);
assert!(mixer.tuning().offset_cents(0, 60) > 50.0);
dispatch_universal_sysex(&[0x7E, 0x7F, 0x09, 0x01, 0xF7], &mut mixer);
assert!(mixer.tuning().offset_cents(0, 60).abs() < 1e-3);
}
#[test]
fn mts_truncated_message_does_not_panic() {
let data = [0x7F, 0x7F, 0x08, 0x02, 0x00, 0x04, 0x3C, 0x3D, 0x00, 0x00];
let mut mixer = Mixer::new();
dispatch_universal_sysex(&data, &mut mixer);
assert!((mixer.tuning().offset_cents(0, 60) - 100.0).abs() < 1e-3);
}
#[test]
fn tempo_change_recomputes_samples_per_tick() {
let mut ev = Vec::new();
ev.extend_from_slice(&[0x00, 0xFF, 0x51, 0x03, 0x03, 0xD0, 0x90]);
ev.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
let smf = parse(&smf_with_events(480, &ev)).unwrap();
let mut s = Scheduler::new(&smf, 44_100);
assert!((s.samples_per_tick - 45.9375).abs() < 1e-6);
let mut mixer = Mixer::new();
let inst = ToneInstrument::new();
s.step(2048, &mut mixer, &inst);
assert!(
(s.samples_per_tick - 22.96875).abs() < 1e-6,
"got {}",
s.samples_per_tick,
);
}
}