diff --git a/Audio/Output/OutputCoreAudio.h b/Audio/Output/OutputCoreAudio.h index 5cbb86285..1f9083b36 100644 --- a/Audio/Output/OutputCoreAudio.h +++ b/Audio/Output/OutputCoreAudio.h @@ -55,9 +55,11 @@ using std::atomic_long; double lastClippedSampleRate; void *ts; + int tslastoptions, tsnewoptions; size_t blockSize, toDrop, samplesBuffered; double ssRenderedIn, ssLastRenderedIn; double ssRenderedOut; + BOOL tsapplynewoptions; void *rsvis; double lastVisRate; diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index fcda82ebc..ff10e9c88 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -34,6 +34,7 @@ static NSString *CogPlaybackDidBeginNotificiation = @"CogPlaybackDidBeginNotific static NSString *CogPlaybackDidResetHeadTracking = @"CogPlaybackDigResetHeadTracking"; #define tts ((RubberBandState)ts) +#define ttslastoptions ((RubberBandOptions)tslastoptions) simd_float4x4 convertMatrix(CMRotationMatrix r) { simd_float4x4 matrix = { @@ -53,6 +54,19 @@ 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) { @@ -406,6 +420,20 @@ 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; + } + } } } @@ -771,6 +799,94 @@ 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; @@ -843,7 +959,8 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons ts = NULL; } - RubberBandOptions options = RubberBandOptionProcessRealTime; + RubberBandOptions options = [self getRubberbandOptions]; + tslastoptions = (int)(options); ts = rubberband_new(realStreamFormat.mSampleRate, realStreamFormat.mChannelsPerFrame, options, 1.0 / tempo, pitch); blockSize = 1024; @@ -1000,7 +1117,33 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons int channels = realStreamFormat.mChannelsPerFrame; //size_t max_block_len = blockSize; - if (fabs(pitch - lastPitch) > 1e-5 || + 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; @@ -1011,7 +1154,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons const double inputRatio = 1.0 / realStreamFormat.mSampleRate; const double outputRatio = inputRatio * tempo; - while (simpleSpeedInput > 0) { + while(simpleSpeedInput > 0) { float *ibuf = samplePtr; size_t len = simpleSpeedInput; if(len > blockSize) len = blockSize; @@ -1027,7 +1170,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons ssRenderedIn += len * inputRatio; size_t samplesAvailable; - while ((samplesAvailable = rubberband_available(tts)) > 0) { + while((samplesAvailable = rubberband_available(tts)) > 0) { if(toDrop > 0) { size_t blockDrop = toDrop; if(blockDrop > blockSize) blockDrop = blockSize; @@ -1333,6 +1476,16 @@ 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]; + observersapplied = YES; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetReferencePosition:) name:CogPlaybackDidResetHeadTracking object:nil]; @@ -1422,6 +1575,15 @@ 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]; observersapplied = NO; } stopping = YES; diff --git a/Preferences/Preferences/Base.lproj/Preferences.xib b/Preferences/Preferences/Base.lproj/Preferences.xib index 2cbef3953..d6e8b6757 100644 --- a/Preferences/Preferences/Base.lproj/Preferences.xib +++ b/Preferences/Preferences/Base.lproj/Preferences.xib @@ -17,6 +17,7 @@ + @@ -336,7 +337,7 @@ - + @@ -366,7 +367,7 @@ - + @@ -382,9 +383,9 @@ - - - + + + @@ -408,9 +409,25 @@ + + + + + + + + + + + + + + name + slug + preference @@ -814,7 +831,7 @@ - + @@ -844,7 +861,7 @@ - + @@ -874,7 +891,7 @@ - + @@ -1058,6 +1075,77 @@ valid + + + name + slug + preference + + + + + + name + slug + preference + + + + + + name + slug + preference + + + + + + name + slug + preference + + + + + + name + slug + preference + + + + + name + slug + preference + + + + + + name + slug + preference + + + + + + name + slug + preference + + + + + + name + slug + preference + + + @@ -1088,5 +1176,283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Preferences/Preferences/GeneralPreferencesPlugin.h b/Preferences/Preferences/GeneralPreferencesPlugin.h index c0c5c38cc..e6bf51cdf 100644 --- a/Preferences/Preferences/GeneralPreferencesPlugin.h +++ b/Preferences/Preferences/GeneralPreferencesPlugin.h @@ -15,6 +15,7 @@ #import "MIDIPane.h" #import "OutputPane.h" #import "AppearancePane.h" +#import "RubberbandPane.h" @interface GeneralPreferencesPlugin : NSObject { IBOutlet HotKeyPane *hotKeyPane; @@ -22,6 +23,7 @@ IBOutlet MIDIPane *midiPane; IBOutlet GeneralPane *generalPane; IBOutlet AppearancePane *appearancePane; + IBOutlet RubberbandPane *rubberbandPane; IBOutlet NSView *playlistView; IBOutlet NSView *updatesView; @@ -39,5 +41,6 @@ - (GeneralPreferencePane *)playlistPane; - (GeneralPreferencePane *)notificationsPane; - (GeneralPreferencePane *)appearancePane; +- (GeneralPreferencePane *)rubberbandPane; @end diff --git a/Preferences/Preferences/GeneralPreferencesPlugin.m b/Preferences/Preferences/GeneralPreferencesPlugin.m index 2ff436c85..2eb325f19 100644 --- a/Preferences/Preferences/GeneralPreferencesPlugin.m +++ b/Preferences/Preferences/GeneralPreferencesPlugin.m @@ -40,7 +40,8 @@ [plugin generalPane], [plugin notificationsPane], [plugin appearancePane], - [plugin midiPane]]; + [plugin midiPane], + [plugin rubberbandPane]]; } - (HotKeyPane *)hotKeyPane { @@ -93,4 +94,8 @@ return appearancePane; } +- (RubberbandPane *)rubberbandPane { + return rubberbandPane; +} + @end diff --git a/Preferences/Preferences/Icons/rubberband.pdf b/Preferences/Preferences/Icons/rubberband.pdf new file mode 100644 index 000000000..25de11a73 Binary files /dev/null and b/Preferences/Preferences/Icons/rubberband.pdf differ diff --git a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj index 02511b44e..5a010673a 100644 --- a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj +++ b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj @@ -33,6 +33,8 @@ 83AC573A2861A54D009D6F50 /* PathSuggester.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AC57382861A54D009D6F50 /* PathSuggester.m */; }; 83B06729180D85B8008E3612 /* MIDIPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 83B06728180D85B8008E3612 /* MIDIPane.m */; }; 83B0672B180D8B39008E3612 /* midi.png in Resources */ = {isa = PBXBuildFile; fileRef = 83B0672A180D8B39008E3612 /* midi.png */; }; + 83B8FE242D59CDA7005854C1 /* rubberband.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83B8FE232D59CDA7005854C1 /* rubberband.pdf */; }; + 83B8FE292D59D059005854C1 /* RubberbandPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 83B8FE282D59D059005854C1 /* RubberbandPane.m */; }; 83EF495F17FBC96A00642E3C /* VolumeBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */; }; 83F27E6B1810DD3A00CEF538 /* appearance@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E651810DD3A00CEF538 /* appearance@2x.png */; }; 83F27E6E1810DD3A00CEF538 /* midi@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E681810DD3A00CEF538 /* midi@2x.png */; }; @@ -133,6 +135,9 @@ 83B06727180D85B8008E3612 /* MIDIPane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIPane.h; sourceTree = ""; }; 83B06728180D85B8008E3612 /* MIDIPane.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIPane.m; sourceTree = ""; }; 83B0672A180D8B39008E3612 /* midi.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = midi.png; path = Icons/midi.png; sourceTree = ""; }; + 83B8FE232D59CDA7005854C1 /* rubberband.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = rubberband.pdf; path = Icons/rubberband.pdf; sourceTree = ""; }; + 83B8FE272D59D03F005854C1 /* RubberbandPane.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RubberbandPane.h; sourceTree = ""; }; + 83B8FE282D59D059005854C1 /* RubberbandPane.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RubberbandPane.m; sourceTree = ""; }; 83BC5AB320E4C90F00631CD4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Preferences.xib; sourceTree = ""; }; 83DAD9F1286EDBCD000FAA9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PathSuggester.xib; sourceTree = ""; }; 83DAD9F3286EDBD2000FAA9A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/PathSuggester.strings; sourceTree = ""; }; @@ -264,6 +269,8 @@ 83B06728180D85B8008E3612 /* MIDIPane.m */, 8307D309286057B5000FF8EB /* GeneralPane.h */, 8307D30A286057B5000FF8EB /* GeneralPane.m */, + 83B8FE272D59D03F005854C1 /* RubberbandPane.h */, + 83B8FE282D59D059005854C1 /* RubberbandPane.m */, ); name = Panes; sourceTree = ""; @@ -346,6 +353,7 @@ 17C643680B8A788000C53518 /* output.png */, 17C7E5AF0DCCC30A003CBCF7 /* playlist.png */, 83F27E691810DD3A00CEF538 /* playlist@2x.png */, + 83B8FE232D59CDA7005854C1 /* rubberband.pdf */, 8E15A86B0B894768006DC802 /* updates.png */, 83F27E6A1810DD3A00CEF538 /* updates@2x.png */, ); @@ -473,6 +481,7 @@ 83F27E6F1810DD3A00CEF538 /* playlist@2x.png in Resources */, 17E41DB80C130AA500AC744D /* Localizable.strings in Resources */, 8307D31828606EAF000FF8EB /* general.png in Resources */, + 83B8FE242D59CDA7005854C1 /* rubberband.pdf in Resources */, 17E78A7E0D68BE3C005C5A59 /* file_tree.png in Resources */, 83F27E701810DD3A00CEF538 /* updates@2x.png in Resources */, 17E78B6A0D68C1E3005C5A59 /* Preferences.xib in Resources */, @@ -490,6 +499,7 @@ buildActionMask = 2147483647; files = ( 83AC573A2861A54D009D6F50 /* PathSuggester.m in Sources */, + 83B8FE292D59D059005854C1 /* RubberbandPane.m in Sources */, 83726F022CF41B3200F15FBF /* AppearancePane.m in Sources */, 83651DA527322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m in Sources */, 83B06729180D85B8008E3612 /* MIDIPane.m in Sources */, diff --git a/Preferences/Preferences/RubberbandPane.h b/Preferences/Preferences/RubberbandPane.h new file mode 100644 index 000000000..96d9567ce --- /dev/null +++ b/Preferences/Preferences/RubberbandPane.h @@ -0,0 +1,68 @@ +// +// Rubberband.h +// Preferences +// +// Created by Christopher Snowhill on 2/9/25. +// + +#ifndef Rubberband_h +#define Rubberband_h + +#import "GeneralPreferencePane.h" +#import + +@interface RubberbandWindowArrayController : NSArrayController + +- (void)reinitWithEngine:(BOOL)engineR3; + +@end + +@interface RubberbandPane : GeneralPreferencePane { + IBOutlet NSTextField *transientsLabel; + IBOutlet NSPopUpButton *transientsButton; + IBOutlet NSTextField *detectorLabel; + IBOutlet NSPopUpButton *detectorButton; + IBOutlet NSTextField *phaseLabel; + IBOutlet NSPopUpButton *phaseButton; + IBOutlet RubberbandWindowArrayController *windowBehavior; + IBOutlet NSTextField *smoothingLabel; + IBOutlet NSPopUpButton *smoothingButton; +} + +- (IBAction)changeState:(id)sender; + +@end + +@interface RubberbandEngineArrayController : NSArrayController + +@end + +@interface RubberbandTransientsArrayController : NSArrayController + +@end + +@interface RubberbandDetectorArrayController : NSArrayController + +@end + +@interface RubberbandPhaseArrayController : NSArrayController + +@end + +@interface RubberbandSmoothingArrayController : NSArrayController + +@end + +@interface RubberbandFormantArrayController : NSArrayController + +@end + +@interface RubberbandPitchArrayController : NSArrayController + +@end + +@interface RubberbandChannelsArrayController : NSArrayController + +@end + +#endif /* Rubberband_h */ diff --git a/Preferences/Preferences/RubberbandPane.m b/Preferences/Preferences/RubberbandPane.m new file mode 100644 index 000000000..7e009f6c1 --- /dev/null +++ b/Preferences/Preferences/RubberbandPane.m @@ -0,0 +1,163 @@ +// +// Rubberband.m +// Preferences +// +// Created by Christopher Snowhill on 2/9/25. +// + +#import + +#import "RubberbandPane.h" + +@implementation RubberbandPane + +- (NSString *)title { + return NSLocalizedPrefString(@"Rubber Band"); +} + +- (NSImage *)icon { + NSImage *icon = [[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForImageResource:@"rubberband"]]; + [icon setTemplate:YES]; + return icon; +} + +- (void)awakeFromNib { + [self changeState:self]; +} + +- (IBAction)changeState:(id)sender { + NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults]; + BOOL engineR3 = [[defaults stringForKey:@"rubberbandEngine"] isEqualToString:@"finer"]; + + [transientsLabel setEnabled:!engineR3]; + [transientsButton setEnabled:!engineR3]; + [phaseLabel setEnabled:!engineR3]; + [phaseButton setEnabled:!engineR3]; + [smoothingLabel setEnabled:!engineR3]; + [smoothingButton setEnabled:!engineR3]; + + [windowBehavior reinitWithEngine:engineR3]; + + if(engineR3) { + NSString *window = [defaults stringForKey:@"rubberbandWindow"]; + if([window isEqualToString:@"long"]) { + [defaults setValue:@"standard" forKey:@"rubberbandWindow"]; + } + } +} + +@end + +@implementation RubberbandEngineArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"EngineFaster", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"faster"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"EngineFiner", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"finer"}]; +} + +@end + +@implementation RubberbandTransientsArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"TransientsCrisp", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"crisp"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"TransientsMixed", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"mixed"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"TransientsSmooth", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"smooth"}]; +} + +@end + +@implementation RubberbandDetectorArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"DetectorCompound", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"compound"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"DetectorPercussive", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"percussive"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"DetectorSoft", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"soft"}]; +} + +@end + +@implementation RubberbandPhaseArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"PhaseLaminar", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"laminar"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"PhaseIndependent", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"independent"}]; +} + +@end + +@implementation RubberbandWindowArrayController +- (void)awakeFromNib { + BOOL engineR3 = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] stringForKey:@"rubberbandEngine"] isEqualToString:@"finer"]; + [self reinitWithEngine:engineR3]; +} + +- (void)reinitWithEngine:(BOOL)engineR3 { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"WindowStandard", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"standard"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"WindowShort", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"short"}]; + + if(!engineR3) { + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"WindowLong", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"long"}]; + } +} + +@end + +@implementation RubberbandSmoothingArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"SmoothingOff", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"off"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"SmoothingOn", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"on"}]; +} + +@end + +@implementation RubberbandFormantArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"FormantShifted", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"shifted"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"FormantPreserved", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"preserved"}]; +} + +@end + +@implementation RubberbandPitchArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"PitchHighSpeed", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"highspeed"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"PitchHighQuality", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"highquality"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"PitchHighConsistency", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"highconsistency"}]; +} + +@end + +@implementation RubberbandChannelsArrayController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"ChannelsApart", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"apart"}]; + + [self addObject:@{@"name": NSLocalizedStringFromTableInBundle(@"ChannelsTogether", nil, [NSBundle bundleForClass:[self class]], @""), @"preference": @"together"}]; +} + +@end diff --git a/Preferences/Preferences/en.lproj/Localizable.strings b/Preferences/Preferences/en.lproj/Localizable.strings index 1aeb50d2e..cf76d66d6 100644 --- a/Preferences/Preferences/en.lproj/Localizable.strings +++ b/Preferences/Preferences/en.lproj/Localizable.strings @@ -30,3 +30,26 @@ "ValidYes" = "Yes"; "Volume scale tag only" = "Volume scale tag only"; "Zero Order Hold" = "Zero Order Hold"; +"Rubber Band" = "Rubber Band"; +"EngineFaster" = "Faster"; +"EngineFiner" = "Finer"; +"TransientsCrisp" = "Crisp"; +"TransientsMixed" = "Mixed"; +"TransientsSmooth" = "Smooth"; +"DetectorCompound" = "Compound"; +"DetectorPercussive" = "Percussive"; +"DetectorSoft" = "Soft"; +"PhaseLaminar" = "Laminar"; +"PhaseIndependent" = "Independent"; +"WindowStandard" = "Standard"; +"WindowShort" = "Short"; +"WindowLong" = "Long"; +"SmoothingOff" = "Off"; +"SmoothingOn" = "On"; +"FormantShifted" = "Shifted"; +"FormantPreserved" = "Preserved"; +"PitchHighSpeed" = "High Speed"; +"PitchHighQuality" = "High Quality"; +"PitchHighConsistency" = "High Consistency"; +"ChannelsApart" = "Apart"; +"ChannelsTogether" = "Together"; diff --git a/Preferences/Preferences/en.lproj/Preferences.strings b/Preferences/Preferences/en.lproj/Preferences.strings index 8b8ec3222..bc47ea3a4 100644 --- a/Preferences/Preferences/en.lproj/Preferences.strings +++ b/Preferences/Preferences/en.lproj/Preferences.strings @@ -256,3 +256,30 @@ /* Class = "NSButtonCell"; title = "Render over background plaque"; ObjectID = "KZd-iR-dXL"; */ "KZd-iR-dXL.title" = "Render over background plaque"; + +/* Class = "NSTextFieldCell"; title = "Engine:"; ObjectID = "ULd-G9-gDZ"; */ +"ULd-G9-gDZ.title" = "Engine:"; + +/* Class = "NSTextFieldCell"; title = "Transients:"; ObjectID = "eI3-LU-JTj"; */ +"eI3-LU-JTj.title" = "Transients:"; + +/* Class = "NSTextFieldCell"; title = "Detector:"; ObjectID = "ZUS-Cr-zSa"; */ +"ZUS-Cr-zSa.title" = "Detector:"; + +/* Class = "NSTextFieldCell"; title = "Phase:"; ObjectID = "xwT-8b-IsJ"; */ +"xwT-8b-IsJ.title" = "Phase:"; + +/* Class = "NSTextFieldCell"; title = "Window Size:"; ObjectID = "qc8-jP-K6o"; */ +"qc8-jP-K6o.title" = "Window Size:"; + +/* Class = "NSTextFieldCell"; title = "Smoothing:"; ObjectID = "FLf-R7-Kew"; */ +"FLf-R7-Kew.title" = "Smoothing:"; + +/* Class = "NSTextFieldCell"; title = "Formant:"; ObjectID = "4zC-Y9-iRt"; */ +"4zC-Y9-iRt.title" = "Formant:"; + +/* Class = "NSTextFieldCell"; title = "Pitch:"; ObjectID = "FBC-tN-fwm"; */ +"FBC-tN-fwm.title" = "Pitch:"; + +/* Class = "NSTextFieldCell"; title = "Channels:"; ObjectID = "LpA-ak-Aj5"; */ +"LpA-ak-Aj5.title" = "Channels:"; diff --git a/Preferences/Preferences/es.lproj/Preferences.strings b/Preferences/Preferences/es.lproj/Preferences.strings index afc069cb8..3486a87eb 100644 --- a/Preferences/Preferences/es.lproj/Preferences.strings +++ b/Preferences/Preferences/es.lproj/Preferences.strings @@ -255,3 +255,30 @@ /* Class = "NSButtonCell"; title = "Render over background plaque"; ObjectID = "KZd-iR-dXL"; */ "KZd-iR-dXL.title" = "Mostrar fondo del icono"; + +/* Class = "NSTextFieldCell"; title = "Engine:"; ObjectID = "ULd-G9-gDZ"; */ +"ULd-G9-gDZ.title" = "Engine:"; + +/* Class = "NSTextFieldCell"; title = "Transients:"; ObjectID = "eI3-LU-JTj"; */ +"eI3-LU-JTj.title" = "Transients:"; + +/* Class = "NSTextFieldCell"; title = "Detector:"; ObjectID = "ZUS-Cr-zSa"; */ +"ZUS-Cr-zSa.title" = "Detector:"; + +/* Class = "NSTextFieldCell"; title = "Phase:"; ObjectID = "xwT-8b-IsJ"; */ +"xwT-8b-IsJ.title" = "Phase:"; + +/* Class = "NSTextFieldCell"; title = "Window Size:"; ObjectID = "qc8-jP-K6o"; */ +"qc8-jP-K6o.title" = "Window Size:"; + +/* Class = "NSTextFieldCell"; title = "Smoothing:"; ObjectID = "FLf-R7-Kew"; */ +"FLf-R7-Kew.title" = "Smoothing:"; + +/* Class = "NSTextFieldCell"; title = "Formant:"; ObjectID = "4zC-Y9-iRt"; */ +"4zC-Y9-iRt.title" = "Formant:"; + +/* Class = "NSTextFieldCell"; title = "Pitch:"; ObjectID = "FBC-tN-fwm"; */ +"FBC-tN-fwm.title" = "Pitch:"; + +/* Class = "NSTextFieldCell"; title = "Channels:"; ObjectID = "LpA-ak-Aj5"; */ +"LpA-ak-Aj5.title" = "Channels:"; diff --git a/en.lproj/Credits.html b/en.lproj/Credits.html index 17f5e361a..b4da13d34 100644 --- a/en.lproj/Credits.html +++ b/en.lproj/Credits.html @@ -35,7 +35,7 @@

This program has been made possible through contributions from users like you.

All Cog code is copyrighted by me, and is licensed under the GPL. Cog contains bits of other code from third parties that are under their own licenses. -

Rubber Band Library was used under the GPL to provide pitch and/or time shifting. +

Rubber Band Library was used under the GPL to provide pitch and/or time shifting. Rubber Band style icon by Lucas Helle.