rustysynth_ext/
midifile.rs1use std::io::Read;
2
3use crate::MidiFileError;
4use crate::MidiFileLoopType;
5use crate::binary_reader::BinaryReader;
6use crate::four_cc::FourCC;
7use crate::read_counter::ReadCounter;
8
9#[derive(Clone, Copy, Debug)]
10#[non_exhaustive]
11pub(crate) enum Message {
12 Normal { status: u8, data1: u8, data2: u8 },
13 TempoChange { bytes: [u8; 3] },
14 LoopStart,
15 LoopEnd,
16 EndOfTrack,
17}
18
19impl Message {
20 pub(crate) fn common1(status: u8, data1: u8) -> Self {
21 Self::Normal {
22 status,
23 data1,
24 data2: 0,
25 }
26 }
27
28 pub(crate) fn common2(status: u8, data1: u8, data2: u8, loop_type: MidiFileLoopType) -> Self {
29 let command = status & 0xF0;
30
31 if command == 0xB0 {
32 match loop_type {
33 MidiFileLoopType::RpgMaker if data1 == 111 => {
34 return Message::LoopStart;
35 }
36
37 MidiFileLoopType::IncredibleMachine => {
38 if data1 == 110 {
39 return Message::LoopStart;
40 }
41 if data1 == 111 {
42 return Message::LoopEnd;
43 }
44 }
45
46 MidiFileLoopType::FinalFantasy => {
47 if data1 == 116 {
48 return Message::LoopStart;
49 }
50 if data1 == 117 {
51 return Message::LoopEnd;
52 }
53 }
54
55 _ => (),
56 }
57 }
58
59 Self::Normal {
60 status,
61 data1,
62 data2,
63 }
64 }
65
66 pub(crate) fn tempo_change(tempo: i32) -> Self {
67 let bytes = tempo.to_be_bytes()[1..].try_into().unwrap();
69 Self::TempoChange { bytes }
70 }
71}
72
73#[derive(Debug)]
75#[non_exhaustive]
76pub struct MidiFile {
77 pub(crate) messages: Vec<Message>,
78 pub(crate) times: Vec<f64>,
79}
80
81impl MidiFile {
82 pub fn new<R: Read>(reader: &mut R) -> Result<Self, MidiFileError> {
88 MidiFile::new_with_loop_type(reader, MidiFileLoopType::LoopPoint(0))
89 }
90
91 pub fn new_with_loop_type<R: Read>(
109 reader: &mut R,
110 loop_type: MidiFileLoopType,
111 ) -> Result<Self, MidiFileError> {
112 let chunk_type = BinaryReader::read_four_cc(reader)?;
113 if chunk_type != b"MThd" {
114 return Err(MidiFileError::InvalidChunkType {
115 expected: FourCC::from_bytes(*b"MThd"),
116 actual: chunk_type,
117 });
118 }
119
120 let size = BinaryReader::read_i32_big_endian(reader)?;
121 if size != 6 {
122 return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
123 *b"MThd",
124 )));
125 }
126
127 let format = BinaryReader::read_i16_big_endian(reader)?;
128 if !(format == 0 || format == 1) {
129 return Err(MidiFileError::UnsupportedFormat(format));
130 }
131
132 let track_count = BinaryReader::read_i16_big_endian(reader)? as i32;
133 let resolution = BinaryReader::read_i16_big_endian(reader)? as i32;
134
135 let mut message_lists: Vec<Vec<Message>> = Vec::with_capacity(track_count.max(0) as usize);
136 let mut tick_lists: Vec<Vec<i32>> = Vec::with_capacity(track_count.max(0) as usize);
137
138 for _i in 0..track_count {
139 let (message_list, tick_list) = MidiFile::read_track(reader, loop_type)?;
140 message_lists.push(message_list);
141 tick_lists.push(tick_list);
142 }
143
144 match loop_type {
145 MidiFileLoopType::LoopPoint(loop_point) if loop_point != 0 => {
146 let loop_point = loop_point as i32;
147 let tick_list = &mut tick_lists[0];
148 let message_list = &mut message_lists[0];
149
150 if loop_point <= *tick_list.last().unwrap() {
151 for i in 0..tick_list.len() {
152 if tick_list[i] >= loop_point {
153 tick_list.insert(i, loop_point);
154 message_list.insert(i, Message::LoopStart);
155 break;
156 }
157 }
158 } else {
159 tick_list.push(loop_point);
160 message_list.push(Message::LoopStart);
161 }
162 }
163 _ => (),
164 }
165
166 let (messages, times) = MidiFile::merge_tracks(&message_lists, &tick_lists, resolution);
167
168 Ok(Self { messages, times })
169 }
170
171 fn discard_data<R: Read>(reader: &mut R) -> Result<(), MidiFileError> {
172 let size = BinaryReader::read_i32_variable_length(reader)? as usize;
173 BinaryReader::discard_data(reader, size)?;
174 Ok(())
175 }
176
177 fn read_tempo<R: Read>(reader: &mut R) -> Result<i32, MidiFileError> {
178 let size = BinaryReader::read_i32_variable_length(reader)?;
179 if size != 3 {
180 return Err(MidiFileError::InvalidTempoValue);
181 }
182
183 let b1 = BinaryReader::read_u8(reader)? as i32;
184 let b2 = BinaryReader::read_u8(reader)? as i32;
185 let b3 = BinaryReader::read_u8(reader)? as i32;
186
187 Ok((b1 << 16) | (b2 << 8) | b3)
188 }
189
190 fn read_track<R: Read>(
191 reader: &mut R,
192 loop_type: MidiFileLoopType,
193 ) -> Result<(Vec<Message>, Vec<i32>), MidiFileError> {
194 let chunk_type = BinaryReader::read_four_cc(reader)?;
195 if chunk_type != b"MTrk" {
196 return Err(MidiFileError::InvalidChunkType {
197 expected: FourCC::from_bytes(*b"MTrk"),
198 actual: chunk_type,
199 });
200 }
201
202 let size = BinaryReader::read_i32_big_endian(reader)? as usize;
203 let reader = &mut ReadCounter::new(reader);
204
205 let mut messages: Vec<Message> = Vec::new();
206 let mut ticks: Vec<i32> = Vec::new();
207
208 let mut tick: i32 = 0;
209 let mut last_status: u8 = 0;
210
211 loop {
212 let delta = BinaryReader::read_i32_variable_length(reader)?;
213 let first = BinaryReader::read_u8(reader)?;
214
215 tick += delta;
216
217 if (first & 128) == 0 {
218 let command = last_status & 0xF0;
219 if command == 0xC0 || command == 0xD0 {
220 messages.push(Message::common1(last_status, first));
221 ticks.push(tick);
222 } else {
223 let data2 = BinaryReader::read_u8(reader)?;
224 messages.push(Message::common2(last_status, first, data2, loop_type));
225 ticks.push(tick);
226 }
227
228 continue;
229 }
230
231 match first {
232 0xF0 => MidiFile::discard_data(reader)?,
233 0xF7 => MidiFile::discard_data(reader)?,
234 0xFF => match BinaryReader::read_u8(reader)? {
235 0x2F => {
236 BinaryReader::read_u8(reader)?;
237 messages.push(Message::EndOfTrack);
238 ticks.push(tick);
239
240 if reader.bytes_read() < size {
243 BinaryReader::discard_data(reader, size - reader.bytes_read())?;
244 }
245
246 return Ok((messages, ticks));
247 }
248 0x51 => {
249 messages.push(Message::tempo_change(MidiFile::read_tempo(reader)?));
250 ticks.push(tick);
251 }
252 _ => MidiFile::discard_data(reader)?,
253 },
254 _ => {
255 let command = first & 0xF0;
256 if command == 0xC0 || command == 0xD0 {
257 let data1 = BinaryReader::read_u8(reader)?;
258 messages.push(Message::common1(first, data1));
259 ticks.push(tick);
260 } else {
261 let data1 = BinaryReader::read_u8(reader)?;
262 let data2 = BinaryReader::read_u8(reader)?;
263 messages.push(Message::common2(first, data1, data2, loop_type));
264 ticks.push(tick);
265 }
266 }
267 }
268
269 last_status = first
270 }
271 }
272
273 fn merge_tracks(
274 message_lists: &[Vec<Message>],
275 tick_lists: &[Vec<i32>],
276 resolution: i32,
277 ) -> (Vec<Message>, Vec<f64>) {
278 let total_messages: usize = tick_lists.iter().map(Vec::len).sum();
280 let mut merged_messages: Vec<Message> = Vec::with_capacity(total_messages);
281 let mut merged_times: Vec<f64> = Vec::with_capacity(total_messages);
282
283 let mut indices: Vec<usize> = vec![0; message_lists.len()];
284
285 let mut current_tick: i32 = 0;
286 let mut current_time: f64 = 0.0;
287
288 let mut tempo: f64 = 120.0;
289
290 loop {
291 let mut min_tick = i32::MAX;
292 let mut min_index: i32 = -1;
293
294 for ch in 0..tick_lists.len() {
295 if indices[ch] < tick_lists[ch].len() {
296 let tick = tick_lists[ch][indices[ch]];
297 if tick < min_tick {
298 min_tick = tick;
299 min_index = ch as i32;
300 }
301 }
302 }
303
304 if min_index == -1 {
305 break;
306 }
307
308 let next_tick = tick_lists[min_index as usize][indices[min_index as usize]];
309 let delta_tick = next_tick - current_tick;
310 let delta_time = 60.0 / (resolution as f64 * tempo) * delta_tick as f64;
311
312 current_tick += delta_tick;
313 current_time += delta_time;
314
315 let message = message_lists[min_index as usize][indices[min_index as usize]];
316 if let Message::TempoChange { bytes } = message {
317 let tempo_i32 = i32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]);
318 tempo = 60000000.0 / tempo_i32 as f64;
319 } else {
320 merged_messages.push(message);
321 merged_times.push(current_time);
322 }
323
324 indices[min_index as usize] += 1;
325 }
326
327 (merged_messages, merged_times)
328 }
329
330 pub fn get_length(&self) -> f64 {
332 *self.times.last().unwrap()
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn test_message_size() {
342 assert_eq!(size_of::<Message>(), 4);
344 }
345}