|
| 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