2022-02-13 04:04:03 -03:00
|
|
|
//
|
|
|
|
// VisualizationController.m
|
|
|
|
// CogAudio Framework
|
|
|
|
//
|
|
|
|
// Created by Christopher Snowhill on 2/12/22.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "VisualizationController.h"
|
|
|
|
#import <Accelerate/Accelerate.h>
|
|
|
|
|
|
|
|
#import "fft.h"
|
|
|
|
|
2025-02-26 01:10:06 -03:00
|
|
|
@implementation VisualizationController {
|
|
|
|
double sampleRate;
|
|
|
|
double latency;
|
|
|
|
float *visAudio;
|
|
|
|
int visAudioCursor, visAudioSize;
|
|
|
|
uint64_t visSamplesPosted;
|
|
|
|
}
|
2022-02-13 04:04:03 -03:00
|
|
|
|
|
|
|
static VisualizationController *_sharedController = nil;
|
|
|
|
|
|
|
|
+ (VisualizationController *)sharedController {
|
|
|
|
@synchronized(self) {
|
|
|
|
if(!_sharedController) {
|
|
|
|
_sharedController = [[VisualizationController alloc] init];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return _sharedController;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)init {
|
|
|
|
self = [super init];
|
|
|
|
if(self) {
|
2022-06-24 02:17:31 -04:00
|
|
|
visAudio = NULL;
|
2025-02-26 01:10:06 -03:00
|
|
|
visAudioSize = 0;
|
2022-06-24 02:17:31 -04:00
|
|
|
latency = 0;
|
2022-02-13 04:04:03 -03:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
fft_free();
|
|
|
|
}
|
|
|
|
|
2025-02-26 01:10:06 -03:00
|
|
|
- (void)reset {
|
|
|
|
@synchronized (self) {
|
|
|
|
latency = 0;
|
|
|
|
visAudioCursor = 0;
|
|
|
|
visSamplesPosted = 0;
|
|
|
|
if(visAudio && visAudioSize) {
|
|
|
|
bzero(visAudio, sizeof(float) * visAudioSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-13 17:15:27 -03:00
|
|
|
- (void)postSampleRate:(double)sampleRate {
|
|
|
|
@synchronized(self) {
|
2022-06-24 02:17:31 -04:00
|
|
|
if(self->sampleRate != sampleRate) {
|
|
|
|
self->sampleRate = sampleRate;
|
2022-06-26 04:09:01 -04:00
|
|
|
int visAudioSize = (int)(sampleRate * 45.0);
|
2022-06-24 02:17:31 -04:00
|
|
|
void *visAudio = realloc(self->visAudio, visAudioSize * sizeof(float));
|
|
|
|
if(visAudio && visAudioSize) {
|
|
|
|
if(visAudioSize > self->visAudioSize) {
|
|
|
|
bzero(((float *)visAudio) + self->visAudioSize, sizeof(float) * (visAudioSize - self->visAudioSize));
|
|
|
|
}
|
|
|
|
self->visAudio = visAudio;
|
|
|
|
self->visAudioSize = visAudioSize;
|
|
|
|
visAudioCursor %= visAudioSize;
|
2025-02-26 01:10:06 -03:00
|
|
|
} else {
|
|
|
|
if(self->visAudio) {
|
|
|
|
free(self->visAudio);
|
|
|
|
self->visAudio = NULL;
|
|
|
|
}
|
|
|
|
self->visAudioSize = 0;
|
2022-06-24 02:17:31 -04:00
|
|
|
}
|
|
|
|
}
|
2022-02-13 17:15:27 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-17 02:54:55 -03:00
|
|
|
- (void)postVisPCM:(const float *)inPCM amount:(int)amount {
|
2022-02-13 04:04:03 -03:00
|
|
|
@synchronized(self) {
|
2025-02-26 01:10:06 -03:00
|
|
|
if(!visAudioSize) {
|
|
|
|
return;
|
|
|
|
}
|
2022-06-24 02:17:31 -04:00
|
|
|
int samplesRead = 0;
|
|
|
|
while(amount > 0) {
|
|
|
|
int amountToCopy = (int)(visAudioSize - visAudioCursor);
|
|
|
|
if(amountToCopy > amount) amountToCopy = amount;
|
|
|
|
cblas_scopy(amountToCopy, inPCM + samplesRead, 1, visAudio + visAudioCursor, 1);
|
|
|
|
visAudioCursor = visAudioCursor + amountToCopy;
|
|
|
|
if(visAudioCursor >= visAudioSize) visAudioCursor -= visAudioSize;
|
|
|
|
amount -= amountToCopy;
|
|
|
|
samplesRead += amountToCopy;
|
2025-02-26 01:10:06 -03:00
|
|
|
visSamplesPosted += amountToCopy;
|
2022-06-24 02:17:31 -04:00
|
|
|
}
|
2022-02-13 17:15:27 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:17:31 -04:00
|
|
|
- (void)postLatency:(double)latency {
|
|
|
|
self->latency = latency;
|
2022-06-26 04:09:01 -04:00
|
|
|
assert(latency < 45.0);
|
2022-06-24 02:17:31 -04:00
|
|
|
}
|
|
|
|
|
2022-02-13 17:15:27 -03:00
|
|
|
- (double)readSampleRate {
|
|
|
|
@synchronized(self) {
|
|
|
|
return sampleRate;
|
2022-02-13 04:04:03 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-26 01:10:06 -03:00
|
|
|
- (UInt64)samplesPosted {
|
|
|
|
return visSamplesPosted;
|
|
|
|
}
|
|
|
|
|
2025-03-13 23:47:35 -03:00
|
|
|
- (void)copyVisPCM:(float *_Nullable)outPCM visFFT:(float *_Nullable)outFFT latencyOffset:(double)latency {
|
2022-06-26 08:36:20 -04:00
|
|
|
if(!outPCM && !outFFT) return;
|
2022-07-11 17:39:23 -04:00
|
|
|
|
|
|
|
if(!visAudio || !visAudioSize) {
|
|
|
|
if(outPCM) bzero(outPCM, sizeof(float) * 4096);
|
|
|
|
if(outFFT) bzero(outFFT, sizeof(float) * 2048);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-02-26 01:10:06 -03:00
|
|
|
void *visAudioTemp = calloc(sizeof(float), 4096);
|
|
|
|
if(!visAudioTemp) {
|
|
|
|
if(outPCM) bzero(outPCM, sizeof(float) * 4096);
|
|
|
|
if(outFFT) bzero(outFFT, sizeof(float) * 2048);
|
|
|
|
return;
|
|
|
|
}
|
2022-07-11 17:39:23 -04:00
|
|
|
|
2022-02-13 04:04:03 -03:00
|
|
|
@synchronized(self) {
|
2022-06-24 02:17:31 -04:00
|
|
|
if(!sampleRate) {
|
|
|
|
bzero(outPCM, 4096 * sizeof(float));
|
2022-06-26 08:36:20 -04:00
|
|
|
if(outFFT) {
|
|
|
|
bzero(outFFT, 2048 * sizeof(float));
|
|
|
|
}
|
2022-06-24 02:17:31 -04:00
|
|
|
return;
|
|
|
|
}
|
2025-02-26 01:10:06 -03:00
|
|
|
int latencySamples = (int)(sampleRate * (self->latency + latency)) + 2048;
|
2022-06-26 08:36:20 -04:00
|
|
|
if(latencySamples < 4096) latencySamples = 4096;
|
2022-06-24 02:17:31 -04:00
|
|
|
int readCursor = visAudioCursor - latencySamples;
|
|
|
|
int samples = 4096;
|
|
|
|
int samplesRead = 0;
|
2025-02-26 01:10:06 -03:00
|
|
|
if(latencySamples + samples > visAudioSize) {
|
|
|
|
samples = (int)(visAudioSize - latencySamples);
|
|
|
|
}
|
2022-06-26 08:36:20 -04:00
|
|
|
while(readCursor < 0)
|
2022-06-24 02:17:31 -04:00
|
|
|
readCursor += visAudioSize;
|
2022-06-26 08:36:20 -04:00
|
|
|
while(readCursor >= visAudioSize)
|
2022-06-24 02:17:31 -04:00
|
|
|
readCursor -= visAudioSize;
|
|
|
|
while(samples > 0) {
|
|
|
|
int samplesToRead = (int)(visAudioSize - readCursor);
|
|
|
|
if(samplesToRead > samples) samplesToRead = samples;
|
2025-02-26 01:10:06 -03:00
|
|
|
cblas_scopy(samplesToRead, visAudio + readCursor, 1, visAudioTemp + samplesRead, 1);
|
2022-06-24 02:17:31 -04:00
|
|
|
samplesRead += samplesToRead;
|
|
|
|
readCursor += samplesToRead;
|
|
|
|
samples -= samplesToRead;
|
|
|
|
if(readCursor >= visAudioSize) readCursor -= visAudioSize;
|
|
|
|
}
|
2022-02-13 04:04:03 -03:00
|
|
|
}
|
2025-02-26 01:10:06 -03:00
|
|
|
if(outPCM) {
|
|
|
|
cblas_scopy(4096, visAudioTemp, 1, outPCM, 1);
|
|
|
|
}
|
2022-06-26 08:36:20 -04:00
|
|
|
if(outFFT) {
|
2025-02-26 01:10:06 -03:00
|
|
|
fft_calculate(visAudioTemp, outFFT, 2048);
|
2022-06-26 08:36:20 -04:00
|
|
|
}
|
2025-02-26 01:10:06 -03:00
|
|
|
|
|
|
|
free(visAudioTemp);
|
2022-02-13 04:04:03 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|