1use std::time::Duration;
2
3use sim_lib_midi_core::{
4 CC_ALL_NOTES_OFF, CC_BANK_SELECT_MSB, CC_EXPRESSION_MSB, CC_PAN_MSB, CC_SUSTAIN_PEDAL,
5 CC_VOLUME_MSB, ChannelMessage, MetaEvent, MidiEvent, MidiPayload, MidiSink, TickTime, U14,
6};
7use sim_lib_pitch_core::Pitch;
8use sim_lib_sound_tuning::{Tuning, render_pitch_with_tuning};
9
10use crate::{
11 BridgeChannelState, BridgeOptions, ScheduledTone, SoundBridgeError, TimbreBank, Voice,
12 VoicePhase, VoicePool, current_timbre, tone_gain,
13};
14
15pub struct MidiToSoundBridge {
18 tpq: u32,
19 options: BridgeOptions,
20 bank: TimbreBank,
21 tuning: Box<dyn Tuning>,
22 clock: Duration,
23 last_tick: TickTime,
24 us_per_quarter: u32,
25 channels: [BridgeChannelState; 16],
26 voices: VoicePool,
27 emitted: Vec<ScheduledTone>,
28}
29
30impl MidiToSoundBridge {
31 pub fn new(
34 tpq: u32,
35 bank: TimbreBank,
36 tuning: Box<dyn Tuning>,
37 options: BridgeOptions,
38 ) -> Result<Self, SoundBridgeError> {
39 Ok(Self {
40 tpq,
41 voices: VoicePool::new(options.polyphony_limit)?,
42 options,
43 bank,
44 tuning,
45 clock: Duration::ZERO,
46 last_tick: TickTime::new(0, tpq).expect("validated tpq"),
47 us_per_quarter: 500_000,
48 channels: std::array::from_fn(|_| BridgeChannelState::default()),
49 emitted: Vec::new(),
50 })
51 }
52
53 pub fn options(&self) -> &BridgeOptions {
55 &self.options
56 }
57
58 pub fn bank(&self) -> &TimbreBank {
60 &self.bank
61 }
62
63 pub fn voice_pool(&self) -> &VoicePool {
65 &self.voices
66 }
67
68 pub fn drain_tones(&mut self) -> Vec<ScheduledTone> {
70 self.drain_pending();
71 std::mem::take(&mut self.emitted)
72 }
73
74 pub fn stolen_voice_count(&self) -> usize {
76 self.voices.stolen_voice_count()
77 }
78
79 fn advance_to(&mut self, time: TickTime) -> Result<(), SoundBridgeError> {
80 let rebased = if time.tpq == self.tpq {
81 time
82 } else {
83 time.quantize(self.tpq)
84 };
85 if rebased.ticks < self.last_tick.ticks {
86 return Err(SoundBridgeError::NonMonotonicTime);
87 }
88 let delta_ticks = rebased.ticks - self.last_tick.ticks;
89 let delta_quarters = delta_ticks as f64 / f64::from(self.tpq);
90 let delta_secs = delta_quarters * f64::from(self.us_per_quarter) / 1_000_000.0;
91 self.clock += Duration::from_secs_f64(delta_secs.max(0.0));
92 self.last_tick = rebased;
93 self.voices.reap_finished(self.clock);
94 self.drain_pending();
95 Ok(())
96 }
97
98 fn handle_channel_message(&mut self, message: &ChannelMessage) {
99 match *message {
100 ChannelMessage::NoteOn { ch, key, vel } if vel.0 == 0 => {
101 let sustain = self.channels[ch.0 as usize].sustain;
102 self.voices.note_off(ch.0, key.0, self.clock, sustain);
103 }
104 ChannelMessage::NoteOn { ch, key, vel } => {
105 let state = self.channels[ch.0 as usize].clone();
106 let pitch = Pitch::from_midi(key.0);
107 let frequency = render_pitch_with_tuning(pitch, self.tuning.as_ref());
108 let frequency = frequency.shift_cents(state.pitch_bend_cents);
109 let voice = Voice {
110 channel: ch.0,
111 key: key.0,
112 started_at: self.clock,
113 release_at: None,
114 release_end: None,
115 frequency,
116 amplitude_gain: tone_gain(&state, vel.0),
117 pan: state.pan,
118 phase: VoicePhase::Held,
119 timbre: current_timbre(&self.bank, &state).clone(),
120 };
121 self.voices.note_on(voice, self.clock);
122 }
123 ChannelMessage::NoteOff { ch, key, .. } => {
124 let sustain = self.channels[ch.0 as usize].sustain;
125 self.voices.note_off(ch.0, key.0, self.clock, sustain);
126 }
127 ChannelMessage::ControlChange { ch, cc, value } => {
128 let state = &mut self.channels[ch.0 as usize];
129 match cc {
130 CC_BANK_SELECT_MSB => state.bank_msb = value.0,
131 CC_VOLUME_MSB => state.volume = f64::from(value.0) / 127.0,
132 CC_PAN_MSB => state.pan = (f32::from(value.0) - 64.0) / 63.0,
133 CC_EXPRESSION_MSB => state.expression = f64::from(value.0) / 127.0,
134 CC_SUSTAIN_PEDAL => {
135 let sustain = value.0 >= 64;
136 if state.sustain && !sustain {
137 self.voices.release_channel_sustain(ch.0, self.clock);
138 }
139 state.sustain = sustain;
140 }
141 CC_ALL_NOTES_OFF => {
142 let sustain = state.sustain;
143 for key in 0..=127 {
144 self.voices.note_off(ch.0, key, self.clock, sustain);
145 }
146 }
147 _ => {}
148 }
149 }
150 ChannelMessage::ProgramChange { ch, program } => {
151 self.channels[ch.0 as usize].program = program.0;
152 }
153 ChannelMessage::PitchBend { ch, value } => {
154 let cents = pitch_bend_to_cents(value, self.options.bend_range_cents);
155 let state = &mut self.channels[ch.0 as usize];
156 let delta = cents - state.pitch_bend_cents;
157 state.pitch_bend_cents = cents;
158 self.voices.transpose_channel_cents(ch.0, delta);
159 }
160 ChannelMessage::PolyAftertouch { .. } | ChannelMessage::ChanAftertouch { .. } => {}
161 }
162 }
163
164 fn drain_pending(&mut self) {
165 self.emitted.extend(self.voices.drain_emitted());
166 }
167}
168
169impl MidiSink for MidiToSoundBridge {
170 type Err = SoundBridgeError;
171
172 fn tpq(&self) -> u32 {
173 self.tpq
174 }
175
176 fn write(&mut self, event: &MidiEvent) -> Result<(), Self::Err> {
177 self.advance_to(event.time)?;
178 match &event.payload {
179 MidiPayload::Channel(message) => self.handle_channel_message(message),
180 MidiPayload::Meta(MetaEvent::Tempo { us_per_quarter }) => {
181 self.us_per_quarter = *us_per_quarter;
182 }
183 MidiPayload::Meta(_) | MidiPayload::SysEx(_) | MidiPayload::Raw(_) => {}
184 }
185 Ok(())
186 }
187
188 fn flush(&mut self) -> Result<(), Self::Err> {
189 self.voices.flush_all(self.clock);
190 self.drain_pending();
191 Ok(())
192 }
193}
194
195fn pitch_bend_to_cents(value: U14, bend_range_cents: f64) -> f64 {
196 let normalized = (f64::from(value.0) - 8192.0) / 8192.0;
197 normalized * bend_range_cents
198}