Skip to content

Commit 7be9b08

Browse files
committed
Sync: Bugfix for MTC.Encoder.locate(to:) full-frame erratic generation conditions
1 parent b9a716b commit 7be9b08

File tree

3 files changed

+164
-3
lines changed

3 files changed

+164
-3
lines changed

Sources/MIDIKitSync/MTC/Generator/MTC Encoder.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,23 +137,32 @@ extension MIDI.MTC {
137137
setLocalFrameRate(frameRate)
138138
}
139139

140+
// Step 1: set Encoder's internal MTC components
141+
140142
let scaledFrames = localFrameRate
141143
.scaledFrames(fromTimecodeFrames: Double(components.f))
142144

143145
var newComponents = components
144146
newComponents.f = scaledFrames.rawMTCFrames
147+
145148
// sanitize: clear subframes since we're working at 1-frame resolution with timecode display values
146149
newComponents.sf = 0
147150

148151
setMTCComponents(mtc: newComponents)
149152
mtcQuarterFrame = scaledFrames.rawMTCQuarterFrames
150153
mtcQuarterFrameStreamHasStartedSinceLastLocate = false
151154

152-
// tell handler to transmit MIDI message
155+
// Step 2: tell handler to transmit full-frame message if applicable
156+
153157
switch transmitFullFrame {
154158
case .always:
155159
sendFullFrameMIDIMessage()
156160
case .ifDifferent:
161+
newComponents = MIDI.MTC.convertToFullFrameComponents(
162+
mtcComponents: newComponents,
163+
mtcQuarterFrames: scaledFrames.rawMTCQuarterFrames
164+
)
165+
157166
let newFullFrame = (mtcComponents: newComponents,
158167
mtcFrameRate: mtcFrameRate)
159168
if !mtcIsEqual(lastTransmitFullFrame, newFullFrame) {
@@ -250,8 +259,10 @@ extension MIDI.MTC {
250259
// rr == 10: 29.97d frames/s (SMPTE drop-frame timecode)
251260
// rr == 11: 30 frames/s
252261

253-
var newComponents = mtcComponents
254-
newComponents.f += ((25 * Int(mtcQuarterFrame)) / 100)
262+
let newComponents = convertToFullFrameComponents(
263+
mtcComponents: mtcComponents,
264+
mtcQuarterFrames: mtcQuarterFrame
265+
)
255266

256267
let midiMessage: [Byte] = [
257268
0xF0,

Sources/MIDIKitSync/MTC/MTC Utilities.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,17 @@ extension MIDI.MTC {
3535

3636
}
3737

38+
/// Internal: Converts MTC components and quarter frames to full-frame components
39+
internal static func convertToFullFrameComponents(
40+
mtcComponents: Timecode.Components,
41+
mtcQuarterFrames: UInt8
42+
) -> Timecode.Components {
43+
44+
var newComponents = mtcComponents
45+
newComponents.f += ((25 * Int(mtcQuarterFrames)) / 100)
46+
47+
return newComponents
48+
49+
}
50+
3851
}

Tests/MIDIKitSyncTests/MTC/Generator/MTC Encoder Tests.swift

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,143 @@ final class MTC_Generator_Encoder_Tests: XCTestCase {
933933

934934
}
935935

936+
func testMTC_Encoder_LocateBehavior() {
937+
938+
var mtcEnc: MIDI.MTC.Encoder
939+
940+
var message: [Byte]?
941+
942+
func initNewEnc() -> MIDI.MTC.Encoder {
943+
return MIDI.MTC.Encoder { midiMessage in
944+
message = midiMessage
945+
}
946+
}
947+
948+
mtcEnc = initNewEnc()
949+
mtcEnc.locate(to: Timecode(at: ._24))
950+
951+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
952+
TCC(h: 0, m: 00, s: 00, f: 00))
953+
XCTAssertEqual(message,
954+
[
955+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
956+
0b0000_0000, // 0rrh_hhhh
957+
0x00, // M
958+
0x00, // S
959+
0x00, // F
960+
0xF7
961+
])
962+
963+
mtcEnc = initNewEnc()
964+
mtcEnc.locate(to: Timecode(at: ._25))
965+
966+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
967+
TCC(h: 0, m: 00, s: 00, f: 00))
968+
XCTAssertEqual(message,
969+
[
970+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
971+
0b0010_0000, // 0rrh_hhhh
972+
0x00, // M
973+
0x00, // S
974+
0x00, // F
975+
0xF7
976+
])
977+
978+
mtcEnc = initNewEnc()
979+
mtcEnc.locate(to: TCC(h: 1, m: 02, s: 03, f: 04).toTimecode(at: ._29_97_drop)!)
980+
981+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
982+
TCC(h: 1, m: 02, s: 03, f: 04))
983+
XCTAssertEqual(message,
984+
[
985+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
986+
0b0100_0001, // 0rrh_hhhh
987+
0x02, // M
988+
0x03, // S
989+
0x04, // F
990+
0xF7
991+
])
992+
993+
mtcEnc.locate(to: TCC(h: 1, m: 02, s: 03, f: 05).toTimecode(at: ._29_97_drop)!)
994+
995+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
996+
TCC(h: 1, m: 02, s: 03, f: 05))
997+
XCTAssertEqual(message,
998+
[
999+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
1000+
0b0100_0001, // 0rrh_hhhh
1001+
0x02, // M
1002+
0x03, // S
1003+
0x05, // F
1004+
0xF7
1005+
])
1006+
1007+
mtcEnc = initNewEnc()
1008+
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 08).toTimecode(at: ._30)!)
1009+
1010+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
1011+
TCC(h: 2, m: 04, s: 06, f: 08))
1012+
XCTAssertEqual(message,
1013+
[
1014+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
1015+
0b0110_0010, // 0rrh_hhhh
1016+
0x04, // M
1017+
0x06, // S
1018+
0x08, // F
1019+
0xF7
1020+
])
1021+
1022+
// scaling frame rates
1023+
1024+
mtcEnc = initNewEnc()
1025+
1026+
// scales to MTC-24 fps
1027+
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 08).toTimecode(at: ._48)!)
1028+
1029+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
1030+
TCC(h: 2, m: 04, s: 06, f: 04))
1031+
XCTAssertEqual(message,
1032+
[
1033+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
1034+
0b0000_0010, // 0rrh_hhhh
1035+
0x04, // M
1036+
0x06, // S
1037+
0x04, // F
1038+
0xF7
1039+
])
1040+
1041+
// scales to MTC-24 fps
1042+
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 09).toTimecode(at: ._48)!)
1043+
1044+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
1045+
TCC(h: 2, m: 04, s: 06, f: 04))
1046+
XCTAssertEqual(message,
1047+
[
1048+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
1049+
0b0000_0010, // 0rrh_hhhh
1050+
0x04, // M
1051+
0x06, // S
1052+
0x04, // F -- rounds down to 4 from 4.5 from scaling
1053+
0xF7
1054+
])
1055+
1056+
// scales to MTC-24 fps
1057+
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 10).toTimecode(at: ._48)!)
1058+
1059+
XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
1060+
TCC(h: 2, m: 04, s: 06, f: 05))
1061+
XCTAssertEqual(message,
1062+
[
1063+
0xF0, 0x7F, 0x7F, 0x01, 0x01,
1064+
0b0000_0010, // 0rrh_hhhh
1065+
0x04, // M
1066+
0x06, // S
1067+
0x05, // F
1068+
0xF7
1069+
])
1070+
1071+
}
1072+
9361073
func testMTC_Encoder_QFMIDIMessage() {
9371074

9381075
let mtcEnc = MIDI.MTC.Encoder()

0 commit comments

Comments
 (0)