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 */,