Add methods to use a `Timecode` struct as a template
Closed this issue · 2 comments
Proposal
In practise, it is often needed to create a new Timecode
instance from a value but retain all of the options set on it.
For example, create a new Timecode
instance from a realTimeValue
(TimeInterval
) without having to copy over all of its properties individually that were set (assuming some or all were not defaults).
// set up with non-default options
let tc1 = try Timecode("01:20:15:10",
at: ._29_97,
limit: ._100days
base: ._80SubFrames
format: [.showsSubFrames]
)
// currently, if you want to grandfather some or all of the properties
// to a new `Timecode` instance with a new time value,
// you have to copy them over individually:
let tc2 = try Timecode("01:20:15:10",
at: tc1.frameRate
limit: ._24hours // new upperLimit
base: tc1.subFramesBase
format: tc1.stringformat
)
When using Timecode
in a codebase that deals heavily in manipulating timecode, this API starts to become very redundant and clutter up the code.
Solution 1
As referenced in #34, making all stored properties in Timecode
mutable would allow the following:
var tc2 = tc1
tc2.upperLimit = ._24hours
Thus allowing you to use a "reference" or "template" instance of Timecode
to derive all its configured properties from while changing one or more attributes, reducing the boilerplate needed.
However, this still requires one to first make a copy then mutate the property/properties.
Also, it does not come with the benefit of using an initializer that may be needed to validate the values with the new parameter(s).
Solution 2
In addition to Solution 1, also introduce a parallel API to add functional methods to create a copy of the instance while changing one or more parameters. Under the hood, they would essentially copy the Timecode
struct, then run the corresponding setter on the copy, then return that copy.
This would also be widely useful in scenarios where you receive an immutable Timecode
instance and you want to produce a copy with a value modified without needing to create an immutable var
copy first.
// parallel `func .settingX() → Timecode` methods to each existing `mutating func .setX()` method
// returns an instance copy if `.setTimecode(:String)` on the copy succeeds.
let tc2 = tc1.settingTimecode("01:20:15:10")
Alternatively, it could be a single method with multiple defaulted parameters.
/// Creates a new instance by copying the current instance with specified modifications.
/// If `nil` is passed, that parameter will be inherited from the current instance.
public func setting(rate: FrameRate? = nil,
limit: UpperLimit? = nil,
base: SubFramesBase? = nil,
format: StringFormat? = nil) -> Timecode
}
This could also inform a complete set of functional methods replicating all of Timecode
's inits.
/// Creates a new instance by copying the current instance with specified modifications.
/// If `nil` is passed, that parameter will be inherited from the current instance.
public func setting(_ exactly: FrameCount.Value,
at rate: FrameRate? = nil,
limit: UpperLimit? = nil,
base: SubFramesBase? = nil,
format: StringFormat? = nil) ->
/// Creates a new instance by copying the current instance with specified modifications.
/// If `nil` is passed, that parameter will be inherited from the current instance.
public func setting(_ exactly: FrameCount,
at rate: FrameRate? = nil,
limit: UpperLimit? = nil,
format: StringFormat? = nil) -> Timecode
/// Creates a new instance by copying the current instance with specified modifications.
/// If `nil` is passed, that parameter will be inherited from the current instance.
public func setting(_ exactly: Components,
at rate: FrameRate? = nil,
limit: UpperLimit? = nil,
base: SubFramesBase? = nil,
format: StringFormat? = nil) -> Timecode
/// Creates a new instance by copying the current instance with specified modifications.
/// If `nil` is passed, that parameter will be inherited from the current instance.
public func setting(_ exactly: String,
at rate: FrameRate? = nil,
limit: UpperLimit? = nil,
base: SubFramesBase? = nil,
format: StringFormat? = nil) -> Timecode
/// Creates a new instance by copying the current instance with specified modifications.
/// If `nil` is passed, that parameter will be inherited from the current instance.
public func setting(realTimeValue: TimeInterval,
at rate: FrameRate? = nil,
limit: UpperLimit? = nil,
base: SubFramesBase? = nil,
format: StringFormat? = nil) -> Timecode
/// Creates a new instance by copying the current instance with specified modifications.
/// If `nil` is passed, that parameter will be inherited from the current instance.
public func setting(samples: Double,
atSampleRate: Int? = nil,
at rate: FrameRate? = nil,
limit: UpperLimit? = nil,
base: SubFramesBase? = nil,
format: StringFormat? = nil) -> Timecode
It is also possible to add an initializer that takes another Timecode
instance as a parameter. Its properties would all be inherited except for non-nil
parameters.
extension Timecode {
public init(from: Timecode,
rate: FrameRate? = nil,
limit: UpperLimit? = nil,
base: SubFramesBase? = nil,
format: StringFormat? = nil) -> Timecode
}
Upon further reflection, the most common use case would likely be setting new time component values while retaining rate, limit, base and format.
In that case, each current initializer could receive an overload that takes a Timecode instance as a template.
let tcTemplate = Timecode(
at: ._29_97,
limit: ._100days
base: ._80SubFrames
format: [.showsSubFrames]
)
let tc2 = try Timecode("01:20:15:10", template: tcTemplate)
// tc2.frameRate == ._29_97
// tc2.limit == ._100days
// tc2.subFramesBase == ._80SubFrames
// tc2.stringFormat == [.showsSubFrames]
This is addressed in the forthcoming 2.0.0 release.
A new Timecode.Properties
struct that contains frameRate
, limit
, and base
can be passed to Timecode
initializers and various other methods.