Skip to content

Commit

Permalink
Implemented real pitch and time shifting using Rubber Band
Browse files Browse the repository at this point in the history
I will implement the more complex setup of providing options for
most of the configuration that Rubber Band provides, at a later
date, when I feel like creating a complex configuration dialog
for it, and asking for help translating every option and setting.

Signed-off-by: Christopher Snowhill <[email protected]>
  • Loading branch information
kode54 committed Dec 10, 2024
1 parent 59f3f41 commit 9c6915e
Show file tree
Hide file tree
Showing 34 changed files with 2,326 additions and 359 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Xcode-config/DEVELOPMENT_TEAM.xcconfig
/ThirdParty/ogg/lib/libogg.0.dylib
/ThirdParty/opus/lib/libopus.0.dylib
/ThirdParty/opusfile/lib/libopusfile.0.dylib
/ThirdParty/rubberband/lib/librubberband.3.dylib
/ThirdParty/speex/libspeex.a
/ThirdParty/vorbis/lib/libvorbisfile.3.dylib
/ThirdParty/vorbis/lib/libvorbis.0.dylib
Expand Down
21 changes: 15 additions & 6 deletions Application/PlaybackController.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
#define DEFAULT_VOLUME_DOWN 5
#define DEFAULT_VOLUME_UP DEFAULT_VOLUME_DOWN

#define DEFAULT_SPEED_DOWN 0.2
#define DEFAULT_SPEED_UP DEFAULT_SPEED_DOWN
#define DEFAULT_PITCH_DOWN 0.2
#define DEFAULT_PITCH_UP DEFAULT_PITCH_DOWN

#define DEFAULT_TEMPO_DOWN 0.2
#define DEFAULT_TEMPO_UP DEFAULT_TEMPO_DOWN

extern NSString *CogPlaybackDidBeginNotificiation;
extern NSString *CogPlaybackDidPauseNotificiation;
Expand All @@ -43,7 +46,9 @@ extern NSDictionary *makeRGInfo(PlaylistEntry *pe);
IBOutlet EqualizerWindowController *equalizerWindowController;

IBOutlet NSSlider *volumeSlider;
IBOutlet NSSlider *speedSlider;
IBOutlet NSSlider *pitchSlider;
IBOutlet NSSlider *tempoSlider;
IBOutlet NSButton *lockButton;

IBOutlet NSArrayController *outputDevices;

Expand Down Expand Up @@ -73,9 +78,13 @@ extern NSDictionary *makeRGInfo(PlaylistEntry *pe);
- (IBAction)volumeDown:(id)sender;
- (IBAction)volumeUp:(id)sender;

- (IBAction)changeSpeed:(id)sender;
- (IBAction)speedDown:(id)sender;
- (IBAction)speedUp:(id)sender;
- (IBAction)changePitch:(id)sender;
- (IBAction)pitchDown:(id)sender;
- (IBAction)pitchUp:(id)sender;

- (IBAction)changeTempo:(id)sender;
- (IBAction)tempoDown:(id)sender;
- (IBAction)tempoUp:(id)sender;

- (IBAction)playPauseResume:(id)sender;
- (IBAction)pauseResume:(id)sender;
Expand Down
123 changes: 105 additions & 18 deletions Application/PlaybackController.m
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ - (id)init {

- (void)initDefaults {
NSDictionary *defaultsDictionary = @{ @"volume": @(75.0),
@"speed": @(1.0),
@"pitch": @(1.0),
@"tempo": @(1.0),
@"speedLock": @(YES),
@"GraphicEQenable": @(NO),
@"GraphicEQpreset": @(-1),
@"GraphicEQtrackgenre": @(NO),
Expand All @@ -101,6 +103,16 @@ - (void)initDefaults {
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
}

static double speedScale(double input, double min, double max) {
input = (input - min) * 100.0 / (max - min);
return ((input * input) * (5.0 - 0.2) / 10000.0) + 0.2;
}

static double reverseSpeedScale(double input, double min, double max) {
input = sqrtf((input - 0.2) * 10000.0 / (5.0 - 0.2));
return (input * (max - min) / 100.0) + min;
}

- (void)awakeFromNib {
BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"];
const double MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0;
Expand All @@ -110,8 +122,15 @@ - (void)awakeFromNib {
[volumeSlider setDoubleValue:logarithmicToLinear(volume, MAX_VOLUME)];
[audioPlayer setVolume:volume];

double speed = [[NSUserDefaults standardUserDefaults] doubleForKey:@"speed"];
[audioPlayer setSpeed:speed];
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"];
[lockButton setTitle:speedLock ? @"🔒" : @"🔓"];

[self setSeekable:NO];
}
Expand Down Expand Up @@ -253,7 +272,7 @@ - (void)playEntry:(PlaylistEntry *)pe startPaused:(BOOL)paused andSeekTo:(id)off

#if 0
// Race here, but the worst that could happen is we re-read the data
if ([pe metadataLoaded] != YES) {
if([pe metadataLoaded] != YES) {
[pe performSelectorOnMainThread:@selector(setMetadata:) withObject:[playlistLoader readEntryInfo:pe] waitUntilDone:YES];
}
#elif 0
Expand Down Expand Up @@ -381,7 +400,7 @@ - (void)changePlayButtonImage:(NSString *)name
NSImage *img = [NSImage imageNamed:name];
// [img retain];
if (img == nil)
if(img == nil)
{
DLog(@"Error loading image!");
}
Expand Down Expand Up @@ -482,12 +501,34 @@ - (IBAction)fade:(id)sender {
}
}

- (IBAction)changeSpeed:(id)sender {
DLog(@"SPEED: %lf", [sender doubleValue]);
- (IBAction)changePitch:(id)sender {
const double pitch = speedScale([sender doubleValue], [pitchSlider minValue], [pitchSlider maxValue]);
DLog(@"PITCH: %lf", pitch);

[audioPlayer setSpeed:[sender doubleValue]];
[audioPlayer setPitch:pitch];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer speed] forKey:@"speed"];
[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"];

if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) {
[audioPlayer setTempo:pitch];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"];
}
}

- (IBAction)changeTempo:(id)sender {
const double tempo = speedScale([sender doubleValue], [tempoSlider minValue], [tempoSlider maxValue]);
DLog(@"TEMPO: %lf", tempo);

[audioPlayer setTempo:tempo];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"];

if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) {
[audioPlayer setPitch:tempo];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"];
}
}

- (IBAction)skipToNextAlbum:(id)sender {
Expand Down Expand Up @@ -591,18 +632,64 @@ - (IBAction)volumeUp:(id)sender {
[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer volume] forKey:@"volume"];
}

- (IBAction)speedDown:(id)sender {
double newSpeed = [audioPlayer speedDown:DEFAULT_SPEED_DOWN];
[speedSlider setDoubleValue:[audioPlayer speed]];
- (IBAction)pitchDown:(id)sender {
/*double newPitch = */[audioPlayer pitchDown:DEFAULT_PITCH_DOWN];
[pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer speed] forKey:@"speed"];
[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"];

if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) {
[audioPlayer setTempo:[audioPlayer pitch]];

[tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"];
}
}

- (IBAction)speedUp:(id)sender {
double newSpeed = [audioPlayer speedUp:DEFAULT_SPEED_UP];
[speedSlider setDoubleValue:[audioPlayer speed]];
- (IBAction)pitchUp:(id)sender {
/*double newPitch = */[audioPlayer tempoUp:DEFAULT_PITCH_UP];
[pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer speed] forKey:@"speed"];
[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"];

if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) {
[audioPlayer setTempo:[audioPlayer pitch]];

[tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"];
}
}

- (IBAction)tempoDown:(id)sender {
/*double newTempo = */[audioPlayer tempoDown:DEFAULT_TEMPO_DOWN];
[tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"];

if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) {
[audioPlayer setPitch:[audioPlayer tempo]];

[pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"];
}
}

- (IBAction)tempoUp:(id)sender {
/*double newTempo = */[audioPlayer tempoUp:DEFAULT_PITCH_UP];
[tempoSlider setDoubleValue:reverseSpeedScale([audioPlayer tempo], [tempoSlider minValue], [tempoSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer tempo] forKey:@"tempo"];

if([[NSUserDefaults standardUserDefaults] boolForKey:@"speedLock"]) {
[audioPlayer setPitch:[audioPlayer tempo]];

[pitchSlider setDoubleValue:reverseSpeedScale([audioPlayer pitch], [pitchSlider minValue], [pitchSlider maxValue])];

[[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer pitch] forKey:@"pitch"];
}
}

- (void)audioPlayer:(AudioPlayer *)player displayEqualizer:(AudioUnit)eq {
Expand Down Expand Up @@ -764,7 +851,7 @@ - (void)audioPlayer:(AudioPlayer *)player restartPlaybackAtCurrentPosition:(id)u

- (void)audioPlayer:(AudioPlayer *)player pushInfo:(NSDictionary *)info toTrack:(id)userInfo {
PlaylistEntry *pe = (PlaylistEntry *)userInfo;
if (!pe) pe = [playlistController currentEntry];
if(!pe) pe = [playlistController currentEntry];
[pe setMetadata:info];
[playlistView refreshTrack:pe];
// Delay the action until this function has returned to the audio thread
Expand Down
16 changes: 11 additions & 5 deletions Audio/AudioPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
OutputNode *output;

double volume;
double speed;
double pitch;
double tempo;

NSMutableArray *chainQueue;

Expand Down Expand Up @@ -75,10 +76,15 @@
- (double)volumeUp:(double)amount;
- (double)volumeDown:(double)amount;

- (void)setSpeed:(double)s;
- (double)speed;
- (double)speedUp:(double)amount;
- (double)speedDown:(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;
Expand Down
85 changes: 63 additions & 22 deletions Audio/AudioPlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ - (id)init {
bufferChain = nil;
outputLaunched = NO;
endOfInputReached = NO;

// Safety
pitch = 1.0;
tempo = 1.0;

chainQueue = [[NSMutableArray alloc] init];

Expand Down Expand Up @@ -75,7 +79,8 @@ - (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)r
}
[output setup];
[output setVolume:volume];
[output setSpeed:speed];
[output setPitch:pitch];
[output setTempo:tempo];
@synchronized(chainQueue) {
for(id anObject in chainQueue) {
[anObject setShouldContinue:NO];
Expand Down Expand Up @@ -211,14 +216,24 @@ - (double)volume {
return volume;
}

- (void)setSpeed:(double)s {
speed = s;
- (void)setPitch:(double)p {
pitch = p;

[output setPitch:p];
}

- (double)pitch {
return pitch;
}

- (void)setTempo:(double)t {
tempo = t;

[output setSpeed:s];
[output setTempo:t];
}

- (double)speed {
return speed;
- (double)tempo {
return tempo;
}

// This is called by the delegate DURING a requestNextStream request.
Expand Down Expand Up @@ -659,30 +674,56 @@ - (double)volumeDown:(double)amount {
return newVolume;
}

- (double)speedUp:(double)amount {
const double MAX_SPEED = 5.0;
- (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 newSpeed;
if((speed + amount) > MAX_SPEED)
newSpeed = MAX_SPEED;
double newTempo;
if((tempo + amount) > MAX_TEMPO)
newTempo = MAX_TEMPO;
else
newSpeed = speed + amount;
newTempo = tempo + amount;

[self setSpeed:newSpeed];
return newSpeed;
[self setTempo:newTempo];
return newTempo;
}

- (double)speedDown:(double)amount {
const double MIN_SPEED = 0.2;
- (double)tempoDown:(double)amount {
const double MIN_TEMPO = 0.2;

double newSpeed;
if((speed - amount) < MIN_SPEED)
newSpeed = MIN_SPEED;
double newTempo;
if((tempo - amount) < MIN_TEMPO)
newTempo = MIN_TEMPO;
else
newSpeed = speed - amount;
newTempo = tempo - amount;

[self setSpeed:newSpeed];
return newSpeed;
[self setTempo:newTempo];
return newTempo;
}

- (void)waitUntilCallbacksExit {
Expand Down
3 changes: 2 additions & 1 deletion Audio/Chain/OutputNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@

- (void)setVolume:(double)v;

- (void)setSpeed:(double)s;
- (void)setPitch:(double)p;
- (void)setTempo:(double)t;

- (void)setShouldContinue:(BOOL)s;

Expand Down
Loading

0 comments on commit 9c6915e

Please sign in to comment.