Skip to content

Commit 27a1390

Browse files
authored
Merge pull request #17 from orchetect/dev
New features and fixes
2 parents bb850da + 190b92d commit 27a1390

30 files changed

+1040
-386
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Note: This documentation does not cover every property and initializer available
8080

8181
### Initialization
8282

83-
Using `(_ exaclty:, ...)` by default:
83+
Using `(_ exactly:, ...)` by default:
8484

8585
```swift
8686
// from Int timecode component values
@@ -91,9 +91,9 @@ TCC(h: 01, m: 00, s: 00, f: 00).toTimecode(at: ._23_976) // alternate method
9191
Timecode("01:00:00:00", at: ._23_976)
9292
"01:00:00:00".toTimecode(at: ._23_976) // alternate method
9393

94-
// from real time (wall clock) elapsed
95-
Timecode(TimeValue(seconds: 4723.241579), at: ._23_976)
96-
TimeValue(seconds: 4723.241579).toTimecode(at: ._23_976) // alternate method
94+
// from real time (wall clock) elapsed in seconds
95+
Timecode(realTimeValue: 4723.241579, at: ._23_976)
96+
(4723.241579).toTimecode(at: ._23_976) // alternate method on TimeInterval
9797

9898
// from elapsed number of audio samples at a given sample rate
9999
Timecode(samples: 123456789, sampleRate: 48000, at: ._23_976)
@@ -181,7 +181,7 @@ TCC(h: 01, m: 00, s: 00, f: 00)
181181

182182
### Math
183183

184-
Using operators:
184+
Using operators (which use `wrapping:` internally if the result underflows or overflows timecode bounds):
185185

186186
```swift
187187
guard let tc1 = "01:00:00:00".toTimecode(at: ._23_976) else { return }
@@ -201,13 +201,15 @@ Mutating methods:
201201
- `.subtract()`
202202
- `.multiply()`
203203
- `.divide()`
204+
- `.offset()`
204205

205206
Non-mutating methods that produce a new `Timecode` instance:
206207

207208
- `.adding()`
208209
- `.subtracting()`
209210
- `.multiplying()`
210211
- `.dividing()`
212+
- `.offsetting()`
211213

212214
### Conversions
213215

@@ -227,13 +229,13 @@ Non-mutating methods that produce a new `Timecode` instance:
227229
// timecode to real-world time
228230
let tc = "01:00:00:00"
229231
.toTimecode(at: ._23_976)?
230-
.realTimeValue // == TimeValue()
232+
.realTimeValue // == TimeInterval (aka Double)
231233

232234
tc?.seconds // == 3603.6
233235
tc?.ms // == 3603600.0
234236

235237
// real-world time to timecode
236-
TimeValue(seconds: 3603.6)
238+
(3603.6) // TimeInterval, aka Double
237239
.toTimecode(at: ._23_976)?
238240
.stringValue // == "01:00:00:00"
239241
```

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,48 @@
66
// Copyright © 2018 Steffan Andrews. All rights reserved.
77
//
88

9+
import Foundation
10+
911
extension Timecode {
1012

11-
/// (Lossy) Returns the current timecode converted to a duration in real-time milliseconds (wall-clock time), based on the frame rate. Value is returned as a Double so a high level of precision can be maintained.
13+
/// (Lossy) Returns the current timecode converted to a duration in real-time (wall-clock time), based on the frame rate.
1214
///
13-
/// Generally, `.realTimeValue` -> `.setTimecode(from: TimeValue)` will produce equivalent results where 'from timecode' == 'out timecode'.
15+
/// Generally, `.realTimeValue` -> `.setTimecode(fromRealTimeValue:)` will produce equivalent results where 'from timecode' == 'out timecode'.
1416
/// When setting, invalid values will cause the setter to fail silently. (Validation is based on the frame rate and `upperLimit` property.)
15-
public var realTimeValue: TimeValue {
17+
public var realTimeValue: TimeInterval {
1618

1719
get {
18-
var calc = Double(totalElapsedFrames) * (1000.0 / frameRate.frameRateForRealTimeCalculation)
20+
var calc = Double(totalElapsedFrames) * (1.0 / frameRate.frameRateForRealTimeCalculation)
1921

2022
// over-estimate so real time is just past the equivalent timecode
2123
// so calculations of real time back into timecode work reliably
2224
// 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
2325

24-
calc += 0.00001
26+
calc += 0.00000001
2527

26-
return TimeValue(ms: calc)
28+
return calc
2729
}
2830

2931
set {
3032
// set, suppressing failure silently
31-
_ = setTimecode(from: newValue)
33+
_ = setTimecode(fromRealTimeValue: newValue)
3234
}
3335

3436
}
3537

36-
/// Sets the timecode to the nearest frame at the current frame rate from real-time milliseconds.
38+
/// Sets the timecode to the nearest frame at the current frame rate from real-time (wall-clock time).
3739
/// Returns false if it underflows or overflows valid timecode range.
3840
@discardableResult
39-
public mutating func setTimecode(from realTimeValue: TimeValue) -> Bool {
41+
public mutating func setTimecode(fromRealTimeValue: TimeInterval) -> Bool {
4042

4143
// the basic calculation
42-
var calc = realTimeValue.ms / (1000.0 / frameRate.frameRateForRealTimeCalculation)
44+
var calc = fromRealTimeValue / (1.0 / frameRate.frameRateForRealTimeCalculation)
4345

4446
// over-estimate so real time is just past the equivalent timecode
4547
// so calculations of real time back into timecode work reliably
4648
// 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
4749

48-
calc += 0.0006
50+
calc += 0.0000006
4951

5052
// final calculation
5153

@@ -60,7 +62,8 @@ extension Timecode {
6062

6163
}
6264

63-
extension TimeValue {
65+
// a.k.a: extension Double
66+
extension TimeInterval {
6467

6568
/// Convenience method to create an `Timecode` struct using the default `(_ exactly:)` initializer.
6669
public func toTimecode(at frameRate: Timecode.FrameRate,
@@ -69,14 +72,14 @@ extension TimeValue {
6972

7073
if let sfd = subFramesDivisor {
7174

72-
return Timecode(self,
75+
return Timecode(realTimeValue: self,
7376
at: frameRate,
7477
limit: limit,
7578
subFramesDivisor: sfd)
7679

7780
} else {
7881

79-
return Timecode(self,
82+
return Timecode(realTimeValue: self,
8083
at: frameRate,
8184
limit: limit)
8285

Sources/TimecodeKit/Data Interchange/Timecode String.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,24 +72,23 @@ extension Timecode {
7272
/// Forms `.stringValue` using filename-compatible characters.
7373
public var stringValueFileNameCompatible: String {
7474

75-
let result = stringValue
75+
stringValue
7676
.replacingOccurrences(of: ":", with: "-")
7777
.replacingOccurrences(of: ";", with: "-")
7878
.replacingOccurrences(of: " ", with: "-")
7979

80-
return result
81-
8280
}
8381

8482
// MARK: stringValueValidated
8583

8684
/// Returns `stringValue` as `NSAttributedString`, highlighting invalid values.
8785
///
8886
/// `invalidAttributes` are the `NSAttributedString` attributes applied to invalid values.
89-
/// If `invalidAttributes` are not passed, the default of red forground color is used.
90-
public func stringValueValidated(invalidAttributes: [NSAttributedString.Key : Any]? = nil,
91-
withDefaultAttributes attrs: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString
92-
{
87+
/// If `invalidAttributes` are not passed, the default of red foreground color is used.
88+
public func stringValueValidated(
89+
invalidAttributes: [NSAttributedString.Key : Any]? = nil,
90+
withDefaultAttributes attrs: [NSAttributedString.Key : Any]? = nil
91+
) -> NSAttributedString{
9392

9493
let sepDays = NSAttributedString(string: " ", attributes: attrs)
9594
let sepMain = NSAttributedString(string: ":", attributes: attrs)

Sources/TimecodeKit/Delta/Delta.swift

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//
2+
// Delta.swift
3+
// TimecodeKit
4+
//
5+
// Created by Steffan Andrews on 2021-04-23.
6+
// Copyright © 2021 Steffan Andrews. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
extension Timecode {
12+
13+
/// Represents an abstract delta between two `Timecode` structs.
14+
public struct Delta {
15+
16+
@usableFromInline let delta: Timecode
17+
@usableFromInline let sign: Sign
18+
19+
@inlinable public init(_ delta: Timecode,
20+
_ sign: Sign = .positive) {
21+
22+
self.delta = delta
23+
self.sign = sign
24+
25+
}
26+
27+
/// Returns true if sign is negative.
28+
@inlinable public var isNegative: Bool {
29+
30+
sign == .negative
31+
32+
}
33+
34+
/// Returns the delta value expressed as a concrete `Timecode` value by wrapping around lower/upper timecode limit bounds if necessary.
35+
@inlinable public var timecode: Timecode {
36+
37+
switch sign {
38+
case .positive:
39+
return delta.adding(wrapping: TCC())
40+
41+
case .negative:
42+
return
43+
Timecode(
44+
TCC(f: 0),
45+
at: delta.frameRate,
46+
limit: delta.upperLimit,
47+
subFramesDivisor: delta.subFramesDivisor
48+
)!
49+
.subtracting(wrapping: delta.components)
50+
}
51+
52+
}
53+
54+
/// Returns a `Timecode` value offsetting it by the delta value, wrapping around lower/upper timecode limit bounds if necessary.
55+
@inlinable public func timecode(offsetting base: Timecode) -> Timecode {
56+
57+
base + timecode
58+
59+
}
60+
61+
/// Returns real-time (wall-clock time) equivalent of the delta time.
62+
@inline(__always) public var realTimeValue: TimeInterval {
63+
64+
switch sign {
65+
case .positive:
66+
return delta.realTimeValue
67+
68+
case .negative:
69+
return -delta.realTimeValue
70+
}
71+
72+
}
73+
74+
}
75+
76+
}
77+
78+
extension Timecode.Delta: CustomStringConvertible, CustomDebugStringConvertible {
79+
80+
public var description: String {
81+
82+
switch sign {
83+
case .positive: return delta.description
84+
case .negative: return "-\(delta.description)"
85+
}
86+
87+
}
88+
89+
public var debugDescription: String {
90+
91+
switch sign {
92+
case .positive: return "Timecode.Delta(\(delta.description))"
93+
case .negative: return "Timecode.Delta(-\(delta.description))"
94+
}
95+
96+
}
97+
98+
public var verboseDescription: String {
99+
100+
"Timecode Delta \(description) @ \(delta.frameRate.stringValue)"
101+
102+
}
103+
104+
}
105+
106+
extension Timecode.Delta {
107+
108+
public enum Sign {
109+
110+
case positive
111+
case negative
112+
113+
}
114+
115+
}

Sources/TimecodeKit/Math/Timecode Math Internal.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,5 +288,56 @@ extension Timecode {
288288
subFramesDivisor: subFramesDivisor)
289289

290290
}
291+
292+
293+
// MARK: - Offset / Delta
294+
295+
/// Utility function to return a Delta duration.
296+
@usableFromInline
297+
internal func __offset(to other: Components) -> Delta {
298+
299+
if self.components == other {
300+
return .init(TCC().toTimecode(rawValuesAt: frameRate,
301+
limit: upperLimit,
302+
subFramesDivisor: subFramesDivisor),
303+
.positive)
304+
}
305+
306+
let otherTimecode = Timecode(rawValues: other,
307+
at: frameRate,
308+
limit: ._100days,
309+
subFramesDivisor: subFramesDivisor)
310+
311+
if otherTimecode > self {
312+
313+
let diff = otherTimecode
314+
.__subtract(wrapping: components,
315+
from: otherTimecode.components)
316+
317+
let deltaTC = diff.toTimecode(rawValuesAt: frameRate,
318+
limit: upperLimit,
319+
subFramesDivisor: subFramesDivisor)
320+
321+
let delta = Delta(deltaTC, .positive)
322+
323+
return delta
324+
325+
} else /* other < self */ {
326+
327+
let diff = otherTimecode
328+
.__subtract(wrapping: other,
329+
from: components)
330+
331+
let deltaTC = diff.toTimecode(rawValuesAt: frameRate,
332+
limit: upperLimit,
333+
subFramesDivisor: subFramesDivisor)
334+
335+
let delta = Delta(deltaTC, .negative)
336+
337+
return delta
338+
339+
}
340+
341+
}
291342

292343
}

0 commit comments

Comments
 (0)