// // Downmix.m // Cog // // Created by Christopher Snowhill on 2/05/22. // Copyright 2022 __LoSnoCo__. All rights reserved. // #import "Downmix.h" #import "Logging.h" static const float STEREO_DOWNMIX[8-2][8][2]={ /*3.0*/ { {0.5858F,0.0F},{0.0F,0.5858F},{0.4142F,0.4142F} }, /*quadrophonic*/ { {0.4226F,0.0F},{0.0F,0.4226F},{0.366F,0.2114F},{0.2114F,0.336F} }, /*5.0*/ { {0.651F,0.0F},{0.0F,0.651F},{0.46F,0.46F},{0.5636F,0.3254F}, {0.3254F,0.5636F} }, /*5.1*/ { {0.529F,0.0F},{0.0F,0.529F},{0.3741F,0.3741F},{0.3741F,0.3741F},{0.4582F,0.2645F}, {0.2645F,0.4582F} }, /*6.1*/ { {0.4553F,0.0F},{0.0F,0.4553F},{0.322F,0.322F},{0.322F,0.322F},{0.2788F,0.2788F}, {0.3943F,0.2277F},{0.2277F,0.3943F} }, /*7.1*/ { {0.3886F,0.0F},{0.0F,0.3886F},{0.2748F,0.2748F},{0.2748F,0.2748F},{0.3366F,0.1943F}, {0.1943F,0.3366F},{0.3366F,0.1943F},{0.1943F,0.3366F} } }; static void downmix_to_stereo(const float * inBuffer, int channels, float * outBuffer, size_t count) { if (channels >= 3 && channels <= 8) for (size_t i = 0; i < count; ++i) { float left = 0, right = 0; for (int j = 0; j < channels; ++j) { left += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][0]; right += inBuffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1]; } outBuffer[i * 2 + 0] = left; outBuffer[i * 2 + 1] = right; } } static void downmix_to_mono(const float * inBuffer, int channels, float * outBuffer, size_t count) { float tempBuffer[count * 2]; if (channels >= 3 && channels <= 8) { downmix_to_stereo(inBuffer, channels, tempBuffer, count); inBuffer = tempBuffer; channels = 2; } float invchannels = 1.0 / (float)channels; for (size_t i = 0; i < count; ++i) { float sample = 0; for (int j = 0; j < channels; ++j) { sample += inBuffer[i * channels + j]; } outBuffer[i] = sample * invchannels; } } static void upmix(const float * inBuffer, int inchannels, float * outBuffer, int outchannels, size_t count) { for (ssize_t i = 0; i < count; ++i) { if (inchannels == 1 && outchannels == 2) { // upmix mono to stereo float sample = inBuffer[i]; outBuffer[i * 2 + 0] = sample; outBuffer[i * 2 + 1] = sample; } else if (inchannels == 1 && outchannels == 4) { // upmix mono to quad float sample = inBuffer[i]; outBuffer[i * 4 + 0] = sample; outBuffer[i * 4 + 1] = sample; outBuffer[i * 4 + 2] = 0; outBuffer[i * 4 + 3] = 0; } else if (inchannels == 1 && (outchannels == 3 || outchannels >= 5)) { // upmix mono to center channel float sample = inBuffer[i]; outBuffer[i * outchannels + 2] = sample; for (int j = 0; j < 2; ++j) { outBuffer[i * outchannels + j] = 0; } for (int j = 3; j < outchannels; ++j) { outBuffer[i * outchannels + j] = 0; } } else if (inchannels == 4 && outchannels >= 5) { float fl = inBuffer[i * 4 + 0]; float fr = inBuffer[i * 4 + 1]; float bl = inBuffer[i * 4 + 2]; float br = inBuffer[i * 4 + 3]; const int skipclfe = (outchannels == 5) ? 1 : 2; outBuffer[i * outchannels + 0] = fl; outBuffer[i * outchannels + 1] = fr; outBuffer[i * outchannels + skipclfe + 2] = bl; outBuffer[i * outchannels + skipclfe + 3] = br; for (int j = 0; j < skipclfe; ++j) { outBuffer[i * outchannels + 2 + j] = 0; } for (int j = 4 + skipclfe; j < outchannels; ++j) { outBuffer[i * outchannels + j] = 0; } } else if (inchannels == 5 && outchannels >= 6) { float fl = inBuffer[i * 5 + 0]; float fr = inBuffer[i * 5 + 1]; float c = inBuffer[i * 5 + 2]; float bl = inBuffer[i * 5 + 3]; float br = inBuffer[i * 5 + 4]; outBuffer[i * outchannels + 0] = fl; outBuffer[i * outchannels + 1] = fr; outBuffer[i * outchannels + 2] = c; outBuffer[i * outchannels + 3] = 0; outBuffer[i * outchannels + 4] = bl; outBuffer[i * outchannels + 5] = br; for (int j = 6; j < outchannels; ++j) { outBuffer[i * outchannels + j] = 0; } } else if (inchannels == 7 && outchannels == 8) { float fl = inBuffer[i * 7 + 0]; float fr = inBuffer[i * 7 + 1]; float c = inBuffer[i * 7 + 2]; float lfe = inBuffer[i * 7 + 3]; float sl = inBuffer[i * 7 + 4]; float sr = inBuffer[i * 7 + 5]; float bc = inBuffer[i * 7 + 6]; outBuffer[i * 8 + 0] = fl; outBuffer[i * 8 + 1] = fr; outBuffer[i * 8 + 2] = c; outBuffer[i * 8 + 3] = lfe; outBuffer[i * 8 + 4] = bc; outBuffer[i * 8 + 5] = bc; outBuffer[i * 8 + 6] = sl; outBuffer[i * 8 + 7] = sr; } else { // upmix N channels to N channels plus silence the empty channels float samples[inchannels]; for (int j = 0; j < inchannels; ++j) { samples[j] = inBuffer[i * inchannels + j]; } for (int j = 0; j < inchannels; ++j) { outBuffer[i * outchannels + j] = samples[j]; } for (int j = inchannels; j < outchannels; ++j) { outBuffer[i * outchannels + j] = 0; } } } } @implementation DownmixProcessor - (id) initWithInputFormat:(AudioStreamBasicDescription)inf andOutputFormat:(AudioStreamBasicDescription)outf { self = [super init]; if (self) { if (inf.mFormatID != kAudioFormatLinearPCM || (inf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked || inf.mBitsPerChannel != 32 || inf.mBytesPerFrame != (4 * inf.mChannelsPerFrame) || inf.mBytesPerPacket != inf.mFramesPerPacket * inf.mBytesPerFrame) return nil; if (outf.mFormatID != kAudioFormatLinearPCM || (outf.mFormatFlags & kAudioFormatFlagsNativeFloatPacked) != kAudioFormatFlagsNativeFloatPacked || outf.mBitsPerChannel != 32 || outf.mBytesPerFrame != (4 * outf.mChannelsPerFrame) || outf.mBytesPerPacket != outf.mFramesPerPacket * outf.mBytesPerFrame) return nil; inputFormat = inf; outputFormat = outf; [self setupVirt]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:nil]; } return self; } - (void) dealloc { [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization"]; [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath"]; } - (void) setupVirt { @synchronized(hFilter) { hFilter = nil; } BOOL hVirt = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"headphoneVirtualization"]; if (hVirt && outputFormat.mChannelsPerFrame == 2 && inputFormat.mChannelsPerFrame >= 1 && inputFormat.mChannelsPerFrame <= 8) { 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) { presetUrl = [[NSBundle mainBundle] URLForResource:@"gsx" withExtension:@"wv"]; if (![HeadphoneFilter validateImpulseFile:presetUrl]) presetUrl = nil; } if (presetUrl) { @synchronized(hFilter) { hFilter = [[HeadphoneFilter alloc] initWithImpulseFile:presetUrl forSampleRate:outputFormat.mSampleRate withInputChannels:inputFormat.mChannelsPerFrame]; } } } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { DLog(@"SOMETHING CHANGED!"); if ([keyPath isEqualToString:@"values.headphoneVirtualization"] || [keyPath isEqualToString:@"values.hrirPath"]) { // Reset the converter, without rebuffering [self setupVirt]; } } - (void) process:(const void *)inBuffer frameCount:(size_t)frames output:(void *)outBuffer { @synchronized (hFilter) { if ( hFilter ) { [hFilter process:(const float *) inBuffer sampleCount:frames toBuffer:(float *) outBuffer]; return; } } if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 ) { downmix_to_stereo( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, frames ); } else if ( inputFormat.mChannelsPerFrame > 1 && outputFormat.mChannelsPerFrame == 1 ) { downmix_to_mono( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, frames ); } else if ( inputFormat.mChannelsPerFrame < outputFormat.mChannelsPerFrame ) { upmix( (const float*) inBuffer, inputFormat.mChannelsPerFrame, (float*) outBuffer, outputFormat.mChannelsPerFrame, frames ); } else if ( inputFormat.mChannelsPerFrame == outputFormat.mChannelsPerFrame ) { memcpy(outBuffer, inBuffer, frames * outputFormat.mBytesPerPacket); } } @end