// // VisualizationController.swift // CogAudio Framework // // Created by Christopher Snowhill on 6/30/22. // import Foundation @objc(VisualizationController) class VisualizationController : NSObject { var serialQueue = DispatchQueue(label: "Visualization Queue") var sampleRate = 0.0 var latency = 0.0 var visAudio: [Float] = Array(repeating: 0.0, count: 44100 * 45) var visAudioCursor = 0 var visAudioSize = 0 var visSamplesPosted: UInt64 = 0 private static var sharedVisualizationController: VisualizationController = { let visualizationController = VisualizationController() return visualizationController }() @objc class func sharedController() -> VisualizationController { return sharedVisualizationController } @objc func reset() { serialQueue.sync { self.latency = 0 self.visAudioSize = 44100 * 45 self.visAudio = Array(repeating: 0.0, count: visAudioSize) self.visSamplesPosted = 0 } } @objc func postLatency(_ latency: Double) { self.latency = latency } @objc func samplesPosted() -> UInt64 { return self.visSamplesPosted } @objc func postSampleRate(_ sampleRate: Double) { serialQueue.sync { if(self.sampleRate != sampleRate) { self.sampleRate = sampleRate self.visAudioSize = (Int)(sampleRate * 45.0) self.visAudio = Array(repeating: 0.0, count: visAudioSize) self.visAudioCursor = 0 } } } @objc func postVisPCM(_ inPCM: UnsafePointer?, amount: Int) { serialQueue.sync { if(self.visAudioSize == 0) { return } let bufferPointer = UnsafeRawPointer(inPCM) if let bptr = bufferPointer { let dataArray = bptr.assumingMemoryBound(to: Float.self) var j = self.visAudioCursor let k = self.visAudioSize for i in 0..= k { j = 0 } } self.visAudioCursor = j self.latency += Double(amount) / self.sampleRate self.visSamplesPosted += UInt64(amount) } } } @objc func readSampleRate() -> Double { serialQueue.sync { return self.sampleRate } } @objc func copyVisPCM(_ outPCM: UnsafeMutablePointer?, visFFT: UnsafeMutablePointer?, latencyOffset: Double) { outPCM?.update(repeating: 0.0, count: 4096) visFFT?.update(repeating: 0.0, count: 2048) if(self.visAudioSize == 0) { return } var outPCMCopy = Array(repeating: 0.0, count: 4096) serialQueue.sync { // Offset latency so the target sample is in the center of the window let latencySamples = (Int)((self.latency + latencyOffset) * self.sampleRate) + 2048 var samplesToDo = 4096 if(latencySamples < 0) { return } if(latencySamples + 4096 > visAudioSize) { return } if(latencySamples < 4096) { // Latency can sometimes dip below this threshold samplesToDo = latencySamples } var j = self.visAudioCursor - latencySamples let k = self.visAudioSize if j < 0 { j += k } for i in 0..= k { j = 0 } } if(samplesToDo < 4096) { for i in samplesToDo...4095 { outPCMCopy[i] = 0 } } } outPCM?.update(from: outPCMCopy, count: 4096) if(visFFT != nil) { serialQueue.sync { fft_calculate(outPCMCopy, visFFT, 2048) } } } }