From 0498eb5f81c000c96c85cc178b86ff7881844f1f Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Mon, 10 Feb 2025 14:37:07 -0800 Subject: [PATCH] Rubber Band: Implement configuration dialog Now there's a configuration dialog for tweaking the settings in semi-real time. Everything that can be changed without restarting is changed without restarting, otherwise the audio pipeline is reset, which happens quickly enough anyway. Awaiting translation to Spanish, other languages have been removed pending their maintainers fixing most of their problems, which includes me being lazy and AI translating bits so I could rush updates. Signed-off-by: Christopher Snowhill --- Audio/Output/OutputCoreAudio.h | 2 + Audio/Output/OutputCoreAudio.m | 170 +++++++- .../Preferences/Base.lproj/Preferences.xib | 382 +++++++++++++++++- .../Preferences/GeneralPreferencesPlugin.h | 3 + .../Preferences/GeneralPreferencesPlugin.m | 7 +- Preferences/Preferences/Icons/rubberband.pdf | Bin 0 -> 3276 bytes .../Preferences.xcodeproj/project.pbxproj | 10 + Preferences/Preferences/RubberbandPane.h | 68 ++++ Preferences/Preferences/RubberbandPane.m | 163 ++++++++ .../Preferences/en.lproj/Localizable.strings | 23 ++ .../Preferences/en.lproj/Preferences.strings | 27 ++ .../Preferences/es.lproj/Preferences.strings | 27 ++ en.lproj/Credits.html | 2 +- 13 files changed, 870 insertions(+), 14 deletions(-) create mode 100644 Preferences/Preferences/Icons/rubberband.pdf create mode 100644 Preferences/Preferences/RubberbandPane.h create mode 100644 Preferences/Preferences/RubberbandPane.m 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 0000000000000000000000000000000000000000..25de11a734209a5314219aea68965803a17ae969 GIT binary patch literal 3276 zcma)<2T)U47su%zgd*M0AJR)mLZk`^5keI(F@Q87Arz$(5GkT`Pc|x9hD9rxbDu*?0nzsn>Y8~bMKoo=gj~AW`0-9R2v0>%PD}w>Q}1vLFWM& z;DNgVQd0w9@f&)=P@LMZ-Gnz92T%GG2r< z6%*Ur6FL$&`$CS?LJrk)LyiLHtoP@gw!N?yI$o>02iv|pIIxUN7k_kTgKgG6jAXyv z?`bzU%0C;_Ceie*Vn>DC7$iT3z^~TUWc2NB7IU>tU~BSi`xIiomz!6{d9|SDk*^{n zxW=J|5nPpf3cCwZHuXLizZ;mD@U1L~zANLf0ONHQ5m4dk;2;lHbdD%-5+^ z$tzC||N7~bDu>BCVMX+dC$`ltyUL8$i3Wsn{?yx7+*}9S9tq0V8qY@u)lairlS?W?K^M^BtQZ#Wwc6t1?O&6=y=3+^tH8du%KrK=c_gZJvR*LU$Zv_~@kX-| zYo&Q;Tu9&r!`xB5-DOl__HX8x_JF0&q^Q7~c=8J-+(7pkWH%T6I6$mzLeliexAS)U zdW2dqT+@(fbWSM=@-usZu0lherf&!iWKO2m-Uf42B|YphNne?}pa*_1&aap7VU~6~ zQ`yH!hiDa#kO0&WH|OM_V7k(ay8YX|A~N*UJYFo*ve=Tr&E?PkQAcdjIZxO8Ceq?3 z$AFW^hBlzWM-$fr^`K+Ve&MgZCy$iEpr?I*C4*M8v~veXde+RvSltbLT7du3eWz(K$M! z6p``zVruJITUrZnJH@%AR-P#>8r82ru4Wmgn!_$+xnsU_PX#i1CL zJd)i+kHZ?nR{x&*H7}D@N>V~OTU|l;qK2U-t49`1n(>TtpKVJNqdP~1KyriO8v!cr4jXbGM;VBnw-)d# z0MW|^Qy}P9WSB-DRDq;xbR9A`65gP)DbA1?I{Jk%mY9HU8d1T%w7}IBo$%X z>6YiPsRptZB;bSgMqNZcD zT`Mc3*#3h|a12w7D#vOzl*-lAAmK$DM5wwAvW+c+s(!y6i$J5tEY>X@y10T?0oo&5$b?H_6g)D}fWKa=i|cSo~I>n#eB zK8Q;8^I|j;J1%lh&0@2+=@*5T<=Ac4(oL~E7~7VjZEG~Cpauz3U9?6@O8bbpLW=oY zIicSw`*^Z~B4!PlCz^xn_Y$F%XT`6Ak+S72ci)OBBl%j%R|i{&WmL%YOKIE}gVsQj zgvCw|H3qqGd^+lO@d%R^q+Cz#(CG5}h#5iSG_$TV1-)P1B9(FeoM|Ny{&2ot#+dT4 z)C$8Ru7WZ&=)C}uqLfunMt@f?J%+{BgHb6td4KPzEENi`Cn~Z+fpY{6#66zED^5qE zfo+<}4SbXnzf<&C!4xRGla%&U`0H0nzO($%0rn=Cwk93xFE6e3vzj|iLusmJCsX_yKTh^xG_GE?E59I>t zz$B-ZXM2mUE~cXYj+M0J5vdnp8`bJQ%vK+WH-%`tN#TuLk{%SdT^YHuTxMJiViv38 z2zMw$HhSn`CjX zj?e)Z{{sm3IMe%^-n)d!B9)Tg`k^}7s`+$U&y%YU=MSgIV7nexZ%ZE79NX-32ULkG#{nrcT)k#@?GjKIu?N}a)aOV!o@m7UeDt~nIlQ}fmw#- zJ@WW4nJ*X?PyhR;OP<_As)j1fH=P<3_r?qeKzgA(uf-5b3K`^GMM{w+JG*H6lowyF zmr5y4z^8g<)nO0aOMcmr73eye!8$Fc9oAzO1B7!z{o&ICK6A)Lx0IW8jF4j; zNza+B5urUihyeWbH~GUN`tFV%09^h@{P(*7-p+34I6vlI z$~{W$r!em!1Pms>PR(G + +@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.