Rubber Band: Move everything to a DSP class

This class can more flexibly process and emit varying chunk sizes than
the previous code could, solving the problem of wide tempo changes.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2025-02-11 00:55:29 -08:00
parent 24f9ca9214
commit 4fefdc7ea3
16 changed files with 652 additions and 445 deletions

View file

@ -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"];
}
}

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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];
}

20
Audio/Chain/DSPNode.h Normal file
View file

@ -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 */

40
Audio/Chain/DSPNode.m Normal file
View file

@ -0,0 +1,40 @@
//
// DSPNode.m
// CogAudio Framework
//
// Created by Christopher Snowhill on 2/10/25.
//
#import <Foundation/Foundation.h>
#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

View file

@ -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 */

View file

@ -0,0 +1,464 @@
//
// DSPRubberbandNode.m
// CogAudio Framework
//
// Created by Christopher Snowhill on 2/10/25.
//
#import <Foundation/Foundation.h>
#import <Accelerate/Accelerate.h>
#import "DSPRubberbandNode.h"
#import <rubberband/rubberband-c.h>
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

View file

@ -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!");

View file

@ -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];

View file

@ -61,9 +61,6 @@
- (void)setVolume:(double)v;
- (void)setPitch:(double)p;
- (void)setTempo:(double)t;
- (void)setShouldContinue:(BOOL)s;
- (void)setShouldPlayOutBuffer:(BOOL)s;

View file

@ -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];

View file

@ -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 = "<group>"; };
839E56F6287974A100DFB5F4 /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../Utils/SandboxBroker.h; sourceTree = "<group>"; };
83B74280289E027F005AAC28 /* CogAudio-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CogAudio-Bridging-Header.h"; sourceTree = "<group>"; };
83FFED502D5B08BC0044CCAF /* DSPNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPNode.h; sourceTree = "<group>"; };
83FFED522D5B09320044CCAF /* DSPNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSPNode.m; sourceTree = "<group>"; };
83FFED542D5B0A1C0044CCAF /* DSPRubberbandNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DSPRubberbandNode.h; sourceTree = "<group>"; };
83FFED562D5B0A6B0044CCAF /* DSPRubberbandNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DSPRubberbandNode.m; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View file

@ -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

View file

@ -23,8 +23,6 @@
#import "FSurroundFilter.h"
#import <rubberband/rubberband-c.h>
#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;
}
}