1mod controller_destination;
2pub use controller_destination::*;
3mod file_dump;
4pub use file_dump::*;
5mod file_reference;
6pub use file_reference::*;
7mod global_parameter;
8pub use global_parameter::*;
9mod key_based_instrument_control;
10pub use key_based_instrument_control::*;
11mod machine_control;
12pub use machine_control::*;
13mod notation;
14pub use notation::*;
15mod sample_dump;
16pub use sample_dump::*;
17mod show_control;
18pub use show_control::*;
19mod tuning;
20pub use tuning::*;
21
22use alloc::vec::Vec;
23
24use super::ReceiverContext;
25use super::general_midi::GeneralMidi;
26use super::parse_error::*;
27use super::time_code::*;
28use super::util::*;
29
30#[derive(Debug, Clone, PartialEq)]
34pub enum SystemExclusiveMsg {
35 Commercial { id: ManufacturerID, data: Vec<u8> },
38 NonCommercial { data: Vec<u8> },
40 UniversalRealTime {
43 device: DeviceID,
44 msg: UniversalRealTimeMsg,
45 },
46 UniversalNonRealTime {
49 device: DeviceID,
50 msg: UniversalNonRealTimeMsg,
51 },
52}
53
54impl SystemExclusiveMsg {
55 pub(crate) fn extend_midi(&self, v: &mut Vec<u8>, first_byte_is_f0: bool) {
56 if first_byte_is_f0 {
57 v.push(0xF0);
58 }
59 match self {
60 SystemExclusiveMsg::Commercial { id, data } => {
61 id.extend_midi(v);
62 data.iter().for_each(|d| v.push(to_u7(*d)));
63 }
64 SystemExclusiveMsg::NonCommercial { data } => {
65 v.push(0x7D);
66 data.iter().for_each(|d| v.push(to_u7(*d)));
67 }
68 SystemExclusiveMsg::UniversalRealTime { device, msg } => {
69 v.push(0x7F);
70 v.push(device.to_u8());
71 msg.extend_midi(v);
72 }
73 SystemExclusiveMsg::UniversalNonRealTime { device, msg } => {
74 let p = v.len();
75 v.push(0x7E);
76 v.push(device.to_u8());
77 msg.extend_midi(v);
78 if let UniversalNonRealTimeMsg::SampleDump(SampleDumpMsg::Packet { .. }) = msg {
79 let q = v.len();
80 v[q - 1] = checksum(&v[p..q - 1]);
81 }
82 if let UniversalNonRealTimeMsg::KeyBasedTuningDump(_) = msg {
83 let q = v.len();
84 v[q - 1] = checksum(&v[p..q - 1]);
85 }
86 if let UniversalNonRealTimeMsg::ScaleTuning1Byte(_) = msg {
87 let q = v.len();
88 v[q - 1] = checksum(&v[p..q - 1]);
89 }
90 if let UniversalNonRealTimeMsg::ScaleTuning2Byte(_) = msg {
91 let q = v.len();
92 v[q - 1] = checksum(&v[p..q - 1]);
93 }
94 if let UniversalNonRealTimeMsg::FileDump(FileDumpMsg::Packet { .. }) = msg {
95 let q = v.len();
96 v[q - 1] = checksum(&v[p..q - 1]);
97 }
98 }
99 }
100 v.push(0xF7);
101 }
102
103 fn sysex_bytes_from_midi(m: &[u8], first_byte_is_f0: bool) -> Result<&[u8], ParseError> {
104 if first_byte_is_f0 && m.first() != Some(&0xF0) {
105 return Err(ParseError::UndefinedSystemExclusiveMessage(
106 m.first().copied(),
107 ));
108 }
109 let offset = if first_byte_is_f0 { 1 } else { 0 };
110 for (i, b) in m[offset..].iter().enumerate() {
111 if b == &0xF7 {
112 return Ok(&m[offset..i + offset]);
113 }
114 if b > &127 {
115 return Err(ParseError::ByteOverflow);
116 }
117 }
118 Err(ParseError::NoEndOfSystemExclusiveFlag)
119 }
120
121 pub(crate) fn from_midi(
122 m: &[u8],
123 ctx: &mut ReceiverContext,
124 ) -> Result<(Self, usize), ParseError> {
125 let m = Self::sysex_bytes_from_midi(m, !ctx.is_smf_sysex)?;
126 match m.first() {
127 Some(0x7D) => Ok((
128 Self::NonCommercial {
129 data: m[1..].to_vec(),
130 },
131 m.len() + 2,
132 )),
133 Some(0x7E) => Ok((
134 Self::UniversalNonRealTime {
135 device: DeviceID::from_midi(&m[1..])?,
136 msg: UniversalNonRealTimeMsg::from_midi(&m[2..])?,
137 },
138 m.len() + 2,
139 )),
140 Some(0x7F) => Ok((
141 Self::UniversalRealTime {
142 device: DeviceID::from_midi(&m[1..])?,
143 msg: UniversalRealTimeMsg::from_midi(&m[2..], ctx)?,
144 },
145 m.len() + 2,
146 )),
147 Some(_) => {
148 let (id, len) = ManufacturerID::from_midi(m)?;
149 Ok((
150 Self::Commercial {
151 id,
152 data: m[len..].to_vec(),
153 },
154 m.len() + 2,
155 ))
156 }
157 None => Err(crate::ParseError::UnexpectedEnd),
158 }
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub struct ManufacturerID(pub u8, pub Option<u8>);
169
170impl ManufacturerID {
171 fn extend_midi(&self, v: &mut Vec<u8>) {
172 if let Some(second) = self.1 {
173 v.push(0x00);
174 v.push(to_u7(self.0));
175 v.push(to_u7(second));
176 } else {
177 v.push(self.0.min(0x7C))
178 }
179 }
180
181 fn from_midi(m: &[u8]) -> Result<(Self, usize), ParseError> {
182 let b1 = u7_from_midi(m)?;
183 if b1 == 0x00 {
184 if m.len() < 3 {
185 return Err(crate::ParseError::UnexpectedEnd);
186 }
187 let b2 = u7_from_midi(&m[1..])?;
188 let b3 = u7_from_midi(&m[2..])?;
189 Ok((Self(b2, Some(b3)), 3))
190 } else {
191 Ok((Self(b1, None), 1))
192 }
193 }
194}
195
196impl From<u8> for ManufacturerID {
197 fn from(a: u8) -> Self {
198 Self(a, None)
199 }
200}
201
202impl From<(u8, u8)> for ManufacturerID {
203 fn from((a, b): (u8, u8)) -> Self {
204 Self(a, Some(b))
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum DeviceID {
212 Device(u8),
213 AllCall,
214}
215
216impl DeviceID {
217 fn to_u8(self) -> u8 {
218 match self {
219 Self::AllCall => 0x7F,
220 Self::Device(x) => to_u7(x),
221 }
222 }
223
224 fn from_midi(m: &[u8]) -> Result<Self, ParseError> {
225 let b = u7_from_midi(m)?;
226 if b == 0x7F {
227 Ok(Self::AllCall)
228 } else {
229 Ok(Self::Device(b))
230 }
231 }
232}
233
234#[derive(Debug, Clone, PartialEq)]
236pub enum UniversalRealTimeMsg {
237 TimeCodeFull(TimeCode),
241 TimeCodeUserBits(UserBits),
243 ShowControl(ShowControlMsg),
245 BarMarker(BarMarker),
247 TimeSignature(TimeSignature),
249 TimeSignatureDelayed(TimeSignature),
251 MasterVolume(u16),
253 MasterBalance(u16),
255 MasterFineTuning(i16),
259 MasterCoarseTuning(i8),
263 GlobalParameterControl(GlobalParameterControl),
265 TimeCodeCueing(TimeCodeCueingMsg),
267 MachineControlCommand(MachineControlCommandMsg),
269 MachineControlResponse(MachineControlResponseMsg),
271 TuningNoteChange(TuningNoteChange),
273 ScaleTuning1Byte(ScaleTuning1Byte),
275 ScaleTuning2Byte(ScaleTuning2Byte),
277 ChannelPressureControllerDestination(ControllerDestination),
279 PolyphonicKeyPressureControllerDestination(ControllerDestination),
281 ControlChangeControllerDestination(ControlChangeControllerDestination),
283 KeyBasedInstrumentControl(KeyBasedInstrumentControl),
285}
286
287impl UniversalRealTimeMsg {
288 fn extend_midi(&self, v: &mut Vec<u8>) {
289 match self {
290 UniversalRealTimeMsg::TimeCodeFull(code) => {
291 v.push(0x1);
292 v.push(0x1);
293 code.extend_midi(v);
294 }
295 UniversalRealTimeMsg::TimeCodeUserBits(user_bits) => {
296 v.push(0x1);
297 v.push(0x2);
298 let [ub1, ub2, ub3, ub4, ub5, ub6, ub7, ub8, ub9] = user_bits.to_nibbles();
299 v.extend_from_slice(&[ub1, ub2, ub3, ub4, ub5, ub6, ub7, ub8, ub9]);
300 }
301 UniversalRealTimeMsg::ShowControl(msg) => {
302 v.push(0x2);
303 msg.extend_midi(v);
304 }
305 UniversalRealTimeMsg::BarMarker(marker) => {
306 v.push(0x3);
307 v.push(0x1);
308 marker.extend_midi(v);
309 }
310 UniversalRealTimeMsg::TimeSignature(signature) => {
311 v.push(0x3);
312 v.push(0x2);
313 signature.extend_midi(v);
314 }
315 UniversalRealTimeMsg::TimeSignatureDelayed(signature) => {
316 v.push(0x3);
317 v.push(0x42);
318 signature.extend_midi(v);
319 }
320 UniversalRealTimeMsg::MasterVolume(vol) => {
321 v.push(0x4);
322 v.push(0x1);
323 push_u14(*vol, v);
324 }
325 UniversalRealTimeMsg::MasterBalance(bal) => {
326 v.push(0x4);
327 v.push(0x2);
328 push_u14(*bal, v);
329 }
330 UniversalRealTimeMsg::MasterFineTuning(t) => {
331 v.push(0x4);
332 v.push(0x3);
333 let [msb, lsb] = i_to_u14(*t);
334 v.push(lsb);
335 v.push(msb);
336 }
337 UniversalRealTimeMsg::MasterCoarseTuning(t) => {
338 v.push(0x4);
339 v.push(0x4);
340 v.push(i_to_u7(*t));
341 }
342 UniversalRealTimeMsg::GlobalParameterControl(gp) => {
343 v.push(0x4);
344 v.push(0x5);
345 gp.extend_midi(v);
346 }
347 UniversalRealTimeMsg::TimeCodeCueing(msg) => {
348 v.push(0x5);
349 msg.extend_midi(v);
350 }
351 UniversalRealTimeMsg::MachineControlCommand(msg) => {
352 v.push(0x6);
353 msg.extend_midi(v);
354 }
355 UniversalRealTimeMsg::MachineControlResponse(msg) => {
356 v.push(0x7);
357 msg.extend_midi(v);
358 }
359 UniversalRealTimeMsg::TuningNoteChange(note_change) => {
360 v.push(0x8);
361 v.push(if note_change.tuning_bank_num.is_some() {
362 0x7
363 } else {
364 0x2
365 });
366 if let Some(bank_num) = note_change.tuning_bank_num {
367 v.push(to_u7(bank_num))
368 }
369 note_change.extend_midi(v);
370 }
371 UniversalRealTimeMsg::ScaleTuning1Byte(tuning) => {
372 v.push(0x8);
373 v.push(0x8);
374 tuning.extend_midi(v);
375 }
376 UniversalRealTimeMsg::ScaleTuning2Byte(tuning) => {
377 v.push(0x8);
378 v.push(0x9);
379 tuning.extend_midi(v);
380 }
381 UniversalRealTimeMsg::ChannelPressureControllerDestination(d) => {
382 v.push(0x9);
383 v.push(0x1);
384 d.extend_midi(v);
385 }
386 UniversalRealTimeMsg::PolyphonicKeyPressureControllerDestination(d) => {
387 v.push(0x9);
388 v.push(0x2);
389 d.extend_midi(v);
390 }
391 UniversalRealTimeMsg::ControlChangeControllerDestination(d) => {
392 v.push(0x9);
393 v.push(0x3);
394 d.extend_midi(v);
395 }
396 UniversalRealTimeMsg::KeyBasedInstrumentControl(control) => {
397 v.push(0xA);
398 v.push(0x1);
399 control.extend_midi(v);
400 }
401 }
402 }
403
404 fn from_midi(m: &[u8], ctx: &mut ReceiverContext) -> Result<Self, ParseError> {
405 if m.len() < 2 {
406 return Err(crate::ParseError::UnexpectedEnd);
407 }
408
409 match (m[0], m[1]) {
410 (0x1, 0x1) => {
411 if m.len() > 6 {
412 Err(ParseError::Invalid(
413 "Extra bytes after a UniversalRealTimeMsg::TimeCodeFull",
414 ))
415 } else {
416 let time_code = TimeCode::from_midi(&m[2..])?;
417 ctx.time_code = time_code;
418 Ok(Self::TimeCodeFull(time_code))
419 }
420 }
421 _ => Err(ParseError::NotImplemented("UniversalRealTimeMsg")),
422 }
423 }
424}
425
426#[derive(Debug, Clone, PartialEq)]
428pub enum UniversalNonRealTimeMsg {
429 SampleDump(SampleDumpMsg),
431 ExtendedSampleDump(ExtendedSampleDumpMsg),
433 TimeCodeCueingSetup(TimeCodeCueingSetupMsg),
435 IdentityRequest,
437 IdentityReply(IdentityReply),
439 FileDump(FileDumpMsg),
441 TuningBulkDumpRequest(u8, Option<u8>),
444 KeyBasedTuningDump(KeyBasedTuningDump),
446 ScaleTuningDump1Byte(ScaleTuningDump1Byte),
448 ScaleTuningDump2Byte(ScaleTuningDump2Byte),
451 TuningNoteChange(TuningNoteChange),
453 ScaleTuning1Byte(ScaleTuning1Byte),
455 ScaleTuning2Byte(ScaleTuning2Byte),
457 GeneralMidi(GeneralMidi),
459 FileReference(FileReferenceMsg),
461 EOF,
463 Wait,
466 Cancel,
468 NAK(u8),
471 ACK(u8),
474}
475
476impl UniversalNonRealTimeMsg {
477 fn extend_midi(&self, v: &mut Vec<u8>) {
478 match self {
479 UniversalNonRealTimeMsg::SampleDump(msg) => {
480 match msg {
481 SampleDumpMsg::Header { .. } => v.push(0x1),
482 SampleDumpMsg::Packet { .. } => v.push(0x2),
483 SampleDumpMsg::Request { .. } => v.push(0x3),
484 SampleDumpMsg::LoopPointTransmission { .. } => {
485 v.push(0x5);
486 v.push(0x1);
487 }
488 SampleDumpMsg::LoopPointsRequest { .. } => {
489 v.push(0x5);
490 v.push(0x2);
491 }
492 }
493 msg.extend_midi(v);
494 }
495 UniversalNonRealTimeMsg::ExtendedSampleDump(msg) => {
496 v.push(0x5);
497 match msg {
498 ExtendedSampleDumpMsg::SampleName { .. } => v.push(0x3),
499 ExtendedSampleDumpMsg::SampleNameRequest { .. } => v.push(0x4),
500 ExtendedSampleDumpMsg::Header { .. } => v.push(0x5),
501 ExtendedSampleDumpMsg::LoopPointTransmission { .. } => v.push(0x6),
502 ExtendedSampleDumpMsg::LoopPointsRequest { .. } => v.push(0x7),
503 }
504 msg.extend_midi(v);
505 }
506 UniversalNonRealTimeMsg::TimeCodeCueingSetup(msg) => {
507 v.push(0x4);
508 msg.extend_midi(v);
509 }
510 UniversalNonRealTimeMsg::IdentityRequest => {
511 v.push(0x6);
512 v.push(0x1);
513 }
514 UniversalNonRealTimeMsg::IdentityReply(identity) => {
515 v.push(0x6);
516 v.push(0x2);
517 identity.extend_midi(v);
518 }
519 UniversalNonRealTimeMsg::FileDump(msg) => {
520 v.push(0x7);
521 msg.extend_midi(v);
522 }
523 UniversalNonRealTimeMsg::TuningBulkDumpRequest(program_num, bank_num) => {
524 v.push(0x8);
525 v.push(if bank_num.is_some() { 0x3 } else { 0x0 });
526 if let Some(bank_num) = bank_num {
527 v.push(to_u7(*bank_num))
528 }
529 v.push(to_u7(*program_num));
530 }
531 UniversalNonRealTimeMsg::KeyBasedTuningDump(tuning) => {
532 v.push(0x8);
533 v.push(if tuning.tuning_bank_num.is_some() {
534 0x4
535 } else {
536 0x1
537 });
538 tuning.extend_midi(v);
539 }
540 UniversalNonRealTimeMsg::ScaleTuningDump1Byte(tuning) => {
541 v.push(0x8);
542 v.push(0x5);
543 tuning.extend_midi(v);
544 }
545 UniversalNonRealTimeMsg::ScaleTuningDump2Byte(tuning) => {
546 v.push(0x8);
547 v.push(0x6);
548 tuning.extend_midi(v);
549 }
550 UniversalNonRealTimeMsg::TuningNoteChange(tuning) => {
551 v.push(0x8);
552 v.push(0x7);
553 if let Some(bank_num) = tuning.tuning_bank_num {
554 v.push(to_u7(bank_num))
555 } else {
556 v.push(0x0); }
558 tuning.extend_midi(v);
559 }
560 UniversalNonRealTimeMsg::ScaleTuning1Byte(tuning) => {
561 v.push(0x8);
562 v.push(0x8);
563 tuning.extend_midi(v);
564 }
565 UniversalNonRealTimeMsg::ScaleTuning2Byte(tuning) => {
566 v.push(0x8);
567 v.push(0x9);
568 tuning.extend_midi(v);
569 }
570 UniversalNonRealTimeMsg::GeneralMidi(gm) => {
571 v.push(0x9);
572 v.push(*gm as u8);
573 }
574 UniversalNonRealTimeMsg::FileReference(msg) => {
575 v.push(0xB);
576 match msg {
577 FileReferenceMsg::Open { .. } => v.push(0x1),
578 FileReferenceMsg::SelectContents { .. } => v.push(0x2),
579 FileReferenceMsg::OpenSelectContents { .. } => v.push(0x3),
580 FileReferenceMsg::Close { .. } => v.push(0x4),
581 }
582 msg.extend_midi(v);
583 }
584
585 UniversalNonRealTimeMsg::EOF => {
586 v.push(0x7B);
587 v.push(0x0);
588 }
589 UniversalNonRealTimeMsg::Wait => {
590 v.push(0x7C);
591 v.push(0x0);
592 }
593 UniversalNonRealTimeMsg::Cancel => {
594 v.push(0x7D);
595 v.push(0x0);
596 }
597 UniversalNonRealTimeMsg::NAK(packet_num) => {
598 v.push(0x7E);
599 v.push(to_u7(*packet_num));
600 }
601 UniversalNonRealTimeMsg::ACK(packet_num) => {
602 v.push(0x7F);
603 v.push(to_u7(*packet_num));
604 }
605 }
606 }
607
608 fn from_midi(m: &[u8]) -> Result<Self, ParseError> {
609 if m.len() < 2 {
610 return Err(crate::ParseError::UnexpectedEnd);
611 }
612
613 match (m[0], m[1]) {
614 (0x6, 0x2) => {
615 if m.len() < 3 {
616 return Err(crate::ParseError::UnexpectedEnd);
617 }
618 Ok(Self::IdentityReply(IdentityReply::from_midi(&m[2..])?))
619 }
620 _ => Err(ParseError::NotImplemented("UniversalNonRealTimeMsg")),
621 }
622 }
623}
624
625#[derive(Debug, Copy, Clone, PartialEq, Eq)]
629pub struct IdentityReply {
630 pub id: ManufacturerID,
631 pub family: u16,
632 pub family_member: u16,
633 pub software_revision: (u8, u8, u8, u8),
635}
636
637impl IdentityReply {
638 fn extend_midi(&self, v: &mut Vec<u8>) {
639 self.id.extend_midi(v);
640 push_u14(self.family, v);
641 push_u14(self.family_member, v);
642 v.push(to_u7(self.software_revision.0));
643 v.push(to_u7(self.software_revision.1));
644 v.push(to_u7(self.software_revision.2));
645 v.push(to_u7(self.software_revision.3));
646 }
647
648 fn from_midi(m: &[u8]) -> Result<Self, ParseError> {
649 let (manufacturer_id, shift) = ManufacturerID::from_midi(m)?;
650 if m.len() < shift + 8 {
651 return Err(crate::ParseError::UnexpectedEnd);
652 }
653 Ok(IdentityReply {
654 id: manufacturer_id,
655 family: u14_from_midi(&m[shift..])?,
656 family_member: u14_from_midi(&m[(shift + 2)..])?,
657 software_revision: (m[shift + 4], m[shift + 5], m[shift + 6], m[shift + 7]),
658 })
659 }
660
661 pub fn family_as_bytes(&self) -> [u8; 4] {
663 let [family_msb, family_lsb] = to_u14(self.family);
664 let [family_member_msb, family_member_lsb] = to_u14(self.family_member);
665 [family_lsb, family_msb, family_member_lsb, family_member_msb]
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::super::*;
672 use alloc::vec;
673
674 #[test]
675 fn serialize_system_exclusive_msg() {
676 assert_eq!(
677 MidiMsg::SystemExclusive {
678 msg: SystemExclusiveMsg::Commercial {
679 id: 1.into(),
680 data: vec![0xff, 0x77, 0x00]
681 }
682 }
683 .to_midi(),
684 vec![0xF0, 0x01, 0x7F, 0x77, 0x00, 0xF7]
685 );
686
687 assert_eq!(
688 MidiMsg::SystemExclusive {
689 msg: SystemExclusiveMsg::Commercial {
690 id: (1, 3).into(),
691 data: vec![0xff, 0x77, 0x00]
692 }
693 }
694 .to_midi(),
695 vec![0xF0, 0x00, 0x01, 0x03, 0x7F, 0x77, 0x00, 0xF7]
696 );
697
698 assert_eq!(
699 MidiMsg::SystemExclusive {
700 msg: SystemExclusiveMsg::NonCommercial {
701 data: vec![0xff, 0x77, 0x00]
702 }
703 }
704 .to_midi(),
705 vec![0xF0, 0x7D, 0x7F, 0x77, 0x00, 0xF7]
706 );
707
708 assert_eq!(
709 MidiMsg::SystemExclusive {
710 msg: SystemExclusiveMsg::UniversalNonRealTime {
711 device: DeviceID::AllCall,
712 msg: UniversalNonRealTimeMsg::EOF
713 }
714 }
715 .to_midi(),
716 vec![0xF0, 0x7E, 0x7F, 0x7B, 0x00, 0xF7]
717 );
718
719 assert_eq!(
720 MidiMsg::SystemExclusive {
721 msg: SystemExclusiveMsg::UniversalRealTime {
722 device: DeviceID::Device(3),
723 msg: UniversalRealTimeMsg::MasterVolume(1000)
724 }
725 }
726 .to_midi(),
727 vec![0xF0, 0x7F, 0x03, 0x04, 0x01, 0x68, 0x07, 0xF7]
728 );
729 }
730
731 #[test]
732 fn deserialize_system_exclusive_msg() {
733 let mut ctx = ReceiverContext::new();
734
735 test_serialization(
736 MidiMsg::SystemExclusive {
737 msg: SystemExclusiveMsg::Commercial {
738 id: 1.into(),
739 data: vec![0x7f, 0x77, 0x00],
740 },
741 },
742 &mut ctx,
743 );
744
745 test_serialization(
746 MidiMsg::SystemExclusive {
747 msg: SystemExclusiveMsg::Commercial {
748 id: (1, 3).into(),
749 data: vec![0x7f, 0x77, 0x00],
750 },
751 },
752 &mut ctx,
753 );
754
755 test_serialization(
756 MidiMsg::SystemExclusive {
757 msg: SystemExclusiveMsg::NonCommercial {
758 data: vec![0x7f, 0x77, 0x00],
759 },
760 },
761 &mut ctx,
762 );
763
764 test_serialization(
765 MidiMsg::SystemExclusive {
766 msg: SystemExclusiveMsg::UniversalRealTime {
767 device: DeviceID::AllCall,
768 msg: UniversalRealTimeMsg::TimeCodeFull(TimeCode {
769 frames: 29,
770 seconds: 58,
771 minutes: 20,
772 hours: 23,
773 code_type: TimeCodeType::DF30,
774 }),
775 },
776 },
777 &mut ctx,
778 );
779
780 assert_eq!(
781 ctx.time_code,
782 TimeCode {
783 frames: 29,
784 seconds: 58,
785 minutes: 20,
786 hours: 23,
787 code_type: TimeCodeType::DF30,
788 }
789 );
790 }
791}