Skip to content

Commit

Permalink
Sync: Bugfix for MTC.Encoder.locate(to:) full-frame erratic generatio…
Browse files Browse the repository at this point in the history
…n conditions
  • Loading branch information
orchetect committed Jun 29, 2021
1 parent b9a716b commit 7be9b08
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 3 deletions.
17 changes: 14 additions & 3 deletions Sources/MIDIKitSync/MTC/Generator/MTC Encoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,23 +137,32 @@ extension MIDI.MTC {
setLocalFrameRate(frameRate)
}

// Step 1: set Encoder's internal MTC components

let scaledFrames = localFrameRate
.scaledFrames(fromTimecodeFrames: Double(components.f))

var newComponents = components
newComponents.f = scaledFrames.rawMTCFrames

// sanitize: clear subframes since we're working at 1-frame resolution with timecode display values
newComponents.sf = 0

setMTCComponents(mtc: newComponents)
mtcQuarterFrame = scaledFrames.rawMTCQuarterFrames
mtcQuarterFrameStreamHasStartedSinceLastLocate = false

// tell handler to transmit MIDI message
// Step 2: tell handler to transmit full-frame message if applicable

switch transmitFullFrame {
case .always:
sendFullFrameMIDIMessage()
case .ifDifferent:
newComponents = MIDI.MTC.convertToFullFrameComponents(
mtcComponents: newComponents,
mtcQuarterFrames: scaledFrames.rawMTCQuarterFrames
)

let newFullFrame = (mtcComponents: newComponents,
mtcFrameRate: mtcFrameRate)
if !mtcIsEqual(lastTransmitFullFrame, newFullFrame) {
Expand Down Expand Up @@ -250,8 +259,10 @@ extension MIDI.MTC {
// rr == 10: 29.97d frames/s (SMPTE drop-frame timecode)
// rr == 11: 30 frames/s

var newComponents = mtcComponents
newComponents.f += ((25 * Int(mtcQuarterFrame)) / 100)
let newComponents = convertToFullFrameComponents(
mtcComponents: mtcComponents,
mtcQuarterFrames: mtcQuarterFrame
)

let midiMessage: [Byte] = [
0xF0,
Expand Down
13 changes: 13 additions & 0 deletions Sources/MIDIKitSync/MTC/MTC Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,17 @@ extension MIDI.MTC {

}

/// Internal: Converts MTC components and quarter frames to full-frame components
internal static func convertToFullFrameComponents(
mtcComponents: Timecode.Components,
mtcQuarterFrames: UInt8
) -> Timecode.Components {

var newComponents = mtcComponents
newComponents.f += ((25 * Int(mtcQuarterFrames)) / 100)

return newComponents

}

}
137 changes: 137 additions & 0 deletions Tests/MIDIKitSyncTests/MTC/Generator/MTC Encoder Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,143 @@ final class MTC_Generator_Encoder_Tests: XCTestCase {

}

func testMTC_Encoder_LocateBehavior() {

var mtcEnc: MIDI.MTC.Encoder

var message: [Byte]?

func initNewEnc() -> MIDI.MTC.Encoder {
return MIDI.MTC.Encoder { midiMessage in
message = midiMessage
}
}

mtcEnc = initNewEnc()
mtcEnc.locate(to: Timecode(at: ._24))

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 0, m: 00, s: 00, f: 00))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0000_0000, // 0rrh_hhhh
0x00, // M
0x00, // S
0x00, // F
0xF7
])

mtcEnc = initNewEnc()
mtcEnc.locate(to: Timecode(at: ._25))

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 0, m: 00, s: 00, f: 00))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0010_0000, // 0rrh_hhhh
0x00, // M
0x00, // S
0x00, // F
0xF7
])

mtcEnc = initNewEnc()
mtcEnc.locate(to: TCC(h: 1, m: 02, s: 03, f: 04).toTimecode(at: ._29_97_drop)!)

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 1, m: 02, s: 03, f: 04))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0100_0001, // 0rrh_hhhh
0x02, // M
0x03, // S
0x04, // F
0xF7
])

mtcEnc.locate(to: TCC(h: 1, m: 02, s: 03, f: 05).toTimecode(at: ._29_97_drop)!)

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 1, m: 02, s: 03, f: 05))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0100_0001, // 0rrh_hhhh
0x02, // M
0x03, // S
0x05, // F
0xF7
])

mtcEnc = initNewEnc()
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 08).toTimecode(at: ._30)!)

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 2, m: 04, s: 06, f: 08))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0110_0010, // 0rrh_hhhh
0x04, // M
0x06, // S
0x08, // F
0xF7
])

// scaling frame rates

mtcEnc = initNewEnc()

// scales to MTC-24 fps
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 08).toTimecode(at: ._48)!)

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 2, m: 04, s: 06, f: 04))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0000_0010, // 0rrh_hhhh
0x04, // M
0x06, // S
0x04, // F
0xF7
])

// scales to MTC-24 fps
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 09).toTimecode(at: ._48)!)

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 2, m: 04, s: 06, f: 04))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0000_0010, // 0rrh_hhhh
0x04, // M
0x06, // S
0x04, // F -- rounds down to 4 from 4.5 from scaling
0xF7
])

// scales to MTC-24 fps
mtcEnc.locate(to: TCC(h: 2, m: 04, s: 06, f: 10).toTimecode(at: ._48)!)

XCTAssertEqual(mtcEnc.generateFullFrameMIDIMessage().components,
TCC(h: 2, m: 04, s: 06, f: 05))
XCTAssertEqual(message,
[
0xF0, 0x7F, 0x7F, 0x01, 0x01,
0b0000_0010, // 0rrh_hhhh
0x04, // M
0x06, // S
0x05, // F
0xF7
])

}

func testMTC_Encoder_QFMIDIMessage() {

let mtcEnc = MIDI.MTC.Encoder()
Expand Down

0 comments on commit 7be9b08

Please sign in to comment.