rustysynth/
midifile.rs

1#![allow(dead_code)]
2
3use std::io::Read;
4
5use crate::binary_reader::BinaryReader;
6use crate::four_cc::FourCC;
7use crate::read_counter::ReadCounter;
8use crate::MidiFileError;
9use crate::MidiFileLoopType;
10
11#[derive(Clone, Copy, Debug)]
12#[non_exhaustive]
13pub(crate) struct Message {
14    pub(crate) channel: u8,
15    pub(crate) command: u8,
16    pub(crate) data1: u8,
17    pub(crate) data2: u8,
18}
19
20impl Message {
21    pub(crate) const NORMAL: u8 = 0;
22    pub(crate) const TEMPO_CHANGE: u8 = 252;
23    pub(crate) const LOOP_START: u8 = 253;
24    pub(crate) const LOOP_END: u8 = 254;
25    pub(crate) const END_OF_TRACK: u8 = 255;
26
27    pub(crate) fn common1(status: u8, data1: u8) -> Self {
28        Self {
29            channel: status & 0x0F,
30            command: status & 0xF0,
31            data1,
32            data2: 0,
33        }
34    }
35
36    pub(crate) fn common2(status: u8, data1: u8, data2: u8, loop_type: MidiFileLoopType) -> Self {
37        let channel = status & 0x0F;
38        let command = status & 0xF0;
39
40        if command == 0xB0 {
41            match loop_type {
42                MidiFileLoopType::RpgMaker => {
43                    if data1 == 111 {
44                        return Message::loop_start();
45                    }
46                }
47
48                MidiFileLoopType::IncredibleMachine => {
49                    if data1 == 110 {
50                        return Message::loop_start();
51                    }
52                    if data1 == 111 {
53                        return Message::loop_end();
54                    }
55                }
56
57                MidiFileLoopType::FinalFantasy => {
58                    if data1 == 116 {
59                        return Message::loop_start();
60                    }
61                    if data1 == 117 {
62                        return Message::loop_end();
63                    }
64                }
65
66                _ => (),
67            }
68        }
69
70        Self {
71            channel,
72            command,
73            data1,
74            data2,
75        }
76    }
77
78    pub(crate) fn tempo_change(tempo: i32) -> Self {
79        Self {
80            channel: Message::TEMPO_CHANGE,
81            command: (tempo >> 16) as u8,
82            data1: (tempo >> 8) as u8,
83            data2: tempo as u8,
84        }
85    }
86
87    pub(crate) fn loop_start() -> Self {
88        Self {
89            channel: Message::LOOP_START,
90            command: 0,
91            data1: 0,
92            data2: 0,
93        }
94    }
95
96    pub(crate) fn loop_end() -> Self {
97        Self {
98            channel: Message::LOOP_END,
99            command: 0,
100            data1: 0,
101            data2: 0,
102        }
103    }
104
105    pub(crate) fn end_of_track() -> Self {
106        Self {
107            channel: Message::END_OF_TRACK,
108            command: 0,
109            data1: 0,
110            data2: 0,
111        }
112    }
113
114    pub(crate) fn get_message_type(&self) -> u8 {
115        match self.channel {
116            Message::TEMPO_CHANGE => Message::TEMPO_CHANGE,
117            Message::LOOP_START => Message::LOOP_START,
118            Message::LOOP_END => Message::LOOP_END,
119            Message::END_OF_TRACK => Message::END_OF_TRACK,
120            _ => Message::NORMAL,
121        }
122    }
123
124    pub(crate) fn get_tempo(&self) -> f64 {
125        60000000.0
126            / (((self.command as i32) << 16) | ((self.data1 as i32) << 8) | (self.data2 as i32))
127                as f64
128    }
129}
130
131/// Represents a standard MIDI file.
132#[derive(Debug)]
133#[non_exhaustive]
134pub struct MidiFile {
135    pub(crate) messages: Vec<Message>,
136    pub(crate) times: Vec<f64>,
137}
138
139impl MidiFile {
140    /// Loads a MIDI file from the stream.
141    ///
142    /// # Arguments
143    ///
144    /// * `reader` - The data stream used to load the MIDI file.
145    pub fn new<R: Read>(reader: &mut R) -> Result<Self, MidiFileError> {
146        MidiFile::new_with_loop_type(reader, MidiFileLoopType::LoopPoint(0))
147    }
148
149    /// Loads a MIDI file from the stream with a specified loop type.
150    ///
151    /// # Arguments
152    ///
153    /// * `reader` - The data stream used to load the MIDI file.
154    /// * `loop_type` - The type of the loop extension to be used.
155    ///
156    /// # Remarks
157    ///
158    /// `MidiFileLoopType` has the following variants:
159    /// * `LoopPoint(usize)` - Specifies the loop start point by a tick value.
160    /// * `RpgMaker` - The RPG Maker style loop.
161    ///   CC #111 will be the loop start point.
162    /// * `IncredibleMachine` - The Incredible Machine style loop.
163    ///   CC #110 and #111 will be the start and end points of the loop.
164    /// * `FinalFantasy` - The Final Fantasy style loop.
165    ///   CC #116 and #117 will be the start and end points of the loop.
166    pub fn new_with_loop_type<R: Read>(
167        reader: &mut R,
168        loop_type: MidiFileLoopType,
169    ) -> Result<Self, MidiFileError> {
170        let chunk_type = BinaryReader::read_four_cc(reader)?;
171        if chunk_type != b"MThd" {
172            return Err(MidiFileError::InvalidChunkType {
173                expected: FourCC::from_bytes(*b"MThd"),
174                actual: chunk_type,
175            });
176        }
177
178        let size = BinaryReader::read_i32_big_endian(reader)?;
179        if size != 6 {
180            return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
181                *b"MThd",
182            )));
183        }
184
185        let format = BinaryReader::read_i16_big_endian(reader)?;
186        if !(format == 0 || format == 1) {
187            return Err(MidiFileError::UnsupportedFormat(format));
188        }
189
190        let track_count = BinaryReader::read_i16_big_endian(reader)? as i32;
191        let resolution = BinaryReader::read_i16_big_endian(reader)? as i32;
192
193        let mut message_lists: Vec<Vec<Message>> = Vec::new();
194        let mut tick_lists: Vec<Vec<i32>> = Vec::new();
195
196        for _i in 0..track_count {
197            let (message_list, tick_list) = MidiFile::read_track(reader, loop_type)?;
198            message_lists.push(message_list);
199            tick_lists.push(tick_list);
200        }
201
202        match loop_type {
203            MidiFileLoopType::LoopPoint(loop_point) if loop_point != 0 => {
204                let loop_point = loop_point as i32;
205                let tick_list = &mut tick_lists[0];
206                let message_list = &mut message_lists[0];
207
208                if loop_point <= *tick_list.last().unwrap() {
209                    for i in 0..tick_list.len() {
210                        if tick_list[i] >= loop_point {
211                            tick_list.insert(i, loop_point);
212                            message_list.insert(i, Message::loop_start());
213                            break;
214                        }
215                    }
216                } else {
217                    tick_list.push(loop_point);
218                    message_list.push(Message::loop_start());
219                }
220            }
221            _ => (),
222        }
223
224        let (messages, times) = MidiFile::merge_tracks(&message_lists, &tick_lists, resolution);
225
226        Ok(Self { messages, times })
227    }
228
229    fn discard_data<R: Read>(reader: &mut R) -> Result<(), MidiFileError> {
230        let size = BinaryReader::read_i32_variable_length(reader)? as usize;
231        BinaryReader::discard_data(reader, size)?;
232        Ok(())
233    }
234
235    fn read_tempo<R: Read>(reader: &mut R) -> Result<i32, MidiFileError> {
236        let size = BinaryReader::read_i32_variable_length(reader)?;
237        if size != 3 {
238            return Err(MidiFileError::InvalidTempoValue);
239        }
240
241        let b1 = BinaryReader::read_u8(reader)? as i32;
242        let b2 = BinaryReader::read_u8(reader)? as i32;
243        let b3 = BinaryReader::read_u8(reader)? as i32;
244
245        Ok((b1 << 16) | (b2 << 8) | b3)
246    }
247
248    fn read_track<R: Read>(
249        reader: &mut R,
250        loop_type: MidiFileLoopType,
251    ) -> Result<(Vec<Message>, Vec<i32>), MidiFileError> {
252        let chunk_type = BinaryReader::read_four_cc(reader)?;
253        if chunk_type != b"MTrk" {
254            return Err(MidiFileError::InvalidChunkType {
255                expected: FourCC::from_bytes(*b"MTrk"),
256                actual: chunk_type,
257            });
258        }
259
260        let size = BinaryReader::read_i32_big_endian(reader)? as usize;
261        let reader = &mut ReadCounter::new(reader);
262
263        let mut messages: Vec<Message> = Vec::new();
264        let mut ticks: Vec<i32> = Vec::new();
265
266        let mut tick: i32 = 0;
267        let mut last_status: u8 = 0;
268
269        loop {
270            let delta = BinaryReader::read_i32_variable_length(reader)?;
271            let first = BinaryReader::read_u8(reader)?;
272
273            tick += delta;
274
275            if (first & 128) == 0 {
276                let command = last_status & 0xF0;
277                if command == 0xC0 || command == 0xD0 {
278                    messages.push(Message::common1(last_status, first));
279                    ticks.push(tick);
280                } else {
281                    let data2 = BinaryReader::read_u8(reader)?;
282                    messages.push(Message::common2(last_status, first, data2, loop_type));
283                    ticks.push(tick);
284                }
285
286                continue;
287            }
288
289            match first {
290                0xF0 => MidiFile::discard_data(reader)?,
291                0xF7 => MidiFile::discard_data(reader)?,
292                0xFF => match BinaryReader::read_u8(reader)? {
293                    0x2F => {
294                        BinaryReader::read_u8(reader)?;
295                        messages.push(Message::end_of_track());
296                        ticks.push(tick);
297
298                        // Some MIDI files may have events inserted after the EOT.
299                        // Such events should be ignored.
300                        if reader.bytes_read() < size {
301                            BinaryReader::discard_data(reader, size - reader.bytes_read())?;
302                        }
303
304                        return Ok((messages, ticks));
305                    }
306                    0x51 => {
307                        messages.push(Message::tempo_change(MidiFile::read_tempo(reader)?));
308                        ticks.push(tick);
309                    }
310                    _ => MidiFile::discard_data(reader)?,
311                },
312                _ => {
313                    let command = first & 0xF0;
314                    if command == 0xC0 || command == 0xD0 {
315                        let data1 = BinaryReader::read_u8(reader)?;
316                        messages.push(Message::common1(first, data1));
317                        ticks.push(tick);
318                    } else {
319                        let data1 = BinaryReader::read_u8(reader)?;
320                        let data2 = BinaryReader::read_u8(reader)?;
321                        messages.push(Message::common2(first, data1, data2, loop_type));
322                        ticks.push(tick);
323                    }
324                }
325            }
326
327            last_status = first
328        }
329    }
330
331    fn merge_tracks(
332        message_lists: &[Vec<Message>],
333        tick_lists: &[Vec<i32>],
334        resolution: i32,
335    ) -> (Vec<Message>, Vec<f64>) {
336        let mut merged_messages: Vec<Message> = Vec::new();
337        let mut merged_times: Vec<f64> = Vec::new();
338
339        let mut indices: Vec<usize> = vec![0; message_lists.len()];
340
341        let mut current_tick: i32 = 0;
342        let mut current_time: f64 = 0.0;
343
344        let mut tempo: f64 = 120.0;
345
346        loop {
347            let mut min_tick = i32::MAX;
348            let mut min_index: i32 = -1;
349
350            for ch in 0..tick_lists.len() {
351                if indices[ch] < tick_lists[ch].len() {
352                    let tick = tick_lists[ch][indices[ch]];
353                    if tick < min_tick {
354                        min_tick = tick;
355                        min_index = ch as i32;
356                    }
357                }
358            }
359
360            if min_index == -1 {
361                break;
362            }
363
364            let next_tick = tick_lists[min_index as usize][indices[min_index as usize]];
365            let delta_tick = next_tick - current_tick;
366            let delta_time = 60.0 / (resolution as f64 * tempo) * delta_tick as f64;
367
368            current_tick += delta_tick;
369            current_time += delta_time;
370
371            let message = message_lists[min_index as usize][indices[min_index as usize]];
372            if message.get_message_type() == Message::TEMPO_CHANGE {
373                tempo = message.get_tempo();
374            } else {
375                merged_messages.push(message);
376                merged_times.push(current_time);
377            }
378
379            indices[min_index as usize] += 1;
380        }
381
382        (merged_messages, merged_times)
383    }
384
385    /// Get the length of the MIDI file in seconds.
386    pub fn get_length(&self) -> f64 {
387        *self.times.last().unwrap()
388    }
389}