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:
parent
24f9ca9214
commit
4fefdc7ea3
16 changed files with 652 additions and 445 deletions
|
@ -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"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
20
Audio/Chain/DSPNode.h
Normal 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
40
Audio/Chain/DSPNode.m
Normal 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
|
28
Audio/Chain/DSPRubberbandNode.h
Normal file
28
Audio/Chain/DSPRubberbandNode.h
Normal 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 */
|
464
Audio/Chain/DSPRubberbandNode.m
Normal file
464
Audio/Chain/DSPRubberbandNode.m
Normal 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
|
|
@ -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!");
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -61,9 +61,6 @@
|
|||
|
||||
- (void)setVolume:(double)v;
|
||||
|
||||
- (void)setPitch:(double)p;
|
||||
- (void)setTempo:(double)t;
|
||||
|
||||
- (void)setShouldContinue:(BOOL)s;
|
||||
|
||||
- (void)setShouldPlayOutBuffer:(BOOL)s;
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue