diff --git a/Application/PlaybackController.m b/Application/PlaybackController.m index 3a4e07efc..95ce723ac 100644 --- a/Application/PlaybackController.m +++ b/Application/PlaybackController.m @@ -67,6 +67,7 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation"; [NSNumber numberWithInt:-1], @"GraphicEQpreset", [NSNumber numberWithBool:NO], @"GraphicEQtrackgenre", [NSNumber numberWithBool:YES], @"volumeLimit", + [NSNumber numberWithBool:NO], @"headphoneVirtualization", nil]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary]; diff --git a/Audio/Chain/ConverterNode.h b/Audio/Chain/ConverterNode.h index 8cd826560..8fb81eb8d 100644 --- a/Audio/Chain/ConverterNode.h +++ b/Audio/Chain/ConverterNode.h @@ -17,6 +17,8 @@ #import "Node.h" #import "RefillNode.h" +#import "HeadphoneFilter.h" + @interface ConverterNode : Node { NSDictionary * rgInfo; @@ -75,6 +77,8 @@ NSString *outputResampling; void *hdcd_decoder; + + HeadphoneFilter *hFilter; } @property AudioStreamBasicDescription inputFormat; diff --git a/Audio/Chain/ConverterNode.m b/Audio/Chain/ConverterNode.m index 153448b27..ff0835116 100644 --- a/Audio/Chain/ConverterNode.m +++ b/Audio/Chain/ConverterNode.m @@ -87,6 +87,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]; } return self; @@ -1045,7 +1046,13 @@ tryagain: scale_by_volume( (float*) floatBuffer, amountReadFromFC / sizeof(float), volumeScale); - if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 ) + if ( hFilter ) { + int samples = amountReadFromFC / floatFormat.mBytesPerFrame; + [hFilter process:floatBuffer sampleCount:samples toBuffer:floatBuffer + amountReadFromFC]; + memmove(floatBuffer, floatBuffer + amountReadFromFC, samples * sizeof(float) * 2); + amountReadFromFC = samples * sizeof(float) * 2; + } + else if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 ) { int samples = amountReadFromFC / floatFormat.mBytesPerFrame; downmix_to_stereo( (float*) floatBuffer, inputFormat.mChannelsPerFrame, samples ); @@ -1090,11 +1097,11 @@ tryagain: context:(void *)context { DLog(@"SOMETHING CHANGED!"); - if ([keyPath isEqual:@"values.volumeScaling"]) { + if ([keyPath isEqualToString:@"values.volumeScaling"]) { //User reset the volume scaling option [self refreshVolumeScaling]; } - else if ([keyPath isEqual:@"values.outputResampling"]) { + else if ([keyPath isEqualToString:@"values.outputResampling"]) { // Reset resampler if (resampler && resampler_data) { NSString *value = [[NSUserDefaults standardUserDefaults] stringForKey:@"outputResampling"]; @@ -1102,6 +1109,14 @@ tryagain: [self inputFormatDidChange:inputFormat]; } } + else if ([keyPath isEqualToString:@"values.headphoneVirtualization"]) { + // Reset the converter, without rebuffering + if (outputFormat.mChannelsPerFrame == 2 && + inputFormat.mChannelsPerFrame >= 1 && + inputFormat.mChannelsPerFrame <= 8) { + [self inputFormatDidChange:inputFormat]; + } + } } static float db_to_scale(float db) @@ -1209,6 +1224,19 @@ static float db_to_scale(float db) dmFloatFormat.mChannelsPerFrame = outputFormat.mChannelsPerFrame; dmFloatFormat.mBytesPerFrame = (32/8)*dmFloatFormat.mChannelsPerFrame; dmFloatFormat.mBytesPerPacket = dmFloatFormat.mBytesPerFrame * floatFormat.mFramesPerPacket; + + BOOL hVirt = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"headphoneVirtualization"]; + + if (hVirt && + outputFormat.mChannelsPerFrame == 2 && + inputFormat.mChannelsPerFrame >= 1 && + inputFormat.mChannelsPerFrame <= 8) { + CFURLRef appUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("gsx"), CFSTR("wv"), NULL); + + if (appUrlRef) { + hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:(__bridge NSURL *)appUrlRef forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame]; + } + } convert_s16_to_float_init_simd(); convert_s32_to_float_init_simd(); @@ -1276,6 +1304,7 @@ 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"]; paused = NO; [self cleanUp]; @@ -1340,6 +1369,9 @@ static float db_to_scale(float db) { usleep(500); } + if (hFilter) { + hFilter = nil; + } if (hdcd_decoder) { free(hdcd_decoder); diff --git a/Audio/Chain/HeadphoneFilter.h b/Audio/Chain/HeadphoneFilter.h new file mode 100644 index 000000000..7c2e1d05c --- /dev/null +++ b/Audio/Chain/HeadphoneFilter.h @@ -0,0 +1,50 @@ +// +// HeadphoneFilter.h +// CogAudio Framework +// +// Created by Christopher Snowhill on 1/24/22. +// + +#ifndef HeadphoneFilter_h +#define HeadphoneFilter_h + +#import +#import + +@interface HeadphoneFilter : NSObject { + FFTSetup fftSetup; + + size_t fftSize; + size_t fftSizeOver2; + size_t log2n; + size_t log2nhalf; + size_t bufferSize; + size_t paddedBufferSize; + size_t channelCount; + + double sampleRate; + + COMPLEX_SPLIT signal_fft; + COMPLEX_SPLIT input_filtered_signal_per_channel[2]; + COMPLEX_SPLIT * impulse_responses; + + float * left_result; + float * right_result; + + float * left_mix_result; + float * right_mix_result; + + float * paddedSignal; + + float * prevOverlap[2]; + + int prevOverlapLength; +} + +- (id)initWithImpulseFile:(NSURL *)url forSampleRate:(double)sampleRate withInputChannels:(size_t)channels; + +- (void)process:(const float*)inBuffer sampleCount:(size_t)count toBuffer:(float *)outBuffer; + +@end + +#endif /* HeadphoneFilter_h */ diff --git a/Audio/Chain/HeadphoneFilter.m b/Audio/Chain/HeadphoneFilter.m new file mode 100644 index 000000000..d9ec0385e --- /dev/null +++ b/Audio/Chain/HeadphoneFilter.m @@ -0,0 +1,295 @@ +// +// HeadphoneFilter.m +// CogAudio Framework +// +// Created by Christopher Snowhill on 1/24/22. +// + +#import "HeadphoneFilter.h" +#import "AudioSource.h" +#import "AudioDecoder.h" + +#import