diff --git a/Application/PlaybackController.h b/Application/PlaybackController.h index 67e912e6e..f76e3a139 100644 --- a/Application/PlaybackController.h +++ b/Application/PlaybackController.h @@ -13,7 +13,7 @@ #import #import -#import "AUPlayerView.h" +#import "EqualizerWindowController.h" #define DEFAULT_VOLUME_DOWN 5 #define DEFAULT_VOLUME_UP DEFAULT_VOLUME_DOWN @@ -36,6 +36,8 @@ extern NSDictionary *makeRGInfo(PlaylistEntry *pe); IBOutlet PlaylistView *playlistView; IBOutlet PlaylistLoader *playlistLoader; + IBOutlet EqualizerWindowController *equalizerWindowController; + IBOutlet NSSlider *volumeSlider; IBOutlet NSArrayController *outputDevices; @@ -53,10 +55,7 @@ extern NSDictionary *makeRGInfo(PlaylistEntry *pe); // progress bar display double progressBarStatus; - BOOL _eqWasOpen; - BOOL _eqStubbed; AudioUnit _eq; - AUPluginUI *_equi; } @property CogStatus playbackStatus; @@ -89,8 +88,6 @@ extern NSDictionary *makeRGInfo(PlaylistEntry *pe); - (IBAction)spam:(id)sender; -- (IBAction)showEq:(id)sender; - - (void)sendMetaData; - (void)initDefaults; diff --git a/Application/PlaybackController.m b/Application/PlaybackController.m index 23c15077f..2a93fda7c 100644 --- a/Application/PlaybackController.m +++ b/Application/PlaybackController.m @@ -43,8 +43,6 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation"; seekable = NO; fading = NO; - _eqWasOpen = NO; - _equi = nil; progressBarStatus = -1; @@ -509,114 +507,17 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { [[NSUserDefaults standardUserDefaults] setDouble:[audioPlayer volume] forKey:@"volume"]; } -- (void)eqAlloc { - // Show a stopped equalizer as a stub - OSStatus err; - AudioComponentDescription desc; - - desc.componentType = kAudioUnitType_Effect; - desc.componentSubType = kAudioUnitSubType_GraphicEQ; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - AudioComponent comp = NULL; - - desc.componentType = kAudioUnitType_Effect; - desc.componentSubType = kAudioUnitSubType_GraphicEQ; - - comp = AudioComponentFindNext(comp, &desc); - if(!comp) - return; - - err = AudioComponentInstanceNew(comp, &_eq); - if(err) - return; - - AudioUnitInitialize(_eq); -} - -- (void)eqDealloc { - AudioUnitUninitialize(_eq); - AudioComponentInstanceDispose(_eq); - _eq = nil; - _eqStubbed = NO; -} - -- (IBAction)showEq:(id)sender { - if(_eq) { - if(_equi && [_equi isOpen]) - [_equi bringToFront]; - else - _equi = [[AUPluginUI alloc] initWithSampler:_eq bringToFront:YES orWindowNumber:0]; - } else { - [self eqAlloc]; - _eqWasOpen = YES; - [self audioPlayer:nil displayEqualizer:_eq]; - [_equi bringToFront]; - } -} - - (void)audioPlayer:(AudioPlayer *)player displayEqualizer:(AudioUnit)eq { - if(_equi) { - _eqWasOpen = [_equi isOpen]; - _equi = nil; - } if(_eq && _eq != eq) { - OSStatus err; - CFPropertyListRef classData; - UInt32 size; - - size = sizeof(classData); - err = AudioUnitGetProperty(_eq, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &classData, &size); - if(err == noErr) { - CFPreferencesSetAppValue(CFSTR("GraphEQ_Preset"), classData, kCFPreferencesCurrentApplication); - CFRelease(classData); - } - - CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); - - // Caller relinquishes EQ to us - [self eqDealloc]; + [equalizerWindowController setEQ:nil]; } _eq = eq; - { - OSStatus err; - ComponentDescription cd; - CFPropertyListRef classData; - CFDictionaryRef dict; - CFNumberRef cfnum; + equalizerLoadPreset(eq); - classData = CFPreferencesCopyAppValue(CFSTR("GraphEQ_Preset"), kCFPreferencesCurrentApplication); - if(classData) { - dict = (CFDictionaryRef)classData; - - cfnum = (CFNumberRef)(CFDictionaryGetValue(dict, CFSTR("type"))); - CFNumberGetValue(cfnum, kCFNumberSInt32Type, &cd.componentType); - cfnum = (CFNumberRef)(CFDictionaryGetValue(dict, CFSTR("subtype"))); - CFNumberGetValue(cfnum, kCFNumberSInt32Type, &cd.componentSubType); - cfnum = (CFNumberRef)(CFDictionaryGetValue(dict, CFSTR("manufacturer"))); - CFNumberGetValue(cfnum, kCFNumberSInt32Type, &cd.componentManufacturer); - - if((cd.componentType == kAudioUnitType_Effect) && - (cd.componentSubType == kAudioUnitSubType_GraphicEQ) && - (cd.componentManufacturer == kAudioUnitManufacturer_Apple)) - err = AudioUnitSetProperty(eq, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &classData, sizeof(classData)); - - CFRelease(classData); - } - - equalizerLoadPreset(eq); - } - - if(_eqWasOpen) { - NSWindow *window = appController.miniMode ? appController.miniWindow : appController.mainWindow; - _equi = [[AUPluginUI alloc] initWithSampler:_eq bringToFront:NO orWindowNumber:window.windowNumber]; - _eqWasOpen = NO; - } + [equalizerWindowController setEQ:eq]; } - (void)audioPlayer:(AudioPlayer *)player refreshEqualizer:(AudioUnit)eq { @@ -638,16 +539,9 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); - if(_equi) { - _eqWasOpen = [_equi isOpen]; - } + [equalizerWindowController setEQ:nil]; - _equi = nil; - [self eqDealloc]; - - if(_eqWasOpen) { - [self showEq:nil]; - } + _eq = nil; } } diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index 88c394b05..4db89c763 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -813,8 +813,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const [writeSemaphore timedWait:5000]; } if(_eq) { - // This takes the EQ and frees it after disposing of any present UIs [outputController endEqualizer:_eq]; + AudioUnitUninitialize(_eq); + AudioComponentInstanceDispose(_eq); _eq = NULL; } if(downmixer) { diff --git a/Base.lproj/Equalizer.xib b/Base.lproj/Equalizer.xib new file mode 100644 index 000000000..c7a96735e --- /dev/null +++ b/Base.lproj/Equalizer.xib @@ -0,0 +1,828 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + name + slug + preference + + + + + + diff --git a/Base.lproj/MainMenu.xib b/Base.lproj/MainMenu.xib index 6883120aa..a24a33a01 100644 --- a/Base.lproj/MainMenu.xib +++ b/Base.lproj/MainMenu.xib @@ -836,7 +836,7 @@ - + @@ -1092,7 +1092,7 @@ - + @@ -1382,7 +1382,7 @@ - + @@ -2097,6 +2097,7 @@ Gw + @@ -2324,6 +2325,7 @@ Gw + diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 7452020af..30dd28234 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -97,6 +97,8 @@ 8305963C277F013200EBFAAE /* File_Extractor.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83059639277F011100EBFAAE /* File_Extractor.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 83059690277F04AB00EBFAAE /* Ogg.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8305968D277F049600EBFAAE /* Ogg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 830596EE277F05EE00EBFAAE /* Vorbis.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 830596E7277F05E200EBFAAE /* Vorbis.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 830C37A127B95E3000E02BB0 /* Equalizer.xib in Resources */ = {isa = PBXBuildFile; fileRef = 830C379F27B95E3000E02BB0 /* Equalizer.xib */; }; + 830C37A527B95EB300E02BB0 /* EqualizerWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 830C37A427B95EB300E02BB0 /* EqualizerWindowController.m */; }; 8314A46F27A28C29000EBE7E /* equalizerTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8314A46527A28C28000EBE7E /* equalizerTemplate.pdf */; }; 832923AF279FAC400048201E /* Cog.q1.json in Resources */ = {isa = PBXBuildFile; fileRef = 832923AE279FAC400048201E /* Cog.q1.json */; }; 83293070277886250010C07E /* OpenMPTOld.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8329306D277885790010C07E /* OpenMPTOld.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -174,7 +176,6 @@ 83D0380F24A40DFB004CF90F /* CogAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 83D0380E24A40DF2004CF90F /* CogAssets.xcassets */; }; 83E5E54C18087CA5001F3284 /* miniModeOffTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83E5E54A18087CA5001F3284 /* miniModeOffTemplate.pdf */; }; 83E5E54D18087CA5001F3284 /* miniModeOnTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83E5E54B18087CA5001F3284 /* miniModeOnTemplate.pdf */; }; - 83E88FD227945204003D6FE5 /* AUPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 83E88FD027945204003D6FE5 /* AUPlayerView.m */; }; 83ED3AD1279A91C000904199 /* hdcdLogoTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83ED3AC7279A91C000904199 /* hdcdLogoTemplate.pdf */; }; 83F9D8071A884C54007ABEC2 /* SilenceDecoder.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83F9D7F61A884B46007ABEC2 /* SilenceDecoder.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; @@ -912,6 +913,9 @@ 83059634277F011100EBFAAE /* File_Extractor.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = File_Extractor.xcodeproj; path = Frameworks/File_Extractor/File_Extractor.xcodeproj; sourceTree = ""; }; 83059684277F049600EBFAAE /* Ogg.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Ogg.xcodeproj; path = Frameworks/Ogg/macosx/Ogg.xcodeproj; sourceTree = ""; }; 830596DA277F05E200EBFAAE /* Vorbis.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Vorbis.xcodeproj; path = Frameworks/Vorbis/macosx/Vorbis.xcodeproj; sourceTree = ""; }; + 830C37A027B95E3000E02BB0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Equalizer.xib; sourceTree = ""; }; + 830C37A327B95EB300E02BB0 /* EqualizerWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EqualizerWindowController.h; path = Equalizer/EqualizerWindowController.h; sourceTree = ""; }; + 830C37A427B95EB300E02BB0 /* EqualizerWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = EqualizerWindowController.m; path = Equalizer/EqualizerWindowController.m; sourceTree = ""; }; 8314A46527A28C28000EBE7E /* equalizerTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = equalizerTemplate.pdf; path = Images/equalizerTemplate.pdf; sourceTree = ""; }; 8314D63B1A354DFE00EEE8E6 /* sidplay.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sidplay.xcodeproj; path = Plugins/sidplay/sidplay.xcodeproj; sourceTree = ""; }; 832923AE279FAC400048201E /* Cog.q1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Cog.q1.json; sourceTree = ""; }; @@ -1015,8 +1019,6 @@ 83E5E54A18087CA5001F3284 /* miniModeOffTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = miniModeOffTemplate.pdf; path = Images/miniModeOffTemplate.pdf; sourceTree = ""; }; 83E5E54B18087CA5001F3284 /* miniModeOnTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = miniModeOnTemplate.pdf; path = Images/miniModeOnTemplate.pdf; sourceTree = ""; }; 83E5EFAC1FFEF78100659F0F /* OpenMPT.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OpenMPT.xcodeproj; path = Plugins/OpenMPT/OpenMPT.xcodeproj; sourceTree = ""; }; - 83E88FD027945204003D6FE5 /* AUPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AUPlayerView.m; path = Window/AUPlayerView.m; sourceTree = ""; }; - 83E88FD127945204003D6FE5 /* AUPlayerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AUPlayerView.h; path = Window/AUPlayerView.h; sourceTree = ""; }; 83ED3AC7279A91C000904199 /* hdcdLogoTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = hdcdLogoTemplate.pdf; path = Images/hdcdLogoTemplate.pdf; sourceTree = ""; }; 83F9D7F11A884B44007ABEC2 /* SilenceDecoder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SilenceDecoder.xcodeproj; path = Plugins/SilenceDecoder/SilenceDecoder.xcodeproj; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; @@ -1084,6 +1086,7 @@ 8E75752A09F31D5A0080F1EE /* Playlist */, 8E07AAEA0AAC90DC00A4B32F /* Preferences */, 17DDF6400E0CB6F100A2E4AD /* FileTree */, + 830C37A227B95E6000E02BB0 /* Equalizer */, 17D1B0FE0F63252900694C57 /* InfoInspector */, 569C52C50D5F2BD500BDBDC9 /* Spotlight */, 1766C68D0B911DF1004A7AE4 /* AudioScrobbler */, @@ -1371,8 +1374,6 @@ 17E0D5D20F520E75005B6FED /* Window */ = { isa = PBXGroup; children = ( - 83E88FD127945204003D6FE5 /* AUPlayerView.h */, - 83E88FD027945204003D6FE5 /* AUPlayerView.m */, 83BC5AB120E4C87100631CD4 /* DualWindow.h */, 83BC5AB020E4C87100631CD4 /* DualWindow.m */, 17E0D5E10F520F02005B6FED /* MainWindow.h */, @@ -1483,6 +1484,7 @@ 1791005D0CB44D6D0070BC5C /* Cog.scriptTerminology */, 832923AE279FAC400048201E /* Cog.q1.json */, 835EDD8027A000E8001EDCCE /* gsx.wv */, + 830C379F27B95E3000E02BB0 /* Equalizer.xib */, 17D1B0D00F6320EA00694C57 /* InfoInspector.xib */, 17342A980D5FD20B00E8D854 /* MainMenu.xib */, 17342ABD0D5FD36400E8D854 /* OpenURLPanel.xib */, @@ -1620,6 +1622,15 @@ name = Products; sourceTree = ""; }; + 830C37A227B95E6000E02BB0 /* Equalizer */ = { + isa = PBXGroup; + children = ( + 830C37A327B95EB300E02BB0 /* EqualizerWindowController.h */, + 830C37A427B95EB300E02BB0 /* EqualizerWindowController.m */, + ); + name = Equalizer; + sourceTree = ""; + }; 8314D63C1A354DFE00EEE8E6 /* Products */ = { isa = PBXGroup; children = ( @@ -2393,6 +2404,7 @@ 171B57DD0C091F2B00F6AFAF /* flac.icns in Resources */, 171B57DE0C091F2B00F6AFAF /* m4a.icns in Resources */, 8384916C18083EAB00E7332D /* stopTemplate.pdf in Resources */, + 830C37A127B95E3000E02BB0 /* Equalizer.xib in Resources */, 171B57DF0C091F2B00F6AFAF /* mp3.icns in Resources */, 171B57E00C091F2B00F6AFAF /* ogg.icns in Resources */, 17818A950C0B27AC001C4916 /* aiff.icns in Resources */, @@ -2482,6 +2494,7 @@ 5604D45B0D60349B004F5C5D /* SpotlightWindowController.m in Sources */, 5604D4F60D60726E004F5C5D /* SpotlightPlaylistEntry.m in Sources */, 56462EAF0D6341F6000AB68C /* SpotlightTransformers.m in Sources */, + 830C37A527B95EB300E02BB0 /* EqualizerWindowController.m in Sources */, 56462EB20D634206000AB68C /* SpotlightPlaylistController.m in Sources */, 07E18DF30D62B38400BB0E11 /* NSArray+ShuffleUtils.m in Sources */, 56C63D910D647DF300EAE25A /* NSComparisonPredicate+CogPredicate.m in Sources */, @@ -2493,7 +2506,6 @@ 179D031F0E0CB2500064A77A /* ContainerNode.m in Sources */, 839DA7CF274A2D4C001B18E5 /* NSDictionary+Merge.m in Sources */, 179D03200E0CB2500064A77A /* DirectoryNode.m in Sources */, - 83E88FD227945204003D6FE5 /* AUPlayerView.m in Sources */, 179D03210E0CB2500064A77A /* FileIconCell.m in Sources */, 179D03220E0CB2500064A77A /* FileNode.m in Sources */, 179D03230E0CB2500064A77A /* FileTreeDataSource.m in Sources */, @@ -2768,6 +2780,14 @@ name = Credits.html; sourceTree = ""; }; + 830C379F27B95E3000E02BB0 /* Equalizer.xib */ = { + isa = PBXVariantGroup; + children = ( + 830C37A027B95E3000E02BB0 /* Base */, + ); + name = Equalizer.xib; + sourceTree = ""; + }; 8E7575D909F31E930080F1EE /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/Equalizer/EqualizerWindowController.h b/Equalizer/EqualizerWindowController.h new file mode 100644 index 000000000..2172f92ea --- /dev/null +++ b/Equalizer/EqualizerWindowController.h @@ -0,0 +1,79 @@ +// +// EqualizerWindowController.h +// Cog +// +// Created by Christopher Snowhill on 2/13/22. +// + +#import + +#import +#import +#import +#import + +void equalizerApplyGenre(AudioUnit _Nullable au, const NSString *_Nonnull genre); +void equalizerLoadPreset(AudioUnit _Nullable au); +void equalizerApplyPreset(AudioUnit _Nullable au, const NSDictionary *_Nonnull preset); + +NS_ASSUME_NONNULL_BEGIN + +@interface EqPresetBehaviorArrayController : NSArrayController + +@end + +@interface EqualizerSlider : NSSlider + +@end + +@interface EqualizerWindowController : NSWindowController { + IBOutlet NSPopUpButton *presetSelector; + IBOutlet EqualizerSlider *eqPreamp; + IBOutlet EqualizerSlider *eq20Hz; + IBOutlet EqualizerSlider *eq25Hz; + IBOutlet EqualizerSlider *eq31p5Hz; + IBOutlet EqualizerSlider *eq40Hz; + IBOutlet EqualizerSlider *eq50Hz; + IBOutlet EqualizerSlider *eq63Hz; + IBOutlet EqualizerSlider *eq80Hz; + IBOutlet EqualizerSlider *eq100Hz; + IBOutlet EqualizerSlider *eq125Hz; + IBOutlet EqualizerSlider *eq160Hz; + IBOutlet EqualizerSlider *eq200Hz; + IBOutlet EqualizerSlider *eq250Hz; + IBOutlet EqualizerSlider *eq315Hz; + IBOutlet EqualizerSlider *eq400Hz; + IBOutlet EqualizerSlider *eq500Hz; + IBOutlet EqualizerSlider *eq630Hz; + IBOutlet EqualizerSlider *eq800Hz; + IBOutlet EqualizerSlider *eq1kHz; + IBOutlet EqualizerSlider *eq1p2kHz; + IBOutlet EqualizerSlider *eq1p6kHz; + IBOutlet EqualizerSlider *eq2kHz; + IBOutlet EqualizerSlider *eq2p5kHz; + IBOutlet EqualizerSlider *eq3p1kHz; + IBOutlet EqualizerSlider *eq4kHz; + IBOutlet EqualizerSlider *eq5kHz; + IBOutlet EqualizerSlider *eq6p3kHz; + IBOutlet EqualizerSlider *eq8kHz; + IBOutlet EqualizerSlider *eq10kHz; + IBOutlet EqualizerSlider *eq12kHz; + IBOutlet EqualizerSlider *eq16kHz; + IBOutlet EqualizerSlider *eq20kHz; + AudioUnit au; +} + +- (void)setEQ:(AudioUnit _Nullable)au; + +- (IBAction)toggleWindow:(id)sender; + +- (IBAction)toggleEnable:(id)sender; +- (IBAction)toggleTracking:(id)sender; +- (IBAction)flattenEQ:(id)sender; +- (IBAction)levelPreamp:(id)sender; +- (IBAction)adjustSlider:(id)sender; +- (IBAction)changePreset:(id)sender; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Equalizer/EqualizerWindowController.m b/Equalizer/EqualizerWindowController.m new file mode 100644 index 000000000..aa1d1d249 --- /dev/null +++ b/Equalizer/EqualizerWindowController.m @@ -0,0 +1,505 @@ +// +// EqualizerWindowController.m +// Cog +// +// Created by Christopher Snowhill on 2/13/22. +// + +#import "EqualizerWindowController.h" + +#import "json.h" + +#import "Logging.h" + +static const NSString *equalizerGenre = @""; +static const NSString *equalizerDefaultGenre = @"Flat"; +static NSArray *equalizer_presets_processed = nil; +static NSDictionary *equalizer_presets_by_name = nil; +static json_value *equalizer_presets = NULL; + +static NSString *_cog_equalizer_type = @"Cog EQ library file v1.0"; + +static NSArray *_cog_equalizer_items() { + return @[@"name", @"hz32", @"hz64", @"hz128", @"hz256", @"hz512", @"hz1000", @"hz2000", @"hz4000", @"hz8000", @"hz16000", @"preamp"]; +} + +static NSArray *_cog_equalizer_band_settings() { + return @[@"eqPreamp", @"eq20Hz", @"eq25Hz", @"eq31p5Hz", @"eq40Hz", @"eq50Hz", @"eq63Hz", @"eq80Hz", @"eq100Hz", @"eq125Hz", @"eq160Hz", @"eq200Hz", @"eq250Hz", @"eq315Hz", @"eq400Hz", @"eq500Hz", @"eq630Hz", @"eq800Hz", @"eq1kHz", @"eq1p2kHz", @"eq1p6kHz", @"eq2kHz", @"eq2p5kHz", @"eq3p1kHz", @"eq4kHz", @"eq5kHz", @"eq6p3kHz", @"eq8kHz", @"eq10kHz", @"eq12kHz", @"eq16kHz", @"eq20kHz"]; +} + +static const float cog_equalizer_bands[10] = { 32, 64, 128, 256, 512, 1000, 2000, 4000, 8000, 16000 }; +static NSArray *cog_equalizer_items = nil; +static NSArray *cog_equalizer_band_settings = nil; +static NSString *cog_equalizer_extra_genres = @"altGenres"; + +static const float apple_equalizer_bands[31] = { 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1200, 1600, 2000, 2500, 3100, 4000, 5000, 6300, 8000, 10000, 12000, 16000, 20000 }; + +static inline float interpolatePoint(const NSDictionary *preset, float freqTarget) { + if(!cog_equalizer_items) + cog_equalizer_items = _cog_equalizer_items(); + + // predict extra bands! lpc was too broken, quadra was broken, let's try simple linear steps + if(freqTarget < cog_equalizer_bands[0]) { + float work[14]; + float work_freq[14]; + for(unsigned int i = 0; i < 10; ++i) { + work[9 - i] = [[preset objectForKey:[cog_equalizer_items objectAtIndex:1 + i]] floatValue]; + work_freq[9 - i] = cog_equalizer_bands[i]; + } + for(unsigned int i = 10; i < 14; ++i) { + work[i] = work[i - 1] + (work[i - 1] - work[i - 2]) * 1.05; + work_freq[i] = work_freq[i - 1] + (work_freq[i - 1] - work_freq[i - 2]) * 1.05; + } + for(unsigned int i = 0; i < 13; ++i) { + if(freqTarget >= work_freq[13 - i] && + freqTarget < work_freq[12 - i]) { + float freqLow = work_freq[13 - i]; + float freqHigh = work_freq[12 - i]; + float valueLow = work[13 - i]; + float valueHigh = work[12 - i]; + + float delta = (freqTarget - freqLow) / (freqHigh - freqLow); + + return valueLow + (valueHigh - valueLow) * delta; + } + } + + return work[13]; + } else if(freqTarget > cog_equalizer_bands[9]) { + float work[14]; + float work_freq[14]; + for(unsigned int i = 0; i < 10; ++i) { + work[i] = [[preset objectForKey:[cog_equalizer_items objectAtIndex:1 + i]] floatValue]; + work_freq[i] = cog_equalizer_bands[i]; + } + for(unsigned int i = 10; i < 14; ++i) { + work[i] = work[i - 1] + (work[i - 1] - work[i - 2]) * 1.05; + work_freq[i] = work_freq[i - 1] + (work_freq[i - 1] - work_freq[i - 2]) * 1.05; + } + for(unsigned int i = 0; i < 13; ++i) { + if(freqTarget >= work_freq[i] && + freqTarget < work_freq[i + 1]) { + float freqLow = work_freq[i]; + float freqHigh = work_freq[i + 1]; + float valueLow = work[i]; + float valueHigh = work[i + 1]; + + float delta = (freqTarget - freqLow) / (freqHigh - freqLow); + + return valueLow + (valueHigh - valueLow) * delta; + } + } + + return work[13]; + } + + // Pick the extremes + if(freqTarget == cog_equalizer_bands[0]) + return [[preset objectForKey:[cog_equalizer_items objectAtIndex:1]] floatValue]; + else if(freqTarget == cog_equalizer_bands[9]) + return [[preset objectForKey:[cog_equalizer_items objectAtIndex:10]] floatValue]; + + // interpolation time! linear is fine for this + + for(size_t i = 0; i < 9; ++i) { + if(freqTarget >= cog_equalizer_bands[i] && + freqTarget < cog_equalizer_bands[i + 1]) { + float freqLow = cog_equalizer_bands[i]; + float freqHigh = cog_equalizer_bands[i + 1]; + float valueLow = [[preset objectForKey:[cog_equalizer_items objectAtIndex:i + 1]] floatValue]; + float valueHigh = [[preset objectForKey:[cog_equalizer_items objectAtIndex:i + 2]] floatValue]; + + float delta = (freqTarget - freqLow) / (freqHigh - freqLow); + + return valueLow + (valueHigh - valueLow) * delta; + } + } + + return 0.0; +} + +static void interpolateBands(float *out, const NSDictionary *preset) { + for(size_t i = 0; i < 31; ++i) { + out[i] = interpolatePoint(preset, apple_equalizer_bands[i]); + } +} + +static float getPreamp(const NSDictionary *preset) { + return [[preset objectForKey:[cog_equalizer_items objectAtIndex:11]] floatValue]; +} + +static void loadPresets() { + if([equalizer_presets_processed count]) return; + + NSURL *url = [[NSBundle mainBundle] URLForResource:@"Cog.q1" withExtension:@"json"]; + + NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:[url path]]; + if(fileHandle) { + NSError *err; + NSData *data; + if(@available(macOS 10.15, *)) { + data = [fileHandle readDataToEndOfFileAndReturnError:&err]; + } else { + data = [fileHandle readDataToEndOfFile]; + err = nil; + } + if(!err && data) { + equalizer_presets = json_parse(data.bytes, data.length); + + if(equalizer_presets->type == json_object && + equalizer_presets->u.object.length == 2 && + strncmp(equalizer_presets->u.object.values[0].name, "type", equalizer_presets->u.object.values[0].name_length) == 0 && + equalizer_presets->u.object.values[0].value->type == json_string && + strncmp(equalizer_presets->u.object.values[0].value->u.string.ptr, [_cog_equalizer_type UTF8String], equalizer_presets -> u.object.values[0].value->u.string.length) == 0 && + strncmp(equalizer_presets->u.object.values[1].name, "presets", equalizer_presets->u.object.values[1].name_length) == 0 && + equalizer_presets->u.object.values[1].value->type == json_array) { + // Got the array of presets + NSMutableArray *array = [[NSMutableArray alloc] init]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + + size_t count = equalizer_presets->u.object.values[1].value->u.array.length; + json_value **values = equalizer_presets->u.object.values[1].value->u.array.values; + + cog_equalizer_items = _cog_equalizer_items(); + + const size_t cog_object_minimum = [cog_equalizer_items count]; + + for(size_t i = 0; i < count; ++i) { + if(values[i]->type == json_object) { + NSMutableArray *extraGenres = [[NSMutableArray alloc] init]; + size_t object_items = values[i]->u.object.length; + json_object_entry *object_entry = values[i]->u.object.values; + size_t requiredItemsPresent = 0; + if(object_items >= cog_object_minimum) { + NSMutableDictionary *equalizerItem = [[NSMutableDictionary alloc] init]; + for(size_t j = 0; j < object_items; ++j) { + NSString *key = [NSString stringWithUTF8String:object_entry[j].name]; + NSInteger index = [cog_equalizer_items indexOfObject:key]; + if(index != NSNotFound) { + if(index == 0 && object_entry[j].value->type == json_string) { + NSString *name = [NSString stringWithUTF8String:object_entry[j].value->u.string.ptr]; + [equalizerItem setObject:name forKey:key]; + ++requiredItemsPresent; + } else if(object_entry[j].value->type == json_integer) { + int64_t value = object_entry[j].value->u.integer; + float floatValue = ((value <= 401 && value >= 1) ? ((float)(value - 201) / 10.0) : 0.0); + [equalizerItem setObject:[NSNumber numberWithFloat:floatValue] forKey:key]; + ++requiredItemsPresent; + } + } else if([key isEqualToString:cog_equalizer_extra_genres]) { + // Process alternate genre matches + if(object_entry[j].value->type == json_array) { + size_t value_count = object_entry[j].value->u.array.length; + json_value **values = object_entry[j].value->u.array.values; + for(size_t k = 0; k < value_count; ++k) { + if(values[k]->type == json_string) { + [extraGenres addObject:[NSString stringWithUTF8String:values[i]->u.string.ptr]]; + } + } + } + } + } + + if(requiredItemsPresent == cog_object_minimum) { + // Add the base item + NSDictionary *outItem = [NSDictionary dictionaryWithDictionary:equalizerItem]; + [array addObject:outItem]; + [dict setObject:outItem forKey:[outItem objectForKey:@"name"]]; + + // Add the alternate genres, if any + for(NSString *genre in extraGenres) { + [dict setObject:outItem forKey:genre]; + } + } + } + } + } + + equalizer_presets_processed = [NSArray arrayWithArray:array]; + equalizer_presets_by_name = [NSDictionary dictionaryWithDictionary:dict]; + } + } + [fileHandle closeFile]; + + json_value_free(equalizer_presets); + equalizer_presets = NULL; + } +} + +void equalizerApplyGenre(AudioUnit au, const NSString *genre) { + equalizerGenre = genre; + if([[NSUserDefaults standardUserDefaults] boolForKey:@"GraphicEQtrackgenre"]) { + loadPresets(); + + NSDictionary *preset = [equalizer_presets_by_name objectForKey:genre]; + if(!preset) { + // Find a match + if(genre && ![genre isEqualToString:@""]) { + NSUInteger matchLength = 0; + NSString *lowerCaseGenre = [genre lowercaseString]; + for(NSString *key in [equalizer_presets_by_name allKeys]) { + NSString *lowerCaseKey = [key lowercaseString]; + if([lowerCaseGenre containsString:lowerCaseKey]) { + if([key length] > matchLength) { + matchLength = [key length]; + preset = [equalizer_presets_by_name objectForKey:key]; + } + } + } + } + + if(!preset) { + preset = [equalizer_presets_by_name objectForKey:equalizerDefaultGenre]; + } + } + if(preset) { + NSInteger index = [equalizer_presets_processed indexOfObject:preset]; + [[NSUserDefaults standardUserDefaults] setInteger:index forKey:@"GraphicEQpreset"]; + + equalizerApplyPreset(au, preset); + } + } +} + +void equalizerLoadPreset(AudioUnit au) { + NSInteger index = [[NSUserDefaults standardUserDefaults] integerForKey:@"GraphicEQpreset"]; + if(index >= 0 && index < [equalizer_presets_processed count]) { + NSDictionary *preset = [equalizer_presets_processed objectAtIndex:index]; + equalizerApplyPreset(au, preset); + } else if(au) { + float preamp = [[NSUserDefaults standardUserDefaults] floatForKey:[cog_equalizer_band_settings objectAtIndex:0]]; + + AudioUnitSetParameter(au, kGraphicEQParam_NumberOfBands, kAudioUnitScope_Global, 0, 1, 0); + for(NSInteger i = 1; i < [cog_equalizer_band_settings count]; ++i) { + float value = [[NSUserDefaults standardUserDefaults] floatForKey:[cog_equalizer_band_settings objectAtIndex:i]]; + AudioUnitSetParameter(au, (int)(i - 1), kAudioUnitScope_Global, 0, value + preamp, 0); + } + } +} + +void equalizerApplyPreset(AudioUnit au, const NSDictionary *preset) { + if(au && preset) { + AudioUnitParameterValue paramValue = 0; + if(AudioUnitGetParameter(au, kGraphicEQParam_NumberOfBands, kAudioUnitScope_Global, 0, ¶mValue)) + return; + + float presetValues[31]; + interpolateBands(presetValues, preset); + + float preamp = getPreamp(preset); + + AudioUnitSetParameter(au, kGraphicEQParam_NumberOfBands, kAudioUnitScope_Global, 0, 1, 0); + for(unsigned int i = 0; i < 31; ++i) { + AudioUnitSetParameter(au, i, kAudioUnitScope_Global, 0, presetValues[i] + preamp, 0); + } + } +} + +@implementation EqPresetBehaviorArrayController + +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + loadPresets(); + + for(NSDictionary *preset in equalizer_presets_processed) { + [self addObject:@{ @"name": [preset objectForKey:@"name"], @"preference": [preset objectForKey:@"name"] }]; + } + + [self addObject:@{ @"name": @"Custom", @"preference": @"Custom" }]; +} + +@end + +@implementation EqualizerSlider + +- (void)awakeFromNib { + if(@available(macOS 10.12.2, *)) { + [self setTrackFillColor:[NSColor systemGrayColor]]; + } +} + +@end + +@interface EqualizerWindowController () + +@end + +@implementation EqualizerWindowController + ++ (void)initialize { + cog_equalizer_band_settings = _cog_equalizer_band_settings(); +} + +- (id)init { + return [super initWithWindowNibName:@"Equalizer"]; +} + +- (void)windowDidLoad { + [super windowDidLoad]; + + [self changePreset:presetSelector]; +} + +- (void)setEQ:(AudioUnit)au { + self->au = au; +} + +- (IBAction)toggleWindow:(id)sender { + if([[self window] isVisible]) + [[self window] orderOut:self]; + else + [self showWindow:self]; +} + +- (IBAction)toggleEnable:(id)sender { +} + +- (IBAction)toggleTracking:(id)sender { + equalizerApplyGenre(au, equalizerGenre); + + [self changePreset:presetSelector]; +} + +- (IBAction)flattenEQ:(id)sender { + [presetSelector selectItemWithTitle:@"Flat"]; + + [self changePreset:presetSelector]; +} + +- (EqualizerSlider *)sliderForIndex:(NSInteger)index { + switch(index) { + case 0: + return eqPreamp; + case 1: + return eq20Hz; + case 2: + return eq25Hz; + case 3: + return eq31p5Hz; + case 4: + return eq40Hz; + case 5: + return eq50Hz; + case 6: + return eq63Hz; + case 7: + return eq80Hz; + case 8: + return eq100Hz; + case 9: + return eq125Hz; + case 10: + return eq160Hz; + case 11: + return eq200Hz; + case 12: + return eq250Hz; + case 13: + return eq315Hz; + case 14: + return eq400Hz; + case 15: + return eq500Hz; + case 16: + return eq630Hz; + case 17: + return eq800Hz; + case 18: + return eq1kHz; + case 19: + return eq1p2kHz; + case 20: + return eq1p6kHz; + case 21: + return eq2kHz; + case 22: + return eq2p5kHz; + case 23: + return eq3p1kHz; + case 24: + return eq4kHz; + case 25: + return eq5kHz; + case 26: + return eq6p3kHz; + case 27: + return eq8kHz; + case 28: + return eq10kHz; + case 29: + return eq12kHz; + case 30: + return eq16kHz; + case 31: + return eq20kHz; + default: + return nil; + } +} + +- (IBAction)levelPreamp:(id)sender { + float preamp = [eqPreamp floatValue]; + + float maxValue = 0.0; + for(NSInteger i = 1; i < [cog_equalizer_band_settings count]; ++i) { + float value = [[self sliderForIndex:i] floatValue]; + if(value > maxValue) maxValue = value; + } + + if(maxValue > 0.0 && preamp != -maxValue) { + [presetSelector selectItemAtIndex:[equalizer_presets_processed count]]; + [[NSUserDefaults standardUserDefaults] setInteger:[equalizer_presets_processed count] forKey:@"GraphicEQpreset"]; + + [eqPreamp setFloatValue:-maxValue]; + [[NSUserDefaults standardUserDefaults] setFloat:-maxValue forKey:[cog_equalizer_band_settings objectAtIndex:0]]; + + if(au) + equalizerLoadPreset(au); + } +} + +- (IBAction)adjustSlider:(id)sender { + NSInteger tag = [sender tag]; + + if(tag == 0) { + float preamp = [eqPreamp floatValue]; + [[NSUserDefaults standardUserDefaults] setFloat:preamp forKey:[cog_equalizer_band_settings objectAtIndex:0]]; + + equalizerLoadPreset(au); + } else if(tag < [cog_equalizer_band_settings count]) { + float preamp = [eqPreamp floatValue]; + + float value = [sender floatValue]; + [[NSUserDefaults standardUserDefaults] setFloat:value forKey:[cog_equalizer_band_settings objectAtIndex:tag]]; + if(au) + AudioUnitSetParameter(au, (int)(tag - 1), kAudioUnitScope_Global, 0, value + preamp, 0); + } +} + +- (void)changePreset:(id)sender { + NSInteger index = [sender indexOfSelectedItem]; + + if(index >= 0 && index < [equalizer_presets_processed count]) { + NSDictionary *preset = [equalizer_presets_processed objectAtIndex:index]; + + equalizerApplyPreset(au, preset); + + float preamp = getPreamp(preset); + float presetValues[31]; + interpolateBands(presetValues, preset); + + for(NSInteger i = 0; i < [cog_equalizer_band_settings count]; ++i) { + EqualizerSlider *slider = [self sliderForIndex:i]; + if(i == 0) { + [slider setFloatValue:preamp]; + [[NSUserDefaults standardUserDefaults] setFloat:preamp forKey:[cog_equalizer_band_settings objectAtIndex:i]]; + } else { + [slider setFloatValue:presetValues[i - 1]]; + [[NSUserDefaults standardUserDefaults] setFloat:presetValues[i - 1] forKey:[cog_equalizer_band_settings objectAtIndex:i]]; + } + } + } +} + +@end diff --git a/Window/AUPlayerView.h b/Window/AUPlayerView.h deleted file mode 100644 index 35f19169a..000000000 --- a/Window/AUPlayerView.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// AUPlayerView.h -// Output -// -// Created by Christopher Snowhill on 1/29/16. -// Copyright © 2016-2022 Christopher Snowhill. All rights reserved. -// - -#ifndef __AUPlayerView_h__ -#define __AUPlayerView_h__ - -#import -#import -#import -#import - -void equalizerApplyGenre(AudioUnit au, NSString *genre); -void equalizerLoadPreset(AudioUnit au); -void equalizerApplyPreset(AudioUnit au, NSDictionary *preset); - -@interface AUPluginUI : NSObject { - AudioUnit au; - - BOOL windowOpen; - - /* Cocoa */ - - NSWindow *cocoa_window; - NSView *au_view; - NSRect last_au_frame; -} - -- (id)initWithSampler:(AudioUnit)_au bringToFront:(BOOL)front orWindowNumber:(NSInteger)window; -- (void)dealloc; - -- (BOOL)isOpen; - -- (void)bringToFront; - -@end - -@interface AUPluginWindow : NSWindow { - AudioUnit au; - AUParameterListenerRef listenerRef; - - NSView *topView; - NSView *auView; - NSSplitView *splitView; - NSPopUpButton *presetButton; -} - -- (id)initWithAuView:(NSView *)_auView withAu:(AudioUnit)au bringToFront:(BOOL)front relativeToWindow:(NSInteger)window; -@end - -#endif diff --git a/Window/AUPlayerView.m b/Window/AUPlayerView.m deleted file mode 100644 index a76c16d25..000000000 --- a/Window/AUPlayerView.m +++ /dev/null @@ -1,711 +0,0 @@ -// -// AUPlayerView.m -// Output -// -// Created by Christopher Snowhill on 1/29/16. -// Copyright © 2016-2022 Christopher Snowhill. All rights reserved. -// - -#import -#import -#import - -#import "AUPlayerView.h" - -#import "json.h" - -#import "Logging.h" - -static NSString *equalizerGenre = @""; -static const NSString *equalizerDefaultGenre = @"Flat"; -static NSArray *equalizer_presets_processed = nil; -static NSDictionary *equalizer_presets_by_name = nil; -static json_value *equalizer_presets = NULL; - -static NSString *_cog_equalizer_type = @"Cog EQ library file v1.0"; - -static NSArray *_cog_equalizer_items() { - return @[@"name", @"hz32", @"hz64", @"hz128", @"hz256", @"hz512", @"hz1000", @"hz2000", @"hz4000", @"hz8000", @"hz16000", @"preamp"]; -} - -static const float cog_equalizer_bands[10] = { 32, 64, 128, 256, 512, 1000, 2000, 4000, 8000, 16000 }; -static NSArray *cog_equalizer_items = nil; -static NSString *cog_equalizer_extra_genres = @"altGenres"; - -static const float apple_equalizer_bands_31[31] = { 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1200, 1600, 2000, 2500, 3100, 4000, 5000, 6300, 8000, 10000, 12000, 16000, 20000 }; -static const float apple_equalizer_bands_10[10] = { 32, 64, 128, 256, 512, 1000, 2000, 4000, 8000, 16000 }; - -static inline float interpolatePoint(NSDictionary *preset, float freqTarget) { - if(!cog_equalizer_items) - cog_equalizer_items = _cog_equalizer_items(); - - // predict extra bands! lpc was too broken, quadra was broken, let's try simple linear steps - if(freqTarget < cog_equalizer_bands[0]) { - float work[14]; - float work_freq[14]; - for(unsigned int i = 0; i < 10; ++i) { - work[9 - i] = [[preset objectForKey:[cog_equalizer_items objectAtIndex:1 + i]] floatValue]; - work_freq[9 - i] = cog_equalizer_bands[i]; - } - for(unsigned int i = 10; i < 14; ++i) { - work[i] = work[i - 1] + (work[i - 1] - work[i - 2]) * 1.05; - work_freq[i] = work_freq[i - 1] + (work_freq[i - 1] - work_freq[i - 2]) * 1.05; - } - for(unsigned int i = 0; i < 13; ++i) { - if(freqTarget >= work_freq[13 - i] && - freqTarget < work_freq[12 - i]) { - float freqLow = work_freq[13 - i]; - float freqHigh = work_freq[12 - i]; - float valueLow = work[13 - i]; - float valueHigh = work[12 - i]; - - float delta = (freqTarget - freqLow) / (freqHigh - freqLow); - - return valueLow + (valueHigh - valueLow) * delta; - } - } - - return work[13]; - } else if(freqTarget > cog_equalizer_bands[9]) { - float work[14]; - float work_freq[14]; - for(unsigned int i = 0; i < 10; ++i) { - work[i] = [[preset objectForKey:[cog_equalizer_items objectAtIndex:1 + i]] floatValue]; - work_freq[i] = cog_equalizer_bands[i]; - } - for(unsigned int i = 10; i < 14; ++i) { - work[i] = work[i - 1] + (work[i - 1] - work[i - 2]) * 1.05; - work_freq[i] = work_freq[i - 1] + (work_freq[i - 1] - work_freq[i - 2]) * 1.05; - } - for(unsigned int i = 0; i < 13; ++i) { - if(freqTarget >= work_freq[i] && - freqTarget < work_freq[i + 1]) { - float freqLow = work_freq[i]; - float freqHigh = work_freq[i + 1]; - float valueLow = work[i]; - float valueHigh = work[i + 1]; - - float delta = (freqTarget - freqLow) / (freqHigh - freqLow); - - return valueLow + (valueHigh - valueLow) * delta; - } - } - - return work[13]; - } - - // Pick the extremes - if(freqTarget == cog_equalizer_bands[0]) - return [[preset objectForKey:[cog_equalizer_items objectAtIndex:1]] floatValue]; - else if(freqTarget == cog_equalizer_bands[9]) - return [[preset objectForKey:[cog_equalizer_items objectAtIndex:10]] floatValue]; - - // interpolation time! linear is fine for this - - for(size_t i = 0; i < 9; ++i) { - if(freqTarget >= cog_equalizer_bands[i] && - freqTarget < cog_equalizer_bands[i + 1]) { - float freqLow = cog_equalizer_bands[i]; - float freqHigh = cog_equalizer_bands[i + 1]; - float valueLow = [[preset objectForKey:[cog_equalizer_items objectAtIndex:i + 1]] floatValue]; - float valueHigh = [[preset objectForKey:[cog_equalizer_items objectAtIndex:i + 2]] floatValue]; - - float delta = (freqTarget - freqLow) / (freqHigh - freqLow); - - return valueLow + (valueHigh - valueLow) * delta; - } - } - - return 0.0; -} - -static void interpolateBandsTo10(float *out, NSDictionary *preset) { - for(size_t i = 0; i < 10; ++i) { - out[i] = interpolatePoint(preset, apple_equalizer_bands_10[i]); - } -} - -static void interpolateBandsTo31(float *out, NSDictionary *preset) { - for(size_t i = 0; i < 31; ++i) { - out[i] = interpolatePoint(preset, apple_equalizer_bands_31[i]); - } -} - -static void loadPresets() { - if([equalizer_presets_processed count]) return; - - NSURL *url = [[NSBundle mainBundle] URLForResource:@"Cog.q1" withExtension:@"json"]; - - NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:[url path]]; - if(fileHandle) { - NSError *err; - NSData *data; - if(@available(macOS 10.15, *)) { - data = [fileHandle readDataToEndOfFileAndReturnError:&err]; - } else { - data = [fileHandle readDataToEndOfFile]; - err = nil; - } - if(!err && data) { - equalizer_presets = json_parse(data.bytes, data.length); - - if(equalizer_presets->type == json_object && - equalizer_presets->u.object.length == 2 && - strncmp(equalizer_presets->u.object.values[0].name, "type", equalizer_presets->u.object.values[0].name_length) == 0 && - equalizer_presets->u.object.values[0].value->type == json_string && - strncmp(equalizer_presets->u.object.values[0].value->u.string.ptr, [_cog_equalizer_type UTF8String], equalizer_presets -> u.object.values[0].value->u.string.length) == 0 && - strncmp(equalizer_presets->u.object.values[1].name, "presets", equalizer_presets->u.object.values[1].name_length) == 0 && - equalizer_presets->u.object.values[1].value->type == json_array) { - // Got the array of presets - NSMutableArray *array = [[NSMutableArray alloc] init]; - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - - size_t count = equalizer_presets->u.object.values[1].value->u.array.length; - json_value **values = equalizer_presets->u.object.values[1].value->u.array.values; - - cog_equalizer_items = _cog_equalizer_items(); - - const size_t cog_object_minimum = [cog_equalizer_items count]; - - for(size_t i = 0; i < count; ++i) { - if(values[i]->type == json_object) { - NSMutableArray *extraGenres = [[NSMutableArray alloc] init]; - size_t object_items = values[i]->u.object.length; - json_object_entry *object_entry = values[i]->u.object.values; - size_t requiredItemsPresent = 0; - if(object_items >= cog_object_minimum) { - NSMutableDictionary *equalizerItem = [[NSMutableDictionary alloc] init]; - for(size_t j = 0; j < object_items; ++j) { - NSString *key = [NSString stringWithUTF8String:object_entry[j].name]; - NSInteger index = [cog_equalizer_items indexOfObject:key]; - if(index != NSNotFound) { - if(index == 0 && object_entry[j].value->type == json_string) { - NSString *name = [NSString stringWithUTF8String:object_entry[j].value->u.string.ptr]; - [equalizerItem setObject:name forKey:key]; - ++requiredItemsPresent; - } else if(object_entry[j].value->type == json_integer) { - int64_t value = object_entry[j].value->u.integer; - float floatValue = ((value <= 401 && value >= 1) ? ((float)(value - 201) / 10.0) : 0.0); - [equalizerItem setObject:[NSNumber numberWithFloat:floatValue] forKey:key]; - ++requiredItemsPresent; - } - } else if([key isEqualToString:cog_equalizer_extra_genres]) { - // Process alternate genre matches - if(object_entry[j].value->type == json_array) { - size_t value_count = object_entry[j].value->u.array.length; - json_value **values = object_entry[j].value->u.array.values; - for(size_t k = 0; k < value_count; ++k) { - if(values[k]->type == json_string) { - [extraGenres addObject:[NSString stringWithUTF8String:values[i]->u.string.ptr]]; - } - } - } - } - } - - if(requiredItemsPresent == cog_object_minimum) { - // Add the base item - NSDictionary *outItem = [NSDictionary dictionaryWithDictionary:equalizerItem]; - [array addObject:outItem]; - [dict setObject:outItem forKey:[outItem objectForKey:@"name"]]; - - // Add the alternate genres, if any - for(NSString *genre in extraGenres) { - [dict setObject:outItem forKey:genre]; - } - } - } - } - } - - equalizer_presets_processed = [NSArray arrayWithArray:array]; - equalizer_presets_by_name = [NSDictionary dictionaryWithDictionary:dict]; - } - } - [fileHandle closeFile]; - - json_value_free(equalizer_presets); - equalizer_presets = NULL; - } -} - -void equalizerApplyGenre(AudioUnit au, NSString *genre) { - equalizerGenre = genre; - if([[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"GraphicEQtrackgenre"]) { - loadPresets(); - - NSDictionary *preset = [equalizer_presets_by_name objectForKey:genre]; - if(!preset) { - // Find a match - if(genre && ![genre isEqualToString:@""]) { - NSUInteger matchLength = 0; - NSString *lowerCaseGenre = [genre lowercaseString]; - for(NSString *key in [equalizer_presets_by_name allKeys]) { - NSString *lowerCaseKey = [key lowercaseString]; - if([lowerCaseGenre containsString:lowerCaseKey]) { - if([key length] > matchLength) { - matchLength = [key length]; - preset = [equalizer_presets_by_name objectForKey:key]; - } - } - } - } - - if(!preset) { - preset = [equalizer_presets_by_name objectForKey:equalizerDefaultGenre]; - } - } - if(preset) { - NSInteger index = [equalizer_presets_processed indexOfObject:preset]; - [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setInteger:index forKey:@"GraphicEQpreset"]; - - equalizerApplyPreset(au, preset); - } - } -} - -void equalizerLoadPreset(AudioUnit au) { - NSInteger index = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] integerForKey:@"GraphicEQpreset"]; - if(index >= 0 && index < [equalizer_presets_processed count]) { - NSDictionary *preset = [equalizer_presets_processed objectAtIndex:index]; - equalizerApplyPreset(au, preset); - } -} - -void equalizerApplyPreset(AudioUnit au, NSDictionary *preset) { - if(au && preset) { - AudioUnitParameterValue paramValue = 0; - if(AudioUnitGetParameter(au, kGraphicEQParam_NumberOfBands, kAudioUnitScope_Global, 0, ¶mValue)) - return; - - size_t numberOfBands = paramValue ? 31 : 10; - - if(numberOfBands == 31) { - float presetValues[31]; - interpolateBandsTo31(presetValues, preset); - - for(unsigned int i = 0; i < 31; ++i) { - AudioUnitSetParameter(au, i, kAudioUnitScope_Global, 0, presetValues[i], 0); - } - } else if(numberOfBands == 10) { - float presetValues[10]; - interpolateBandsTo10(presetValues, preset); - - for(unsigned int i = 0; i < 10; ++i) { - AudioUnitSetParameter(au, i, kAudioUnitScope_Global, 0, presetValues[i], 0); - } - } - } -} - -@interface AUPluginUI (Private) -- (BOOL)test_cocoa_view_support; -- (int)create_cocoa_view; -- (BOOL)plugin_class_valid:(Class)pluginClass; -@end - -@implementation AUPluginUI -- (id)initWithSampler:(AudioUnit)_au bringToFront:(BOOL)front orWindowNumber:(NSInteger)window { - self = [super init]; - if(self) { - au = _au; - - windowOpen = NO; - - cocoa_window = nil; - au_view = nil; - - if([self test_cocoa_view_support]) { - [self create_cocoa_view]; - } - - if(au_view) { - cocoa_window = [[AUPluginWindow alloc] initWithAuView:au_view withAu:au bringToFront:front relativeToWindow:window]; - - if(cocoa_window) { - windowOpen = YES; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowClosed:) name:NSWindowWillCloseNotification object:cocoa_window]; - } - } - } - - return self; -} - -- (void)dealloc { - if(windowOpen) { - [self windowClosed:nil]; - } - [cocoa_window close]; - cocoa_window = nil; - au_view = nil; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (BOOL)isOpen { - return windowOpen; -} - -- (void)bringToFront { - [cocoa_window orderFront:cocoa_window]; -} - -- (void)windowClosed:(NSNotification *)notification { - [cocoa_window saveFrameUsingName:@"GraphicEQposition"]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - windowOpen = NO; -} - -- (BOOL)test_cocoa_view_support { - UInt32 dataSize = 0; - Boolean isWritable = 0; - OSStatus err = AudioUnitGetPropertyInfo(au, - kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, - 0, &dataSize, &isWritable); - - return dataSize > 0 && err == noErr; -} - -- (BOOL)plugin_class_valid:(Class)pluginClass { - if([pluginClass conformsToProtocol:@protocol(AUCocoaUIBase)]) { - if([pluginClass instancesRespondToSelector:@selector(interfaceVersion)] && - [pluginClass instancesRespondToSelector:@selector(uiViewForAudioUnit:withSize:)]) { - return true; - } - } - return false; -} - -- (int)create_cocoa_view { - bool wasAbleToLoadCustomView = false; - AudioUnitCocoaViewInfo *cocoaViewInfo = NULL; - UInt32 numberOfClasses = 0; - UInt32 dataSize; - Boolean isWritable; - NSString *factoryClassName = 0; - NSURL *CocoaViewBundlePath = NULL; - - OSStatus result = AudioUnitGetPropertyInfo(au, - kAudioUnitProperty_CocoaUI, - kAudioUnitScope_Global, - 0, - &dataSize, - &isWritable); - - numberOfClasses = (dataSize - sizeof(CFURLRef)) / sizeof(CFStringRef); - - // Does view have custom Cocoa UI? - - if((result == noErr) && (numberOfClasses > 0)) { - cocoaViewInfo = (AudioUnitCocoaViewInfo *)malloc(dataSize); - - if(AudioUnitGetProperty(au, - kAudioUnitProperty_CocoaUI, - kAudioUnitScope_Global, - 0, - cocoaViewInfo, - &dataSize) == noErr) { - CocoaViewBundlePath = (__bridge NSURL *)cocoaViewInfo->mCocoaAUViewBundleLocation; - - // we only take the first view in this example. - factoryClassName = (__bridge NSString *)cocoaViewInfo->mCocoaAUViewClass[0]; - } else { - if(cocoaViewInfo != NULL) { - free(cocoaViewInfo); - cocoaViewInfo = NULL; - } - } - } - - // [A] Show custom UI if view has it - - if(CocoaViewBundlePath && factoryClassName) { - NSBundle *viewBundle = [NSBundle bundleWithPath:[CocoaViewBundlePath path]]; - - if(viewBundle == NULL) { - return -1; - } else { - Class factoryClass = [viewBundle classNamed:factoryClassName]; - if(!factoryClass) { - return -1; - } - - // make sure 'factoryClass' implements the AUCocoaUIBase protocol - if(![self plugin_class_valid:factoryClass]) { - return -1; - } - // make a factory - id factory = [[factoryClass alloc] init]; - if(factory == NULL) { - return -1; - } - - // make a view - au_view = [factory uiViewForAudioUnit:au withSize:NSZeroSize]; - - // cleanup - if(cocoaViewInfo) { - UInt32 i; - for(i = 0; i < numberOfClasses; i++) - CFRelease(cocoaViewInfo->mCocoaAUViewClass[i]); - - free(cocoaViewInfo); - } - wasAbleToLoadCustomView = true; - } - } - - if(!wasAbleToLoadCustomView) { - // load generic Cocoa view - au_view = [[AUGenericView alloc] initWithAudioUnit:au]; - [(AUGenericView *)au_view setShowsExpertParameters:1]; - } - - return 0; -} - -@end - -@implementation AUPluginWindow -- (id)initWithAuView:(NSView *)_auView withAu:(AudioUnit)au bringToFront:(BOOL)front relativeToWindow:(NSInteger)window { - NSRect frame = [_auView frame]; - CGFloat req_width = frame.size.width; - CGFloat req_height = frame.size.height; - // BOOL resizable = [_auView autoresizingMask]; - - self = [super initWithContentRect:NSMakeRect(0, 0, req_width, req_height + 32) - styleMask:(NSWindowStyleMaskTitled | - NSWindowStyleMaskClosable) - backing:NSBackingStoreBuffered - defer:NO]; - if(self) { - self->au = au; - auView = _auView; - - [self setAutodisplay:YES]; - [self setOneShot:YES]; - - [self setReleasedWhenClosed:NO]; - - NSRect topRect = NSMakeRect(0, 0, req_width, 32); - - topView = [[NSView alloc] initWithFrame:topRect]; - - NSRect topFrame = NSMakeRect(0, req_height, req_width, topRect.size.height); - NSRect newFrame = NSMakeRect(0, 0, req_width, req_height); - - topRect = NSMakeRect(0, 0, req_width, req_height + topRect.size.height); - - splitView = [[NSSplitView alloc] initWithFrame:topRect]; - [splitView setDividerStyle:NSSplitViewDividerStyleThin]; - - [splitView setSubviews:@[topView, auView]]; - - [self setContentView:splitView]; - - [topView setFrame:topFrame]; - [auView setFrame:newFrame]; - - BOOL enabled = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"GraphicEQenable"]; - - NSButton *button = [NSButton checkboxWithTitle:@"Enabled" target:self action:@selector(toggleEnable:)]; - [button setState:enabled ? NSControlStateValueOn : NSControlStateValueOff]; - - NSRect buttonFrame = [button frame]; - buttonFrame.origin = NSMakePoint(18, 7); - [button setFrame:buttonFrame]; - - [topView addSubview:button]; - - enabled = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"GraphicEQtrackgenre"]; - NSButton *trackButton = [NSButton checkboxWithTitle:@"Tracking genre tags" target:self action:@selector(toggleTracking:)]; - [trackButton setState:enabled ? NSControlStateValueOn : NSControlStateValueOff]; - - NSRect trackButtonFrame = [trackButton frame]; - trackButtonFrame.origin = NSMakePoint(buttonFrame.origin.x + buttonFrame.size.width + 4, 7); - [trackButton setFrame:trackButtonFrame]; - - [topView addSubview:trackButton]; - - loadPresets(); - - NSRect popupFrame = NSMakeRect(req_width - 320, 0, 308, 26); - - presetButton = [[NSPopUpButton alloc] initWithFrame:popupFrame]; - [topView addSubview:presetButton]; - [presetButton setAction:@selector(changePreset:)]; - [presetButton setTarget:self]; - - NSMutableArray *array = [[NSMutableArray alloc] init]; - for(NSDictionary *preset in equalizer_presets_processed) { - [array addObject:[preset objectForKey:@"name"]]; - } - [array addObject:@"Custom"]; - [presetButton addItemsWithTitles:array]; - - NSInteger presetSelected = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] integerForKey:@"GraphicEQpreset"]; - if(presetSelected < 0 || presetSelected >= [equalizer_presets_processed count]) - presetSelected = [equalizer_presets_processed count]; - - [presetButton selectItemAtIndex:presetSelected]; - equalizerLoadPreset(au); - - NSTextField *presetLabel = [NSTextField labelWithString:@"Preset:"]; - - NSRect labelFrame = [presetLabel frame]; - labelFrame.origin = NSMakePoint(popupFrame.origin.x - labelFrame.size.width - 2, 7); - [presetLabel setFrame:labelFrame]; - [topView addSubview:presetLabel]; - - [splitView adjustSubviews]; - - [splitView setDelegate:self]; - - [self setFrameUsingName:@"GraphicEQposition"]; - - AUListenerCreateWithDispatchQueue(&listenerRef, 0.5, dispatch_get_main_queue(), ^void(void *inObject, const AudioUnitParameter *inParameter, AudioUnitParameterValue inValue) { - AUPluginWindow *_self = (__bridge AUPluginWindow *)inObject; - - if(inParameter->mParameterID >= 0 && inParameter->mParameterID <= 31) { - [_self savePresetToDefaults]; - if([_self->presetButton indexOfSelectedItem] != [equalizer_presets_processed count]) { - [_self->presetButton selectItemAtIndex:[equalizer_presets_processed count]]; - [_self changePreset:_self->presetButton]; - } - } else if(inParameter->mParameterID == kGraphicEQParam_NumberOfBands) { - [_self changePreset:self->presetButton]; - } - }); - - AudioUnitParameter param; - - param.mAudioUnit = au; - param.mElement = 0; - param.mScope = kAudioUnitScope_Global; - - for(unsigned int i = 0; i < 31; ++i) { - param.mParameterID = i; - AUListenerAddParameter(listenerRef, (__bridge void *)self, ¶m); - } - - param.mParameterID = kGraphicEQParam_NumberOfBands; - AUListenerAddParameter(listenerRef, (__bridge void *)self, ¶m); - - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQpreset" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:nil]; - -#if 0 // I have been told this makes the view look unbalanced, so whatevs - // Time to hack the auView - NSButton * flattenButton = nil; - NSPopUpButton * bandsButton = nil; - NSArray * views = [auView subviews]; - - for (NSView * view in views) { - if ([view isKindOfClass:[NSPopUpButton class]]) { - NSPopUpButton * popupButton = (NSPopUpButton *) view; - bandsButton = popupButton; - } - else if ([view isKindOfClass:[NSButton class]]) { - NSButton * button = (NSButton *) view; - flattenButton = button; - } - } - - [flattenButton setHidden:YES]; - - // Hacking done, showing window -#endif - - if(front) { - [self orderFront:self]; - } else - [self orderWindow:NSWindowBelow relativeTo:window]; - } - - return self; -} - -- (void)dealloc { - if(listenerRef) { - AUListenerDispose(listenerRef); - } - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.GraphicEQpreset" context:nil]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if([keyPath isEqualToString:@"values.GraphicEQpreset"]) { - NSInteger index = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] integerForKey:@"GraphicEQpreset"]; - - if(index < 0 || index > [equalizer_presets_processed count]) - index = [equalizer_presets_processed count]; - - NSInteger selectedIndex = [presetButton indexOfSelectedItem]; - - // Don't want to get into an observe/modify loop here - if(selectedIndex != index) { - [presetButton selectItemAtIndex:index]; - - [self changePreset:presetButton]; - } - } -} - -- (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex { - return NSZeroRect; -} - -- (void)toggleEnable:(id)sender { - BOOL enabled = [sender state] == NSControlStateValueOn; - - [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setBool:enabled forKey:@"GraphicEQenable"]; -} - -- (void)toggleTracking:(id)sender { - BOOL enabled = [sender state] == NSControlStateValueOn; - - [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setBool:enabled forKey:@"GraphicEQtrackgenre"]; - - equalizerApplyGenre(au, equalizerGenre); - - [self changePreset:presetButton]; -} - -- (void)changePreset:(id)sender { - NSInteger index = [sender indexOfSelectedItem]; - - // Prevent circular application - if(index == [[[NSUserDefaultsController sharedUserDefaultsController] defaults] integerForKey:@"GraphicEQpreset"]) - return; - - if(index < [equalizer_presets_processed count]) { - NSDictionary *preset = [equalizer_presets_processed objectAtIndex:index]; - - equalizerApplyPreset(au, preset); - - NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:[self windowNumber] context:nil eventNumber:0 clickCount:1 pressure:1.0]; - - [auView mouseDown:event]; - [auView mouseUp:event]; - - [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setInteger:index forKey:@"GraphicEQpreset"]; - } else { - [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setInteger:-1 forKey:@"GraphicEQpreset"]; - } -} - -- (void)savePresetToDefaults { - OSStatus err; - CFPropertyListRef classData; - UInt32 size; - - size = sizeof(classData); - err = AudioUnitGetProperty(au, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &classData, &size); - if(err == noErr) { - CFPreferencesSetAppValue(CFSTR("GraphEQ_Preset"), classData, kCFPreferencesCurrentApplication); - CFRelease(classData); - } - - CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication); -} - -@end