From 708c7dc72177359c03b4de6b8f4914bab5017327 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Tue, 25 Jan 2022 21:30:33 -0800 Subject: [PATCH] Headphone Virtualization: Implement customization Implement the ability to configure and select an HRIR preset to use with the HRIR filter, or remove the preset. It will validate the file's usefulness before setting it for the player to use. Also, fixed back center channel filtering for 7.0 format audio. Signed-off-by: Christopher Snowhill --- Audio/Chain/ConverterNode.m | 30 ++- Audio/Chain/HeadphoneFilter.h | 2 + Audio/Chain/HeadphoneFilter.m | 234 ++++++++++++------ .../Preferences/Base.lproj/Preferences.xib | 62 ++++- Preferences/Preferences/OutputPane.h | 2 + Preferences/Preferences/OutputPane.m | 34 ++- .../Preferences.xcodeproj/project.pbxproj | 2 + 7 files changed, 283 insertions(+), 83 deletions(-) diff --git a/Audio/Chain/ConverterNode.m b/Audio/Chain/ConverterNode.m index ff0835116..1b168c917 100644 --- a/Audio/Chain/ConverterNode.m +++ b/Audio/Chain/ConverterNode.m @@ -88,6 +88,7 @@ void PrintStreamDesc (AudioStreamBasicDescription *inDesc) [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputResampling" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:nil]; } return self; @@ -1109,7 +1110,8 @@ tryagain: [self inputFormatDidChange:inputFormat]; } } - else if ([keyPath isEqualToString:@"values.headphoneVirtualization"]) { + else if ([keyPath isEqualToString:@"values.headphoneVirtualization"] || + [keyPath isEqualToString:@"values.hrirPath"]) { // Reset the converter, without rebuffering if (outputFormat.mChannelsPerFrame == 2 && inputFormat.mChannelsPerFrame >= 1 && @@ -1231,10 +1233,27 @@ static float db_to_scale(float db) outputFormat.mChannelsPerFrame == 2 && inputFormat.mChannelsPerFrame >= 1 && inputFormat.mChannelsPerFrame <= 8) { - CFURLRef appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("gsx"), CFSTR("wv"), NULL); + NSString * userPreset = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"hrirPath"]; + + NSURL * presetUrl = nil; + + if (userPreset && ![userPreset isEqualToString:@""]) { + presetUrl = [NSURL fileURLWithPath:userPreset]; + if (![HeadphoneFilter validateImpulseFile:presetUrl]) + presetUrl = nil; + } + + if (!presetUrl) { + CFURLRef appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("gsx"), CFSTR("wv"), NULL); + if (appUrlRef) + presetUrl = (__bridge NSURL *) appUrlRef; + CFRelease(appUrlRef); + if (![HeadphoneFilter validateImpulseFile:presetUrl]) + presetUrl = nil; + } - if (appUrlRef) { - hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:(__bridge NSURL *)appUrlRef forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame]; + if (presetUrl) { + hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:presetUrl forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame]; } } @@ -1305,7 +1324,8 @@ static float db_to_scale(float db) [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeScaling"]; [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.outputResampling"]; [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization"]; - + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath"]; + paused = NO; [self cleanUp]; } diff --git a/Audio/Chain/HeadphoneFilter.h b/Audio/Chain/HeadphoneFilter.h index 7c2e1d05c..12b3fc7f8 100644 --- a/Audio/Chain/HeadphoneFilter.h +++ b/Audio/Chain/HeadphoneFilter.h @@ -41,6 +41,8 @@ int prevOverlapLength; } ++ (BOOL)validateImpulseFile:(NSURL *)url; + - (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels; - (void)process:(const float*)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer; diff --git a/Audio/Chain/HeadphoneFilter.m b/Audio/Chain/HeadphoneFilter.m index 930901be9..5a87ef3eb 100644 --- a/Audio/Chain/HeadphoneFilter.m +++ b/Audio/Chain/HeadphoneFilter.m @@ -14,6 +14,62 @@ @implementation HeadphoneFilter +// Symmetrical / no-echo sets +static const int8_t speakers_to_hesuvi_7[8][2][8] = { + { { 6 }, { 6 } }, // mono/center + { { 0, 1 }, { 1, 0 } }, // left/right + { { 0, 1, 6 }, { 1, 0, 6 } }, // left/right/center + { { 0, 1, 4, 5 }, { 1, 0, 5, 4 } },// left/right/left back/right back + { { 0, 1, 6, 4, 5 }, { 1, 0, 6, 5, 4 } }, // left/right/center/back left/back right + { { 0, 1, 6, 6, 4, 5 }, { 1, 0, 6, 6, 5, 4 } }, // left/right/center/lfe(center)/back left/back right + { { 0, 1, 6, 6, -1, 2, 3 }, { 1, 0, 6, 6, -1, 3, 2 } }, // left/right/center/lfe(center)/back center(special)/side left/side right + { { 0, 1, 6, 6, 4, 5, 2, 3 }, { 1, 0, 6, 6, 5, 4, 3, 2 } } // left/right/center/lfe(center)/back left/back right/side left/side right +}; + +// Asymmetrical / echo sets +static const int8_t speakers_to_hesuvi_14[8][2][8] = { + { { 6 }, { 13 } }, // mono/center + { { 0, 8 }, { 1, 7 } }, // left/right + { { 0, 8, 6 }, { 1, 7, 13 } }, // left/right/center + { { 0, 8, 4, 12 }, { 1, 7, 5, 11 } },// left/right/left back/right back + { { 0, 8, 6, 4, 12 }, { 1, 7, 13, 5, 11 } }, // left/right/center/back left/back right + { { 0, 8, 6, 6, 4, 12 }, { 1, 7, 13, 13, 5, 11 } }, // left/right/center/lfe(center)/back left/back right + { { 0, 8, 6, 6, -1, 2, 10 }, { 1, 7, 13, 13, -1, 3, 9 } }, // left/right/center/lfe(center)/back center(special)/side left/side right + { { 0, 8, 6, 6, 4, 12, 2, 10 }, { 1, 7, 13, 13, 5, 11, 3, 9 } } // left/right/center/lfe(center)/back left/back right/side left/side right +}; + ++ (BOOL)validateImpulseFile:(NSURL *)url { + id source = [AudioSource audioSourceForURL:url]; + if (!source) + return NO; + + if (![source open:url]) + return NO; + + id decoder = [AudioDecoder audioDecoderForSource:source]; + + if (decoder == nil) + return NO; + + if (![decoder open:source]) + { + return NO; + } + + NSDictionary *properties = [decoder properties]; + + int impulseChannels = [[properties objectForKey:@"channels"] intValue]; + + if ([[properties objectForKey:@"floatingPoint"] boolValue] != YES || + [[properties objectForKey:@"bitsPerSample"] intValue] != 32 || + !([[properties objectForKey:@"endian"] isEqualToString:@"native"] || + [[properties objectForKey:@"endian"] isEqualToString:@"little"]) || + (impulseChannels != 14 && impulseChannels != 7)) + return NO; + + return YES; +} + - (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels { self = [super init]; @@ -48,30 +104,35 @@ [[properties objectForKey:@"endian"] isEqualToString:@"little"]) || (impulseChannels != 14 && impulseChannels != 7)) return nil; - + float * impulseBuffer = calloc(sizeof(float), (sampleCount + 1024) * sizeof(float) * impulseChannels); - [decoder readAudio:impulseBuffer frames:sampleCount]; + if (!impulseBuffer) + return nil; + + if ([decoder readAudio:impulseBuffer frames:sampleCount] != sampleCount) + return nil; + [decoder close]; decoder = nil; source = nil; - + if (sampleRateOfSource != sampleRate) { double sampleRatio = sampleRate / sampleRateOfSource; int resampledCount = (int)ceil((double)sampleCount * sampleRatio); - + void *resampler_data = NULL; const retro_resampler_t *resampler = NULL; - + if (!retro_resampler_realloc(&resampler_data, &resampler, "sinc", RESAMPLER_QUALITY_NORMAL, impulseChannels, sampleRatio)) { free(impulseBuffer); return nil; } - + int resamplerLatencyIn = (int) resampler->latency(resampler_data); int resamplerLatencyOut = (int)ceil(resamplerLatencyIn * sampleRatio); - + float * resampledImpulse = calloc(sizeof(float), (resampledCount + resamplerLatencyOut * 2 + 128) * sizeof(float) * impulseChannels); - + memmove(impulseBuffer + resamplerLatencyIn * impulseChannels, impulseBuffer, sampleCount * sizeof(float) * impulseChannels); memset(impulseBuffer, 0, resamplerLatencyIn * sizeof(float) * impulseChannels); memset(impulseBuffer + (resamplerLatencyIn + sampleCount) * impulseChannels, 0, resamplerLatencyIn * sizeof(float) * impulseChannels); @@ -82,34 +143,37 @@ src_data.data_out = resampledImpulse; src_data.output_frames = 0; src_data.ratio = sampleRatio; - + resampler->process(resampler_data, &src_data); - + resampler->free(resampler, resampler_data); - + src_data.output_frames -= resamplerLatencyOut * 2; - + memmove(resampledImpulse, resampledImpulse + resamplerLatencyOut * impulseChannels, src_data.output_frames * sizeof(float) * impulseChannels); - + free(impulseBuffer); impulseBuffer = resampledImpulse; sampleCount = (int) src_data.output_frames; } - + channelCount = channels; - + bufferSize = 512; fftSize = sampleCount + bufferSize; int pow = 1; while (fftSize > 2) { pow++; fftSize /= 2; } fftSize = 2 << pow; + + float * deinterleavedImpulseBuffer = (float *) memalign_calloc(16, sizeof(float), fftSize * impulseChannels); + if (!deinterleavedImpulseBuffer) { + free(impulseBuffer); + return nil; + } - float * deinterleavedImpulseBuffer = (float *) memalign_calloc(128, sizeof(float), fftSize * impulseChannels); for (size_t i = 0; i < impulseChannels; ++i) { - for (size_t j = 0; j < sampleCount; ++j) { - deinterleavedImpulseBuffer[i * fftSize + j] = impulseBuffer[i + impulseChannels * j]; - } + cblas_scopy(sampleCount, impulseBuffer + i, impulseChannels, deinterleavedImpulseBuffer + i * fftSize, 1); } free(impulseBuffer); @@ -120,48 +184,57 @@ log2nhalf = log2n / 2; fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2); + if (!fftSetup) { + memalign_free(deinterleavedImpulseBuffer); + return nil; + } - paddedSignal = (float *) memalign_calloc(128, sizeof(float), paddedBufferSize); + paddedSignal = (float *) memalign_calloc(16, sizeof(float), paddedBufferSize); + if (!paddedSignal) { + memalign_free(deinterleavedImpulseBuffer); + return nil; + } - signal_fft.realp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); - signal_fft.imagp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); + signal_fft.realp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + signal_fft.imagp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + if (!signal_fft.realp || !signal_fft.imagp) { + memalign_free(deinterleavedImpulseBuffer); + return nil; + } - input_filtered_signal_per_channel[0].realp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); - input_filtered_signal_per_channel[0].imagp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); - input_filtered_signal_per_channel[1].realp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); - input_filtered_signal_per_channel[1].imagp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); + input_filtered_signal_per_channel[0].realp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + input_filtered_signal_per_channel[0].imagp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + if (!input_filtered_signal_per_channel[0].realp || + !input_filtered_signal_per_channel[0].imagp) { + memalign_free(deinterleavedImpulseBuffer); + return nil; + } + + input_filtered_signal_per_channel[1].realp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + input_filtered_signal_per_channel[1].imagp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + if (!input_filtered_signal_per_channel[1].realp || + !input_filtered_signal_per_channel[1].imagp) { + memalign_free(deinterleavedImpulseBuffer); + return nil; + } impulse_responses = (COMPLEX_SPLIT *) calloc(sizeof(COMPLEX_SPLIT), channels * 2); + if (!impulse_responses) { + memalign_free(deinterleavedImpulseBuffer); + return nil; + } - // Symmetrical / no-echo sets - const int8_t speakers_to_hesuvi_7[8][2][8] = { - { { 6, }, { 6, } }, // mono/center - { { 0, 1 }, { 1, 0 } }, // left/right - { { 0, 1, 6 }, { 1, 0, 6 } }, // left/right/center - { { 0, 1, 4, 5 }, { 1, 0, 5, 4 } },// left/right/left back/right back - { { 0, 1, 6, 4, 5 }, { 1, 0, 6, 5, 4 } }, // left/right/center/back left/back right - { { 0, 1, 6, 6, 4, 5 }, { 1, 0, 6, 6, 5, 4 } }, // left/right/center/lfe(center)/back left/back right - { { 0, 1, 6, 6, -1, 2, 3 }, { 1, 0, 6, 6, -1, 3, 2 } }, // left/right/center/lfe(center)/back center(special)/side left/side right - { { 0, 1, 6, 6, 4, 5, 2, 3 }, { 1, 0, 6, 6, 5, 4, 3, 2 } } // left/right/center/lfe(center)/back left/back right/side left/side right - }; - - // Asymmetrical / echo sets - const int8_t speakers_to_hesuvi_14[8][2][8] = { - { { 6, }, { 13, } }, // mono/center - { { 0, 8 }, { 1, 7 } }, // left/right - { { 0, 8, 6 }, { 1, 7, 13 } }, // left/right/center - { { 0, 8, 4, 12 }, { 1, 7, 5, 11 } },// left/right/left back/right back - { { 0, 8, 6, 4, 12 }, { 1, 7, 13, 5, 11 } }, // left/right/center/back left/back right - { { 0, 8, 6, 6, 4, 12 }, { 1, 7, 13, 13, 5, 11 } }, // left/right/center/lfe(center)/back left/back right - { { 0, 8, 6, 6, -1, 2, 10 }, { 1, 7, 13, 13, -1, 3, 9 } }, // left/right/center/lfe(center)/back center(special)/side left/side right - { { 0, 8, 6, 6, 4, 12, 2, 10 }, { 1, 7, 13, 13, 5, 11, 3, 9 } } // left/right/center/lfe(center)/back left/back right/side left/side right - }; - for (size_t i = 0; i < channels; ++i) { - impulse_responses[i * 2 + 0].realp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); - impulse_responses[i * 2 + 0].imagp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); - impulse_responses[i * 2 + 1].realp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); - impulse_responses[i * 2 + 1].imagp = (float *) memalign_calloc(128, sizeof(float), fftSizeOver2); + impulse_responses[i * 2 + 0].realp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + impulse_responses[i * 2 + 0].imagp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + impulse_responses[i * 2 + 1].realp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + impulse_responses[i * 2 + 1].imagp = (float *) memalign_calloc(16, sizeof(float), fftSizeOver2); + + if (!impulse_responses[i * 2 + 0].realp || !impulse_responses[i * 2 + 0].imagp || + !impulse_responses[i * 2 + 1].realp || !impulse_responses[i * 2 + 1].imagp) { + memalign_free(deinterleavedImpulseBuffer); + return nil; + } int leftInChannel; int rightInChannel; @@ -176,22 +249,37 @@ } if (leftInChannel == -1 || rightInChannel == -1) { - float * temp = calloc(sizeof(float), fftSize * 2); + float * temp; if (impulseChannels == 7) { - for (size_t i = 0; i < fftSize; i++) { - temp[i + fftSize] = temp[i] = deinterleavedImpulseBuffer[i + 2 * fftSize] + deinterleavedImpulseBuffer[i + 3 * fftSize]; + temp = calloc(sizeof(float), fftSize); + if (!temp) { + memalign_free(deinterleavedImpulseBuffer); + return nil; } + + cblas_scopy((int)fftSize, temp, 1, deinterleavedImpulseBuffer + 4 * fftSize, 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 { - for (size_t i = 0; i < fftSize; i++) { - temp[i] = deinterleavedImpulseBuffer[i + 2 * fftSize] + deinterleavedImpulseBuffer[i + 9 * fftSize]; - temp[i + fftSize] = deinterleavedImpulseBuffer[i + 3 * fftSize] + deinterleavedImpulseBuffer[i + 10 * fftSize]; + temp = calloc(sizeof(float), fftSize * 2); + if (!temp) { + memalign_free(deinterleavedImpulseBuffer); + return nil; } + + cblas_scopy((int)fftSize, temp, 1, deinterleavedImpulseBuffer + 4 * fftSize, 1); + vDSP_vadd(temp, 1, deinterleavedImpulseBuffer + 12 * fftSize, 1, temp, 1, fftSize); + + cblas_scopy((int)fftSize, temp + fftSize, 1, deinterleavedImpulseBuffer + 5 * 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); } - - 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 { @@ -205,14 +293,20 @@ memalign_free(deinterleavedImpulseBuffer); - left_result = (float *) memalign_calloc(128, sizeof(float), fftSize); - right_result = (float *) memalign_calloc(128, sizeof(float), fftSize); + left_result = (float *) memalign_calloc(16, sizeof(float), fftSize); + right_result = (float *) memalign_calloc(16, sizeof(float), fftSize); + if (!left_result || !right_result) + return nil; - prevOverlap[0] = (float *) memalign_calloc(128, sizeof(float), fftSize); - prevOverlap[1] = (float *) memalign_calloc(128, sizeof(float), fftSize); + prevOverlap[0] = (float *) memalign_calloc(16, sizeof(float), fftSize); + prevOverlap[1] = (float *) memalign_calloc(16, sizeof(float), fftSize); + if (!prevOverlap[0] || !prevOverlap[1]) + return nil; - left_mix_result = (float *) memalign_calloc(128, sizeof(float), fftSize); - right_mix_result = (float *) memalign_calloc(128, sizeof(float), fftSize); + left_mix_result = (float *) memalign_calloc(16, sizeof(float), fftSize); + right_mix_result = (float *) memalign_calloc(16, sizeof(float), fftSize); + if (!left_mix_result || !right_mix_result) + return nil; prevOverlapLength = 0; } diff --git a/Preferences/Preferences/Base.lproj/Preferences.xib b/Preferences/Preferences/Base.lproj/Preferences.xib index 86ec6fa3a..3cd4ad0db 100644 --- a/Preferences/Preferences/Base.lproj/Preferences.xib +++ b/Preferences/Preferences/Base.lproj/Preferences.xib @@ -245,7 +245,7 @@ - + @@ -339,7 +339,7 @@ - + + + + + + + + + + + + + + + + + + + + + + Built-in + PathToFileTransformer + + + + + - + diff --git a/Preferences/Preferences/OutputPane.h b/Preferences/Preferences/OutputPane.h index 7e61195a5..e85638d3b 100644 --- a/Preferences/Preferences/OutputPane.h +++ b/Preferences/Preferences/OutputPane.h @@ -15,5 +15,7 @@ } - (IBAction) takeDeviceID:(id)sender; +- (IBAction) setHrir:(id)sender; +- (IBAction) clearHrir:(id)sender; @end diff --git a/Preferences/Preferences/OutputPane.m b/Preferences/Preferences/OutputPane.m index 6eccc528b..8f0177ac1 100644 --- a/Preferences/Preferences/OutputPane.m +++ b/Preferences/Preferences/OutputPane.m @@ -7,7 +7,7 @@ // #import "OutputPane.h" - +#import "HeadphoneFilter.h" @implementation OutputPane @@ -31,5 +31,37 @@ [[NSUserDefaults standardUserDefaults] setObject: device forKey:@"outputDevice"]; } +- (IBAction)setHrir:(id)sender +{ + NSArray *fileTypes = @[@"wav", @"wv"]; + NSOpenPanel * panel = [NSOpenPanel openPanel]; + [panel setAllowsMultipleSelection:NO]; + [panel setCanChooseDirectories:NO]; + [panel setCanChooseFiles:YES]; + [panel setFloatingPanel:YES]; + [panel setAllowedFileTypes:fileTypes]; + NSString * oldPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"hrirPath"]; + if ( oldPath != nil ) + [panel setDirectoryURL:[NSURL fileURLWithPath:oldPath]]; + NSInteger result = [panel runModal]; + if(result == NSModalResponseOK) + { + NSString * path = [[panel URL] path]; + if ([NSClassFromString(@"HeadphoneFilter") validateImpulseFile:[NSURL fileURLWithPath:path]]) + [[NSUserDefaults standardUserDefaults] setValue:[[panel URL] path] forKey:@"hrirPath"]; + else { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Invalid impulse"]; + [alert setInformativeText:@"The selected file does not conform to the HeSuVi HRIR specification."]; + [alert addButtonWithTitle:@"Ok"]; + [alert runModal]; + } + } +} + +- (IBAction)clearHrir:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setValue:@"" forKey:@"hrirPath"]; +} @end diff --git a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj index 94126b5bf..262da0887 100644 --- a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj +++ b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ 83B06728180D85B8008E3612 /* MIDIPane.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIPane.m; sourceTree = ""; }; 83B0672A180D8B39008E3612 /* midi.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = midi.png; path = Icons/midi.png; sourceTree = ""; }; 83BC5AB320E4C90F00631CD4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Preferences.xib; sourceTree = ""; }; + 83D34B7D27A10FA700784D34 /* HeadphoneFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HeadphoneFilter.h; path = ../../Audio/Chain/HeadphoneFilter.h; sourceTree = ""; }; 83EF495D17FBC96A00642E3C /* VolumeBehaviorArrayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VolumeBehaviorArrayController.h; sourceTree = ""; }; 83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VolumeBehaviorArrayController.m; sourceTree = ""; }; 83F27E651810DD3A00CEF538 /* appearance@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "appearance@2x.png"; path = "Icons/appearance@2x.png"; sourceTree = ""; }; @@ -195,6 +196,7 @@ children = ( 83F27E711810E41A00CEF538 /* Transformers */, 8384913618081ECB00E7332D /* Logging.h */, + 83D34B7D27A10FA700784D34 /* HeadphoneFilter.h */, 17D503410ABDB1660022D1E8 /* Custom */, 17D5033F0ABDB1570022D1E8 /* Panes */, 17D1B3F60F6349CE00694C57 /* PreferencePanePlugin.h */,