Skip to content

Commit

Permalink
Internal refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
orchetect committed Sep 22, 2024
1 parent 02a4de2 commit 2c3652f
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 132 deletions.
153 changes: 21 additions & 132 deletions Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo TimeValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,144 +38,31 @@ extension ProTools.SessionInfo {
/// Export Session Text window while exporting. Pro Tools uses a PPQ base of 960 ticks per
/// quarter.
case feetAndFrames(feet: Int, frames: Int, subFrames: Int?)

/// Returns the corresponding ``TimeValueFormat`` case for the time value.
public var format: TimeValueFormat {
switch self {
case .timecode: return .timecode
case .minSecs: return .minSecs
case .samples: return .samples
case .barsAndBeats: return .barsAndBeats
case .feetAndFrames: return .feetAndFrames
}
}
}

public enum TimeValueFormat: Equatable, Hashable, CaseIterable, CustomStringConvertible {
/// Timecode at the project frame rate.
///
/// Pro Tools always uses a subframe base of 100 subframes per frame.
case timecode

/// Min:Secs time format.
/// This can either be `MM:SS` or `MM:SS.sss` (where `sss` is milliseconds).
case minSecs

/// Elapsed audio samples since the project start.
///
/// Refer to ``ProTools/SessionInfo/main-swift.property``.``ProTools/SessionInfo/Main-swift.struct/sampleRate``
/// for project sample rate.
case samples

/// Bars and Beats (musical).
/// Ticks (quarter note division) is only present when the _Show Subframes_ option is
/// enabled in Pro Tools' Export Session Text window while exporting. Pro Tools uses a PPQ
/// base of 960 ticks per quarter.
case barsAndBeats

/// Feet and Frames.
///
/// This can either be `FT:FR` or `FT:FR.sf` (where `sf` is subframes).
///
/// SubFrames is only present when the _Show Subframes_ option is enabled in Pro Tools'
/// Export Session Text window while exporting. Pro Tools uses a PPQ base of 960 ticks per
/// quarter.
case feetAndFrames

/// Returns human-readable name of the time value format type suitable for UI or debugging.
public var name: String {
switch self {
case .timecode: return "Timecode"
case .minSecs: return "Min:Secs"
case .samples: return "Samples"
case .barsAndBeats: return "Bars|Beats"
case .feetAndFrames: return "Feet+Frames"
}
}

public var description: String {
name
}
}
}

// MARK: - Internal Methods
extension ProTools.SessionInfo.TimeValue: Identifiable {
public var id: Self { self }
}

extension ProTools.SessionInfo.TimeValueFormat {
/// Employs a format detection heuristic to attempt to determine the time format of the given
/// time string.
/// This does not perform exhaustive validation on the values themselves, but matches against
/// expected formatting.
/// Returns `nil` if no matches can be ascertained.
init(heuristic source: String) throws {
// as a performance optimization, the formats here
// are ordered from most common to least common

if Self.isTimecode(source) {
self = .timecode
return
}
if Self.isMinSecs(source) {
self = .minSecs
return
}
if Self.isBarsAndBeats(source) {
self = .barsAndBeats
return
}
if Self.isSamples(source) {
self = .samples
return
}
if Self.isFeetAndFrames(source) {
self = .feetAndFrames
return
extension ProTools.SessionInfo.TimeValue: Sendable { }

extension ProTools.SessionInfo.TimeValue {
/// Returns the corresponding ``TimeValueFormat`` case for the time value.
public var format: ProTools.SessionInfo.TimeValueFormat {
switch self {
case .timecode: return .timecode
case .minSecs: return .minSecs
case .samples: return .samples
case .barsAndBeats: return .barsAndBeats
case .feetAndFrames: return .feetAndFrames
}

throw ProTools.SessionInfo.ParseError.general(
"Not a valid time value."
)
}

private static func isTimecode(
_ source: String
) -> Bool {
let regExPattern = #"^\d{2}:\d{2}:\d{2}[:|;]\d{2}(.\d{2}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isMinSecs(
_ source: String
) -> Bool {
let regExPattern = #"^(\d+):(\d{2})(.\d{3}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isSamples(
_ source: String
) -> Bool {
let regExPattern = #"^\d+$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isBarsAndBeats(
_ source: String
) -> Bool {
let regExPattern = #"^\d+\|\d+(\|[\s\d]{1}\d{3}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isFeetAndFrames(
_ source: String
) -> Bool {
let regExPattern = #"^(\d+)\+(\d{2})(.\d{2}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}
}

// MARK: - Umbrella Methods

extension ProTools.SessionInfo {
// MARK: - Umbrella Methods

/// Form a ``TimeValue`` instance from a time string with unknown format.
/// Employs a format detection heuristic to attempt to determine the time format of the given
/// time string.
Expand Down Expand Up @@ -214,9 +101,11 @@ extension ProTools.SessionInfo {
return try formTimeValue(feetAndFramesString: source)
}
}

// MARK: - Format-Specific Methods

}

// MARK: - Format-Specific Methods

extension ProTools.SessionInfo {
/// Form a ``TimeValue/timecode(_:)`` instance from timecode string.
/// Timecode is validated at the given frame rate and an error is thrown if invalid.
/// Ancillary timecode metadata is automatically derived from ``ProTools`` constants.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// SessionInfo TimeValue.swift
// DAWFileKit • https://github.com/orchetect/DAWFileKit
// © 2022 Steffan Andrews • Licensed under MIT License
//

import Foundation

extension ProTools.SessionInfo {
public enum TimeValueFormat: Equatable, Hashable, CaseIterable {
/// Timecode at the project frame rate.
///
/// Pro Tools always uses a subframe base of 100 subframes per frame.
case timecode

/// Min:Secs time format.
/// This can either be `MM:SS` or `MM:SS.sss` (where `sss` is milliseconds).
case minSecs

/// Elapsed audio samples since the project start.
///
/// Refer to ``ProTools/SessionInfo/main-swift.property``.``ProTools/SessionInfo/Main-swift.struct/sampleRate``
/// for project sample rate.
case samples

/// Bars and Beats (musical).
/// Ticks (quarter note division) is only present when the _Show Subframes_ option is
/// enabled in Pro Tools' Export Session Text window while exporting. Pro Tools uses a PPQ
/// base of 960 ticks per quarter.
case barsAndBeats

/// Feet and Frames.
///
/// This can either be `FT:FR` or `FT:FR.sf` (where `sf` is subframes).
///
/// SubFrames is only present when the _Show Subframes_ option is enabled in Pro Tools'
/// Export Session Text window while exporting. Pro Tools uses a PPQ base of 960 ticks per
/// quarter.
case feetAndFrames
}
}

extension ProTools.SessionInfo.TimeValueFormat: Identifiable {
public var id: Self { self }
}

extension ProTools.SessionInfo.TimeValueFormat: Sendable { }

extension ProTools.SessionInfo.TimeValueFormat: CustomStringConvertible {
public var description: String {
name
}
}

extension ProTools.SessionInfo.TimeValueFormat {
/// Returns human-readable name of the time value format type suitable for UI or debugging.
public var name: String {
switch self {
case .timecode: return "Timecode"
case .minSecs: return "Min:Secs"
case .samples: return "Samples"
case .barsAndBeats: return "Bars|Beats"
case .feetAndFrames: return "Feet+Frames"
}
}
}

// MARK: - Internal Methods

extension ProTools.SessionInfo.TimeValueFormat {
/// Employs a format detection heuristic to attempt to determine the time format of the given
/// time string.
/// This does not perform exhaustive validation on the values themselves, but matches against
/// expected formatting.
/// Returns `nil` if no matches can be ascertained.
init(heuristic source: String) throws {
// as a performance optimization, the formats here
// are ordered from most common to least common

if Self.isTimecode(source) {
self = .timecode
return
}
if Self.isMinSecs(source) {
self = .minSecs
return
}
if Self.isBarsAndBeats(source) {
self = .barsAndBeats
return
}
if Self.isSamples(source) {
self = .samples
return
}
if Self.isFeetAndFrames(source) {
self = .feetAndFrames
return
}

throw ProTools.SessionInfo.ParseError.general(
"Not a valid time value."
)
}

private static func isTimecode(
_ source: String
) -> Bool {
let regExPattern = #"^\d{2}:\d{2}:\d{2}[:|;]\d{2}(.\d{2}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isMinSecs(
_ source: String
) -> Bool {
let regExPattern = #"^(\d+):(\d{2})(.\d{3}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isSamples(
_ source: String
) -> Bool {
let regExPattern = #"^\d+$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isBarsAndBeats(
_ source: String
) -> Bool {
let regExPattern = #"^\d+\|\d+(\|[\s\d]{1}\d{3}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}

private static func isFeetAndFrames(
_ source: String
) -> Bool {
let regExPattern = #"^(\d+)\+(\d{2})(.\d{2}){0,1}$"#
return source.regexMatches(pattern: regExPattern).count == 1
}
}

0 comments on commit 2c3652f

Please sign in to comment.