Skip to content

Commit 826bc0c

Browse files
authored
Merge pull request #19 from orchetect/dev
Improved real time conversion & added additional unit test
2 parents 2e71cb7 + 09c4720 commit 826bc0c

File tree

2 files changed

+107
-14
lines changed

2 files changed

+107
-14
lines changed

Sources/TimecodeKit/Data Interchange/Timecode Real Time.swift

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,14 @@ extension Timecode {
1212

1313
/// (Lossy) Returns the current timecode converted to a duration in real-time (wall-clock time), based on the frame rate.
1414
///
15-
/// Generally, `.realTimeValue` -> `.setTimecode(fromRealTimeValue:)` will produce equivalent results where 'from timecode' == 'out timecode'.
16-
/// When setting, invalid values will cause the setter to fail silently. (Validation is based on the frame rate and `upperLimit` property.)
15+
/// Generally, `.realTimeValue` -> `.setTimecode(fromRealTimeValue:)` will produce equivalent timecodes.
16+
///
17+
/// When setting, invalid values will cause the setter to fail silently.
18+
/// (Validation is based on the frame rate and `upperLimit` property.)
1719
public var realTimeValue: TimeInterval {
1820

1921
get {
20-
var calc = Double(totalElapsedFrames) * (1.0 / frameRate.frameRateForRealTimeCalculation)
21-
22-
// over-estimate so real time is just past the equivalent timecode
23-
// so calculations of real time back into timecode work reliably
24-
// otherwise, this math produces a real time value that can be a hair under the actual elapsed real time that would trigger the equivalent timecode
25-
26-
calc += 0.00000001
27-
28-
return calc
22+
Double(totalElapsedFrames) * (1.0 / frameRate.frameRateForRealTimeCalculation)
2923
}
3024

3125
set {
@@ -36,18 +30,19 @@ extension Timecode {
3630
}
3731

3832
/// Sets the timecode to the nearest frame at the current frame rate from real-time (wall-clock time).
33+
///
3934
/// Returns false if it underflows or overflows valid timecode range.
35+
/// (Validation is based on the frame rate and `upperLimit` property.)
4036
@discardableResult
4137
public mutating func setTimecode(fromRealTimeValue: TimeInterval) -> Bool {
4238

4339
// the basic calculation
4440
var calc = fromRealTimeValue / (1.0 / frameRate.frameRateForRealTimeCalculation)
4541

4642
// over-estimate so real time is just past the equivalent timecode
47-
// so calculations of real time back into timecode work reliably
48-
// otherwise, this math produces a real time value that can be a hair under the actual elapsed real time that would trigger the equivalent timecode
43+
// since raw time values in practise can be a hair under the actual elapsed real time that would trigger the equivalent timecode (due to precision and rounding behaviors that may not be in our control, depending on where the passed real time value originated)
4944

50-
calc += 0.0000006
45+
calc += 0.000_010 // 10 microseconds
5146

5247
// final calculation
5348

Tests/TimecodeKit-Unit-Tests/Unit Tests/Data Interchange/Timecode Real Time Tests.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,104 @@ class Timecode_UT_DI_Real_Time_Tests: XCTestCase {
161161

162162
}
163163

164+
func testTimecode_RealTimeValue_RealWorld_SubFrames() {
165+
166+
// test against real-world values extracted from DAWs
167+
168+
// Cubase 11 XML file output (high resolution floating-point times in seconds)
169+
170+
// the timecodes in the constant variable names are the timecodes as seen in Cubase
171+
// the float-point number constant values are extracted from a Track Archive XML file exported from the Cubase project which outputs very high precision float-point numbers in seconds to define many attributes such as the project start time, and event start times and lengths on tracks which are in absolute time mode (not musical bars/beats mode which gets stored as PPQ values in the XML file instead of float-point seconds)
172+
173+
174+
// 23.976fps, 80 subframe divisor
175+
176+
// _HH_MM_SS_FF_SF
177+
178+
// session start timecode
179+
let _00_49_27_15_00 = 2970.5926250000002255546860396862030029296875
180+
181+
// events: delta times from session start time
182+
let _00_49_29_17_00_delta = 2.0854162836310767836778268247144296765327453613281
183+
let _00_49_31_09_00_delta = 3.7537499627098442900319241744000464677810668945312
184+
let _00_49_33_21_79_delta = 6.297436893962323978257700218819081783294677734375
185+
let _00_49_38_01_79_delta = 10.468270548180987233877203834708780050277709960938
186+
187+
// test timecode formation from real time
188+
189+
let start = Timecode(realTimeValue: _00_49_27_15_00,
190+
at: ._23_976)!
191+
XCTAssertEqual(start.components,
192+
TCC(h: 00, m: 49, s: 27, f: 15, sf: 00))
193+
194+
let event1 = Timecode(realTimeValue: _00_49_27_15_00 + _00_49_29_17_00_delta,
195+
at: ._23_976)!
196+
XCTAssertEqual(event1.components,
197+
TCC(h: 00, m: 49, s: 29, f: 17, sf: 00))
198+
199+
let event2 = Timecode(realTimeValue: _00_49_27_15_00 + _00_49_31_09_00_delta,
200+
at: ._23_976)!
201+
XCTAssertEqual(event2.components,
202+
TCC(h: 00, m: 49, s: 31, f: 09, sf: 00))
203+
204+
let event3 = Timecode(realTimeValue: _00_49_27_15_00 + _00_49_33_21_79_delta,
205+
at: ._23_976)!
206+
XCTAssertEqual(event3.components,
207+
TCC(h: 00, m: 49, s: 33, f: 21, sf: 79))
208+
209+
let event4 = Timecode(realTimeValue: _00_49_27_15_00 + _00_49_38_01_79_delta,
210+
at: ._23_976)!
211+
XCTAssertEqual(event4.components,
212+
TCC(h: 00, m: 49, s: 38, f: 01, sf: 79))
213+
214+
// test real time matching the seconds constants
215+
216+
// start
217+
XCTAssertEqual(
218+
TCC(h: 00, m: 49, s: 27, f: 15, sf: 00)
219+
.toTimecode(at: ._23_976)!
220+
.realTimeValue,
221+
_00_49_27_15_00
222+
)
223+
224+
// event1
225+
XCTAssertEqual(
226+
TCC(h: 00, m: 49, s: 29, f: 17, sf: 00)
227+
.toTimecode(at: ._23_976)!
228+
.realTimeValue,
229+
_00_49_27_15_00 + _00_49_29_17_00_delta,
230+
accuracy: 0.0000005
231+
)
232+
233+
// event2
234+
XCTAssertEqual(
235+
TCC(h: 00, m: 49, s: 31, f: 09, sf: 00)
236+
.toTimecode(at: ._23_976)!
237+
.realTimeValue,
238+
_00_49_27_15_00 + _00_49_31_09_00_delta,
239+
accuracy: 0.0000005
240+
)
241+
242+
// event3
243+
XCTAssertEqual(
244+
TCC(h: 00, m: 49, s: 33, f: 21, sf: 79)
245+
.toTimecode(at: ._23_976)!
246+
.realTimeValue,
247+
_00_49_27_15_00 + _00_49_33_21_79_delta,
248+
accuracy: 0.0000005
249+
)
250+
251+
// event4
252+
XCTAssertEqual(
253+
TCC(h: 00, m: 49, s: 38, f: 01, sf: 79)
254+
.toTimecode(at: ._23_976)!
255+
.realTimeValue,
256+
_00_49_27_15_00 + _00_49_38_01_79_delta,
257+
accuracy: 0.0000005
258+
)
259+
260+
}
261+
164262
}
165263

166264
#endif

0 commit comments

Comments
 (0)