[HRIR Convolver] Rewrite to use PFFFT float
Replace overlap-add vDSP/Accelerate implementation with a faster PFFFT overlap-save implementation, using fewer FFT steps as well. Signed-off-by: Christopher Snowhill <kode54@gmail.com>swiftingly
parent
18af8a06df
commit
c208f60da4
|
@ -16,7 +16,13 @@
|
||||||
#import <CoreAudio/CoreAudio.h>
|
#import <CoreAudio/CoreAudio.h>
|
||||||
#import <CoreAudio/CoreAudioTypes.h>
|
#import <CoreAudio/CoreAudioTypes.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#import <atomic>
|
||||||
|
using std::atomic_int;
|
||||||
|
using std::atomic_bool;
|
||||||
|
#else
|
||||||
#import <stdatomic.h>
|
#import <stdatomic.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
@class BufferChain;
|
@class BufferChain;
|
||||||
@class OutputNode;
|
@class OutputNode;
|
||||||
|
|
|
@ -8,36 +8,28 @@
|
||||||
#ifndef HeadphoneFilter_h
|
#ifndef HeadphoneFilter_h
|
||||||
#define HeadphoneFilter_h
|
#define HeadphoneFilter_h
|
||||||
|
|
||||||
#import <Accelerate/Accelerate.h>
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#import "pffft.h"
|
||||||
|
|
||||||
@interface HeadphoneFilter : NSObject {
|
@interface HeadphoneFilter : NSObject {
|
||||||
FFTSetup fftSetup;
|
PFFFT_Setup *fftSetup;
|
||||||
|
|
||||||
size_t fftSize;
|
size_t fftSize;
|
||||||
size_t fftSizeOver2;
|
|
||||||
size_t log2n;
|
|
||||||
size_t log2nhalf;
|
|
||||||
size_t bufferSize;
|
size_t bufferSize;
|
||||||
size_t paddedBufferSize;
|
size_t paddedBufferSize;
|
||||||
size_t channelCount;
|
size_t channelCount;
|
||||||
|
|
||||||
COMPLEX_SPLIT signal_fft;
|
float *workBuffer;
|
||||||
COMPLEX_SPLIT input_filtered_signal_per_channel[2];
|
|
||||||
COMPLEX_SPLIT *impulse_responses;
|
float **impulse_responses;
|
||||||
|
|
||||||
|
float **prevInputs;
|
||||||
|
|
||||||
float *left_result;
|
float *left_result;
|
||||||
float *right_result;
|
float *right_result;
|
||||||
|
|
||||||
float *left_mix_result;
|
|
||||||
float *right_mix_result;
|
|
||||||
|
|
||||||
float *paddedSignal;
|
float *paddedSignal;
|
||||||
|
|
||||||
float *prevOverlapLeft;
|
|
||||||
float *prevOverlapRight;
|
|
||||||
|
|
||||||
int prevOverlapLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)validateImpulseFile:(NSURL *)url;
|
+ (BOOL)validateImpulseFile:(NSURL *)url;
|
||||||
|
|
|
@ -17,14 +17,7 @@
|
||||||
#import "lpc.h"
|
#import "lpc.h"
|
||||||
#import "util.h"
|
#import "util.h"
|
||||||
|
|
||||||
// Apparently _mm_malloc is Intel-only on newer macOS targets, so use supported posix_memalign
|
#import "pffft_double.h"
|
||||||
static void *_memalign_malloc(size_t size, size_t align) {
|
|
||||||
void *ret = NULL;
|
|
||||||
if(posix_memalign(&ret, align, size) != 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@implementation HeadphoneFilter
|
@implementation HeadphoneFilter
|
||||||
|
|
||||||
|
@ -155,7 +148,7 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
|
|
||||||
NSDictionary *properties = [decoder properties];
|
NSDictionary *properties = [decoder properties];
|
||||||
|
|
||||||
double sampleRateOfSource = [[properties objectForKey:@"sampleRate"] floatValue];
|
double sampleRateOfSource = [[properties objectForKey:@"sampleRate"] doubleValue];
|
||||||
|
|
||||||
int sampleCount = [[properties objectForKey:@"totalFrames"] intValue];
|
int sampleCount = [[properties objectForKey:@"totalFrames"] intValue];
|
||||||
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
int impulseChannels = [[properties objectForKey:@"channels"] intValue];
|
||||||
|
@ -172,7 +165,7 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
float *impulseBuffer = (float *)malloc(sampleCount * sizeof(float) * impulseChannels);
|
float *impulseBuffer = (float *)pffft_aligned_malloc(sampleCount * sizeof(float) * impulseChannels);
|
||||||
if(!impulseBuffer) {
|
if(!impulseBuffer) {
|
||||||
[decoder close];
|
[decoder close];
|
||||||
decoder = nil;
|
decoder = nil;
|
||||||
|
@ -182,6 +175,7 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if([decoder readAudio:impulseBuffer frames:sampleCount] != sampleCount) {
|
if([decoder readAudio:impulseBuffer frames:sampleCount] != sampleCount) {
|
||||||
|
pffft_aligned_free(impulseBuffer);
|
||||||
[decoder close];
|
[decoder close];
|
||||||
decoder = nil;
|
decoder = nil;
|
||||||
[source close];
|
[source close];
|
||||||
|
@ -212,19 +206,18 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
int resamplerLatencyIn = (int)N_samples_to_add_;
|
int resamplerLatencyIn = (int)N_samples_to_add_;
|
||||||
int resamplerLatencyOut = (int)N_samples_to_drop_;
|
int resamplerLatencyOut = (int)N_samples_to_drop_;
|
||||||
|
|
||||||
float *tempImpulse = (float *)realloc(impulseBuffer, (sampleCount + resamplerLatencyIn * 2 + 1024) * sizeof(float) * impulseChannels);
|
float *tempImpulse = (float *)pffft_aligned_malloc((sampleCount + resamplerLatencyIn * 2 + 1024) * sizeof(float) * impulseChannels);
|
||||||
if(!tempImpulse) {
|
if(!tempImpulse) {
|
||||||
free(impulseBuffer);
|
pffft_aligned_free(impulseBuffer);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
impulseBuffer = tempImpulse;
|
|
||||||
|
|
||||||
resampledCount += resamplerLatencyOut * 2 + 1024;
|
resampledCount += resamplerLatencyOut * 2 + 1024;
|
||||||
|
|
||||||
float *resampledImpulse = (float *)malloc(resampledCount * sizeof(float) * impulseChannels);
|
float *resampledImpulse = (float *)pffft_aligned_malloc(resampledCount * sizeof(float) * impulseChannels);
|
||||||
if(!resampledImpulse) {
|
if(!resampledImpulse) {
|
||||||
free(impulseBuffer);
|
pffft_aligned_free(impulseBuffer);
|
||||||
|
pffft_aligned_free(tempImpulse);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,16 +226,16 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
void *extrapolate_buffer = NULL;
|
void *extrapolate_buffer = NULL;
|
||||||
size_t extrapolate_buffer_size = 0;
|
size_t extrapolate_buffer_size = 0;
|
||||||
|
|
||||||
memmove(impulseBuffer + resamplerLatencyIn * impulseChannels, impulseBuffer, sampleCount * sizeof(float) * impulseChannels);
|
memcpy(tempImpulse + resamplerLatencyIn * impulseChannels, impulseBuffer, sampleCount * sizeof(float) * impulseChannels);
|
||||||
lpc_extrapolate_bkwd(impulseBuffer + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
lpc_extrapolate_bkwd(tempImpulse + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||||
lpc_extrapolate_fwd(impulseBuffer + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
lpc_extrapolate_fwd(tempImpulse + N_samples_to_add_ * impulseChannels, sampleCount, prime, impulseChannels, LPC_ORDER, N_samples_to_add_, &extrapolate_buffer, &extrapolate_buffer_size);
|
||||||
free(extrapolate_buffer);
|
free(extrapolate_buffer);
|
||||||
|
|
||||||
size_t inputDone = 0;
|
size_t inputDone = 0;
|
||||||
size_t outputDone = 0;
|
size_t outputDone = 0;
|
||||||
|
|
||||||
outputDone = _r8bstate->resample(impulseBuffer, sampleCount + N_samples_to_add_ * 2, &inputDone, resampledImpulse, resampledCount);
|
outputDone = _r8bstate->resample(tempImpulse, sampleCount + N_samples_to_add_ * 2, &inputDone, resampledImpulse, resampledCount);
|
||||||
|
|
||||||
if (outputDone < resampledCount) {
|
if (outputDone < resampledCount) {
|
||||||
outputDone += _r8bstate->flush(resampledImpulse + outputDone * impulseChannels, resampledCount - outputDone);
|
outputDone += _r8bstate->flush(resampledImpulse + outputDone * impulseChannels, resampledCount - outputDone);
|
||||||
}
|
}
|
||||||
|
@ -253,7 +246,8 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
|
|
||||||
memmove(resampledImpulse, resampledImpulse + N_samples_to_drop_ * impulseChannels, outputDone * sizeof(float) * impulseChannels);
|
memmove(resampledImpulse, resampledImpulse + N_samples_to_drop_ * impulseChannels, outputDone * sizeof(float) * impulseChannels);
|
||||||
|
|
||||||
free(impulseBuffer);
|
pffft_aligned_free(tempImpulse);
|
||||||
|
pffft_aligned_free(impulseBuffer);
|
||||||
impulseBuffer = resampledImpulse;
|
impulseBuffer = resampledImpulse;
|
||||||
sampleCount = (int)outputDone;
|
sampleCount = (int)outputDone;
|
||||||
|
|
||||||
|
@ -267,16 +261,11 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
bufferSize = 512;
|
bufferSize = 512;
|
||||||
fftSize = sampleCount + bufferSize;
|
fftSize = sampleCount + bufferSize;
|
||||||
|
|
||||||
int pow = 1;
|
fftSize = (size_t)pffftd_next_power_of_two((int)fftSize);
|
||||||
while(fftSize > 2) {
|
|
||||||
pow++;
|
|
||||||
fftSize /= 2;
|
|
||||||
}
|
|
||||||
fftSize = 2 << pow;
|
|
||||||
|
|
||||||
float *deinterleavedImpulseBuffer = (float *)_memalign_malloc(fftSize * sizeof(float) * (impulseChannels + 1), 16);
|
float *deinterleavedImpulseBuffer = (float *)pffft_aligned_malloc(fftSize * sizeof(float) * impulseChannels);
|
||||||
if(!deinterleavedImpulseBuffer) {
|
if(!deinterleavedImpulseBuffer) {
|
||||||
free(impulseBuffer);
|
pffft_aligned_free(impulseBuffer);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,66 +274,40 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize + sampleCount, 1, fftSize - sampleCount);
|
vDSP_vclr(deinterleavedImpulseBuffer + i * fftSize + sampleCount, 1, fftSize - sampleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(impulseBuffer);
|
pffft_aligned_free(impulseBuffer);
|
||||||
|
|
||||||
// Null impulse
|
|
||||||
vDSP_vclr(deinterleavedImpulseBuffer + impulseChannels * fftSize, 1, fftSize);
|
|
||||||
|
|
||||||
paddedBufferSize = fftSize;
|
paddedBufferSize = fftSize;
|
||||||
fftSizeOver2 = (fftSize + 1) / 2;
|
|
||||||
log2n = log2f(fftSize);
|
|
||||||
log2nhalf = log2n / 2;
|
|
||||||
|
|
||||||
fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
|
fftSetup = pffft_new_setup((int)fftSize, PFFFT_REAL);
|
||||||
if(!fftSetup) {
|
if(!fftSetup) {
|
||||||
free(deinterleavedImpulseBuffer);
|
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
paddedSignal = (float *)_memalign_malloc(sizeof(float) * paddedBufferSize, 16);
|
workBuffer = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||||
|
if(!workBuffer) {
|
||||||
|
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
paddedSignal = (float *)pffft_aligned_malloc(sizeof(float) * paddedBufferSize);
|
||||||
if(!paddedSignal) {
|
if(!paddedSignal) {
|
||||||
free(deinterleavedImpulseBuffer);
|
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
signal_fft.realp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
impulse_responses = (float **)calloc(sizeof(float *), channels * 2);
|
||||||
signal_fft.imagp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
|
||||||
if(!signal_fft.realp || !signal_fft.imagp) {
|
|
||||||
free(deinterleavedImpulseBuffer);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
input_filtered_signal_per_channel[0].realp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
|
||||||
input_filtered_signal_per_channel[0].imagp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
|
||||||
if(!input_filtered_signal_per_channel[0].realp ||
|
|
||||||
!input_filtered_signal_per_channel[0].imagp) {
|
|
||||||
free(deinterleavedImpulseBuffer);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
input_filtered_signal_per_channel[1].realp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
|
||||||
input_filtered_signal_per_channel[1].imagp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
|
||||||
if(!input_filtered_signal_per_channel[1].realp ||
|
|
||||||
!input_filtered_signal_per_channel[1].imagp) {
|
|
||||||
free(deinterleavedImpulseBuffer);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
impulse_responses = (COMPLEX_SPLIT *)calloc(sizeof(COMPLEX_SPLIT), channels * 2);
|
|
||||||
if(!impulse_responses) {
|
if(!impulse_responses) {
|
||||||
free(deinterleavedImpulseBuffer);
|
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(size_t i = 0; i < channels; ++i) {
|
for(size_t i = 0; i < channels; ++i) {
|
||||||
impulse_responses[i * 2 + 0].realp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
impulse_responses[i * 2 + 0] = (float *)pffft_aligned_malloc(sizeof(float) * fftSize * 2);
|
||||||
impulse_responses[i * 2 + 0].imagp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
impulse_responses[i * 2 + 1] = (float *)pffft_aligned_malloc(sizeof(float) * fftSize * 2);
|
||||||
impulse_responses[i * 2 + 1].realp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
|
||||||
impulse_responses[i * 2 + 1].imagp = (float *)_memalign_malloc(sizeof(float) * fftSizeOver2, 16);
|
|
||||||
|
|
||||||
if(!impulse_responses[i * 2 + 0].realp || !impulse_responses[i * 2 + 0].imagp ||
|
if(!impulse_responses[i * 2 + 0] || !impulse_responses[i * 2 + 1]) {
|
||||||
!impulse_responses[i * 2 + 1].realp || !impulse_responses[i * 2 + 1].imagp) {
|
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||||
free(deinterleavedImpulseBuffer);
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,167 +330,106 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(leftInChannel == speaker_is_back_center || rightInChannel == speaker_is_back_center) {
|
if(leftInChannel == speaker_is_back_center || rightInChannel == speaker_is_back_center) {
|
||||||
float *temp;
|
|
||||||
if(impulseChannels == 7) {
|
if(impulseChannels == 7) {
|
||||||
temp = (float *)malloc(sizeof(float) * fftSize);
|
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, impulse_responses[i * 2 + 0], 1);
|
||||||
if(!temp) {
|
vDSP_vadd(impulse_responses[i * 2 + 0], 1, deinterleavedImpulseBuffer + 5 * fftSize, 1, impulse_responses[i * 2 + 0], 1, fftSize);
|
||||||
free(deinterleavedImpulseBuffer);
|
cblas_scopy((int)fftSize, impulse_responses[i * 2 + 0], 1, impulse_responses[i * 2 + 1], 1);
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
|
||||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp, 1, fftSize);
|
|
||||||
|
|
||||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
|
||||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
|
||||||
} else {
|
} else {
|
||||||
temp = (float *)malloc(sizeof(float) * fftSize * 2);
|
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, impulse_responses[i * 2 + 0], 1);
|
||||||
if(!temp) {
|
vDSP_vadd(impulse_responses[i * 2 + 0], 1, deinterleavedImpulseBuffer + 12 * fftSize, 1, impulse_responses[i * 2 + 0], 1, fftSize);
|
||||||
free(deinterleavedImpulseBuffer);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 4 * fftSize, 1, temp, 1);
|
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 5 * fftSize, 1, impulse_responses[i * 2 + 1], 1);
|
||||||
vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 12 * fftSize, 1, temp, 1, fftSize);
|
vDSP_vadd(impulse_responses[i * 2 + 1], 1, deinterleavedImpulseBuffer + 11 * fftSize, 1, impulse_responses[i * 2 + 1], 1, fftSize);
|
||||||
|
|
||||||
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + 5 * fftSize, 1, temp + fftSize, 1);
|
|
||||||
vDSP_vadd(temp + fftSize, 1, deinterleavedImpulseBuffer + 11 * fftSize, 1, temp + fftSize, 1, fftSize);
|
|
||||||
|
|
||||||
vDSP_ctoz((DSPComplex *)temp, 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
|
||||||
vDSP_ctoz((DSPComplex *)(temp + fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free(temp);
|
|
||||||
} else if(leftInChannel == speaker_not_present || rightInChannel == speaker_not_present) {
|
} else if(leftInChannel == speaker_not_present || rightInChannel == speaker_not_present) {
|
||||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + impulseChannels * fftSize), 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
vDSP_vclr(impulse_responses[i * 2 + 0], 1, fftSize);
|
||||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + impulseChannels * fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
vDSP_vclr(impulse_responses[i * 2 + 1], 1, fftSize);
|
||||||
} else {
|
} else {
|
||||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + leftInChannel * fftSize), 2, &impulse_responses[i * 2 + 0], 1, fftSizeOver2);
|
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + leftInChannel * fftSize, 1, impulse_responses[i * 2 + 0], 1);
|
||||||
vDSP_ctoz((DSPComplex *)(deinterleavedImpulseBuffer + rightInChannel * fftSize), 2, &impulse_responses[i * 2 + 1], 1, fftSizeOver2);
|
cblas_scopy((int)fftSize, deinterleavedImpulseBuffer + rightInChannel * fftSize, 1, impulse_responses[i * 2 + 1], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
vDSP_fft_zrip(fftSetup, &impulse_responses[i * 2 + 0], 1, log2n, FFT_FORWARD);
|
pffft_transform(fftSetup, impulse_responses[i * 2 + 0], impulse_responses[i * 2 + 0], workBuffer, PFFFT_FORWARD);
|
||||||
vDSP_fft_zrip(fftSetup, &impulse_responses[i * 2 + 1], 1, log2n, FFT_FORWARD);
|
pffft_transform(fftSetup, impulse_responses[i * 2 + 1], impulse_responses[i * 2 + 1], workBuffer, PFFFT_FORWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(deinterleavedImpulseBuffer);
|
pffft_aligned_free(deinterleavedImpulseBuffer);
|
||||||
|
|
||||||
left_result = (float *)_memalign_malloc(sizeof(float) * fftSize, 16);
|
left_result = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||||
right_result = (float *)_memalign_malloc(sizeof(float) * fftSize, 16);
|
right_result = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||||
if(!left_result || !right_result)
|
if(!left_result || !right_result)
|
||||||
return nil;
|
return nil;
|
||||||
|
|
||||||
prevOverlapLeft = (float *)_memalign_malloc(sizeof(float) * fftSize, 16);
|
prevInputs = (float **)calloc(sizeof(float *), channels);
|
||||||
prevOverlapRight = (float *)_memalign_malloc(sizeof(float) * fftSize, 16);
|
if(!prevInputs) {
|
||||||
if(!prevOverlapLeft || !prevOverlapRight)
|
|
||||||
return nil;
|
return nil;
|
||||||
|
}
|
||||||
left_mix_result = (float *)_memalign_malloc(sizeof(float) * fftSize, 16);
|
for(size_t i = 0; i < channels; ++i) {
|
||||||
right_mix_result = (float *)_memalign_malloc(sizeof(float) * fftSize, 16);
|
prevInputs[i] = (float *)pffft_aligned_malloc(sizeof(float) * fftSize);
|
||||||
if(!left_mix_result || !right_mix_result)
|
if(!prevInputs[i]) {
|
||||||
return nil;
|
return nil;
|
||||||
|
}
|
||||||
prevOverlapLength = 0;
|
vDSP_vclr(prevInputs[i], 1, fftSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
if(fftSetup) vDSP_destroy_fftsetup(fftSetup);
|
if(fftSetup) pffft_destroy_setup(fftSetup);
|
||||||
|
|
||||||
free(paddedSignal);
|
pffft_aligned_free(workBuffer);
|
||||||
|
|
||||||
free(signal_fft.realp);
|
pffft_aligned_free(paddedSignal);
|
||||||
free(signal_fft.imagp);
|
|
||||||
|
|
||||||
free(input_filtered_signal_per_channel[0].realp);
|
|
||||||
free(input_filtered_signal_per_channel[0].imagp);
|
|
||||||
free(input_filtered_signal_per_channel[1].realp);
|
|
||||||
free(input_filtered_signal_per_channel[1].imagp);
|
|
||||||
|
|
||||||
if(impulse_responses) {
|
if(impulse_responses) {
|
||||||
for(size_t i = 0; i < channelCount * 2; ++i) {
|
for(size_t i = 0; i < channelCount * 2; ++i) {
|
||||||
free(impulse_responses[i].realp);
|
pffft_aligned_free(impulse_responses[i]);
|
||||||
free(impulse_responses[i].imagp);
|
|
||||||
}
|
}
|
||||||
free(impulse_responses);
|
free(impulse_responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(left_result);
|
if(prevInputs) {
|
||||||
free(right_result);
|
for(size_t i = 0; i < channelCount; ++i) {
|
||||||
|
pffft_aligned_free(prevInputs[i]);
|
||||||
|
}
|
||||||
|
free(prevInputs);
|
||||||
|
}
|
||||||
|
|
||||||
free(prevOverlapLeft);
|
pffft_aligned_free(left_result);
|
||||||
free(prevOverlapRight);
|
pffft_aligned_free(right_result);
|
||||||
|
|
||||||
free(left_mix_result);
|
|
||||||
free(right_mix_result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer {
|
- (void)process:(const float *)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer {
|
||||||
const float scale = 1.0 / (4.0 * (float)fftSize);
|
const float scale = 1.0 / ((float)fftSize);
|
||||||
|
|
||||||
while(count > 0) {
|
while(count > 0) {
|
||||||
size_t countToDo = (count > bufferSize) ? bufferSize : count;
|
const size_t countToDo = (count > bufferSize) ? bufferSize : count;
|
||||||
|
const size_t outOffset = fftSize - countToDo;
|
||||||
|
|
||||||
vDSP_vclr(left_mix_result, 1, fftSize);
|
vDSP_vclr(left_result, 1, fftSize);
|
||||||
vDSP_vclr(right_mix_result, 1, fftSize);
|
vDSP_vclr(right_result, 1, fftSize);
|
||||||
|
|
||||||
for(size_t i = 0; i < channelCount; ++i) {
|
for(size_t i = 0; i < channelCount; ++i) {
|
||||||
cblas_scopy((int)countToDo, inBuffer + i, (int)channelCount, paddedSignal, 1);
|
cblas_scopy((int)outOffset, prevInputs[i] + countToDo, 1, paddedSignal, 1);
|
||||||
|
cblas_scopy((int)countToDo, inBuffer + i, (int)channelCount, paddedSignal + outOffset, 1);
|
||||||
|
cblas_scopy((int)fftSize, paddedSignal, 1, prevInputs[i], 1);
|
||||||
|
|
||||||
vDSP_vclr(paddedSignal + countToDo, 1, paddedBufferSize - countToDo);
|
pffft_transform(fftSetup, paddedSignal, paddedSignal, workBuffer, PFFFT_FORWARD);
|
||||||
|
|
||||||
vDSP_ctoz((DSPComplex *)paddedSignal, 2, &signal_fft, 1, fftSizeOver2);
|
pffft_zconvolve_accumulate(fftSetup, paddedSignal, impulse_responses[i * 2 + 0], left_result, 1.0);
|
||||||
|
pffft_zconvolve_accumulate(fftSetup, paddedSignal, impulse_responses[i * 2 + 1], right_result, 1.0);
|
||||||
vDSP_fft_zrip(fftSetup, &signal_fft, 1, log2n, FFT_FORWARD);
|
|
||||||
|
|
||||||
// One channel forward, then multiply and back twice
|
|
||||||
|
|
||||||
float preserveIRNyq = impulse_responses[i * 2 + 0].imagp[0];
|
|
||||||
float preserveSigNyq = signal_fft.imagp[0];
|
|
||||||
impulse_responses[i * 2 + 0].imagp[0] = 0;
|
|
||||||
signal_fft.imagp[0] = 0;
|
|
||||||
|
|
||||||
vDSP_zvmul(&signal_fft, 1, &impulse_responses[i * 2 + 0], 1, &input_filtered_signal_per_channel[0], 1, fftSizeOver2, 1);
|
|
||||||
|
|
||||||
input_filtered_signal_per_channel[0].imagp[0] = preserveIRNyq * preserveSigNyq;
|
|
||||||
impulse_responses[i * 2 + 0].imagp[0] = preserveIRNyq;
|
|
||||||
|
|
||||||
preserveIRNyq = impulse_responses[i * 2 + 1].imagp[0];
|
|
||||||
impulse_responses[i * 2 + 1].imagp[0] = 0;
|
|
||||||
|
|
||||||
vDSP_zvmul(&signal_fft, 1, &impulse_responses[i * 2 + 1], 1, &input_filtered_signal_per_channel[1], 1, fftSizeOver2, 1);
|
|
||||||
|
|
||||||
input_filtered_signal_per_channel[1].imagp[0] = preserveIRNyq * preserveSigNyq;
|
|
||||||
impulse_responses[i * 2 + 1].imagp[0] = preserveIRNyq;
|
|
||||||
|
|
||||||
vDSP_fft_zrip(fftSetup, &input_filtered_signal_per_channel[0], 1, log2n, FFT_INVERSE);
|
|
||||||
vDSP_fft_zrip(fftSetup, &input_filtered_signal_per_channel[1], 1, log2n, FFT_INVERSE);
|
|
||||||
|
|
||||||
vDSP_ztoc(&input_filtered_signal_per_channel[0], 1, (DSPComplex *)left_result, 2, fftSizeOver2);
|
|
||||||
vDSP_ztoc(&input_filtered_signal_per_channel[1], 1, (DSPComplex *)right_result, 2, fftSizeOver2);
|
|
||||||
|
|
||||||
vDSP_vadd(left_mix_result, 1, left_result, 1, left_mix_result, 1, fftSize);
|
|
||||||
vDSP_vadd(right_mix_result, 1, right_result, 1, right_mix_result, 1, fftSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Integrate previous overlap
|
pffft_transform(fftSetup, left_result, left_result, workBuffer, PFFFT_BACKWARD);
|
||||||
if(prevOverlapLength) {
|
pffft_transform(fftSetup, right_result, right_result, workBuffer, PFFFT_BACKWARD);
|
||||||
vDSP_vadd(prevOverlapLeft, 1, left_mix_result, 1, left_mix_result, 1, prevOverlapLength);
|
|
||||||
vDSP_vadd(prevOverlapRight, 1, right_mix_result, 1, right_mix_result, 1, prevOverlapLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevOverlapLength = (int)(fftSize - countToDo);
|
vDSP_vsmul(left_result + outOffset, 1, &scale, left_result + outOffset, 1, countToDo);
|
||||||
|
vDSP_vsmul(right_result + outOffset, 1, &scale, right_result + outOffset, 1, countToDo);
|
||||||
|
|
||||||
cblas_scopy(prevOverlapLength, left_mix_result + countToDo, 1, prevOverlapLeft, 1);
|
cblas_scopy((int)countToDo, left_result + outOffset, 1, outBuffer + 0, 2);
|
||||||
cblas_scopy(prevOverlapLength, right_mix_result + countToDo, 1, prevOverlapRight, 1);
|
cblas_scopy((int)countToDo, right_result + outOffset, 1, outBuffer + 1, 2);
|
||||||
|
|
||||||
vDSP_vsmul(left_mix_result, 1, &scale, left_mix_result, 1, countToDo);
|
|
||||||
vDSP_vsmul(right_mix_result, 1, &scale, right_mix_result, 1, countToDo);
|
|
||||||
|
|
||||||
cblas_scopy((int)countToDo, left_mix_result, 1, outBuffer + 0, 2);
|
|
||||||
cblas_scopy((int)countToDo, right_mix_result, 1, outBuffer + 1, 2);
|
|
||||||
|
|
||||||
inBuffer += countToDo * channelCount;
|
inBuffer += countToDo * channelCount;
|
||||||
outBuffer += countToDo * 2;
|
outBuffer += countToDo * 2;
|
||||||
|
@ -537,7 +439,9 @@ static const int8_t speakers_to_hesuvi_14[11][2] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reset {
|
- (void)reset {
|
||||||
prevOverlapLength = 0;
|
for(size_t i = 0; i < channelCount; ++i) {
|
||||||
|
vDSP_vclr(prevInputs[i], 1, fftSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
835EDD7D279FE307001EDCCE /* HeadphoneFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */; };
|
835EDD7D279FE307001EDCCE /* HeadphoneFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */; };
|
||||||
835FAC5E27BCA14D00BA8562 /* BadSampleCleaner.h in Headers */ = {isa = PBXBuildFile; fileRef = 835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */; };
|
835FAC5E27BCA14D00BA8562 /* BadSampleCleaner.h in Headers */ = {isa = PBXBuildFile; fileRef = 835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */; };
|
||||||
835FAC5F27BCA14D00BA8562 /* BadSampleCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */; };
|
835FAC5F27BCA14D00BA8562 /* BadSampleCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */; };
|
||||||
|
8363BABE284E428F00E5C9DD /* pffft.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8363BABD284E428F00E5C9DD /* pffft.cpp */; };
|
||||||
83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7B27AA0D8A0003F694 /* Accelerate.framework */; };
|
83725A9027AA16C90003F694 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7B27AA0D8A0003F694 /* Accelerate.framework */; };
|
||||||
83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */; };
|
83725A9127AA16D50003F694 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83725A7C27AA0D8E0003F694 /* AVFoundation.framework */; };
|
||||||
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */ = {isa = PBXBuildFile; fileRef = 8377C64B27B8C51500E8BC0F /* fft_accelerate.c */; };
|
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */ = {isa = PBXBuildFile; fileRef = 8377C64B27B8C51500E8BC0F /* fft_accelerate.c */; };
|
||||||
|
@ -197,6 +198,7 @@
|
||||||
835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HeadphoneFilter.h; sourceTree = "<group>"; };
|
835EDD7C279FE307001EDCCE /* HeadphoneFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HeadphoneFilter.h; sourceTree = "<group>"; };
|
||||||
835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BadSampleCleaner.h; path = Utils/BadSampleCleaner.h; sourceTree = SOURCE_ROOT; };
|
835FAC5C27BCA14D00BA8562 /* BadSampleCleaner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BadSampleCleaner.h; path = Utils/BadSampleCleaner.h; sourceTree = SOURCE_ROOT; };
|
||||||
835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BadSampleCleaner.m; path = Utils/BadSampleCleaner.m; sourceTree = SOURCE_ROOT; };
|
835FAC5D27BCA14D00BA8562 /* BadSampleCleaner.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BadSampleCleaner.m; path = Utils/BadSampleCleaner.m; sourceTree = SOURCE_ROOT; };
|
||||||
|
8363BABD284E428F00E5C9DD /* pffft.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pffft.cpp; sourceTree = "<group>"; };
|
||||||
83725A7B27AA0D8A0003F694 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
83725A7B27AA0D8A0003F694 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||||
83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
83725A7C27AA0D8E0003F694 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||||
8377C64B27B8C51500E8BC0F /* fft_accelerate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fft_accelerate.c; sourceTree = "<group>"; };
|
8377C64B27B8C51500E8BC0F /* fft_accelerate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fft_accelerate.c; sourceTree = "<group>"; };
|
||||||
|
@ -529,6 +531,7 @@
|
||||||
83F18ADE27D1E8EF00385946 /* r8brain-free-src */ = {
|
83F18ADE27D1E8EF00385946 /* r8brain-free-src */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
8363BABD284E428F00E5C9DD /* pffft.cpp */,
|
||||||
83B69B6A2845DF6500D2435A /* pffft_double */,
|
83B69B6A2845DF6500D2435A /* pffft_double */,
|
||||||
83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */,
|
83F18ADF27D1E8EF00385946 /* CDSPHBDownsampler.h */,
|
||||||
83F18AF827D1E8EF00385946 /* CDSPSincFilterGen.h */,
|
83F18AF827D1E8EF00385946 /* CDSPSincFilterGen.h */,
|
||||||
|
@ -716,6 +719,7 @@
|
||||||
17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */,
|
17D21DC80B8BE79700D1EBDE /* CoreAudioUtils.m in Sources */,
|
||||||
8328995327CB511000D7F028 /* RedundantPlaylistDataStore.m in Sources */,
|
8328995327CB511000D7F028 /* RedundantPlaylistDataStore.m in Sources */,
|
||||||
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */,
|
8377C64C27B8C51500E8BC0F /* fft_accelerate.c in Sources */,
|
||||||
|
8363BABE284E428F00E5C9DD /* pffft.cpp in Sources */,
|
||||||
839366681815923C006DD712 /* CogPluginMulti.m in Sources */,
|
839366681815923C006DD712 /* CogPluginMulti.m in Sources */,
|
||||||
835C88AA2797D4D400E28EAE /* lpc.c in Sources */,
|
835C88AA2797D4D400E28EAE /* lpc.c in Sources */,
|
||||||
17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */,
|
17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */,
|
||||||
|
|
|
@ -15,7 +15,12 @@
|
||||||
#import <CoreAudio/AudioHardware.h>
|
#import <CoreAudio/AudioHardware.h>
|
||||||
#import <CoreAudio/CoreAudioTypes.h>
|
#import <CoreAudio/CoreAudioTypes.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#import <atomic>
|
||||||
|
using std::atomic_long;
|
||||||
|
#else
|
||||||
#import <stdatomic.h>
|
#import <stdatomic.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#import "Downmix.h"
|
#import "Downmix.h"
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,7 @@
|
||||||
8347435F20E6D5A000063D45 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Preferences.strings; sourceTree = "<group>"; };
|
8347435F20E6D5A000063D45 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Preferences.strings; sourceTree = "<group>"; };
|
||||||
835C888922CC1880001B4B3F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
835C888922CC1880001B4B3F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
835C888A22CC1880001B4B3F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
835C888A22CC1880001B4B3F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
8363BABF284E450E00E5C9DD /* pffft.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = pffft.h; path = "../../Audio/ThirdParty/r8brain-free-src/pffft.h"; sourceTree = "<group>"; };
|
||||||
83651DA327322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIFlavorBehaviorArrayController.m; sourceTree = "<group>"; };
|
83651DA327322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIFlavorBehaviorArrayController.m; sourceTree = "<group>"; };
|
||||||
83651DA427322C8700A2C097 /* MIDIFlavorBehaviorArrayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIFlavorBehaviorArrayController.h; sourceTree = "<group>"; };
|
83651DA427322C8700A2C097 /* MIDIFlavorBehaviorArrayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIFlavorBehaviorArrayController.h; sourceTree = "<group>"; };
|
||||||
8372053518E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResamplerBehaviorArrayController.h; sourceTree = "<group>"; };
|
8372053518E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResamplerBehaviorArrayController.h; sourceTree = "<group>"; };
|
||||||
|
@ -268,6 +269,7 @@
|
||||||
32C88E010371C26100C91783 /* Other Sources */ = {
|
32C88E010371C26100C91783 /* Other Sources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
8363BABF284E450E00E5C9DD /* pffft.h */,
|
||||||
ED69CF0B25BE75330090B90D /* Shortcuts.h */,
|
ED69CF0B25BE75330090B90D /* Shortcuts.h */,
|
||||||
32DBCF630370AF2F00C91783 /* Preferences_Prefix.pch */,
|
32DBCF630370AF2F00C91783 /* Preferences_Prefix.pch */,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue