Cog/Audio/Visualization/VisualizationController.swift
Christopher Snowhill 8e90da6292 Visualization: Improve latency and buffering appearance
Adjust the buffering so if latency is too low, we fill the rest of
the output with silence instead of peeking at the oldest part
of the buffer. Also increase latency by half a buffer size so
that the requested sample is in the center of the buffer, which
improves the 4096 sample situation with the current low
latency output.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2023-10-03 19:34:42 -07:00

137 lines
3.2 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(repeating: 0.0, count: 44100 * 45)
var visAudioCursor = 0
var visAudioSize = 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;
let amount = self.visAudioSize
for i in 0..<amount {
self.visAudio[i] = 0
}
}
}
@objc
func postLatency(_ latency: Double) {
self.latency = latency
}
@objc
func postSampleRate(_ sampleRate: Double) {
serialQueue.sync {
if(self.sampleRate != sampleRate) {
self.sampleRate = sampleRate
visAudioSize = (Int)(sampleRate * 45.0)
visAudio = Array(repeating: 0.0, count: visAudioSize)
visAudioCursor = 0
}
}
}
@objc
func postVisPCM(_ inPCM: UnsafePointer<Float>?, amount: Int) {
serialQueue.sync {
let bufferPointer = UnsafeBufferPointer<Float>(start: inPCM, count: amount)
var j = self.visAudioCursor
let k = self.visAudioSize
for i in 0..<amount {
let x = bufferPointer[i]
self.visAudio[j] = x
j += 1; if j >= k { j = 0 }
}
self.visAudioCursor = j
self.latency += Double(amount) / self.sampleRate
}
}
@objc
func readSampleRate() -> Double {
serialQueue.sync {
return self.sampleRate
}
}
@objc
func copyVisPCM(_ outPCM: UnsafeMutablePointer<Float>?, visFFT: UnsafeMutablePointer<Float>?, latencyOffset: Double) {
if(self.visAudioSize == 0) {
if(outPCM != nil) {
let pcmPointer = UnsafeMutableBufferPointer<Float>(start: outPCM, count: 4096)
for i in 0...4095 {
pcmPointer[i] = 0.0
}
}
if(visFFT != nil) {
let fftPointer = UnsafeMutableBufferPointer<Float>(start: visFFT, count: 2048)
for i in 0...2047 {
fftPointer[i] = 0.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 < 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
}
}
}
if(outPCM != nil) {
let pcmPointer = UnsafeMutableBufferPointer<Float>(start: outPCM, count: 4096)
for i in 0...4095 {
let x = outPCMCopy[i]
pcmPointer[i] = x
}
}
if(visFFT != nil) {
serialQueue.sync {
fft_calculate(outPCMCopy, visFFT, 2048)
}
}
}
}