From 8db2e41049109ad0a7af957fb5b82642612740f2 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Wed, 15 Jun 2022 16:47:43 -0700 Subject: [PATCH] [Event Handling] Add context to all observers Add context field to all observers that support it, in case it's useful. Signed-off-by: Christopher Snowhill --- Audio/Chain/ConverterNode.mm | 18 +++++--- Audio/Chain/Downmix.m | 24 ++++++---- Audio/Chain/InputNode.m | 69 ++++++++++++++++------------ Audio/CogPluginMulti.m | 18 +++++--- Audio/Output/OutputCoreAudio.m | 19 +++++--- Audio/Plugin.h | 1 + FileTree/FileTreeDataSource.m | 18 +++++--- InfoInspector/InfoWindowController.m | 22 +++++---- Plugins/CueSheet/CueSheetDecoder.m | 36 +++++++++------ SpectrumViewLegacy.m | 2 + Visualization/SpectrumView.m | 2 + Window/VolumeSlider.m | 15 +++++- 12 files changed, 157 insertions(+), 87 deletions(-) diff --git a/Audio/Chain/ConverterNode.mm b/Audio/Chain/ConverterNode.mm index ef722f391..ac6d654f9 100644 --- a/Audio/Chain/ConverterNode.mm +++ b/Audio/Chain/ConverterNode.mm @@ -47,6 +47,8 @@ void PrintStreamDesc(AudioStreamBasicDescription *inDesc) { @implementation ConverterNode +static void *kConverterNodeContext = &kConverterNodeContext; + @synthesize inputFormat; - (id)initWithController:(id)c previous:(id)p { @@ -78,7 +80,7 @@ void PrintStreamDesc(AudioStreamBasicDescription *inDesc) { lastChunkIn = nil; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:kConverterNodeContext]; } return self; @@ -900,10 +902,14 @@ tryagain: ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - DLog(@"SOMETHING CHANGED!"); - if([keyPath isEqualToString:@"values.volumeScaling"]) { - // User reset the volume scaling option - [self refreshVolumeScaling]; + if(context == kConverterNodeContext) { + DLog(@"SOMETHING CHANGED!"); + if([keyPath isEqualToString:@"values.volumeScaling"]) { + // User reset the volume scaling option + [self refreshVolumeScaling]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } @@ -1056,7 +1062,7 @@ static float db_to_scale(float db) { - (void)dealloc { DLog(@"Decoder dealloc"); - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeScaling"]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeScaling" context:kConverterNodeContext]; paused = NO; [self cleanUp]; diff --git a/Audio/Chain/Downmix.m b/Audio/Chain/Downmix.m index 773dac994..edb1a07f7 100644 --- a/Audio/Chain/Downmix.m +++ b/Audio/Chain/Downmix.m @@ -250,6 +250,8 @@ static void upmix(const float *inBuffer, int inchannels, uint32_t inconfig, floa @implementation DownmixProcessor +static void *kDownmixProcessorContext = &kDownmixProcessorContext; + - (id)initWithInputFormat:(AudioStreamBasicDescription)inf inputConfig:(uint32_t)iConfig andOutputFormat:(AudioStreamBasicDescription)outf outputConfig:(uint32_t)oConfig { self = [super init]; @@ -276,16 +278,16 @@ static void upmix(const float *inBuffer, int inchannels, uint32_t inconfig, floa [self setupVirt]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:nil]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.headphoneVirtualization" options:0 context:kDownmixProcessorContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hrirPath" options:0 context:kDownmixProcessorContext]; } return self; } - (void)dealloc { - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization"]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath"]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.headphoneVirtualization" context:kDownmixProcessorContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.hrirPath" context:kDownmixProcessorContext]; } - (void)setupVirt { @@ -328,11 +330,15 @@ static void upmix(const float *inBuffer, int inchannels, uint32_t inconfig, floa ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - DLog(@"SOMETHING CHANGED!"); - if([keyPath isEqualToString:@"values.headphoneVirtualization"] || - [keyPath isEqualToString:@"values.hrirPath"]) { - // Reset the converter, without rebuffering - [self setupVirt]; + if(context == kDownmixProcessorContext) { + DLog(@"SOMETHING CHANGED!"); + if([keyPath isEqualToString:@"values.headphoneVirtualization"] || + [keyPath isEqualToString:@"values.hrirPath"]) { + // Reset the converter, without rebuffering + [self setupVirt]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } diff --git a/Audio/Chain/InputNode.m b/Audio/Chain/InputNode.m index f8f5705b2..a3d2a2993 100644 --- a/Audio/Chain/InputNode.m +++ b/Audio/Chain/InputNode.m @@ -18,6 +18,9 @@ #import "Logging.h" @implementation InputNode + +static void *kInputNodeContext = &kInputNodeContext; + @synthesize exitAtTheEndOfTheStream; - (id)initWithController:(id)c previous:(id)p { @@ -84,47 +87,53 @@ } - (void)registerObservers { - DLog(@"REGISTERING OBSERVERS"); - [decoder addObserver:self - forKeyPath:@"properties" - options:(NSKeyValueObservingOptionNew) - context:NULL]; + if(!observersAdded) { + DLog(@"REGISTERING OBSERVERS"); + [decoder addObserver:self + forKeyPath:@"properties" + options:(NSKeyValueObservingOptionNew) + context:kInputNodeContext]; - [decoder addObserver:self - forKeyPath:@"metadata" - options:(NSKeyValueObservingOptionNew) - context:NULL]; + [decoder addObserver:self + forKeyPath:@"metadata" + options:(NSKeyValueObservingOptionNew) + context:kInputNodeContext]; - observersAdded = YES; + observersAdded = YES; + } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - DLog(@"SOMETHING CHANGED!"); - if([keyPath isEqual:@"properties"]) { - DLog(@"Input format changed"); - // Converter may need resetting, it'll do that when it reaches the new chunks - NSDictionary *properties = [decoder properties]; + if(context == kInputNodeContext) { + DLog(@"SOMETHING CHANGED!"); + if([keyPath isEqual:@"properties"]) { + DLog(@"Input format changed"); + // Converter may need resetting, it'll do that when it reaches the new chunks + NSDictionary *properties = [decoder properties]; - int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue]; - int channels = [[properties objectForKey:@"channels"] intValue]; + int bitsPerSample = [[properties objectForKey:@"bitsPerSample"] intValue]; + int channels = [[properties objectForKey:@"channels"] intValue]; - bytesPerFrame = ((bitsPerSample + 7) / 8) * channels; + bytesPerFrame = ((bitsPerSample + 7) / 8) * channels; - nodeFormat = propertiesToASBD(properties); - nodeChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue]; - nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]; - } else if([keyPath isEqual:@"metadata"]) { - // Inform something of metadata change - NSDictionary *entryProperties = [decoder properties]; - if(entryProperties == nil) - return; + nodeFormat = propertiesToASBD(properties); + nodeChannelConfig = [[properties valueForKey:@"channelConfig"] unsignedIntValue]; + nodeLossless = [[properties valueForKey:@"encoding"] isEqualToString:@"lossless"]; + } else if([keyPath isEqual:@"metadata"]) { + // Inform something of metadata change + NSDictionary *entryProperties = [decoder properties]; + if(entryProperties == nil) + return; - NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:[decoder metadata]]; + NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:[decoder metadata]]; - [controller pushInfo:entryInfo]; + [controller pushInfo:entryInfo]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } @@ -251,8 +260,8 @@ - (void)removeObservers { if(observersAdded) { - [decoder removeObserver:self forKeyPath:@"properties"]; - [decoder removeObserver:self forKeyPath:@"metadata"]; + [decoder removeObserver:self forKeyPath:@"properties" context:kInputNodeContext]; + [decoder removeObserver:self forKeyPath:@"metadata" context:kInputNodeContext]; observersAdded = NO; } } diff --git a/Audio/CogPluginMulti.m b/Audio/CogPluginMulti.m index 422234422..879705223 100644 --- a/Audio/CogPluginMulti.m +++ b/Audio/CogPluginMulti.m @@ -42,6 +42,8 @@ NSArray *sortClassesByPriority(NSArray *theClasses) { @implementation CogDecoderMulti +static void *kCogDecoderMultiContext = &kCogDecoderMultiContext; + + (NSArray *)mimeTypes { return nil; } @@ -114,17 +116,17 @@ NSArray *sortClassesByPriority(NSArray *theClasses) { [theDecoder addObserver:self forKeyPath:@"properties" options:(NSKeyValueObservingOptionNew) - context:NULL]; + context:kCogDecoderMultiContext]; [theDecoder addObserver:self forKeyPath:@"metadata" options:(NSKeyValueObservingOptionNew) - context:NULL]; + context:kCogDecoderMultiContext]; } - (void)removeObservers { - [theDecoder removeObserver:self forKeyPath:@"properties"]; - [theDecoder removeObserver:self forKeyPath:@"metadata"]; + [theDecoder removeObserver:self forKeyPath:@"properties" context:kCogDecoderMultiContext]; + [theDecoder removeObserver:self forKeyPath:@"metadata" context:kCogDecoderMultiContext]; } - (BOOL)setTrack:(NSURL *)track { @@ -136,8 +138,12 @@ NSArray *sortClassesByPriority(NSArray *theClasses) { ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - [self willChangeValueForKey:keyPath]; - [self didChangeValueForKey:keyPath]; + if(context == kCogDecoderMultiContext) { + [self willChangeValueForKey:keyPath]; + [self didChangeValueForKey:keyPath]; + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } } @end diff --git a/Audio/Output/OutputCoreAudio.m b/Audio/Output/OutputCoreAudio.m index b82d9d812..362ee181b 100644 --- a/Audio/Output/OutputCoreAudio.m +++ b/Audio/Output/OutputCoreAudio.m @@ -23,6 +23,8 @@ static NSString *CogPlaybackDidBeginNotficiation = @"CogPlaybackDidBeginNotficia @implementation OutputCoreAudio +static void *kOutputCoreAudioContext = &kOutputCoreAudioContext; + static void fillBuffers(AudioBufferList *ioData, float *inbuffer, size_t count, size_t offset) { const size_t channels = ioData->mNumberBuffers; for(int i = 0; i < channels; ++i) { @@ -223,6 +225,11 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if(context != kOutputCoreAudioContext) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + if([keyPath isEqualToString:@"values.outputDevice"]) { NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"]; @@ -774,9 +781,9 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const visController = [VisualizationController sharedController]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:NULL]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQenable" options:0 context:NULL]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.eqPreamp" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:NULL]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:kOutputCoreAudioContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.GraphicEQenable" options:0 context:kOutputCoreAudioContext]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.eqPreamp" options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:kOutputCoreAudioContext]; observersapplied = YES; return (err == nil); @@ -805,10 +812,10 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const - (void)stop { stopInvoked = YES; if(observersapplied) { + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.outputDevice" context:kOutputCoreAudioContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.GraphicEQenable" context:kOutputCoreAudioContext]; + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.eqPreamp" context:kOutputCoreAudioContext]; observersapplied = NO; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.outputDevice"]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.GraphicEQenable"]; - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.eqPreamp"]; } if(stopNext && !paused) { if(!started) { diff --git a/Audio/Plugin.h b/Audio/Plugin.h index c29ac3a90..3c6119520 100644 --- a/Audio/Plugin.h +++ b/Audio/Plugin.h @@ -53,6 +53,7 @@ // These are in NSObject, so as long as you are a subclass of that, you are ok. - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; +- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context; - (BOOL)isSilence; @end diff --git a/FileTree/FileTreeDataSource.m b/FileTree/FileTreeDataSource.m index 4bffe9a91..816483dff 100644 --- a/FileTree/FileTreeDataSource.m +++ b/FileTree/FileTreeDataSource.m @@ -13,6 +13,8 @@ #import "Logging.h" +static void *kFileTreeDataSourceContext = &kFileTreeDataSourceContext; + static NSURL *defaultMusicDirectory(void) { return [[NSFileManager defaultManager] URLForDirectory:NSMusicDirectory inDomain:NSUserDomainMask @@ -44,18 +46,22 @@ static NSURL *defaultMusicDirectory(void) { forKeyPath:@"values.fileTreeRootURL" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial - context:nil]; + context:kFileTreeDataSourceContext]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if([keyPath isEqualToString:@"values.fileTreeRootURL"]) { - NSString *url = - [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"fileTreeRootURL"]; - DLog(@"File tree root URL: %@\n", url); - self.rootURL = [NSURL URLWithString:url]; + if(context == kFileTreeDataSourceContext) { + if([keyPath isEqualToString:@"values.fileTreeRootURL"]) { + NSString *url = + [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"fileTreeRootURL"]; + DLog(@"File tree root URL: %@\n", url); + self.rootURL = [NSURL URLWithString:url]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } diff --git a/InfoInspector/InfoWindowController.m b/InfoInspector/InfoWindowController.m index 97cc5079d..1a798b7c0 100644 --- a/InfoInspector/InfoWindowController.m +++ b/InfoInspector/InfoWindowController.m @@ -14,6 +14,8 @@ @implementation InfoWindowController +static void *kInfoWindowControllerContext = &kInfoWindowControllerContext; + @synthesize valueToDisplay; + (void)initialize { @@ -27,18 +29,22 @@ } - (void)awakeFromNib { - [playlistSelectionController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:nil]; - [currentEntryController addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew context:nil]; - [appController addObserver:self forKeyPath:@"miniMode" options:NSKeyValueObservingOptionNew context:nil]; + [playlistSelectionController addObserver:self forKeyPath:@"selection" options:NSKeyValueObservingOptionNew context:kInfoWindowControllerContext]; + [currentEntryController addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew context:kInfoWindowControllerContext]; + [appController addObserver:self forKeyPath:@"miniMode" options:NSKeyValueObservingOptionNew context:kInfoWindowControllerContext]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - // Avoid "selection" because it creates a proxy that's hard to reason with when we don't need to write. - PlaylistEntry *currentSelection = [[playlistSelectionController selectedObjects] firstObject]; - if(currentSelection != NULL) { - [self setValueToDisplay:currentSelection]; + if(context == kInfoWindowControllerContext) { + // Avoid "selection" because it creates a proxy that's hard to reason with when we don't need to write. + PlaylistEntry *currentSelection = [[playlistSelectionController selectedObjects] firstObject]; + if(currentSelection != NULL) { + [self setValueToDisplay:currentSelection]; + } else { + [self setValueToDisplay:[currentEntryController content]]; + } } else { - [self setValueToDisplay:[currentEntryController content]]; + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } diff --git a/Plugins/CueSheet/CueSheetDecoder.m b/Plugins/CueSheet/CueSheetDecoder.m index 2d1aa005d..69153ae55 100644 --- a/Plugins/CueSheet/CueSheetDecoder.m +++ b/Plugins/CueSheet/CueSheetDecoder.m @@ -18,6 +18,8 @@ @implementation CueSheetDecoder +static void *kCueSheetDecoderContext = &kCueSheetDecoderContext; + + (NSArray *)fileTypes { return [CueSheetContainer fileTypes]; } @@ -199,24 +201,26 @@ } - (void)registerObservers { - DLog(@"REGISTERING OBSERVERS"); - [decoder addObserver:self - forKeyPath:@"properties" - options:(NSKeyValueObservingOptionNew) - context:NULL]; + if(!observersAdded) { + DLog(@"REGISTERING OBSERVERS"); + [decoder addObserver:self + forKeyPath:@"properties" + options:(NSKeyValueObservingOptionNew) + context:kCueSheetDecoderContext]; - [decoder addObserver:self - forKeyPath:@"metadata" - options:(NSKeyValueObservingOptionNew) - context:NULL]; + [decoder addObserver:self + forKeyPath:@"metadata" + options:(NSKeyValueObservingOptionNew) + context:kCueSheetDecoderContext]; - observersAdded = YES; + observersAdded = YES; + } } - (void)removeObservers { if(observersAdded) { - [decoder removeObserver:self forKeyPath:@"properties"]; - [decoder removeObserver:self forKeyPath:@"metadata"]; + [decoder removeObserver:self forKeyPath:@"properties" context:kCueSheetDecoderContext]; + [decoder removeObserver:self forKeyPath:@"metadata" context:kCueSheetDecoderContext]; observersAdded = NO; } } @@ -225,8 +229,12 @@ ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - [self willChangeValueForKey:keyPath]; - [self didChangeValueForKey:keyPath]; + if(context == kCueSheetDecoderContext) { + [self willChangeValueForKey:keyPath]; + [self didChangeValueForKey:keyPath]; + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } } - (void)close { diff --git a/SpectrumViewLegacy.m b/SpectrumViewLegacy.m index 7efc9d3b6..43e4849c8 100644 --- a/SpectrumViewLegacy.m +++ b/SpectrumViewLegacy.m @@ -109,6 +109,8 @@ extern NSString *CogPlaybackDidStopNotficiation; } else { [self colorsDidChange:nil]; } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } diff --git a/Visualization/SpectrumView.m b/Visualization/SpectrumView.m index 286438cb4..e6eeb8915 100644 --- a/Visualization/SpectrumView.m +++ b/Visualization/SpectrumView.m @@ -85,6 +85,8 @@ extern NSString *CogPlaybackDidStopNotficiation; } else { [self updateControls]; } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } diff --git a/Window/VolumeSlider.m b/Window/VolumeSlider.m index b2ac2a9f4..3c815d0bb 100644 --- a/Window/VolumeSlider.m +++ b/Window/VolumeSlider.m @@ -10,9 +10,12 @@ #import "CogAudio/Helper.h" #import "PlaybackController.h" +static void *kVolumeSliderContext = &kVolumeSliderContext; + @implementation VolumeSlider { NSTimer *currentTimer; BOOL wasInsideSnapRange; + BOOL observersadded; } - (id)initWithFrame:(NSRect)frame { @@ -46,11 +49,14 @@ popover.animates = NO; [popover setContentSize:textView.bounds.size]; - [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeLimit" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeLimit" options:0 context:kVolumeSliderContext]; + observersadded = YES; } - (void)dealloc { - [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeLimit"]; + if(observersadded) { + [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.volumeLimit" context:kVolumeSliderContext]; + } } - (void)updateToolTip { @@ -116,6 +122,11 @@ } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if(context != kVolumeSliderContext) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + if([keyPath isEqualToString:@"values.volumeLimit"]) { BOOL volumeLimit = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"volumeLimit"]; const double new_MAX_VOLUME = (volumeLimit) ? 100.0 : 800.0;