diff --git a/Application/PlaybackController.m b/Application/PlaybackController.m index c7d920962..28685ec9a 100644 --- a/Application/PlaybackController.m +++ b/Application/PlaybackController.m @@ -125,10 +125,8 @@ static double reverseSpeedScale(double input, double min, double max) { [audioPlayer setVolume:volume]; double pitch = [[NSUserDefaults standardUserDefaults] doubleForKey:@"pitch"]; - [audioPlayer setPitch:pitch]; [pitchSlider setDoubleValue:reverseSpeedScale(pitch, [pitchSlider minValue], [pitchSlider maxValue])]; double tempo = [[NSUserDefaults standardUserDefaults] doubleForKey:@"tempo"]; - [audioPlayer setTempo:tempo]; [tempoSlider setDoubleValue:reverseSpeedScale(tempo, [tempoSlider minValue], [tempoSlider maxValue])]; BOOL speedLock = [[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]; @@ -507,14 +505,10 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { const double pitch = speedScale([sender doubleValue], [pitchSlider minValue], [pitchSlider maxValue]); DLog(@"PITCH: %lf", pitch); - [audioPlayer setPitch:pitch]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"]; + [[NSUserDefaults standardUserDefaults] setDouble:pitch forKey:@"pitch"]; if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) { - [audioPlayer setTempo:pitch]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"]; + [[NSUserDefaults standardUserDefaults] setDouble:pitch forKey:@"tempo"]; } } @@ -522,14 +516,10 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { const double tempo = speedScale([sender doubleValue], [tempoSlider minValue], [tempoSlider maxValue]); DLog(@"TEMPO: %lf", tempo); - [audioPlayer setTempo:tempo]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"]; + [[NSUserDefaults standardUserDefaults] setDouble:tempo forKey:@"tempo"]; if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) { - [audioPlayer setPitch:tempo]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"]; + [[NSUserDefaults standardUserDefaults] setDouble:tempo forKey:@"pitch"]; } } @@ -635,62 +625,70 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { } - (IBAction)pitchDown:(id)sender { - /*double newPitch = */[audioPlayer pitchDown:DEFAULT_PITCH_DOWN]; - [pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])]; + double pitch = speedScale([pitchSlider doubleValue], [pitchSlider minValue], [pitchSlider maxValue]); + double newPitch = pitch - DEFAULT_PITCH_DOWN; + if(newPitch < 0.2) { + newPitch = 0.2; + } + [pitchSlider setDoubleValue:reverseSpeedScale(newPitch, [pitchSlider minValue], [pitchSlider maxValue])]; - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"]; + [[NSUserDefaults standardUserDefaults] setDouble:newPitch forKey:@"pitch"]; if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) { - [audioPlayer setTempo:[audioPlayer pitch]]; + [tempoSlider setDoubleValue:reverseSpeedScale(newPitch, [tempoSlider minValue], [tempoSlider maxValue])]; - [tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"]; + [[NSUserDefaults standardUserDefaults] setDouble:newPitch forKey:@"tempo"]; } } - (IBAction)pitchUp:(id)sender { - /*double newPitch = */[audioPlayer tempoUp:DEFAULT_PITCH_UP]; - [pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])]; + double pitch = speedScale([pitchSlider doubleValue], [pitchSlider minValue], [pitchSlider maxValue]); + double newPitch = pitch + DEFAULT_PITCH_UP; + if(newPitch > 5.0) { + newPitch = 5.0; + } + [pitchSlider setDoubleValue:reverseSpeedScale(newPitch, [pitchSlider minValue], [pitchSlider maxValue])]; + + [[NSUserDefaults standardUserDefaults] setDouble:newPitch forKey:@"pitch"]; - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"]; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) { - [audioPlayer setTempo:[audioPlayer pitch]]; + [tempoSlider setDoubleValue:reverseSpeedScale(newPitch, [tempoSlider minValue], [tempoSlider maxValue])]; - [tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"]; + [[NSUserDefaults standardUserDefaults] setDouble:newPitch forKey:@"tempo"]; } } - (IBAction)tempoDown:(id)sender { - /*double newTempo = */[audioPlayer tempoDown:DEFAULT_TEMPO_DOWN]; - [tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])]; + double tempo = speedScale([tempoSlider doubleValue], [tempoSlider minValue], [tempoSlider maxValue]); + double newTempo = tempo - DEFAULT_TEMPO_DOWN; + if(newTempo < 0.2) { + newTempo = 0.2; + } + [tempoSlider setDoubleValue:reverseSpeedScale(newTempo, [tempoSlider minValue], [tempoSlider maxValue])]; - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"]; + [[NSUserDefaults standardUserDefaults] setDouble:newTempo forKey:@"tempo"]; if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) { - [audioPlayer setPitch:[audioPlayer tempo]]; + [pitchSlider setDoubleValue:reverseSpeedScale(newTempo, [pitchSlider minValue], [pitchSlider maxValue])]; - [pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"]; + [[NSUserDefaults standardUserDefaults] setDouble:newTempo forKey:@"pitch"]; } } - (IBAction)tempoUp:(id)sender { - /*double newTempo = */[audioPlayer tempoUp:DEFAULT_PITCH_UP]; - [tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])]; + double tempo = speedScale([tempoSlider doubleValue], [tempoSlider minValue], [tempoSlider maxValue]); + double newTempo = tempo + DEFAULT_TEMPO_UP; + if(newTempo > 5.0) { + newTempo = 5.0; + } + [tempoSlider setDoubleValue:reverseSpeedScale(newTempo, [tempoSlider minValue], [tempoSlider maxValue])]; + + [[NSUserDefaults standardUserDefaults] setDouble:newTempo forKey:@"tempo"]; - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"]; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) { - [audioPlayer setPitch:[audioPlayer tempo]]; + [pitchSlider setDoubleValue:reverseSpeedScale(newTempo, [pitchSlider minValue], [pitchSlider maxValue])]; - [pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])]; - - [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"]; + [[NSUserDefaults standardUserDefaults] setDouble:newTempo forKey:@"pitch"]; } } diff --git a/Audio/AudioPlayer.h b/Audio/AudioPlayer.h index a6ea2d6bc..fc90ff69c 100644 --- a/Audio/AudioPlayer.h +++ b/Audio/AudioPlayer.h @@ -76,16 +76,6 @@ - (double)volumeUp:(double)amount; - (double)volumeDown:(double)amount; -- (void)setPitch:(double)s; -- (double)pitch; -- (double)pitchUp:(double)amount; -- (double)pitchDown:(double)amount; - -- (void)setTempo:(double)s; -- (double)tempo; -- (double)tempoUp:(double)amount; -- (double)tempoDown:(double)amount; - - (double)amountPlayed; - (double)amountPlayedInterval; diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index aaeb9cf11..ef5632b10 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -79,8 +79,6 @@ } [output setup]; [output setVolume:volume]; - [output setPitch:pitch]; - [output setTempo:tempo]; @synchronized(chainQueue) { for(id anObject in chainQueue) { [anObject setShouldContinue:NO]; @@ -216,26 +214,6 @@ return volume; } -- (void)setPitch:(double)p { - pitch = p; - - [output setPitch:p]; -} - -- (double)pitch { - return pitch; -} - -- (void)setTempo:(double)t { - tempo = t; - - [output setTempo:t]; -} - -- (double)tempo { - return tempo; -} - // This is called by the delegate DURING a requestNextStream request. - (void)setNextStream:(NSURL *)url { [self setNextStream:url withUserInfo:nil withRGInfo:nil]; @@ -511,6 +489,7 @@ break; } + [bufferChain setShouldContinue:NO]; bufferChain = nil; bufferChain = [chainQueue objectAtIndex:0]; @@ -674,58 +653,6 @@ return newVolume; } -- (double)pitchUp:(double)amount { - const double MAX_PITCH = 5.0; - - double newPitch; - if((pitch + amount) > MAX_PITCH) - newPitch = MAX_PITCH; - else - newPitch = pitch + amount; - - [self setPitch:newPitch]; - return newPitch; -} - -- (double)pitchDown:(double)amount { - const double MIN_PITCH = 0.2; - - double newPitch; - if((pitch - amount) < MIN_PITCH) - newPitch = MIN_PITCH; - else - newPitch = pitch - amount; - - [self setPitch:newPitch]; - return newPitch; -} - -- (double)tempoUp:(double)amount { - const double MAX_TEMPO = 5.0; - - double newTempo; - if((tempo + amount) > MAX_TEMPO) - newTempo = MAX_TEMPO; - else - newTempo = tempo + amount; - - [self setTempo:newTempo]; - return newTempo; -} - -- (double)tempoDown:(double)amount { - const double MIN_TEMPO = 0.2; - - double newTempo; - if((tempo - amount) < MIN_TEMPO) - newTempo = MIN_TEMPO; - else - newTempo = tempo - amount; - - [self setTempo:newTempo]; - return newTempo; -} - - (void)waitUntilCallbacksExit { // This sucks! And since the thread that's inside the function can be calling // event dispatches, we have to pump the message queue if we're on the main diff --git a/Audio/Chain/BufferChain.h b/Audio/Chain/BufferChain.h index 0e649da23..dbfeef689 100644 --- a/Audio/Chain/BufferChain.h +++ b/Audio/Chain/BufferChain.h @@ -10,11 +10,13 @@ #import "AudioPlayer.h" #import "ConverterNode.h" +#import "DSPRubberbandNode.h" #import "InputNode.h" @interface BufferChain : NSObject { InputNode *inputNode; ConverterNode *converterNode; + DSPRubberbandNode *rubberbandNode; NSURL *streamURL; id userInfo; @@ -71,6 +73,8 @@ - (AudioStreamBasicDescription)inputFormat; - (uint32_t)inputConfig; +- (DSPRubberbandNode *)rubberband; + - (double)secondsBuffered; - (void)sustainHDCD; diff --git a/Audio/Chain/BufferChain.m b/Audio/Chain/BufferChain.m index bccd2f56e..2e5a7b452 100644 --- a/Audio/Chain/BufferChain.m +++ b/Audio/Chain/BufferChain.m @@ -25,6 +25,8 @@ inputNode = nil; converterNode = nil; + + rubberbandNode = nil; } return self; @@ -36,8 +38,9 @@ inputNode = [[InputNode alloc] initWithController:self previous:nil]; converterNode = [[ConverterNode alloc] initWithController:self previous:inputNode]; + rubberbandNode = [[DSPRubberbandNode alloc] initWithController:self previous:converterNode latency:0.03]; - finalNode = converterNode; + finalNode = rubberbandNode; } - (BOOL)open:(NSURL *)url withOutputFormat:(AudioStreamBasicDescription)outputFormat withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi { @@ -146,6 +149,7 @@ [inputNode launchThread]; [converterNode launchThread]; + [rubberbandNode launchThread]; } - (void)setUserInfo:(id)i { @@ -213,6 +217,7 @@ - (void)setShouldContinue:(BOOL)s { [inputNode setShouldContinue:s]; [converterNode setShouldContinue:s]; + [rubberbandNode setShouldContinue:s]; } - (BOOL)isRunning { @@ -231,6 +236,10 @@ return converterNode; } +- (DSPRubberbandNode *)rubberband { + return rubberbandNode; +} + - (AudioStreamBasicDescription)inputFormat { return [inputNode nodeFormat]; } diff --git a/Audio/Chain/DSPNode.h b/Audio/Chain/DSPNode.h new file mode 100644 index 000000000..50ac1475f --- /dev/null +++ b/Audio/Chain/DSPNode.h @@ -0,0 +1,20 @@ +// +// DSPNode.h +// CogAudio +// +// Created by Christopher Snowhill on 2/10/25. +// + +#ifndef DSPNode_h +#define DSPNode_h + +#import "Node.h" + +@interface DSPNode : Node { +} + +- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p latency:(double)latency; + +@end + +#endif /* DSPNode_h */ diff --git a/Audio/Chain/DSPNode.m b/Audio/Chain/DSPNode.m new file mode 100644 index 000000000..59d78313c --- /dev/null +++ b/Audio/Chain/DSPNode.m @@ -0,0 +1,40 @@ +// +// DSPNode.m +// CogAudio Framework +// +// Created by Christopher Snowhill on 2/10/25. +// + +#import + +#import "DSPNode.h" + +@implementation DSPNode + +- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p latency:(double)latency { + self = [super init]; + if(self) { + buffer = [[ChunkList alloc] initWithMaximumDuration:latency]; + + semaphore = [[Semaphore alloc] init]; + + accessLock = [[NSLock alloc] init]; + + initialBufferFilled = NO; + + controller = c; + endOfStream = NO; + shouldContinue = YES; + + nodeChannelConfig = 0; + nodeLossless = NO; + + durationPrebuffer = latency * 0.25; + + [self setPreviousNode:p]; + } + + return self; +} + +@end diff --git a/Audio/Chain/DSPRubberbandNode.h b/Audio/Chain/DSPRubberbandNode.h new file mode 100644 index 000000000..f32a76293 --- /dev/null +++ b/Audio/Chain/DSPRubberbandNode.h @@ -0,0 +1,28 @@ +// +// DSPRubberbandNode.h +// CogAudio +// +// Created by Christopher Snowhill on 2/10/25. +// + +#ifndef DSPRubberbandNode_h +#define DSPRubberbandNode_h + +#import "DSPNode.h" + +@interface DSPRubberbandNode : DSPNode { +} + +- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p latency:(double)latency; + +- (BOOL)setup; +- (void)cleanUp; + +- (void)resetBuffer; + +- (void)process; +- (AudioChunk * _Nullable)convert; + +@end + +#endif /* DSPRubberbandNode_h */ diff --git a/Audio/Chain/DSPRubberbandNode.m b/Audio/Chain/DSPRubberbandNode.m new file mode 100644 index 000000000..cdc5c3b19 --- /dev/null +++ b/Audio/Chain/DSPRubberbandNode.m @@ -0,0 +1,464 @@ +// +// DSPRubberbandNode.m +// CogAudio Framework +// +// Created by Christopher Snowhill on 2/10/25. +// + +#import + +#import + +#import "DSPRubberbandNode.h" + +#import + +static void * kDSPRubberbandNodeContext = &kDSPRubberbandNodeContext; + +@implementation DSPRubberbandNode { + RubberBandState ts; + RubberBandOptions tslastoptions, tsnewoptions; + size_t blockSize, toDrop, samplesBuffered, tschannels; + BOOL tsapplynewoptions; + BOOL tsrestartengine; + double tempo, pitch; + double lastTempo, lastPitch; + + BOOL stopping, paused; + BOOL processEntered; + + BOOL observersapplied; + + AudioStreamBasicDescription lastInputFormat; + AudioStreamBasicDescription inputFormat; + + uint32_t lastInputChannelConfig, inputChannelConfig; + + float *rsPtrs[32]; + float rsInBuffer[4096 * 32]; + float rsOutBuffer[65536 * 32]; +} + ++ (void)initialize { + NSDictionary *defaults = @{@"rubberbandEngine": @"faster", + @"rubberbandTransients": @"crisp", + @"rubberbandDetector": @"compound", + @"rubberbandPhase": @"laminar", + @"rubberbandWindow": @"standard", + @"rubberbandSmoothing": @"off", + @"rubberbandFormant": @"shifted", + @"rubberbandPitch": @"highspeed", + @"rubberbandChannels": @"apart"}; + [[[NSUserDefaultsController sharedUserDefaultsController] defaults] registerDefaults:defaults]; +} + +- (id _Nullable)initWithController:(id _Nonnull)c previous:(id _Nullable)p latency:(double)latency { + self = [super initWithController:c previous:p latency:latency]; + if(self) { + NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults]; + pitch = [defaults doubleForKey:@"pitch"]; + tempo = [defaults doubleForKey:@"tempo"]; + + lastPitch = pitch; + lastTempo = tempo; + + [self addObservers]; + } + return self; +} + +- (void)dealloc { + [self cleanUp]; + [self removeObservers]; +} + +- (void)addObservers { + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.pitch" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.tempo" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandEngine" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandTransients" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandDetector" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandPhase" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandWindow" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandSmoothing" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandFormant" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandPitch" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandChannels" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kDSPRubberbandNodeContext]; + + observersapplied = YES; +} + +- (void)removeObservers { + if(observersapplied) { + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.pitch" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.tempo" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandEngine" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandTransients" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandDetector" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandPhase" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandWindow" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandSmoothing" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandFormant" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandPitch" context:kDSPRubberbandNodeContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandChannels" context:kDSPRubberbandNodeContext]; + observersapplied = NO; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if(context != kDSPRubberbandNodeContext) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + + if([keyPath isEqualToString:@"values.pitch"] || + [keyPath isEqualToString:@"values.tempo"]) { + NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults]; + pitch = [defaults doubleForKey:@"pitch"]; + tempo = [defaults doubleForKey:@"tempo"]; + tsapplynewoptions = YES; + } else if([[keyPath substringToIndex:17] isEqualToString:@"values.rubberband"]) { + if(ts) { + RubberBandOptions options = [self getRubberbandOptions]; + RubberBandOptions changed = options ^ tslastoptions; + if(changed) { + BOOL engineR3 = !!(options & RubberBandOptionEngineFiner); + // Options which require a restart of the engine + const RubberBandOptions mustRestart = RubberBandOptionEngineFaster | RubberBandOptionEngineFiner | RubberBandOptionWindowStandard | RubberBandOptionWindowShort | RubberBandOptionWindowLong | RubberBandOptionSmoothingOff | RubberBandOptionSmoothingOn | (engineR3 ? RubberBandOptionPitchHighSpeed | RubberBandOptionPitchHighQuality | RubberBandOptionPitchHighConsistency : 0) | RubberBandOptionChannelsApart | RubberBandOptionChannelsTogether; + if(changed & mustRestart) { + tsrestartengine = YES; + } else { + tsnewoptions = options; + tsapplynewoptions = YES; + } + } + } + } +} + +- (RubberBandOptions)getRubberbandOptions { + RubberBandOptions options = RubberBandOptionProcessRealTime; + + NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults]; + NSString *value = [defaults stringForKey:@"rubberbandEngine"]; + BOOL engineR3 = NO; + if([value isEqualToString:@"faster"]) { + options |= RubberBandOptionEngineFaster; + } else if([value isEqualToString:@"finer"]) { + options |= RubberBandOptionEngineFiner; + engineR3 = YES; + } + + if(!engineR3) { + value = [defaults stringForKey:@"rubberbandTransients"]; + if([value isEqualToString:@"crisp"]) { + options |= RubberBandOptionTransientsCrisp; + } else if([value isEqualToString:@"mixed"]) { + options |= RubberBandOptionTransientsMixed; + } else if([value isEqualToString:@"smooth"]) { + options |= RubberBandOptionTransientsSmooth; + } + + value = [defaults stringForKey:@"rubberbandDetector"]; + if([value isEqualToString:@"compound"]) { + options |= RubberBandOptionDetectorCompound; + } else if([value isEqualToString:@"percussive"]) { + options |= RubberBandOptionDetectorPercussive; + } else if([value isEqualToString:@"soft"]) { + options |= RubberBandOptionDetectorSoft; + } + + value = [defaults stringForKey:@"rubberbandPhase"]; + if([value isEqualToString:@"laminar"]) { + options |= RubberBandOptionPhaseLaminar; + } else if([value isEqualToString:@"independent"]) { + options |= RubberBandOptionPhaseIndependent; + } + } + + value = [defaults stringForKey:@"rubberbandWindow"]; + if([value isEqualToString:@"standard"]) { + options |= RubberBandOptionWindowStandard; + } else if([value isEqualToString:@"short"]) { + options |= RubberBandOptionWindowShort; + } else if([value isEqualToString:@"long"]) { + if(engineR3) { + options |= RubberBandOptionWindowStandard; + } else { + options |= RubberBandOptionWindowLong; + } + } + + if(!engineR3) { + value = [defaults stringForKey:@"rubberbandSmoothing"]; + if([value isEqualToString:@"off"]) { + options |= RubberBandOptionSmoothingOff; + } else if([value isEqualToString:@"on"]) { + options |= RubberBandOptionSmoothingOn; + } + } + + value = [defaults stringForKey:@"rubberbandFormant"]; + if([value isEqualToString:@"shifted"]) { + options |= RubberBandOptionFormantShifted; + } else if([value isEqualToString:@"preserved"]) { + options |= RubberBandOptionFormantPreserved; + } + + value = [defaults stringForKey:@"rubberbandPitch"]; + if([value isEqualToString:@"highspeed"]) { + options |= RubberBandOptionPitchHighSpeed; + } else if([value isEqualToString:@"highquality"]) { + options |= RubberBandOptionPitchHighQuality; + } else if([value isEqualToString:@"highconsistency"]) { + options |= RubberBandOptionPitchHighConsistency; + } + + value = [defaults stringForKey:@"rubberbandChannels"]; + if([value isEqualToString:@"apart"]) { + options |= RubberBandOptionChannelsApart; + } else if([value isEqualToString:@"together"]) { + options |= RubberBandOptionChannelsTogether; + } + + return options; +} + +- (BOOL)fullInit { + RubberBandOptions options = [self getRubberbandOptions]; + tslastoptions = options; + tschannels = inputFormat.mChannelsPerFrame; + ts = rubberband_new(inputFormat.mSampleRate, inputFormat.mChannelsPerFrame, options, 1.0 / tempo, pitch); + if(!ts) + return NO; + + blockSize = rubberband_get_process_size_limit(ts); + toDrop = rubberband_get_start_delay(ts); + samplesBuffered = 0; + if(blockSize > 4096) + blockSize = 4096; + rubberband_set_max_process_size(ts, (unsigned int)blockSize); + + for(size_t i = 0; i < 32; ++i) { + rsPtrs[i] = &rsInBuffer[4096 * i]; + } + + size_t toPad = rubberband_get_preferred_start_pad(ts); + if(toPad > 0) { + for(size_t i = 0; i < inputFormat.mChannelsPerFrame; ++i) { + memset(rsPtrs[i], 0, 4096 * sizeof(float)); + } + while(toPad > 0) { + size_t p = toPad; + if(p > blockSize) p = blockSize; + rubberband_process(ts, (const float * const *)rsPtrs, (int)p, false); + toPad -= p; + } + } + + tsapplynewoptions = NO; + tsrestartengine = NO; + + return YES; +} + +- (void)partialInit { + if(stopping || paused || !ts) return; + + RubberBandOptions changed = tslastoptions ^ tsnewoptions; + + if(changed) { + tslastoptions = tsnewoptions; + + BOOL engineR3 = !!(tsnewoptions & RubberBandOptionEngineFiner); + const RubberBandOptions transientsmask = RubberBandOptionTransientsCrisp | RubberBandOptionTransientsMixed | RubberBandOptionTransientsSmooth; + const RubberBandOptions detectormask = RubberBandOptionDetectorCompound | RubberBandOptionDetectorPercussive | RubberBandOptionDetectorSoft; + const RubberBandOptions phasemask = RubberBandOptionPhaseLaminar | RubberBandOptionPhaseIndependent; + const RubberBandOptions formantmask = RubberBandOptionFormantShifted | RubberBandOptionFormantPreserved; + const RubberBandOptions pitchmask = RubberBandOptionPitchHighSpeed | RubberBandOptionPitchHighQuality | RubberBandOptionPitchHighConsistency; + if(changed & transientsmask) + rubberband_set_transients_option(ts, tsnewoptions & transientsmask); + if(!engineR3) { + if(changed & detectormask) + rubberband_set_detector_option(ts, tsnewoptions & detectormask); + if(changed & phasemask) + rubberband_set_phase_option(ts, tsnewoptions & phasemask); + } + if(changed & formantmask) + rubberband_set_formant_option(ts, tsnewoptions & formantmask); + if(!engineR3 && (changed & pitchmask)) + rubberband_set_pitch_option(ts, tsnewoptions & pitchmask); + } + + if(fabs(pitch - lastPitch) > 1e-5 || + fabs(tempo - lastTempo) > 1e-5) { + lastPitch = pitch; + lastTempo = tempo; + rubberband_set_pitch_scale(ts, pitch); + rubberband_set_time_ratio(ts, 1.0 / tempo); + } + + tsapplynewoptions = NO; +} + +- (void)fullShutdown { + if(ts) { + rubberband_delete(ts); + ts = NULL; + } +} + +- (BOOL)setup { + if(stopping) + return NO; + [self fullShutdown]; + return [self fullInit]; +} + +- (void)cleanUp { + stopping = YES; + while(processEntered) { + usleep(1000); + } + [self fullShutdown]; +} + +- (void)resetBuffer { + paused = YES; + while(processEntered) { + usleep(500); + } + [super resetBuffer]; + [self fullShutdown]; + paused = NO; +} + +- (void)process { + // Removed endOfStream check from here, since we want to be able to flush the converter + // when the end of stream is reached. Convert function instead processes what it can, + // and returns 0 samples when it has nothing more to process at the end of stream. + while([self shouldContinue] == YES) { + if(paused) { + usleep(500); + continue; + } + @autoreleasepool { + AudioChunk *chunk = nil; + chunk = [self convert]; + if(!chunk) { + if([self endOfStream] == YES) { + break; + } + if(paused) { + continue; + } + } else { + [self writeChunk:chunk]; + chunk = nil; + } + if(tsrestartengine) { + [self fullShutdown]; + } else if(tsapplynewoptions) { + [self partialInit]; + } + } + } +} + +- (AudioChunk *)convert { + if(stopping) + return 0; + + processEntered = YES; + + if(stopping || [self endOfStream] == YES || [self shouldContinue] == NO) { + processEntered = NO; + return nil; + } + + if(![self peekFormat:&inputFormat channelConfig:&inputChannelConfig]) { + processEntered = NO; + return nil; + } + + if(!ts || memcmp(&inputFormat, &lastInputFormat, sizeof(inputFormat)) != 0 || + inputChannelConfig != lastInputChannelConfig) { + lastInputFormat = inputFormat; + lastInputChannelConfig = inputChannelConfig; + [self fullShutdown]; + if(![self setup]) { + processEntered = NO; + return nil; + } + } + + size_t samplesToProcess = rubberband_get_samples_required(ts); + if(samplesToProcess > blockSize) + samplesToProcess = blockSize; + + AudioChunk *chunk = [self readChunkAsFloat32:samplesToProcess]; + if(!chunk) { + processEntered = NO; + return nil; + } + + bool endOfStream = [[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES; + + size_t frameCount = [chunk frameCount]; + + int len = (int)frameCount; + int channels = (int)(inputFormat.mChannelsPerFrame); + NSData *samples = [chunk removeSamples:frameCount]; + float *ibuf = (float *)[samples bytes]; + + for(size_t i = 0; i < channels; ++i) { + cblas_scopy(len, ibuf + i, channels, rsPtrs[i], 1); + } + + rubberband_process(ts, (const float * const *)rsPtrs, len, endOfStream); + + size_t samplesAvailable; + while(!stopping && (samplesAvailable = rubberband_available(ts)) > 0) { + if(toDrop > 0) { + size_t blockDrop = toDrop; + if(blockDrop > samplesAvailable) blockDrop = samplesAvailable; + if(blockDrop > blockSize) blockDrop = blockSize; + rubberband_retrieve(ts, (float * const *)rsPtrs, (int)blockDrop); + toDrop -= blockDrop; + continue; + } + size_t maxAvailable = 65536 - samplesBuffered; + if(samplesAvailable > maxAvailable) { + samplesAvailable = maxAvailable; + if(!samplesAvailable) { + break; + } + } + size_t samplesOut = samplesAvailable; + if(samplesOut > blockSize) samplesOut = blockSize; + rubberband_retrieve(ts, (float * const *)rsPtrs, (int)samplesOut); + for(size_t i = 0; i < channels; ++i) { + cblas_scopy((int)samplesOut, rsPtrs[i], 1, &rsOutBuffer[samplesBuffered * channels + i], channels); + } + samplesBuffered += samplesOut; + if(endOfStream && samplesOut < 4096) { + self->endOfStream = YES; + break; + } + } + + AudioChunk *outputChunk = nil; + if(samplesBuffered) { + outputChunk = [[AudioChunk alloc] init]; + [outputChunk setFormat:inputFormat]; + if(inputChannelConfig) { + [outputChunk setChannelConfig:inputChannelConfig]; + } + [outputChunk assignSamples:rsOutBuffer frameCount:samplesBuffered]; + samplesBuffered = 0; + } + + processEntered = NO; + return outputChunk; +} +@end diff --git a/Audio/Chain/InputNode.m b/Audio/Chain/InputNode.m index 98fa02c5d..0cf96dcbe 100644 --- a/Audio/Chain/InputNode.m +++ b/Audio/Chain/InputNode.m @@ -161,12 +161,14 @@ static void *kInputNodeContext = &kInputNodeContext; if(shouldSeek == YES) { BufferChain *bufferChain = [[controller controller] bufferChain]; ConverterNode *converter = [bufferChain converter]; + DSPRubberbandNode *rubberband = [bufferChain rubberband]; DLog(@"SEEKING! Resetting Buffer"); // This resets the converter's buffer [self resetBuffer]; [converter resetBuffer]; [converter inputFormatDidChange:[bufferChain inputFormat] inputConfig:[bufferChain inputConfig]]; + [rubberband resetBuffer]; DLog(@"Reset buffer!"); diff --git a/Audio/Chain/Node.m b/Audio/Chain/Node.m index 9fac2158d..a5d07b60e 100644 --- a/Audio/Chain/Node.m +++ b/Audio/Chain/Node.m @@ -110,9 +110,13 @@ } while(shouldContinue == YES && chunkDuration > durationLeft) { + if(previousNode && [previousNode shouldContinue] == NO) { + shouldContinue = NO; + break; + } if(durationLeft < chunkDuration || shouldReset) { [accessLock unlock]; - [semaphore wait]; + [semaphore timedWait:500]; [accessLock lock]; } @@ -137,6 +141,12 @@ - (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config { [accessLock lock]; + if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) { + endOfStream = YES; + [accessLock unlock]; + return NO; + } + BOOL ret = [[previousNode buffer] peekFormat:format channelConfig:config]; [accessLock unlock]; diff --git a/Audio/Chain/OutputNode.h b/Audio/Chain/OutputNode.h index ef9f9651d..23e618dad 100644 --- a/Audio/Chain/OutputNode.h +++ b/Audio/Chain/OutputNode.h @@ -61,9 +61,6 @@ - (void)setVolume:(double)v; -- (void)setPitch:(double)p; -- (void)setTempo:(double)t; - - (void)setShouldContinue:(BOOL)s; - (void)setShouldPlayOutBuffer:(BOOL)s; diff --git a/Audio/Chain/OutputNode.m b/Audio/Chain/OutputNode.m index 593cd8fd2..9864e5569 100644 --- a/Audio/Chain/OutputNode.m +++ b/Audio/Chain/OutputNode.m @@ -164,14 +164,6 @@ [output setVolume:v]; } -- (void)setPitch:(double)p { - [output setPitch:p]; -} - -- (void)setTempo:(double)t { - [output setTempo:t]; -} - - (void)setShouldContinue:(BOOL)s { [super setShouldContinue:s]; diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index f49021c89..cd43d8665 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -95,6 +95,10 @@ 839E56EE2879515D00DFB5F4 /* HeadphoneFilter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 839E56EC2879515D00DFB5F4 /* HeadphoneFilter.mm */; }; 839E56F7287974A100DFB5F4 /* SandboxBroker.h in Headers */ = {isa = PBXBuildFile; fileRef = 839E56F6287974A100DFB5F4 /* SandboxBroker.h */; }; 83B74281289E027F005AAC28 /* CogAudio-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 83B74280289E027F005AAC28 /* CogAudio-Bridging-Header.h */; }; + 83FFED512D5B08BC0044CCAF /* DSPNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FFED502D5B08BC0044CCAF /* DSPNode.h */; }; + 83FFED532D5B09320044CCAF /* DSPNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FFED522D5B09320044CCAF /* DSPNode.m */; }; + 83FFED552D5B0A1C0044CCAF /* DSPRubberbandNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FFED542D5B0A1C0044CCAF /* DSPRubberbandNode.h */; }; + 83FFED572D5B0A6B0044CCAF /* DSPRubberbandNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 83FFED562D5B0A6B0044CCAF /* DSPRubberbandNode.m */; }; 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; 8E8D3D2F0CBAEE6E00135C1B /* AudioContainer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E8D3D2E0CBAEE6E00135C1B /* AudioContainer.m */; }; @@ -209,6 +213,10 @@ 839E56EC2879515D00DFB5F4 /* HeadphoneFilter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HeadphoneFilter.mm; sourceTree = ""; }; 839E56F6287974A100DFB5F4 /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../Utils/SandboxBroker.h; sourceTree = ""; }; 83B74280289E027F005AAC28 /* CogAudio-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CogAudio-Bridging-Header.h"; sourceTree = ""; }; + 83FFED502D5B08BC0044CCAF /* DSPNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPNode.h; sourceTree = ""; }; + 83FFED522D5B09320044CCAF /* DSPNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSPNode.m; sourceTree = ""; }; + 83FFED542D5B0A1C0044CCAF /* DSPRubberbandNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPRubberbandNode.h; sourceTree = ""; }; + 83FFED562D5B0A6B0044CCAF /* DSPRubberbandNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSPRubberbandNode.m; sourceTree = ""; }; 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8DC2EF5B0486A6940098B216 /* CogAudio.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CogAudio.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8E8D3D2D0CBAEE6E00135C1B /* AudioContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioContainer.h; sourceTree = ""; }; @@ -361,6 +369,10 @@ 17D21C7D0B8BE4BA00D1EBDE /* Node.m */, 17D21C7E0B8BE4BA00D1EBDE /* OutputNode.h */, 17D21C7F0B8BE4BA00D1EBDE /* OutputNode.m */, + 83FFED502D5B08BC0044CCAF /* DSPNode.h */, + 83FFED522D5B09320044CCAF /* DSPNode.m */, + 83FFED542D5B0A1C0044CCAF /* DSPRubberbandNode.h */, + 83FFED562D5B0A6B0044CCAF /* DSPRubberbandNode.m */, ); path = Chain; sourceTree = ""; @@ -546,8 +558,10 @@ 8328995727CB51B700D7F028 /* SHA256Digest.h in Headers */, 834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */, 834A41AF287ABD6F00EB9D9B /* FSurroundFilter.h in Headers */, + 83FFED552D5B0A1C0044CCAF /* DSPRubberbandNode.h in Headers */, 17A2D3C50B8D1D37000778C4 /* AudioDecoder.h in Headers */, 8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */, + 83FFED512D5B08BC0044CCAF /* DSPNode.h in Headers */, 17C940230B900909008627D6 /* AudioMetadataReader.h in Headers */, 839E56F7287974A100DFB5F4 /* SandboxBroker.h in Headers */, 839065F32853338700636FBB /* dsd2float.h in Headers */, @@ -640,6 +654,7 @@ files = ( 17D21CA20B8BE4BA00D1EBDE /* BufferChain.m in Sources */, 17D21CA60B8BE4BA00D1EBDE /* InputNode.m in Sources */, + 83FFED572D5B0A6B0044CCAF /* DSPRubberbandNode.m in Sources */, 839E56EE2879515D00DFB5F4 /* HeadphoneFilter.mm in Sources */, 83504166286447DA006B32CC /* Downmix.m in Sources */, 8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */, @@ -667,6 +682,7 @@ 17ADB13D0B97926D00257CA2 /* AudioSource.m in Sources */, 834FD4F127AF93680063BC83 /* ChunkList.m in Sources */, 834A41B0287ABD6F00EB9D9B /* FSurroundFilter.mm in Sources */, + 83FFED532D5B09320044CCAF /* DSPNode.m in Sources */, 8EC122600B993BD500C5B3AD /* ConverterNode.m in Sources */, 835DD2672ACAF1D90057E319 /* OutputCoreAudio.m in Sources */, 8E8D3D300CBAEE6E00135C1B /* AudioContainer.m in Sources */, diff --git a/Audio/Output/OutputCoreAudio.h b/Audio/Output/OutputCoreAudio.h index 1f9083b36..0f968d6c5 100644 --- a/Audio/Output/OutputCoreAudio.h +++ b/Audio/Output/OutputCoreAudio.h @@ -52,14 +52,9 @@ using std::atomic_long; double secondsLatency; double visPushed; - double lastClippedSampleRate; + double tempo; - void *ts; - int tslastoptions, tsnewoptions; - size_t blockSize, toDrop, samplesBuffered; - double ssRenderedIn, ssLastRenderedIn; - double ssRenderedOut; - BOOL tsapplynewoptions; + double lastClippedSampleRate; void *rsvis; double lastVisRate; @@ -95,9 +90,6 @@ using std::atomic_long; float volume; float eqPreamp; - double pitch, tempo; - double lastPitch, lastTempo; - AVAudioFormat *_deviceFormat; AudioDeviceID outputDeviceID; @@ -144,13 +136,11 @@ using std::atomic_long; float *samplePtr; float tempBuffer[512 * 32]; - float *rsPtrs[32]; - float rsInBuffer[1024 * 32]; - float rsOutBuffer[65536 * 32]; float inputBuffer[4096 * 32]; // 4096 samples times maximum supported channel count float fsurroundBuffer[8192 * 6]; float hrtfBuffer[4096 * 2]; float eqBuffer[4096 * 32]; + float eqOutBuffer[4096 * 32]; float downmixBuffer[4096 * 8]; float visAudio[4096]; @@ -190,7 +180,6 @@ using std::atomic_long; - (void)reportMotion:(simd_float4x4)matrix; - (void)resetReferencePosition:(NSNotification *)notification; -- (void)setPitch:(double)p; - (void)setTempo:(double)t; @end diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index ff10e9c88..830dbc74a 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -23,8 +23,6 @@ #import "FSurroundFilter.h" -#import - #define OCTAVES 5 extern void scale_by_volume(float *buffer, size_t count, float volume); @@ -33,9 +31,6 @@ static NSString *CogPlaybackDidBeginNotificiation = @"CogPlaybackDidBeginNotific static NSString *CogPlaybackDidResetHeadTracking = @"CogPlaybackDigResetHeadTracking"; -#define tts ((RubberBandState)ts) -#define ttslastoptions ((RubberBandOptions)tslastoptions) - simd_float4x4 convertMatrix(CMRotationMatrix r) { simd_float4x4 matrix = { simd_make_float4(r.m33, -r.m31, r.m32, 0.0f), @@ -54,19 +49,6 @@ OutputCoreAudio *registeredMotionListener = nil; + (void)initialize { motionManagerLock = [[NSLock alloc] init]; - { - NSDictionary *defaults = @{@"rubberbandEngine": @"faster", - @"rubberbandTransients": @"crisp", - @"rubberbandDetector": @"compound", - @"rubberbandPhase": @"laminar", - @"rubberbandWindow": @"standard", - @"rubberbandSmoothing": @"off", - @"rubberbandFormant": @"shifted", - @"rubberbandPitch": @"highspeed", - @"rubberbandChannels": @"apart"}; - [[[NSUserDefaultsController sharedUserDefaultsController] defaults] registerDefaults:defaults]; - } - if(@available(macOS 14, *)) { CMAuthorizationStatus status = [CMHeadphoneMotionManager authorizationStatus]; if(status == CMAuthorizationStatusDenied) { @@ -356,10 +338,9 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA secondsHdcdSustained = 0; - outputLock = [[NSLock alloc] init]; + tempo = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] doubleForKey:@"tempo"]; - pitch = 1.0; tempo = 1.0; - lastPitch = 1.0; lastTempo = 1.0; + outputLock = [[NSLock alloc] init]; #ifdef OUTPUT_LOG NSString *logName = [NSTemporaryDirectory() stringByAppendingPathComponent:@"CogAudioLog.raw"]; @@ -420,20 +401,8 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons enableFSurround = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"enableFSurround"]; if(streamFormatStarted) resetStreamFormat = YES; - } else if([[keyPath substringToIndex:17] isEqualToString:@"values.rubberband"]) { - RubberBandOptions options = [self getRubberbandOptions]; - RubberBandOptions changed = options ^ ttslastoptions; - if(changed) { - BOOL engineR3 = !!(options & RubberBandOptionEngineFiner); - // Options which require a restart of the engine - const RubberBandOptions mustRestart = RubberBandOptionEngineFaster | RubberBandOptionEngineFiner | RubberBandOptionWindowStandard | RubberBandOptionWindowShort | RubberBandOptionWindowLong | RubberBandOptionSmoothingOff | RubberBandOptionSmoothingOn | (engineR3 ? RubberBandOptionPitchHighSpeed | RubberBandOptionPitchHighQuality | RubberBandOptionPitchHighConsistency : 0) | RubberBandOptionChannelsApart | RubberBandOptionChannelsTogether; - if(changed & mustRestart) { - resetStreamFormat = YES; - } else { - tsnewoptions = options; - tsapplynewoptions = YES; - } - } + } else if([keyPath isEqualToString:@"values.tempo"]) { + tempo = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] doubleForKey:@"tempo"]; } } @@ -449,7 +418,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons } - (BOOL)processEndOfStream { - if(stopping || ([outputController endOfStream] == YES && [self signalEndOfStream:secondsLatency])) { + if(stopping || ([outputController endOfStream] == YES && [self signalEndOfStream:secondsLatency / tempo])) { stopping = YES; return YES; } @@ -799,94 +768,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons return YES; } -- (RubberBandOptions)getRubberbandOptions { - RubberBandOptions options = RubberBandOptionProcessRealTime; - - NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults]; - NSString *value = [defaults stringForKey:@"rubberbandEngine"]; - BOOL engineR3 = NO; - if([value isEqualToString:@"faster"]) { - options |= RubberBandOptionEngineFaster; - } else if([value isEqualToString:@"finer"]) { - options |= RubberBandOptionEngineFiner; - engineR3 = YES; - } - - if(!engineR3) { - value = [defaults stringForKey:@"rubberbandTransients"]; - if([value isEqualToString:@"crisp"]) { - options |= RubberBandOptionTransientsCrisp; - } else if([value isEqualToString:@"mixed"]) { - options |= RubberBandOptionTransientsMixed; - } else if([value isEqualToString:@"smooth"]) { - options |= RubberBandOptionTransientsSmooth; - } - - value = [defaults stringForKey:@"rubberbandDetector"]; - if([value isEqualToString:@"compound"]) { - options |= RubberBandOptionDetectorCompound; - } else if([value isEqualToString:@"percussive"]) { - options |= RubberBandOptionDetectorPercussive; - } else if([value isEqualToString:@"soft"]) { - options |= RubberBandOptionDetectorSoft; - } - - value = [defaults stringForKey:@"rubberbandPhase"]; - if([value isEqualToString:@"laminar"]) { - options |= RubberBandOptionPhaseLaminar; - } else if([value isEqualToString:@"independent"]) { - options |= RubberBandOptionPhaseIndependent; - } - } - - value = [defaults stringForKey:@"rubberbandWindow"]; - if([value isEqualToString:@"standard"]) { - options |= RubberBandOptionWindowStandard; - } else if([value isEqualToString:@"short"]) { - options |= RubberBandOptionWindowShort; - } else if([value isEqualToString:@"long"]) { - if(engineR3) { - options |= RubberBandOptionWindowStandard; - } else { - options |= RubberBandOptionWindowLong; - } - } - - if(!engineR3) { - value = [defaults stringForKey:@"rubberbandSmoothing"]; - if([value isEqualToString:@"off"]) { - options |= RubberBandOptionSmoothingOff; - } else if([value isEqualToString:@"on"]) { - options |= RubberBandOptionSmoothingOn; - } - } - - value = [defaults stringForKey:@"rubberbandFormant"]; - if([value isEqualToString:@"shifted"]) { - options |= RubberBandOptionFormantShifted; - } else if([value isEqualToString:@"preserved"]) { - options |= RubberBandOptionFormantPreserved; - } - - value = [defaults stringForKey:@"rubberbandPitch"]; - if([value isEqualToString:@"highspeed"]) { - options |= RubberBandOptionPitchHighSpeed; - } else if([value isEqualToString:@"highquality"]) { - options |= RubberBandOptionPitchHighQuality; - } else if([value isEqualToString:@"highconsistency"]) { - options |= RubberBandOptionPitchHighConsistency; - } - - value = [defaults stringForKey:@"rubberbandChannels"]; - if([value isEqualToString:@"apart"]) { - options |= RubberBandOptionChannelsApart; - } else if([value isEqualToString:@"together"]) { - options |= RubberBandOptionChannelsTogether; - } - - return options; -} - - (void)updateStreamFormat { /* Set the channel layout for the audio queue */ resetStreamFormat = NO; @@ -954,41 +835,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons [outputLock unlock]; } - if(ts) { - rubberband_delete(tts); - ts = NULL; - } - - RubberBandOptions options = [self getRubberbandOptions]; - tslastoptions = (int)(options); - ts = rubberband_new(realStreamFormat.mSampleRate, realStreamFormat.mChannelsPerFrame, options, 1.0 / tempo, pitch); - - blockSize = 1024; - toDrop = rubberband_get_start_delay(tts); - samplesBuffered = 0; - rubberband_set_max_process_size(tts, (int)blockSize); - - for(size_t i = 0; i < 32; ++i) { - rsPtrs[i] = &rsInBuffer[1024 * i]; - } - - size_t toPad = rubberband_get_preferred_start_pad(tts); - if(toPad > 0) { - for(size_t i = 0; i < realStreamFormat.mChannelsPerFrame; ++i) { - memset(rsPtrs[i], 0, 1024 * sizeof(float)); - } - while(toPad > 0) { - size_t p = toPad; - if(p > blockSize) p = blockSize; - rubberband_process(tts, (const float * const *)rsPtrs, (int)p, false); - toPad -= p; - } - } - - ssRenderedIn = 0.0; - ssLastRenderedIn = 0.0; - ssRenderedOut = 0.0; - streamFormat = realStreamFormat; streamFormat.mChannelsPerFrame = channels; streamFormat.mBytesPerFrame = sizeof(float) * channels; @@ -1111,96 +957,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons samplePtr = &inputBuffer[0]; if(samplesRendered || fsurround) { - { - int simpleSpeedInput = samplesRendered; - int simpleSpeedRendered = 0; - int channels = realStreamFormat.mChannelsPerFrame; - //size_t max_block_len = blockSize; - - if(tsapplynewoptions) { - tsapplynewoptions = NO; - - RubberBandOptions changed = tslastoptions ^ tsnewoptions; - tslastoptions = tsnewoptions; - - BOOL engineR3 = !!(tsnewoptions & RubberBandOptionEngineFiner); - const RubberBandOptions transientsmask = RubberBandOptionTransientsCrisp | RubberBandOptionTransientsMixed | RubberBandOptionTransientsSmooth; - const RubberBandOptions detectormask = RubberBandOptionDetectorCompound | RubberBandOptionDetectorPercussive | RubberBandOptionDetectorSoft; - const RubberBandOptions phasemask = RubberBandOptionPhaseLaminar | RubberBandOptionPhaseIndependent; - const RubberBandOptions formantmask = RubberBandOptionFormantShifted | RubberBandOptionFormantPreserved; - const RubberBandOptions pitchmask = RubberBandOptionPitchHighSpeed | RubberBandOptionPitchHighQuality | RubberBandOptionPitchHighConsistency; - if(changed & transientsmask) - rubberband_set_transients_option(tts, tsnewoptions & transientsmask); - if(!engineR3) { - if(changed & detectormask) - rubberband_set_detector_option(tts, tsnewoptions & detectormask); - if(changed & phasemask) - rubberband_set_phase_option(tts, tsnewoptions & phasemask); - } - if(changed & formantmask) - rubberband_set_formant_option(tts, tsnewoptions & formantmask); - if(!engineR3 && (changed & pitchmask)) - rubberband_set_pitch_option(tts, tsnewoptions & pitchmask); - } - - if(fabs(pitch - lastPitch) > 1e-5 || - fabs(tempo - lastTempo) > 1e-5) { - lastPitch = pitch; - lastTempo = tempo; - rubberband_set_pitch_scale(tts, pitch); - rubberband_set_time_ratio(tts, 1.0 / tempo); - } - - const double inputRatio = 1.0 / realStreamFormat.mSampleRate; - const double outputRatio = inputRatio * tempo; - - while(simpleSpeedInput > 0) { - float *ibuf = samplePtr; - size_t len = simpleSpeedInput; - if(len > blockSize) len = blockSize; - - for(size_t i = 0; i < channels; ++i) { - cblas_scopy((int)len, ibuf + i, channels, rsPtrs[i], 1); - } - - rubberband_process(tts, (const float * const *)rsPtrs, (int)len, false); - - simpleSpeedInput -= len; - ibuf += len * channels; - ssRenderedIn += len * inputRatio; - - size_t samplesAvailable; - while((samplesAvailable = rubberband_available(tts)) > 0) { - if(toDrop > 0) { - size_t blockDrop = toDrop; - if(blockDrop > blockSize) blockDrop = blockSize; - rubberband_retrieve(tts, (float * const *)rsPtrs, (int)blockDrop); - toDrop -= blockDrop; - continue; - } - size_t maxAvailable = 65536 - samplesBuffered; - if(samplesAvailable > maxAvailable) { - samplesAvailable = maxAvailable; - if(!samplesAvailable) { - break; - } - } - size_t samplesOut = samplesAvailable; - if(samplesOut > blockSize) samplesOut = blockSize; - rubberband_retrieve(tts, (float * const *)rsPtrs, (int)samplesOut); - for(size_t i = 0; i < channels; ++i) { - cblas_scopy((int)samplesOut, rsPtrs[i], 1, &rsOutBuffer[samplesBuffered * channels + i], channels); - } - samplesBuffered += samplesOut; - ssRenderedOut += samplesOut * outputRatio; - simpleSpeedRendered += samplesOut; - } - samplePtr = ibuf; - } - samplePtr = &rsOutBuffer[0]; - samplesRendered = simpleSpeedRendered; - samplesBuffered = 0; - } [outputLock lock]; if(fsurround) { int countToProcess = samplesRendered; @@ -1225,10 +981,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons } [outputLock unlock]; - if(!samplesRendered) { - return 0; - } - [outputLock lock]; if(hrtf) { if(rotationMatrixUpdated) { @@ -1273,8 +1025,9 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons timeStamp.mSampleTime += ((double)samplesRendered) / streamFormat.mSampleRate; for(int i = 0; i < channels; ++i) { - cblas_scopy(samplesRendered, &eqBuffer[4096 * i], 1, samplePtr + i, channels); + cblas_scopy(samplesRendered, &eqBuffer[4096 * i], 1, &eqOutBuffer[i], channels); } + samplePtr = &eqOutBuffer[0]; } } @@ -1476,15 +1229,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.enableHrtf" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.enableHeadTracking" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.enableFSurround" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandEngine" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandTransients" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandDetector" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandPhase" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandWindow" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandSmoothing" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandFormant" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandPitch" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.rubberbandChannels" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.tempo" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; observersapplied = YES; @@ -1500,13 +1245,9 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons - (void)updateLatency:(double)secondsPlayed { if(secondsPlayed > 0) { - double rendered = ssRenderedIn - ssLastRenderedIn; - secondsPlayed = rendered; - ssLastRenderedIn = ssRenderedIn; - [outputController incrementAmountPlayed:rendered]; + [outputController incrementAmountPlayed:secondsPlayed * tempo]; } - double simpleSpeedLatency = ssRenderedIn - ssRenderedOut; - double visLatency = visPushed + simpleSpeedLatency; + double visLatency = visPushed; visPushed -= secondsPlayed; if(visLatency < secondsPlayed || visLatency > 30.0) { visLatency = secondsPlayed; @@ -1520,14 +1261,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons volume = v * 0.01f; } -- (void)setPitch:(double)p { - pitch = p; -} - -- (void)setTempo:(double)t { - tempo = t; -} - - (void)setEqualizerEnabled:(BOOL)enabled { if(enabled && !eqEnabled) { if(_eq) { @@ -1575,15 +1308,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.enableHrtf" context:kOutputCoreAudioContext]; [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.enableHeadTracking" context:kOutputCoreAudioContext]; [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.enableFSurround" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandEngine" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandTransients" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandDetector" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandPhase" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandWindow" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandSmoothing" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandFormant" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandPitch" context:kOutputCoreAudioContext]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.rubberbandChannels" context:kOutputCoreAudioContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.tempo" context:kOutputCoreAudioContext]; observersapplied = NO; } stopping = YES; @@ -1657,10 +1382,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons rsstate_delete(rsvis); rsvis = NULL; } - if(ts) { - rubberband_delete(tts); - ts = NULL; - } stopCompleted = YES; } }