Cog/Audio/Visualization/VisualizationController.swift
Christopher Snowhill 88f370ed91 Visualization: Tweak systems a bit
This should improve performance slightly. It's
still recommended to switch off SceneKit to
save CPU usage, or switch of vis entirely.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2025-02-01 15:08:25 -08:00

140 lines
3.3 KiB
Swift

//
// 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<Float>(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<Float>(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<Float>(repeating: 0.0, count: visAudioSize)
self.visAudioCursor = 0
}
}
}
@objc
func postVisPCM(_ inPCM: UnsafePointer<Float>?, 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..<amount {
let x = dataArray[i]
self.visAudio[j] = x
j += 1; if j >= 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<Float>?, visFFT: UnsafeMutablePointer<Float>?, latencyOffset: Double) {
outPCM?.update(repeating: 0.0, count: 4096)
visFFT?.update(repeating: 0.0, count: 2048)
if(self.visAudioSize == 0) {
return
}
var outPCMCopy = Array<Float>(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..<samplesToDo {
let x = self.visAudio[j]
outPCMCopy[i] = x
j += 1; if j >= 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)
}
}
}
}