[FreeSurround] Experimental implementation code
This is a working implementation of FreeSurround, but I can't get it to work in the Cog code base, as the whole project crashes head over heels if this code is inserted into the output chain. Signed-off-by: Christopher Snowhill <kode54@gmail.com>xcode15
parent
34884d825a
commit
eb9d642192
|
@ -76,6 +76,12 @@
|
||||||
8328995A27CB51C900D7F028 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8328995927CB51C900D7F028 /* Security.framework */; };
|
8328995A27CB51C900D7F028 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8328995927CB51C900D7F028 /* Security.framework */; };
|
||||||
8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */; };
|
8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */; };
|
||||||
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */; };
|
8347C7422796C58800FA8A7D /* NSFileHandle+CreateFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */; };
|
||||||
|
834A41A9287A90AB00EB9D9B /* freesurround_decoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 834A41A5287A90AB00EB9D9B /* freesurround_decoder.h */; };
|
||||||
|
834A41AA287A90AB00EB9D9B /* freesurround_decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 834A41A6287A90AB00EB9D9B /* freesurround_decoder.cpp */; };
|
||||||
|
834A41AB287A90AB00EB9D9B /* channelmaps.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 834A41A7287A90AB00EB9D9B /* channelmaps.cpp */; };
|
||||||
|
834A41AC287A90AB00EB9D9B /* channelmaps.h in Headers */ = {isa = PBXBuildFile; fileRef = 834A41A8287A90AB00EB9D9B /* channelmaps.h */; };
|
||||||
|
834A41AF287ABD6F00EB9D9B /* FSurroundFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 834A41AD287ABD6F00EB9D9B /* FSurroundFilter.h */; };
|
||||||
|
834A41B0287ABD6F00EB9D9B /* FSurroundFilter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 834A41AE287ABD6F00EB9D9B /* FSurroundFilter.mm */; };
|
||||||
834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */ = {isa = PBXBuildFile; fileRef = 834FD4EA27AF8F380063BC83 /* AudioChunk.h */; };
|
834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */ = {isa = PBXBuildFile; fileRef = 834FD4EA27AF8F380063BC83 /* AudioChunk.h */; };
|
||||||
834FD4ED27AF91220063BC83 /* AudioChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = 834FD4EC27AF91220063BC83 /* AudioChunk.m */; };
|
834FD4ED27AF91220063BC83 /* AudioChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = 834FD4EC27AF91220063BC83 /* AudioChunk.m */; };
|
||||||
834FD4F027AF93680063BC83 /* ChunkList.h in Headers */ = {isa = PBXBuildFile; fileRef = 834FD4EE27AF93680063BC83 /* ChunkList.h */; };
|
834FD4F027AF93680063BC83 /* ChunkList.h in Headers */ = {isa = PBXBuildFile; fileRef = 834FD4EE27AF93680063BC83 /* ChunkList.h */; };
|
||||||
|
@ -212,6 +218,12 @@
|
||||||
8328995927CB51C900D7F028 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
8328995927CB51C900D7F028 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
|
||||||
8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSFileHandle+CreateFile.h"; path = "../../Utils/NSFileHandle+CreateFile.h"; sourceTree = "<group>"; };
|
8347C73F2796C58800FA8A7D /* NSFileHandle+CreateFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSFileHandle+CreateFile.h"; path = "../../Utils/NSFileHandle+CreateFile.h"; sourceTree = "<group>"; };
|
||||||
8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSFileHandle+CreateFile.m"; path = "../../Utils/NSFileHandle+CreateFile.m"; sourceTree = "<group>"; };
|
8347C7402796C58800FA8A7D /* NSFileHandle+CreateFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSFileHandle+CreateFile.m"; path = "../../Utils/NSFileHandle+CreateFile.m"; sourceTree = "<group>"; };
|
||||||
|
834A41A5287A90AB00EB9D9B /* freesurround_decoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = freesurround_decoder.h; sourceTree = "<group>"; };
|
||||||
|
834A41A6287A90AB00EB9D9B /* freesurround_decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = freesurround_decoder.cpp; sourceTree = "<group>"; };
|
||||||
|
834A41A7287A90AB00EB9D9B /* channelmaps.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = channelmaps.cpp; sourceTree = "<group>"; };
|
||||||
|
834A41A8287A90AB00EB9D9B /* channelmaps.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = channelmaps.h; sourceTree = "<group>"; };
|
||||||
|
834A41AD287ABD6F00EB9D9B /* FSurroundFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSurroundFilter.h; sourceTree = "<group>"; };
|
||||||
|
834A41AE287ABD6F00EB9D9B /* FSurroundFilter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSurroundFilter.mm; sourceTree = "<group>"; };
|
||||||
834FD4EA27AF8F380063BC83 /* AudioChunk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioChunk.h; sourceTree = "<group>"; };
|
834FD4EA27AF8F380063BC83 /* AudioChunk.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AudioChunk.h; sourceTree = "<group>"; };
|
||||||
834FD4EC27AF91220063BC83 /* AudioChunk.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AudioChunk.m; sourceTree = "<group>"; };
|
834FD4EC27AF91220063BC83 /* AudioChunk.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AudioChunk.m; sourceTree = "<group>"; };
|
||||||
834FD4EE27AF93680063BC83 /* ChunkList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ChunkList.h; sourceTree = "<group>"; };
|
834FD4EE27AF93680063BC83 /* ChunkList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ChunkList.h; sourceTree = "<group>"; };
|
||||||
|
@ -397,6 +409,8 @@
|
||||||
17D21C9B0B8BE4BA00D1EBDE /* Output */ = {
|
17D21C9B0B8BE4BA00D1EBDE /* Output */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
834A41AD287ABD6F00EB9D9B /* FSurroundFilter.h */,
|
||||||
|
834A41AE287ABD6F00EB9D9B /* FSurroundFilter.mm */,
|
||||||
839E56EB2879515D00DFB5F4 /* HeadphoneFilter.h */,
|
839E56EB2879515D00DFB5F4 /* HeadphoneFilter.h */,
|
||||||
839E56EC2879515D00DFB5F4 /* HeadphoneFilter.mm */,
|
839E56EC2879515D00DFB5F4 /* HeadphoneFilter.mm */,
|
||||||
17D21C9C0B8BE4BA00D1EBDE /* OutputAVFoundation.h */,
|
17D21C9C0B8BE4BA00D1EBDE /* OutputAVFoundation.h */,
|
||||||
|
@ -408,6 +422,7 @@
|
||||||
17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = {
|
17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
834A41A4287A90AB00EB9D9B /* fsurround */,
|
||||||
839E56E02879450300DFB5F4 /* hrtf */,
|
839E56E02879450300DFB5F4 /* hrtf */,
|
||||||
831A50152865A8800049CFE4 /* r8bstate.cpp */,
|
831A50152865A8800049CFE4 /* r8bstate.cpp */,
|
||||||
831A50172865A8B30049CFE4 /* r8bstate.h */,
|
831A50172865A8B30049CFE4 /* r8bstate.h */,
|
||||||
|
@ -507,6 +522,17 @@
|
||||||
path = simd;
|
path = simd;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
834A41A4287A90AB00EB9D9B /* fsurround */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
834A41A5287A90AB00EB9D9B /* freesurround_decoder.h */,
|
||||||
|
834A41A6287A90AB00EB9D9B /* freesurround_decoder.cpp */,
|
||||||
|
834A41A7287A90AB00EB9D9B /* channelmaps.cpp */,
|
||||||
|
834A41A8287A90AB00EB9D9B /* channelmaps.h */,
|
||||||
|
);
|
||||||
|
path = fsurround;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
835C88AE279811A500E28EAE /* hdcd */ = {
|
835C88AE279811A500E28EAE /* hdcd */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -566,7 +592,9 @@
|
||||||
17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */,
|
17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */,
|
||||||
831A4FE02865A7DC0049CFE4 /* pf_double.h in Headers */,
|
831A4FE02865A7DC0049CFE4 /* pf_double.h in Headers */,
|
||||||
831A50142865A7FD0049CFE4 /* r8bstate.hpp in Headers */,
|
831A50142865A7FD0049CFE4 /* r8bstate.hpp in Headers */,
|
||||||
|
834A41AC287A90AB00EB9D9B /* channelmaps.h in Headers */,
|
||||||
17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */,
|
17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */,
|
||||||
|
834A41A9287A90AB00EB9D9B /* freesurround_decoder.h in Headers */,
|
||||||
17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */,
|
17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */,
|
||||||
8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */,
|
8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */,
|
||||||
831A4FF32865A7DC0049CFE4 /* r8butil.h in Headers */,
|
831A4FF32865A7DC0049CFE4 /* r8butil.h in Headers */,
|
||||||
|
@ -601,6 +629,7 @@
|
||||||
17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */,
|
17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */,
|
||||||
8328995727CB51B700D7F028 /* SHA256Digest.h in Headers */,
|
8328995727CB51B700D7F028 /* SHA256Digest.h in Headers */,
|
||||||
834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */,
|
834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */,
|
||||||
|
834A41AF287ABD6F00EB9D9B /* FSurroundFilter.h in Headers */,
|
||||||
17A2D3C50B8D1D37000778C4 /* AudioDecoder.h in Headers */,
|
17A2D3C50B8D1D37000778C4 /* AudioDecoder.h in Headers */,
|
||||||
831A50092865A7DC0049CFE4 /* CDSPHBUpsampler.h in Headers */,
|
831A50092865A7DC0049CFE4 /* CDSPHBUpsampler.h in Headers */,
|
||||||
831A500E2865A7DC0049CFE4 /* r8bconf.h in Headers */,
|
831A500E2865A7DC0049CFE4 /* r8bconf.h in Headers */,
|
||||||
|
@ -705,6 +734,7 @@
|
||||||
831A50112865A7DC0049CFE4 /* r8bbase.cpp in Sources */,
|
831A50112865A7DC0049CFE4 /* r8bbase.cpp in Sources */,
|
||||||
83504166286447DA006B32CC /* Downmix.m in Sources */,
|
83504166286447DA006B32CC /* Downmix.m in Sources */,
|
||||||
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */,
|
8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */,
|
||||||
|
834A41AB287A90AB00EB9D9B /* channelmaps.cpp in Sources */,
|
||||||
831A50162865A8800049CFE4 /* r8bstate.cpp in Sources */,
|
831A50162865A8800049CFE4 /* r8bstate.cpp in Sources */,
|
||||||
17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */,
|
17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */,
|
||||||
17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */,
|
17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */,
|
||||||
|
@ -729,9 +759,11 @@
|
||||||
17B619310B909BC300BC003F /* AudioPropertiesReader.m in Sources */,
|
17B619310B909BC300BC003F /* AudioPropertiesReader.m in Sources */,
|
||||||
17ADB13D0B97926D00257CA2 /* AudioSource.m in Sources */,
|
17ADB13D0B97926D00257CA2 /* AudioSource.m in Sources */,
|
||||||
834FD4F127AF93680063BC83 /* ChunkList.m in Sources */,
|
834FD4F127AF93680063BC83 /* ChunkList.m in Sources */,
|
||||||
|
834A41B0287ABD6F00EB9D9B /* FSurroundFilter.mm in Sources */,
|
||||||
8EC122600B993BD500C5B3AD /* ConverterNode.m in Sources */,
|
8EC122600B993BD500C5B3AD /* ConverterNode.m in Sources */,
|
||||||
8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */,
|
8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */,
|
||||||
B0575F300D687A4000411D77 /* Helper.m in Sources */,
|
B0575F300D687A4000411D77 /* Helper.m in Sources */,
|
||||||
|
834A41AA287A90AB00EB9D9B /* freesurround_decoder.cpp in Sources */,
|
||||||
07DB5F3F0ED353A900C2E3EF /* AudioMetadataWriter.m in Sources */,
|
07DB5F3F0ED353A900C2E3EF /* AudioMetadataWriter.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// FSurroundFilter.h
|
||||||
|
// CogAudio
|
||||||
|
//
|
||||||
|
// Created by Christopher Snowhill on 7/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FSurroundFilter_h
|
||||||
|
#define FSurroundFilter_h
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#import <stdint.h>
|
||||||
|
|
||||||
|
#define FSurroundChunkSize 4096
|
||||||
|
|
||||||
|
@interface FSurroundFilter : NSObject {
|
||||||
|
void *decoder;
|
||||||
|
void *params;
|
||||||
|
double srate;
|
||||||
|
uint32_t channelCount;
|
||||||
|
uint32_t channelConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithSampleRate:(double)srate;
|
||||||
|
|
||||||
|
- (uint32_t)channelCount;
|
||||||
|
- (uint32_t)channelConfig;
|
||||||
|
|
||||||
|
- (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif /* FSurround_h */
|
|
@ -0,0 +1,144 @@
|
||||||
|
//
|
||||||
|
// FSurroundFilter.m
|
||||||
|
// CogAudio Framework
|
||||||
|
//
|
||||||
|
// Created by Christopher Snowhill on 7/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "FSurroundFilter.h"
|
||||||
|
|
||||||
|
#import "freesurround_decoder.h"
|
||||||
|
|
||||||
|
#import "AudioChunk.h"
|
||||||
|
|
||||||
|
#import <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
|
#import <map>
|
||||||
|
#import <vector>
|
||||||
|
|
||||||
|
struct freesurround_params {
|
||||||
|
// the user-configurable parameters
|
||||||
|
float center_image, shift, depth, circular_wrap, focus, front_sep, rear_sep, bass_lo, bass_hi;
|
||||||
|
bool use_lfe;
|
||||||
|
channel_setup channels_fs; // FreeSurround channel setup
|
||||||
|
std::vector<unsigned> chanmap; // FreeSurround -> WFX channel index translation (derived data for faster lookup)
|
||||||
|
|
||||||
|
// construct with defaults
|
||||||
|
freesurround_params()
|
||||||
|
: center_image(0.7), shift(0), depth(1), circular_wrap(90), focus(0), front_sep(1), rear_sep(1),
|
||||||
|
bass_lo(40), bass_hi(90), use_lfe(false) {
|
||||||
|
set_channels_fs(cs_5point1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the WFX version of the channel setup code
|
||||||
|
unsigned channel_count() {
|
||||||
|
return (unsigned)chanmap.size();
|
||||||
|
}
|
||||||
|
unsigned channels_wfx() {
|
||||||
|
unsigned res = 0;
|
||||||
|
for(unsigned i = 0; i < chanmap.size(); res |= chanmap[i++]) {};
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign a channel setup & recompute derived data
|
||||||
|
void set_channels_fs(channel_setup setup) {
|
||||||
|
channels_fs = setup;
|
||||||
|
chanmap.clear();
|
||||||
|
// Note: Because WFX does not define a few of the more exotic channels (side front left&right, side rear left&right, back center left&right),
|
||||||
|
// the side front/back channel pairs (both left and right sides, resp.) are mapped here onto foobar's top front/back channel pairs and the
|
||||||
|
// back (off-)center left/right channels are mapped onto foobar's top front center and top back center, respectively.
|
||||||
|
// Therefore, these speakers should be connected to those outputs instead.
|
||||||
|
std::map<channel_id, uint32_t> fs2wfx;
|
||||||
|
fs2wfx[ci_front_left] = AudioChannelFrontLeft;
|
||||||
|
fs2wfx[ci_front_center_left] = AudioChannelFrontCenterLeft;
|
||||||
|
fs2wfx[ci_front_center] = AudioChannelFrontCenter;
|
||||||
|
fs2wfx[ci_front_center_right] = AudioChannelFrontCenterRight;
|
||||||
|
fs2wfx[ci_front_right] = AudioChannelFrontRight;
|
||||||
|
fs2wfx[ci_side_front_left] = AudioChannelFrontLeft;
|
||||||
|
fs2wfx[ci_side_front_right] = AudioChannelTopFrontRight;
|
||||||
|
fs2wfx[ci_side_center_left] = AudioChannelSideLeft;
|
||||||
|
fs2wfx[ci_side_center_right] = AudioChannelSideRight;
|
||||||
|
fs2wfx[ci_side_back_left] = AudioChannelTopBackLeft;
|
||||||
|
fs2wfx[ci_side_back_right] = AudioChannelTopBackRight;
|
||||||
|
fs2wfx[ci_back_left] = AudioChannelBackLeft;
|
||||||
|
fs2wfx[ci_back_center_left] = AudioChannelTopFrontCenter;
|
||||||
|
fs2wfx[ci_back_center] = AudioChannelBackCenter;
|
||||||
|
fs2wfx[ci_back_center_right] = AudioChannelTopBackCenter;
|
||||||
|
fs2wfx[ci_back_right] = AudioChannelBackRight;
|
||||||
|
fs2wfx[ci_lfe] = AudioChannelLFE;
|
||||||
|
for(unsigned i = 0; i < freesurround_decoder::num_channels(channels_fs); i++)
|
||||||
|
chanmap.push_back(fs2wfx[freesurround_decoder::channel_at(channels_fs, i)]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@implementation FSurroundFilter
|
||||||
|
|
||||||
|
- (id)initWithSampleRate:(double)srate {
|
||||||
|
self = [super init];
|
||||||
|
if(!self) return nil;
|
||||||
|
|
||||||
|
self->srate = srate;
|
||||||
|
|
||||||
|
freesurround_params *_params = new freesurround_params;
|
||||||
|
params = (void *)_params;
|
||||||
|
|
||||||
|
freesurround_decoder *_decoder = new freesurround_decoder(cs_5point1, 1024);
|
||||||
|
decoder = (void *)_decoder;
|
||||||
|
|
||||||
|
_decoder->circular_wrap(_params->circular_wrap);
|
||||||
|
_decoder->shift(_params->shift);
|
||||||
|
_decoder->depth(_params->depth);
|
||||||
|
_decoder->focus(_params->focus);
|
||||||
|
_decoder->center_image(_params->center_image);
|
||||||
|
_decoder->front_separation(_params->front_sep);
|
||||||
|
_decoder->rear_separation(_params->rear_sep);
|
||||||
|
_decoder->bass_redirection(_params->use_lfe);
|
||||||
|
_decoder->low_cutoff(_params->bass_lo / (srate / 2.0));
|
||||||
|
_decoder->high_cutoff(_params->bass_hi / (srate / 2.0));
|
||||||
|
|
||||||
|
channelCount = _params->channel_count();
|
||||||
|
channelConfig = _params->channels_wfx();
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
if(decoder) {
|
||||||
|
freesurround_decoder *_decoder = (freesurround_decoder *)decoder;
|
||||||
|
delete _decoder;
|
||||||
|
}
|
||||||
|
if(params) {
|
||||||
|
freesurround_params *_params = (freesurround_params *)params;
|
||||||
|
delete _params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (uint32_t)channelCount {
|
||||||
|
return channelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (uint32_t)channelConfig {
|
||||||
|
return channelConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)process:(const float *)samplesIn output:(float *)samplesOut count:(uint32_t)count {
|
||||||
|
freesurround_params *_params = (freesurround_params *)params;
|
||||||
|
freesurround_decoder *_decoder = (freesurround_decoder *)decoder;
|
||||||
|
|
||||||
|
float tempInput[1024 * 2];
|
||||||
|
|
||||||
|
if(count < 1024) {
|
||||||
|
cblas_scopy(count * 2, samplesIn, 1, &tempInput[0], 1);
|
||||||
|
vDSP_vclr(&tempInput[count * 2], 1, 2048 - count * 2);
|
||||||
|
samplesIn = &tempInput[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
float *src = _decoder->decode(samplesIn);
|
||||||
|
|
||||||
|
for(unsigned c = 0, num = channelCount; c < num; c++) {
|
||||||
|
unsigned idx = [AudioChunk channelIndexFromConfig:channelConfig forFlag:_params->chanmap[c]];
|
||||||
|
cblas_scopy(count, src + c, num, samplesOut + idx, num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2010 Christian Kothe
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU General Public License
|
||||||
|
as published by the Free Software Foundation; either version 2
|
||||||
|
of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CHANNELMAPS_H
|
||||||
|
#define CHANNELMAPS_H
|
||||||
|
#include "freesurround_decoder.h"
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
const int grid_res = 21; // resolution of the lookup grid
|
||||||
|
|
||||||
|
// channel allocation maps (per setup)
|
||||||
|
typedef std::vector<std::vector<float*> > alloc_lut;
|
||||||
|
extern std::map<unsigned, alloc_lut> chn_alloc;
|
||||||
|
// channel metadata maps (per setup)
|
||||||
|
extern std::map<unsigned, std::vector<float> > chn_angle;
|
||||||
|
extern std::map<unsigned, std::vector<float> > chn_xsf;
|
||||||
|
extern std::map<unsigned, std::vector<float> > chn_ysf;
|
||||||
|
extern std::map<unsigned, std::vector<channel_id> > chn_id;
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,416 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2007-2010 Christian Kothe
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU General Public License
|
||||||
|
as published by the Free Software Foundation; either version 2
|
||||||
|
of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "freesurround_decoder.h"
|
||||||
|
#include "channelmaps.h"
|
||||||
|
#include <Accelerate/Accelerate.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
#pragma warning(disable : 4244)
|
||||||
|
|
||||||
|
#define pi _pi
|
||||||
|
const float _pi = 3.141592654f;
|
||||||
|
const float epsilon = 0.000001f;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#undef min
|
||||||
|
#undef max
|
||||||
|
|
||||||
|
static void *_memalign_malloc(size_t size, size_t align) {
|
||||||
|
void *ret = NULL;
|
||||||
|
if(posix_memalign(&ret, align, size) != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _dsp_complexalloc(DSPDoubleSplitComplex *cpx, int count) {
|
||||||
|
cpx->realp = (double *)_memalign_malloc(count * sizeof(double), 16);
|
||||||
|
cpx->imagp = (double *)_memalign_malloc(count * sizeof(double), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _dsp_complexfree(DSPDoubleSplitComplex *cpx) {
|
||||||
|
free(cpx->realp);
|
||||||
|
free(cpx->imagp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int log2n(int n) {
|
||||||
|
int pow = 1;
|
||||||
|
while(n > 2) {
|
||||||
|
pow++;
|
||||||
|
n /= 2;
|
||||||
|
}
|
||||||
|
return pow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeSurround implementation
|
||||||
|
class decoder_impl {
|
||||||
|
public:
|
||||||
|
// instantiate the decoder with a given channel setup and processing block size (in samples)
|
||||||
|
decoder_impl(channel_setup setup, unsigned N)
|
||||||
|
: N(N), log2N(log2n(N)), Nover2((N + 1) / 2),
|
||||||
|
wnd(N), inbuf(3 * N), setup(setup), C((unsigned)chn_alloc[setup].size()),
|
||||||
|
buffer_empty(true), lt(N), rt(N), dst(N), dstf(N),
|
||||||
|
dftsetupF(vDSP_DFT_zrop_CreateSetupD(0, N, vDSP_DFT_FORWARD)),
|
||||||
|
dftsetupB(vDSP_DFT_zrop_CreateSetupD(0, N, vDSP_DFT_INVERSE)) {
|
||||||
|
_dsp_complexalloc(&lf, Nover2);
|
||||||
|
_dsp_complexalloc(&rf, Nover2);
|
||||||
|
|
||||||
|
// allocate per-channel buffers
|
||||||
|
outbuf.resize((N + N / 2) * C);
|
||||||
|
signal.resize(C);
|
||||||
|
for(unsigned k = 0; k < C; k++)
|
||||||
|
_dsp_complexalloc(&signal[k], N);
|
||||||
|
|
||||||
|
// init the window function
|
||||||
|
for(unsigned k = 0; k < N; k++)
|
||||||
|
wnd[k] = sqrt(0.5 * (1 - cos(2 * pi * k / N)) / N);
|
||||||
|
|
||||||
|
// set default parameters
|
||||||
|
set_circular_wrap(90);
|
||||||
|
set_shift(0);
|
||||||
|
set_depth(1);
|
||||||
|
set_focus(0);
|
||||||
|
set_center_image(1);
|
||||||
|
set_front_separation(1);
|
||||||
|
set_rear_separation(1);
|
||||||
|
set_low_cutoff(40.0 / 22050);
|
||||||
|
set_high_cutoff(90.0 / 22050);
|
||||||
|
set_bass_redirection(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
~decoder_impl() {
|
||||||
|
_dsp_complexfree(&lf);
|
||||||
|
_dsp_complexfree(&rf);
|
||||||
|
|
||||||
|
for(unsigned k = 0; k < C; k++)
|
||||||
|
_dsp_complexfree(&signal[k]);
|
||||||
|
|
||||||
|
vDSP_DFT_DestroySetupD(dftsetupF);
|
||||||
|
vDSP_DFT_DestroySetupD(dftsetupB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode a stereo chunk, produces a multichannel chunk of the same size (lagged)
|
||||||
|
float *decode(const float *input) {
|
||||||
|
// append incoming data to the end of the input buffer
|
||||||
|
memcpy(&inbuf[N], &input[0], 8 * N);
|
||||||
|
// process first and second half, overlapped
|
||||||
|
buffered_decode(&inbuf[0]);
|
||||||
|
buffered_decode(&inbuf[N]);
|
||||||
|
// shift last half of the input to the beginning (for overlapping with a future block)
|
||||||
|
memcpy(&inbuf[0], &inbuf[2 * N], 4 * N);
|
||||||
|
buffer_empty = false;
|
||||||
|
return &outbuf[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush the internal buffers
|
||||||
|
void flush() {
|
||||||
|
memset(&outbuf[0], 0, outbuf.size() * 4);
|
||||||
|
memset(&inbuf[0], 0, inbuf.size() * 4);
|
||||||
|
buffer_empty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// number of samples currently held in the buffer
|
||||||
|
unsigned buffered() {
|
||||||
|
return buffer_empty ? 0 : N / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set soundfield & rendering parameters
|
||||||
|
void set_circular_wrap(float v) {
|
||||||
|
circular_wrap = v;
|
||||||
|
}
|
||||||
|
void set_shift(float v) {
|
||||||
|
shift = v;
|
||||||
|
}
|
||||||
|
void set_depth(float v) {
|
||||||
|
depth = v;
|
||||||
|
}
|
||||||
|
void set_focus(float v) {
|
||||||
|
focus = v;
|
||||||
|
}
|
||||||
|
void set_center_image(float v) {
|
||||||
|
center_image = v;
|
||||||
|
}
|
||||||
|
void set_front_separation(float v) {
|
||||||
|
front_separation = v;
|
||||||
|
}
|
||||||
|
void set_rear_separation(float v) {
|
||||||
|
rear_separation = v;
|
||||||
|
}
|
||||||
|
void set_low_cutoff(float v) {
|
||||||
|
lo_cut = v * (N / 2);
|
||||||
|
}
|
||||||
|
void set_high_cutoff(float v) {
|
||||||
|
hi_cut = v * (N / 2);
|
||||||
|
}
|
||||||
|
void set_bass_redirection(bool v) {
|
||||||
|
use_lfe = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// helper functions
|
||||||
|
static inline float sqr(double x) {
|
||||||
|
return x * x;
|
||||||
|
}
|
||||||
|
static inline double amplitude(const DSPDoubleSplitComplex &cpx, size_t index) {
|
||||||
|
return sqrt(sqr(cpx.realp[index]) + sqr(cpx.imagp[index]));
|
||||||
|
}
|
||||||
|
static inline double phase(const DSPDoubleSplitComplex &cpx, size_t index) {
|
||||||
|
return atan2(cpx.imagp[index], cpx.realp[index]);
|
||||||
|
}
|
||||||
|
static inline void polar(double a, double p, DSPDoubleSplitComplex &cpx, size_t index) {
|
||||||
|
cpx.realp[index] = a * cos(p);
|
||||||
|
cpx.imagp[index] = a * sin(p);
|
||||||
|
}
|
||||||
|
static inline float min(double a, double b) {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
static inline float max(double a, double b) {
|
||||||
|
return a > b ? a : b;
|
||||||
|
}
|
||||||
|
static inline float clamp(double x) {
|
||||||
|
return max(-1, min(1, x));
|
||||||
|
}
|
||||||
|
static inline float sign(double x) {
|
||||||
|
return x < 0 ? -1 : (x > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
// get the distance of the soundfield edge, along a given angle
|
||||||
|
static inline double edgedistance(double a) {
|
||||||
|
return min(sqrt(1 + sqr(tan(a))), sqrt(1 + sqr(1 / tan(a))));
|
||||||
|
}
|
||||||
|
// get the index (and fractional offset!) in a piecewise-linear channel allocation grid
|
||||||
|
int map_to_grid(double &x) {
|
||||||
|
double gp = ((x + 1) * 0.5) * (grid_res - 1), i = min(grid_res - 2, floor(gp));
|
||||||
|
x = gp - i;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode a block of data and overlap-add it into outbuf
|
||||||
|
void buffered_decode(float *input) {
|
||||||
|
// demultiplex and apply window function
|
||||||
|
vDSP_vspdp(input, 2, <[0], 1, N);
|
||||||
|
vDSP_vspdp(input + 1, 2, &rt[0], 1, N);
|
||||||
|
vDSP_vmulD(<[0], 1, &wnd[0], 1, <[0], 1, N);
|
||||||
|
vDSP_vmulD(&rt[0], 1, &wnd[0], 1, &rt[0], 1, N);
|
||||||
|
|
||||||
|
// map into spectral domain
|
||||||
|
vDSP_ctozD((DSPDoubleComplex *)(<[0]), 2, &lf, 1, Nover2);
|
||||||
|
vDSP_ctozD((DSPDoubleComplex *)(&rt[0]), 2, &rf, 1, Nover2);
|
||||||
|
|
||||||
|
vDSP_DFT_ExecuteD(dftsetupF, lf.realp, lf.imagp, lf.realp, lf.imagp);
|
||||||
|
vDSP_DFT_ExecuteD(dftsetupF, rf.realp, rf.imagp, rf.realp, rf.imagp);
|
||||||
|
|
||||||
|
for(unsigned c = 0; c < C; c++) {
|
||||||
|
signal[c].realp[0] = 0;
|
||||||
|
signal[c].imagp[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute multichannel output signal in the spectral domain
|
||||||
|
for(unsigned f = 1; f < N / 2; f++) {
|
||||||
|
// get Lt/Rt amplitudes & phases
|
||||||
|
double ampL = amplitude(lf, f), ampR = amplitude(rf, f);
|
||||||
|
double phaseL = phase(lf, f), phaseR = phase(rf, f);
|
||||||
|
// calculate the amplitude & phase differences
|
||||||
|
double ampDiff = clamp((ampL + ampR < epsilon) ? 0 : (ampR - ampL) / (ampR + ampL));
|
||||||
|
double phaseDiff = abs(phaseL - phaseR);
|
||||||
|
if(phaseDiff > pi) phaseDiff = 2 * pi - phaseDiff;
|
||||||
|
|
||||||
|
// decode into x/y soundfield position
|
||||||
|
double x, y;
|
||||||
|
transform_decode(ampDiff, phaseDiff, x, y);
|
||||||
|
// add wrap control
|
||||||
|
transform_circular_wrap(x, y, circular_wrap);
|
||||||
|
// add shift control
|
||||||
|
y = clamp(y - shift);
|
||||||
|
// add depth control
|
||||||
|
y = clamp(1 - (1 - y) * depth);
|
||||||
|
// add focus control
|
||||||
|
transform_focus(x, y, focus);
|
||||||
|
// add crossfeed control
|
||||||
|
x = clamp(x * (front_separation * (1 + y) / 2 + rear_separation * (1 - y) / 2));
|
||||||
|
|
||||||
|
// get total signal amplitude
|
||||||
|
double amp_total = sqrt(ampL * ampL + ampR * ampR);
|
||||||
|
// and total L/C/R signal phases
|
||||||
|
double phase_of[] = { phaseL, atan2(lf.imagp[f] + rf.imagp[f], lf.realp[f] + rf.realp[f]), phaseR };
|
||||||
|
// compute 2d channel map indexes p/q and update x/y to fractional offsets in the map grid
|
||||||
|
int p = map_to_grid(x), q = map_to_grid(y);
|
||||||
|
// map position to channel volumes
|
||||||
|
for(unsigned c = 0; c < C - 1; c++) {
|
||||||
|
// look up channel map at respective position (with bilinear interpolation) and build the signal
|
||||||
|
vector<float *> &a = chn_alloc[setup][c];
|
||||||
|
polar(amp_total * ((1 - x) * (1 - y) * a[q][p] + x * (1 - y) * a[q][p + 1] + (1 - x) * y * a[q + 1][p] + x * y * a[q + 1][p + 1]),
|
||||||
|
phase_of[1 + (int)sign(chn_xsf[setup][c])], signal[c], f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionally redirect bass
|
||||||
|
if(use_lfe && f < hi_cut) {
|
||||||
|
// level of LFE channel according to normalized frequency
|
||||||
|
double lfe_level = f < lo_cut ? 1 : 0.5 * (1 + cos(pi * (f - lo_cut) / (hi_cut - lo_cut)));
|
||||||
|
// assign LFE channel
|
||||||
|
polar(amp_total, phase_of[1], signal[C - 1], f);
|
||||||
|
signal[C - 1].realp[f] *= lfe_level;
|
||||||
|
signal[C - 1].imagp[f] *= lfe_level;
|
||||||
|
// subtract the signal from the other channels
|
||||||
|
for(unsigned c = 0; c < C - 1; c++) {
|
||||||
|
signal[c].realp[f] *= (1 - lfe_level);
|
||||||
|
signal[c].imagp[f] *= (1 - lfe_level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift the last 2/3 to the first 2/3 of the output buffer
|
||||||
|
memcpy(&outbuf[0], &outbuf[C * N / 2], N * C * 4);
|
||||||
|
// and clear the rest
|
||||||
|
memset(&outbuf[C * N], 0, C * 4 * N / 2);
|
||||||
|
// backtransform each channel and overlap-add
|
||||||
|
for(unsigned c = 0; c < C; c++) {
|
||||||
|
// back-transform into time domain
|
||||||
|
vDSP_DFT_ExecuteD(dftsetupB, signal[c].realp, signal[c].imagp, signal[c].realp, signal[c].imagp);
|
||||||
|
vDSP_ztocD(&signal[c], 1, (DSPDoubleComplex *)(&dst[0]), 2, Nover2);
|
||||||
|
// add the result to the last 2/3 of the output buffer, windowed (and remultiplex)
|
||||||
|
vDSP_vmulD(&dst[0], 1, &wnd[0], 1, &dst[0], 1, N);
|
||||||
|
vDSP_vdpsp(&dst[0], 1, &dstf[0], 1, N);
|
||||||
|
vDSP_vadd(&outbuf[C * N / 2 + c], C, &dstf[0], 1, &outbuf[C * N / 2 + c], C, N);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform amp/phase difference space into x/y soundfield space
|
||||||
|
void transform_decode(double a, double p, double &x, double &y) {
|
||||||
|
x = clamp(1.0047 * a + 0.46804 * a * p * p * p - 0.2042 * a * p * p * p * p + 0.0080586 * a * p * p * p * p * p * p * p - 0.0001526 * a * p * p * p * p * p * p * p * p * p * p - 0.073512 * a * a * a * p - 0.2499 * a * a * a * p * p * p * p + 0.016932 * a * a * a * p * p * p * p * p * p * p - 0.00027707 * a * a * a * p * p * p * p * p * p * p * p * p * p + 0.048105 * a * a * a * a * a * p * p * p * p * p * p * p - 0.0065947 * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p + 0.0016006 * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p * p - 0.0071132 * a * a * a * a * a * a * a * p * p * p * p * p * p * p * p * p + 0.0022336 * a * a * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p * p - 0.0004804 * a * a * a * a * a * a * a * p * p * p * p * p * p * p * p * p * p * p * p);
|
||||||
|
y = clamp(0.98592 - 0.62237 * p + 0.077875 * p * p - 0.0026929 * p * p * p * p * p + 0.4971 * a * a * p - 0.00032124 * a * a * p * p * p * p * p * p + 9.2491e-006 * a * a * a * a * p * p * p * p * p * p * p * p * p * p + 0.051549 * a * a * a * a * a * a * a * a + 1.0727e-014 * a * a * a * a * a * a * a * a * a * a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply a circular_wrap transformation to some position
|
||||||
|
void transform_circular_wrap(double &x, double &y, double refangle) {
|
||||||
|
if(refangle == 90)
|
||||||
|
return;
|
||||||
|
refangle = refangle * pi / 180;
|
||||||
|
double baseangle = 90 * pi / 180;
|
||||||
|
// translate into edge-normalized polar coordinates
|
||||||
|
double ang = atan2(x, y), len = sqrt(x * x + y * y);
|
||||||
|
len = len / edgedistance(ang);
|
||||||
|
// apply circular_wrap transform
|
||||||
|
if(abs(ang) < baseangle / 2)
|
||||||
|
// angle falls within the front region (to be enlarged)
|
||||||
|
ang *= refangle / baseangle;
|
||||||
|
else
|
||||||
|
// angle falls within the rear region (to be shrunken)
|
||||||
|
ang = pi - (-(((refangle - 2 * pi) * (pi - abs(ang)) * sign(ang)) / (2 * pi - baseangle)));
|
||||||
|
// translate back into soundfield position
|
||||||
|
len = len * edgedistance(ang);
|
||||||
|
x = clamp(sin(ang) * len);
|
||||||
|
y = clamp(cos(ang) * len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply a focus transformation to some position
|
||||||
|
void transform_focus(double &x, double &y, double focus) {
|
||||||
|
if(focus == 0)
|
||||||
|
return;
|
||||||
|
// translate into edge-normalized polar coordinates
|
||||||
|
double ang = atan2(x, y), len = clamp(sqrt(x * x + y * y) / edgedistance(ang));
|
||||||
|
// apply focus
|
||||||
|
len = focus > 0 ? 1 - pow(1 - len, 1 + focus * 20) : pow(len, 1 - focus * 20);
|
||||||
|
// back-transform into euclidian soundfield position
|
||||||
|
len = len * edgedistance(ang);
|
||||||
|
x = clamp(sin(ang) * len);
|
||||||
|
y = clamp(cos(ang) * len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// constants
|
||||||
|
unsigned N, C; // number of samples per input/output block, number of output channels
|
||||||
|
unsigned log2N, Nover2; // derivations of the block size
|
||||||
|
channel_setup setup; // the channel setup
|
||||||
|
|
||||||
|
// parameters
|
||||||
|
float circular_wrap; // angle of the front soundstage around the listener (90<39>=default)
|
||||||
|
float shift; // forward/backward offset of the soundstage
|
||||||
|
float depth; // backward extension of the soundstage
|
||||||
|
float focus; // localization of the sound events
|
||||||
|
float center_image; // presence of the center speaker
|
||||||
|
float front_separation; // front stereo separation
|
||||||
|
float rear_separation; // rear stereo separation
|
||||||
|
float lo_cut, hi_cut; // LFE cutoff frequencies
|
||||||
|
bool use_lfe; // whether to use the LFE channel
|
||||||
|
|
||||||
|
// FFT data structures
|
||||||
|
vector<double> lt, rt, dst; // left total, right total (source arrays), time-domain destination buffer array
|
||||||
|
vector<float> dstf; // float conversion destination array
|
||||||
|
DSPDoubleSplitComplex lf, rf; // left total / right total in frequency domain
|
||||||
|
vDSP_DFT_SetupD dftsetupF, dftsetupB; // FFT objects
|
||||||
|
|
||||||
|
// buffers
|
||||||
|
bool buffer_empty; // whether the buffer is currently empty or dirty
|
||||||
|
vector<float> inbuf; // stereo input buffer (multiplexed)
|
||||||
|
vector<float> outbuf; // multichannel output buffer (multiplexed)
|
||||||
|
vector<double> wnd; // the window function, precomputed
|
||||||
|
vector<DSPDoubleSplitComplex> signal; // the signal to be constructed in every channel, in the frequency domain
|
||||||
|
};
|
||||||
|
|
||||||
|
// implementation of the shell class
|
||||||
|
freesurround_decoder::freesurround_decoder(channel_setup setup, unsigned blocksize)
|
||||||
|
: impl(new decoder_impl(setup, blocksize)) {
|
||||||
|
}
|
||||||
|
freesurround_decoder::~freesurround_decoder() {
|
||||||
|
delete impl;
|
||||||
|
}
|
||||||
|
float *freesurround_decoder::decode(const float *input) {
|
||||||
|
return impl->decode(input);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::flush() {
|
||||||
|
impl->flush();
|
||||||
|
}
|
||||||
|
void freesurround_decoder::circular_wrap(float v) {
|
||||||
|
impl->set_circular_wrap(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::shift(float v) {
|
||||||
|
impl->set_shift(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::depth(float v) {
|
||||||
|
impl->set_depth(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::focus(float v) {
|
||||||
|
impl->set_focus(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::center_image(float v) {
|
||||||
|
impl->set_center_image(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::front_separation(float v) {
|
||||||
|
impl->set_front_separation(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::rear_separation(float v) {
|
||||||
|
impl->set_rear_separation(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::low_cutoff(float v) {
|
||||||
|
impl->set_low_cutoff(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::high_cutoff(float v) {
|
||||||
|
impl->set_high_cutoff(v);
|
||||||
|
}
|
||||||
|
void freesurround_decoder::bass_redirection(bool v) {
|
||||||
|
impl->set_bass_redirection(v);
|
||||||
|
}
|
||||||
|
unsigned freesurround_decoder::buffered() {
|
||||||
|
return impl->buffered();
|
||||||
|
}
|
||||||
|
unsigned freesurround_decoder::num_channels(channel_setup s) {
|
||||||
|
return (unsigned)chn_id[s].size();
|
||||||
|
}
|
||||||
|
channel_id freesurround_decoder::channel_at(channel_setup s, unsigned i) {
|
||||||
|
return i < chn_id[s].size() ? chn_id[s][i] : ci_none;
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2007-2010 Christian Kothe
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU General Public License
|
||||||
|
as published by the Free Software Foundation; either version 2
|
||||||
|
of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FREESURROUND_DECODER_H
|
||||||
|
#define FREESURROUND_DECODER_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifiers for the supported output channels (from front to back, left to right).
|
||||||
|
* The ordering here also determines the ordering of interleaved samples in the output signal.
|
||||||
|
*/
|
||||||
|
typedef enum channel_id {
|
||||||
|
ci_none = 0,
|
||||||
|
ci_front_left = 1 << 1,
|
||||||
|
ci_front_center_left = 1 << 2,
|
||||||
|
ci_front_center = 1 << 3,
|
||||||
|
ci_front_center_right = 1 << 4,
|
||||||
|
ci_front_right = 1 << 5,
|
||||||
|
ci_side_front_left = 1 << 6,
|
||||||
|
ci_side_front_right = 1 << 7,
|
||||||
|
ci_side_center_left = 1 << 8,
|
||||||
|
ci_side_center_right = 1 << 9,
|
||||||
|
ci_side_back_left = 1 << 10,
|
||||||
|
ci_side_back_right = 1 << 11,
|
||||||
|
ci_back_left = 1 << 12,
|
||||||
|
ci_back_center_left = 1 << 13,
|
||||||
|
ci_back_center = 1 << 14,
|
||||||
|
ci_back_center_right = 1 << 15,
|
||||||
|
ci_back_right = 1 << 16,
|
||||||
|
ci_lfe = 1 << 31
|
||||||
|
} channel_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The supported output channel setups.
|
||||||
|
* A channel setup is defined by the set of channels that are present. Here is a graphic
|
||||||
|
* of the cs_5point1 setup: http://en.wikipedia.org/wiki/File:5_1_channels_(surround_sound)_label.svg
|
||||||
|
*/
|
||||||
|
typedef enum channel_setup {
|
||||||
|
cs_stereo = ci_front_left | ci_front_right | ci_lfe,
|
||||||
|
cs_3stereo = ci_front_left | ci_front_center | ci_front_right | ci_lfe,
|
||||||
|
cs_5stereo = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right | ci_lfe,
|
||||||
|
cs_4point1 = ci_front_left | ci_front_right | ci_back_left | ci_back_right | ci_lfe,
|
||||||
|
cs_5point1 = ci_front_left | ci_front_center | ci_front_right | ci_back_left | ci_back_right | ci_lfe,
|
||||||
|
cs_6point1 = ci_front_left | ci_front_center | ci_front_right | ci_side_center_left | ci_side_center_right | ci_back_center | ci_lfe,
|
||||||
|
cs_7point1 = ci_front_left | ci_front_center | ci_front_right | ci_side_center_left | ci_side_center_right | ci_back_left | ci_back_right | ci_lfe,
|
||||||
|
cs_7point1_panorama = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||||
|
ci_side_center_left | ci_side_center_right | ci_lfe,
|
||||||
|
cs_7point1_tricenter = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||||
|
ci_back_left | ci_back_right | ci_lfe,
|
||||||
|
cs_8point1 = ci_front_left | ci_front_center | ci_front_right | ci_side_center_left | ci_side_center_right |
|
||||||
|
ci_back_left | ci_back_center | ci_back_right | ci_lfe,
|
||||||
|
cs_9point1_densepanorama = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||||
|
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right | ci_lfe,
|
||||||
|
cs_9point1_wrap = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||||
|
ci_side_center_left | ci_side_center_right | ci_back_left | ci_back_right | ci_lfe,
|
||||||
|
cs_11point1_densewrap = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||||
|
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right |
|
||||||
|
ci_side_back_left | ci_side_back_right | ci_lfe,
|
||||||
|
cs_13point1_totalwrap = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||||
|
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right |
|
||||||
|
ci_side_back_left | ci_side_back_right | ci_back_left | ci_back_right | ci_lfe,
|
||||||
|
cs_16point1 = ci_front_left | ci_front_center_left | ci_front_center | ci_front_center_right | ci_front_right |
|
||||||
|
ci_side_front_left | ci_side_front_right | ci_side_center_left | ci_side_center_right | ci_side_back_left |
|
||||||
|
ci_side_back_right | ci_back_left | ci_back_center_left | ci_back_center | ci_back_center_right | ci_back_right | ci_lfe,
|
||||||
|
cs_legacy = 0 // same channels as cs_5point1 but different upmixing transform; does not support the focus control
|
||||||
|
} channel_setup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FreeSurround decoder.
|
||||||
|
*/
|
||||||
|
class freesurround_decoder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create an instance of the decoder.
|
||||||
|
* @param setup The output channel setup -- determines the number of output channels
|
||||||
|
* and their place in the sound field.
|
||||||
|
* @param blocksize Granularity at which data is processed by the decode() function.
|
||||||
|
* Must be a power of two and should correspond to ca. 10ms worth of single-channel
|
||||||
|
* samples (default is 4096 for 44.1Khz data). Do not make it shorter or longer
|
||||||
|
* than 5ms to 20ms since the granularity at which locations are decoded
|
||||||
|
* changes with this.
|
||||||
|
*/
|
||||||
|
freesurround_decoder(channel_setup setup = cs_5point1, unsigned blocksize = 4096);
|
||||||
|
~freesurround_decoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a chunk of stereo sound. The output is delayed by half of the blocksize.
|
||||||
|
* This function is the only one needed for straightforward decoding.
|
||||||
|
* @param input Contains exactly blocksize (multiplexed) stereo samples, i.e. 2*blocksize numbers.
|
||||||
|
* @return A pointer to an internal buffer of exactly blocksize (multiplexed) multichannel samples.
|
||||||
|
* The actual number of values depends on the number of output channels in the chosen
|
||||||
|
* channel setup.
|
||||||
|
*/
|
||||||
|
float *decode(const float *input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush the internal buffer.
|
||||||
|
*/
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
// --- soundfield transformations
|
||||||
|
// These functions allow to set up geometric transformations of the sound field after it has been decoded.
|
||||||
|
// The sound field is best pictured as a 2-dimensional square with the listener in its
|
||||||
|
// center which can be shifted or stretched in various ways before it is sent to the
|
||||||
|
// speakers. The order in which these transformations are applied is as listed below.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to wrap the soundfield around the listener in a circular manner.
|
||||||
|
* Determines the angle of the frontal sound stage relative to the listener, in degrees.
|
||||||
|
* A setting of 90<EFBFBD> corresponds to standard surround decoding, 180<EFBFBD> stretches the front stage from
|
||||||
|
* ear to ear, 270<EFBFBD> wraps it around most of the head. The side and rear content of the sound
|
||||||
|
* field is compressed accordingly behind the listerer. (default: 90, range: [0<EFBFBD>..360<EFBFBD>])
|
||||||
|
*/
|
||||||
|
void circular_wrap(float v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to shift the soundfield forward or backward.
|
||||||
|
* Value range: [-1.0..+1.0]. 0 is no offset, positive values move the sound
|
||||||
|
* forward, negative values move it backwards. (default: 0)
|
||||||
|
*/
|
||||||
|
void shift(float v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to scale the soundfield backwards.
|
||||||
|
* Value range: [0.0..+5.0] -- 0 is all compressed to the front, 1 is no change, 5 is scaled 5x backwards (default: 1)
|
||||||
|
*/
|
||||||
|
void depth(float v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to control the localization (i.e., focality) of sources.
|
||||||
|
* Value range: [-1.0..+1.0] -- 0 means unchanged, positive means more localized, negative means more ambient (default: 0)
|
||||||
|
*/
|
||||||
|
void focus(float v);
|
||||||
|
|
||||||
|
// --- rendering parameters
|
||||||
|
// These parameters control how the sound field is mapped onto speakers.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the presence of the front center channel(s).
|
||||||
|
* Value range: [0.0..1.0] -- fully present at 1.0, fully replaced by left/right at 0.0 (default: 1).
|
||||||
|
* The default of 1.0 results in spec-conformant decoding ("movie mode") while a value of 0.7 is
|
||||||
|
* better suited for music reproduction (which is usually mixed without a center channel).
|
||||||
|
*/
|
||||||
|
void center_image(float v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the front stereo separation.
|
||||||
|
* Value range: [0.0..inf] -- 1.0 is default, 0.0 is mono.
|
||||||
|
*/
|
||||||
|
void front_separation(float v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the rear stereo separation.
|
||||||
|
* Value range: [0.0..inf] -- 1.0 is default, 0.0 is mono.
|
||||||
|
*/
|
||||||
|
void rear_separation(float v);
|
||||||
|
|
||||||
|
// --- bass redirection (to LFE)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/disable LFE channel (default: false = disabled)
|
||||||
|
*/
|
||||||
|
void bass_redirection(bool v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the lower end of the transition band, in Hz/Nyquist (default: 40/22050).
|
||||||
|
*/
|
||||||
|
void low_cutoff(float v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the upper end of the transition band, in Hz/Nyquist (default: 90/22050).
|
||||||
|
*/
|
||||||
|
void high_cutoff(float v);
|
||||||
|
|
||||||
|
// --- info
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of samples currently held in the buffer.
|
||||||
|
*/
|
||||||
|
unsigned buffered();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of channels in the given setup.
|
||||||
|
*/
|
||||||
|
static unsigned num_channels(channel_setup s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel id of the i'th channel in the given setup.
|
||||||
|
*/
|
||||||
|
static channel_id channel_at(channel_setup s, unsigned i);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class decoder_impl *impl; // private implementation
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue