Bug Fix: Handle gaplessness for headphone filter

The filter uses a pre-buffer of input audio, so extrapolate from the
actual input to fill the buffer. Fixes clicking on non-zero-crossing
track endings.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2025-03-04 00:45:31 -08:00
parent ea7eff40e0
commit 68a146f6b8
3 changed files with 33 additions and 0 deletions

View file

@ -13,6 +13,8 @@
#import "DSPHRTFNode.h"
#import "lpc.h"
#import "HeadphoneFilter.h"
static void * kDSPHRTFNodeContext = &kDSPHRTFNodeContext;
@ -75,6 +77,8 @@ static void unregisterMotionListener(void) {
BOOL processEntered;
BOOL resetFilter;
size_t needPrefill;
BOOL observersapplied;
AudioStreamBasicDescription lastInputFormat;
@ -89,7 +93,11 @@ static void unregisterMotionListener(void) {
simd_float4x4 rotationMatrix;
simd_float4x4 referenceMatrix;
float prefillBuffer[4096 * 32];
float outBuffer[4096 * 2];
void *extrapolate_buffer;
size_t extrapolate_buffer_size;
}
+ (void)initialize {
@ -131,6 +139,11 @@ static void unregisterMotionListener(void) {
[self cleanUp];
[self removeObservers];
[super cleanUp];
if(extrapolate_buffer) {
free(extrapolate_buffer);
extrapolate_buffer = NULL;
extrapolate_buffer_size = 0;
}
}
- (void)addObservers {
@ -210,6 +223,8 @@ static void unregisterMotionListener(void) {
outputChannelConfig = AudioChannelSideLeft | AudioChannelSideRight;
resetFilter = NO;
needPrefill = [hrtf needPrefill];
} else {
if(lastEnableHeadTracking) {
lastEnableHeadTracking = NO;
@ -359,6 +374,18 @@ static void unregisterMotionListener(void) {
size_t frameCount = [chunk frameCount];
NSData *sampleData = [chunk removeSamples:frameCount];
if(needPrefill) {
size_t maxToUse = 4096 - needPrefill;
if(maxToUse > frameCount) {
maxToUse = frameCount;
}
size_t channels = inputFormat.mChannelsPerFrame;
memcpy(&prefillBuffer[needPrefill * channels], [sampleData bytes], maxToUse * sizeof(float) * channels);
lpc_extrapolate_bkwd(&prefillBuffer[needPrefill * channels], maxToUse, maxToUse, (int)channels, LPC_ORDER, needPrefill, &extrapolate_buffer, &extrapolate_buffer_size);
[hrtf process:&prefillBuffer[0] sampleCount:(int)needPrefill toBuffer:&outBuffer[0]];
needPrefill = 0;
}
[hrtf process:(const float *)[sampleData bytes] sampleCount:(int)frameCount toBuffer:&outBuffer[0]];
AudioChunk *outputChunk = [[AudioChunk alloc] init];

View file

@ -39,6 +39,8 @@
- (void)reset;
- (size_t)needPrefill;
@end
#endif /* HeadphoneFilter_h */

View file

@ -379,4 +379,8 @@ static impulseSetCache *_sharedController = nil;
}
}
- (size_t)needPrefill {
return paddedBufferSize;
}
@end