304 lines
10 KiB
Mathematica
304 lines
10 KiB
Mathematica
|
//
|
||
|
// 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
|
||
|
|