Skip to content

Commit 0e08f8c

Browse files
authored
Merge pull request #46 from rorp/master
Pluggable signal tracker
2 parents e7c480a + 266385e commit 0e08f8c

7 files changed

Lines changed: 136 additions & 17 deletions

File tree

Beethoven.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
D5A49C751C343EC300427BF8 /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5A49C731C343EC300427BF8 /* Quick.framework */; };
4343
D5A49C761C343EC300427BF8 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5A49C741C343EC300427BF8 /* Nimble.framework */; };
4444
D5A49CDE1C3458EA00427BF8 /* Pitchy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5A49CDD1C3458EA00427BF8 /* Pitchy.framework */; };
45+
E0BF88FD1E85C31A00079F64 /* SimulatorSignalTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0BF88FC1E85C31A00079F64 /* SimulatorSignalTracker.swift */; };
4546
/* End PBXBuildFile section */
4647

4748
/* Begin PBXContainerItemProxy section */
@@ -93,6 +94,7 @@
9394
D5A49C731C343EC300427BF8 /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = "<group>"; };
9495
D5A49C741C343EC300427BF8 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = "<group>"; };
9596
D5A49CDD1C3458EA00427BF8 /* Pitchy.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pitchy.framework; path = Carthage/Build/iOS/Pitchy.framework; sourceTree = "<group>"; };
97+
E0BF88FC1E85C31A00079F64 /* SimulatorSignalTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimulatorSignalTracker.swift; sourceTree = "<group>"; };
9698
/* End PBXFileReference section */
9799

98100
/* Begin PBXFrameworksBuildPhase section */
@@ -290,6 +292,7 @@
290292
D59CB14D1C345E0A00290B63 /* Units */ = {
291293
isa = PBXGroup;
292294
children = (
295+
E0BF88FC1E85C31A00079F64 /* SimulatorSignalTracker.swift */,
293296
D59CB14E1C345E0A00290B63 /* InputSignalTracker.swift */,
294297
D59CB14F1C345E0A00290B63 /* OutputSignalTracker.swift */,
295298
);
@@ -492,6 +495,7 @@
492495
D5843EE41DBD2D290077F86E /* YINUtil.swift in Sources */,
493496
D59CB1611C345E0A00290B63 /* MaxValueEstimator.swift in Sources */,
494497
D59CB15F1C345E0A00290B63 /* HPSEstimator.swift in Sources */,
498+
E0BF88FD1E85C31A00079F64 /* SimulatorSignalTracker.swift in Sources */,
495499
D59CB16B1C345E0A00290B63 /* SimpleTransformer.swift in Sources */,
496500
D5843EE21DBD2D110077F86E /* Array+Extensions.swift in Sources */,
497501
D59CB1571C345E0A00290B63 /* Config.swift in Sources */,

Cartfile.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
github "Quick/Nimble" "v5.1.0"
2-
github "Quick/Quick" "v0.10.0"
1+
github "Quick/Nimble" "v6.0.1"
2+
github "Quick/Quick" "v1.1.0"
33
github "vadymmarkov/Pitchy" "2.0.1"

Source/PitchEngine.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ public enum PitchEngineError: Error {
1414

1515
public class PitchEngine {
1616

17-
public enum Mode {
18-
case record, playback
19-
}
20-
2117
public let bufferSize: AVAudioFrameCount
2218
public var active = false
2319
public weak var delegate: PitchEngineDelegate?
@@ -26,8 +22,8 @@ public class PitchEngine {
2622
fileprivate var signalTracker: SignalTracker
2723
fileprivate var queue: DispatchQueue
2824

29-
public var mode: Mode {
30-
return signalTracker is InputSignalTracker ? .record : .playback
25+
public var mode: SignalTrackerMode {
26+
return signalTracker.mode
3127
}
3228

3329
public var levelThreshold: Float? {
@@ -45,18 +41,22 @@ public class PitchEngine {
4541

4642
// MARK: - Initialization
4743

48-
public init(config: Config = Config(), delegate: PitchEngineDelegate? = nil) {
44+
public init(config: Config = Config(), signalTracker: SignalTracker? = nil, delegate: PitchEngineDelegate? = nil) {
4945
bufferSize = config.bufferSize
5046
estimator = EstimationFactory.create(config.estimationStrategy)
5147

52-
if let audioUrl = config.audioUrl {
53-
signalTracker = OutputSignalTracker(audioUrl: audioUrl, bufferSize: bufferSize)
48+
if let signalTracker = signalTracker {
49+
self.signalTracker = signalTracker
5450
} else {
55-
signalTracker = InputSignalTracker(bufferSize: bufferSize)
51+
if let audioUrl = config.audioUrl {
52+
self.signalTracker = OutputSignalTracker(audioUrl: audioUrl, bufferSize: bufferSize)
53+
} else {
54+
self.signalTracker = InputSignalTracker(bufferSize: bufferSize)
55+
}
5656
}
5757

5858
queue = DispatchQueue(label: "BeethovenQueue", attributes: [])
59-
signalTracker.delegate = self
59+
self.signalTracker.delegate = self
6060
self.delegate = delegate
6161
}
6262

@@ -117,7 +117,7 @@ public class PitchEngine {
117117

118118
extension PitchEngine: SignalTrackerDelegate {
119119

120-
func signalTracker(_ signalTracker: SignalTracker,
120+
public func signalTracker(_ signalTracker: SignalTracker,
121121
didReceiveBuffer buffer: AVAudioPCMBuffer, atTime time: AVAudioTime) {
122122
queue.async { [weak self] in
123123
guard let weakSelf = self else { return }
@@ -140,7 +140,7 @@ extension PitchEngine: SignalTrackerDelegate {
140140
}
141141
}
142142

143-
func signalTrackerWentBelowLevelThreshold(_ signalTracker: SignalTracker) {
143+
public func signalTrackerWentBelowLevelThreshold(_ signalTracker: SignalTracker) {
144144
DispatchQueue.main.async {
145145
self.delegate?.pitchEngineWentBelowLevelThreshold(self)
146146
}

Source/SignalTracking/SignalTracker.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import AVFoundation
22

3-
protocol SignalTrackerDelegate: class {
3+
public protocol SignalTrackerDelegate: class {
44
func signalTracker(_ signalTracker: SignalTracker,
55
didReceiveBuffer buffer: AVAudioPCMBuffer,
66
atTime time: AVAudioTime)
77

88
func signalTrackerWentBelowLevelThreshold(_ signalTracker: SignalTracker)
99
}
1010

11-
protocol SignalTracker: class {
11+
public enum SignalTrackerMode {
12+
case record, playback
13+
}
14+
15+
public protocol SignalTracker: class {
16+
var mode: SignalTrackerMode { get }
1217
var levelThreshold: Float? { get set }
1318
var peakLevel: Float? { get }
1419
var averageLevel: Float? { get }

Source/SignalTracking/Units/InputSignalTracker.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ class InputSignalTracker: SignalTracker {
2828
}
2929
}
3030

31+
var mode: SignalTrackerMode {
32+
get { return .record }
33+
}
34+
3135
// MARK: - Initialization
3236

3337
required init(bufferSize: AVAudioFrameCount = 2048,

Source/SignalTracking/Units/OutputSignalTracker.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ class OutputSignalTracker: SignalTracker {
1919
var averageLevel: Float? {
2020
get { return 0.0 }
2121
}
22+
23+
var mode: SignalTrackerMode {
24+
get { return .playback }
25+
}
2226

2327
// MARK: - Initialization
2428

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import AVFoundation
2+
3+
/*
4+
* A mock implemamtation of SignalTracker useful for unit testing and/or running in the simulator.
5+
*
6+
* It creates a series of PCM buffers filled with sine waves of given frequencies,
7+
* and passes the buffers to the delegate every delayMs milliseconds.
8+
*
9+
* Example:
10+
*
11+
* #if (arch(i386) || arch(x86_64)) && os(iOS)
12+
* // Simulator
13+
* let frequencies = try? [
14+
* 391.995435981749,
15+
* 391.995435981749,
16+
* 415.304697579945,
17+
* Note(letter: Note.Letter.A, octave: 4).frequency,
18+
* 466.163761518090,
19+
* 466.163761518090,
20+
* Note(letter: Note.Letter.A, octave: 4).frequency,
21+
* 415.304697579945,
22+
* 391.995435981749
23+
* ]
24+
* let signalTracker = SimulatorSignalTracker(frequencies: frequencies, delayMs: 1000)
25+
* let pitchEngine = PitchEngine(config: config, signalTracker: signalTracker, delegate: delegate)
26+
* #else
27+
* // Device
28+
* let pitchEngine = PitchEngine(config: config, delegate: delegate)
29+
* #endif
30+
*
31+
*/
32+
public class SimulatorSignalTracker: SignalTracker {
33+
34+
public var mode: SignalTrackerMode = .record
35+
36+
public var levelThreshold: Float?
37+
38+
public var peakLevel: Float?
39+
40+
public var averageLevel: Float?
41+
42+
public weak var delegate: SignalTrackerDelegate?
43+
44+
private var frequencies: [Double]?
45+
private var delay: Int
46+
47+
private static let sampleRate = 8000.0
48+
private static let sampleCount = 1024
49+
50+
public init(delegate: SignalTrackerDelegate? = nil, frequencies: [Double]? = nil, delayMs: Int = 0) {
51+
self.delegate = delegate
52+
self.frequencies = frequencies
53+
self.delay = delayMs
54+
}
55+
56+
public func start() throws {
57+
guard let frequencies = self.frequencies else { return }
58+
59+
let time = AVAudioTime(sampleTime: 0, atRate: SimulatorSignalTracker.sampleRate)
60+
61+
var i = 0
62+
63+
for frequency in frequencies {
64+
let buffer = createPCMBuffer(frequency)
65+
if i == 0 {
66+
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50), execute: {
67+
self.delegate?.signalTracker(self, didReceiveBuffer: buffer, atTime: time)
68+
})
69+
70+
} else {
71+
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delay * i), execute: {
72+
self.delegate?.signalTracker(self, didReceiveBuffer: buffer, atTime: time)
73+
})
74+
}
75+
i += 1
76+
}
77+
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delay * i), execute: {
78+
self.delegate?.signalTrackerWentBelowLevelThreshold(self)
79+
})
80+
}
81+
82+
public func stop() {
83+
}
84+
85+
private func createPCMBuffer(_ frequency: Double) -> AVAudioPCMBuffer {
86+
let format = AVAudioFormat(standardFormatWithSampleRate: SimulatorSignalTracker.sampleRate, channels: 1)
87+
let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(SimulatorSignalTracker.sampleCount))
88+
89+
if let channelData = buffer.floatChannelData {
90+
let velocity = Float32(2.0 * M_PI * frequency / SimulatorSignalTracker.sampleRate)
91+
92+
for i in 0..<SimulatorSignalTracker.sampleCount {
93+
let sample: Float32 = sin(velocity * Float32(i))
94+
channelData[0][i] = sample
95+
}
96+
97+
buffer.frameLength = buffer.frameCapacity
98+
}
99+
100+
return buffer
101+
}
102+
}

0 commit comments

Comments
 (0)