Skip to main content

sim_lib_sound_bridge/
bridge.rs

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
15/// A [`MidiSink`] that interprets MIDI events into scheduled tones, managing
16/// per-channel state, tuning, voice allocation, and timing.
17pub 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    /// Builds a bridge with the given ticks-per-quarter, timbre bank, tuning,
32    /// and options.
33    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    /// Returns the bridge options.
54    pub fn options(&self) -> &BridgeOptions {
55        &self.options
56    }
57
58    /// Returns the timbre bank.
59    pub fn bank(&self) -> &TimbreBank {
60        &self.bank
61    }
62
63    /// Returns the underlying voice pool.
64    pub fn voice_pool(&self) -> &VoicePool {
65        &self.voices
66    }
67
68    /// Drains all tones emitted so far, returning them and clearing the buffer.
69    pub fn drain_tones(&mut self) -> Vec<ScheduledTone> {
70        self.drain_pending();
71        std::mem::take(&mut self.emitted)
72    }
73
74    /// Returns the number of voices stolen due to polyphony limits.
75    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}