1use std::cmp::Ordering;
2use std::io::Read;
3
4use crate::MidiFileError;
5use crate::MidiFileLoopType;
6use crate::binary_reader::BinaryReader;
7use crate::four_cc::FourCC;
8use crate::read_counter::ReadCounter;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum MidiMessage {
14 Normal { status: u8, data1: u8, data2: u8 },
16 TempoChange { bytes: [u8; 3] },
21 LoopStart,
23 LoopEnd,
25 EndOfTrack,
27}
28
29impl MidiMessage {
30 pub(crate) fn common1(status: u8, data1: u8) -> Self {
31 Self::Normal {
32 status,
33 data1,
34 data2: 0,
35 }
36 }
37
38 pub(crate) fn common2(status: u8, data1: u8, data2: u8, loop_type: MidiFileLoopType) -> Self {
39 let command = status & 0xF0;
40
41 if command == 0xB0 {
42 match loop_type {
43 MidiFileLoopType::RpgMaker if data1 == 111 => {
44 return MidiMessage::LoopStart;
45 }
46
47 MidiFileLoopType::IncredibleMachine => {
48 if data1 == 110 {
49 return MidiMessage::LoopStart;
50 }
51 if data1 == 111 {
52 return MidiMessage::LoopEnd;
53 }
54 }
55
56 MidiFileLoopType::FinalFantasy => {
57 if data1 == 116 {
58 return MidiMessage::LoopStart;
59 }
60 if data1 == 117 {
61 return MidiMessage::LoopEnd;
62 }
63 }
64
65 _ => (),
66 }
67 }
68
69 Self::Normal {
70 status,
71 data1,
72 data2,
73 }
74 }
75
76 pub(crate) fn tempo_change(tempo: i32) -> Self {
77 let bytes = tempo.to_be_bytes()[1..].try_into().unwrap();
79 Self::TempoChange { bytes }
80 }
81}
82
83#[derive(Debug)]
85#[non_exhaustive]
86pub struct MidiFile {
87 pub(crate) messages: Vec<MidiMessage>,
88 pub(crate) times: Vec<f64>,
89}
90
91impl MidiFile {
92 pub fn new<R: Read>(reader: &mut R) -> Result<Self, MidiFileError> {
98 MidiFile::new_with_loop_type(reader, MidiFileLoopType::LoopPoint(0))
99 }
100
101 pub fn new_with_loop_type<R: Read>(
119 reader: &mut R,
120 loop_type: MidiFileLoopType,
121 ) -> Result<Self, MidiFileError> {
122 let chunk_type = BinaryReader::read_four_cc(reader)?;
123 if chunk_type != b"MThd" {
124 return Err(MidiFileError::InvalidChunkType {
125 expected: FourCC::from_bytes(*b"MThd"),
126 actual: chunk_type,
127 });
128 }
129
130 let size = BinaryReader::read_i32_big_endian(reader)?;
131 if size != 6 {
132 return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
133 *b"MThd",
134 )));
135 }
136
137 let format = BinaryReader::read_i16_big_endian(reader)?;
138 if !(format == 0 || format == 1) {
139 return Err(MidiFileError::UnsupportedFormat(format));
140 }
141
142 let track_count = BinaryReader::read_i16_big_endian(reader)?;
144 if track_count <= 0 {
145 return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
146 *b"MThd",
147 )));
148 }
149
150 let resolution = BinaryReader::read_i16_big_endian(reader)?;
154 if resolution <= 0 {
155 return Err(MidiFileError::InvalidTimeDivision(resolution));
156 }
157 let track_count = track_count as i32;
158 let resolution = resolution as i32;
159
160 let mut message_lists: Vec<Vec<MidiMessage>> = Vec::with_capacity(track_count as usize);
161 let mut tick_lists: Vec<Vec<i32>> = Vec::with_capacity(track_count as usize);
162
163 for _i in 0..track_count {
164 let (message_list, tick_list) = MidiFile::read_track(reader, loop_type)?;
165 message_lists.push(message_list);
166 tick_lists.push(tick_list);
167 }
168
169 match loop_type {
170 MidiFileLoopType::LoopPoint(loop_point) if loop_point != 0 => {
171 let loop_point = loop_point as i32;
172 let tick_list = &mut tick_lists[0];
173 let message_list = &mut message_lists[0];
174
175 if loop_point <= *tick_list.last().unwrap() {
176 for i in 0..tick_list.len() {
177 if tick_list[i] >= loop_point {
178 tick_list.insert(i, loop_point);
179 message_list.insert(i, MidiMessage::LoopStart);
180 break;
181 }
182 }
183 } else {
184 tick_list.push(loop_point);
185 message_list.push(MidiMessage::LoopStart);
186 }
187 }
188 _ => (),
189 }
190
191 let (messages, times) = MidiFile::merge_tracks(&message_lists, &tick_lists, resolution);
192
193 Ok(Self { messages, times })
194 }
195
196 fn discard_data<R: Read>(reader: &mut R) -> Result<(), MidiFileError> {
197 let size = BinaryReader::read_i32_variable_length(reader)? as usize;
198 BinaryReader::discard_data(reader, size)?;
199 Ok(())
200 }
201
202 fn read_tempo<R: Read>(reader: &mut R) -> Result<i32, MidiFileError> {
203 let size = BinaryReader::read_i32_variable_length(reader)?;
204 if size != 3 {
205 return Err(MidiFileError::InvalidTempoValue);
206 }
207
208 let b1 = BinaryReader::read_u8(reader)? as i32;
209 let b2 = BinaryReader::read_u8(reader)? as i32;
210 let b3 = BinaryReader::read_u8(reader)? as i32;
211
212 Ok((b1 << 16) | (b2 << 8) | b3)
213 }
214
215 fn read_track<R: Read>(
216 reader: &mut R,
217 loop_type: MidiFileLoopType,
218 ) -> Result<(Vec<MidiMessage>, Vec<i32>), MidiFileError> {
219 let chunk_type = BinaryReader::read_four_cc(reader)?;
220 if chunk_type != b"MTrk" {
221 return Err(MidiFileError::InvalidChunkType {
222 expected: FourCC::from_bytes(*b"MTrk"),
223 actual: chunk_type,
224 });
225 }
226
227 let size_i32 = BinaryReader::read_i32_big_endian(reader)?;
228 if size_i32 < 0 {
231 return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
232 *b"MTrk",
233 )));
234 }
235 let size = size_i32 as usize;
236 let reader = &mut ReadCounter::new(reader);
237
238 let mut messages: Vec<MidiMessage> = Vec::new();
239 let mut ticks: Vec<i32> = Vec::new();
240
241 let mut tick: i32 = 0;
242 let mut last_status: u8 = 0;
243
244 loop {
245 if reader.bytes_read() >= size {
249 return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
250 *b"MTrk",
251 )));
252 }
253
254 let delta = BinaryReader::read_i32_variable_length(reader)?;
255 let first = BinaryReader::read_u8(reader)?;
256
257 let Some(next) = tick.checked_add(delta) else {
261 return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
262 *b"MTrk",
263 )));
264 };
265 tick = next;
266
267 if (first & 128) == 0 {
268 let command = last_status & 0xF0;
269 if command == 0xC0 || command == 0xD0 {
270 messages.push(MidiMessage::common1(last_status, first));
271 ticks.push(tick);
272 } else {
273 let data2 = BinaryReader::read_u8(reader)?;
274 messages.push(MidiMessage::common2(last_status, first, data2, loop_type));
275 ticks.push(tick);
276 }
277
278 continue;
279 }
280
281 match first {
282 0xF0 => MidiFile::discard_data(reader)?,
283 0xF7 => MidiFile::discard_data(reader)?,
284 0xFF => match BinaryReader::read_u8(reader)? {
285 0x2F => {
286 BinaryReader::read_u8(reader)?;
287 messages.push(MidiMessage::EndOfTrack);
288 ticks.push(tick);
289
290 match reader.bytes_read().cmp(&size) {
295 Ordering::Less => {
296 BinaryReader::discard_data(reader, size - reader.bytes_read())?;
297 }
298 Ordering::Equal => (),
299 Ordering::Greater => {
300 return Err(MidiFileError::InvalidChunkData(FourCC::from_bytes(
301 *b"MTrk",
302 )));
303 }
304 }
305
306 return Ok((messages, ticks));
307 }
308 0x51 => {
309 messages.push(MidiMessage::tempo_change(MidiFile::read_tempo(reader)?));
310 ticks.push(tick);
311 }
312 _ => MidiFile::discard_data(reader)?,
313 },
314 _ => {
315 let command = first & 0xF0;
316 if command == 0xC0 || command == 0xD0 {
317 let data1 = BinaryReader::read_u8(reader)?;
318 messages.push(MidiMessage::common1(first, data1));
319 ticks.push(tick);
320 } else {
321 let data1 = BinaryReader::read_u8(reader)?;
322 let data2 = BinaryReader::read_u8(reader)?;
323 messages.push(MidiMessage::common2(first, data1, data2, loop_type));
324 ticks.push(tick);
325 }
326 }
327 }
328
329 last_status = if first >= 0xF0 { 0 } else { first };
332 }
333 }
334
335 fn merge_tracks(
336 message_lists: &[Vec<MidiMessage>],
337 tick_lists: &[Vec<i32>],
338 resolution: i32,
339 ) -> (Vec<MidiMessage>, Vec<f64>) {
340 let total_messages: usize = tick_lists.iter().map(Vec::len).sum();
342 let mut merged_messages: Vec<MidiMessage> = Vec::with_capacity(total_messages);
343 let mut merged_times: Vec<f64> = Vec::with_capacity(total_messages);
344
345 let mut indices: Vec<usize> = vec![0; message_lists.len()];
346
347 let mut current_tick: i32 = 0;
348 let mut current_time: f64 = 0.0;
349
350 let mut tempo: f64 = 120.0;
351
352 loop {
353 let mut min_tick = i64::MAX;
357 let mut min_index: i32 = -1;
358
359 for ch in 0..tick_lists.len() {
360 if indices[ch] < tick_lists[ch].len() {
361 let tick = tick_lists[ch][indices[ch]] as i64;
362 if tick < min_tick {
363 min_tick = tick;
364 min_index = ch as i32;
365 }
366 }
367 }
368
369 if min_index == -1 {
370 break;
371 }
372
373 let next_tick = tick_lists[min_index as usize][indices[min_index as usize]];
374 let delta_tick = next_tick as i64 - current_tick as i64;
375 let delta_time = 60.0 / (resolution as f64 * tempo) * delta_tick as f64;
376
377 current_tick = next_tick;
378 current_time += delta_time;
379
380 let message = message_lists[min_index as usize][indices[min_index as usize]];
381 if let MidiMessage::TempoChange { bytes } = message {
382 let tempo_i32 = i32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]);
383 if tempo_i32 > 0 {
386 tempo = 60000000.0 / tempo_i32 as f64;
387 }
388 } else {
389 merged_messages.push(message);
390 merged_times.push(current_time);
391 }
392
393 indices[min_index as usize] += 1;
394 }
395
396 (merged_messages, merged_times)
397 }
398
399 pub fn get_length(&self) -> f64 {
401 self.times.last().copied().unwrap_or(0.0)
402 }
403
404 pub fn get_messages(&self) -> &[MidiMessage] {
409 &self.messages
410 }
411
412 pub fn get_times(&self) -> &[f64] {
416 &self.times
417 }
418
419 pub fn new_with_events<I>(events: I) -> Result<Self, MidiFileError>
425 where
426 I: IntoIterator<Item = (f64, MidiMessage)>,
427 {
428 let mut file = Self::default();
429 file.extend_events(events)?;
430 Ok(file)
431 }
432
433 pub fn extend_events<I>(&mut self, events: I) -> Result<(), MidiFileError>
440 where
441 I: IntoIterator<Item = (f64, MidiMessage)>,
442 {
443 let base = self.messages.len();
444 let mut last_time = self.times.last().copied().unwrap_or(f64::NEG_INFINITY);
445
446 let iter = events.into_iter();
447 let (min, max) = iter.size_hint();
448 let capacity = max.unwrap_or(min);
452 self.messages.reserve(capacity);
453 self.times.reserve(capacity);
454
455 for (time, message) in iter {
456 if !matches!(
459 time.partial_cmp(&last_time),
460 Some(Ordering::Greater | Ordering::Equal)
461 ) {
462 self.messages.truncate(base);
463 self.times.truncate(base);
464 return Err(MidiFileError::InvalidEventList);
465 }
466 last_time = time;
467 self.messages.push(message);
468 self.times.push(time);
469 }
470 Ok(())
471 }
472
473 pub fn clear(&mut self) {
475 self.messages.clear();
476 self.times.clear();
477 }
478}
479
480impl Default for MidiFile {
481 fn default() -> Self {
483 Self {
484 messages: Vec::new(),
485 times: Vec::new(),
486 }
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn test_message_size() {
496 assert_eq!(size_of::<MidiMessage>(), 4);
498 }
499
500 fn vlq(mut value: u32) -> Vec<u8> {
502 let mut groups = Vec::new();
503 loop {
504 groups.push((value & 0x7F) as u8);
505 value >>= 7;
506 if value == 0 {
507 break;
508 }
509 }
510 groups.reverse();
511 let last = groups.len() - 1;
512 for (i, byte) in groups.iter_mut().enumerate() {
513 if i != last {
514 *byte |= 0x80;
515 }
516 }
517 groups
518 }
519
520 fn mthd(format: u16, track_count: u16, resolution: u16) -> Vec<u8> {
522 let mut bytes = Vec::new();
523 bytes.extend_from_slice(b"MThd");
524 bytes.extend_from_slice(&6u32.to_be_bytes());
525 bytes.extend_from_slice(&format.to_be_bytes());
526 bytes.extend_from_slice(&track_count.to_be_bytes());
527 bytes.extend_from_slice(&resolution.to_be_bytes());
528 bytes
529 }
530
531 fn mtrk(bytes: &mut Vec<u8>, data: &[u8]) {
533 bytes.extend_from_slice(b"MTrk");
534 bytes.extend_from_slice(&(data.len() as u32).to_be_bytes());
535 bytes.extend_from_slice(data);
536 }
537
538 fn note_at_480() -> Vec<u8> {
541 let mut data = mthd(0, 1, 480);
542 let mut track = vlq(480);
543 track.extend_from_slice(&[0x90, 0x3C, 0x64]);
544 track.extend_from_slice(&vlq(480));
545 track.extend_from_slice(&[0xFF, 0x2F, 0x00]);
546 mtrk(&mut data, &track);
547 data
548 }
549
550 fn assert_close(actual: f64, expected: f64) {
551 assert!(
552 (actual - expected).abs() < 1e-6,
553 "expected {expected}, but was {actual}"
554 );
555 }
556
557 #[test]
558 fn test_default_tempo_timing() {
559 let data = note_at_480();
560 let midi = MidiFile::new(&mut data.as_slice()).unwrap();
561
562 assert_eq!(midi.messages.len(), 2);
563 assert_eq!(
564 midi.messages[0],
565 MidiMessage::Normal {
566 status: 0x90,
567 data1: 0x3C,
568 data2: 0x64,
569 }
570 );
571 assert_eq!(midi.messages[1], MidiMessage::EndOfTrack);
572 assert_eq!(midi.times.len(), 2);
573 assert_close(midi.times[0], 0.5);
574 assert_close(midi.times[1], 1.0);
575 assert_close(midi.get_length(), 1.0);
576 }
577
578 #[test]
579 fn test_tempo_change_timing() {
580 let mut data = mthd(0, 1, 480);
583 let mut track = vlq(0);
584 track.extend_from_slice(&[0xFF, 0x51, 0x03, 0x03, 0xD0, 0x90]);
585 track.extend_from_slice(&vlq(480));
586 track.extend_from_slice(&[0x90, 0x3C, 0x64]);
587 track.extend_from_slice(&vlq(480));
588 track.extend_from_slice(&[0xFF, 0x2F, 0x00]);
589 mtrk(&mut data, &track);
590
591 let midi = MidiFile::new(&mut data.as_slice()).unwrap();
592
593 assert_eq!(midi.messages.len(), 2);
595 assert_close(midi.times[0], 0.25);
596 assert_close(midi.times[1], 0.5);
597 assert_close(midi.get_length(), 0.5);
598 }
599
600 #[test]
601 fn test_invalid_tempo_value_is_ignored() {
602 let mut data = mthd(0, 1, 480);
605 let mut track = vlq(0);
606 track.extend_from_slice(&[0xFF, 0x51, 0x03, 0x00, 0x00, 0x00]);
607 track.extend_from_slice(&vlq(480));
608 track.extend_from_slice(&[0x90, 0x3C, 0x64]);
609 track.extend_from_slice(&vlq(480));
610 track.extend_from_slice(&[0xFF, 0x2F, 0x00]);
611 mtrk(&mut data, &track);
612
613 let midi = MidiFile::new(&mut data.as_slice()).unwrap();
614 assert_eq!(midi.messages.len(), 2);
615 assert_close(midi.times[0], 0.5);
616 assert_close(midi.times[1], 1.0);
617 }
618
619 #[test]
620 fn test_running_status_within_channel_messages() {
621 let mut data = mthd(0, 1, 480);
623 let mut track = vlq(0);
624 track.extend_from_slice(&[0x90, 0x3C, 0x64]); track.extend_from_slice(&vlq(0));
626 track.extend_from_slice(&[0x3D, 0x60]); track.extend_from_slice(&vlq(0));
628 track.extend_from_slice(&[0x3E, 0x50]); track.extend_from_slice(&vlq(0));
630 track.extend_from_slice(&[0xFF, 0x2F, 0x00]);
631 mtrk(&mut data, &track);
632
633 let midi = MidiFile::new(&mut data.as_slice()).unwrap();
634 assert_eq!(
635 midi.messages,
636 vec![
637 MidiMessage::Normal {
638 status: 0x90,
639 data1: 0x3C,
640 data2: 0x64,
641 },
642 MidiMessage::Normal {
643 status: 0x90,
644 data1: 0x3D,
645 data2: 0x60,
646 },
647 MidiMessage::Normal {
648 status: 0x90,
649 data1: 0x3E,
650 data2: 0x50,
651 },
652 MidiMessage::EndOfTrack,
653 ]
654 );
655 }
656
657 #[test]
658 fn test_running_status_cleared_by_meta_event() {
659 let mut data = mthd(0, 1, 480);
663 let mut track = vlq(0);
664 track.extend_from_slice(&[0x90, 0x3C, 0x64]);
665 track.extend_from_slice(&vlq(0));
666 track.extend_from_slice(&[0xFF, 0x51, 0x03, 0x07, 0xA1, 0x20]); track.extend_from_slice(&vlq(0));
668 track.extend_from_slice(&[0x3D, 0x64]); track.extend_from_slice(&vlq(0));
670 track.extend_from_slice(&[0xFF, 0x2F, 0x00]);
671 mtrk(&mut data, &track);
672
673 let midi = MidiFile::new(&mut data.as_slice()).unwrap();
674 assert_eq!(
675 midi.messages,
676 vec![
677 MidiMessage::Normal {
678 status: 0x90,
679 data1: 0x3C,
680 data2: 0x64,
681 },
682 MidiMessage::Normal {
683 status: 0x00,
684 data1: 0x3D,
685 data2: 0x64,
686 },
687 MidiMessage::EndOfTrack,
688 ]
689 );
690 }
691
692 #[test]
693 fn test_smpte_time_division_is_rejected() {
694 let data = mthd(0, 1, 0xE728);
698 assert!(matches!(
699 MidiFile::new(&mut data.as_slice()),
700 Err(MidiFileError::InvalidTimeDivision(-6360))
701 ));
702 }
703
704 #[test]
705 fn test_zero_time_division_is_rejected() {
706 let data = mthd(0, 1, 0x0000);
707 assert!(matches!(
708 MidiFile::new(&mut data.as_slice()),
709 Err(MidiFileError::InvalidTimeDivision(0))
710 ));
711 }
712
713 #[test]
714 fn test_empty_track_list_is_rejected() {
715 let data = mthd(0, 0, 480);
717 assert!(matches!(
718 MidiFile::new(&mut data.as_slice()),
719 Err(MidiFileError::InvalidChunkData(_))
720 ));
721 }
722
723 #[test]
724 fn test_message_accessors() {
725 let data = note_at_480();
726 let midi = MidiFile::new(&mut data.as_slice()).unwrap();
727
728 assert_eq!(midi.get_messages().len(), 2);
729 assert_eq!(midi.get_times().len(), 2);
730 assert_close(midi.get_times()[0], 0.5);
731 assert_close(midi.get_times()[1], 1.0);
732
733 let events: Vec<(f64, MidiMessage)> = midi
734 .get_times()
735 .iter()
736 .copied()
737 .zip(midi.get_messages().iter().copied())
738 .collect();
739 assert_eq!(events.len(), 2);
740 assert_close(events[0].0, 0.5);
741 assert_eq!(
742 events[0].1,
743 MidiMessage::Normal {
744 status: 0x90,
745 data1: 0x3C,
746 data2: 0x64,
747 }
748 );
749 assert_close(events[1].0, 1.0);
750 assert_eq!(events[1].1, MidiMessage::EndOfTrack);
751 }
752
753 #[test]
754 fn test_construct_with_events() {
755 let midi = MidiFile::new_with_events(vec![
756 (
757 0.25,
758 MidiMessage::Normal {
759 status: 0x90,
760 data1: 60,
761 data2: 100,
762 },
763 ),
764 (0.5, MidiMessage::EndOfTrack),
765 ])
766 .unwrap();
767
768 assert_eq!(midi.get_messages().len(), 2);
769 assert_eq!(midi.get_times(), &[0.25, 0.5]);
770 assert_close(midi.get_length(), 0.5);
771 assert_close(midi.get_times()[0], 0.25);
772 assert_eq!(
773 midi.get_messages()[0],
774 MidiMessage::Normal {
775 status: 0x90,
776 data1: 60,
777 data2: 100,
778 }
779 );
780 }
781
782 #[test]
783 fn test_construct_allows_same_time_events() {
784 let midi = MidiFile::new_with_events(vec![
785 (
786 0.0,
787 MidiMessage::Normal {
788 status: 0x90,
789 data1: 60,
790 data2: 100,
791 },
792 ),
793 (
794 0.0,
795 MidiMessage::Normal {
796 status: 0x90,
797 data1: 61,
798 data2: 100,
799 },
800 ),
801 ])
802 .unwrap();
803 assert_eq!(midi.get_messages().len(), 2);
804 }
805
806 #[test]
807 fn test_construct_from_iterator_without_vec() {
808 let midi = MidiFile::new_with_events((0..3).map(|i| {
811 (
812 i as f64 * 0.5,
813 MidiMessage::Normal {
814 status: 0x90,
815 data1: i as u8,
816 data2: 100,
817 },
818 )
819 }))
820 .unwrap();
821 assert_eq!(midi.get_messages().len(), 3);
822 assert_eq!(midi.get_times(), &[0.0, 0.5, 1.0]);
823 }
824
825 #[test]
826 fn test_construct_rejects_unsorted_times() {
827 let result = MidiFile::new_with_events(vec![
828 (0.5, MidiMessage::EndOfTrack),
829 (
830 0.25,
831 MidiMessage::Normal {
832 status: 0x90,
833 data1: 60,
834 data2: 100,
835 },
836 ),
837 ]);
838 assert!(matches!(result, Err(MidiFileError::InvalidEventList)));
839 }
840
841 #[test]
842 fn test_construct_rejects_nan_time() {
843 let result = MidiFile::new_with_events(vec![(f64::NAN, MidiMessage::EndOfTrack)]);
844 assert!(matches!(result, Err(MidiFileError::InvalidEventList)));
845 }
846
847 #[test]
848 fn test_extend_events_and_clear() {
849 let mut midi = MidiFile::default();
850 assert!(midi.get_messages().is_empty());
851 assert_close(midi.get_length(), 0.0);
852
853 midi.extend_events(vec![(0.5, MidiMessage::EndOfTrack)])
854 .unwrap();
855 assert_eq!(midi.get_times(), &[0.5]);
856 assert_close(midi.get_length(), 0.5);
857
858 midi.extend_events(vec![(0.5, MidiMessage::EndOfTrack)])
860 .unwrap();
861 assert_eq!(midi.get_messages().len(), 2);
862 assert_eq!(midi.get_times(), &[0.5, 0.5]);
863
864 midi.clear();
865 assert!(midi.get_messages().is_empty());
866 assert_close(midi.get_length(), 0.0);
867 }
868
869 #[test]
870 fn test_extend_events_reuses_allocation() {
871 let mut midi = MidiFile::default();
872
873 let events: Vec<(f64, MidiMessage)> = (0..100)
874 .map(|i| {
875 (
876 i as f64 * 0.01,
877 MidiMessage::Normal {
878 status: 0x90,
879 data1: i as u8,
880 data2: 0,
881 },
882 )
883 })
884 .collect();
885 midi.extend_events(events.iter().copied()).unwrap();
886 let messages_capacity = midi.messages.capacity();
887 let times_capacity = midi.times.capacity();
888 assert!(messages_capacity >= 100);
889 assert!(times_capacity >= 100);
890
891 midi.clear();
894 midi.extend_events(vec![(0.0, MidiMessage::EndOfTrack)])
895 .unwrap();
896 assert_eq!(midi.messages.capacity(), messages_capacity);
897 assert_eq!(midi.times.capacity(), times_capacity);
898 assert_eq!(midi.get_messages().len(), 1);
899 assert_eq!(midi.get_times(), &[0.0]);
900 }
901
902 #[test]
903 fn test_extend_events_invalid_input_is_transactional() {
904 let mut midi = MidiFile::new_with_events(vec![(0.0, MidiMessage::EndOfTrack)]).unwrap();
905
906 let result = midi.extend_events(vec![
909 (0.5, MidiMessage::EndOfTrack),
910 (0.25, MidiMessage::EndOfTrack),
911 ]);
912 assert!(matches!(result, Err(MidiFileError::InvalidEventList)));
913 assert_eq!(midi.get_messages().len(), 1);
914 assert_eq!(midi.get_times(), &[0.0]);
915
916 let result = midi.extend_events(vec![(-0.5, MidiMessage::EndOfTrack)]);
919 assert!(matches!(result, Err(MidiFileError::InvalidEventList)));
920 assert_eq!(midi.get_times(), &[0.0]);
921 }
922
923 #[test]
924 fn test_vlq_encoding() {
925 assert_eq!(vlq(0), vec![0x00]);
926 assert_eq!(vlq(0x40), vec![0x40]);
927 assert_eq!(vlq(0x7F), vec![0x7F]);
928 assert_eq!(vlq(0x80), vec![0x81, 0x00]);
929 assert_eq!(vlq(480), vec![0x83, 0x60]);
930 assert_eq!(vlq(0x1FFFFF), vec![0xFF, 0xFF, 0x7F]);
931 }
932
933 #[test]
934 fn test_negative_mtrk_size_is_rejected() {
935 let mut data = mthd(0, 1, 480);
938 data.extend_from_slice(b"MTrk");
939 data.extend_from_slice(&(-1i32).to_be_bytes());
940 data.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
941 assert!(matches!(
942 MidiFile::new(&mut data.as_slice()),
943 Err(MidiFileError::InvalidChunkData(_))
944 ));
945 }
946
947 #[test]
948 fn test_empty_mtrk_is_rejected() {
949 let mut data = mthd(0, 1, 480);
952 mtrk(&mut data, &[]);
953 assert!(matches!(
954 MidiFile::new(&mut data.as_slice()),
955 Err(MidiFileError::InvalidChunkData(_))
956 ));
957 }
958
959 #[test]
960 fn test_track_without_eot_does_not_bleed_into_next_chunk() {
961 let mut data = mthd(0, 2, 480);
965 let mut track1 = vlq(0);
966 track1.extend_from_slice(&[0x90, 0x3C, 0x64]);
967 mtrk(&mut data, &track1);
968 let mut track2 = vlq(480);
969 track2.extend_from_slice(&[0x90, 0x3C, 0x64]);
970 track2.extend_from_slice(&vlq(480));
971 track2.extend_from_slice(&[0xFF, 0x2F, 0x00]);
972 mtrk(&mut data, &track2);
973 assert!(matches!(
974 MidiFile::new(&mut data.as_slice()),
975 Err(MidiFileError::InvalidChunkData(_))
976 ));
977 }
978
979 #[test]
980 fn test_eot_past_chunk_end_is_rejected() {
981 let mut data = mthd(0, 1, 480);
985 data.extend_from_slice(b"MTrk");
986 data.extend_from_slice(&2u32.to_be_bytes());
987 data.extend_from_slice(&[0x00, 0xFF, 0x2F, 0x00]);
988 assert!(matches!(
989 MidiFile::new(&mut data.as_slice()),
990 Err(MidiFileError::InvalidChunkData(_))
991 ));
992 }
993
994 #[test]
997 fn test_a_tick_overflow_is_rejected() {
998 let mut data = mthd(0, 1, 480);
999 let mut track = Vec::new();
1000
1001 for _ in 0..10 {
1005 track.extend_from_slice(&vlq(0x0FFF_FFFF));
1006 track.extend_from_slice(&[0x90, 0x3C, 0x64]);
1007 }
1008 mtrk(&mut data, &track);
1009
1010 assert!(matches!(
1011 MidiFile::new(&mut data.as_slice()),
1012 Err(MidiFileError::InvalidChunkData(_))
1013 ));
1014 }
1015
1016 #[test]
1020 fn test_a_tick_at_the_type_limit_is_kept() {
1021 let mut data = mthd(0, 1, 480);
1022 let mut track = Vec::new();
1023
1024 for _ in 0..8 {
1027 track.extend_from_slice(&vlq(0x0FFF_FFFF));
1028 track.extend_from_slice(&[0x90, 0x3C, 0x64]);
1029 }
1030 track.extend_from_slice(&vlq(7));
1031 track.extend_from_slice(&[0xFF, 0x2F, 0x00]);
1032 mtrk(&mut data, &track);
1033
1034 let midi = MidiFile::new(&mut data.as_slice()).unwrap();
1035 assert_eq!(midi.messages.len(), 9);
1036 assert_eq!(*midi.messages.last().unwrap(), MidiMessage::EndOfTrack);
1037 }
1038}