Skip to main content

rustysynth_ext/
midifile_sequencer.rs

1use std::cmp;
2use std::sync::Arc;
3
4use crate::midifile::MidiFile;
5use crate::midifile::MidiMessage;
6use crate::synthesizer::Synthesizer;
7
8/// An instance of the MIDI file sequencer.
9#[derive(Debug)]
10#[non_exhaustive]
11pub struct MidiFileSequencer {
12    synthesizer: Synthesizer,
13
14    speed: f64,
15
16    midi_file: Option<Arc<MidiFile>>,
17    play_loop: bool,
18
19    block_wrote: usize,
20
21    current_time: f64,
22    msg_index: usize,
23    loop_index: usize,
24}
25
26impl MidiFileSequencer {
27    /// Initializes a new instance of the sequencer.
28    ///
29    /// # Arguments
30    ///
31    /// * `synthesizer` - The synthesizer to be handled by the sequencer.
32    pub fn new(synthesizer: Synthesizer) -> Self {
33        Self {
34            synthesizer,
35            speed: 1.0,
36            midi_file: None,
37            play_loop: false,
38            block_wrote: 0,
39            current_time: 0.0,
40            msg_index: 0,
41            loop_index: 0,
42        }
43    }
44
45    /// Plays the MIDI file.
46    ///
47    /// # Arguments
48    ///
49    /// * `midi_file` - The MIDI file to be played.
50    /// * `play_loop` - If `true`, the MIDI file loops after reaching the end.
51    pub fn play(&mut self, midi_file: &Arc<MidiFile>, play_loop: bool) {
52        self.midi_file = Some(Arc::clone(midi_file));
53        self.play_loop = play_loop;
54
55        self.block_wrote = self.synthesizer.block_size;
56
57        self.current_time = 0.0;
58        self.msg_index = 0;
59        self.loop_index = 0;
60
61        self.synthesizer.reset()
62    }
63
64    /// Stops playing.
65    pub fn stop(&mut self) {
66        self.midi_file = None;
67        self.synthesizer.reset();
68    }
69
70    /// Renders the waveform.
71    ///
72    /// # Arguments
73    ///
74    /// * `left` - The buffer of the left channel to store the rendered waveform.
75    /// * `right` - The buffer of the right channel to store the rendered waveform.
76    ///
77    /// # Remarks
78    ///
79    /// The output buffers for the left and right must be the same length.
80    pub fn render(&mut self, left: &mut [f32], right: &mut [f32]) {
81        if left.len() != right.len() {
82            panic!("The output buffers for the left and right must be the same length.");
83        }
84
85        let left_length = left.len();
86        let mut wrote: usize = 0;
87        while wrote < left_length {
88            if self.block_wrote == self.synthesizer.block_size {
89                self.process_events();
90                self.block_wrote = 0;
91                self.current_time += self.speed * self.synthesizer.block_size as f64
92                    / self.synthesizer.sample_rate as f64;
93            }
94
95            let src_rem = self.synthesizer.block_size - self.block_wrote;
96            let dst_rem = left_length - wrote;
97            let rem = cmp::min(src_rem, dst_rem);
98
99            self.synthesizer.render(
100                &mut left[wrote..wrote + rem],
101                &mut right[wrote..wrote + rem],
102            );
103
104            self.block_wrote += rem;
105            wrote += rem;
106        }
107    }
108
109    fn process_events(&mut self) {
110        let midi_file = match self.midi_file.as_ref() {
111            Some(value) => value,
112            None => return,
113        };
114
115        while self.msg_index < midi_file.messages.len() {
116            let time = midi_file.times[self.msg_index];
117            let msg = midi_file.messages[self.msg_index];
118
119            if time <= self.current_time {
120                match msg {
121                    MidiMessage::Normal {
122                        status,
123                        data1,
124                        data2,
125                    } => {
126                        let channel = status & 0x0F;
127                        let command = status & 0xF0;
128                        self.synthesizer.process_midi_message(
129                            channel as i32,
130                            command as i32,
131                            data1 as i32,
132                            data2 as i32,
133                        );
134                    }
135                    MidiMessage::LoopStart if self.play_loop => self.loop_index = self.msg_index,
136                    // A loop that starts and ends at the same tick would rewind to the
137                    // very message that is being processed and never let the render call
138                    // finish, so a loop needs a non-empty tick range to be taken.
139                    // https://github.com/sinshu/rustysynth/issues/57
140                    MidiMessage::LoopEnd
141                        if self.play_loop && time > midi_file.times[self.loop_index] =>
142                    {
143                        self.current_time = midi_file.times[self.loop_index];
144                        self.msg_index = self.loop_index;
145                        self.synthesizer.note_off_all(false);
146                    }
147                    _ => (),
148                }
149                self.msg_index += 1;
150            } else {
151                break;
152            }
153        }
154
155        if self.msg_index == midi_file.messages.len()
156            && self.play_loop
157            && self.loop_index < self.msg_index
158            && midi_file.times[self.loop_index] < midi_file.times[self.msg_index - 1]
159        {
160            self.current_time = midi_file.times[self.loop_index];
161            self.msg_index = self.loop_index;
162            self.synthesizer.note_off_all(false);
163        }
164    }
165
166    /// Gets the synthesizer handled by the sequencer.
167    pub fn get_synthesizer(&self) -> &Synthesizer {
168        &self.synthesizer
169    }
170
171    /// Gets the currently playing MIDI file.
172    pub fn get_midi_file(&self) -> Option<&MidiFile> {
173        match &self.midi_file {
174            None => None,
175            Some(value) => Some(value),
176        }
177    }
178
179    /// Gets the current playback position in seconds.
180    pub fn get_position(&self) -> f64 {
181        self.current_time
182    }
183
184    /// Gets a value that indicates whether the current playback position is at the end of the sequence.
185    ///
186    /// # Remarks
187    ///
188    /// If the `play` method has not yet been called, this value will be `true`.
189    /// This value will never be `true` if loop playback is enabled.
190    pub fn end_of_sequence(&self) -> bool {
191        match &self.midi_file {
192            None => true,
193            Some(value) => self.msg_index == value.messages.len(),
194        }
195    }
196
197    /// Gets the current playback speed.
198    ///
199    /// # Remarks
200    ///
201    /// The default value is 1.
202    /// The tempo will be multiplied by this value during playback.
203    pub fn get_speed(&self) -> f64 {
204        self.speed
205    }
206
207    /// Sets the playback speed.
208    ///
209    /// # Remarks
210    ///
211    /// The value must be non-negative.
212    pub fn set_speed(&mut self, value: f64) {
213        if value < 0.0 {
214            panic!("The playback speed must be a non-negative value.");
215        }
216
217        self.speed = value;
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[cfg(not(target_family = "wasm"))]
226    use std::sync::mpsc;
227    #[cfg(not(target_family = "wasm"))]
228    use std::thread;
229    #[cfg(not(target_family = "wasm"))]
230    use std::time::Duration;
231
232    use crate::test_font::*;
233    use crate::{SoundFont, SynthesizerSettings};
234
235    fn synthesizer() -> Synthesizer {
236        let mut reader = std::io::Cursor::new(build_sf2(&sample_spec(), 1));
237        let sound_font = Arc::new(SoundFont::new(&mut reader).unwrap());
238        let settings = SynthesizerSettings::new(44100);
239        Synthesizer::new(&sound_font, &settings).unwrap()
240    }
241
242    // A loop that starts and ends on the same tick used to rewind to the very
243    // message being processed, so a single render call never returned (#57).
244    #[test]
245    fn test_a_zero_length_loop_does_not_hang_the_sequencer() {
246        let midi_file = Arc::new(
247            MidiFile::new_with_events([
248                (0.0, MidiMessage::LoopStart),
249                (0.0, MidiMessage::LoopEnd),
250                (0.0, MidiMessage::EndOfTrack),
251            ])
252            .unwrap(),
253        );
254
255        let render = move |midi_file: Arc<MidiFile>| {
256            let mut sequencer = MidiFileSequencer::new(synthesizer());
257            sequencer.play(&midi_file, true);
258
259            let mut left = vec![0_f32; 512];
260            let mut right = vec![0_f32; 512];
261            sequencer.render(&mut left, &mut right);
262
263            sequencer.end_of_sequence()
264        };
265
266        // The work runs on a thread so that a regression fails the test instead
267        // of hanging the test run forever, which is not an option where threads
268        // are not available.
269        #[cfg(not(target_family = "wasm"))]
270        {
271            let (sender, receiver) = mpsc::channel();
272            thread::spawn(move || {
273                sender.send(render(midi_file)).unwrap();
274            });
275
276            let end_of_sequence = receiver
277                .recv_timeout(Duration::from_secs(10))
278                .expect("the sequencer did not finish rendering");
279            assert!(end_of_sequence);
280        }
281
282        #[cfg(target_family = "wasm")]
283        assert!(render(midi_file));
284    }
285
286    // A loop with a range still repeats: rendering must not fall through to
287    // the end of the sequence.
288    #[test]
289    fn test_a_normal_loop_still_repeats() {
290        let midi_file = Arc::new(
291            MidiFile::new_with_events([
292                (0.0, MidiMessage::LoopStart),
293                (
294                    0.0,
295                    MidiMessage::Normal {
296                        status: 0x90,
297                        data1: 60,
298                        data2: 100,
299                    },
300                ),
301                (1.0, MidiMessage::LoopEnd),
302                (2.0, MidiMessage::EndOfTrack),
303            ])
304            .unwrap(),
305        );
306
307        let mut sequencer = MidiFileSequencer::new(synthesizer());
308        sequencer.play(&midi_file, true);
309
310        let mut left = vec![0_f32; 44100];
311        let mut right = vec![0_f32; 44100];
312        sequencer.render(&mut left, &mut right);
313
314        assert!(!sequencer.end_of_sequence());
315        assert!(sequencer.get_position() < 2.0);
316    }
317}