From 6638e9d0e3c581e73b77a9a37466e707677715bb Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 19 Feb 2015 10:51:52 -0700 Subject: [PATCH 001/284] MIKMIDIEndpointSynthesizer is now a subclass of the new MIKMIDISynthesizer. Renamed MIKMIDIEndpointSynthesizerInstrument to MIKMIDISynthesizerInstrument and factored it into its own files. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 25 ++ Source/MIKMIDIEndpointSynthesizer.h | 111 +------- Source/MIKMIDIEndpointSynthesizer.m | 285 +------------------- Source/MIKMIDIMetronome.m | 3 +- Source/MIKMIDISynthesizer.h | 107 ++++++++ Source/MIKMIDISynthesizer.m | 198 ++++++++++++++ Source/MIKMIDISynthesizerInstrument.h | 47 ++++ Source/MIKMIDISynthesizerInstrument.m | 117 ++++++++ 8 files changed, 506 insertions(+), 387 deletions(-) create mode 100644 Source/MIKMIDISynthesizer.h create mode 100644 Source/MIKMIDISynthesizer.m create mode 100644 Source/MIKMIDISynthesizerInstrument.h create mode 100644 Source/MIKMIDISynthesizerInstrument.m diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 6ab4ccbb..8137871b 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -216,6 +216,14 @@ 9DAF8B8F1A7B04CA00F46528 /* MIKMIDI-iOS-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9DAF8B8E1A7B04CA00F46528 /* MIKMIDI-iOS-Info.plist */; }; 9DAF8B941A7B050300F46528 /* MIKMIDIMappingXMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DAF8B911A7B04F700F46528 /* MIKMIDIMappingXMLParser.m */; }; 9DAF8B951A7B050700F46528 /* MIKMIDIMappingXMLParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DAF8B901A7B04F700F46528 /* MIKMIDIMappingXMLParser.h */; }; + 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB366EE1A964C55001D1CF3 /* MIKMIDISynthesizer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB366EE1A964C55001D1CF3 /* MIKMIDISynthesizer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DB366F21A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */; }; + 9DB366F31A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */; }; + 9DB366F61A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DB366F71A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DB366F81A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */; }; + 9DB366F91A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; @@ -345,6 +353,10 @@ 9DAF8B8E1A7B04CA00F46528 /* MIKMIDI-iOS-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "MIKMIDI-iOS-Info.plist"; sourceTree = SOURCE_ROOT; }; 9DAF8B901A7B04F700F46528 /* MIKMIDIMappingXMLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingXMLParser.h; sourceTree = ""; }; 9DAF8B911A7B04F700F46528 /* MIKMIDIMappingXMLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingXMLParser.m; sourceTree = ""; }; + 9DB366EE1A964C55001D1CF3 /* MIKMIDISynthesizer.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizer.h; sourceTree = ""; tabWidth = 4; usesTabs = 1; wrapsLines = 1; }; + 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizer.m; sourceTree = ""; tabWidth = 4; usesTabs = 1; wrapsLines = 1; }; + 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizerInstrument.h; sourceTree = ""; }; + 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizerInstrument.m; sourceTree = ""; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 9DF99E771831841A004EE5F4 /* MIKMIDICommandThrottler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommandThrottler.h; sourceTree = ""; }; @@ -518,6 +530,10 @@ 9DAF8AFF1A7AFAB800F46528 /* Synthesis */ = { isa = PBXGroup; children = ( + 9DB366EE1A964C55001D1CF3 /* MIKMIDISynthesizer.h */, + 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */, + 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */, + 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */, 9DAE7D8C19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h */, 9DAE7D8B19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.m */, 83BC19B91A23CD0D004F384F /* MIKMIDIMetronome.h */, @@ -659,6 +675,7 @@ 839D936D19C3A30B007589C3 /* MIKMIDISequence.h in Headers */, 839D937319C3A319007589C3 /* MIKMIDITempoEvent.h in Headers */, 839D936119C3A2F5007589C3 /* MIKMIDIMetaTimeSignatureEvent.h in Headers */, + 9DB366F61A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */, 839D935B19C3A2F5007589C3 /* MIKMIDIMetaMarkerTextEvent.h in Headers */, 9D877DFD1A6706E6001BA997 /* MIKMIDIProgramChangeCommand.h in Headers */, 839D936319C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, @@ -677,6 +694,7 @@ 9D74EF8B17A713A100BEE89F /* MIKMIDIResponder.h in Headers */, 9D74EF8C17A713A100BEE89F /* MIKMIDISourceEndpoint.h in Headers */, 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, + 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D74EF8E17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.h in Headers */, 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */, @@ -717,6 +735,7 @@ 9DAF8B611A7B008A00F46528 /* MIKMIDIChannelVoiceCommand.h in Headers */, 9DAF8B701A7B00A700F46528 /* MIKMIDIMetaCuePointEvent.h in Headers */, 9DAF8B671A7B008A00F46528 /* MIKMIDIProgramChangeCommand.h in Headers */, + 9DB366F71A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */, 9DAF8B691A7B009100F46528 /* MIKMIDIMappingGenerator.h in Headers */, 9DAF8B641A7B008A00F46528 /* MIKMIDINoteOffCommand.h in Headers */, 9DAF8B581A7B007300F46528 /* MIKMIDIPort.h in Headers */, @@ -737,6 +756,7 @@ 9DAF8B531A7B005C00F46528 /* MIKMIDIErrors.h in Headers */, 9DAF8B7F1A7B00B100F46528 /* MIKMIDISequencer.h in Headers */, 9DAF8B6E1A7B00A700F46528 /* MIKMIDIEventIterator.h in Headers */, + 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 9DAF8B571A7B007300F46528 /* MIKMIDIEntity.h in Headers */, 9DAF8B621A7B008A00F46528 /* MIKMIDIControlChangeCommand.h in Headers */, 9DAF8B821A7B00BB00F46528 /* MIKMIDIClock.h in Headers */, @@ -886,9 +906,11 @@ 9D74EF8917A713A100BEE89F /* MIKMIDIPort.m in Sources */, 839D933619C3A2C9007589C3 /* MIKMIDIEventIterator.m in Sources */, 839D935A19C3A2F5007589C3 /* MIKMIDIMetaLyricEvent.m in Sources */, + 9DB366F81A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */, 9D74EF8D17A713A100BEE89F /* MIKMIDISourceEndpoint.m in Sources */, 839D936E19C3A30B007589C3 /* MIKMIDISequence.m in Sources */, 839D933419C3A2C9007589C3 /* MIKMIDIEvent.m in Sources */, + 9DB366F21A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */, 9D74EF8F17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.m in Sources */, 9D74EF9117A713A100BEE89F /* MIKMIDISystemMessageCommand.m in Sources */, 839D935419C3A2F5007589C3 /* MIKMIDIMetaEvent.m in Sources */, @@ -903,6 +925,7 @@ files = ( 9DAF8B1F1A7AFF5900F46528 /* MIKMIDIDeviceManager.m in Sources */, 9DAF8B201A7AFF5900F46528 /* MIKMIDIObject.m in Sources */, + 9DB366F91A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */, 9DAF8B211A7AFF5900F46528 /* MIKMIDIDevice.m in Sources */, 9DAF8B221A7AFF5900F46528 /* MIKMIDIEntity.m in Sources */, 9DAF8B231A7AFF5900F46528 /* MIKMIDIPort.m in Sources */, @@ -948,6 +971,7 @@ 9DAF8B4A1A7AFF7500F46528 /* MIKMIDIEndpointSynthesizer.m in Sources */, 9DAF8B4B1A7AFF7500F46528 /* MIKMIDIMetronome.m in Sources */, 9DAF8B4C1A7AFF7500F46528 /* MIKMIDISequencer.m in Sources */, + 9DB366F31A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */, 9DAF8B4D1A7AFF7500F46528 /* MIKMIDIUtilities.m in Sources */, 9DAF8B4E1A7AFF7500F46528 /* MIKMIDICommandThrottler.m in Sources */, 9DAF8B4F1A7AFF7500F46528 /* MIKMIDIClock.m in Sources */, @@ -1177,6 +1201,7 @@ 9DAF8B1A1A7AFF1200F46528 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Source/MIKMIDIEndpointSynthesizer.h b/Source/MIKMIDIEndpointSynthesizer.h index 36e6fd7c..31642b7b 100644 --- a/Source/MIKMIDIEndpointSynthesizer.h +++ b/Source/MIKMIDIEndpointSynthesizer.h @@ -6,13 +6,12 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // -#import -#import +#import "MIKMIDISynthesizer.h" @class MIKMIDIEndpoint; @class MIKMIDISourceEndpoint; @class MIKMIDIClientDestinationEndpoint; -@class MIKMIDIEndpointSynthesizerInstrument; +@class MIKMIDISynthesizerInstrument; /** * MIKMIDIEndpointSynthesizer provides a very simple way to synthesize MIDI commands coming from a @@ -21,7 +20,7 @@ * To use it, simply create a synthesizer instance with the source you'd like it to play. It will * continue playing incoming MIDI until it is deallocated. */ -@interface MIKMIDIEndpointSynthesizer : NSObject +@interface MIKMIDIEndpointSynthesizer : MIKMIDISynthesizer /** * Creates and initializes an MIKMIDIEndpointSynthesizer instance using Apple's DLS synth as the @@ -111,115 +110,15 @@ */ - (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination componentDescription:(AudioComponentDescription)componentDescription; -/** - * Changes the instrument/voice used by the synthesizer. - * - * @param instrument An MIKMIDIEndpointSynthesizerInstrument instance. - * - * @return YES if the instrument was successfully changed, NO if the change failed. - * - * @see +[MIKMIDIEndpointSynthesizerInstrument availableInstruments] - */ -- (BOOL)selectInstrument:(MIKMIDIEndpointSynthesizerInstrument *)instrument; - -/** - * Plays MIDI messages through the synthesizer. - * - * This method can be used to synthesize arbitrary MIDI events. It is especially - * useful for MIKMIDIEndpointSynthesizers that are not connected to a MIDI - * endpoint. - * - * @param messages An NSArray of MIKMIDICommand (subclass) instances. - */ -- (void)handleMIDIMessages:(NSArray *)messages; +// Properties /** * The endpoint from which the receiver is receiving MIDI messages. * This may be either an external MIKMIDISourceEndpoint, e.g. to synthesize MIDI * events coming from an external MIDI keyboard, or it may be an MIKMIDIClientDestinationEndpoint, - * most commonly to synthesize MIDI coming from an MIKMIDIPlayer. - * + * e.g. to synthesize MIDI events coming from an another application on the system. */ @property (nonatomic, strong, readonly) MIKMIDIEndpoint *endpoint; -/** - * The component description of the underlying Audio Unit instrument. - */ -@property (nonatomic, readonly) AudioComponentDescription componentDescription; - -/** - * The Audio Unit instrument that ultimately receives all of the MIDI messages sent to - * this endpoint synthesizer. - * - * @note You should only use the setter for this property from an - * MIKMIDIEndpointSynthesizer subclass. - * - * @see -setupAUGraph - */ -@property (nonatomic) AudioUnit instrument; - - -/** - * The AUGraph for the instrument. - * - * @note You should only use the setter for this property from an - * MIKMIDIEndpointSynthesizer subclass. - * - * @see -setupAUGraph - */ -@property (nonatomic) AUGraph graph; - -/** - * Sets up the AUGraph for the instrument. Do not call this method, as it is - * called automatically during initialization. - * - * The method is provided to give subclasses a chance to override - * the AUGraph behavior for the instrument. If you do override it, you will need - * to create an AudioUnit instrument and set it to the instrument property. Also, - * if you intend to use the graph property, you will be responsible for setting - * that as well. DisposeAUGraph() is called on the previous graph when setting - * the graph property, and in dealloc. - * - * @return YES is setting up the graph was succesful, and initialization - * should continue, NO if setting up the graph failed and initialization should - * return nil. - */ -- (BOOL)setupAUGraph; - @end -#pragma mark - - -/** - * MIKMIDIEndpointSynthesizerInstrument is used to represent - */ -@interface MIKMIDIEndpointSynthesizerInstrument : NSObject - -/** - * An array of available MIKMIDIEndpointSynthesizerInstruments for use - * with MIKMIDIEndpointSynthesizer. - * - * @return An NSArray containing MIKMIDIEndpointSynthesizerInstrument instances. - */ -+ (NSArray *)availableInstruments; - -/** - * Creates and initializes an MIKMIDIEndpointSynthesizerInstrument with the corresponding instrument ID. - * - * @param instrumentID The MusicDeviceInstrumentID for the desired MIKMIDIEndpointSynthesizerInstrument - * - * @return A MIKMIDIEndpointSynthesizerInstrument with the matching instrument ID, or nil if no instrument was found. - */ -+ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID; - -/** - * The human readable name of the receiver. e.g. "Piano 1". - */ -@property (readonly, copy, nonatomic) NSString *name; - -/** - * The Core Audio supplied instrumentID for the receiver. - */ -@property (readonly, nonatomic) MusicDeviceInstrumentID instrumentID; - -@end diff --git a/Source/MIKMIDIEndpointSynthesizer.m b/Source/MIKMIDIEndpointSynthesizer.m index c104ea7b..64b7e49c 100644 --- a/Source/MIKMIDIEndpointSynthesizer.m +++ b/Source/MIKMIDIEndpointSynthesizer.m @@ -22,19 +22,14 @@ @interface MIKMIDIEndpointSynthesizer () @implementation MIKMIDIEndpointSynthesizer -- (instancetype)init -{ - return [self initWithMIDISource:nil]; -} - + (instancetype)playerWithMIDISource:(MIKMIDISourceEndpoint *)source { - return [self playerWithMIDISource:source componentDescription:[self appleSynthComponentDescription]]; + return [[self alloc] initWithMIDISource:source]; } + (instancetype)playerWithMIDISource:(MIKMIDISourceEndpoint *)source componentDescription:(AudioComponentDescription)componentDescription { - return [[self alloc] initWithMIDISource:source]; + return [[self alloc] initWithMIDISource:source componentDescription:componentDescription]; } - (instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source @@ -44,7 +39,7 @@ - (instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source - (instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source componentDescription:(AudioComponentDescription)componentDescription; { - self = [super init]; + self = [super initWithAudioUnitDescription:componentDescription]; if (self) { if (source) { NSError *error = nil; @@ -53,10 +48,7 @@ - (instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source componentDesc return nil; } _endpoint = source; - _componentDescription = componentDescription; } - _componentDescription = componentDescription; - if (![self setupAUGraph]) return nil; } return self; } @@ -83,7 +75,7 @@ - (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpo return nil; } - self = [super init]; + self = [super initWithAudioUnitDescription:componentDescription]; if (self) { __weak MIKMIDIEndpointSynthesizer *weakSelf = self; @@ -92,23 +84,18 @@ - (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpo [strongSelf handleMIDIMessages:commands]; }; _endpoint = destination; - _componentDescription = componentDescription; - - if (![self setupAUGraph]) return nil; } return self; } - (void)dealloc { - if (self.endpoint) { - if ([self.endpoint isKindOfClass:[MIKMIDISourceEndpoint class]]) { + if (_endpoint) { + if ([_endpoint isKindOfClass:[MIKMIDISourceEndpoint class]]) { [[MIKMIDIDeviceManager sharedDeviceManager] disconnectInput:(MIKMIDISourceEndpoint *)self.endpoint forConnectionToken:self.connectionToken]; } // Don't need to do anything for a destination endpoint. __weak reference in the messages handler will automatically nil out. } - - self.graph = NULL; } #pragma mark - Private @@ -132,274 +119,14 @@ - (BOOL)connectToMIDISource:(MIKMIDISourceEndpoint *)source error:(NSError **)er return YES; } -- (void)handleMIDIMessages:(NSArray *)commands -{ - for (MIKMIDICommand *command in commands) { - OSStatus err = MusicDeviceMIDIEvent(self.instrument, command.commandType, command.dataByte1, command.dataByte2, 0); - if (err) NSLog(@"Unable to send MIDI command to synthesizer %@: %i", command, err); - } -} -#pragma mark Audio Graph -- (BOOL)setupAUGraph -{ - AUGraph graph; - OSStatus err = 0; - if ((err = NewAUGraph(&graph))) { - NSLog(@"Unable to create AU graph: %i", err); - return NO; - } - - AudioComponentDescription outputcd = {0}; - outputcd.componentType = kAudioUnitType_Output; -#if TARGET_OS_IPHONE - outputcd.componentSubType = kAudioUnitSubType_RemoteIO; -#else - outputcd.componentSubType = kAudioUnitSubType_DefaultOutput; -#endif - - outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; - - AUNode outputNode; - if ((err = AUGraphAddNode(graph, &outputcd, &outputNode))) { - NSLog(@"Unable to add ouptput node to graph: %i", err); - return NO; - } - - AudioComponentDescription instrumentcd = self.componentDescription; - - AUNode instrumentNode; - if ((err = AUGraphAddNode(graph, &instrumentcd, &instrumentNode))) { - NSLog(@"Unable to add instrument node to AU graph: %i", err); - return NO; - } - - if ((err = AUGraphOpen(graph))) { - NSLog(@"Unable to open AU graph: %i", err); - return NO; - } - - AudioUnit instrumentUnit; - if ((err = AUGraphNodeInfo(graph, instrumentNode, NULL, &instrumentUnit))) { - NSLog(@"Unable to get instrument AU unit: %i", err); - return NO; - } - - if ((err = AUGraphConnectNodeInput(graph, instrumentNode, 0, outputNode, 0))) { - NSLog(@"Unable to connect instrument to output: %i", err); - return NO; - } - - if ((err = AUGraphInitialize(graph))) { - NSLog(@"Unable to initialize AU graph: %i", err); - return NO; - } - -#if !TARGET_OS_IPHONE - // Turn down reverb which is way too high by default - if (instrumentcd.componentSubType == kAudioUnitSubType_DLSSynth) { - if ((err = AudioUnitSetParameter(instrumentUnit, kMusicDeviceParam_ReverbVolume, kAudioUnitScope_Global, 0, -120, 0))) { - NSLog(@"Unable to set reverb level to -120: %i", err); - } - } -#endif - - if ((err = AUGraphStart(graph))) { - NSLog(@"Unable to start AU graph: %i", err); - return NO; - } - - self.graph = graph; - self.instrument = instrumentUnit; - - return YES; -} - -+ (AudioComponentDescription)appleSynthComponentDescription -{ - AudioComponentDescription instrumentcd = (AudioComponentDescription){0}; - instrumentcd.componentManufacturer = kAudioUnitManufacturer_Apple; - instrumentcd.componentType = kAudioUnitType_MusicDevice; -#if TARGET_OS_IPHONE - instrumentcd.componentSubType = kAudioUnitSubType_Sampler; -#else - instrumentcd.componentSubType = kAudioUnitSubType_DLSSynth; -#endif - return instrumentcd; -} #pragma mark - Instruments -- (BOOL)selectInstrument:(MIKMIDIEndpointSynthesizerInstrument *)instrument; -{ - if (!instrument) return NO; - if (!self.isUsingAppleSynth) return NO; - - MusicDeviceInstrumentID instrumentID = instrument.instrumentID; - for (UInt8 channel = 0; channel < 16; channel++) { - // http://lists.apple.com/archives/coreaudio-api/2002/Sep/msg00015.html - UInt8 bankSelectMSB = (instrumentID >> 16) & 0x7F; - UInt8 bankSelectLSB = (instrumentID >> 8) & 0x7F; - UInt8 programChange = instrumentID & 0x7F; - - UInt32 bankSelectStatus = 0xB0 | channel; - UInt32 programChangeStatus = 0xC0 | channel; - - AudioUnit instrumentUnit = self.instrument; - OSStatus err = MusicDeviceMIDIEvent(instrumentUnit, bankSelectStatus, 0x00, bankSelectMSB, 0); - if (err) { - NSLog(@"MusicDeviceMIDIEvent() (MSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return NO; - } - - err = MusicDeviceMIDIEvent(instrumentUnit, bankSelectStatus, 0x20, bankSelectLSB, 0); - if (err) { - NSLog(@"MusicDeviceMIDIEvent() (LSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return NO; - } - - err = MusicDeviceMIDIEvent(instrumentUnit, programChangeStatus, programChange, 0, 0); - if (err) { - NSLog(@"MusicDeviceMIDIEvent() (Program Change) failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return NO; - } - } - - return YES; -} - #pragma mark - Properties -- (void)setGraph:(AUGraph)graph -{ - if (graph != _graph) { - if (_graph) DisposeAUGraph(_graph); - _graph = graph; - } -} - -- (BOOL)isUsingAppleSynth -{ - AudioComponentDescription description = self.componentDescription; - AudioComponentDescription appleSynthDescription = [[self class] appleSynthComponentDescription]; - if (description.componentManufacturer != appleSynthDescription.componentManufacturer) return NO; - if (description.componentType != appleSynthDescription.componentType) return NO; - if (description.componentSubType != appleSynthDescription.componentSubType) return NO; - if (description.componentFlags != appleSynthDescription.componentFlags) return NO; - if (description.componentFlagsMask != appleSynthDescription.componentFlagsMask) return NO; - return YES; -} - -@end - -#pragma mark - - -@implementation MIKMIDIEndpointSynthesizerInstrument - -+ (AudioUnit)instrumentUnit -{ - static AudioUnit instrumentUnit = NULL; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - AudioComponentDescription componentDesc = { - .componentManufacturer = kAudioUnitManufacturer_Apple, - .componentType = kAudioUnitType_MusicDevice, -#if TARGET_OS_IPHONE - .componentSubType = kAudioUnitSubType_MIDISynth, -#else - .componentSubType = kAudioUnitSubType_DLSSynth, -#endif - }; - AudioComponent instrumentComponent = AudioComponentFindNext(NULL, &componentDesc); - if (!instrumentComponent) { - NSLog(@"Unable to create the DLSSynth audio unit."); - return; - } - AudioComponentInstanceNew(instrumentComponent, &instrumentUnit); - AudioUnitInitialize(instrumentUnit); - }); - - return instrumentUnit; -} - -+ (NSArray *)availableInstruments -{ - static NSArray *availableInstruments = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - AudioUnit audioUnit = [self instrumentUnit]; - NSMutableArray *result = [NSMutableArray array]; - - UInt32 instrumentCount; - UInt32 instrumentCountSize = sizeof(instrumentCount); - - OSStatus err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentCount, kAudioUnitScope_Global, 0, &instrumentCount, &instrumentCountSize); - if (err) { - NSLog(@"AudioUnitGetProperty() (Instrument Count) failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return; - } - - for (UInt32 i = 0; i < instrumentCount; i++) { - MusicDeviceInstrumentID instrumentID; - UInt32 idSize = sizeof(instrumentID); - err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentNumber, kAudioUnitScope_Global, i, &instrumentID, &idSize); - if (err) { - NSLog(@"AudioUnitGetProperty() (Instrument Number) failed with error %d in %s.", err, __PRETTY_FUNCTION__); - continue; - } - - MIKMIDIEndpointSynthesizerInstrument *instrument = [MIKMIDIEndpointSynthesizerInstrument instrumentWithID:instrumentID]; - if (instrument) [result addObject:instrument]; - } - - availableInstruments = [result copy]; - }); - - return availableInstruments; -} - -+ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID; -{ - char cName[256]; - UInt32 cNameSize = sizeof(cName); - OSStatus err = AudioUnitGetProperty([self instrumentUnit], kMusicDeviceProperty_InstrumentName, kAudioUnitScope_Global, instrumentID, &cName, &cNameSize); - if (err) { - NSLog(@"AudioUnitGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return nil; - } - - NSString *name = [NSString stringWithCString:cName encoding:NSASCIIStringEncoding]; - return [[self alloc] initWithName:name instrumentID:instrumentID]; -} -- (instancetype)initWithName:(NSString *)name instrumentID:(MusicDeviceInstrumentID)instrumentID -{ - self = [super init]; - if (self) { - _name = name; - _instrumentID = instrumentID; - } - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"%@", self.name]; -} - -- (BOOL)isEqual:(id)object -{ - if (object == self) return YES; - if (![object isMemberOfClass:[self class]]) return NO; - if (!self.instrumentID == [object instrumentID]) return NO; - return [self.name isEqualToString:[object name]]; -} - -- (NSUInteger)hash -{ - return (NSUInteger)self.instrumentID; -} @end diff --git a/Source/MIKMIDIMetronome.m b/Source/MIKMIDIMetronome.m index 11ef53cd..3b456edd 100644 --- a/Source/MIKMIDIMetronome.m +++ b/Source/MIKMIDIMetronome.m @@ -9,14 +9,13 @@ #import "MIKMIDIMetronome.h" #import "MIKMIDINoteEvent.h" - @implementation MIKMIDIMetronome - (void)setupMetronome { self.tickMessage = (MIDINoteMessage){ .channel = 0, .note = 57, .velocity = 127, .duration = 0.5, .releaseVelocity = 0 }; self.tockMessage = (MIDINoteMessage){ .channel = 0, .note = 56, .velocity = 127, .duration = 0.5, .releaseVelocity = 0 }; - [self selectInstrument:[MIKMIDIEndpointSynthesizerInstrument instrumentWithID:7864376]]; + [self selectInstrument:[MIKMIDISynthesizerInstrument instrumentWithID:7864376]]; } - (instancetype)init diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h new file mode 100644 index 00000000..43d3581e --- /dev/null +++ b/Source/MIKMIDISynthesizer.h @@ -0,0 +1,107 @@ +// +// MIKMIDISynthesizer.h +// +// +// Created by Andrew Madsen on 2/19/15. +// +// + +#import +#import +#import + +@interface MIKMIDISynthesizer : NSObject + +/** + * Initializes an MIKMIDISynthesizer instance which uses the default + * MIDI instrument audio unit. + * + * On OS X, the default unit is Apple's DLS Synth audio unit. + * On iOS, the default is Apple's AUSampler audio unit. + * + * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. + */ +- (instancetype)init; + +/** + * Initializes an MIKMIDISynthesizer instance which uses an audio unit matching + * the provided description. + * + * @param componentDescription AudioComponentDescription describing the Audio Unit instrument + * you would like the synthesizer to use. + * + * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. + */ +- (instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription NS_DESIGNATED_INITIALIZER; + +/** + * Changes the instrument/voice used by the synthesizer. + * + * @param instrument An MIKMIDISynthesizerInstrument instance. + * + * @return YES if the instrument was successfully changed, NO if the change failed. + * + * @see +[MIKMIDISynthesizerInstrument availableInstruments] + */ +- (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument; + ++ (AudioComponentDescription)appleSynthComponentDescription; + +// methods for property 'componentDescription' +/** + * Sets up the AUGraph for the instrument. Do not call this method, as it is + * called automatically during initialization. + * + * The method is provided to give subclasses a chance to override + * the AUGraph behavior for the instrument. If you do override it, you will need + * to create an AudioUnit instrument and set it to the instrument property. Also, + * if you intend to use the graph property, you will be responsible for setting + * that as well. DisposeAUGraph() is called on the previous graph when setting + * the graph property, and in dealloc. + * + * @return YES is setting up the graph was succesful, and initialization + * should continue, NO if setting up the graph failed and initialization should + * return nil. + */ +- (BOOL)setupAUGraph; + +/** + * Plays MIDI messages through the synthesizer. + * + * This method can be used to synthesize arbitrary MIDI events. It is especially + * useful for MIKMIDIEndpointSynthesizers that are not connected to a MIDI + * endpoint. + * + * @param messages An NSArray of MIKMIDICommand (subclass) instances. + */ +- (void)handleMIDIMessages:(NSArray *)messages; + +// Properties + +/** + * The component description of the underlying Audio Unit instrument. + */ +@property (nonatomic, readonly) AudioComponentDescription componentDescription; + +/** + * The Audio Unit instrument that ultimately receives all of the MIDI messages sent to + * this endpoint synthesizer. + * + * @note You should only use the setter for this property from an + * MIKMIDIEndpointSynthesizer subclass. + * + * @see -setupAUGraph + */ +@property (nonatomic) AudioUnit instrument; + +/** + * The AUGraph for the instrument. + * + * @note You should only use the setter for this property from an + * MIKMIDIEndpointSynthesizer subclass. + * + * @see -setupAUGraph + */ +@property (nonatomic) AUGraph graph; + +@end diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m new file mode 100644 index 00000000..4e6943f5 --- /dev/null +++ b/Source/MIKMIDISynthesizer.m @@ -0,0 +1,198 @@ +// +// MIKMIDISynthesizer.m +// +// +// Created by Andrew Madsen on 2/19/15. +// +// + +#import "MIKMIDISynthesizer.h" +#import "MIKMIDICommand.h" + +@implementation MIKMIDISynthesizer + +- (instancetype)init +{ + return [self initWithAudioUnitDescription:[[self class] appleSynthComponentDescription]]; +} + +- (instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription +{ + self = [super init]; + if (self) { + _componentDescription = componentDescription; + if (![self setupAUGraph]) return nil; + } + return self; +} + +- (void)dealloc +{ + self.graph = NULL; +} + +#pragma mark - Public + +- (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument; +{ + if (!instrument) return NO; + if (!self.isUsingAppleSynth) return NO; + + MusicDeviceInstrumentID instrumentID = instrument.instrumentID; + for (UInt8 channel = 0; channel < 16; channel++) { + // http://lists.apple.com/archives/coreaudio-api/2002/Sep/msg00015.html + UInt8 bankSelectMSB = (instrumentID >> 16) & 0x7F; + UInt8 bankSelectLSB = (instrumentID >> 8) & 0x7F; + UInt8 programChange = instrumentID & 0x7F; + + UInt32 bankSelectStatus = 0xB0 | channel; + UInt32 programChangeStatus = 0xC0 | channel; + + AudioUnit instrumentUnit = self.instrument; + OSStatus err = MusicDeviceMIDIEvent(instrumentUnit, bankSelectStatus, 0x00, bankSelectMSB, 0); + if (err) { + NSLog(@"MusicDeviceMIDIEvent() (MSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return NO; + } + + err = MusicDeviceMIDIEvent(instrumentUnit, bankSelectStatus, 0x20, bankSelectLSB, 0); + if (err) { + NSLog(@"MusicDeviceMIDIEvent() (LSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return NO; + } + + err = MusicDeviceMIDIEvent(instrumentUnit, programChangeStatus, programChange, 0, 0); + if (err) { + NSLog(@"MusicDeviceMIDIEvent() (Program Change) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return NO; + } + } + + return YES; +} + + +#pragma mark - Private + +#pragma mark Audio Graph + +- (BOOL)setupAUGraph +{ + AUGraph graph; + OSStatus err = 0; + if ((err = NewAUGraph(&graph))) { + NSLog(@"Unable to create AU graph: %i", err); + return NO; + } + + AudioComponentDescription outputcd = {0}; + outputcd.componentType = kAudioUnitType_Output; +#if TARGET_OS_IPHONE + outputcd.componentSubType = kAudioUnitSubType_RemoteIO; +#else + outputcd.componentSubType = kAudioUnitSubType_DefaultOutput; +#endif + + outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; + + AUNode outputNode; + if ((err = AUGraphAddNode(graph, &outputcd, &outputNode))) { + NSLog(@"Unable to add ouptput node to graph: %i", err); + return NO; + } + + AudioComponentDescription instrumentcd = self.componentDescription; + + AUNode instrumentNode; + if ((err = AUGraphAddNode(graph, &instrumentcd, &instrumentNode))) { + NSLog(@"Unable to add instrument node to AU graph: %i", err); + return NO; + } + + if ((err = AUGraphOpen(graph))) { + NSLog(@"Unable to open AU graph: %i", err); + return NO; + } + + AudioUnit instrumentUnit; + if ((err = AUGraphNodeInfo(graph, instrumentNode, NULL, &instrumentUnit))) { + NSLog(@"Unable to get instrument AU unit: %i", err); + return NO; + } + + if ((err = AUGraphConnectNodeInput(graph, instrumentNode, 0, outputNode, 0))) { + NSLog(@"Unable to connect instrument to output: %i", err); + return NO; + } + + if ((err = AUGraphInitialize(graph))) { + NSLog(@"Unable to initialize AU graph: %i", err); + return NO; + } + +#if !TARGET_OS_IPHONE + // Turn down reverb which is way too high by default + if (instrumentcd.componentSubType == kAudioUnitSubType_DLSSynth) { + if ((err = AudioUnitSetParameter(instrumentUnit, kMusicDeviceParam_ReverbVolume, kAudioUnitScope_Global, 0, -120, 0))) { + NSLog(@"Unable to set reverb level to -120: %i", err); + } + } +#endif + + if ((err = AUGraphStart(graph))) { + NSLog(@"Unable to start AU graph: %i", err); + return NO; + } + + self.graph = graph; + self.instrument = instrumentUnit; + + return YES; +} + +#pragma mark - Instruments + +- (BOOL)isUsingAppleSynth +{ + AudioComponentDescription description = self.componentDescription; + AudioComponentDescription appleSynthDescription = [[self class] appleSynthComponentDescription]; + if (description.componentManufacturer != appleSynthDescription.componentManufacturer) return NO; + if (description.componentType != appleSynthDescription.componentType) return NO; + if (description.componentSubType != appleSynthDescription.componentSubType) return NO; + if (description.componentFlags != appleSynthDescription.componentFlags) return NO; + if (description.componentFlagsMask != appleSynthDescription.componentFlagsMask) return NO; + return YES; +} + ++ (AudioComponentDescription)appleSynthComponentDescription +{ + AudioComponentDescription instrumentcd = (AudioComponentDescription){0}; + instrumentcd.componentManufacturer = kAudioUnitManufacturer_Apple; + instrumentcd.componentType = kAudioUnitType_MusicDevice; +#if TARGET_OS_IPHONE + instrumentcd.componentSubType = kAudioUnitSubType_Sampler; +#else + instrumentcd.componentSubType = kAudioUnitSubType_DLSSynth; +#endif + return instrumentcd; +} + +#pragma mark - Properties + +- (void)setGraph:(AUGraph)graph +{ + if (graph != _graph) { + if (_graph) DisposeAUGraph(_graph); + _graph = graph; + } +} + +- (void)handleMIDIMessages:(NSArray *)commands +{ + for (MIKMIDICommand *command in commands) { + OSStatus err = MusicDeviceMIDIEvent(self.instrument, command.commandType, command.dataByte1, command.dataByte2, 0); + if (err) NSLog(@"Unable to send MIDI command to synthesizer %@: %i", command, err); + } +} + +@end diff --git a/Source/MIKMIDISynthesizerInstrument.h b/Source/MIKMIDISynthesizerInstrument.h new file mode 100644 index 00000000..2c973712 --- /dev/null +++ b/Source/MIKMIDISynthesizerInstrument.h @@ -0,0 +1,47 @@ +// +// MIKMIDISynthesizerInstrument.h +// MIKMIDI +// +// Created by Andrew Madsen on 2/19/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import + +/** + * MIKMIDISynthesizerInstrument is used to represent + */ +@interface MIKMIDISynthesizerInstrument : NSObject + +/** + * An array of available MIKMIDISynthesizerInstruments for use + * with MIKMIDIEndpointSynthesizer. + * + * @return An NSArray containing MIKMIDISynthesizerInstrument instances. + */ ++ (NSArray *)availableInstruments; + +/** + * Creates and initializes an MIKMIDISynthesizerInstrument with the corresponding instrument ID. + * + * @param instrumentID The MusicDeviceInstrumentID for the desired MIKMIDISynthesizerInstrument + * + * @return A MIKMIDISynthesizerInstrument with the matching instrument ID, or nil if no instrument was found. + */ ++ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID; + +/** + * The human readable name of the receiver. e.g. "Piano 1". + */ +@property (readonly, copy, nonatomic) NSString *name; + +/** + * The Core Audio supplied instrumentID for the receiver. + */ +@property (readonly, nonatomic) MusicDeviceInstrumentID instrumentID; + +@end + +// For backwards compatibility with applications written against MIKMIDI 1.0.x +@compatibility_alias MIKMIDIEndpointSynthesizerInstrument MIKMIDISynthesizerInstrument; \ No newline at end of file diff --git a/Source/MIKMIDISynthesizerInstrument.m b/Source/MIKMIDISynthesizerInstrument.m new file mode 100644 index 00000000..f687d11b --- /dev/null +++ b/Source/MIKMIDISynthesizerInstrument.m @@ -0,0 +1,117 @@ +// +// MIKMIDISynthesizerInstrument.m +// MIKMIDI +// +// Created by Andrew Madsen on 2/19/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDISynthesizerInstrument.h" + +@implementation MIKMIDISynthesizerInstrument + ++ (AudioUnit)instrumentUnit +{ + static AudioUnit instrumentUnit = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + AudioComponentDescription componentDesc = { + .componentManufacturer = kAudioUnitManufacturer_Apple, + .componentType = kAudioUnitType_MusicDevice, +#if TARGET_OS_IPHONE + .componentSubType = kAudioUnitSubType_MIDISynth, +#else + .componentSubType = kAudioUnitSubType_DLSSynth, +#endif + }; + AudioComponent instrumentComponent = AudioComponentFindNext(NULL, &componentDesc); + if (!instrumentComponent) { + NSLog(@"Unable to create the default synthesizer instrument audio unit."); + return; + } + AudioComponentInstanceNew(instrumentComponent, &instrumentUnit); + AudioUnitInitialize(instrumentUnit); + }); + + return instrumentUnit; +} + ++ (NSArray *)availableInstruments +{ + static NSArray *availableInstruments = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + AudioUnit audioUnit = [self instrumentUnit]; + NSMutableArray *result = [NSMutableArray array]; + + UInt32 instrumentCount; + UInt32 instrumentCountSize = sizeof(instrumentCount); + + OSStatus err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentCount, kAudioUnitScope_Global, 0, &instrumentCount, &instrumentCountSize); + if (err) { + NSLog(@"AudioUnitGetProperty() (Instrument Count) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return; + } + + for (UInt32 i = 0; i < instrumentCount; i++) { + MusicDeviceInstrumentID instrumentID; + UInt32 idSize = sizeof(instrumentID); + err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentNumber, kAudioUnitScope_Global, i, &instrumentID, &idSize); + if (err) { + NSLog(@"AudioUnitGetProperty() (Instrument Number) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + continue; + } + + MIKMIDISynthesizerInstrument *instrument = [MIKMIDISynthesizerInstrument instrumentWithID:instrumentID]; + if (instrument) [result addObject:instrument]; + } + + availableInstruments = [result copy]; + }); + + return availableInstruments; +} + ++ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID; +{ + char cName[256]; + UInt32 cNameSize = sizeof(cName); + OSStatus err = AudioUnitGetProperty([self instrumentUnit], kMusicDeviceProperty_InstrumentName, kAudioUnitScope_Global, instrumentID, &cName, &cNameSize); + if (err) { + NSLog(@"AudioUnitGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return nil; + } + + NSString *name = [NSString stringWithCString:cName encoding:NSASCIIStringEncoding]; + return [[self alloc] initWithName:name instrumentID:instrumentID]; +} + +- (instancetype)initWithName:(NSString *)name instrumentID:(MusicDeviceInstrumentID)instrumentID +{ + self = [super init]; + if (self) { + _name = name; + _instrumentID = instrumentID; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@", self.name]; +} + +- (BOOL)isEqual:(id)object +{ + if (object == self) return YES; + if (![object isMemberOfClass:[self class]]) return NO; + if (!self.instrumentID == [object instrumentID]) return NO; + return [self.name isEqualToString:[object name]]; +} + +- (NSUInteger)hash +{ + return (NSUInteger)self.instrumentID; +} + +@end From d78c984fb51b399d86bdffc0ee5073ee880d02fe Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 19 Feb 2015 11:01:17 -0700 Subject: [PATCH 002/284] Updated MIKMIDI(Endpoint)Synthesizer documentation. --- Source/MIKMIDIEndpointSynthesizer.h | 7 +++++-- Source/MIKMIDIEndpointSynthesizer.m | 11 ----------- Source/MIKMIDISynthesizer.h | 13 +++++++++++++ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Source/MIKMIDIEndpointSynthesizer.h b/Source/MIKMIDIEndpointSynthesizer.h index 31642b7b..04b6d335 100644 --- a/Source/MIKMIDIEndpointSynthesizer.h +++ b/Source/MIKMIDIEndpointSynthesizer.h @@ -14,11 +14,14 @@ @class MIKMIDISynthesizerInstrument; /** - * MIKMIDIEndpointSynthesizer provides a very simple way to synthesize MIDI commands coming from a - * source endpoint (e.g. from a connected MIDI piano keyboard) to produce sound output. + * MIKMIDIEndpointSynthesizer is a subclass of MIKMIDISynthesizer that + * provides a very simple way to synthesize MIDI commands coming from a + * MIDI endpoint (e.g. from a connected MIDI piano keyboard) to produce sound output. * * To use it, simply create a synthesizer instance with the source you'd like it to play. It will * continue playing incoming MIDI until it is deallocated. + * + * @see MIKMIDISynthesizer */ @interface MIKMIDIEndpointSynthesizer : MIKMIDISynthesizer diff --git a/Source/MIKMIDIEndpointSynthesizer.m b/Source/MIKMIDIEndpointSynthesizer.m index 64b7e49c..43f3d6bb 100644 --- a/Source/MIKMIDIEndpointSynthesizer.m +++ b/Source/MIKMIDIEndpointSynthesizer.m @@ -109,7 +109,6 @@ - (BOOL)connectToMIDISource:(MIKMIDISourceEndpoint *)source error:(NSError **)er id connectionToken = [deviceManager connectInput:source error:error eventHandler:^(MIKMIDISourceEndpoint *source, NSArray *commands) { __strong MIKMIDIEndpointSynthesizer *strongSelf = weakSelf; [strongSelf handleMIDIMessages:commands]; - }]; if (!connectionToken) return NO; @@ -119,14 +118,4 @@ - (BOOL)connectToMIDISource:(MIKMIDISourceEndpoint *)source error:(NSError **)er return YES; } - - - - -#pragma mark - Instruments - -#pragma mark - Properties - - - @end diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 43d3581e..1e94c19a 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -10,6 +10,19 @@ #import #import +/** + * MIKMIDISynthesizer provides a simple way to synthesize MIDI messages to + * produce sound output. + * + * To use it, simply create a synthesizer instance, then pass MIDI messages + * to it by calling -handleMIDIMessages:. + * + * A subclass, MIKMIDIEndpointSynthesizer, adds the ability to easily connect + * to a MIDI endpoint and automatically synthesize incoming messages. + * + * @see MIKMIDIEndpointSynthesizer + * + */ @interface MIKMIDISynthesizer : NSObject /** From 089e720ecb81ceb8cc48d9370d2a76192cc52c73 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 19 Feb 2015 11:45:46 -0700 Subject: [PATCH 003/284] Internal, non-functional change to MIKMIDISequencer's builtinSynthesizer type. --- Source/MIKMIDISequencer.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 1608289f..b746e89b 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -20,7 +20,7 @@ #import "MIKMIDIMetaTimeSignatureEvent.h" #import "MIKMIDIClientDestinationEndpoint.h" #import "MIKMIDIUtilities.h" - +#import "MIKMIDISynthesizer.h" #define MIKMIDISequencerDefaultTempo 120 #define MIKMIDISequencerDefaultTimeSignature ((MIKMIDITimeSignature) { .numerator = 4, .denominator = 4 }) @@ -73,7 +73,7 @@ @interface MIKMIDISequencer () @property (nonatomic, strong) MIKMIDIClientDestinationEndpoint *metronomeEndpoint; @property (nonatomic, strong, readonly) MIKMIDIClientDestinationEndpoint *builtinEndpoint; -@property (nonatomic, strong, readonly) MIKMIDIEndpointSynthesizer *builtinSynthesizer; +@property (nonatomic, strong, readonly) MIKMIDISynthesizer *builtinSynthesizer; @end @@ -640,7 +640,7 @@ - (MIKMIDIClientDestinationEndpoint *)builtinEndpoint } @synthesize builtinSynthesizer = _builtinSynthesizer; -- (MIKMIDIEndpointSynthesizer *)builtinSynthesizer +- (MIKMIDISynthesizer *)builtinSynthesizer { if (!_builtinSynthesizer) { _builtinSynthesizer = [MIKMIDIEndpointSynthesizer synthesizerWithClientDestinationEndpoint:self.builtinEndpoint]; From b6fd829e04766fe111f7b0b514a7c4e9414f4f34 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 19 Feb 2015 11:49:53 -0700 Subject: [PATCH 004/284] Very minor documentation format tweaks. --- Source/MIKMIDIClock.h | 2 -- Source/MIKMIDIEndpointSynthesizer.h | 4 ---- Source/MIKMIDISequence.h | 6 +----- Source/MIKMIDISequencer.h | 1 - Source/MIKMIDITrack.h | 16 ---------------- 5 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index ec2227d2..e608ffd4 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -48,9 +48,7 @@ * -midiTimeStampForMusicTimeStamp: will return any meaningful values. * * @param musicTimeStamp The MusicTimeStamp to synchronize the clock to. - * * @param tempo The beats per minute at which MusicTimeStamps should tick. - * * @param midiTimeStamp The MIDITimeStamp to synchronize the clock to. * * @see -musicTimeStampForMIDITimeStamp: diff --git a/Source/MIKMIDIEndpointSynthesizer.h b/Source/MIKMIDIEndpointSynthesizer.h index 04b6d335..9f84ffca 100644 --- a/Source/MIKMIDIEndpointSynthesizer.h +++ b/Source/MIKMIDIEndpointSynthesizer.h @@ -39,7 +39,6 @@ * Creates and initializes an MIKMIDIEndpointSynthesizer instance. * * @param source An MIKMIDISourceEndpoint instance from which MIDI note events will be received. - * * @param componentDescription an AudioComponentDescription describing the Audio Unit instrument * you would like the synthesizer to use. * @@ -61,7 +60,6 @@ * Initializes an MIKMIDIEndpointSynthesizer instance. * * @param source An MIKMIDISourceEndpoint instance from which MIDI note events will be received. - * * @param componentDescription an AudioComponentDescription describing the Audio Unit instrument * you would like the synthesizer to use. * @@ -83,7 +81,6 @@ * Creates and initializes an MIKMIDIEndpointSynthesizer instance. * * @param destination An MIKMIDIClientDestinationEndpoint instance from which MIDI note events will be received. - * * @param componentDescription an AudioComponentDescription describing the Audio Unit instrument * you would like the synthesizer to use. * @@ -105,7 +102,6 @@ * Initializes an MIKMIDIEndpointSynthesizer instance. * * @param destination An MIKMIDIClientDestinationEndpoint instance from which MIDI note events will be received. - * * @param componentDescription an AudioComponentDescription describing the Audio Unit instrument * you would like the synthesizer to use. * diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 776a08f5..e5b43c10 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -40,7 +40,6 @@ typedef struct { * Creates and initilazes a new instance of MIKMIDISequence from a MIDI file. * * @param fileURL The URL of the MIDI file. - * * @param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. * @@ -52,7 +51,6 @@ typedef struct { * Initilazes a new instance of MIKMIDISequence from a MIDI file. * * @param fileURL The URL of the MIDI file. - * * @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. * @@ -82,7 +80,6 @@ typedef struct { * Writes the MIDI sequence in Standard MIDI File format to a file at the specified URL. * * @param fileURL The URL to write the MIDI file to. - * * @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. * @@ -151,7 +148,6 @@ typedef struct { * Inserts a tempo event with the desired bpm into the tempo track at the specified time stamp. * * @param bpm The number of beats per minute for the tempo. - * * @param timeStamp The time stamp at which to set the tempo. * * @return Whether or not setting the tempo of the sequence was succesful. @@ -159,10 +155,10 @@ typedef struct { - (BOOL)setTempo:(Float64)bpm atTimeStamp:(MusicTimeStamp)timeStamp; /** + * * Gets the bpm of the last tempo event before the specified time stamp. * * @param bpm On output, the beats per minute of the tempo at the specified time stamp. - * * @param timeStamp The time stamp that you would like to know the sequence's tempo at. * * @return Whether or not getting the tempo was succesful. diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 5ebfbc4b..4adb0623 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -91,7 +91,6 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * such as an audio track, or another MIKMIDISequencer instance. * * @param timeStamp The position in the sequence to begin playback from. - * * @param midiTimeStamp The MIDITimeStamp to begin playback at. */ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITimeStamp)midiTimeStamp; diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 6fe4b61d..63f10f0a 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -79,7 +79,6 @@ * Gets all of the MIDI events in the track starting from startTimeStamp and ending at endTimeStamp inclusively. * * @param startTimeStamp The starting time stamp for the range to get MIDI events for. - * * @param endTimeStamp The ending time stamp for the range to get MIDI events for. Use kMusicTimeStamp_EndOfTrack to get events up to the * end of the track. * @@ -91,9 +90,7 @@ * Gets all of the MIDI events of a specific class in the track starting from startTimeStamp and ending at endTimeStamp inclusively. * * @param eventClass The class of MIDI events you would like to retrieve. This class must be the MIKMIDIEvent class or a subclass thereof. - * * @param startTimeStamp The staring time stamp for the range to get MIDI events for. - * * @param endTimeStamp The ending time stamp for the range to get MIDI events for. Use kMusicTimeStamp_EndOfTrack to get events up to the * end of the track. * @@ -105,7 +102,6 @@ * Gets all of the MIDI notes in the track starting from startTimeStamp and ending at endTimeStamp inclusively. * * @param startTimeStamp The starting time stamp for the range to get MIDI events for. - * * @param endTimeStamp The ending time stamp for the range to get MIDI notes for. Use kMusicTimeStamp_EndOfTrack to get events up to the * end of the track. * @@ -119,9 +115,7 @@ * Moves all of the MIDI events between startTimeStamp and endTimeStamp inclusively by the specified offset. * * @param startTimeStamp The starting time stamp for the range of the events to move. - * * @param endTimeStamp The ending time stamp for the range of the events to move. - * * @param offsetTimeStamp The amount to move the events * * @return Whether or not moving the events was succesful. @@ -132,7 +126,6 @@ * Removes all of the MIDI events between startTimeStamp and endTimeStamp inclusively. * * @param startTimeStamp The starting time stamp for the range of the events to remove. - * * @param endTimeStamp The ending time stamp for the range of the events to move. * * @return Whether or not moving the MIDI events was succesful. @@ -144,7 +137,6 @@ * specified range will be moved back by the specified range time. * * @param startTimeStamp The starting time stamp for the range of the events to cut. - * * @param endTimeStamp The ending time stamp for the range of the events to cut. * * @return Whether or not cutting the MIDI events was succesful. @@ -155,11 +147,8 @@ * Copies MIDI events from one track and inserts them into the receiver. * * @param origTrack The track to copy the events from. - * * @param startTimeStamp The starting time stamp for the range of the events to copy. - * * @param endTimeStamp The ending time stamp for the range of the events to copy. - * * @param destTimeStamp The time stamp at which to the copied events will be inserted into the receiver. * * @return Whether or not copying the MIDI events was succesful. @@ -170,11 +159,8 @@ * Copies MIDI events from one track and merges them into the receiver. * * @param origTrack The track to copy the events from. - * * @param startTimeStamp The starting time stamp for the range of the events to copy. - * * @param endTimeStamp The ending time stamp for the range of the events to copy. - * * @param destTimeStamp The time stamp at which to the copied events will be merged into the receiver. * * @return Whether or not merging the MIDI events was succesful. @@ -185,7 +171,6 @@ * Creates and initializes a new MIKMIDITrack. * * @param sequence The MIDI sequence the new track will belong to. - * * @param musicTrack The MusicTrack to use as the backing for the new MIDI track. * * @note You should not call this method. To add a new track to a MIDI sequence use -[MIKMIDISequence addTrack]. @@ -196,7 +181,6 @@ * Sets a temporary length and loopInfo for the track. * * @param length The temporary length for the track. - * * @param loopInfo The temporary loopInfo for the track. * * @note You should not call this method. It is exclusivley used by MIKMIDISequence when the sequence is being looped by a MIKMIDIPlayer. From 98126a819420487c517d3cd631e159635121e64b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 19 Feb 2015 12:17:00 -0700 Subject: [PATCH 005/284] Issue #27: Deprecated and replaced -get...: pass by reference methods in MIKMIDISequence. Improved documentation. --- Source/MIKMIDISequence.h | 83 +++++++++++++++++++++++++++++++++++----- Source/MIKMIDISequence.m | 68 +++++++++++++++++++++----------- 2 files changed, 119 insertions(+), 32 deletions(-) diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index e5b43c10..9983bbc4 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -13,7 +13,7 @@ typedef struct { UInt8 numerator; UInt8 denominator; -} MIKMIDITimeSignature; +} MIKMIDITimeSignature; // Deprecated @class MIKMIDITrack; @@ -145,7 +145,8 @@ typedef struct { - (BOOL)setOverallTempo:(Float64)bpm; /** - * Inserts a tempo event with the desired bpm into the tempo track at the specified time stamp. + * Inserts a tempo event with the desired tempo in beats per minute (BPM) + * into the tempo track at the specified time stamp. * * @param bpm The number of beats per minute for the tempo. * @param timeStamp The time stamp at which to set the tempo. @@ -155,20 +156,58 @@ typedef struct { - (BOOL)setTempo:(Float64)bpm atTimeStamp:(MusicTimeStamp)timeStamp; /** + * Returns the tempo in beats per minute (BPM) of the last tempo event before + * the specified time stamp. * - * Gets the bpm of the last tempo event before the specified time stamp. + * @param timeStamp The time stamp at which you would like to know the sequence's tempo. * - * @param bpm On output, the beats per minute of the tempo at the specified time stamp. - * @param timeStamp The time stamp that you would like to know the sequence's tempo at. - * - * @return Whether or not getting the tempo was succesful. + * @return The tempo in beats per minute at the specified time stamp, or 0.0 if an error occurred. */ -- (BOOL)getTempo:(Float64 *)bpm atTimeStamp:(MusicTimeStamp)timeStamp; +- (Float64)tempoAtTimeStamp:(MusicTimeStamp)timeStamp; -// TODO: Document these +/** + * Sets the overall time signature for the receiver. + * + * This method deletes all existing time signature events from the receiver's + * tempo track, replacing them with a single event with the specified time signature. + * + * @param signature An MIKMIDITimeSignature struct with the desired overall time signature. + * + * @return YES if setting the overall time signature was succesful, NO if an error occurred. + * + * @see -timeSignatureAtTimeStamp: + * @see -setTimeSignature:atTimeStamp: + */ - (BOOL)setOverallTimeSignature:(MIKMIDITimeSignature)signature; + +/** + * Sets the time signature of the receiver at a specified time. + * + * Unlike -setOverallTimeSignature, this method does not modify any existing + * time signature events. + * + * @param signature An MIKMIDITimeSignature struct representing the desired time signature. + * @param timeStamp The time stamp at which the time signature should be effective. + * + * @return YES if setting the time signature was successful, NO if an error occurred. + * + * @see -timeSignatureAtTimeStamp: + * @see -setOverallTimeSignature: + */ - (BOOL)setTimeSignature:(MIKMIDITimeSignature)signature atTimeStamp:(MusicTimeStamp)timeStamp; -- (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTimeStamp)timeStamp; + +/** + * Returns the time signature in effect at the the specified time stamp. + * + * @param timeStamp The time stamp at which you would like to know the receiver's time signature. + * + * @return An MIKMIDITimeSignature struct representing the time signature of the sequence at + * the specified time stamp. Defaults to 4/4 if no time signature events are found. + * + * @see -timeSignatureAtTimeStamp: + * @see -setOverallTimeSignature: + */ +- (MIKMIDITimeSignature)timeSignatureAtTimeStamp:(MusicTimeStamp)timeStamp; // Properties @@ -215,12 +254,36 @@ typedef struct { #pragma mark - Deprecated /** + * This method has been deprecated. You should not call it. Instead, use MIKMIDISequencer's API + * for routing tracks' output to specific endpoints. If you must set an endpoint on an MusicSequence, + * use CoreMIDI's API instead. + * * Sets the destination endpoint for each track in the sequence. * * @param destinationEndpoint The destination endpoint to set for each track in the sequence. */ - (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)destinationEndpoint DEPRECATED_ATTRIBUTE; +/** + * This method has been replaced by -tempoAtTimeStamp: and simply calls through to that method. + * You should not call it, and should update your code to call -timeSignatureAtTimeStamp: instead. + * + * @param bpm On output, the beats per minute of the tempo at the specified time stamp. + * @param timeStamp The time stamp that you would like to know the sequence's tempo at. + * @return YES if getting the tempo was successful, NO if an error occurred. + */ +- (BOOL)getTempo:(Float64 *)bpm atTimeStamp:(MusicTimeStamp)timeStamp DEPRECATED_ATTRIBUTE; + +/** + * This method has been replaced by -timeSignatureAtTimeStamp: and simply calls through to that method. + * You should not call it, and should update your code to call -timeSignatureAtTimeStamp: instead. + * + * @param signature On output, a time signature instance with its values populated. + * @param timeStamp The time stamp at which you would like to know the time signature. + * @return YES if getting the time signature was successful, NO if an error occurred. + */ +- (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTimeStamp)timeStamp DEPRECATED_ATTRIBUTE; + @end diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 5851419e..4bd313db 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -223,15 +223,10 @@ - (BOOL)setTempo:(Float64)bpm atTimeStamp:(MusicTimeStamp)timeStamp return [self.tempoTrack insertMIDIEvent:event]; } -- (BOOL)getTempo:(Float64 *)bpm atTimeStamp:(MusicTimeStamp)timeStamp +- (Float64)tempoAtTimeStamp:(MusicTimeStamp)timeStamp { - if (!bpm) return NO; - NSArray *events = [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; - if (!events.count) return NO; - - MIKMIDITempoEvent *event = [events lastObject]; - *bpm = event.bpm; - return YES; + NSArray *events = [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; + return [[events lastObject] bpm]; } #pragma mark - Time Signature @@ -261,20 +256,16 @@ - (BOOL)setTimeSignature:(MIKMIDITimeSignature)signature atTimeStamp:(MusicTimeS return [self.tempoTrack insertMIDIEvent:event]; } -- (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTimeStamp)timeStamp +- (MIKMIDITimeSignature)timeSignatureAtTimeStamp:(MusicTimeStamp)timeStamp { - if (!signature) return NO; + MIKMIDITimeSignature result = {4, 4}; NSArray *events = [self.tempoTrack eventsOfClass:[MIKMIDIMetaTimeSignatureEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; - if (!events.count) { - signature->numerator = 4; - signature->denominator = 4; - return YES; - }; - MIKMIDIMetaTimeSignatureEvent *event = [events lastObject]; - signature->numerator = event.numerator; - signature->denominator = event.denominator; - return YES; + if (event) { + result.numerator = event.numerator; + result.denominator = event.denominator; + } + return result; } #pragma mark - Description @@ -319,12 +310,45 @@ - (NSData *)dataValue return (__bridge_transfer NSData *)data; } +#pragma mark - Deprecated + - (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)destinationEndpoint { NSLog(@"%s is deprecated. You should update your code to avoid calling this method. Use MIKMIDISequencer's API instead.", __PRETTY_FUNCTION__); - for (MIKMIDITrack *track in self.tracks) { - track.destinationEndpoint = destinationEndpoint; - } + for (MIKMIDITrack *track in self.tracks) { + track.destinationEndpoint = destinationEndpoint; + } +} + +- (BOOL)getTempo:(Float64 *)bpm atTimeStamp:(MusicTimeStamp)timeStamp +{ + static BOOL deprectionMsgShown = NO; + if (!deprectionMsgShown) { + NSLog(@"WARNING: %s has been deprecated. Please use -timeSignatureAtTimeStamp: instead. This message will only be logged once", __PRETTY_FUNCTION__); + deprectionMsgShown = YES; + } + + if (!bpm) return NO; + Float64 result = [self tempoAtTimeStamp:timeStamp]; + if (result == 0.0) return NO; + *bpm = result; + return YES; +} + +- (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTimeStamp)timeStamp +{ + static BOOL deprectionMsgShown = NO; + if (!deprectionMsgShown) { + NSLog(@"WARNING: %s has been deprecated. Please use -timeSignatureAtTimeStamp: instead. This message will only be logged once", __PRETTY_FUNCTION__); + deprectionMsgShown = YES; + } + + if (!signature) return NO; + MIKMIDITimeSignature result = [self timeSignatureAtTimeStamp:timeStamp]; + if (result.numerator == 0) return NO; + signature->numerator = result.numerator; + signature->denominator = result.denominator; + return YES; } @end From 8c73b1c50f9c4b5c496eebf77e35c6c82ea7099f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 19 Feb 2015 12:23:28 -0700 Subject: [PATCH 006/284] Issue #27: Updated MIKMIDIPlayer and MIKMIDISequencer to no longer use deprecated methods. --- Source/MIKMIDIPlayer.m | 3 +-- Source/MIKMIDISequencer.m | 12 +++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Source/MIKMIDIPlayer.m b/Source/MIKMIDIPlayer.m index cc25fd82..9a5cf38e 100644 --- a/Source/MIKMIDIPlayer.m +++ b/Source/MIKMIDIPlayer.m @@ -197,8 +197,7 @@ - (void)addClickTrackWhenNeededFromTimeStamp:(MusicTimeStamp)fromTimeStamp MIDINoteMessage tockMessage = self.metronome.tockMessage; MusicTimeStamp increment = 1; for (MusicTimeStamp clickTimeStamp = floor(fromTimeStamp); clickTimeStamp <= toTimeStamp; clickTimeStamp += increment) { - MIKMIDITimeSignature timeSignature; - if (![clickSequence getTimeSignature:&timeSignature atTimeStamp:clickTimeStamp]) continue; + MIKMIDITimeSignature timeSignature = [clickSequence timeSignatureAtTimeStamp:clickTimeStamp]; if (!timeSignature.numerator || !timeSignature.denominator) continue; NSInteger adjustedTimeStamp = clickTimeStamp * timeSignature.denominator / 4.0; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b746e89b..2978b9b4 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -23,7 +23,6 @@ #import "MIKMIDISynthesizer.h" #define MIKMIDISequencerDefaultTempo 120 -#define MIKMIDISequencerDefaultTimeSignature ((MIKMIDITimeSignature) { .numerator = 4, .denominator = 4 }) #pragma mark - @@ -130,8 +129,8 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; self.startingTimeStamp = startingTimeStamp; - Float64 startingTempo; - if (![self.sequence getTempo:&startingTempo atTimeStamp:startingTimeStamp]) startingTempo = MIKMIDISequencerDefaultTempo; + Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; + if (!startingTempo) startingTempo = MIKMIDISequencerDefaultTempo; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; self.playing = YES; @@ -245,8 +244,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (isLooping) { if (calculatedToMusicTimeStamp > toMusicTimeStamp) { [self recordAllPendingNoteEventsWithOffTimeStamp:loopEndTimeStamp]; - Float64 tempo; - if (![sequence getTempo:&tempo atTimeStamp:loopStartTimeStamp]) tempo = MIKMIDISequencerDefaultTempo; + Float64 tempo = [sequence tempoAtTimeStamp:loopStartTimeStamp]; + if (!tempo) tempo = MIKMIDISequencerDefaultTempo; MusicTimeStamp loopLength = loopEndTimeStamp - loopStartTimeStamp; MIDITimeStamp loopStartMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:loopStartTimeStamp + loopLength]; @@ -527,8 +526,7 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp MIKMIDISequence *sequence = self.sequence; MusicTimeStamp playbackOffset = self.playbackOffset; - MIKMIDITimeSignature timeSignature; - if (![sequence getTimeSignature:&timeSignature atTimeStamp:fromTimeStamp - playbackOffset]) timeSignature = MIKMIDISequencerDefaultTimeSignature; + MIKMIDITimeSignature timeSignature = [sequence timeSignatureAtTimeStamp:fromTimeStamp - playbackOffset]; NSMutableArray *timeSignatureEvents = [[sequence.tempoTrack eventsOfClass:[MIKMIDIMetaTimeSignatureEvent class] fromTimeStamp:MAX(fromTimeStamp - playbackOffset, 0) toTimeStamp:toTimeStamp] mutableCopy]; From 53353fb43a441c2d22dda410cfd1b1435e31fce9 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 20 Feb 2015 12:25:32 -0700 Subject: [PATCH 007/284] Removed MIKMIDIEvent's channel property, which was not really implemented. Added setter for channel to MIKMIDINoteEvent. --- Source/MIKMIDIEvent.h | 5 ----- Source/MIKMIDIEvent.m | 1 - Source/MIKMIDINoteEvent.h | 45 +++++++++++++++++++++++++++++---------- Source/MIKMIDINoteEvent.m | 27 +++++++++++++++++++++++ 4 files changed, 61 insertions(+), 17 deletions(-) diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index d11136e8..6216998c 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -119,11 +119,6 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) */ @property (nonatomic, readonly) MusicEventType eventType; -/** - * The channel for the MIDI event. - */ -@property (nonatomic, readonly) UInt8 channel; - /** * The timeStamp of the MIDI event. When used in a MusicSequence of type kMusicSequenceType_Beats * a timeStamp of 1 equals one quarter note. See the MusicSequence Reference for more information. diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 7511c6ec..0d012acf 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -171,7 +171,6 @@ + (BOOL)isMutable { return YES; } + (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return [[self immutableCounterpartClass] supportsMIKMIDIEventType:type]; } @dynamic eventType; -@dynamic channel; @dynamic data; @dynamic timeStamp; diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 12ab38c4..bbf897d2 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -8,11 +8,26 @@ #import "MIKMIDIEvent.h" +@class MIKMIDIClock; + /** * A MIDI note event. */ @interface MIKMIDINoteEvent : MIKMIDIEvent +/** + * Convenience method for creating a new MIKMIDINoteEvent. + * + * @param timeStamp The MusicTimeStamp for the event. + * + * @param message The MIDINoteMessage for the event. + * + * @return A new MIKMIDINoteEvent instance, or nil if there is an error. + */ ++ (instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDINoteMessage)message; + +// Properties + /** * The MIDI note number for the event. */ @@ -23,6 +38,11 @@ */ @property (nonatomic, readonly) UInt8 velocity; +/** + * The channel for the MIDI event. + */ +@property (nonatomic, readonly) UInt8 channel; + /** * The release velocity of the event. Use 0 if you don’t want to specify a particular value. */ @@ -53,19 +73,10 @@ */ @property (nonatomic, readonly) NSString *noteLetterAndOctave; -/** - * Convenience method for creating a new MIKMIDINoteEvent. - * - * @param timeStamp The MusicTimeStamp for the event. - * - * @param message The MIDINoteMessage for the event. - * - * @return A new MIKMIDINoteEvent instance, or nil if there is an error. - */ -+ (instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDINoteMessage)message; - @end +#pragma mark - + /** * The mutable counterpart of MIKMIDINoteEvent */ @@ -73,7 +84,19 @@ @property (nonatomic, readwrite) UInt8 note; @property (nonatomic, readwrite) UInt8 velocity; +@property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 releaseVelocity; @property (nonatomic, readwrite) Float32 duration; +@end + +#pragma mark - + +#import +#import + +@interface MIKMIDICommand (MIKMIDINoteEventToCommands) + ++ (NSArray *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; + @end \ No newline at end of file diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 134c25da..a4730b51 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -9,6 +9,7 @@ #import "MIKMIDINoteEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" #import "MIKMIDIUtilities.h" +#import "MIKMIDIClock.h" #if !__has_feature(objc_arc) #error MIKMIDINoteEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target @@ -154,9 +155,35 @@ @implementation MIKMutableMIDINoteEvent @dynamic note; @dynamic velocity; +@dynamic channel; @dynamic releaseVelocity; @dynamic duration; + (BOOL)isMutable { return YES; } @end + +#pragma mark - + +@implementation MIKMIDICommand (MIKMIDINoteEventToCommands) + ++ (NSArray *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock +{ + // Note On + MIKMutableMIDINoteOnCommand *noteOn = [MIKMutableMIDINoteOnCommand commandForCommandType:MIKMIDICommandTypeNoteOn]; + noteOn.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:noteEvent.timeStamp]; + noteOn.channel = noteEvent.channel; + noteOn.note = noteEvent.note; + noteOn.velocity = noteEvent.velocity; + + // Note Off + MIKMutableMIDINoteOffCommand *noteOff = [MIKMutableMIDINoteOffCommand commandForCommandType:MIKMIDICommandTypeNoteOff]; + noteOff.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:noteEvent.endTimeStamp]; + noteOff.channel = noteEvent.channel; + noteOff.note = noteEvent.note; + noteOff.velocity = noteEvent.releaseVelocity; + + return @[[noteOn copy], [noteOff copy]]; +} + +@end \ No newline at end of file From eb44fa5df8ccba36017de3bc0a1685883e2821df Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 20 Feb 2015 13:42:41 -0700 Subject: [PATCH 008/284] Added read-only statusByte property to MIKMIDICommand. --- Source/MIKMIDICommand.h | 8 ++++++++ Source/MIKMIDICommand.m | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/Source/MIKMIDICommand.h b/Source/MIKMIDICommand.h index 8f5d89aa..3bea78d9 100644 --- a/Source/MIKMIDICommand.h +++ b/Source/MIKMIDICommand.h @@ -164,6 +164,14 @@ typedef NS_ENUM(NSUInteger, MIKMIDICommandType) { */ @property (nonatomic, readonly) MIKMIDICommandType commandType; +/** + * The MIDI status byte. The exact meaning of the contents + * of this byte differ for different command types. See + * http://www.midi.org/techspecs/midimessages.php for a information + * about the contents of this value. + */ +@property (nonatomic, readonly) UInt8 statusByte; + /** * The first byte of the MIDI data (after the command type). */ diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 151637e5..3dc09701 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -262,6 +262,12 @@ - (void)setCommandType:(MIKMIDICommandType)commandType data[0] = commandType; } +- (UInt8)statusByte +{ + if ([self.internalData length] < 1) return 0; + return ((UInt8 *)[self.internalData bytes])[0]; +} + - (UInt8)dataByte1 { if ([self.internalData length] < 2) return 0; From 7e739b91cd2b85cc54be6080ad17e0bd7a5ad255 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 20 Feb 2015 15:01:45 -0700 Subject: [PATCH 009/284] Switched MIDI Files Testbed project to use MIKMIDI.framework. Fixed build errors. --- .../project.pbxproj | 460 ++---------------- .../Source/MIKAppDelegate.m | 2 +- .../Source/MIKMIDISequenceView.m | 7 +- 3 files changed, 56 insertions(+), 413 deletions(-) diff --git a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index 1bd0b5f1..3c289fa4 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -7,75 +7,43 @@ objects = { /* Begin PBXBuildFile section */ - 9D3A2FBF192E85B800314B49 /* MIKMIDITempoEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D3A2FBE192E85B800314B49 /* MIKMIDITempoEvent.m */; }; + 9D54C5021A97E5D20050BB43 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */; }; 9DAE7D37192FC1B800B25DD7 /* MIKMIDISequenceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */; }; 9DB2A5F7192D184D0047A3EB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DB2A5F6192D184D0047A3EB /* Cocoa.framework */; }; - 9DB2A616192D184D0047A3EB /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DB2A615192D184D0047A3EB /* XCTest.framework */; }; - 9DB2A617192D184D0047A3EB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DB2A5F6192D184D0047A3EB /* Cocoa.framework */; }; - 9DB2A61F192D184D0047A3EB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A61D192D184D0047A3EB /* InfoPlist.strings */; }; - 9DB2A621192D184D0047A3EB /* MIDI_Files_TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A620192D184D0047A3EB /* MIDI_Files_TestbedTests.m */; }; 9DB2A62F192D18970047A3EB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A62B192D18970047A3EB /* main.m */; }; 9DB2A630192D18970047A3EB /* MIKAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A62E192D18970047A3EB /* MIKAppDelegate.m */; }; 9DB2A63A192D189E0047A3EB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A632192D189E0047A3EB /* MainMenu.xib */; }; 9DB2A63B192D189E0047A3EB /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A634192D189E0047A3EB /* Credits.rtf */; }; 9DB2A63C192D189E0047A3EB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A636192D189E0047A3EB /* InfoPlist.strings */; }; 9DB2A63D192D189E0047A3EB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A638192D189E0047A3EB /* Images.xcassets */; }; - 9DB2A67B192D19A60047A3EB /* MIKMIDIChannelVoiceCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A642192D19A60047A3EB /* MIKMIDIChannelVoiceCommand.m */; }; - 9DB2A67C192D19A60047A3EB /* MIKMIDICommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A645192D19A60047A3EB /* MIKMIDICommand.m */; }; - 9DB2A67D192D19A60047A3EB /* MIKMIDICommandThrottler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A648192D19A60047A3EB /* MIKMIDICommandThrottler.m */; }; - 9DB2A67E192D19A60047A3EB /* MIKMIDIControlChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A64A192D19A60047A3EB /* MIKMIDIControlChangeCommand.m */; }; - 9DB2A67F192D19A60047A3EB /* MIKMIDIDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A64C192D19A60047A3EB /* MIKMIDIDestinationEndpoint.m */; }; - 9DB2A680192D19A60047A3EB /* MIKMIDIDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A64E192D19A60047A3EB /* MIKMIDIDevice.m */; }; - 9DB2A681192D19A60047A3EB /* MIKMIDIDeviceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A650192D19A60047A3EB /* MIKMIDIDeviceManager.m */; }; - 9DB2A682192D19A60047A3EB /* MIKMIDIEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A652192D19A60047A3EB /* MIKMIDIEndpoint.m */; }; - 9DB2A683192D19A60047A3EB /* MIKMIDIEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A654192D19A60047A3EB /* MIKMIDIEntity.m */; }; - 9DB2A684192D19A60047A3EB /* MIKMIDIErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A656192D19A60047A3EB /* MIKMIDIErrors.m */; }; - 9DB2A685192D19A60047A3EB /* MIKMIDIInputPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A658192D19A60047A3EB /* MIKMIDIInputPort.m */; }; - 9DB2A686192D19A60047A3EB /* MIKMIDIMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A65A192D19A60047A3EB /* MIKMIDIMapping.m */; }; - 9DB2A687192D19A60047A3EB /* MIKMIDIMappingGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A65C192D19A60047A3EB /* MIKMIDIMappingGenerator.m */; }; - 9DB2A688192D19A60047A3EB /* MIKMIDIMappingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A65E192D19A60047A3EB /* MIKMIDIMappingManager.m */; }; - 9DB2A689192D19A60047A3EB /* MIKMIDIMappingXMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A660192D19A60047A3EB /* MIKMIDIMappingXMLParser.m */; }; - 9DB2A68A192D19A60047A3EB /* MIKMIDINoteOffCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A662192D19A60047A3EB /* MIKMIDINoteOffCommand.m */; }; - 9DB2A68B192D19A60047A3EB /* MIKMIDINoteOnCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A664192D19A60047A3EB /* MIKMIDINoteOnCommand.m */; }; - 9DB2A68C192D19A60047A3EB /* MIKMIDIObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A666192D19A60047A3EB /* MIKMIDIObject.m */; }; - 9DB2A68D192D19A60047A3EB /* MIKMIDIOutputPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A669192D19A60047A3EB /* MIKMIDIOutputPort.m */; }; - 9DB2A68E192D19A60047A3EB /* MIKMIDIPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A66B192D19A60047A3EB /* MIKMIDIPort.m */; }; - 9DB2A68F192D19A60047A3EB /* MIKMIDIPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A66F192D19A60047A3EB /* MIKMIDIPrivateUtilities.m */; }; - 9DB2A690192D19A60047A3EB /* MIKMIDISourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A672192D19A60047A3EB /* MIKMIDISourceEndpoint.m */; }; - 9DB2A691192D19A60047A3EB /* MIKMIDISystemExclusiveCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A674192D19A60047A3EB /* MIKMIDISystemExclusiveCommand.m */; }; - 9DB2A692192D19A60047A3EB /* MIKMIDISystemMessageCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A676192D19A60047A3EB /* MIKMIDISystemMessageCommand.m */; }; - 9DB2A693192D19A60047A3EB /* MIKMIDIUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A678192D19A60047A3EB /* MIKMIDIUtilities.m */; }; - 9DB2A694192D19A60047A3EB /* NSUIApplication+MIKMIDI.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A67A192D19A60047A3EB /* NSUIApplication+MIKMIDI.m */; }; - 9DB2A697192D19D30047A3EB /* MIKMIDISequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A696192D19D30047A3EB /* MIKMIDISequence.m */; }; - 9DB2A69B192D1BE30047A3EB /* MIKMIDITrack.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A69A192D1BE30047A3EB /* MIKMIDITrack.m */; }; - F263C02E192D2A0C00F1B297 /* MIKMIDIEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C02D192D2A0C00F1B297 /* MIKMIDIEvent.m */; }; - F263C031192D547B00F1B297 /* MIKMIDINoteEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C030192D547B00F1B297 /* MIKMIDINoteEvent.m */; }; - F263C09F192E8F1C00F1B297 /* MIKMIDIMetaEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C09E192E8F1C00F1B297 /* MIKMIDIMetaEvent.m */; }; - F263C0A2192E8FC900F1B297 /* MIKMIDIMetaSequenceEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0A1192E8FC900F1B297 /* MIKMIDIMetaSequenceEvent.m */; }; - F263C0A5192EBDDE00F1B297 /* MIKMIDIMetaTextEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0A4192EBDDE00F1B297 /* MIKMIDIMetaTextEvent.m */; }; - F263C0A8192EC14800F1B297 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0A7192EC14800F1B297 /* MIKMIDIMetaTimeSignatureEvent.m */; }; - F263C0AB192EC5D200F1B297 /* MIKMIDIMetaLyricEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0AA192EC5D200F1B297 /* MIKMIDIMetaLyricEvent.m */; }; - F263C0AE192ED5AF00F1B297 /* MIKMIDIMetaCopyrightEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0AD192ED5AF00F1B297 /* MIKMIDIMetaCopyrightEvent.m */; }; - F263C0B1192ED69D00F1B297 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0B0192ED69D00F1B297 /* MIKMIDIMetaTrackSequenceNameEvent.m */; }; - F263C0B4192ED6B800F1B297 /* MIKMIDIMetaMarkerTextEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0B3192ED6B800F1B297 /* MIKMIDIMetaMarkerTextEvent.m */; }; - F263C0B7192ED6D300F1B297 /* MIKMIDIMetaInstrumentNameEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0B6192ED6D300F1B297 /* MIKMIDIMetaInstrumentNameEvent.m */; }; - F263C0BA192ED6F200F1B297 /* MIKMIDIMetaCuePointEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0B9192ED6F200F1B297 /* MIKMIDIMetaCuePointEvent.m */; }; - F263C0C3192FC81C00F1B297 /* MIKMIDIMetaKeySignatureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = F263C0C2192FC81C00F1B297 /* MIKMIDIMetaKeySignatureEvent.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 9DB2A618192D184D0047A3EB /* PBXContainerItemProxy */ = { + 9D54C4FE1A97E5C90050BB43 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 9DB2A5EB192D184D0047A3EB /* Project object */; + containerPortal = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9D74EEA517A7129300BEE89F; + remoteInfo = MIKMIDI; + }; + 9D54C5001A97E5C90050BB43 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9DAF8B061A7AFF1100F46528; + remoteInfo = "MIKMIDI-iOS"; + }; + 9D54C5031A97E5E90050BB43 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; proxyType = 1; - remoteGlobalIDString = 9DB2A5F2192D184D0047A3EB; - remoteInfo = "MIDI Files Testbed"; + remoteGlobalIDString = 9D74EEA417A7129300BEE89F; + remoteInfo = MIKMIDI; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 9D3A2FBD192E85B800314B49 /* MIKMIDITempoEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITempoEvent.h; sourceTree = ""; }; - 9D3A2FBE192E85B800314B49 /* MIKMIDITempoEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDITempoEvent.m; sourceTree = ""; }; + 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MIKMIDI.xcodeproj; path = ../../Framework/MIKMIDI.xcodeproj; sourceTree = ""; }; 9DAE7D35192FC1B800B25DD7 /* MIKMIDISequenceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequenceView.h; sourceTree = ""; }; 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequenceView.m; sourceTree = ""; }; 9DB2A5F3192D184D0047A3EB /* MIDI Files Testbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MIDI Files Testbed.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -83,7 +51,6 @@ 9DB2A5F9192D184D0047A3EB /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 9DB2A5FA192D184D0047A3EB /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 9DB2A5FB192D184D0047A3EB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 9DB2A614192D184D0047A3EB /* MIDI Files TestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIDI Files TestbedTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9DB2A615192D184D0047A3EB /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 9DB2A61C192D184D0047A3EB /* MIDI Files TestbedTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MIDI Files TestbedTests-Info.plist"; sourceTree = ""; }; 9DB2A61E192D184D0047A3EB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -97,96 +64,6 @@ 9DB2A637192D189E0047A3EB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 9DB2A638192D189E0047A3EB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 9DB2A639192D189E0047A3EB /* MIDI Files Testbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "MIDI Files Testbed-Info.plist"; sourceTree = ""; }; - 9DB2A640192D19A60047A3EB /* MIKMIDI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDI.h; sourceTree = ""; }; - 9DB2A641192D19A60047A3EB /* MIKMIDIChannelVoiceCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelVoiceCommand.h; sourceTree = ""; }; - 9DB2A642192D19A60047A3EB /* MIKMIDIChannelVoiceCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelVoiceCommand.m; sourceTree = ""; }; - 9DB2A643192D19A60047A3EB /* MIKMIDIChannelVoiceCommand_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelVoiceCommand_SubclassMethods.h; sourceTree = ""; }; - 9DB2A644192D19A60047A3EB /* MIKMIDICommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommand.h; sourceTree = ""; }; - 9DB2A645192D19A60047A3EB /* MIKMIDICommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommand.m; sourceTree = ""; }; - 9DB2A646192D19A60047A3EB /* MIKMIDICommand_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommand_SubclassMethods.h; sourceTree = ""; }; - 9DB2A647192D19A60047A3EB /* MIKMIDICommandThrottler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommandThrottler.h; sourceTree = ""; }; - 9DB2A648192D19A60047A3EB /* MIKMIDICommandThrottler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandThrottler.m; sourceTree = ""; }; - 9DB2A649192D19A60047A3EB /* MIKMIDIControlChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIControlChangeCommand.h; sourceTree = ""; }; - 9DB2A64A192D19A60047A3EB /* MIKMIDIControlChangeCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIControlChangeCommand.m; sourceTree = ""; }; - 9DB2A64B192D19A60047A3EB /* MIKMIDIDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIDestinationEndpoint.h; sourceTree = ""; }; - 9DB2A64C192D19A60047A3EB /* MIKMIDIDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIDestinationEndpoint.m; sourceTree = ""; }; - 9DB2A64D192D19A60047A3EB /* MIKMIDIDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIDevice.h; sourceTree = ""; }; - 9DB2A64E192D19A60047A3EB /* MIKMIDIDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIDevice.m; sourceTree = ""; }; - 9DB2A64F192D19A60047A3EB /* MIKMIDIDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIDeviceManager.h; sourceTree = ""; }; - 9DB2A650192D19A60047A3EB /* MIKMIDIDeviceManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIDeviceManager.m; sourceTree = ""; }; - 9DB2A651192D19A60047A3EB /* MIKMIDIEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEndpoint.h; sourceTree = ""; }; - 9DB2A652192D19A60047A3EB /* MIKMIDIEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEndpoint.m; sourceTree = ""; }; - 9DB2A653192D19A60047A3EB /* MIKMIDIEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEntity.h; sourceTree = ""; }; - 9DB2A654192D19A60047A3EB /* MIKMIDIEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEntity.m; sourceTree = ""; }; - 9DB2A655192D19A60047A3EB /* MIKMIDIErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIErrors.h; sourceTree = ""; }; - 9DB2A656192D19A60047A3EB /* MIKMIDIErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIErrors.m; sourceTree = ""; }; - 9DB2A657192D19A60047A3EB /* MIKMIDIInputPort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIInputPort.h; sourceTree = ""; }; - 9DB2A658192D19A60047A3EB /* MIKMIDIInputPort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIInputPort.m; sourceTree = ""; }; - 9DB2A659192D19A60047A3EB /* MIKMIDIMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMapping.h; sourceTree = ""; }; - 9DB2A65A192D19A60047A3EB /* MIKMIDIMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMapping.m; sourceTree = ""; }; - 9DB2A65B192D19A60047A3EB /* MIKMIDIMappingGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingGenerator.h; sourceTree = ""; }; - 9DB2A65C192D19A60047A3EB /* MIKMIDIMappingGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingGenerator.m; sourceTree = ""; }; - 9DB2A65D192D19A60047A3EB /* MIKMIDIMappingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingManager.h; sourceTree = ""; }; - 9DB2A65E192D19A60047A3EB /* MIKMIDIMappingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingManager.m; sourceTree = ""; }; - 9DB2A65F192D19A60047A3EB /* MIKMIDIMappingXMLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingXMLParser.h; sourceTree = ""; }; - 9DB2A660192D19A60047A3EB /* MIKMIDIMappingXMLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingXMLParser.m; sourceTree = ""; }; - 9DB2A661192D19A60047A3EB /* MIKMIDINoteOffCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDINoteOffCommand.h; sourceTree = ""; }; - 9DB2A662192D19A60047A3EB /* MIKMIDINoteOffCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDINoteOffCommand.m; sourceTree = ""; }; - 9DB2A663192D19A60047A3EB /* MIKMIDINoteOnCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDINoteOnCommand.h; sourceTree = ""; }; - 9DB2A664192D19A60047A3EB /* MIKMIDINoteOnCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDINoteOnCommand.m; sourceTree = ""; }; - 9DB2A665192D19A60047A3EB /* MIKMIDIObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIObject.h; sourceTree = ""; }; - 9DB2A666192D19A60047A3EB /* MIKMIDIObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIObject.m; sourceTree = ""; }; - 9DB2A667192D19A60047A3EB /* MIKMIDIObject_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIObject_SubclassMethods.h; sourceTree = ""; }; - 9DB2A668192D19A60047A3EB /* MIKMIDIOutputPort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIOutputPort.h; sourceTree = ""; }; - 9DB2A669192D19A60047A3EB /* MIKMIDIOutputPort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIOutputPort.m; sourceTree = ""; }; - 9DB2A66A192D19A60047A3EB /* MIKMIDIPort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPort.h; sourceTree = ""; }; - 9DB2A66B192D19A60047A3EB /* MIKMIDIPort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPort.m; sourceTree = ""; }; - 9DB2A66C192D19A60047A3EB /* MIKMIDIPort_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPort_SubclassMethods.h; sourceTree = ""; }; - 9DB2A66D192D19A60047A3EB /* MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPrivate.h; sourceTree = ""; }; - 9DB2A66E192D19A60047A3EB /* MIKMIDIPrivateUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPrivateUtilities.h; sourceTree = ""; }; - 9DB2A66F192D19A60047A3EB /* MIKMIDIPrivateUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPrivateUtilities.m; sourceTree = ""; }; - 9DB2A670192D19A60047A3EB /* MIKMIDIResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIResponder.h; sourceTree = ""; }; - 9DB2A671192D19A60047A3EB /* MIKMIDISourceEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISourceEndpoint.h; sourceTree = ""; }; - 9DB2A672192D19A60047A3EB /* MIKMIDISourceEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISourceEndpoint.m; sourceTree = ""; }; - 9DB2A673192D19A60047A3EB /* MIKMIDISystemExclusiveCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISystemExclusiveCommand.h; sourceTree = ""; }; - 9DB2A674192D19A60047A3EB /* MIKMIDISystemExclusiveCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISystemExclusiveCommand.m; sourceTree = ""; }; - 9DB2A675192D19A60047A3EB /* MIKMIDISystemMessageCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISystemMessageCommand.h; sourceTree = ""; }; - 9DB2A676192D19A60047A3EB /* MIKMIDISystemMessageCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISystemMessageCommand.m; sourceTree = ""; }; - 9DB2A677192D19A60047A3EB /* MIKMIDIUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIUtilities.h; sourceTree = ""; }; - 9DB2A678192D19A60047A3EB /* MIKMIDIUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIUtilities.m; sourceTree = ""; }; - 9DB2A679192D19A60047A3EB /* NSUIApplication+MIKMIDI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUIApplication+MIKMIDI.h"; sourceTree = ""; }; - 9DB2A67A192D19A60047A3EB /* NSUIApplication+MIKMIDI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUIApplication+MIKMIDI.m"; sourceTree = ""; }; - 9DB2A695192D19D30047A3EB /* MIKMIDISequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequence.h; sourceTree = ""; }; - 9DB2A696192D19D30047A3EB /* MIKMIDISequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequence.m; sourceTree = ""; }; - 9DB2A699192D1BE30047A3EB /* MIKMIDITrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITrack.h; sourceTree = ""; }; - 9DB2A69A192D1BE30047A3EB /* MIKMIDITrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDITrack.m; sourceTree = ""; }; - F263C02C192D2A0C00F1B297 /* MIKMIDIEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEvent.h; sourceTree = ""; }; - F263C02D192D2A0C00F1B297 /* MIKMIDIEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEvent.m; sourceTree = ""; }; - F263C02F192D547B00F1B297 /* MIKMIDINoteEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDINoteEvent.h; sourceTree = ""; }; - F263C030192D547B00F1B297 /* MIKMIDINoteEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDINoteEvent.m; sourceTree = ""; }; - F263C032192D62A600F1B297 /* MIKMIDIEvent_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEvent_SubclassMethods.h; sourceTree = ""; }; - F263C09D192E8F1C00F1B297 /* MIKMIDIMetaEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaEvent.h; sourceTree = ""; }; - F263C09E192E8F1C00F1B297 /* MIKMIDIMetaEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaEvent.m; sourceTree = ""; }; - F263C0A0192E8FC900F1B297 /* MIKMIDIMetaSequenceEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaSequenceEvent.h; sourceTree = ""; }; - F263C0A1192E8FC900F1B297 /* MIKMIDIMetaSequenceEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaSequenceEvent.m; sourceTree = ""; }; - F263C0A3192EBDDE00F1B297 /* MIKMIDIMetaTextEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaTextEvent.h; sourceTree = ""; }; - F263C0A4192EBDDE00F1B297 /* MIKMIDIMetaTextEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaTextEvent.m; sourceTree = ""; }; - F263C0A6192EC14800F1B297 /* MIKMIDIMetaTimeSignatureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaTimeSignatureEvent.h; sourceTree = ""; }; - F263C0A7192EC14800F1B297 /* MIKMIDIMetaTimeSignatureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaTimeSignatureEvent.m; sourceTree = ""; }; - F263C0A9192EC5D200F1B297 /* MIKMIDIMetaLyricEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaLyricEvent.h; sourceTree = ""; }; - F263C0AA192EC5D200F1B297 /* MIKMIDIMetaLyricEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaLyricEvent.m; sourceTree = ""; }; - F263C0AC192ED5AF00F1B297 /* MIKMIDIMetaCopyrightEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaCopyrightEvent.h; sourceTree = ""; }; - F263C0AD192ED5AF00F1B297 /* MIKMIDIMetaCopyrightEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaCopyrightEvent.m; sourceTree = ""; }; - F263C0AF192ED69D00F1B297 /* MIKMIDIMetaTrackSequenceNameEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaTrackSequenceNameEvent.h; sourceTree = ""; }; - F263C0B0192ED69D00F1B297 /* MIKMIDIMetaTrackSequenceNameEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaTrackSequenceNameEvent.m; sourceTree = ""; }; - F263C0B2192ED6B800F1B297 /* MIKMIDIMetaMarkerTextEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaMarkerTextEvent.h; sourceTree = ""; }; - F263C0B3192ED6B800F1B297 /* MIKMIDIMetaMarkerTextEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaMarkerTextEvent.m; sourceTree = ""; }; - F263C0B5192ED6D300F1B297 /* MIKMIDIMetaInstrumentNameEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaInstrumentNameEvent.h; sourceTree = ""; }; - F263C0B6192ED6D300F1B297 /* MIKMIDIMetaInstrumentNameEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaInstrumentNameEvent.m; sourceTree = ""; }; - F263C0B8192ED6F200F1B297 /* MIKMIDIMetaCuePointEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaCuePointEvent.h; sourceTree = ""; }; - F263C0B9192ED6F200F1B297 /* MIKMIDIMetaCuePointEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaCuePointEvent.m; sourceTree = ""; }; - F263C0C1192FC81C00F1B297 /* MIKMIDIMetaKeySignatureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaKeySignatureEvent.h; sourceTree = ""; }; - F263C0C2192FC81C00F1B297 /* MIKMIDIMetaKeySignatureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaKeySignatureEvent.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -194,71 +71,28 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9D54C5021A97E5D20050BB43 /* MIKMIDI.framework in Frameworks */, 9DB2A5F7192D184D0047A3EB /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 9DB2A611192D184D0047A3EB /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 9DB2A617192D184D0047A3EB /* Cocoa.framework in Frameworks */, - 9DB2A616192D184D0047A3EB /* XCTest.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 9D3A2FBC192E670E00314B49 /* MIDI Events */ = { + 9D54C4FA1A97E5C90050BB43 /* Products */ = { isa = PBXGroup; children = ( - F263C02C192D2A0C00F1B297 /* MIKMIDIEvent.h */, - F263C032192D62A600F1B297 /* MIKMIDIEvent_SubclassMethods.h */, - F263C02D192D2A0C00F1B297 /* MIKMIDIEvent.m */, - F263C02F192D547B00F1B297 /* MIKMIDINoteEvent.h */, - F263C030192D547B00F1B297 /* MIKMIDINoteEvent.m */, - 9D3A2FBD192E85B800314B49 /* MIKMIDITempoEvent.h */, - 9D3A2FBE192E85B800314B49 /* MIKMIDITempoEvent.m */, - 9D5821CA192FA6E3000A89B5 /* Meta Event Subclasses */, + 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */, + 9D54C5011A97E5C90050BB43 /* MIKMIDI.framework */, ); - name = "MIDI Events"; - sourceTree = ""; - }; - 9D5821CA192FA6E3000A89B5 /* Meta Event Subclasses */ = { - isa = PBXGroup; - children = ( - F263C09D192E8F1C00F1B297 /* MIKMIDIMetaEvent.h */, - F263C09E192E8F1C00F1B297 /* MIKMIDIMetaEvent.m */, - F263C0A0192E8FC900F1B297 /* MIKMIDIMetaSequenceEvent.h */, - F263C0A1192E8FC900F1B297 /* MIKMIDIMetaSequenceEvent.m */, - F263C0A3192EBDDE00F1B297 /* MIKMIDIMetaTextEvent.h */, - F263C0A4192EBDDE00F1B297 /* MIKMIDIMetaTextEvent.m */, - F263C0A6192EC14800F1B297 /* MIKMIDIMetaTimeSignatureEvent.h */, - F263C0A7192EC14800F1B297 /* MIKMIDIMetaTimeSignatureEvent.m */, - F263C0A9192EC5D200F1B297 /* MIKMIDIMetaLyricEvent.h */, - F263C0AA192EC5D200F1B297 /* MIKMIDIMetaLyricEvent.m */, - F263C0AC192ED5AF00F1B297 /* MIKMIDIMetaCopyrightEvent.h */, - F263C0AD192ED5AF00F1B297 /* MIKMIDIMetaCopyrightEvent.m */, - F263C0AF192ED69D00F1B297 /* MIKMIDIMetaTrackSequenceNameEvent.h */, - F263C0B0192ED69D00F1B297 /* MIKMIDIMetaTrackSequenceNameEvent.m */, - F263C0B2192ED6B800F1B297 /* MIKMIDIMetaMarkerTextEvent.h */, - F263C0B3192ED6B800F1B297 /* MIKMIDIMetaMarkerTextEvent.m */, - F263C0B5192ED6D300F1B297 /* MIKMIDIMetaInstrumentNameEvent.h */, - F263C0B6192ED6D300F1B297 /* MIKMIDIMetaInstrumentNameEvent.m */, - F263C0B8192ED6F200F1B297 /* MIKMIDIMetaCuePointEvent.h */, - F263C0B9192ED6F200F1B297 /* MIKMIDIMetaCuePointEvent.m */, - F263C0C1192FC81C00F1B297 /* MIKMIDIMetaKeySignatureEvent.h */, - F263C0C2192FC81C00F1B297 /* MIKMIDIMetaKeySignatureEvent.m */, - ); - name = "Meta Event Subclasses"; + name = Products; sourceTree = ""; }; 9DB2A5EA192D184D0047A3EB = { isa = PBXGroup; children = ( + 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */, 9DB2A62A192D18970047A3EB /* Source */, - 9DB2A63F192D19A60047A3EB /* MIKMIDI */, 9DB2A631192D189E0047A3EB /* Resources */, 9DB2A61A192D184D0047A3EB /* MIDI Files TestbedTests */, 9DB2A5F5192D184D0047A3EB /* Frameworks */, @@ -270,7 +104,6 @@ isa = PBXGroup; children = ( 9DB2A5F3192D184D0047A3EB /* MIDI Files Testbed.app */, - 9DB2A614192D184D0047A3EB /* MIDI Files TestbedTests.xctest */, ); name = Products; sourceTree = ""; @@ -338,86 +171,6 @@ path = Resources; sourceTree = ""; }; - 9DB2A63F192D19A60047A3EB /* MIKMIDI */ = { - isa = PBXGroup; - children = ( - 9DB2A640192D19A60047A3EB /* MIKMIDI.h */, - 9DB2A698192D19DB0047A3EB /* MIDI Files */, - 9DB2A641192D19A60047A3EB /* MIKMIDIChannelVoiceCommand.h */, - 9DB2A642192D19A60047A3EB /* MIKMIDIChannelVoiceCommand.m */, - 9DB2A643192D19A60047A3EB /* MIKMIDIChannelVoiceCommand_SubclassMethods.h */, - 9DB2A644192D19A60047A3EB /* MIKMIDICommand.h */, - 9DB2A645192D19A60047A3EB /* MIKMIDICommand.m */, - 9DB2A646192D19A60047A3EB /* MIKMIDICommand_SubclassMethods.h */, - 9DB2A647192D19A60047A3EB /* MIKMIDICommandThrottler.h */, - 9DB2A648192D19A60047A3EB /* MIKMIDICommandThrottler.m */, - 9DB2A649192D19A60047A3EB /* MIKMIDIControlChangeCommand.h */, - 9DB2A64A192D19A60047A3EB /* MIKMIDIControlChangeCommand.m */, - 9DB2A64B192D19A60047A3EB /* MIKMIDIDestinationEndpoint.h */, - 9DB2A64C192D19A60047A3EB /* MIKMIDIDestinationEndpoint.m */, - 9DB2A64D192D19A60047A3EB /* MIKMIDIDevice.h */, - 9DB2A64E192D19A60047A3EB /* MIKMIDIDevice.m */, - 9DB2A64F192D19A60047A3EB /* MIKMIDIDeviceManager.h */, - 9DB2A650192D19A60047A3EB /* MIKMIDIDeviceManager.m */, - 9DB2A651192D19A60047A3EB /* MIKMIDIEndpoint.h */, - 9DB2A652192D19A60047A3EB /* MIKMIDIEndpoint.m */, - 9DB2A653192D19A60047A3EB /* MIKMIDIEntity.h */, - 9DB2A654192D19A60047A3EB /* MIKMIDIEntity.m */, - 9DB2A655192D19A60047A3EB /* MIKMIDIErrors.h */, - 9DB2A656192D19A60047A3EB /* MIKMIDIErrors.m */, - 9DB2A657192D19A60047A3EB /* MIKMIDIInputPort.h */, - 9DB2A658192D19A60047A3EB /* MIKMIDIInputPort.m */, - 9DB2A659192D19A60047A3EB /* MIKMIDIMapping.h */, - 9DB2A65A192D19A60047A3EB /* MIKMIDIMapping.m */, - 9DB2A65B192D19A60047A3EB /* MIKMIDIMappingGenerator.h */, - 9DB2A65C192D19A60047A3EB /* MIKMIDIMappingGenerator.m */, - 9DB2A65D192D19A60047A3EB /* MIKMIDIMappingManager.h */, - 9DB2A65E192D19A60047A3EB /* MIKMIDIMappingManager.m */, - 9DB2A65F192D19A60047A3EB /* MIKMIDIMappingXMLParser.h */, - 9DB2A660192D19A60047A3EB /* MIKMIDIMappingXMLParser.m */, - 9DB2A661192D19A60047A3EB /* MIKMIDINoteOffCommand.h */, - 9DB2A662192D19A60047A3EB /* MIKMIDINoteOffCommand.m */, - 9DB2A663192D19A60047A3EB /* MIKMIDINoteOnCommand.h */, - 9DB2A664192D19A60047A3EB /* MIKMIDINoteOnCommand.m */, - 9DB2A665192D19A60047A3EB /* MIKMIDIObject.h */, - 9DB2A666192D19A60047A3EB /* MIKMIDIObject.m */, - 9DB2A667192D19A60047A3EB /* MIKMIDIObject_SubclassMethods.h */, - 9DB2A668192D19A60047A3EB /* MIKMIDIOutputPort.h */, - 9DB2A669192D19A60047A3EB /* MIKMIDIOutputPort.m */, - 9DB2A66A192D19A60047A3EB /* MIKMIDIPort.h */, - 9DB2A66B192D19A60047A3EB /* MIKMIDIPort.m */, - 9DB2A66C192D19A60047A3EB /* MIKMIDIPort_SubclassMethods.h */, - 9DB2A66D192D19A60047A3EB /* MIKMIDIPrivate.h */, - 9DB2A66E192D19A60047A3EB /* MIKMIDIPrivateUtilities.h */, - 9DB2A66F192D19A60047A3EB /* MIKMIDIPrivateUtilities.m */, - 9DB2A670192D19A60047A3EB /* MIKMIDIResponder.h */, - 9DB2A671192D19A60047A3EB /* MIKMIDISourceEndpoint.h */, - 9DB2A672192D19A60047A3EB /* MIKMIDISourceEndpoint.m */, - 9DB2A673192D19A60047A3EB /* MIKMIDISystemExclusiveCommand.h */, - 9DB2A674192D19A60047A3EB /* MIKMIDISystemExclusiveCommand.m */, - 9DB2A675192D19A60047A3EB /* MIKMIDISystemMessageCommand.h */, - 9DB2A676192D19A60047A3EB /* MIKMIDISystemMessageCommand.m */, - 9DB2A677192D19A60047A3EB /* MIKMIDIUtilities.h */, - 9DB2A678192D19A60047A3EB /* MIKMIDIUtilities.m */, - 9DB2A679192D19A60047A3EB /* NSUIApplication+MIKMIDI.h */, - 9DB2A67A192D19A60047A3EB /* NSUIApplication+MIKMIDI.m */, - ); - name = MIKMIDI; - path = ../../Source; - sourceTree = ""; - }; - 9DB2A698192D19DB0047A3EB /* MIDI Files */ = { - isa = PBXGroup; - children = ( - 9DB2A695192D19D30047A3EB /* MIKMIDISequence.h */, - 9DB2A696192D19D30047A3EB /* MIKMIDISequence.m */, - 9DB2A699192D1BE30047A3EB /* MIKMIDITrack.h */, - 9DB2A69A192D1BE30047A3EB /* MIKMIDITrack.m */, - 9D3A2FBC192E670E00314B49 /* MIDI Events */, - ); - name = "MIDI Files"; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -432,30 +185,13 @@ buildRules = ( ); dependencies = ( + 9D54C5041A97E5E90050BB43 /* PBXTargetDependency */, ); name = "MIDI Files Testbed"; productName = "MIDI Files Testbed"; productReference = 9DB2A5F3192D184D0047A3EB /* MIDI Files Testbed.app */; productType = "com.apple.product-type.application"; }; - 9DB2A613192D184D0047A3EB /* MIDI Files TestbedTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 9DB2A627192D184D0047A3EB /* Build configuration list for PBXNativeTarget "MIDI Files TestbedTests" */; - buildPhases = ( - 9DB2A610192D184D0047A3EB /* Sources */, - 9DB2A611192D184D0047A3EB /* Frameworks */, - 9DB2A612192D184D0047A3EB /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 9DB2A619192D184D0047A3EB /* PBXTargetDependency */, - ); - name = "MIDI Files TestbedTests"; - productName = "MIDI Files TestbedTests"; - productReference = 9DB2A614192D184D0047A3EB /* MIDI Files TestbedTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -465,11 +201,6 @@ CLASSPREFIX = MIK; LastUpgradeCheck = 0510; ORGANIZATIONNAME = "Mixed In Key"; - TargetAttributes = { - 9DB2A613192D184D0047A3EB = { - TestTargetID = 9DB2A5F2192D184D0047A3EB; - }; - }; }; buildConfigurationList = 9DB2A5EE192D184D0047A3EB /* Build configuration list for PBXProject "MIDI Files Testbed" */; compatibilityVersion = "Xcode 3.2"; @@ -482,14 +213,36 @@ mainGroup = 9DB2A5EA192D184D0047A3EB; productRefGroup = 9DB2A5F4192D184D0047A3EB /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 9D54C4FA1A97E5C90050BB43 /* Products */; + ProjectRef = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; + }, + ); projectRoot = ""; targets = ( 9DB2A5F2192D184D0047A3EB /* MIDI Files Testbed */, - 9DB2A613192D184D0047A3EB /* MIDI Files TestbedTests */, ); }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MIKMIDI.framework; + remoteRef = 9D54C4FE1A97E5C90050BB43 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 9D54C5011A97E5C90050BB43 /* MIKMIDI.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MIKMIDI.framework; + remoteRef = 9D54C5001A97E5C90050BB43 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ 9DB2A5F1192D184D0047A3EB /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -502,14 +255,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 9DB2A612192D184D0047A3EB /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9DB2A61F192D184D0047A3EB /* InfoPlist.strings in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -517,69 +262,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F263C09F192E8F1C00F1B297 /* MIKMIDIMetaEvent.m in Sources */, - 9DB2A690192D19A60047A3EB /* MIKMIDISourceEndpoint.m in Sources */, - F263C0B1192ED69D00F1B297 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, - 9DB2A67E192D19A60047A3EB /* MIKMIDIControlChangeCommand.m in Sources */, - 9DB2A68F192D19A60047A3EB /* MIKMIDIPrivateUtilities.m in Sources */, - 9DB2A68C192D19A60047A3EB /* MIKMIDIObject.m in Sources */, - F263C0B4192ED6B800F1B297 /* MIKMIDIMetaMarkerTextEvent.m in Sources */, - 9DB2A67B192D19A60047A3EB /* MIKMIDIChannelVoiceCommand.m in Sources */, - 9DB2A67F192D19A60047A3EB /* MIKMIDIDestinationEndpoint.m in Sources */, - 9DB2A684192D19A60047A3EB /* MIKMIDIErrors.m in Sources */, - F263C0BA192ED6F200F1B297 /* MIKMIDIMetaCuePointEvent.m in Sources */, - 9DB2A68A192D19A60047A3EB /* MIKMIDINoteOffCommand.m in Sources */, - 9DB2A681192D19A60047A3EB /* MIKMIDIDeviceManager.m in Sources */, - 9DB2A67D192D19A60047A3EB /* MIKMIDICommandThrottler.m in Sources */, - F263C0A8192EC14800F1B297 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */, 9DB2A630192D18970047A3EB /* MIKAppDelegate.m in Sources */, - F263C0A2192E8FC900F1B297 /* MIKMIDIMetaSequenceEvent.m in Sources */, - F263C0B7192ED6D300F1B297 /* MIKMIDIMetaInstrumentNameEvent.m in Sources */, - 9DB2A67C192D19A60047A3EB /* MIKMIDICommand.m in Sources */, - 9DB2A689192D19A60047A3EB /* MIKMIDIMappingXMLParser.m in Sources */, - 9DB2A685192D19A60047A3EB /* MIKMIDIInputPort.m in Sources */, - 9DB2A693192D19A60047A3EB /* MIKMIDIUtilities.m in Sources */, - 9DB2A686192D19A60047A3EB /* MIKMIDIMapping.m in Sources */, - 9DB2A680192D19A60047A3EB /* MIKMIDIDevice.m in Sources */, - 9DB2A697192D19D30047A3EB /* MIKMIDISequence.m in Sources */, 9DAE7D37192FC1B800B25DD7 /* MIKMIDISequenceView.m in Sources */, - F263C0C3192FC81C00F1B297 /* MIKMIDIMetaKeySignatureEvent.m in Sources */, - F263C02E192D2A0C00F1B297 /* MIKMIDIEvent.m in Sources */, - 9DB2A68D192D19A60047A3EB /* MIKMIDIOutputPort.m in Sources */, - 9DB2A692192D19A60047A3EB /* MIKMIDISystemMessageCommand.m in Sources */, 9DB2A62F192D18970047A3EB /* main.m in Sources */, - 9DB2A691192D19A60047A3EB /* MIKMIDISystemExclusiveCommand.m in Sources */, - F263C0AE192ED5AF00F1B297 /* MIKMIDIMetaCopyrightEvent.m in Sources */, - F263C0A5192EBDDE00F1B297 /* MIKMIDIMetaTextEvent.m in Sources */, - 9DB2A69B192D1BE30047A3EB /* MIKMIDITrack.m in Sources */, - 9DB2A683192D19A60047A3EB /* MIKMIDIEntity.m in Sources */, - 9DB2A694192D19A60047A3EB /* NSUIApplication+MIKMIDI.m in Sources */, - 9DB2A688192D19A60047A3EB /* MIKMIDIMappingManager.m in Sources */, - 9DB2A687192D19A60047A3EB /* MIKMIDIMappingGenerator.m in Sources */, - F263C0AB192EC5D200F1B297 /* MIKMIDIMetaLyricEvent.m in Sources */, - F263C031192D547B00F1B297 /* MIKMIDINoteEvent.m in Sources */, - 9D3A2FBF192E85B800314B49 /* MIKMIDITempoEvent.m in Sources */, - 9DB2A68E192D19A60047A3EB /* MIKMIDIPort.m in Sources */, - 9DB2A682192D19A60047A3EB /* MIKMIDIEndpoint.m in Sources */, - 9DB2A68B192D19A60047A3EB /* MIKMIDINoteOnCommand.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 9DB2A610192D184D0047A3EB /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9DB2A621192D184D0047A3EB /* MIDI_Files_TestbedTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 9DB2A619192D184D0047A3EB /* PBXTargetDependency */ = { + 9D54C5041A97E5E90050BB43 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 9DB2A5F2192D184D0047A3EB /* MIDI Files Testbed */; - targetProxy = 9DB2A618192D184D0047A3EB /* PBXContainerItemProxy */; + name = MIKMIDI; + targetProxy = 9D54C5031A97E5E90050BB43 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -715,46 +410,6 @@ }; name = Release; }; - 9DB2A628192D184D0047A3EB /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MIDI Files Testbed.app/Contents/MacOS/MIDI Files Testbed"; - COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MIDI Files Testbed/MIDI Files Testbed-Prefix.pch"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = "MIDI Files TestbedTests/MIDI Files TestbedTests-Info.plist"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; - }; - name = Debug; - }; - 9DB2A629192D184D0047A3EB /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/MIDI Files Testbed.app/Contents/MacOS/MIDI Files Testbed"; - COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(DEVELOPER_FRAMEWORKS_DIR)", - "$(inherited)", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "MIDI Files Testbed/MIDI Files Testbed-Prefix.pch"; - INFOPLIST_FILE = "MIDI Files TestbedTests/MIDI Files TestbedTests-Info.plist"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -776,15 +431,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9DB2A627192D184D0047A3EB /* Build configuration list for PBXNativeTarget "MIDI Files TestbedTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 9DB2A628192D184D0047A3EB /* Debug */, - 9DB2A629192D184D0047A3EB /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 9DB2A5EB192D184D0047A3EB /* Project object */; diff --git a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m index e65bba03..e2a96f87 100644 --- a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m +++ b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m @@ -7,7 +7,7 @@ // #import "MIKAppDelegate.h" -#import "MIKMIDISequence.h" +#import @implementation MIKAppDelegate diff --git a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m index fc73b68b..937f6821 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m +++ b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m @@ -7,10 +7,7 @@ // #import "MIKMIDISequenceView.h" -#import "MIKMIDISequence.h" -#import "MIKMIDITrack.h" -#import "MIKMIDIEvent.h" -#import "MIKMIDINoteEvent.h" +#import @interface MIKMIDISequenceView () @@ -50,7 +47,7 @@ - (void)drawRect:(NSRect)dirtyRect [noteColor setFill]; CGFloat yPosition = NSMinY([self bounds]) + note.note * [self pixelsPerNote]; - NSRect noteRect = NSMakeRect(NSMinX([self bounds]) + note.musicTimeStamp * ppt, yPosition, note.duration * ppt, noteHeight); + NSRect noteRect = NSMakeRect(NSMinX([self bounds]) + note.timeStamp * ppt, yPosition, note.duration * ppt, noteHeight); NSBezierPath *path = [NSBezierPath bezierPathWithRect:noteRect]; [path fill]; From caa52b57a4ace0455fd9e6714e867ab8123b434a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 20 Feb 2015 16:25:37 -0700 Subject: [PATCH 010/284] Added basic recording feature to MIDI Files Testbed. Not yet working (see Issue #45). --- .../project.pbxproj | 18 ++ .../Resources/Base.lproj/MainMenu.xib | 52 +----- .../Resources/Base.lproj/MainWindow.xib | 99 +++++++++++ .../Source/MIKAppDelegate.h | 10 +- .../Source/MIKAppDelegate.m | 35 +--- .../Source/MIKMainWindowController.h | 31 ++++ .../Source/MIKMainWindowController.m | 164 ++++++++++++++++++ 7 files changed, 324 insertions(+), 85 deletions(-) create mode 100644 Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib create mode 100644 Examples/MIDI Files Testbed/Source/MIKMainWindowController.h create mode 100644 Examples/MIDI Files Testbed/Source/MIKMainWindowController.m diff --git a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index 3c289fa4..7ab6f151 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 9D54C5021A97E5D20050BB43 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */; }; + 9D54C50B1A97E6F10050BB43 /* MIKMainWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D54C5091A97E6F10050BB43 /* MIKMainWindowController.m */; }; + 9D54C50F1A97E7070050BB43 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9D54C50D1A97E7070050BB43 /* MainWindow.xib */; }; 9DAE7D37192FC1B800B25DD7 /* MIKMIDISequenceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */; }; 9DB2A5F7192D184D0047A3EB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DB2A5F6192D184D0047A3EB /* Cocoa.framework */; }; 9DB2A62F192D18970047A3EB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A62B192D18970047A3EB /* main.m */; }; @@ -44,6 +46,9 @@ /* Begin PBXFileReference section */ 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MIKMIDI.xcodeproj; path = ../../Framework/MIKMIDI.xcodeproj; sourceTree = ""; }; + 9D54C5081A97E6F10050BB43 /* MIKMainWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMainWindowController.h; sourceTree = ""; }; + 9D54C5091A97E6F10050BB43 /* MIKMainWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMainWindowController.m; sourceTree = ""; }; + 9D54C50E1A97E7070050BB43 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainWindow.xib; sourceTree = ""; }; 9DAE7D35192FC1B800B25DD7 /* MIKMIDISequenceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequenceView.h; sourceTree = ""; }; 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequenceView.m; sourceTree = ""; }; 9DB2A5F3192D184D0047A3EB /* MIDI Files Testbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MIDI Files Testbed.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -155,6 +160,8 @@ 9DB2A62E192D18970047A3EB /* MIKAppDelegate.m */, 9DAE7D35192FC1B800B25DD7 /* MIKMIDISequenceView.h */, 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */, + 9D54C5081A97E6F10050BB43 /* MIKMainWindowController.h */, + 9D54C5091A97E6F10050BB43 /* MIKMainWindowController.m */, ); path = Source; sourceTree = ""; @@ -163,6 +170,7 @@ isa = PBXGroup; children = ( 9DB2A632192D189E0047A3EB /* MainMenu.xib */, + 9D54C50D1A97E7070050BB43 /* MainWindow.xib */, 9DB2A634192D189E0047A3EB /* Credits.rtf */, 9DB2A636192D189E0047A3EB /* InfoPlist.strings */, 9DB2A638192D189E0047A3EB /* Images.xcassets */, @@ -249,6 +257,7 @@ buildActionMask = 2147483647; files = ( 9DB2A63A192D189E0047A3EB /* MainMenu.xib in Resources */, + 9D54C50F1A97E7070050BB43 /* MainWindow.xib in Resources */, 9DB2A63D192D189E0047A3EB /* Images.xcassets in Resources */, 9DB2A63C192D189E0047A3EB /* InfoPlist.strings in Resources */, 9DB2A63B192D189E0047A3EB /* Credits.rtf in Resources */, @@ -264,6 +273,7 @@ files = ( 9DB2A630192D18970047A3EB /* MIKAppDelegate.m in Sources */, 9DAE7D37192FC1B800B25DD7 /* MIKMIDISequenceView.m in Sources */, + 9D54C50B1A97E6F10050BB43 /* MIKMainWindowController.m in Sources */, 9DB2A62F192D18970047A3EB /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -279,6 +289,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 9D54C50D1A97E7070050BB43 /* MainWindow.xib */ = { + isa = PBXVariantGroup; + children = ( + 9D54C50E1A97E7070050BB43 /* Base */, + ); + name = MainWindow.xib; + sourceTree = ""; + }; 9DB2A61D192D184D0047A3EB /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainMenu.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainMenu.xib index 9f5584f2..9c82086a 100644 --- a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainMenu.xib +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainMenu.xib @@ -1,7 +1,8 @@ - + - + + @@ -10,13 +11,8 @@ - - - - - - - + + @@ -667,43 +663,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib new file mode 100644 index 00000000..46e9a5bc --- /dev/null +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.h b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.h index c6f9cf26..2ccc6124 100644 --- a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.h +++ b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.h @@ -7,15 +7,11 @@ // #import -#import "MIKMIDISequenceView.h" -@class MIKMIDISequenceView; +@class MIKMainWindowController; -@interface MIKAppDelegate : NSObject +@interface MIKAppDelegate : NSObject -- (IBAction)loadFile:(id)sender; - -@property (assign) IBOutlet NSWindow *window; -@property (weak) IBOutlet MIKMIDISequenceView *trackView; +@property (nonatomic, strong) MIKMainWindowController *mainWindowController; @end diff --git a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m index e2a96f87..02d4a44e 100644 --- a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m +++ b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m @@ -7,41 +7,14 @@ // #import "MIKAppDelegate.h" -#import +#import "MIKMainWindowController.h" @implementation MIKAppDelegate -- (IBAction)loadFile:(id)sender +- (void)applicationDidFinishLaunching:(NSNotification *)notification { - NSOpenPanel *openPanel = [NSOpenPanel openPanel]; - [openPanel setAllowsMultipleSelection:NO]; - [openPanel setCanChooseDirectories:NO]; - [openPanel setAllowedFileTypes:@[@"mid", @"midi"]]; - [openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { - if (result != NSFileHandlingPanelOKButton) return; - [self loadMIDIFile:[[openPanel URL] path]]; - }]; -} - -#pragma mark - MIKMIDISequenceViewDelegate - -- (void)midiSequenceView:(MIKMIDISequenceView *)sequenceView receivedDroppedMIDIFiles:(NSArray *)midiFiles -{ - [self loadMIDIFile:[midiFiles firstObject]]; -} - -#pragma mark - Private - -- (void)loadMIDIFile:(NSString *)path -{ - NSError *error = nil; - MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:[NSURL fileURLWithPath:path] error:&error]; - if (!sequence) { - NSLog(@"Error loading MIDI file: %@", error); - } else { - NSLog(@"Loaded MIDI file: %@", sequence); - self.trackView.sequence = sequence; - } + self.mainWindowController = [MIKMainWindowController windowController]; + [self.mainWindowController showWindow:nil]; } @end diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.h b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.h new file mode 100644 index 00000000..4de5b9d8 --- /dev/null +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.h @@ -0,0 +1,31 @@ +// +// MIKMainWindowController.h +// MIDI Files Testbed +// +// Created by Andrew Madsen on 2/20/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import "MIKMIDISequenceView.h" + +@class MIKMIDIDeviceManager; +@class MIKMIDIDevice; + +@interface MIKMainWindowController : NSWindowController + ++ (instancetype)windowController; + +- (IBAction)loadFile:(id)sender; +- (IBAction)toggleRecording:(id)sender; + +@property (nonatomic, readonly) MIKMIDIDeviceManager *deviceManager; + +@property (nonatomic, strong) MIKMIDISequence *sequence; +@property (nonatomic, strong) MIKMIDIDevice *device; +@property (nonatomic, getter=isRecording, readonly) BOOL recording; + +@property (weak) IBOutlet MIKMIDISequenceView *trackView; +@property (nonatomic, readonly) NSString *recordButtonLabel; + +@end diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m new file mode 100644 index 00000000..f2d45fec --- /dev/null +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -0,0 +1,164 @@ +// +// MIKMainWindowController.m +// MIDI Files Testbed +// +// Created by Andrew Madsen on 2/20/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMainWindowController.h" +#import + +@interface MIKMainWindowController () + +@property (nonatomic, strong) MIKMIDISequencer *sequencer; + +@property (nonatomic, strong) id deviceConnectionToken; + +@end + +@implementation MIKMainWindowController + ++ (instancetype)windowController +{ + return [[self alloc] initWithWindowNibName:@"MainWindow"]; +} + +- (void)dealloc +{ + self.device = nil; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + + self.sequencer = [MIKMIDISequencer sequencer]; +} + +#pragma mark - Actions + +- (IBAction)loadFile:(id)sender +{ + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + [openPanel setAllowsMultipleSelection:NO]; + [openPanel setCanChooseDirectories:NO]; + [openPanel setAllowedFileTypes:@[@"mid", @"midi"]]; + [openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { + if (result != NSFileHandlingPanelOKButton) return; + [self loadMIDIFile:[[openPanel URL] path]]; + }]; +} + +- (IBAction)toggleRecording:(id)sender +{ + if (self.isRecording) { + [self.sequencer stop]; + [self.trackView setNeedsDisplay:YES]; + return; + } else { + if (!self.sequence) self.sequence = [MIKMIDISequence sequence]; + self.sequencer.recordEnabledTracks = [NSSet setWithObject:[self.sequence addTrack]]; + [self.sequencer startRecording]; + } +} + +#pragma mark - MIKMIDISequenceViewDelegate + +- (void)midiSequenceView:(MIKMIDISequenceView *)sequenceView receivedDroppedMIDIFiles:(NSArray *)midiFiles +{ + [self loadMIDIFile:[midiFiles firstObject]]; +} + +#pragma mark - Private + +- (void)loadMIDIFile:(NSString *)path +{ + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:[NSURL fileURLWithPath:path] error:&error]; + if (!sequence) { + NSLog(@"Error loading MIDI file: %@", error); + return; + } + self.sequence = sequence; +} + +#pragma mark Device Connection/Disconnection + +- (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error +{ + error = error ? error : &(NSError *__autoreleasing){ nil }; + MIKMIDISourceEndpoint *source = [[[device.entities firstObject] sources] firstObject]; + if (!source) { + *error = [NSError MIKMIDIErrorWithCode:MIKMIDIUnknownErrorCode userInfo:nil]; + return NO; + } + + MIKMIDIDeviceManager *manager = [MIKMIDIDeviceManager sharedDeviceManager]; + self.deviceConnectionToken = [manager connectInput:source error:error eventHandler:^(MIKMIDISourceEndpoint *source, NSArray *commands) { + for (MIKMIDICommand *command in commands) { + if (self.isRecording) [self.sequencer recordMIDICommand:command]; + } + }]; + return self.deviceConnectionToken != nil; +} + +- (void)disconnectFromDevice +{ + if (!self.deviceConnectionToken) return; + + MIKMIDISourceEndpoint *source = [[[self.device.entities firstObject] sources] firstObject]; + if (!source) return; + [[MIKMIDIDeviceManager sharedDeviceManager] disconnectInput:source forConnectionToken:self.deviceConnectionToken]; + self.deviceConnectionToken = nil; +} + +#pragma mark - Properties + +- (MIKMIDIDeviceManager *)deviceManager { return [MIKMIDIDeviceManager sharedDeviceManager]; } + +- (void)setSequence:(MIKMIDISequence *)sequence +{ + if (sequence != _sequence) { + _sequence = sequence; + self.trackView.sequence = sequence; + self.sequencer.sequence = sequence; + } +} + +- (void)setDevice:(MIKMIDIDevice *)device +{ + if (device != _device) { + [self disconnectFromDevice]; + + NSError *error = nil; + if (![self connectToDevice:device error:&error]) { + [self presentError:error]; + _device = nil; + } else { + _device = device; + } + } +} + ++ (NSSet *)keyPathsForValuesAffectingRecording +{ + return [NSSet setWithObjects:@"sequencer.recording", nil]; +} + +- (BOOL)isRecording +{ + return self.sequencer.isRecording; +} + ++ (NSSet *)keyPathsForValuesAffectingRecordButtonLabel +{ + return [NSSet setWithObjects:@"recording", nil]; +} + +- (NSString *)recordButtonLabel +{ + return self.isRecording ? @"Stop" : @"Record"; +} + +@end From 670d33813f318bc464b73baefee76731ec0f2983 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 20 Feb 2015 16:36:29 -0700 Subject: [PATCH 011/284] Record button in MIDI Files Testbed now changes to "Stop" during recording. --- .../MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index 46e9a5bc..48cae48b 100644 --- a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -60,9 +60,9 @@ + - From b4c305eec49a920326f923be629d85ae92584da1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 20 Feb 2015 16:37:31 -0700 Subject: [PATCH 012/284] Issue #45: MIKMIDISequencer continues recording until -stop is called. --- Source/MIKMIDISequencer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 87ae4458..b7df4162 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -256,7 +256,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam [self updateClockWithMusicTimeStamp:loopStartTimeStamp tempo:tempo atMIDITimeStamp:loopStartMIDITimeStamp]; [self processSequenceStartingFromMIDITimeStamp:loopStartMIDITimeStamp]; } - } else { + } else if (!self.isRecording) { // Don't stop automatically during recording MIDITimeStamp systemTimeStamp = MIKMIDIGetCurrentTimeStamp(); if ((systemTimeStamp > lastProcessedMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= sequence.length + playbackOffset)) { [self stop]; From f881807331ac0bf7b014e86413cce68c98ff24a0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 21 Feb 2015 12:46:17 -0700 Subject: [PATCH 013/284] Issue #47: Added -loadSoundfontFromFileAtURL:error: to MIKMIDISynthesizer. --- Source/MIKMIDISynthesizer.h | 10 +++++++ Source/MIKMIDISynthesizer.m | 56 ++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 1e94c19a..45a40a90 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -58,6 +58,16 @@ */ - (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument; +/** + * Loads the sound font (.dls or .sf2) file at fileURL. + * + * @param fileURL A fileURL for a .dls or .sf2 file. + * @param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, you may pass in NULL. + * + * @return YES if loading the sound font file was succesful, NO if an error occurred. + */ +- (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error; + + (AudioComponentDescription)appleSynthComponentDescription; // methods for property 'componentDescription' diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 4e6943f5..6c2e45da 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -1,6 +1,6 @@ // // MIKMIDISynthesizer.m -// +// // // Created by Andrew Madsen on 2/19/15. // @@ -71,6 +71,60 @@ - (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument; return YES; } +- (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error +{ + error = error ? error : &(NSError *__autoreleasing){ nil }; + OSStatus err = noErr; + + if (self.componentDescription.componentSubType == kAudioUnitSubType_Sampler) { + // fill out a bank preset data structure + NSDictionary *typesByFileExtension = @{@"sf2" : @(kInstrumentType_SF2Preset), + @"dls" : @(kInstrumentType_DLSPreset), + @"aupreset" : @(kInstrumentType_AUPreset)}; + AUSamplerInstrumentData instrumentData; + instrumentData.fileURL = (__bridge CFURLRef)fileURL; + instrumentData.instrumentType = [typesByFileExtension[[fileURL pathExtension]] intValue]; + instrumentData.bankMSB = kAUSampler_DefaultMelodicBankMSB; + instrumentData.bankLSB = kAUSampler_DefaultBankLSB; + instrumentData.presetID = 0; + + // set the kAUSamplerProperty_LoadPresetFromBank property + err = AudioUnitSetProperty(self.instrument, + kAUSamplerProperty_LoadInstrument, + kAudioUnitScope_Global, + 0, + &instrumentData, + sizeof(instrumentData)); + + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + return YES; + } else { +#if TARGET_OS_IPHONE + return NO; + } +#else + FSRef fsRef; + err = FSPathMakeRef((const UInt8*)[[fileURL path] cStringUsingEncoding:NSUTF8StringEncoding], &fsRef, 0); + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + + err = AudioUnitSetProperty(self.instrument, + kMusicDeviceProperty_SoundBankFSRef, + kAudioUnitScope_Global, 0, + &fsRef, sizeof(fsRef)); + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + return YES; +#endif + } +} #pragma mark - Private From b16a0f163525d410265318c9d35879eacde3d71d Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 21 Feb 2015 12:50:57 -0700 Subject: [PATCH 014/284] Issue #47: Exposed MIKMIDISequencer's builtin/default synthesizer publicly (read-only). --- Source/MIKMIDISequencer.h | 8 ++++++++ Source/MIKMIDISequencer.m | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 522efcb4..47f8eed6 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -14,6 +14,7 @@ @class MIKMIDIMetronome; @class MIKMIDICommand; @class MIKMIDIDestinationEndpoint; +@class MIKMIDISynthesizer; /** * Types of click track statuses, that determine when the click track will be audible. @@ -261,6 +262,13 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (nonatomic) MusicTimeStamp loopEndTimeStamp; +/** + * The synthesizer the receiver will use to synthesize MIDI during playback + * for any tracks whose MIDI has not been routed to a custom endpoint using + * -setDestinationEndpoint:forTrack:. + */ +@property (nonatomic, strong, readonly) MIKMIDISynthesizer *builtinSynthesizer; + /** * The metronome to send click track events to. */ diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b7df4162..cdccab1e 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -76,7 +76,6 @@ @interface MIKMIDISequencer () @property (nonatomic, strong) MIKMIDIClientDestinationEndpoint *metronomeEndpoint; @property (nonatomic, strong, readonly) MIKMIDIClientDestinationEndpoint *builtinEndpoint; -@property (nonatomic, strong, readonly) MIKMIDISynthesizer *builtinSynthesizer; @end From 657f28e3e81c5607ea47ba0f159a8400523c31b6 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 21 Feb 2015 12:51:48 -0700 Subject: [PATCH 015/284] MIDI Files Testbed can now playback its sequence. --- .../Resources/Base.lproj/MainWindow.xib | 27 +++++++++++++++++ .../Source/MIKMainWindowController.h | 3 ++ .../Source/MIKMainWindowController.m | 30 ++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index 48cae48b..624a825e 100644 --- a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -81,14 +81,41 @@ + + + diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.h b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.h index 4de5b9d8..f71a492e 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.h +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.h @@ -18,14 +18,17 @@ - (IBAction)loadFile:(id)sender; - (IBAction)toggleRecording:(id)sender; +- (IBAction)togglePlayback:(id)sender; @property (nonatomic, readonly) MIKMIDIDeviceManager *deviceManager; @property (nonatomic, strong) MIKMIDISequence *sequence; @property (nonatomic, strong) MIKMIDIDevice *device; +@property (nonatomic, getter=isPlaying, readonly) BOOL playing; @property (nonatomic, getter=isRecording, readonly) BOOL recording; @property (weak) IBOutlet MIKMIDISequenceView *trackView; @property (nonatomic, readonly) NSString *recordButtonLabel; +@property (nonatomic, readonly) NSString *playButtonLabel; @end diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m index f2d45fec..e33a2ef5 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -12,6 +12,7 @@ @interface MIKMainWindowController () @property (nonatomic, strong) MIKMIDISequencer *sequencer; +@property (nonatomic, strong) MIKMIDIEndpointSynthesizer *endpointSynth; @property (nonatomic, strong) id deviceConnectionToken; @@ -63,6 +64,11 @@ - (IBAction)toggleRecording:(id)sender } } +- (IBAction)togglePlayback:(id)sender +{ + self.isPlaying ? [self.sequencer stop] : [self.sequencer startPlayback]; +} + #pragma mark - MIKMIDISequenceViewDelegate - (void)midiSequenceView:(MIKMIDISequenceView *)sequenceView receivedDroppedMIDIFiles:(NSArray *)midiFiles @@ -100,7 +106,9 @@ - (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error if (self.isRecording) [self.sequencer recordMIDICommand:command]; } }]; - return self.deviceConnectionToken != nil; + if (!self.deviceConnectionToken) return NO; + + return YES; } - (void)disconnectFromDevice @@ -141,6 +149,16 @@ - (void)setDevice:(MIKMIDIDevice *)device } } ++ (NSSet *)keyPathsForValuesAffectingPlaying +{ + return [NSSet setWithObjects:@"sequencer.playing", nil]; +} + +- (BOOL)isPlaying +{ + return self.sequencer.isPlaying; +} + + (NSSet *)keyPathsForValuesAffectingRecording { return [NSSet setWithObjects:@"sequencer.recording", nil]; @@ -161,4 +179,14 @@ - (NSString *)recordButtonLabel return self.isRecording ? @"Stop" : @"Record"; } ++ (NSSet *)keyPathsForValuesAffectingPlayButtonLabel +{ + return [NSSet setWithObjects:@"playing", nil]; +} + +- (NSString *)playButtonLabel +{ + return self.isPlaying ? @"Stop" : @"Play"; +} + @end From 8ff369a03d8403e801c10476b9b9c7ccb281b4ed Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 21 Feb 2015 12:52:23 -0700 Subject: [PATCH 016/284] MIDI Files Testbed creates a synthesizer upon device connection so e.g. a connected MIDI keyboard makes sound. --- Examples/MIDI Files Testbed/Source/MIKMainWindowController.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m index e33a2ef5..8d2f6ec3 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -108,6 +108,9 @@ - (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error }]; if (!self.deviceConnectionToken) return NO; + // So audio can be heard + self.endpointSynth = [[MIKMIDIEndpointSynthesizer alloc] initWithMIDISource:source]; + return YES; } From d8ffbb3adbe4c1f51dcbd9615e2b6bfa04b28581 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 21 Feb 2015 12:53:53 -0700 Subject: [PATCH 017/284] Fixed bad closing brace that caused build error on iOS. --- Source/MIKMIDISynthesizer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 6c2e45da..0e3144e7 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -122,8 +122,8 @@ - (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error return NO; } return YES; -#endif } +#endif } #pragma mark - Private From 2f99a3a111cc2b16a7be0ed7403f23712e102c72 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 22 Feb 2015 21:27:17 -0700 Subject: [PATCH 018/284] Issue #27 deprecated -[MIKMIDITrack getTrackNumber:] replacing it with read only trackNumber @property. --- Source/MIKMIDITrack.h | 25 ++++++++++++++++--------- Source/MIKMIDITrack.m | 34 +++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 63f10f0a..83517fa4 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -66,15 +66,6 @@ */ - (BOOL)clearAllEvents; -/** - * Gets the track's track number in it's owning MIDI sequence. - * - * @param trackNumber On output, the track number of the track. - * - * @return Whether or not getting the track number was succesful. - */ -- (BOOL)getTrackNumber:(UInt32 *)trackNumber; - /** * Gets all of the MIDI events in the track starting from startTimeStamp and ending at endTimeStamp inclusively. * @@ -214,6 +205,11 @@ */ @property (nonatomic, readonly) NSArray *notes; +/** + * The receiver's index in its containing sequence, or -1 if the track isn't in a sequence. + */ +@property (nonatomic, readonly) NSInteger trackNumber; + /** * Whether the track is set to loop. */ @@ -270,6 +266,17 @@ #pragma mark - Deprecated +/** + * Gets the track's track number in it's owning MIDI sequence. + * + * @param trackNumber On output, the track number of the track. + * + * @return Whether or not getting the track number was succesful. + * + * @deprecated This method is deprecated. Use -trackNumber instead. + */ +- (BOOL)getTrackNumber:(UInt32 *)trackNumber DEPRECATED_ATTRIBUTE; + /** * The destination endpoint for the MIDI events of the track during playback. */ diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 0a74cc2f..7ab35fb7 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -252,15 +252,6 @@ - (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicT return !err; } -#pragma mark - Track Number - -- (BOOL)getTrackNumber:(UInt32 *)trackNumber -{ - OSStatus err = MusicSequenceGetTrackIndex(self.sequence.musicSequence, self.musicTrack, trackNumber); - if (err) NSLog(@"MusicSequenceGetTrackIndex() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; -} - #pragma mark - Temporary Length and Loop Info - (void)setTemporaryLength:(MusicTimeStamp)length andLoopInfo:(MusicTrackLoopInfo)loopInfo @@ -300,6 +291,17 @@ - (NSArray *)notes return [self notesFromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; } +- (NSInteger)trackNumber +{ + if (!self.sequence) return -1; + UInt32 trackNumber = 0; + OSStatus err = MusicSequenceGetTrackIndex(self.sequence.musicSequence, self.musicTrack, &trackNumber); + if (err) { + NSLog(@"MusicSequenceGetTrackIndex() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return -1; + } + return (NSInteger)trackNumber; +} - (BOOL)doesLoop { @@ -424,6 +426,20 @@ - (SInt16)timeResolution #pragma mark - Properties +#pragma mark - Deprecated + +- (BOOL)getTrackNumber:(UInt32 *)trackNumber +{ + static BOOL deprectionMsgShown = NO; + if (!deprectionMsgShown) { + NSLog(@"WARNING: %s has been deprecated. Please use -trackNumber instead. This message will only be logged once", __PRETTY_FUNCTION__); + deprectionMsgShown = YES; + } + NSInteger result = self.trackNumber; + *trackNumber = (UInt32)result; + return (result >= 0); +} + @synthesize destinationEndpoint = _destinationEndpoint; - (MIKMIDIDestinationEndpoint *)destinationEndpoint From 759f5870357326787a91abc52e2ce98772735638 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 22 Feb 2015 21:31:19 -0700 Subject: [PATCH 019/284] Improved annotation of documentation for deprecated methods. --- Source/MIKMIDICommand_SubclassMethods.h | 2 +- Source/MIKMIDISequence.h | 11 +++++++---- Source/MIKMIDITrack.h | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDICommand_SubclassMethods.h b/Source/MIKMIDICommand_SubclassMethods.h index 4be918bb..09b4ff45 100644 --- a/Source/MIKMIDICommand_SubclassMethods.h +++ b/Source/MIKMIDICommand_SubclassMethods.h @@ -30,7 +30,7 @@ + (void)registerSubclass:(Class)subclass; /** - * This method has been replaced by +supportedMIDICommandTypes + * @deprecated This method has been replaced by +supportedMIDICommandTypes * and by default simply calls through to that method. Subclasses * no longer need implement this. * diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 29230728..b0091a06 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -263,7 +263,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d #pragma mark - Deprecated /** - * This method is deprecated. Use +sequenceWithData:error: instead. + * @deprecated This method is deprecated. Use +sequenceWithData:error: instead. * * Creates and initializes a new instance of MIKMIDISequence from MIDI data. * @@ -274,7 +274,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d + (instancetype)sequenceWithData:(NSData *)data DEPRECATED_ATTRIBUTE; /** - * This method is deprecated. Use -initWithData:error: instead. + * @deprecated This method is deprecated. Use -initWithData:error: instead. * * Initializes a new instance of MIKMIDISequence from MIDI data. * @@ -285,6 +285,9 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d - (instancetype)initWithData:(NSData *)data DEPRECATED_ATTRIBUTE; /** + * @deprecated This method is deprecated. Use -[MIKMIDISequencer + * setDestinationEndpoint:forTrack:] instead. + * * Sets the destination endpoint for each track in the sequence. * * @param destinationEndpoint The destination endpoint to set for each track in the sequence. @@ -292,7 +295,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d - (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)destinationEndpoint DEPRECATED_ATTRIBUTE; /** - * This method has been replaced by -tempoAtTimeStamp: and simply calls through to that method. + * @deprecated This method has been replaced by -tempoAtTimeStamp: and simply calls through to that method. * You should not call it, and should update your code to call -timeSignatureAtTimeStamp: instead. * * @param bpm On output, the beats per minute of the tempo at the specified time stamp. @@ -302,7 +305,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d - (BOOL)getTempo:(Float64 *)bpm atTimeStamp:(MusicTimeStamp)timeStamp DEPRECATED_ATTRIBUTE; /** - * This method has been replaced by -timeSignatureAtTimeStamp: and simply calls through to that method. + * @deprecated This method has been replaced by -timeSignatureAtTimeStamp: and simply calls through to that method. * You should not call it, and should update your code to call -timeSignatureAtTimeStamp: instead. * * @param signature On output, a time signature instance with its values populated. diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 83517fa4..6df39764 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -278,7 +278,9 @@ - (BOOL)getTrackNumber:(UInt32 *)trackNumber DEPRECATED_ATTRIBUTE; /** - * The destination endpoint for the MIDI events of the track during playback. + * @deprecated This property has been deprecated. Use -[MIKMIDISequencer setDestinationEndpoint:forTrack:] instead. + * + * The destination endpoint for the MIDI events of the track during playback. */ @property (nonatomic, strong, readwrite) MIKMIDIDestinationEndpoint *destinationEndpoint DEPRECATED_ATTRIBUTE; From 393b97467494b5c88311a31f832f1fe8b5c15ac9 Mon Sep 17 00:00:00 2001 From: Parker Wightman Date: Tue, 24 Feb 2015 08:09:52 -0700 Subject: [PATCH 020/284] Add all Event and MetaEvent superclass properties to mutable subclasses. --- Source/MIKMIDIMetaCopyrightEvent.h | 3 +++ Source/MIKMIDIMetaCopyrightEvent.m | 3 +++ Source/MIKMIDIMetaCuePointEvent.h | 3 +++ Source/MIKMIDIMetaCuePointEvent.m | 5 +++++ Source/MIKMIDIMetaInstrumentNameEvent.h | 3 +++ Source/MIKMIDIMetaInstrumentNameEvent.m | 3 +++ Source/MIKMIDIMetaKeySignatureEvent.h | 2 ++ Source/MIKMIDIMetaKeySignatureEvent.m | 6 ++++-- Source/MIKMIDIMetaLyricEvent.h | 3 +++ Source/MIKMIDIMetaLyricEvent.m | 3 +++ Source/MIKMIDIMetaMarkerTextEvent.h | 3 +++ Source/MIKMIDIMetaMarkerTextEvent.m | 3 +++ Source/MIKMIDIMetaSequenceEvent.h | 3 +++ Source/MIKMIDIMetaSequenceEvent.m | 3 +++ Source/MIKMIDIMetaTextEvent.h | 2 ++ Source/MIKMIDIMetaTextEvent.m | 6 ++++-- Source/MIKMIDIMetaTimeSignatureEvent.h | 2 ++ Source/MIKMIDIMetaTimeSignatureEvent.m | 6 ++++-- Source/MIKMIDIMetaTrackSequenceNameEvent.h | 3 +++ Source/MIKMIDIMetaTrackSequenceNameEvent.m | 3 +++ Source/MIKMIDINoteEvent.h | 6 ++++++ Source/MIKMIDINoteEvent.m | 6 ++++++ Source/MIKMIDITempoEvent.h | 2 ++ Source/MIKMIDITempoEvent.m | 2 ++ 24 files changed, 78 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDIMetaCopyrightEvent.h b/Source/MIKMIDIMetaCopyrightEvent.h index b7461b96..b15f3e45 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.h +++ b/Source/MIKMIDIMetaCopyrightEvent.h @@ -20,4 +20,7 @@ */ @interface MIKMutableMIDIMetaCopyrightEvent : MIKMIDIMetaCopyrightEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaCopyrightEvent.m b/Source/MIKMIDIMetaCopyrightEvent.m index 03d2779a..4ef7584e 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.m +++ b/Source/MIKMIDIMetaCopyrightEvent.m @@ -26,6 +26,9 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaCopyrightEvent +@dynamic metadataType; +@dynamic metaData; + + (BOOL)isMutable { return YES; } @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaCuePointEvent.h b/Source/MIKMIDIMetaCuePointEvent.h index cd3929ea..a5932745 100644 --- a/Source/MIKMIDIMetaCuePointEvent.h +++ b/Source/MIKMIDIMetaCuePointEvent.h @@ -20,4 +20,7 @@ */ @interface MIKMutableMIDIMetaCuePointEvent : MIKMIDIMetaCuePointEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaCuePointEvent.m b/Source/MIKMIDIMetaCuePointEvent.m index 895f3cb2..f6b00340 100644 --- a/Source/MIKMIDIMetaCuePointEvent.m +++ b/Source/MIKMIDIMetaCuePointEvent.m @@ -25,5 +25,10 @@ + (BOOL)isMutable { return NO; } @end @implementation MIKMutableMIDIMetaCuePointEvent + +@dynamic metadataType; +@dynamic metaData; + + (BOOL)isMutable { return YES; } + @end diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.h b/Source/MIKMIDIMetaInstrumentNameEvent.h index 23802034..a51c1008 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.h +++ b/Source/MIKMIDIMetaInstrumentNameEvent.h @@ -21,4 +21,7 @@ */ @interface MIKMutableMIDIMetaInstrumentNameEvent : MIKMIDIMetaInstrumentNameEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.m b/Source/MIKMIDIMetaInstrumentNameEvent.m index 2b1286b7..74d20e24 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.m +++ b/Source/MIKMIDIMetaInstrumentNameEvent.m @@ -26,6 +26,9 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaInstrumentNameEvent +@dynamic metadataType; +@dynamic metaData; + + (BOOL)isMutable { return YES; } @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaKeySignatureEvent.h b/Source/MIKMIDIMetaKeySignatureEvent.h index 8b8b5991..010dcdaa 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.h +++ b/Source/MIKMIDIMetaKeySignatureEvent.h @@ -31,6 +31,8 @@ */ @interface MIKMutableMIDIMetaKeySignatureEvent : MIKMIDIMetaKeySignatureEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; @property (nonatomic, readwrite) UInt8 key; @property (nonatomic, readwrite) UInt8 scale; diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index dfd76f70..c89f4119 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -68,9 +68,11 @@ - (NSString *)additionalEventDescription @implementation MIKMutableMIDIMetaKeySignatureEvent -+ (BOOL)isMutable { return YES; } - +@dynamic metadataType; +@dynamic metaData; @dynamic key; @dynamic scale; ++ (BOOL)isMutable { return YES; } + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaLyricEvent.h b/Source/MIKMIDIMetaLyricEvent.h index dc6a560c..7da9c0b8 100644 --- a/Source/MIKMIDIMetaLyricEvent.h +++ b/Source/MIKMIDIMetaLyricEvent.h @@ -20,4 +20,7 @@ */ @interface MIKMutableMIDIMetaLyricEvent : MIKMIDIMetaLyricEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; + @end diff --git a/Source/MIKMIDIMetaLyricEvent.m b/Source/MIKMIDIMetaLyricEvent.m index 528a4d7e..f47487fb 100644 --- a/Source/MIKMIDIMetaLyricEvent.m +++ b/Source/MIKMIDIMetaLyricEvent.m @@ -26,6 +26,9 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaLyricEvent +@dynamic metadataType; +@dynamic metaData; + + (BOOL)isMutable { return YES; } @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaMarkerTextEvent.h b/Source/MIKMIDIMetaMarkerTextEvent.h index 08a971b8..b18e1003 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.h +++ b/Source/MIKMIDIMetaMarkerTextEvent.h @@ -20,4 +20,7 @@ */ @interface MIKMutableMIDIMetaMarkerTextEvent : MIKMIDIMetaMarkerTextEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaMarkerTextEvent.m b/Source/MIKMIDIMetaMarkerTextEvent.m index 40967270..7fa94b1b 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.m +++ b/Source/MIKMIDIMetaMarkerTextEvent.m @@ -26,6 +26,9 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaMarkerTextEvent +@dynamic metadataType; +@dynamic metaData; + + (BOOL)isMutable { return YES; } @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaSequenceEvent.h b/Source/MIKMIDIMetaSequenceEvent.h index d42a8667..5554da46 100644 --- a/Source/MIKMIDIMetaSequenceEvent.h +++ b/Source/MIKMIDIMetaSequenceEvent.h @@ -20,4 +20,7 @@ */ @interface MIKMutableMIDIMetaSequenceEvent : MIKMIDIMetaSequenceEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; + @end diff --git a/Source/MIKMIDIMetaSequenceEvent.m b/Source/MIKMIDIMetaSequenceEvent.m index e90fc28c..3ead4b62 100644 --- a/Source/MIKMIDIMetaSequenceEvent.m +++ b/Source/MIKMIDIMetaSequenceEvent.m @@ -18,4 +18,7 @@ @implementation MIKMIDIMetaSequenceEvent @implementation MIKMutableMIDIMetaSequenceEvent +@dynamic metadataType; +@dynamic metaData; + @end diff --git a/Source/MIKMIDIMetaTextEvent.h b/Source/MIKMIDIMetaTextEvent.h index 0ec46de4..eda50b8d 100644 --- a/Source/MIKMIDIMetaTextEvent.h +++ b/Source/MIKMIDIMetaTextEvent.h @@ -25,6 +25,8 @@ */ @interface MIKMutableMIDIMetaTextEvent : MIKMIDIMetaTextEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; @property (nonatomic, readwrite) NSString *string; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaTextEvent.m b/Source/MIKMIDIMetaTextEvent.m index fdb1bd31..a5bc6733 100644 --- a/Source/MIKMIDIMetaTextEvent.m +++ b/Source/MIKMIDIMetaTextEvent.m @@ -52,8 +52,10 @@ - (NSString *)additionalEventDescription @implementation MIKMutableMIDIMetaTextEvent -+ (BOOL)isMutable { return YES; } - +@dynamic metadataType; +@dynamic metaData; @dynamic string; ++ (BOOL)isMutable { return YES; } + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.h b/Source/MIKMIDIMetaTimeSignatureEvent.h index b3c4dad1..e35daab5 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.h +++ b/Source/MIKMIDIMetaTimeSignatureEvent.h @@ -40,6 +40,8 @@ */ @interface MIKMutableMIDIMetaTimeSignatureEvent : MIKMIDIMetaTimeSignatureEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 numerator; @property (nonatomic, readwrite) UInt8 denominator; diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index 715171d8..ea075e46 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -101,12 +101,14 @@ - (NSString *)additionalEventDescription @implementation MIKMutableMIDIMetaTimeSignatureEvent -+ (BOOL)isMutable { return YES; } - +@dynamic metadataType; +@dynamic metaData; @dynamic timeStamp; @dynamic numerator; @dynamic denominator; @dynamic metronomePulse; @dynamic thirtySecondsPerQuarterNote; ++ (BOOL)isMutable { return YES; } + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index 5ca78c31..d5d7e514 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -20,4 +20,7 @@ */ @interface MIKMutableMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTrackSequenceNameEvent +@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) NSData *metaData; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.m b/Source/MIKMIDIMetaTrackSequenceNameEvent.m index b77abb23..f68aea84 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.m +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.m @@ -26,6 +26,9 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaTrackSequenceNameEvent +@dynamic metadataType; +@dynamic metaData; + + (BOOL)isMutable { return YES; } @end \ No newline at end of file diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index bbf897d2..d7009b88 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -82,11 +82,17 @@ */ @interface MIKMutableMIDINoteEvent : MIKMIDINoteEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; @property (nonatomic, readwrite) UInt8 note; @property (nonatomic, readwrite) UInt8 velocity; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 releaseVelocity; @property (nonatomic, readwrite) Float32 duration; +@property (nonatomic, readwrite) MusicTimeStamp endTimeStamp; +@property (nonatomic, readwrite) float frequency; +@property (nonatomic, readwrite) NSString *noteLetter; +@property (nonatomic, readwrite) NSString *noteLetterAndOctave; @end diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 6810ec17..9b3f240f 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -150,6 +150,12 @@ @implementation MIKMutableMIDINoteEvent @dynamic channel; @dynamic releaseVelocity; @dynamic duration; +@dynamic endTimeStamp; +@dynamic frequency; +@dynamic noteLetter; +@dynamic noteLetterAndOctave; +@dynamic timeStamp; +@dynamic data; + (BOOL)isMutable { return YES; } diff --git a/Source/MIKMIDITempoEvent.h b/Source/MIKMIDITempoEvent.h index 769ec547..42a8a037 100644 --- a/Source/MIKMIDITempoEvent.h +++ b/Source/MIKMIDITempoEvent.h @@ -38,6 +38,8 @@ */ @interface MIKMutableMIDITempoEvent : MIKMIDITempoEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; @property (nonatomic, readwrite) Float64 bpm; @end \ No newline at end of file diff --git a/Source/MIKMIDITempoEvent.m b/Source/MIKMIDITempoEvent.m index 56af9a83..b217ec3d 100644 --- a/Source/MIKMIDITempoEvent.m +++ b/Source/MIKMIDITempoEvent.m @@ -62,5 +62,7 @@ @implementation MIKMutableMIDITempoEvent + (BOOL)isMutable { return YES; } @dynamic bpm; +@dynamic timeStamp; +@dynamic data; @end \ No newline at end of file From 2bf11a582cafd3d3bb27279306a21bfa81716e75 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 24 Feb 2015 09:45:42 -0700 Subject: [PATCH 021/284] Added missing readwrite declaration for timeStamp property to more MIKMIDIEvent subclasses. --- Source/MIKMIDIMetaCopyrightEvent.h | 1 + Source/MIKMIDIMetaCopyrightEvent.m | 1 + Source/MIKMIDIMetaCuePointEvent.h | 1 + Source/MIKMIDIMetaCuePointEvent.m | 1 + Source/MIKMIDIMetaEvent.h | 1 + Source/MIKMIDIMetaEvent.m | 1 + Source/MIKMIDIMetaInstrumentNameEvent.h | 1 + Source/MIKMIDIMetaInstrumentNameEvent.m | 1 + Source/MIKMIDIMetaKeySignatureEvent.h | 1 + Source/MIKMIDIMetaKeySignatureEvent.m | 1 + Source/MIKMIDIMetaLyricEvent.h | 1 + Source/MIKMIDIMetaLyricEvent.m | 1 + Source/MIKMIDIMetaMarkerTextEvent.h | 1 + Source/MIKMIDIMetaMarkerTextEvent.m | 1 + Source/MIKMIDIMetaSequenceEvent.h | 1 + Source/MIKMIDIMetaSequenceEvent.m | 1 + Source/MIKMIDIMetaTextEvent.h | 1 + Source/MIKMIDIMetaTextEvent.m | 1 + Source/MIKMIDIMetaTrackSequenceNameEvent.h | 1 + Source/MIKMIDIMetaTrackSequenceNameEvent.m | 1 + 20 files changed, 20 insertions(+) diff --git a/Source/MIKMIDIMetaCopyrightEvent.h b/Source/MIKMIDIMetaCopyrightEvent.h index b15f3e45..619ebd9e 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.h +++ b/Source/MIKMIDIMetaCopyrightEvent.h @@ -20,6 +20,7 @@ */ @interface MIKMutableMIDIMetaCopyrightEvent : MIKMIDIMetaCopyrightEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaCopyrightEvent.m b/Source/MIKMIDIMetaCopyrightEvent.m index 4ef7584e..156e2b0e 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.m +++ b/Source/MIKMIDIMetaCopyrightEvent.m @@ -26,6 +26,7 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaCopyrightEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; diff --git a/Source/MIKMIDIMetaCuePointEvent.h b/Source/MIKMIDIMetaCuePointEvent.h index a5932745..95374b77 100644 --- a/Source/MIKMIDIMetaCuePointEvent.h +++ b/Source/MIKMIDIMetaCuePointEvent.h @@ -20,6 +20,7 @@ */ @interface MIKMutableMIDIMetaCuePointEvent : MIKMIDIMetaCuePointEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaCuePointEvent.m b/Source/MIKMIDIMetaCuePointEvent.m index f6b00340..c7b672d6 100644 --- a/Source/MIKMIDIMetaCuePointEvent.m +++ b/Source/MIKMIDIMetaCuePointEvent.m @@ -26,6 +26,7 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaCuePointEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; diff --git a/Source/MIKMIDIMetaEvent.h b/Source/MIKMIDIMetaEvent.h index 57d55abb..11dee4f2 100644 --- a/Source/MIKMIDIMetaEvent.h +++ b/Source/MIKMIDIMetaEvent.h @@ -38,6 +38,7 @@ */ @interface MIKMutableMIDIMetaEvent : MIKMIDIMetaEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index 2d5261f4..c8e4ae92 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -81,6 +81,7 @@ - (void)setMetaData:(NSData *)metaData @implementation MIKMutableMIDIMetaEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.h b/Source/MIKMIDIMetaInstrumentNameEvent.h index a51c1008..e88bd74f 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.h +++ b/Source/MIKMIDIMetaInstrumentNameEvent.h @@ -21,6 +21,7 @@ */ @interface MIKMutableMIDIMetaInstrumentNameEvent : MIKMIDIMetaInstrumentNameEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.m b/Source/MIKMIDIMetaInstrumentNameEvent.m index 74d20e24..b856ee0c 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.m +++ b/Source/MIKMIDIMetaInstrumentNameEvent.m @@ -26,6 +26,7 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaInstrumentNameEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; diff --git a/Source/MIKMIDIMetaKeySignatureEvent.h b/Source/MIKMIDIMetaKeySignatureEvent.h index 010dcdaa..1f97cf37 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.h +++ b/Source/MIKMIDIMetaKeySignatureEvent.h @@ -31,6 +31,7 @@ */ @interface MIKMutableMIDIMetaKeySignatureEvent : MIKMIDIMetaKeySignatureEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; @property (nonatomic, readwrite) UInt8 key; diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index c89f4119..09fdb588 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -68,6 +68,7 @@ - (NSString *)additionalEventDescription @implementation MIKMutableMIDIMetaKeySignatureEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; @dynamic key; diff --git a/Source/MIKMIDIMetaLyricEvent.h b/Source/MIKMIDIMetaLyricEvent.h index 7da9c0b8..3921eea1 100644 --- a/Source/MIKMIDIMetaLyricEvent.h +++ b/Source/MIKMIDIMetaLyricEvent.h @@ -20,6 +20,7 @@ */ @interface MIKMutableMIDIMetaLyricEvent : MIKMIDIMetaLyricEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaLyricEvent.m b/Source/MIKMIDIMetaLyricEvent.m index f47487fb..f906a64e 100644 --- a/Source/MIKMIDIMetaLyricEvent.m +++ b/Source/MIKMIDIMetaLyricEvent.m @@ -26,6 +26,7 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaLyricEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; diff --git a/Source/MIKMIDIMetaMarkerTextEvent.h b/Source/MIKMIDIMetaMarkerTextEvent.h index b18e1003..fbe881ff 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.h +++ b/Source/MIKMIDIMetaMarkerTextEvent.h @@ -20,6 +20,7 @@ */ @interface MIKMutableMIDIMetaMarkerTextEvent : MIKMIDIMetaMarkerTextEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaMarkerTextEvent.m b/Source/MIKMIDIMetaMarkerTextEvent.m index 7fa94b1b..0d143a09 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.m +++ b/Source/MIKMIDIMetaMarkerTextEvent.m @@ -26,6 +26,7 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaMarkerTextEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; diff --git a/Source/MIKMIDIMetaSequenceEvent.h b/Source/MIKMIDIMetaSequenceEvent.h index 5554da46..075924aa 100644 --- a/Source/MIKMIDIMetaSequenceEvent.h +++ b/Source/MIKMIDIMetaSequenceEvent.h @@ -20,6 +20,7 @@ */ @interface MIKMutableMIDIMetaSequenceEvent : MIKMIDIMetaSequenceEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaSequenceEvent.m b/Source/MIKMIDIMetaSequenceEvent.m index 3ead4b62..c0c5b73f 100644 --- a/Source/MIKMIDIMetaSequenceEvent.m +++ b/Source/MIKMIDIMetaSequenceEvent.m @@ -18,6 +18,7 @@ @implementation MIKMIDIMetaSequenceEvent @implementation MIKMutableMIDIMetaSequenceEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; diff --git a/Source/MIKMIDIMetaTextEvent.h b/Source/MIKMIDIMetaTextEvent.h index eda50b8d..e9378171 100644 --- a/Source/MIKMIDIMetaTextEvent.h +++ b/Source/MIKMIDIMetaTextEvent.h @@ -25,6 +25,7 @@ */ @interface MIKMutableMIDIMetaTextEvent : MIKMIDIMetaTextEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; @property (nonatomic, readwrite) NSString *string; diff --git a/Source/MIKMIDIMetaTextEvent.m b/Source/MIKMIDIMetaTextEvent.m index a5bc6733..e46f6918 100644 --- a/Source/MIKMIDIMetaTextEvent.m +++ b/Source/MIKMIDIMetaTextEvent.m @@ -52,6 +52,7 @@ - (NSString *)additionalEventDescription @implementation MIKMutableMIDIMetaTextEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; @dynamic string; diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index d5d7e514..e783b722 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -20,6 +20,7 @@ */ @interface MIKMutableMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTrackSequenceNameEvent +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.m b/Source/MIKMIDIMetaTrackSequenceNameEvent.m index f68aea84..8cf06964 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.m +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.m @@ -26,6 +26,7 @@ + (BOOL)isMutable { return NO; } @implementation MIKMutableMIDIMetaTrackSequenceNameEvent +@dynamic timeStamp; @dynamic metadataType; @dynamic metaData; From 487549f9b7f6c6ada91ffcba2ab4b8f5fa7d48d2 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 24 Feb 2015 12:35:27 -0700 Subject: [PATCH 022/284] Very minor documentation improvement. --- Source/MIKMIDISequence.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index b0091a06..92929a60 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -226,7 +226,8 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d @property (nonatomic, readonly) MIKMIDITrack *tempoTrack; /** - * The MIDI tracks for the sequence. An array of MIKMIDITrack instances. + * The MIDI music tracks for the sequence. An array of MIKMIDITrack instances. + * Does not include the tempo track. */ @property (nonatomic, readonly) NSArray *tracks; From 5b61bd0b2220fed69ea590d6006aad2aa881972a Mon Sep 17 00:00:00 2001 From: Parker Wightman Date: Tue, 24 Feb 2015 18:35:47 -0700 Subject: [PATCH 023/284] Removing computed properties that don't have setters Rearranged @dynamics to have same order as properties. --- Source/MIKMIDINoteEvent.h | 4 ---- Source/MIKMIDINoteEvent.m | 8 ++------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index d7009b88..38aa0102 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -89,10 +89,6 @@ @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 releaseVelocity; @property (nonatomic, readwrite) Float32 duration; -@property (nonatomic, readwrite) MusicTimeStamp endTimeStamp; -@property (nonatomic, readwrite) float frequency; -@property (nonatomic, readwrite) NSString *noteLetter; -@property (nonatomic, readwrite) NSString *noteLetterAndOctave; @end diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 9b3f240f..21ea9bd0 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -145,17 +145,13 @@ - (NSString *)additionalEventDescription @implementation MIKMutableMIDINoteEvent +@dynamic timeStamp; +@dynamic data; @dynamic note; @dynamic velocity; @dynamic channel; @dynamic releaseVelocity; @dynamic duration; -@dynamic endTimeStamp; -@dynamic frequency; -@dynamic noteLetter; -@dynamic noteLetterAndOctave; -@dynamic timeStamp; -@dynamic data; + (BOOL)isMutable { return YES; } From d9bdab046a41a77ff8e7766fe762dfb024927599 Mon Sep 17 00:00:00 2001 From: Parker Wightman Date: Tue, 24 Feb 2015 23:01:30 -0700 Subject: [PATCH 024/284] Warn when attempting to insert NULL or unknown eventType. --- Source/MIKMIDITrack.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 7ab35fb7..e8b4dc30 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -76,6 +76,7 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event switch (event.eventType) { case kMusicEventType_NULL: + NSLog(@"Warning: insertMIDITrack: attempted to insert NULL event."); break; case kMusicEventType_ExtendedNote: @@ -122,6 +123,9 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event err = MusicTrackNewAUPresetEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewAUPresetEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; + default: + err = 1; + NSLog(@"Warning: insertMIDIEvent: attempted to insert unknown event type %d.", event.eventType); } return !err; From d15dd3ee55855af1a938c7ae2a50fd7492143726 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 10:32:58 -0700 Subject: [PATCH 025/284] Issue #57: Refactored MIKMIDIEvent initialization machinery so -[MIKMIDIEventSomeSubclass init] correct sets event type. Passing nil data to full initializer also works now. --- Source/MIKMIDICommand_SubclassMethods.h | 26 ++++--- Source/MIKMIDIEvent.h | 85 +++++++++++++--------- Source/MIKMIDIEvent.m | 48 ++++++++---- Source/MIKMIDIEvent_SubclassMethods.h | 27 +++++-- Source/MIKMIDIMetaCopyrightEvent.m | 2 +- Source/MIKMIDIMetaCuePointEvent.m | 2 +- Source/MIKMIDIMetaEvent.m | 2 +- Source/MIKMIDIMetaInstrumentNameEvent.m | 2 +- Source/MIKMIDIMetaKeySignatureEvent.m | 2 +- Source/MIKMIDIMetaLyricEvent.m | 2 +- Source/MIKMIDIMetaMarkerTextEvent.m | 2 +- Source/MIKMIDIMetaTextEvent.m | 2 +- Source/MIKMIDIMetaTimeSignatureEvent.m | 2 +- Source/MIKMIDIMetaTrackSequenceNameEvent.m | 2 +- Source/MIKMIDINoteEvent.m | 2 +- Source/MIKMIDITempoEvent.m | 2 +- Source/MIKMIDITrack.m | 47 ++++++++---- 17 files changed, 163 insertions(+), 94 deletions(-) diff --git a/Source/MIKMIDICommand_SubclassMethods.h b/Source/MIKMIDICommand_SubclassMethods.h index 09b4ff45..6f3e3b36 100644 --- a/Source/MIKMIDICommand_SubclassMethods.h +++ b/Source/MIKMIDICommand_SubclassMethods.h @@ -29,17 +29,6 @@ */ + (void)registerSubclass:(Class)subclass; -/** - * @deprecated This method has been replaced by +supportedMIDICommandTypes - * and by default simply calls through to that method. Subclasses - * no longer need implement this. - * - * @param type An MIKMIDICommandType value. - * - * @return YES if the subclass supports type, NO otherwise. - */ -+ (BOOL)supportsMIDICommandType:(MIKMIDICommandType)type DEPRECATED_ATTRIBUTE; - /** * Subclasses of MIKMIDICommand must override this method, and return the MIKMIDICommandType * values they support. MIKMIDICommand uses this method to determine which @@ -47,7 +36,7 @@ * * Note that the older +supportsMIDICommandType: by default simply calls through to this method. * - * @return An NSArray containing NSNumber instances containing MIKMIDICommandType values. + * @return An NSArray of NSNumber instances containing MIKMIDICommandType values. */ + (NSArray *)supportedMIDICommandTypes; @@ -109,4 +98,17 @@ */ - (NSString *)additionalCommandDescription; +// Deprecated + +/** + * @deprecated This method has been replaced by +supportedMIDICommandTypes + * and by default simply calls through to that method. Subclasses + * no longer need implement this. + * + * @param type An MIKMIDICommandType value. + * + * @return YES if the subclass supports type, NO otherwise. + */ ++ (BOOL)supportsMIDICommandType:(MIKMIDICommandType)type DEPRECATED_ATTRIBUTE; + @end diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index 6216998c..79785db2 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -18,17 +18,18 @@ typedef NS_ENUM(NSUInteger, MIKMIDIEventType) { - MIKMIDIEventTypeNULL, - MIKMIDIEventTypeExtendedNote, - MIKMIDIEventTypeExtendedTempo, - MIKMIDIEventTypeUser, - MIKMIDIEventTypeMIDINoteMessage, - MIKMIDIEventTypeMIDIChannelMessage, - MIKMIDIEventTypeMIDIRawData, - MIKMIDIEventTypeParameter, - MIKMIDIEventTypeAUPreset, - MIKMIDIEventTypeExtendedControl, - MIKMIDIEventTypeMeta, + MIKMIDIEventTypeNULL = kMusicEventType_NULL, + MIKMIDIEventTypeExtendedNote = kMusicEventType_ExtendedNote, + MIKMIDIEventTypeExtendedTempo = kMusicEventType_ExtendedTempo, + MIKMIDIEventTypeUser = kMusicEventType_User, + MIKMIDIEventTypeMeta = kMusicEventType_Meta, + MIKMIDIEventTypeMIDINoteMessage = kMusicEventType_MIDINoteMessage, + MIKMIDIEventTypeMIDIChannelMessage = kMusicEventType_MIDIChannelMessage, + MIKMIDIEventTypeMIDIRawData = kMusicEventType_MIDIRawData, + MIKMIDIEventTypeParameter = kMusicEventType_Parameter, + MIKMIDIEventTypeAUPreset = kMusicEventType_AUPreset, + + // Meta types MIKMIDIEventTypeMetaSequence, MIKMIDIEventTypeMetaText, MIKMIDIEventTypeMetaCopyright, @@ -43,7 +44,10 @@ typedef NS_ENUM(NSUInteger, MIKMIDIEventType) MIKMIDIEventTypeMetaSMPTEOffset, MIKMIDIEventTypeMetaTimeSignature, MIKMIDIEventTypeMetaKeySignature, - MIKMIDIEventTypeMetaSequenceSpecificEvent + MIKMIDIEventTypeMetaSequenceSpecificEvent, + + // Deprecated, and unsupported. + MIKMIDIEventTypeExtendedControl = kMusicEventType_ExtendedControl, }; typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) @@ -115,9 +119,41 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) @interface MIKMIDIEvent : NSObject /** - * The MIDI event type. See MusicPlayer.h for a list of possible values. + * Convenience method for creating a new MIKMIDIEvent instance from an NSData instance. + * For event types for which there is a specific MIKMIDIEvent subclass, + * an instance of the appropriate subclass will be returned. + * + * The NSData argument is used in conjunction with the eventType to propertly discriminate + * between different kMusicEventType_Meta subtypes. + * + * @param timeStamp A MusicTimeStamp value indicating the timestamp for the event. + * @param eventType A MusicEventType value indicating the type of the event. + * @param data An NSData instance containing the raw data for the event. May be nil for an empty event. + * + * @return For supported event types, an initialized MIKMIDIEvent subclass. Otherwise, an instance + * of MIKMIDIEvent itself. nil if there is an error. + * + * @see +mikEventTypeForMusicEventType: */ -@property (nonatomic, readonly) MusicEventType eventType; ++ (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data; + +/** + * Initializes a new MIKMIDIEvent subclass instance. This method may return an instance of a different class than the + * receiver. + * + * @param timeStamp A MusicTimeStamp value indicating the timestamp for the event. + * @param eventType An MIKMIDIEventType value indicating the type of the event. + * @param data An NSData instance containing the raw data for the event. May be nil for an empty event. + * + * @return For supported command types, an initialized MIKMIDIEvent subclass. Otherwise, an instance of + * MIKMIDICommand itself. nil if there is an error. + */ +- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(NSData *)data; + +/** + * The MIDI event type. + */ +@property (nonatomic, readonly) MIKMIDIEventType eventType; /** * The timeStamp of the MIDI event. When used in a MusicSequence of type kMusicSequenceType_Beats @@ -131,25 +167,6 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) */ @property (nonatomic, readonly) NSData *data; - -/** - * Convenience method for creating a new MIKMIDIEvent instance from an NSData instance. - * For event types for which there is a specific MIKMIDIEvent subclass, - * an instance of the appropriate subclass will be returned. - * - * @param timeStamp The MusicTimeStamp for the event. - * - * @param eventType The MusicEventType of the event. - * - * @param data The data representing the event. - * - * @return For supported event types, an initialized MIKMIDIEvent subclass. Otherwise, an instance - * of MIKMIDIEvent itself. nil if there is an error. - * - * @see +mikEventTypeForMusicEventType: - */ -+ (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data; - @end /** @@ -157,7 +174,7 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) */ @interface MIKMutableMIDIEvent : MIKMIDIEvent -@property (nonatomic, readonly) MusicEventType eventType; +@property (nonatomic, readonly) MIKMIDIEventType eventType; @property (nonatomic) MusicTimeStamp timeStamp; @property (nonatomic, strong, readwrite) NSMutableData *data; diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 9e46dae7..26bb70b2 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -29,39 +29,52 @@ + (void)registerSubclass:(Class)subclass; + (BOOL)isMutable { return NO; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return NO; } ++ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return [[self supportedMIDIEventTypes] containsObject:@(type)]; } ++ (NSArray *)supportedMIDIEventTypes { return @[]; } + (Class)immutableCounterpartClass; { return [MIKMIDIEvent class]; } + (Class)mutableCounterpartClass; { return [MIKMutableMIDIEvent class]; } + (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data { - Class subclass = [[self class] subclassForEventType:eventType andData:data]; + MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data]; + // -initWithTimeStamp:midiEventType:data: will do subclass lookup too, but this way we avoid a second alloc + Class subclass = [[self class] subclassForEventType:eventType]; if (!subclass) subclass = self; if ([self isMutable]) subclass = [subclass mutableCounterpartClass]; - MIKMIDIEvent *result = [[subclass alloc] initWithTimeStamp:timeStamp eventType:eventType data:data]; - return result; + return [[subclass alloc] initWithTimeStamp:timeStamp midiEventType:midiEventType data:data]; } - (id)init { - self = [self initWithTimeStamp:0 eventType:MIKMIDIEventTypeNULL data:nil]; - if (self) { - self.internalData = [NSMutableData data]; - } - return self; + MIKMIDIEventType eventType = (MIKMIDIEventType)[[[[self class] supportedMIDIEventTypes] firstObject] unsignedIntegerValue]; + return [self initWithTimeStamp:0 midiEventType:eventType data:nil]; } -- (id)initWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data +- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(NSData *)data { + // If we don't directly support eventType, return an instance of an MIKMIDIEvent subclass that does. + if (![[[self class] supportedMIDIEventTypes] containsObject:@(eventType)]) { + BOOL isMutable = [[self class] isMutable]; + Class subclass = [[self class] subclassForEventType:eventType]; + if (!subclass) subclass = [MIKMIDIEvent class]; + if (isMutable) subclass = [subclass mutableCounterpartClass]; + self = [subclass alloc]; + } + self = [super init]; if (self) { _timeStamp = timeStamp; _eventType = eventType; - self.internalData = [data mutableCopy]; + self.internalData = [NSMutableData dataWithData:data]; } return self; } +- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType +{ + return [self initWithTimeStamp:timeStamp midiEventType:eventType data:nil]; +} + - (NSString *)additionalEventDescription { return @""; @@ -113,19 +126,24 @@ + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType } } -+ (Class)subclassForEventType:(MusicEventType)eventType andData:(NSData *)data ++ (Class)subclassForEventType:(MIKMIDIEventType)eventType { Class result = nil; - MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data]; for (Class subclass in registeredMIKMIDIEventSubclasses) { - if ([subclass supportsMIKMIDIEventType:midiEventType]) { + if ([[subclass supportedMIDIEventTypes] containsObject:@(eventType)]) { result = subclass; break; } - } + } return result; } ++ (Class)subclassForMusicEventType:(MusicEventType)eventType andData:(NSData *)data +{ + MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data]; + return [self subclassForEventType:midiEventType]; +} + #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone diff --git a/Source/MIKMIDIEvent_SubclassMethods.h b/Source/MIKMIDIEvent_SubclassMethods.h index 2d703dac..81e7896b 100644 --- a/Source/MIKMIDIEvent_SubclassMethods.h +++ b/Source/MIKMIDIEvent_SubclassMethods.h @@ -23,15 +23,15 @@ + (void)registerSubclass:(Class)subclass; /** - * Subclasses of MIKMIDIEvent must override this method, and return YES for any - * MIKMIDIEventType values they support. MIKMIDIEvent uses this method to determine which - * subclass to use to represent a particular MIDI event type. + * Subclasses of MIKMIDIEvent must override this method, and return the MIKMIDIEventType + * values they support. MIKMIDIEvent uses this method to determine which + * subclass to use to represent a particular MIDI Event type. * - * @param type An MIKMIDIEventType value. + * Note that the older +supportsMIDIEventType: by default simply calls through to this method. * - * @return YES if the subclass supports type, NO otherwise. + * @return An NSArray of NSNumber instances containing MIKMIDIEventType values. */ -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type; ++ (NSArray *)supportedMIDIEventTypes; /** * The immutable counterpart class of the receiver. @@ -68,7 +68,7 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) MusicEventType eventType; +@property (nonatomic, readwrite) MIKMIDIEventType eventType; @property (nonatomic, strong, readwrite) NSData *metaData; @@ -83,4 +83,17 @@ - (NSString *)additionalEventDescription; +// Deprecated + +/** + * @deprecated This method has been replaced by +supportedMIDIEventTypes + * and by default simply calls through to that method. Subclasses + * no longer need implement this. + * + * @param type An MIKMIDIEventType value. + * + * @return YES if the subclass supports type, NO otherwise. + */ ++ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type DEPRECATED_ATTRIBUTE; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaCopyrightEvent.m b/Source/MIKMIDIMetaCopyrightEvent.m index 156e2b0e..77ba8c4c 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.m +++ b/Source/MIKMIDIMetaCopyrightEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaCopyrightEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaCopyright; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaCopyright)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaCopyrightEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaCopyrightEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaCuePointEvent.m b/Source/MIKMIDIMetaCuePointEvent.m index c7b672d6..501a288a 100644 --- a/Source/MIKMIDIMetaCuePointEvent.m +++ b/Source/MIKMIDIMetaCuePointEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaCuePointEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaCuePoint; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaCuePoint)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaCuePointEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaCuePointEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index c8e4ae92..855a0719 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMeta; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMeta)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.m b/Source/MIKMIDIMetaInstrumentNameEvent.m index b856ee0c..fe8f22ce 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.m +++ b/Source/MIKMIDIMetaInstrumentNameEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaInstrumentNameEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaInstrumentName; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaInstrumentName)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaInstrumentNameEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaInstrumentNameEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index 09fdb588..8dee8459 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaKeySignatureEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaKeySignature; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaKeySignature)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaKeySignatureEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaKeySignatureEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaLyricEvent.m b/Source/MIKMIDIMetaLyricEvent.m index f906a64e..5f20a1bc 100644 --- a/Source/MIKMIDIMetaLyricEvent.m +++ b/Source/MIKMIDIMetaLyricEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaLyricEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaLyricText; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaLyricText)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaLyricEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaLyricEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaMarkerTextEvent.m b/Source/MIKMIDIMetaMarkerTextEvent.m index 0d143a09..f8522189 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.m +++ b/Source/MIKMIDIMetaMarkerTextEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaMarkerTextEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaMarkerText; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaMarkerText)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaMarkerTextEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaMarkerTextEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaTextEvent.m b/Source/MIKMIDIMetaTextEvent.m index e46f6918..112c0480 100644 --- a/Source/MIKMIDIMetaTextEvent.m +++ b/Source/MIKMIDIMetaTextEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaTextEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaText; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaText)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaTextEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTextEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index ea075e46..13feb49d 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaTimeSignatureEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaTimeSignature; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaTimeSignature)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaTimeSignatureEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTimeSignatureEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.m b/Source/MIKMIDIMetaTrackSequenceNameEvent.m index 8cf06964..ff7c8fdc 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.m +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDIMetaTrackSequenceNameEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMetaTrackSequenceName; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaTrackSequenceName)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaTrackSequenceNameEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTrackSequenceNameEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 21ea9bd0..5e7f8a5a 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -18,7 +18,7 @@ @implementation MIKMIDINoteEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeMIDINoteMessage; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDINoteMessage)]; } + (Class)immutableCounterpartClass { return [MIKMIDINoteEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDINoteEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDITempoEvent.m b/Source/MIKMIDITempoEvent.m index b217ec3d..529c5ac6 100644 --- a/Source/MIKMIDITempoEvent.m +++ b/Source/MIKMIDITempoEvent.m @@ -17,7 +17,7 @@ @implementation MIKMIDITempoEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return type == MIKMIDIEventTypeExtendedTempo; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeExtendedTempo)]; } + (Class)immutableCounterpartClass { return [MIKMIDITempoEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDITempoEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 7ab35fb7..1f1c76ac 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -75,53 +75,72 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event const void *data = [event.data bytes]; switch (event.eventType) { - case kMusicEventType_NULL: + case MIKMIDIEventTypeNULL: break; - case kMusicEventType_ExtendedNote: + case MIKMIDIEventTypeExtendedNote: err = MusicTrackNewExtendedNoteEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewExtendedNoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; + + case MIKMIDIEventTypeExtendedControl: + NSLog(@"Events of type MIKMIDIEventTypeExtendedControl are unsupported because the underlying CoreMIDI API is deprecated."); + break; - case kMusicEventType_ExtendedTempo: + case MIKMIDIEventTypeExtendedTempo: err = MusicTrackNewExtendedTempoEvent(track, timeStamp, ((ExtendedTempoEvent *)data)->bpm); if (err) NSLog(@"MusicTrackNewExtendedTempoEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - case kMusicEventType_User: + case MIKMIDIEventTypeUser: err = MusicTrackNewUserEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewUserEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - case kMusicEventType_Meta: - err = MusicTrackNewMetaEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewMetaEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; - - case kMusicEventType_MIDINoteMessage: + case MIKMIDIEventTypeMIDINoteMessage: err = MusicTrackNewMIDINoteEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewMIDINoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - case kMusicEventType_MIDIChannelMessage: + case MIKMIDIEventTypeMIDIChannelMessage: err = MusicTrackNewMIDIChannelEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewMIDIChannelEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - case kMusicEventType_MIDIRawData: + case MIKMIDIEventTypeMIDIRawData: err = MusicTrackNewMIDIRawDataEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewMIDIRawDataEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - case kMusicEventType_Parameter: + case MIKMIDIEventTypeParameter: err = MusicTrackNewParameterEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewParameterEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - case kMusicEventType_AUPreset: + case MIKMIDIEventTypeAUPreset: err = MusicTrackNewAUPresetEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewAUPresetEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; + + case MIKMIDIEventTypeMeta: + case MIKMIDIEventTypeMetaSequence: + case MIKMIDIEventTypeMetaText: + case MIKMIDIEventTypeMetaCopyright: + case MIKMIDIEventTypeMetaTrackSequenceName: + case MIKMIDIEventTypeMetaInstrumentName: + case MIKMIDIEventTypeMetaLyricText: + case MIKMIDIEventTypeMetaMarkerText: + case MIKMIDIEventTypeMetaCuePoint: + case MIKMIDIEventTypeMetaMIDIChannelPrefix: + case MIKMIDIEventTypeMetaEndOfTrack: + case MIKMIDIEventTypeMetaTempoSetting: + case MIKMIDIEventTypeMetaSMPTEOffset: + case MIKMIDIEventTypeMetaTimeSignature: + case MIKMIDIEventTypeMetaKeySignature: + case MIKMIDIEventTypeMetaSequenceSpecificEvent: + err = MusicTrackNewMetaEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewMetaEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; } return !err; From 5bd27ff601ea52ec08de7a6686cb72e79bb348ad Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 10:50:21 -0700 Subject: [PATCH 026/284] Issue #59: Made -initWithTimeStamp:midiEventType:data: the designated initializer for MIKMIDIEvent. --- Source/MIKMIDIEvent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index 79785db2..fd22f499 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -148,7 +148,7 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) * @return For supported command types, an initialized MIKMIDIEvent subclass. Otherwise, an instance of * MIKMIDICommand itself. nil if there is an error. */ -- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(NSData *)data; +- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(NSData *)data NS_DESIGNATED_INITIALIZER; /** * The MIDI event type. From c02158839891bd19218dddb66d0d74353ef4afb7 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 10:51:04 -0700 Subject: [PATCH 027/284] Issue #57: Added mechanism to allow MIKMIDIEvent subclasses to specify the minimum internal data size they require. --- Source/MIKMIDIEvent.m | 12 +++++++++--- Source/MIKMIDIEvent_SubclassMethods.h | 8 ++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 26bb70b2..f20ca1d5 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -27,12 +27,12 @@ + (void)registerSubclass:(Class)subclass; [registeredMIKMIDIEventSubclasses addObject:subclass]; } -+ (BOOL)isMutable { return NO; } - + (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return [[self supportedMIDIEventTypes] containsObject:@(type)]; } + (NSArray *)supportedMIDIEventTypes { return @[]; } + (Class)immutableCounterpartClass; { return [MIKMIDIEvent class]; } + (Class)mutableCounterpartClass; { return [MIKMutableMIDIEvent class]; } ++ (BOOL)isMutable { return NO; } ++ (size_t)minimumDataSize { return 0; } + (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data { @@ -65,7 +65,13 @@ - (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMI if (self) { _timeStamp = timeStamp; _eventType = eventType; - self.internalData = [NSMutableData dataWithData:data]; + + NSMutableData *internalData = [NSMutableData dataWithData:data]; + size_t minSize = [[self class] minimumDataSize]; + if ([internalData length] < minSize) { + [internalData increaseLengthBy:(minSize - [internalData length])]; + } + _internalData = internalData; } return self; } diff --git a/Source/MIKMIDIEvent_SubclassMethods.h b/Source/MIKMIDIEvent_SubclassMethods.h index 81e7896b..9fc62047 100644 --- a/Source/MIKMIDIEvent_SubclassMethods.h +++ b/Source/MIKMIDIEvent_SubclassMethods.h @@ -58,6 +58,14 @@ */ + (BOOL)isMutable; +/** + * Subclasses of MIKMIDIEvent can override this to specify a minum internal data length + * necessary to hold their contents. For example, MIKMIDINoteEvent returns sizeof(MIDINoteMessage). + * + * @return A size_t value indicating the minimum size in bytes required to hold the receiver's data. + */ ++ (size_t)minimumDataSize; + /** * This is the property used internally by MIKMIDIEvent to store the raw data for * a MIDI packet. It is essentially the mutable backing store for MIKMIDIEvent's From 1d6c2758a664f3c42440a5eaea1db3e4ba5472da Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 11:12:54 -0700 Subject: [PATCH 028/284] Issue #57: Fixed exception/crash when setting properties on an empty MIKMIDIMetaTimeSignatureEvent. --- Source/MIKMIDIMetaTimeSignatureEvent.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index 13feb49d..739b75dd 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -21,6 +21,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaTimeSignat + (Class)immutableCounterpartClass { return [MIKMIDIMetaTimeSignatureEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTimeSignatureEvent class]; } + (BOOL)isMutable { return NO; } ++ (size_t)minimumDataSize { return [super minimumDataSize] + 4; /* Account for numerator, denominator, metronome, and 32nd note bytes */ } + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { @@ -44,8 +45,9 @@ - (void)setNumerator:(UInt8)numerator if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; NSMutableData *mutableMetaData = self.metaData.mutableCopy; + if ([mutableMetaData length] < 1) [mutableMetaData increaseLengthBy:1]; [mutableMetaData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&numerator length:1]; - [self setMetaData:[mutableMetaData copy]]; + [self setMetaData:mutableMetaData]; } - (UInt8)denominator @@ -59,9 +61,10 @@ - (void)setDenominator:(UInt8)denominator if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; NSMutableData *mutableMetaData = self.metaData.mutableCopy; + if ([mutableMetaData length] < 2) [mutableMetaData increaseLengthBy:2-[mutableMetaData length]]; UInt8 denominatorPower = log2(denominator); [mutableMetaData replaceBytesInRange:NSMakeRange(1, 1) withBytes:&denominatorPower length:1]; - [self setMetaData:[mutableMetaData copy]]; + [self setMetaData:mutableMetaData]; } - (UInt8)metronomePulse @@ -74,8 +77,9 @@ - (void)setMetronomePulse:(UInt8)metronomePulse if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; NSMutableData *mutableMetaData = self.metaData.mutableCopy; + if ([mutableMetaData length] < 3) [mutableMetaData increaseLengthBy:3-[mutableMetaData length]]; [mutableMetaData replaceBytesInRange:NSMakeRange(2, 1) withBytes:&metronomePulse length:1]; - [self setMetaData:[mutableMetaData copy]]; + [self setMetaData:mutableMetaData]; } - (UInt8)thirtySecondsPerQuarterNote @@ -88,8 +92,9 @@ - (void)setThirtySecondsPerQuarterNote:(UInt8)thirtySecondsPerQuarterNote if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; NSMutableData *mutableMetaData = self.metaData.mutableCopy; + if ([mutableMetaData length] < 4) [mutableMetaData increaseLengthBy:4-[mutableMetaData length]]; [mutableMetaData replaceBytesInRange:NSMakeRange(3, 1) withBytes:&thirtySecondsPerQuarterNote length:1]; - [self setMetaData:[mutableMetaData copy]]; + [self setMetaData:mutableMetaData]; } - (NSString *)additionalEventDescription From 02fe70b380cb07174d05e6b69a5af410719aa85d Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 11:13:18 -0700 Subject: [PATCH 029/284] Issue #57: Additional MIKMIDIEvent subclasses now specify their minimum data size (where necessary). --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 16 ++++++++-------- Source/MIKMIDIMetaEvent.m | 6 +++--- Source/MIKMIDIMetaKeySignatureEvent.m | 1 + Source/MIKMIDIMetaTextEvent.m | 1 + Source/MIKMIDINoteEvent.m | 1 + Source/MIKMIDITempoEvent.m | 1 + 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index c1d01f8f..b0031240 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -527,14 +527,16 @@ 839D932E19C3A2C9007589C3 /* MIKMIDIEvent.h */, 839D932D19C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h */, 839D932F19C3A2C9007589C3 /* MIKMIDIEvent.m */, - 839D933019C3A2C9007589C3 /* MIKMIDIEventIterator.h */, - 839D933119C3A2C9007589C3 /* MIKMIDIEventIterator.m */, + 839D934D19C3A2F5007589C3 /* MIKMIDINoteEvent.h */, + 839D934E19C3A2F5007589C3 /* MIKMIDINoteEvent.m */, + 839D936F19C3A319007589C3 /* MIKMIDITempoEvent.h */, + 839D937019C3A319007589C3 /* MIKMIDITempoEvent.m */, + 839D933B19C3A2F5007589C3 /* MIKMIDIMetaEvent.h */, + 839D933C19C3A2F5007589C3 /* MIKMIDIMetaEvent.m */, 839D933719C3A2F5007589C3 /* MIKMIDIMetaCopyrightEvent.h */, 839D933819C3A2F5007589C3 /* MIKMIDIMetaCopyrightEvent.m */, 839D933919C3A2F5007589C3 /* MIKMIDIMetaCuePointEvent.h */, 839D933A19C3A2F5007589C3 /* MIKMIDIMetaCuePointEvent.m */, - 839D933B19C3A2F5007589C3 /* MIKMIDIMetaEvent.h */, - 839D933C19C3A2F5007589C3 /* MIKMIDIMetaEvent.m */, 839D933D19C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h */, 839D933E19C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.m */, 839D933F19C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.h */, @@ -551,10 +553,8 @@ 839D934A19C3A2F5007589C3 /* MIKMIDIMetaTimeSignatureEvent.m */, 839D934B19C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.h */, 839D934C19C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.m */, - 839D934D19C3A2F5007589C3 /* MIKMIDINoteEvent.h */, - 839D934E19C3A2F5007589C3 /* MIKMIDINoteEvent.m */, - 839D936F19C3A319007589C3 /* MIKMIDITempoEvent.h */, - 839D937019C3A319007589C3 /* MIKMIDITempoEvent.m */, + 839D933019C3A2C9007589C3 /* MIKMIDIEventIterator.h */, + 839D933119C3A2C9007589C3 /* MIKMIDIEventIterator.m */, ); name = Events; sourceTree = ""; diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index 855a0719..b10dc361 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -21,6 +21,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMeta)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaEvent class]; } + (BOOL)isMutable { return NO; } ++ (size_t)minimumDataSize { return sizeof(MIDIMetaEvent); } - (NSString *)additionalEventDescription { @@ -43,7 +44,7 @@ - (UInt8)metadataType - (void)setMetadataType:(UInt8)metadataType { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - + MIDIMetaEvent *metaEvent = (MIDIMetaEvent*)[self.internalData bytes]; metaEvent->metaEventType = metadataType; } @@ -61,8 +62,7 @@ - (UInt32)metadataLength - (NSData *)metaData { - MIDIMetaEvent *metaEvent = (MIDIMetaEvent*)[self.internalData bytes]; - return [self.internalData subdataWithRange:NSMakeRange(MIKMIDIEventMetadataStartOffset, metaEvent->dataLength)]; + return [self.internalData subdataWithRange:NSMakeRange(MIKMIDIEventMetadataStartOffset, self.metadataLength)]; } - (void)setMetaData:(NSData *)metaData diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index 8dee8459..175c723c 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -21,6 +21,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaKeySignatu + (Class)immutableCounterpartClass { return [MIKMIDIMetaKeySignatureEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaKeySignatureEvent class]; } + (BOOL)isMutable { return NO; } ++ (size_t)minimumDataSize { return [super minimumDataSize] + 2; /* Account for key and scale bytes */ } + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { diff --git a/Source/MIKMIDIMetaTextEvent.m b/Source/MIKMIDIMetaTextEvent.m index 112c0480..c62e6cd5 100644 --- a/Source/MIKMIDIMetaTextEvent.m +++ b/Source/MIKMIDIMetaTextEvent.m @@ -33,6 +33,7 @@ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key - (NSString *)string { + if (![self.metaData length]) return nil; return [[NSString alloc] initWithData:self.metaData encoding:NSUTF8StringEncoding]; } diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 5e7f8a5a..f7641a93 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -22,6 +22,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDINoteMessag + (Class)immutableCounterpartClass { return [MIKMIDINoteEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDINoteEvent class]; } + (BOOL)isMutable { return NO; } ++ (size_t)minimumDataSize { return sizeof(MIDINoteMessage); } #pragma mark - Lifecycle diff --git a/Source/MIKMIDITempoEvent.m b/Source/MIKMIDITempoEvent.m index 529c5ac6..4ca0a4d9 100644 --- a/Source/MIKMIDITempoEvent.m +++ b/Source/MIKMIDITempoEvent.m @@ -21,6 +21,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeExtendedTempo) + (Class)immutableCounterpartClass { return [MIKMIDITempoEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDITempoEvent class]; } + (BOOL)isMutable { return NO; } ++ (size_t)minimumDataSize { return sizeof(ExtendedTempoEvent); } + (instancetype)tempoEventWithTimeStamp:(MusicTimeStamp)timeStamp tempo:(Float64)bpm; { From 0e3b7629b8336ce0041646f53ea5c0346b50cbc5 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 11:14:54 -0700 Subject: [PATCH 030/284] Fixed use of dot notation to call -mutableCopy in various places . --- Source/MIKMIDIMetaEvent.m | 2 +- Source/MIKMIDIMetaKeySignatureEvent.m | 4 ++-- Source/MIKMIDIMetaTimeSignatureEvent.m | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index b10dc361..4f5495ce 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -71,7 +71,7 @@ - (void)setMetaData:(NSData *)metaData MIDIMetaEvent *metaEvent = (MIDIMetaEvent*)[self.internalData bytes]; metaEvent->dataLength = (UInt32)[metaData length]; - NSMutableData *newMetaData = [self.internalData subdataWithRange:NSMakeRange(0, MIKMIDIEventMetadataStartOffset)].mutableCopy; + NSMutableData *newMetaData = [[self.internalData subdataWithRange:NSMakeRange(0, MIKMIDIEventMetadataStartOffset)] mutableCopy]; [newMetaData appendData:metaData]; self.internalData = newMetaData; } diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index 175c723c..baade9ae 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -41,7 +41,7 @@ - (void)setKey:(NSString *)key { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - NSMutableData *mutableMetaData = self.metaData.mutableCopy; + NSMutableData *mutableMetaData = [self.metaData mutableCopy]; [mutableMetaData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&key length:1]; [self setMetaData:[mutableMetaData copy]]; } @@ -55,7 +55,7 @@ - (void)setScale:(UInt8)scale { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - NSMutableData *mutableMetaData = self.metaData.mutableCopy; + NSMutableData *mutableMetaData = [self.metaData mutableCopy]; [mutableMetaData replaceBytesInRange:NSMakeRange(1, 1) withBytes:&scale length:1]; [self setMetaData:[mutableMetaData copy]]; } diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index 739b75dd..c0c375ae 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -44,7 +44,7 @@ - (void)setNumerator:(UInt8)numerator { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - NSMutableData *mutableMetaData = self.metaData.mutableCopy; + NSMutableData *mutableMetaData = [self.metaData mutableCopy]; if ([mutableMetaData length] < 1) [mutableMetaData increaseLengthBy:1]; [mutableMetaData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&numerator length:1]; [self setMetaData:mutableMetaData]; @@ -60,7 +60,7 @@ - (void)setDenominator:(UInt8)denominator { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - NSMutableData *mutableMetaData = self.metaData.mutableCopy; + NSMutableData *mutableMetaData = [self.metaData mutableCopy]; if ([mutableMetaData length] < 2) [mutableMetaData increaseLengthBy:2-[mutableMetaData length]]; UInt8 denominatorPower = log2(denominator); [mutableMetaData replaceBytesInRange:NSMakeRange(1, 1) withBytes:&denominatorPower length:1]; @@ -76,7 +76,7 @@ - (void)setMetronomePulse:(UInt8)metronomePulse { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - NSMutableData *mutableMetaData = self.metaData.mutableCopy; + NSMutableData *mutableMetaData = [self.metaData mutableCopy]; if ([mutableMetaData length] < 3) [mutableMetaData increaseLengthBy:3-[mutableMetaData length]]; [mutableMetaData replaceBytesInRange:NSMakeRange(2, 1) withBytes:&metronomePulse length:1]; [self setMetaData:mutableMetaData]; @@ -91,7 +91,7 @@ - (void)setThirtySecondsPerQuarterNote:(UInt8)thirtySecondsPerQuarterNote { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - NSMutableData *mutableMetaData = self.metaData.mutableCopy; + NSMutableData *mutableMetaData = [self.metaData mutableCopy]; if ([mutableMetaData length] < 4) [mutableMetaData increaseLengthBy:4-[mutableMetaData length]]; [mutableMetaData replaceBytesInRange:NSMakeRange(3, 1) withBytes:&thirtySecondsPerQuarterNote length:1]; [self setMetaData:mutableMetaData]; From 6fbec21a4d55c8a9e5131c44fad4fbe736c246ad Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 12:19:34 -0700 Subject: [PATCH 031/284] Moved MIKMIDITrack convenience initializer into protected subclass. It has always only been for internal use. --- Source/MIKMIDISequence.h | 3 ++- Source/MIKMIDISequence.m | 2 +- Source/MIKMIDITrack.h | 10 ---------- Source/MIKMIDITrack_Protected.h | 24 ++++++++++++++++++++++++ 4 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 Source/MIKMIDITrack_Protected.h diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 92929a60..0b7c1a7d 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -221,7 +221,8 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d // Properties /** - * The tempo track for the sequence. + * The tempo track for the sequence. Even in a new, empty sequence, + * this will return a tempo track to which tempo events can be added. */ @property (nonatomic, readonly) MIKMIDITrack *tempoTrack; diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 1bbdba62..8f91ec5f 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -9,6 +9,7 @@ #import "MIKMIDISequence.h" #import #import "MIKMIDITrack.h" +#import "MIKMIDITrack_Protected.h" #import "MIKMIDITempoEvent.h" #import "MIKMIDIMetaTimeSignatureEvent.h" #import "MIKMIDIDestinationEndpoint.h" @@ -19,7 +20,6 @@ const MusicTimeStamp MIKMIDISequenceLongestTrackLength = -1; - @interface MIKMIDISequence () @property (nonatomic) MusicSequence musicSequence; diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 6df39764..233b8458 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -158,16 +158,6 @@ */ - (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp atTimeStamp:(MusicTimeStamp)destTimeStamp; -/** - * Creates and initializes a new MIKMIDITrack. - * - * @param sequence The MIDI sequence the new track will belong to. - * @param musicTrack The MusicTrack to use as the backing for the new MIDI track. - * - * @note You should not call this method. To add a new track to a MIDI sequence use -[MIKMIDISequence addTrack]. - */ -+ (instancetype)trackWithSequence:(MIKMIDISequence *)sequence musicTrack:(MusicTrack)musicTrack; - /** * Sets a temporary length and loopInfo for the track. * diff --git a/Source/MIKMIDITrack_Protected.h b/Source/MIKMIDITrack_Protected.h new file mode 100644 index 00000000..9a2975c9 --- /dev/null +++ b/Source/MIKMIDITrack_Protected.h @@ -0,0 +1,24 @@ +// +// MIKMIDITrack_Protected.h +// MIKMIDI +// +// Created by Andrew Madsen on 2/25/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDITrack.h" + +@interface MIKMIDITrack () + +/** + * Creates and initializes a new MIKMIDITrack. + * + * @param sequence The MIDI sequence the new track will belong to. + * @param musicTrack The MusicTrack to use as the backing for the new MIDI track. + * + * @note You should not call this method. It is for internal MIKMIDI use only. + * To add a new track to a MIDI sequence use -[MIKMIDISequence addTrack]. + */ ++ (instancetype)trackWithSequence:(MIKMIDISequence *)sequence musicTrack:(MusicTrack)musicTrack; + +@end From ef867e2c0d279929203af6fada5df7895be229a2 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 12:20:14 -0700 Subject: [PATCH 032/284] Added new convenience initializer to make creating MIKMIDINoteEvents easier. --- Source/MIKMIDINoteEvent.h | 22 +++++++++++++++++++--- Source/MIKMIDINoteEvent.m | 16 ++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 38aa0102..40c479c9 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -18,9 +18,25 @@ /** * Convenience method for creating a new MIKMIDINoteEvent. * - * @param timeStamp The MusicTimeStamp for the event. + * @param timeStamp A MusicTimeStamp value indicating the timestamp for the event. + * @param note The note number for the event, from 0 to 127. 60 is middle C. + * @param velocity A number from 0-127 specifying the velocity of the note. + * @param duration The duration of the event in MusicTimeStamp units. + * @param channel The channel on which the MIDI event should be sent. * - * @param message The MIDINoteMessage for the event. + * @return An initialized MIKMIDINoteEvent instance, or nil if an error occurred. + */ ++ (instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp + note:(UInt8)note + velocity:(UInt8)velocity + duration:(Float32)duration + channel:(UInt8)channel; + +/** + * Convenience method for creating a new MIKMIDINoteEvent from a CoreMIDI MIDINoteMessage struct. + * + * @param timeStamp A MusicTimeStamp value indicating the timestamp for the event. + * @param message A MIDINoteMessage struct containing properties for the event. * * @return A new MIKMIDINoteEvent instance, or nil if there is an error. */ @@ -49,7 +65,7 @@ @property (nonatomic, readonly) UInt8 releaseVelocity; /** - * The duration of the event. + * The duration of the event in MusicTimeStamp units. */ @property (nonatomic, readonly) Float32 duration; diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index f7641a93..9462af80 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -26,6 +26,22 @@ + (size_t)minimumDataSize { return sizeof(MIDINoteMessage); } #pragma mark - Lifecycle ++ (instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp + note:(UInt8)note + velocity:(UInt8)velocity + duration:(Float32)duration + channel:(UInt8)channel +{ + MIDINoteMessage message = { + .note = note, + .velocity = velocity, + .channel = channel, + .duration = duration, + .releaseVelocity = 0, + }; + return [self noteEventWithTimeStamp:timeStamp message:message]; +} + + (instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDINoteMessage)message { NSData *data = [NSData dataWithBytes:&message length:sizeof(message)]; From 14d937d0b6e07c6ab7e3be76ff3b5ac27097a792 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 12:20:42 -0700 Subject: [PATCH 033/284] Added name property (shadow of parent's string property) to MIKMIDIMetaTrackSequenceNameEvent. --- Source/MIKMIDIMetaTrackSequenceNameEvent.h | 7 ++++++- Source/MIKMIDIMetaTrackSequenceNameEvent.m | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index e783b722..cd2bec2b 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -9,10 +9,12 @@ #import "MIKMIDIMetaTextEvent.h" /** - * A meta event containing track sequence information. + * A meta event containing a track name. */ @interface MIKMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTextEvent +@property (nonatomic, readonly) NSString *name; + @end /** @@ -20,8 +22,11 @@ */ @interface MIKMutableMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTrackSequenceNameEvent +@property (nonatomic, readwrite) NSString *name; + @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; @property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, readwrite) NSString *string; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.m b/Source/MIKMIDIMetaTrackSequenceNameEvent.m index ff7c8fdc..bb4f88cc 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.m +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.m @@ -22,13 +22,23 @@ + (Class)immutableCounterpartClass { return [MIKMIDIMetaTrackSequenceNameEvent c + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTrackSequenceNameEvent class]; } + (BOOL)isMutable { return NO; } ++ (NSSet *)keyPathsForValuesAffectingName +{ + return [NSSet setWithObjects:@"string", nil]; +} + +- (NSString *)name { return self.string; } + @end @implementation MIKMutableMIDIMetaTrackSequenceNameEvent +- (void)setName:(NSString *)name { self.string = name; } + @dynamic timeStamp; @dynamic metadataType; @dynamic metaData; +@dynamic string; + (BOOL)isMutable { return YES; } From 1050a368739e9092f097c0fb99d1dc948667e29e Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 12:21:00 -0700 Subject: [PATCH 034/284] Added MIKMIDITrack_Protected.h to the framework project. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index b0031240..b0ea6f8f 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -104,6 +104,7 @@ 9D74EF9317A713A100BEE89F /* MIKMIDIUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF6017A713A100BEE89F /* MIKMIDIUtilities.m */; }; 9D74EF9417A713A100BEE89F /* NSUIApplication+MIKMIDI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF6117A713A100BEE89F /* NSUIApplication+MIKMIDI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF9517A713A100BEE89F /* NSUIApplication+MIKMIDI.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF6217A713A100BEE89F /* NSUIApplication+MIKMIDI.m */; }; + 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */; }; 9D877D841A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D877D821A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D877D851A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877D831A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m */; }; 9D877DFC1A670261001BA997 /* MIKMIDIProgramChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877DFA1A670261001BA997 /* MIKMIDIProgramChangeCommand.m */; }; @@ -339,6 +340,7 @@ 9D74EF6017A713A100BEE89F /* MIKMIDIUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIUtilities.m; sourceTree = ""; }; 9D74EF6117A713A100BEE89F /* NSUIApplication+MIKMIDI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUIApplication+MIKMIDI.h"; sourceTree = ""; }; 9D74EF6217A713A100BEE89F /* NSUIApplication+MIKMIDI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUIApplication+MIKMIDI.m"; sourceTree = ""; }; + 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITrack_Protected.h; sourceTree = ""; }; 9D877D821A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientSourceEndpoint.h; sourceTree = ""; }; 9D877D831A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientSourceEndpoint.m; sourceTree = ""; }; 9D877DF91A670261001BA997 /* MIKMIDIProgramChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIProgramChangeCommand.h; sourceTree = ""; }; @@ -394,6 +396,7 @@ 839D936B19C3A30B007589C3 /* MIKMIDISequence.h */, 839D936C19C3A30B007589C3 /* MIKMIDISequence.m */, 839D937119C3A319007589C3 /* MIKMIDITrack.h */, + 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */, 839D937219C3A319007589C3 /* MIKMIDITrack.m */, 9DEE37BF1A9D66C2007B7FC7 /* Events */, ); @@ -704,6 +707,7 @@ 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, + 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, 9D74EF8E17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.h in Headers */, 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */, 9D74EF9017A713A100BEE89F /* MIKMIDISystemMessageCommand.h in Headers */, From c839bb71b5a519b70816c3c6a4ddef4430e690d7 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 25 Feb 2015 12:21:16 -0700 Subject: [PATCH 035/284] Minor cleanup of MIKMIDITempoEvent.h. --- Source/MIKMIDITempoEvent.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Source/MIKMIDITempoEvent.h b/Source/MIKMIDITempoEvent.h index 42a8a037..d859a11d 100644 --- a/Source/MIKMIDITempoEvent.h +++ b/Source/MIKMIDITempoEvent.h @@ -13,12 +13,6 @@ */ @interface MIKMIDITempoEvent : MIKMIDIEvent -/** - * The beats per minute of the tempo event. - */ -@property (nonatomic, readonly) Float64 bpm; - - /** * Creates and initializes a new MIKMIDITempoEvent. * @@ -30,8 +24,12 @@ */ + (instancetype)tempoEventWithTimeStamp:(MusicTimeStamp)timeStamp tempo:(Float64)bpm; -@end +/** + * The beats per minute of the tempo event. + */ +@property (nonatomic, readonly) Float64 bpm; +@end /** * The mutable counterpart of MIKMIDITempoEvent. From 90db6d319336ce11a4c9a93223fbcf51a49c4e02 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 26 Feb 2015 10:20:44 -0700 Subject: [PATCH 036/284] Made MIKMIDIEventTypeExtendedControl platform conditional. It's not available on iOS. --- Source/MIKMIDIEvent.h | 4 +++- Source/MIKMIDITrack.m | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index fd22f499..fa2e5dd9 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -46,8 +46,10 @@ typedef NS_ENUM(NSUInteger, MIKMIDIEventType) MIKMIDIEventTypeMetaKeySignature, MIKMIDIEventTypeMetaSequenceSpecificEvent, - // Deprecated, and unsupported. +#if !TARGET_OS_IPHONE + // Deprecated, and unsupported. Unavailable on iOS. MIKMIDIEventTypeExtendedControl = kMusicEventType_ExtendedControl, +#endif }; typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 1f1c76ac..0ae83ef8 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -82,10 +82,12 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event err = MusicTrackNewExtendedNoteEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewExtendedNoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - + +#if !TARGET_OS_IPHONE // Unavailable altogether on iOS. case MIKMIDIEventTypeExtendedControl: NSLog(@"Events of type MIKMIDIEventTypeExtendedControl are unsupported because the underlying CoreMIDI API is deprecated."); break; +#endif case MIKMIDIEventTypeExtendedTempo: err = MusicTrackNewExtendedTempoEvent(track, timeStamp, ((ExtendedTempoEvent *)data)->bpm); From ee1fa3f0ad32ae36c28f6fc9d9bac16a574610f9 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 26 Feb 2015 10:21:48 -0700 Subject: [PATCH 037/284] Fixed 'auto property synthesis will not synthesize property' warning in Xcode 6.3 beta for new -name property on MIKMutableMIDIMetaTrackSequenceNameEvent. --- Source/MIKMIDIMetaTrackSequenceNameEvent.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.m b/Source/MIKMIDIMetaTrackSequenceNameEvent.m index bb4f88cc..fbc30fd9 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.m +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.m @@ -35,6 +35,7 @@ @implementation MIKMutableMIDIMetaTrackSequenceNameEvent - (void)setName:(NSString *)name { self.string = name; } +@dynamic name; @dynamic timeStamp; @dynamic metadataType; @dynamic metaData; From ae7160b1c964dfdd675064a25f3e755f79974fc2 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 26 Feb 2015 10:40:57 -0700 Subject: [PATCH 038/284] MIDI Files Testbed automatically selects the first non-Bluetooth, non-Network MIDI device on launch. --- .../Source/MIKMainWindowController.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m index 8d2f6ec3..4764fb48 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -35,6 +35,12 @@ - (void)windowDidLoad [super windowDidLoad]; self.sequencer = [MIKMIDISequencer sequencer]; + + NSPredicate *nonBluetoothNetworkPredicate = [NSPredicate predicateWithBlock:^BOOL(MIKMIDIDevice *device, NSDictionary *b) { + return ![device.name isEqualToString:@"Bluetooth"] && ![device.name isEqualToString:@"Network"]; + }]; + NSArray *devices = [self.deviceManager.availableDevices filteredArrayUsingPredicate:nonBluetoothNetworkPredicate]; + if (!self.device) self.device = [devices firstObject]; } #pragma mark - Actions @@ -110,6 +116,8 @@ - (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error // So audio can be heard self.endpointSynth = [[MIKMIDIEndpointSynthesizer alloc] initWithMIDISource:source]; + NSString *soundfontFile = [@"~/Desktop/test.sf2" stringByExpandingTildeInPath]; + [self.endpointSynth loadSoundfontFromFileAtURL:[NSURL fileURLWithPath:soundfontFile] error:NULL]; return YES; } @@ -141,7 +149,7 @@ - (void)setDevice:(MIKMIDIDevice *)device { if (device != _device) { [self disconnectFromDevice]; - + NSError *error = nil; if (![self connectToDevice:device error:&error]) { [self presentError:error]; From 73774df473c960b9a2565eddcd8acdc2c47b0432 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 26 Feb 2015 12:15:25 -0700 Subject: [PATCH 039/284] Renamed MIKMIDISynthesizer's instrument property to instrumentUnit (deprecating old name). Made its call-setter-only-from-subclass policy explicit. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 6 ++++++ Source/MIKMIDISynthesizer.h | 9 ++++++++- Source/MIKMIDISynthesizer.m | 17 ++++++++++++----- Source/MIKMIDISynthesizer_SubclassMethods.h | 15 +++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 Source/MIKMIDISynthesizer_SubclassMethods.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index b0ea6f8f..6f56cca5 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -52,6 +52,8 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; + 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; }; + 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; }; 9D74EEBF17A712E900BEE89F /* MIKMIDI-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EEBE17A712E900BEE89F /* MIKMIDI-Prefix.pch */; }; 9D74EEC417A712F100BEE89F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9D74EEC117A712F100BEE89F /* InfoPlist.strings */; }; 9D74EF6317A713A100BEE89F /* MIKMIDI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3017A713A100BEE89F /* MIKMIDI.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -280,6 +282,7 @@ 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; + 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizer_SubclassMethods.h; sourceTree = ""; }; 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MIKMIDI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9D74EEA817A7129300BEE89F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 9D74EEAB17A7129300BEE89F /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -502,6 +505,7 @@ isa = PBXGroup; children = ( 9DB366EE1A964C55001D1CF3 /* MIKMIDISynthesizer.h */, + 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */, 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */, 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */, 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */, @@ -678,6 +682,7 @@ 9D74EF6817A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h in Headers */, 839D935719C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.h in Headers */, 839D933219C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h in Headers */, + 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 839D935519C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h in Headers */, 9DF99E7D18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h in Headers */, 839D935F19C3A2F5007589C3 /* MIKMIDIMetaTextEvent.h in Headers */, @@ -732,6 +737,7 @@ 9DAF8B5A1A7B007300F46528 /* MIKMIDIOutputPort.h in Headers */, 9DAF8B751A7B00A700F46528 /* MIKMIDIMetaMarkerTextEvent.h in Headers */, 9DAF8B601A7B008A00F46528 /* MIKMIDICommand.h in Headers */, + 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 9DAF8B551A7B007300F46528 /* MIKMIDIObject.h in Headers */, 9DAF8B8D1A7B031F00F46528 /* MIKMIDI-iOS-Prefix.pch in Headers */, 9DAF8B731A7B00A700F46528 /* MIKMIDIMetaKeySignatureEvent.h in Headers */, diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 45a40a90..3696933f 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -115,7 +115,7 @@ * * @see -setupAUGraph */ -@property (nonatomic) AudioUnit instrument; +@property (nonatomic, readonly) AudioUnit instrumentUnit; /** * The AUGraph for the instrument. @@ -127,4 +127,11 @@ */ @property (nonatomic) AUGraph graph; +// Deprecated + +/** + * @deprecated This has been (renamed to) instrumentUnit. Use that instead. + */ +@property (nonatomic) AudioUnit instrument DEPRECATED_ATTRIBUTE; + @end diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 0e3144e7..c97f1a2d 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -8,6 +8,7 @@ #import "MIKMIDISynthesizer.h" #import "MIKMIDICommand.h" +#import "MIKMIDISynthesizer_SubclassMethods.h" @implementation MIKMIDISynthesizer @@ -48,7 +49,7 @@ - (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument; UInt32 bankSelectStatus = 0xB0 | channel; UInt32 programChangeStatus = 0xC0 | channel; - AudioUnit instrumentUnit = self.instrument; + AudioUnit instrumentUnit = self.instrumentUnit; OSStatus err = MusicDeviceMIDIEvent(instrumentUnit, bankSelectStatus, 0x00, bankSelectMSB, 0); if (err) { NSLog(@"MusicDeviceMIDIEvent() (MSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); @@ -89,7 +90,7 @@ - (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error instrumentData.presetID = 0; // set the kAUSamplerProperty_LoadPresetFromBank property - err = AudioUnitSetProperty(self.instrument, + err = AudioUnitSetProperty(self.instrumentUnit, kAUSamplerProperty_LoadInstrument, kAudioUnitScope_Global, 0, @@ -113,7 +114,7 @@ - (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error return NO; } - err = AudioUnitSetProperty(self.instrument, + err = AudioUnitSetProperty(self.instrumentUnit, kMusicDeviceProperty_SoundBankFSRef, kAudioUnitScope_Global, 0, &fsRef, sizeof(fsRef)); @@ -199,7 +200,7 @@ - (BOOL)setupAUGraph } self.graph = graph; - self.instrument = instrumentUnit; + self.instrumentUnit = instrumentUnit; return YES; } @@ -244,9 +245,15 @@ - (void)setGraph:(AUGraph)graph - (void)handleMIDIMessages:(NSArray *)commands { for (MIKMIDICommand *command in commands) { - OSStatus err = MusicDeviceMIDIEvent(self.instrument, command.commandType, command.dataByte1, command.dataByte2, 0); + OSStatus err = MusicDeviceMIDIEvent(self.instrumentUnit, command.commandType, command.dataByte1, command.dataByte2, 0); if (err) NSLog(@"Unable to send MIDI command to synthesizer %@: %i", command, err); } } +#pragma mark - Deprecated + ++ (NSSet *)keyPathsForValuesAffectingInstrument { return [NSSet setWithObjects:@"instrumentUnit", nil]; } +- (AudioUnit)instrument { return self.instrumentUnit; } +- (void)setInstrument:(AudioUnit)instrument { self.instrumentUnit = instrument; } + @end diff --git a/Source/MIKMIDISynthesizer_SubclassMethods.h b/Source/MIKMIDISynthesizer_SubclassMethods.h new file mode 100644 index 00000000..9034acdc --- /dev/null +++ b/Source/MIKMIDISynthesizer_SubclassMethods.h @@ -0,0 +1,15 @@ +// +// MIKMIDISynthesizer_SubclassMethods.h +// MIKMIDI +// +// Created by Andrew Madsen on 2/26/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDISynthesizer.h" + +@interface MIKMIDISynthesizer () + +@property (nonatomic, readwrite) AudioUnit instrumentUnit; + +@end \ No newline at end of file From efbdafbcfa8046c8eee869fe3c1a5c59602d540b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 26 Feb 2015 12:16:06 -0700 Subject: [PATCH 040/284] Better documentation in MIKMIDIProgramChangeCommand. --- Source/MIKMIDIProgramChangeCommand.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/MIKMIDIProgramChangeCommand.h b/Source/MIKMIDIProgramChangeCommand.h index 9e6be712..5a1aad29 100644 --- a/Source/MIKMIDIProgramChangeCommand.h +++ b/Source/MIKMIDIProgramChangeCommand.h @@ -10,9 +10,16 @@ /** * A MIDI program change message. + * + * Program change messages indicate a change in the patch number. + * These messages can be sent to to a MIDI device or synthesizer to + * change the instrument the instrument/voice being used to synthesize MIDI. */ @interface MIKMIDIProgramChangeCommand : MIKMIDIChannelVoiceCommand +/** + * The program (aka patch) number. From 0-127. + */ @property (nonatomic, readonly) NSUInteger programNumber; @end From c576d6cbd967ecd1a3387442e4727ab9650ec540 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 26 Feb 2015 12:20:16 -0700 Subject: [PATCH 041/284] Made MIKMIDISynthesizer_SubclassMethods.h framework public. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 6f56cca5..f3075a59 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -52,8 +52,8 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; - 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; }; - 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; }; + 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EEBF17A712E900BEE89F /* MIKMIDI-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EEBE17A712E900BEE89F /* MIKMIDI-Prefix.pch */; }; 9D74EEC417A712F100BEE89F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9D74EEC117A712F100BEE89F /* InfoPlist.strings */; }; 9D74EF6317A713A100BEE89F /* MIKMIDI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3017A713A100BEE89F /* MIKMIDI.h */; settings = {ATTRIBUTES = (Public, ); }; }; From b7f500bfb1febe117a93bfe6d39ff84680f1729d Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 1 Mar 2015 19:53:50 -0700 Subject: [PATCH 042/284] Fixed warning in NSLog statement. --- Source/MIKMIDITrack.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 24b7bd29..37637de7 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -146,7 +146,7 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event break; default: err = -1; - NSLog(@"Warning: %s attempted to insert unknown event type %d.", __PRETTY_FUNCTION__, event.eventType); + NSLog(@"Warning: %s attempted to insert unknown event type %lu.", __PRETTY_FUNCTION__, event.eventType); break; } From 6b13587bfa1b21f134f554b48f3302869c57313f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 3 Mar 2015 08:38:04 -0700 Subject: [PATCH 043/284] Issue #48: MIKMIDISequencer playback no longer stalls when mouse is held down. --- Source/MIKMIDISequencer.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 45c1927d..226ba512 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -140,11 +140,12 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi self.pendingNoteOffs = [NSMutableDictionary dictionary]; self.pendingNoteOffMIDITimeStamps = [NSMutableOrderedSet orderedSet]; self.lastProcessedMIDITimeStamp = midiTimeStamp - 1; - self.processingTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 - target:self - selector:@selector(processingTimerFired:) - userInfo:nil - repeats:YES]; + NSTimer *processingTimer = [NSTimer timerWithTimeInterval:0.05 + target:self + selector:@selector(processingTimerFired:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:processingTimer forMode:NSRunLoopCommonModes]; [self.processingTimer fire]; } From 624f6bd8c31d1ba8ac6c37826028474a41972797 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 3 Mar 2015 08:41:46 -0700 Subject: [PATCH 044/284] Better main window sizing in MIDI Files Testbed. --- .../Resources/Base.lproj/MainWindow.xib | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index 624a825e..6c652dbd 100644 --- a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -42,6 +42,10 @@ + + + + @@ -89,6 +93,7 @@ + @@ -103,7 +108,6 @@ NSIsNotNil - @@ -112,6 +116,7 @@ + @@ -122,6 +127,7 @@ + From b9e46e3918ce6e5fe10b6a5190740a42b13e2a5a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 3 Mar 2015 08:43:16 -0700 Subject: [PATCH 045/284] Fixed drag/drop of MIDI files onto MIDI Files Testbed to load them. --- .../MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index 6c652dbd..33d55fe6 100644 --- a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -46,6 +46,9 @@ + + + From 986ebd5bbe5ab52c6358cfc497c7e0716094ef6b Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 3 Mar 2015 12:12:53 -0600 Subject: [PATCH 046/284] Removed override of isUsingAppleSynth property --- Source/MIKMIDIEndpointSynthesizer.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/MIKMIDIEndpointSynthesizer.m b/Source/MIKMIDIEndpointSynthesizer.m index 3034ab28..05ca5e3f 100644 --- a/Source/MIKMIDIEndpointSynthesizer.m +++ b/Source/MIKMIDIEndpointSynthesizer.m @@ -20,7 +20,6 @@ @interface MIKMIDIEndpointSynthesizer () @property (nonatomic, strong, readwrite) MIKMIDIEndpoint *endpoint; @property (nonatomic, strong) id connectionToken; -@property (readonly, nonatomic) BOOL isUsingAppleSynth; @end From 45f5d9fffe4bb9181edddbd6ca4dd85fb4b97e83 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 09:34:05 -0700 Subject: [PATCH 047/284] MIDI Files Testbed: Changed MIKMIDISequenceView to use built in MIKMIDITrack method for getting note events. --- Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m index 937f6821..8fb53cdd 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m +++ b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m @@ -38,9 +38,7 @@ - (void)drawRect:(NSRect)dirtyRect NSInteger index=0; for (MIKMIDITrack *track in self.sequence.tracks) { - for (MIKMIDINoteEvent *note in [track events]) { - if (note.eventType != kMusicEventType_MIDINoteMessage) continue; - + for (MIKMIDINoteEvent *note in [track notes]) { NSColor *noteColor = [self.sequence.tracks count] <= 2 ? [self colorForNote:note] : [self colorForTrackAtIndex:index]; [[NSColor blackColor] setStroke]; From cc127f8dbd38866e5198e5f380cc2c3d29ed31d3 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 10:12:44 -0700 Subject: [PATCH 048/284] MIDI Files Testbed: Let the run loop spin a little after accepting a drop to give the drop a chance to finish. Aids debugging. --- Examples/MIDI Files Testbed/Source/MIKMainWindowController.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m index 4764fb48..cf994788 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -79,7 +79,9 @@ - (IBAction)togglePlayback:(id)sender - (void)midiSequenceView:(MIKMIDISequenceView *)sequenceView receivedDroppedMIDIFiles:(NSArray *)midiFiles { - [self loadMIDIFile:[midiFiles firstObject]]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self loadMIDIFile:[midiFiles firstObject]]; + }); } #pragma mark - Private From 48354249884f2b4f5c0eb6e03f3f350efcdca4a8 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 10:13:06 -0700 Subject: [PATCH 049/284] Fixed ARC warning in MIKMIDIMetaCopyrightEvent.m. --- Source/MIKMIDIMetaCopyrightEvent.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIMetaCopyrightEvent.m b/Source/MIKMIDIMetaCopyrightEvent.m index 77ba8c4c..738464bc 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.m +++ b/Source/MIKMIDIMetaCopyrightEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaCopyrightEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaCopyrightEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaCopyrightEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaCopyrightEvent From 5ba3ee57bc98803d4b256c308d4de80dad4805bf Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 10:23:42 -0700 Subject: [PATCH 050/284] Issue #63: Added MIKMIDIChannelEvent, and subclasses MIKMIDIControlChangeEvent and MIKMIDIProgramChangeEvent. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 60 ++++++++++- Source/MIKMIDIChannelEvent.h | 50 ++++++++++ Source/MIKMIDIChannelEvent.m | 104 ++++++++++++++++++++ Source/MIKMIDIControlChangeEvent.h | 45 +++++++++ Source/MIKMIDIControlChangeEvent.m | 91 +++++++++++++++++ Source/MIKMIDIEvent.h | 24 ++++- Source/MIKMIDIEvent.m | 12 ++- Source/MIKMIDIProgramChangeEvent.h | 40 ++++++++ Source/MIKMIDIProgramChangeEvent.m | 70 +++++++++++++ 9 files changed, 488 insertions(+), 8 deletions(-) create mode 100644 Source/MIKMIDIChannelEvent.h create mode 100644 Source/MIKMIDIChannelEvent.m create mode 100644 Source/MIKMIDIControlChangeEvent.h create mode 100644 Source/MIKMIDIControlChangeEvent.m create mode 100644 Source/MIKMIDIProgramChangeEvent.h create mode 100644 Source/MIKMIDIProgramChangeEvent.m diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index c76598f9..d56b2bc7 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -52,9 +52,9 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; + 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EEBF17A712E900BEE89F /* MIKMIDI-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EEBE17A712E900BEE89F /* MIKMIDI-Prefix.pch */; }; 9D74EEC417A712F100BEE89F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9D74EEC117A712F100BEE89F /* InfoPlist.strings */; }; 9D74EF6317A713A100BEE89F /* MIKMIDI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3017A713A100BEE89F /* MIKMIDI.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -108,6 +108,10 @@ 9D74EF9417A713A100BEE89F /* NSUIApplication+MIKMIDI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF6117A713A100BEE89F /* NSUIApplication+MIKMIDI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF9517A713A100BEE89F /* NSUIApplication+MIKMIDI.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF6217A713A100BEE89F /* NSUIApplication+MIKMIDI.m */; }; 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */; }; + 9D84951C1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; }; + 9D84951D1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; }; + 9D84951E1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; + 9D84951F1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; 9D877D841A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D877D821A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D877D851A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877D831A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m */; }; 9D877DFC1A670261001BA997 /* MIKMIDIProgramChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877DFA1A670261001BA997 /* MIKMIDIProgramChangeCommand.m */; }; @@ -231,6 +235,14 @@ 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; + 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; }; + 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; }; + 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; + 9DEF1CAB1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; + 9DEF1CAE1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; }; + 9DEF1CAF1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; }; + 9DEF1CB01AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */; }; + 9DEF1CB11AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */; }; 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E771831841A004EE5F4 /* MIKMIDICommandThrottler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DF99E7A1831841A004EE5F4 /* MIKMIDICommandThrottler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF99E781831841A004EE5F4 /* MIKMIDICommandThrottler.m */; }; 9DF99E7D18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E7B18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -345,6 +357,8 @@ 9D74EF6117A713A100BEE89F /* NSUIApplication+MIKMIDI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUIApplication+MIKMIDI.h"; sourceTree = ""; }; 9D74EF6217A713A100BEE89F /* NSUIApplication+MIKMIDI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUIApplication+MIKMIDI.m"; sourceTree = ""; }; 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITrack_Protected.h; sourceTree = ""; }; + 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIProgramChangeEvent.h; sourceTree = ""; }; + 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIProgramChangeEvent.m; sourceTree = ""; }; 9D877D821A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientSourceEndpoint.h; sourceTree = ""; }; 9D877D831A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientSourceEndpoint.m; sourceTree = ""; }; 9D877DF91A670261001BA997 /* MIKMIDIProgramChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIProgramChangeCommand.h; sourceTree = ""; }; @@ -365,6 +379,10 @@ 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizerInstrument.m; sourceTree = ""; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelEvent.h; sourceTree = ""; }; + 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelEvent.m; sourceTree = ""; }; + 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIControlChangeEvent.h; sourceTree = ""; }; + 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIControlChangeEvent.m; sourceTree = ""; }; 9DF99E771831841A004EE5F4 /* MIKMIDICommandThrottler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommandThrottler.h; sourceTree = ""; }; 9DF99E781831841A004EE5F4 /* MIKMIDICommandThrottler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandThrottler.m; sourceTree = ""; }; 9DF99E7B18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPrivateUtilities.h; sourceTree = ""; }; @@ -539,6 +557,17 @@ 839D934E19C3A2F5007589C3 /* MIKMIDINoteEvent.m */, 839D936F19C3A319007589C3 /* MIKMIDITempoEvent.h */, 839D937019C3A319007589C3 /* MIKMIDITempoEvent.m */, + 9DEF1CB31AA6801F00E10273 /* Channel Events */, + 9DEF1CB21AA6801600E10273 /* Meta Events */, + 839D933019C3A2C9007589C3 /* MIKMIDIEventIterator.h */, + 839D933119C3A2C9007589C3 /* MIKMIDIEventIterator.m */, + ); + name = Events; + sourceTree = ""; + }; + 9DEF1CB21AA6801600E10273 /* Meta Events */ = { + isa = PBXGroup; + children = ( 839D933B19C3A2F5007589C3 /* MIKMIDIMetaEvent.h */, 839D933C19C3A2F5007589C3 /* MIKMIDIMetaEvent.m */, 839D933719C3A2F5007589C3 /* MIKMIDIMetaCopyrightEvent.h */, @@ -561,10 +590,21 @@ 839D934A19C3A2F5007589C3 /* MIKMIDIMetaTimeSignatureEvent.m */, 839D934B19C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.h */, 839D934C19C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.m */, - 839D933019C3A2C9007589C3 /* MIKMIDIEventIterator.h */, - 839D933119C3A2C9007589C3 /* MIKMIDIEventIterator.m */, ); - name = Events; + name = "Meta Events"; + sourceTree = ""; + }; + 9DEF1CB31AA6801F00E10273 /* Channel Events */ = { + isa = PBXGroup; + children = ( + 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */, + 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */, + 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */, + 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */, + 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */, + 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */, + ); + name = "Channel Events"; sourceTree = ""; }; 9DF99E731831837C004EE5F4 /* Device Support */ = { @@ -670,6 +710,7 @@ 9D74EF6B17A713A100BEE89F /* MIKMIDIDestinationEndpoint.h in Headers */, 9D74EF6D17A713A100BEE89F /* MIKMIDIDevice.h in Headers */, 9D74EF6F17A713A100BEE89F /* MIKMIDIDeviceManager.h in Headers */, + 9D84951C1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */, 9D877D841A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h in Headers */, 9D74EF7117A713A100BEE89F /* MIKMIDIEndpoint.h in Headers */, 9D74EF7317A713A100BEE89F /* MIKMIDIEntity.h in Headers */, @@ -682,6 +723,7 @@ 839D935319C3A2F5007589C3 /* MIKMIDIMetaEvent.h in Headers */, 9D74EF6817A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h in Headers */, 839D935719C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.h in Headers */, + 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 839D933219C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h in Headers */, 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 839D935519C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h in Headers */, @@ -690,6 +732,7 @@ 839D936919C3A303007589C3 /* MIKMIDIPlayer.h in Headers */, 839D936519C3A2F5007589C3 /* MIKMIDINoteEvent.h in Headers */, 839D936D19C3A30B007589C3 /* MIKMIDISequence.h in Headers */, + 9DEF1CAE1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */, 839D937319C3A319007589C3 /* MIKMIDITempoEvent.h in Headers */, 839D936119C3A2F5007589C3 /* MIKMIDIMetaTimeSignatureEvent.h in Headers */, 9DB366F61A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */, @@ -737,6 +780,7 @@ 9DAF8B5B1A7B007300F46528 /* MIKMIDIEndpoint.h in Headers */, 9DAF8B5A1A7B007300F46528 /* MIKMIDIOutputPort.h in Headers */, 9DAF8B751A7B00A700F46528 /* MIKMIDIMetaMarkerTextEvent.h in Headers */, + 9DEF1CAF1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */, 9DAF8B601A7B008A00F46528 /* MIKMIDICommand.h in Headers */, 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 9DAF8B551A7B007300F46528 /* MIKMIDIObject.h in Headers */, @@ -745,6 +789,7 @@ 9DAF8B7D1A7B00B100F46528 /* MIKMIDIEndpointSynthesizer.h in Headers */, 9DAF8B801A7B00B100F46528 /* MIKMIDIUtilities.h in Headers */, 9DAF8B631A7B008A00F46528 /* MIKMIDINoteOnCommand.h in Headers */, + 9D84951D1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */, 9DAF8B5E1A7B007300F46528 /* MIKMIDIDestinationEndpoint.h in Headers */, 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */, 9DAF8B7B1A7B00A700F46528 /* MIKMIDIPlayer.h in Headers */, @@ -775,6 +820,7 @@ 9DAF8B6F1A7B00A700F46528 /* MIKMIDIMetaCopyrightEvent.h in Headers */, 9DAF8B531A7B005C00F46528 /* MIKMIDIErrors.h in Headers */, 9DAF8B7F1A7B00B100F46528 /* MIKMIDISequencer.h in Headers */, + 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9DAF8B6E1A7B00A700F46528 /* MIKMIDIEventIterator.h in Headers */, 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 9DAF8B571A7B007300F46528 /* MIKMIDIEntity.h in Headers */, @@ -886,6 +932,7 @@ files = ( 9D74EF6517A713A100BEE89F /* MIKMIDIChannelVoiceCommand.m in Sources */, 839D936619C3A2F5007589C3 /* MIKMIDINoteEvent.m in Sources */, + 9D84951E1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */, 839D936419C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, 9D74EF6717A713A100BEE89F /* MIKMIDICommand.m in Sources */, 9D74EF6A17A713A100BEE89F /* MIKMIDIControlChangeCommand.m in Sources */, @@ -910,7 +957,9 @@ 9D74EF7817A713A100BEE89F /* MIKMIDIInputPort.m in Sources */, 9D74EF7A17A713A100BEE89F /* MIKMIDIMapping.m in Sources */, 9D74EF7C17A713A100BEE89F /* MIKMIDIMappingGenerator.m in Sources */, + 9DEF1CB01AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */, 833B73DF1A26346F00E0CC9F /* MIKMIDIClock.m in Sources */, + 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */, 9D74EF7E17A713A100BEE89F /* MIKMIDIMappingManager.m in Sources */, 9D74EF8017A713A100BEE89F /* MIKMIDINoteOffCommand.m in Sources */, 833B73DB1A262FE100E0CC9F /* MIKMIDISequencer.m in Sources */, @@ -961,6 +1010,7 @@ 9DAF8B331A7AFF6700F46528 /* MIKMIDISystemExclusiveCommand.m in Sources */, 9DAF8B341A7AFF6700F46528 /* MIKMIDIProgramChangeCommand.m in Sources */, 9DAF8B351A7AFF6700F46528 /* MIKMIDIMapping.m in Sources */, + 9D84951F1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */, 9DAF8B361A7AFF6700F46528 /* MIKMIDIMappingGenerator.m in Sources */, 9DAF8B371A7AFF6700F46528 /* MIKMIDIMappingManager.m in Sources */, 9DAF8B261A7AFF5900F46528 /* MIKMIDIEndpoint.m in Sources */, @@ -980,6 +1030,7 @@ 9DAF8B411A7AFF6B00F46528 /* MIKMIDIMetaLyricEvent.m in Sources */, 9DAF8B891A7B01FF00F46528 /* NSUIApplication+MIKMIDI.m in Sources */, 9DAF8B421A7AFF6B00F46528 /* MIKMIDIMetaMarkerTextEvent.m in Sources */, + 9DEF1CAB1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */, 9DAF8B431A7AFF6B00F46528 /* MIKMIDIMetaSequenceEvent.m in Sources */, 9DAF8B441A7AFF6B00F46528 /* MIKMIDIMetaTextEvent.m in Sources */, 9DAF8B451A7AFF6B00F46528 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */, @@ -996,6 +1047,7 @@ 9DAF8B4E1A7AFF7500F46528 /* MIKMIDICommandThrottler.m in Sources */, 9DAF8B4F1A7AFF7500F46528 /* MIKMIDIClock.m in Sources */, 9DAF8B501A7AFF7500F46528 /* MIKMIDIPrivateUtilities.m in Sources */, + 9DEF1CB11AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Source/MIKMIDIChannelEvent.h b/Source/MIKMIDIChannelEvent.h new file mode 100644 index 00000000..0a594d37 --- /dev/null +++ b/Source/MIKMIDIChannelEvent.h @@ -0,0 +1,50 @@ +// +// MIKMIDIChannelEvent.h +// MIKMIDI +// +// Created by Andrew Madsen on 3/3/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import + +@interface MIKMIDIChannelEvent : MIKMIDIEvent + +/** + * Convenience method for creating a new MIKMIDIChannelEvent from a CoreMIDI MIDIChannelMessage struct. + * + * @param timeStamp A MusicTimeStamp value indicating the timestamp for the event. + * @param message A MIDIChannelMessage struct containing properties for the event. + * + * @return A new instance of a subclass of MIKMIDIChannelEvent, or nil if there is an error. + */ ++ (instancetype)channelEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDIChannelMessage)message; + +// Properties + +/** + * The channel for the MIDI event. + */ +@property (nonatomic, readonly) UInt8 channel; + +/** + * The first byte of data for the event. + */ +@property (nonatomic, readonly) UInt8 dataByte1; + +/** + * The second byte of data for the event. + */ +@property (nonatomic, readonly) UInt8 dataByte2; + +@end + +@interface MIKMutableMIDIChannelEvent : MIKMIDIChannelEvent + +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m new file mode 100644 index 00000000..d4aaf1a5 --- /dev/null +++ b/Source/MIKMIDIChannelEvent.m @@ -0,0 +1,104 @@ +// +// MIKMIDIChannelEvent.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/3/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelEvent.h" +#import "MIKMIDIEvent_SubclassMethods.h" + +#if !__has_feature(objc_arc) +#error __FILE__ must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for __FILE__ in the Build Phases for this target +#endif + +@interface MIKMIDIChannelEvent () + +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end + +@implementation MIKMIDIChannelEvent + ++ (void)load { //[MIKMIDIEvent registerSubclass:self]; +} ++ (NSArray *)supportedMIDIEventTypes +{ + return @[@(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage), + @(MIKMIDIEventTypeMIDIControlChangeMessage), + @(MIKMIDIEventTypeMIDIProgramChangeMessage), + @(MIKMIDIEventTypeMIDIChannelPressureMessage), + @(MIKMIDIEventTypeMIDIPitchBendChangeMessage)]; +} ++ (Class)immutableCounterpartClass { return [MIKMIDIChannelEvent class]; } ++ (Class)mutableCounterpartClass { return [MIKMutableMIDIChannelEvent class]; } ++ (BOOL)isMutable { return NO; } ++ (size_t)minimumDataSize { return sizeof(MIDIChannelMessage); } + ++ (instancetype)channelEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDIChannelMessage)message; +{ + NSData *data = [NSData dataWithBytes:&message length:sizeof(message)]; + return [self midiEventWithTimeStamp:timeStamp eventType:kMusicEventType_MIDIChannelMessage data:data]; +} + +#pragma mark - Properties + ++ (NSSet *)keyPathsForValuesAffectingInternalData +{ + return [NSSet setWithObjects:@"channel", @"dataByte1", @"dataByte2", nil]; +} + +- (UInt8)channel +{ + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; + return (channelMessage->status & 0x0F); +} + +- (void)setChannel:(UInt8)channel +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; + channelMessage->status = (channelMessage->status & 0xF0) | (channel & 0x0F); +} + +- (UInt8)dataByte1 +{ + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; + return channelMessage->data1; +} + +- (void)setDataByte1:(UInt8)dataByte1 +{ + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; + channelMessage->data1 = dataByte1; +} + +- (UInt8)dataByte2 +{ + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; + return channelMessage->data2; +} + +- (void)setDataByte2:(UInt8)dataByte2 +{ + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; + channelMessage->data2 = dataByte2; +} + +@end + +@implementation MIKMutableMIDIChannelEvent + +@dynamic timeStamp; +@dynamic data; +@dynamic channel; +@dynamic dataByte1; +@dynamic dataByte2; + ++ (BOOL)isMutable { return YES; } + +@end \ No newline at end of file diff --git a/Source/MIKMIDIControlChangeEvent.h b/Source/MIKMIDIControlChangeEvent.h new file mode 100644 index 00000000..bcef2235 --- /dev/null +++ b/Source/MIKMIDIControlChangeEvent.h @@ -0,0 +1,45 @@ +// +// MIKMIDIControlChangeEvent.h +// MIKMIDI +// +// Created by Andrew Madsen on 3/3/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelEvent.h" + +/** + * Control change events are typically sent when a controller value changes. + * Controllers include devices such as pedals and levers. + * + * This event is the counterpart to MIKMIDIControlChangeCommand in the context + * of sequences/MIDI Files. + */ +@interface MIKMIDIControlChangeEvent : MIKMIDIChannelEvent + +/** + * The MIDI controller number for the event. + * Only values from 0-127 are valid. + */ +@property (nonatomic, readonly) NSUInteger controllerNumber; + +/** + * The value of the controller specified by controllerNumber. + * Only values from 0-127 are valid. + */ +@property (nonatomic, readonly) NSUInteger controllerValue; + +@end + +@interface MIKMutableMIDIControlChangeEvent : MIKMIDIControlChangeEvent + +@property (nonatomic, readwrite) NSUInteger controllerNumber; +@property (nonatomic, readwrite) NSUInteger controllerValue; + +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIControlChangeEvent.m b/Source/MIKMIDIControlChangeEvent.m new file mode 100644 index 00000000..143b28c4 --- /dev/null +++ b/Source/MIKMIDIControlChangeEvent.m @@ -0,0 +1,91 @@ +// +// MIKMIDIControlChangeEvent.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/3/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIControlChangeEvent.h" +#import "MIKMIDIEvent_SubclassMethods.h" + +#if !__has_feature(objc_arc) +#error __FILE__ must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for __FILE__ in the Build Phases for this target +#endif + +@interface MIKMIDIChannelEvent (Protected) + +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end + +@interface MIKMIDIControlChangeEvent () + +@property (nonatomic, readwrite) NSUInteger controllerNumber; +@property (nonatomic, readwrite) NSUInteger controllerValue; + +@end + +@implementation MIKMIDIControlChangeEvent + ++ (void)load { [MIKMIDIEvent registerSubclass:self]; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIControlChangeMessage)]; } ++ (Class)immutableCounterpartClass { return [MIKMIDIControlChangeEvent class]; } ++ (Class)mutableCounterpartClass { return [MIKMutableMIDIControlChangeEvent class]; } ++ (BOOL)isMutable { return NO; } + +- (NSString *)additionalEventDescription +{ + return [NSString stringWithFormat:@"controller number: %lu value: %lu", (unsigned long)self.controllerNumber, (unsigned long)self.controllerValue]; +} + +#pragma mark - Properties + ++ (NSSet *)keyPathsForValuesAffectingControllerNumber +{ + return [NSSet setWithObjects:@"dataByte1", nil]; +} + +- (NSUInteger)controllerNumber +{ + return self.dataByte1; +} + +- (void)setControllerNumber:(NSUInteger)controllerNumber +{ + self.dataByte1 = MIN(controllerNumber, 127); +} + ++ (NSSet *)keyPathsForValuesAffectingControllerValue +{ + return [NSSet setWithObjects:@"dataByte2", nil]; +} + +- (NSUInteger)controllerValue +{ + return self.dataByte2; +} + +- (void)setControllerValue:(NSUInteger)controllerValue +{ + self.dataByte2 = MIN(controllerValue, 127); +} + +@end + +@implementation MIKMutableMIDIControlChangeEvent + +@dynamic controllerNumber; +@dynamic controllerValue; + +@dynamic timeStamp; +@dynamic data; +@dynamic channel; +@dynamic dataByte1; +@dynamic dataByte2; + ++ (BOOL)isMutable { return YES; } + +@end \ No newline at end of file diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index fa2e5dd9..d74af3f2 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -22,14 +22,23 @@ typedef NS_ENUM(NSUInteger, MIKMIDIEventType) MIKMIDIEventTypeExtendedNote = kMusicEventType_ExtendedNote, MIKMIDIEventTypeExtendedTempo = kMusicEventType_ExtendedTempo, MIKMIDIEventTypeUser = kMusicEventType_User, - MIKMIDIEventTypeMeta = kMusicEventType_Meta, + MIKMIDIEventTypeMeta = kMusicEventType_Meta, /* See subtypes below */ MIKMIDIEventTypeMIDINoteMessage = kMusicEventType_MIDINoteMessage, - MIKMIDIEventTypeMIDIChannelMessage = kMusicEventType_MIDIChannelMessage, + MIKMIDIEventTypeMIDIChannelMessage = kMusicEventType_MIDIChannelMessage, /* See subtypes below */ MIKMIDIEventTypeMIDIRawData = kMusicEventType_MIDIRawData, MIKMIDIEventTypeParameter = kMusicEventType_Parameter, MIKMIDIEventTypeAUPreset = kMusicEventType_AUPreset, - // Meta types + + // Channel Message subtypes + MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage, + MIKMIDIEventTypeMIDIControlChangeMessage, + MIKMIDIEventTypeMIDIProgramChangeMessage, + MIKMIDIEventTypeMIDIChannelPressureMessage, + MIKMIDIEventTypeMIDIPitchBendChangeMessage, + + + // Meta subtypes MIKMIDIEventTypeMetaSequence, MIKMIDIEventTypeMetaText, MIKMIDIEventTypeMetaCopyright, @@ -52,6 +61,15 @@ typedef NS_ENUM(NSUInteger, MIKMIDIEventType) #endif }; +typedef NS_ENUM(NSUInteger, MIKMIDIChannelEventType) +{ + MIKMIDIChannelEventTypePolyphonicKeyPressure = 0xA0, + MIKMIDIChannelEventTypeControlChange = 0xB0, + MIKMIDIChannelEventTypeProgramChange = 0xC0, + MIKMIDIChannelEventTypeChannelPressure = 0xD0, + MIKMIDIChannelEventTypePitchBendChange = 0xE0, +}; + typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) { MIKMIDIMetaEventTypeSequenceNumber = 0x00, diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index f20ca1d5..c99415fc 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -38,7 +38,7 @@ + (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(Musi { MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data]; // -initWithTimeStamp:midiEventType:data: will do subclass lookup too, but this way we avoid a second alloc - Class subclass = [[self class] subclassForEventType:eventType]; + Class subclass = [[self class] subclassForEventType:midiEventType]; if (!subclass) subclass = self; if ([self isMutable]) subclass = [subclass mutableCounterpartClass]; return [[subclass alloc] initWithTimeStamp:timeStamp midiEventType:midiEventType data:data]; @@ -99,6 +99,12 @@ - (NSString *)description + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType andData:(NSData *)data { + NSDictionary *channelEventTypeToMIDITypeMap = @{@(MIKMIDIChannelEventTypePolyphonicKeyPressure) : @(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage), + @(MIKMIDIChannelEventTypeControlChange) : @(MIKMIDIEventTypeMIDIControlChangeMessage), + @(MIKMIDIChannelEventTypeProgramChange) : @(MIKMIDIEventTypeMIDIProgramChangeMessage), + @(MIKMIDIChannelEventTypeChannelPressure) : @(MIKMIDIEventTypeMIDIChannelPressureMessage), + @(MIKMIDIChannelEventTypePitchBendChange) : @(MIKMIDIEventTypeMIDIPitchBendChangeMessage)}; + NSDictionary *metaTypeToMIDITypeMap = @{@(MIKMIDIMetaEventTypeSequenceNumber) : @(MIKMIDIEventTypeMetaSequence), @(MIKMIDIMetaEventTypeTextEvent) : @(MIKMIDIEventTypeMetaText), @(MIKMIDIMetaEventTypeCopyrightNotice) : @(MIKMIDIEventTypeMetaCopyright), @@ -114,6 +120,7 @@ + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType @(MIKMIDIMetaEventTypeTimeSignature) : @(MIKMIDIEventTypeMetaTimeSignature), @(MIKMIDIMetaEventTypeKeySignature) : @(MIKMIDIEventTypeMetaKeySignature), @(MIKMIDIMetaEventTypeSequencerSpecificEvent) : @(MIKMIDIEventTypeMetaSequenceSpecificEvent),}; + NSDictionary *musicEventToMIDITypeMap = @{@(kMusicEventType_NULL) : @(MIKMIDIEventTypeNULL), @(kMusicEventType_ExtendedNote) : @(MIKMIDIEventTypeExtendedNote), @(kMusicEventType_ExtendedTempo) : @(MIKMIDIEventTypeExtendedTempo), @@ -127,6 +134,9 @@ + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType if (musicEventType == kMusicEventType_Meta) { UInt8 metaEventType = *(UInt8 *)[data bytes]; return [metaTypeToMIDITypeMap[@(metaEventType)] unsignedIntegerValue]; + } else if (musicEventType == kMusicEventType_MIDIChannelMessage) { + UInt8 channelEventType = *(UInt8 *)[data bytes] & 0xF0; + return [channelEventTypeToMIDITypeMap[@(channelEventType)] unsignedIntegerValue]; } else { return [musicEventToMIDITypeMap[@(musicEventType)] unsignedIntegerValue]; } diff --git a/Source/MIKMIDIProgramChangeEvent.h b/Source/MIKMIDIProgramChangeEvent.h new file mode 100644 index 00000000..13f558f4 --- /dev/null +++ b/Source/MIKMIDIProgramChangeEvent.h @@ -0,0 +1,40 @@ +// +// MIKMIDIProgramChangeEvent.h +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelEvent.h" + +/** + * A MIDI program change event. + * + * Program change events indicate a change in the patch number. + * These events can be sent to to a MIDI device or synthesizer to + * change the instrument the instrument/voice being used to synthesize MIDI. + * + * This event is the counterpart to MIKMIDIProgramChangeCommand in the context + * of sequences/MIDI Files. + */ +@interface MIKMIDIProgramChangeEvent : MIKMIDIChannelEvent + +/** + * The program (aka patch) number. From 0-127. + */ +@property (nonatomic, readonly) NSUInteger programNumber; + +@end + +@interface MIKMutableMIDIProgramChangeEvent : MIKMIDIProgramChangeEvent + +@property (nonatomic, readwrite) NSUInteger programNumber; + +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end diff --git a/Source/MIKMIDIProgramChangeEvent.m b/Source/MIKMIDIProgramChangeEvent.m new file mode 100644 index 00000000..ba53753b --- /dev/null +++ b/Source/MIKMIDIProgramChangeEvent.m @@ -0,0 +1,70 @@ +// +// MIKMIDIProgramChangeEvent.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIProgramChangeEvent.h" +#import "MIKMIDIEvent_SubclassMethods.h" + +@interface MIKMIDIChannelEvent (Protected) + +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end + +@interface MIKMIDIProgramChangeEvent () + +@property (nonatomic, readwrite) NSUInteger programNumber; + +@end + +@implementation MIKMIDIProgramChangeEvent + ++ (void)load { [MIKMIDIEvent registerSubclass:self]; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIProgramChangeMessage)]; } ++ (Class)immutableCounterpartClass { return [MIKMIDIProgramChangeEvent class]; } ++ (Class)mutableCounterpartClass { return [MIKMutableMIDIProgramChangeEvent class]; } ++ (BOOL)isMutable { return NO; } + +- (NSString *)additionalEventDescription +{ + return [NSString stringWithFormat:@"program number: %lu", (unsigned long)self.programNumber]; +} + +#pragma mark - Properties + ++ (NSSet *)keyPathsForValuesAffectingProgramNumber +{ + return [NSSet setWithObjects:@"dataByte1", nil]; +} + +- (NSUInteger)programNumber +{ + return self.dataByte1; +} + +- (void)setProgramNumber:(NSUInteger)programNumber +{ + self.dataByte1 = MIN(programNumber, 127); +} + +@end + +@implementation MIKMutableMIDIProgramChangeEvent + +@dynamic programNumber; + +@dynamic timeStamp; +@dynamic data; +@dynamic channel; +@dynamic dataByte1; +@dynamic dataByte2; + ++ (BOOL)isMutable { return YES; } + +@end \ No newline at end of file From 0bbc0607fc646a4845bf3b573d8a217dd4570e7a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 10:33:36 -0700 Subject: [PATCH 051/284] Fixed warnings when compiled in mixed ARC/non-ARC mode due to omission of memory manage specifier in MIKMIDIMetaEvent and subclasses properties. --- Source/MIKMIDIMetaCopyrightEvent.h | 2 +- Source/MIKMIDIMetaCuePointEvent.h | 2 +- Source/MIKMIDIMetaEvent.h | 2 +- Source/MIKMIDIMetaInstrumentNameEvent.h | 2 +- Source/MIKMIDIMetaKeySignatureEvent.h | 2 +- Source/MIKMIDIMetaLyricEvent.h | 2 +- Source/MIKMIDIMetaMarkerTextEvent.h | 2 +- Source/MIKMIDIMetaSequenceEvent.h | 2 +- Source/MIKMIDIMetaTextEvent.h | 4 ++-- Source/MIKMIDIMetaTimeSignatureEvent.h | 2 +- Source/MIKMIDIMetaTrackSequenceNameEvent.h | 6 +++--- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Source/MIKMIDIMetaCopyrightEvent.h b/Source/MIKMIDIMetaCopyrightEvent.h index 619ebd9e..36de74e5 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.h +++ b/Source/MIKMIDIMetaCopyrightEvent.h @@ -22,6 +22,6 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaCuePointEvent.h b/Source/MIKMIDIMetaCuePointEvent.h index 95374b77..f9861002 100644 --- a/Source/MIKMIDIMetaCuePointEvent.h +++ b/Source/MIKMIDIMetaCuePointEvent.h @@ -22,6 +22,6 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaEvent.h b/Source/MIKMIDIMetaEvent.h index 11dee4f2..5ea84d8b 100644 --- a/Source/MIKMIDIMetaEvent.h +++ b/Source/MIKMIDIMetaEvent.h @@ -40,6 +40,6 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.h b/Source/MIKMIDIMetaInstrumentNameEvent.h index e88bd74f..34aac8bf 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.h +++ b/Source/MIKMIDIMetaInstrumentNameEvent.h @@ -23,6 +23,6 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaKeySignatureEvent.h b/Source/MIKMIDIMetaKeySignatureEvent.h index 1f97cf37..3d004e9b 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.h +++ b/Source/MIKMIDIMetaKeySignatureEvent.h @@ -33,7 +33,7 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @property (nonatomic, readwrite) UInt8 key; @property (nonatomic, readwrite) UInt8 scale; diff --git a/Source/MIKMIDIMetaLyricEvent.h b/Source/MIKMIDIMetaLyricEvent.h index 3921eea1..d19bcfef 100644 --- a/Source/MIKMIDIMetaLyricEvent.h +++ b/Source/MIKMIDIMetaLyricEvent.h @@ -22,6 +22,6 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaMarkerTextEvent.h b/Source/MIKMIDIMetaMarkerTextEvent.h index fbe881ff..358a9132 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.h +++ b/Source/MIKMIDIMetaMarkerTextEvent.h @@ -22,6 +22,6 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaSequenceEvent.h b/Source/MIKMIDIMetaSequenceEvent.h index 075924aa..e2aa1f3f 100644 --- a/Source/MIKMIDIMetaSequenceEvent.h +++ b/Source/MIKMIDIMetaSequenceEvent.h @@ -22,6 +22,6 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaTextEvent.h b/Source/MIKMIDIMetaTextEvent.h index e9378171..271de1b0 100644 --- a/Source/MIKMIDIMetaTextEvent.h +++ b/Source/MIKMIDIMetaTextEvent.h @@ -27,7 +27,7 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; -@property (nonatomic, readwrite) NSString *string; +@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, copy, readwrite) NSString *string; @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.h b/Source/MIKMIDIMetaTimeSignatureEvent.h index e35daab5..3810d182 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.h +++ b/Source/MIKMIDIMetaTimeSignatureEvent.h @@ -41,7 +41,7 @@ @interface MIKMutableMIDIMetaTimeSignatureEvent : MIKMIDIMetaTimeSignatureEvent @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite) NSData *metaData; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 numerator; @property (nonatomic, readwrite) UInt8 denominator; diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index cd2bec2b..42883361 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -22,11 +22,11 @@ */ @interface MIKMutableMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTrackSequenceNameEvent -@property (nonatomic, readwrite) NSString *name; +@property (nonatomic, copy, readwrite) NSString *name; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, readwrite) NSData *metaData; -@property (nonatomic, readwrite) NSString *string; +@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, copy, readwrite) NSString *string; @end \ No newline at end of file From aa71e944ac9c124504b4b604e3b560fdc3771417 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 10:37:18 -0700 Subject: [PATCH 052/284] Fixed more ARC warnings. --- Source/MIKMIDIChannelEvent.m | 2 +- Source/MIKMIDIControlChangeEvent.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index d4aaf1a5..4a71ca5e 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -10,7 +10,7 @@ #import "MIKMIDIEvent_SubclassMethods.h" #if !__has_feature(objc_arc) -#error __FILE__ must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for __FILE__ in the Build Phases for this target +#error MIKMIDIChannelEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIChannelEvent.m in the Build Phases for this target #endif @interface MIKMIDIChannelEvent () diff --git a/Source/MIKMIDIControlChangeEvent.m b/Source/MIKMIDIControlChangeEvent.m index 143b28c4..9d2bcdf9 100644 --- a/Source/MIKMIDIControlChangeEvent.m +++ b/Source/MIKMIDIControlChangeEvent.m @@ -10,7 +10,7 @@ #import "MIKMIDIEvent_SubclassMethods.h" #if !__has_feature(objc_arc) -#error __FILE__ must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for __FILE__ in the Build Phases for this target +#error MIKMIDIControlChangeEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIControlChangeEvent.m in the Build Phases for this target #endif @interface MIKMIDIChannelEvent (Protected) From 89a7b5d037830c1c4867398691e8e2d590f8f797 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 10:44:23 -0700 Subject: [PATCH 053/284] Issue #63: Added MIKMIDIPolyphonicKeyPressureEvent. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 +++ Source/MIKMIDIPolyphonicKeyPressureEvent.h | 41 ++++++++++ Source/MIKMIDIPolyphonicKeyPressureEvent.m | 84 +++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 Source/MIKMIDIPolyphonicKeyPressureEvent.h create mode 100644 Source/MIKMIDIPolyphonicKeyPressureEvent.m diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index d56b2bc7..b8c14d1b 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -112,6 +112,10 @@ 9D84951D1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; }; 9D84951E1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; 9D84951F1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; + 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; }; + 9D8495231AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; }; + 9D8495241AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */; }; + 9D8495251AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */; }; 9D877D841A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D877D821A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D877D851A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877D831A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m */; }; 9D877DFC1A670261001BA997 /* MIKMIDIProgramChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877DFA1A670261001BA997 /* MIKMIDIProgramChangeCommand.m */; }; @@ -359,6 +363,8 @@ 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITrack_Protected.h; sourceTree = ""; }; 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIProgramChangeEvent.h; sourceTree = ""; }; 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIProgramChangeEvent.m; sourceTree = ""; }; + 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureEvent.h; sourceTree = ""; }; + 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureEvent.m; sourceTree = ""; }; 9D877D821A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientSourceEndpoint.h; sourceTree = ""; }; 9D877D831A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientSourceEndpoint.m; sourceTree = ""; }; 9D877DF91A670261001BA997 /* MIKMIDIProgramChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIProgramChangeCommand.h; sourceTree = ""; }; @@ -599,6 +605,8 @@ children = ( 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */, 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */, + 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */, + 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */, 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */, 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */, 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */, @@ -724,6 +732,7 @@ 9D74EF6817A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h in Headers */, 839D935719C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.h in Headers */, 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, + 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */, 839D933219C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h in Headers */, 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 839D935519C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h in Headers */, @@ -819,6 +828,7 @@ 9DAF8B791A7B00A700F46528 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, 9DAF8B6F1A7B00A700F46528 /* MIKMIDIMetaCopyrightEvent.h in Headers */, 9DAF8B531A7B005C00F46528 /* MIKMIDIErrors.h in Headers */, + 9D8495231AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */, 9DAF8B7F1A7B00B100F46528 /* MIKMIDISequencer.h in Headers */, 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9DAF8B6E1A7B00A700F46528 /* MIKMIDIEventIterator.h in Headers */, @@ -957,6 +967,7 @@ 9D74EF7817A713A100BEE89F /* MIKMIDIInputPort.m in Sources */, 9D74EF7A17A713A100BEE89F /* MIKMIDIMapping.m in Sources */, 9D74EF7C17A713A100BEE89F /* MIKMIDIMappingGenerator.m in Sources */, + 9D8495241AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */, 9DEF1CB01AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */, 833B73DF1A26346F00E0CC9F /* MIKMIDIClock.m in Sources */, 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */, @@ -1018,6 +1029,7 @@ 9DAF8B281A7AFF5900F46528 /* MIKMIDIClientSourceEndpoint.m in Sources */, 9DAF8B291A7AFF5900F46528 /* MIKMIDIDestinationEndpoint.m in Sources */, 9DAF8B2A1A7AFF5900F46528 /* MIKMIDIClientDestinationEndpoint.m in Sources */, + 9D8495251AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */, 9DAF8B381A7AFF6B00F46528 /* MIKMIDISequence.m in Sources */, 9DAF8B391A7AFF6B00F46528 /* MIKMIDITrack.m in Sources */, 9DAF8B3A1A7AFF6B00F46528 /* MIKMIDIEvent.m in Sources */, diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.h b/Source/MIKMIDIPolyphonicKeyPressureEvent.h new file mode 100644 index 00000000..f327cbb3 --- /dev/null +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.h @@ -0,0 +1,41 @@ +// +// MIKMIDIPolyphonicKeyPressureEvent.h +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelEvent.h" + +/** + * A polyphonic key pressure (aftertouch) event. + * + * This event most often represents pressing down on a key after it "bottoms out". + */ +@interface MIKMIDIPolyphonicKeyPressureEvent : MIKMIDIChannelEvent + +/** + * The MIDI note number for the event. + */ +@property (nonatomic, readonly) UInt8 note; + +/** + * The pressure of the event. From 0-127. + */ +@property (nonatomic, readonly) UInt8 pressure; + +@end + +@interface MIKMutableMIDIPolyphonicKeyPressureEvent : MIKMIDIPolyphonicKeyPressureEvent + +@property (nonatomic, readwrite) UInt8 note; +@property (nonatomic, readwrite) UInt8 pressure; + +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.m b/Source/MIKMIDIPolyphonicKeyPressureEvent.m new file mode 100644 index 00000000..150264a5 --- /dev/null +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.m @@ -0,0 +1,84 @@ +// +// MIKMIDIPolyphonicKeyPressureEvent.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIPolyphonicKeyPressureEvent.h" +#import "MIKMIDIEvent_SubclassMethods.h" + +#if !__has_feature(objc_arc) +#error MIKMIDIPolyphonicKeyPressureEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIPolyphonicKeyPressureEvent.m in the Build Phases for this target +#endif + +@interface MIKMIDIChannelEvent (Protected) + +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end + +@interface MIKMIDIPolyphonicKeyPressureEvent () + +@property (nonatomic, readwrite) UInt8 note; +@property (nonatomic, readwrite) UInt8 pressure; + +@end + +@implementation MIKMIDIPolyphonicKeyPressureEvent + ++ (void)load { [MIKMIDIEvent registerSubclass:self]; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage)]; } ++ (Class)immutableCounterpartClass { return [MIKMIDIPolyphonicKeyPressureEvent class]; } ++ (Class)mutableCounterpartClass { return [MIKMutableMIDIPolyphonicKeyPressureEvent class]; } ++ (BOOL)isMutable { return NO; } + ++ (NSSet *)keyPathsForValuesAffectingNote +{ + return [NSSet setWithObjects:@"dataByte1", nil]; +} + +- (UInt8)note +{ + return self.dataByte1; +} + +- (void)setNote:(UInt8)note +{ + self.dataByte1 = MIN(note, 127); +} + ++ (NSSet *)keyPathsForValuesAffectingPressure +{ + return [NSSet setWithObjects:@"dataByte2", nil]; +} + +- (UInt8)pressure +{ + return self.dataByte2; +} + +- (void)setPressure:(UInt8)pressure +{ + self.dataByte2 = MIN(pressure, 127); +} + +@end + +@implementation MIKMutableMIDIPolyphonicKeyPressureEvent + +@dynamic note; +@dynamic pressure; + +@dynamic timeStamp; +@dynamic data; +@dynamic channel; +@dynamic dataByte1; +@dynamic dataByte2; + ++ (BOOL)isMutable { return YES; } + +@end \ No newline at end of file From d5b40055e58215acebfb3a7c9aa8e8994b559b04 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 10:45:55 -0700 Subject: [PATCH 054/284] Issue #63: Added missing mutation guards to MIKMIDIChannelEvent (and subclasses) setters. --- Source/MIKMIDIChannelEvent.m | 8 ++++++-- Source/MIKMIDIControlChangeEvent.m | 4 ++++ Source/MIKMIDIPolyphonicKeyPressureEvent.m | 4 ++++ Source/MIKMIDIProgramChangeEvent.m | 2 ++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index 4a71ca5e..c74f2d5e 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -73,8 +73,10 @@ - (UInt8)dataByte1 - (void)setDataByte1:(UInt8)dataByte1 { + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; - channelMessage->data1 = dataByte1; + channelMessage->data1 = dataByte1 & 0x7F; } - (UInt8)dataByte2 @@ -85,8 +87,10 @@ - (UInt8)dataByte2 - (void)setDataByte2:(UInt8)dataByte2 { + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + MIDIChannelMessage *channelMessage = (MIDIChannelMessage*)[self.internalData bytes]; - channelMessage->data2 = dataByte2; + channelMessage->data2 = dataByte2 & 0x7F; } @end diff --git a/Source/MIKMIDIControlChangeEvent.m b/Source/MIKMIDIControlChangeEvent.m index 9d2bcdf9..1151e6d7 100644 --- a/Source/MIKMIDIControlChangeEvent.m +++ b/Source/MIKMIDIControlChangeEvent.m @@ -55,6 +55,8 @@ - (NSUInteger)controllerNumber - (void)setControllerNumber:(NSUInteger)controllerNumber { + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.dataByte1 = MIN(controllerNumber, 127); } @@ -70,6 +72,8 @@ - (NSUInteger)controllerValue - (void)setControllerValue:(NSUInteger)controllerValue { + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.dataByte2 = MIN(controllerValue, 127); } diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.m b/Source/MIKMIDIPolyphonicKeyPressureEvent.m index 150264a5..d628d910 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.m +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.m @@ -48,6 +48,8 @@ - (UInt8)note - (void)setNote:(UInt8)note { + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.dataByte1 = MIN(note, 127); } @@ -63,6 +65,8 @@ - (UInt8)pressure - (void)setPressure:(UInt8)pressure { + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.dataByte2 = MIN(pressure, 127); } diff --git a/Source/MIKMIDIProgramChangeEvent.m b/Source/MIKMIDIProgramChangeEvent.m index ba53753b..79cff234 100644 --- a/Source/MIKMIDIProgramChangeEvent.m +++ b/Source/MIKMIDIProgramChangeEvent.m @@ -50,6 +50,8 @@ - (NSUInteger)programNumber - (void)setProgramNumber:(NSUInteger)programNumber { + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.dataByte1 = MIN(programNumber, 127); } From eeb61031db13cc3aa16b31c656c430ae0fc82940 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 11:18:27 -0700 Subject: [PATCH 055/284] Issue #63: Added MIKMIDIPitchBendChangeEvent. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 ++++ Source/MIKMIDIPitchBendChangeEvent.h | 41 +++++++++++ Source/MIKMIDIPitchBendChangeEvent.m | 80 +++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 Source/MIKMIDIPitchBendChangeEvent.h create mode 100644 Source/MIKMIDIPitchBendChangeEvent.m diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index b8c14d1b..2544f490 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -239,6 +239,10 @@ 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; + 9DED4E221AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */; }; + 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */; }; + 9DED4E241AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; + 9DED4E251AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; }; 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; }; 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; @@ -385,6 +389,8 @@ 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizerInstrument.m; sourceTree = ""; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeEvent.h; sourceTree = ""; }; + 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPitchBendChangeEvent.m; sourceTree = ""; }; 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelEvent.h; sourceTree = ""; }; 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelEvent.m; sourceTree = ""; }; 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIControlChangeEvent.h; sourceTree = ""; }; @@ -611,6 +617,8 @@ 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */, 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */, 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */, + 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */, + 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */, ); name = "Channel Events"; sourceTree = ""; @@ -754,6 +762,7 @@ 839D935D19C3A2F5007589C3 /* MIKMIDIMetaSequenceEvent.h in Headers */, 839D935119C3A2F5007589C3 /* MIKMIDIMetaCuePointEvent.h in Headers */, 839D933319C3A2C9007589C3 /* MIKMIDIEvent.h in Headers */, + 9DED4E221AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */, 9D74EF7D17A713A100BEE89F /* MIKMIDIMappingManager.h in Headers */, 9D74EF7F17A713A100BEE89F /* MIKMIDINoteOffCommand.h in Headers */, 9D74EF8117A713A100BEE89F /* MIKMIDINoteOnCommand.h in Headers */, @@ -809,6 +818,7 @@ 9DAF8B611A7B008A00F46528 /* MIKMIDIChannelVoiceCommand.h in Headers */, 9DAF8B701A7B00A700F46528 /* MIKMIDIMetaCuePointEvent.h in Headers */, 9DAF8B671A7B008A00F46528 /* MIKMIDIProgramChangeCommand.h in Headers */, + 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */, 9DB366F71A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */, 9DAF8B691A7B009100F46528 /* MIKMIDIMappingGenerator.h in Headers */, 9DAF8B641A7B008A00F46528 /* MIKMIDINoteOffCommand.h in Headers */, @@ -969,6 +979,7 @@ 9D74EF7C17A713A100BEE89F /* MIKMIDIMappingGenerator.m in Sources */, 9D8495241AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */, 9DEF1CB01AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */, + 9DED4E241AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */, 833B73DF1A26346F00E0CC9F /* MIKMIDIClock.m in Sources */, 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */, 9D74EF7E17A713A100BEE89F /* MIKMIDIMappingManager.m in Sources */, @@ -1024,6 +1035,7 @@ 9D84951F1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */, 9DAF8B361A7AFF6700F46528 /* MIKMIDIMappingGenerator.m in Sources */, 9DAF8B371A7AFF6700F46528 /* MIKMIDIMappingManager.m in Sources */, + 9DED4E251AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */, 9DAF8B261A7AFF5900F46528 /* MIKMIDIEndpoint.m in Sources */, 9DAF8B271A7AFF5900F46528 /* MIKMIDISourceEndpoint.m in Sources */, 9DAF8B281A7AFF5900F46528 /* MIKMIDIClientSourceEndpoint.m in Sources */, diff --git a/Source/MIKMIDIPitchBendChangeEvent.h b/Source/MIKMIDIPitchBendChangeEvent.h new file mode 100644 index 00000000..36db4e01 --- /dev/null +++ b/Source/MIKMIDIPitchBendChangeEvent.h @@ -0,0 +1,41 @@ +// +// MIKMIDIPitchBendChangeEvent.h +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelEvent.h" + +/** + * A pitch bed change event. + * + * This event indicates a pitch bend change. On devices, pitch + * bends are usually generated using a wheel or lever. + */ +@interface MIKMIDIPitchBendChangeEvent : MIKMIDIChannelEvent + +/** + * A 14-bit value indicating the pitch bend. + * Center is 0x2000 (8192). + * Valid range is from 0-16383. + */ +@property (nonatomic, readonly) UInt16 pitchChange; + +@end + +/** + * The mutable counterpart of MIKMIDIPitchBendChangeEvent. + */ +@interface MIKMutableMIDIPitchBendChangeEvent : MIKMIDIPitchBendChangeEvent + +@property (nonatomic, readonly) UInt16 pitchChange; + +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIPitchBendChangeEvent.m b/Source/MIKMIDIPitchBendChangeEvent.m new file mode 100644 index 00000000..cab97d7f --- /dev/null +++ b/Source/MIKMIDIPitchBendChangeEvent.m @@ -0,0 +1,80 @@ +// +// MIKMIDIPitchBendChangeEvent.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIPitchBendChangeEvent.h" +#import "MIKMIDIEvent_SubclassMethods.h" + +#if !__has_feature(objc_arc) +#error MIKMIDIPitchBendChangeEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIPitchBendChangeEvent.m in the Build Phases for this target +#endif + +@interface MIKMIDIChannelEvent (Protected) + +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end + +@interface MIKMIDIPitchBendChangeEvent () + +@property (nonatomic, readwrite) UInt16 pitchChange; + +@end + +@implementation MIKMIDIPitchBendChangeEvent + ++ (void)load { [MIKMIDIEvent registerSubclass:self]; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIPitchBendChangeMessage)]; } ++ (Class)immutableCounterpartClass { return [MIKMIDIPitchBendChangeEvent class]; } ++ (Class)mutableCounterpartClass { return [MIKMutableMIDIPitchBendChangeEvent class]; } ++ (BOOL)isMutable { return NO; } + +- (NSString *)additionalEventDescription +{ + return [NSString stringWithFormat:@"pitch change: %u", (unsigned)self.pitchChange]; +} + +#pragma mark - Properties + ++ (NSSet *)keyPathsForValuesAffectingPitchChange +{ + return [NSSet setWithObjects:@"dataByte1", @"dataByte2", nil]; +} + +- (UInt16)pitchChange +{ + UInt16 ms7 = (self.dataByte2 << 7) & 0x3F80; + UInt16 ls7 = self.dataByte1 & 0x007F; + return ms7 | ls7; +} + +- (void)setPitchChange:(UInt16)pitchChange +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + + pitchChange = MIN(pitchChange, 0x3FFF); + self.dataByte1 = pitchChange & 0x007F; + self.dataByte2 = pitchChange & 0x3F80; +} + +@end + +@implementation MIKMutableMIDIPitchBendChangeEvent + +@dynamic pitchChange; + +@dynamic timeStamp; +@dynamic data; +@dynamic channel; +@dynamic dataByte1; +@dynamic dataByte2; + ++ (BOOL)isMutable { return YES; } + +@end \ No newline at end of file From 9cc7cd14ec37351e4f05b5d1beceade9957d9127 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 11:19:49 -0700 Subject: [PATCH 056/284] Issue #63: Added -additionalEventDescription for MIKMIDIPolyphonicKeyPressureEvent. --- Source/MIKMIDIPolyphonicKeyPressureEvent.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.m b/Source/MIKMIDIPolyphonicKeyPressureEvent.m index d628d910..5655d9c9 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.m +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.m @@ -36,6 +36,13 @@ + (Class)immutableCounterpartClass { return [MIKMIDIPolyphonicKeyPressureEvent c + (Class)mutableCounterpartClass { return [MIKMutableMIDIPolyphonicKeyPressureEvent class]; } + (BOOL)isMutable { return NO; } +- (NSString *)additionalEventDescription +{ + return [NSString stringWithFormat:@"note: %u pressure: %u", (unsigned)self.note, (unsigned)self.pressure]; +} + +#pragma mark - Properties + + (NSSet *)keyPathsForValuesAffectingNote { return [NSSet setWithObjects:@"dataByte1", nil]; From 1064d4abaed77a1c08db1d88c9b91a7cf2de205e Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 11:21:05 -0700 Subject: [PATCH 057/284] Issue #63: Minor documentation improvements. --- Source/MIKMIDIControlChangeEvent.h | 3 +++ Source/MIKMIDIPolyphonicKeyPressureEvent.h | 3 +++ Source/MIKMIDIProgramChangeEvent.h | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Source/MIKMIDIControlChangeEvent.h b/Source/MIKMIDIControlChangeEvent.h index bcef2235..0f5b1cbe 100644 --- a/Source/MIKMIDIControlChangeEvent.h +++ b/Source/MIKMIDIControlChangeEvent.h @@ -31,6 +31,9 @@ @end +/** + * The mutable counter part of MIKMIDIControlChangeEvent + */ @interface MIKMutableMIDIControlChangeEvent : MIKMIDIControlChangeEvent @property (nonatomic, readwrite) NSUInteger controllerNumber; diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.h b/Source/MIKMIDIPolyphonicKeyPressureEvent.h index f327cbb3..58c78275 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.h +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.h @@ -27,6 +27,9 @@ @end +/** + * The mutable counter part of MIKMIDIPolyphonicKeyPressureEvent + */ @interface MIKMutableMIDIPolyphonicKeyPressureEvent : MIKMIDIPolyphonicKeyPressureEvent @property (nonatomic, readwrite) UInt8 note; diff --git a/Source/MIKMIDIProgramChangeEvent.h b/Source/MIKMIDIProgramChangeEvent.h index 13f558f4..da081afd 100644 --- a/Source/MIKMIDIProgramChangeEvent.h +++ b/Source/MIKMIDIProgramChangeEvent.h @@ -27,6 +27,9 @@ @end +/** + * The mutable counter part of MIKMIDIProgramChangeEvent + */ @interface MIKMutableMIDIProgramChangeEvent : MIKMIDIProgramChangeEvent @property (nonatomic, readwrite) NSUInteger programNumber; From f500b94b97e860192202fe54be6b4d189ee8081e Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 14:46:43 -0700 Subject: [PATCH 058/284] Cleaned up the organization of MIKMIDI.h. --- Source/MIKMIDI.h | 78 +++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 0ab9a2c1..e154d8e8 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -8,37 +8,50 @@ /** Umbrella header for MIKMIDI public interface. */ +// Core MIDI object wrapper +#import "MIKMIDIObject.h" + +// MIDI port +#import "MIKMIDIPort.h" +#import "MIKMIDIInputPort.h" +#import "MIKMIDIOutputPort.h" + +// MIDI Device support +#import "MIKMIDIDevice.h" +#import "MIKMIDIDeviceManager.h" + +#import "MIKMIDIEntity.h" + +// Endpoints +#import "MIKMIDIEndpoint.h" +#import "MIKMIDIDestinationEndpoint.h" +#import "MIKMIDISourceEndpoint.h" +#import "MIKMIDIClientDestinationEndpoint.h" +#import "MIKMIDIClientSourceEndpoint.h" + +// MIDI Commands/Messages #import "MIKMIDICommand.h" #import "MIKMIDIChannelVoiceCommand.h" #import "MIKMIDIControlChangeCommand.h" #import "MIKMIDIProgramChangeCommand.h" #import "MIKMIDINoteOnCommand.h" #import "MIKMIDINoteOffCommand.h" -#import "MIKMIDIDestinationEndpoint.h" -#import "MIKMIDIDevice.h" -#import "MIKMIDIDeviceManager.h" -#import "MIKMIDIEndpoint.h" -#import "MIKMIDIEntity.h" -#import "MIKMIDIInputPort.h" -#import "MIKMIDIObject.h" -#import "MIKMIDIOutputPort.h" -#import "MIKMIDIPort.h" -#import "MIKMIDIResponder.h" -#import "MIKMIDISourceEndpoint.h" #import "MIKMIDISystemExclusiveCommand.h" #import "MIKMIDISystemMessageCommand.h" -#import "MIKMIDIMapping.h" -#import "MIKMIDIMappingManager.h" -#import "MIKMIDIMappingGenerator.h" -#import "MIKMIDIUtilities.h" -#import "NSUIApplication+MIKMIDI.h" -#import "MIKMIDIErrors.h" -#import "MIKMIDICommandThrottler.h" -#import "MIKMIDIEndpointSynthesizer.h" + +// MIDI Sequence/File support +#import "MIKMIDISequence.h" +#import "MIKMIDITrack.h" + +// MIDI Events #import "MIKMIDIEvent.h" +#import "MIKMIDITempoEvent.h" +#import "MIKMIDINoteEvent.h" + +// Meta Events +#import "MIKMIDIMetaEvent.h" #import "MIKMIDIMetaCopyrightEvent.h" #import "MIKMIDIMetaCuePointEvent.h" -#import "MIKMIDIMetaEvent.h" #import "MIKMIDIMetaInstrumentNameEvent.h" #import "MIKMIDIMetaKeySignatureEvent.h" #import "MIKMIDIMetaLyricEvent.h" @@ -47,13 +60,24 @@ #import "MIKMIDIMetaTextEvent.h" #import "MIKMIDIMetaTimeSignatureEvent.h" #import "MIKMIDIMetaTrackSequenceNameEvent.h" -#import "MIKMIDINoteEvent.h" -#import "MIKMIDIPlayer.h" -#import "MIKMIDISequence.h" -#import "MIKMIDITempoEvent.h" -#import "MIKMIDITrack.h" -#import "MIKMIDIClientDestinationEndpoint.h" -#import "MIKMIDIClientSourceEndpoint.h" + +// Sequencing and Synthesis #import "MIKMIDISequencer.h" #import "MIKMIDIMetronome.h" #import "MIKMIDIClock.h" +#import "MIKMIDIPlayer.h" +#import "MIKMIDIEndpointSynthesizer.h" + +// MIDI Mapping +#import "MIKMIDIMapping.h" +#import "MIKMIDIMappingManager.h" +#import "MIKMIDIMappingGenerator.h" + +// Intra-application MIDI command routing +#import "NSUIApplication+MIKMIDI.h" +#import "MIKMIDIResponder.h" +#import "MIKMIDICommandThrottler.h" + +// Utilities +#import "MIKMIDIUtilities.h" +#import "MIKMIDIErrors.h" From c9c08a7bd49968da39649d17106d64a9f450b988 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 14:50:25 -0700 Subject: [PATCH 059/284] Issue #63: Fixed up #imports in MIKMIDIChannelEvent and its subclasses. --- Source/MIKMIDIChannelEvent.h | 2 +- Source/MIKMIDIChannelEvent.m | 1 + Source/MIKMIDIControlChangeEvent.m | 1 + Source/MIKMIDIPitchBendChangeEvent.m | 1 + Source/MIKMIDIPolyphonicKeyPressureEvent.m | 1 + Source/MIKMIDIProgramChangeEvent.m | 1 + 6 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIChannelEvent.h b/Source/MIKMIDIChannelEvent.h index 0a594d37..e19bc3f1 100644 --- a/Source/MIKMIDIChannelEvent.h +++ b/Source/MIKMIDIChannelEvent.h @@ -6,7 +6,7 @@ // Copyright (c) 2015 Mixed In Key. All rights reserved. // -#import +#import @interface MIKMIDIChannelEvent : MIKMIDIEvent diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index c74f2d5e..35c69b47 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -8,6 +8,7 @@ #import "MIKMIDIChannelEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) #error MIKMIDIChannelEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIChannelEvent.m in the Build Phases for this target diff --git a/Source/MIKMIDIControlChangeEvent.m b/Source/MIKMIDIControlChangeEvent.m index 1151e6d7..eec4bfef 100644 --- a/Source/MIKMIDIControlChangeEvent.m +++ b/Source/MIKMIDIControlChangeEvent.m @@ -8,6 +8,7 @@ #import "MIKMIDIControlChangeEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) #error MIKMIDIControlChangeEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIControlChangeEvent.m in the Build Phases for this target diff --git a/Source/MIKMIDIPitchBendChangeEvent.m b/Source/MIKMIDIPitchBendChangeEvent.m index cab97d7f..c78108c0 100644 --- a/Source/MIKMIDIPitchBendChangeEvent.m +++ b/Source/MIKMIDIPitchBendChangeEvent.m @@ -8,6 +8,7 @@ #import "MIKMIDIPitchBendChangeEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) #error MIKMIDIPitchBendChangeEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIPitchBendChangeEvent.m in the Build Phases for this target diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.m b/Source/MIKMIDIPolyphonicKeyPressureEvent.m index 5655d9c9..94d6db8f 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.m +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.m @@ -8,6 +8,7 @@ #import "MIKMIDIPolyphonicKeyPressureEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) #error MIKMIDIPolyphonicKeyPressureEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIPolyphonicKeyPressureEvent.m in the Build Phases for this target diff --git a/Source/MIKMIDIProgramChangeEvent.m b/Source/MIKMIDIProgramChangeEvent.m index 79cff234..14cca191 100644 --- a/Source/MIKMIDIProgramChangeEvent.m +++ b/Source/MIKMIDIProgramChangeEvent.m @@ -8,6 +8,7 @@ #import "MIKMIDIProgramChangeEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIUtilities.h" @interface MIKMIDIChannelEvent (Protected) From 994c907ca18eff850abd8777dfe34a603cb80e0f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 14:51:48 -0700 Subject: [PATCH 060/284] Issue #63: Added MIKMIDIChannelEvent and its subclasses' headers to MIKMIDI.h, and made them public. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 ++++++------ Source/MIKMIDI.h | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 2544f490..4790b605 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -108,11 +108,11 @@ 9D74EF9417A713A100BEE89F /* NSUIApplication+MIKMIDI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF6117A713A100BEE89F /* NSUIApplication+MIKMIDI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF9517A713A100BEE89F /* NSUIApplication+MIKMIDI.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF6217A713A100BEE89F /* NSUIApplication+MIKMIDI.m */; }; 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */; }; - 9D84951C1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; }; + 9D84951C1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D84951D1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; }; 9D84951E1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; 9D84951F1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; - 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; }; + 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D8495231AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; }; 9D8495241AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */; }; 9D8495251AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */; }; @@ -239,15 +239,15 @@ 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; - 9DED4E221AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */; }; - 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */; }; + 9DED4E221AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DED4E241AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; 9DED4E251AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; - 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; }; + 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; }; 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; 9DEF1CAB1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; - 9DEF1CAE1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; }; + 9DEF1CAE1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DEF1CAF1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; }; 9DEF1CB01AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */; }; 9DEF1CB11AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */; }; diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index e154d8e8..7c5391e6 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -48,6 +48,13 @@ #import "MIKMIDITempoEvent.h" #import "MIKMIDINoteEvent.h" +// Channel Events +#import "MIKMIDIChannelEvent.h" +#import "MIKMIDIPolyphonicKeyPressureEvent.h" +#import "MIKMIDIControlChangeEvent.h" +#import "MIKMIDIProgramChangeEvent.h" +#import "MIKMIDIPitchBendChangeEvent.h" + // Meta Events #import "MIKMIDIMetaEvent.h" #import "MIKMIDIMetaCopyrightEvent.h" From 1304b7065ed9822fbb7dd2254e9700db37bd145f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 15:40:08 -0700 Subject: [PATCH 061/284] Issue #63: Added convencience method to create MIKMIDICommands from MIKMIDIChannelEvents. --- Source/MIKMIDIChannelEvent.h | 16 ++++++++++++++++ Source/MIKMIDIChannelEvent.m | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Source/MIKMIDIChannelEvent.h b/Source/MIKMIDIChannelEvent.h index e19bc3f1..937c8b48 100644 --- a/Source/MIKMIDIChannelEvent.h +++ b/Source/MIKMIDIChannelEvent.h @@ -39,6 +39,9 @@ @end +/** + * The mutable counterpart of MIKMIDIChannelEvent. + */ @interface MIKMutableMIDIChannelEvent : MIKMIDIChannelEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @@ -47,4 +50,17 @@ @property (nonatomic, readwrite) UInt8 dataByte1; @property (nonatomic, readwrite) UInt8 dataByte2; +@end + +#pragma mark - + +#import +#import + +@class MIKMIDIClock; + +@interface MIKMIDICommand (MIKMIDIChannelEventToCommands) + ++ (instancetype)commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:(MIKMIDIClock *)clock; + @end \ No newline at end of file diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index 35c69b47..4d94e79a 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -9,6 +9,7 @@ #import "MIKMIDIChannelEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" #import "MIKMIDIUtilities.h" +#import "MIKMIDIClock.h" #if !__has_feature(objc_arc) #error MIKMIDIChannelEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIChannelEvent.m in the Build Phases for this target @@ -106,4 +107,26 @@ @implementation MIKMutableMIDIChannelEvent + (BOOL)isMutable { return YES; } +@end + +#pragma mark - MIKMIDICommand+MIKMIDIChannelEventToCommands + +@implementation MIKMIDICommand (MIKMIDIChannelEventToCommands) + ++ (instancetype)commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:(MIKMIDIClock *)clock +{ + NSDictionary *classes = @{@(MIKMIDIEventTypeMIDIControlChangeMessage) : [MIKMIDIControlChangeCommand class], + @(MIKMIDIEventTypeMIDIProgramChangeMessage) : [MIKMIDIProgramChangeCommand class]}; + Class commandClass = classes[@(event.eventType)]; + if (!commandClass) return nil; + + MIKMutableMIDIChannelVoiceCommand *result = [[[commandClass mutableCounterpartClass] alloc] init]; + result.channel = event.channel; + result.dataByte1 = event.dataByte1; + result.dataByte2 = event.dataByte2; + result.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:event.timeStamp]; + + return [result copy]; +} + @end \ No newline at end of file From 962faa1e9eb08928a5b1698fc21e25689a46fe11 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 15:42:43 -0700 Subject: [PATCH 062/284] Fixed MIKMIDISequencer's failure to stop when -stop is called. Broken in 6b13587 (Issue #48). --- Source/MIKMIDISequencer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 226ba512..5f12e24c 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -140,12 +140,11 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi self.pendingNoteOffs = [NSMutableDictionary dictionary]; self.pendingNoteOffMIDITimeStamps = [NSMutableOrderedSet orderedSet]; self.lastProcessedMIDITimeStamp = midiTimeStamp - 1; - NSTimer *processingTimer = [NSTimer timerWithTimeInterval:0.05 + self.processingTimer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(processingTimerFired:) userInfo:nil repeats:YES]; - [[NSRunLoop currentRunLoop] addTimer:processingTimer forMode:NSRunLoopCommonModes]; [self.processingTimer fire]; } @@ -610,6 +609,7 @@ - (void)setProcessingTimer:(NSTimer *)processingTimer if (processingTimer != _processingTimer) { [_processingTimer invalidate]; _processingTimer = processingTimer; + if (_processingTimer) [[NSRunLoop currentRunLoop] addTimer:_processingTimer forMode:NSRunLoopCommonModes]; } } From ac8701e7aa5f2cca56a65499ab193552f3fd8f30 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 16:05:24 -0700 Subject: [PATCH 063/284] MIDI Files Testbed: Removed test code for loading a soundfont file. --- Examples/MIDI Files Testbed/Source/MIKMainWindowController.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m index cf994788..8f4202d6 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -118,8 +118,6 @@ - (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error // So audio can be heard self.endpointSynth = [[MIKMIDIEndpointSynthesizer alloc] initWithMIDISource:source]; - NSString *soundfontFile = [@"~/Desktop/test.sf2" stringByExpandingTildeInPath]; - [self.endpointSynth loadSoundfontFromFileAtURL:[NSURL fileURLWithPath:soundfontFile] error:NULL]; return YES; } From edb387d07d726d92f34a27a5287593b2ebed598c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 16:05:51 -0700 Subject: [PATCH 064/284] MIDI Files Testbed: Fixed MIKMIDISequence view's note coloring for sequences with exactly 2 note tracks. --- Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m index 8fb53cdd..bd18b598 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m +++ b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m @@ -39,7 +39,7 @@ - (void)drawRect:(NSRect)dirtyRect for (MIKMIDITrack *track in self.sequence.tracks) { for (MIKMIDINoteEvent *note in [track notes]) { - NSColor *noteColor = [self.sequence.tracks count] <= 2 ? [self colorForNote:note] : [self colorForTrackAtIndex:index]; + NSColor *noteColor = [self.sequence.tracks count] < 2 ? [self colorForNote:note] : [self colorForTrackAtIndex:index]; [[NSColor blackColor] setStroke]; [noteColor setFill]; From 8b65c3ac9fdce7b642bc9dd7e720fc8bb8b3377f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 16:24:36 -0700 Subject: [PATCH 065/284] Issue #63: MIKMIDIChannelEvent now used for MIKMIDIEventTypeMIDIChannelPressureMessage until we get a subclass for that. --- Source/MIKMIDIChannelEvent.m | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index 4d94e79a..fced01e6 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -25,15 +25,11 @@ @interface MIKMIDIChannelEvent () @implementation MIKMIDIChannelEvent -+ (void)load { //[MIKMIDIEvent registerSubclass:self]; -} ++ (void)load { [MIKMIDIEvent registerSubclass:self]; } + (NSArray *)supportedMIDIEventTypes { - return @[@(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage), - @(MIKMIDIEventTypeMIDIControlChangeMessage), - @(MIKMIDIEventTypeMIDIProgramChangeMessage), - @(MIKMIDIEventTypeMIDIChannelPressureMessage), - @(MIKMIDIEventTypeMIDIPitchBendChangeMessage)]; + // We have subclasses for all but MIKMIDIEventTypeMIDIChannelPressureMessage. + return @[@(MIKMIDIEventTypeMIDIChannelPressureMessage)]; } + (Class)immutableCounterpartClass { return [MIKMIDIChannelEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIChannelEvent class]; } From f3e45874ab93f7992b03d565e721d49c8ca43918 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 16:25:09 -0700 Subject: [PATCH 066/284] Issue #63: Added support for MIKMIDIChannelEvent and subclasses to -[MIKMIDITrack insertMIDIEvent:] --- Source/MIKMIDITrack.m | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 37637de7..a21e70a9 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -75,7 +75,7 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event const void *data = [event.data bytes]; switch (event.eventType) { - case kMusicEventType_NULL: + case MIKMIDIEventTypeNULL: NSLog(@"Warning: %s attempted to insert NULL event.", __PRETTY_FUNCTION__); break; @@ -105,11 +105,6 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event if (err) NSLog(@"MusicTrackNewMIDINoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; - case MIKMIDIEventTypeMIDIChannelMessage: - err = MusicTrackNewMIDIChannelEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewMIDIChannelEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; - case MIKMIDIEventTypeMIDIRawData: err = MusicTrackNewMIDIRawDataEvent(track, timeStamp, data); if (err) NSLog(@"MusicTrackNewMIDIRawDataEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); @@ -125,6 +120,16 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event if (err) NSLog(@"MusicTrackNewAUPresetEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); break; + case MIKMIDIEventTypeMIDIChannelMessage: + case MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage: + case MIKMIDIEventTypeMIDIControlChangeMessage: + case MIKMIDIEventTypeMIDIProgramChangeMessage: + case MIKMIDIEventTypeMIDIChannelPressureMessage: + case MIKMIDIEventTypeMIDIPitchBendChangeMessage: + err = MusicTrackNewMIDIChannelEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewMIDIChannelEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; + case MIKMIDIEventTypeMeta: case MIKMIDIEventTypeMetaSequence: case MIKMIDIEventTypeMetaText: From 0df95c2ee3bf75715a40db045e94f4799557619c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 16:27:30 -0700 Subject: [PATCH 067/284] Issue #37: MIKMIDISequencer now schedules MIKMIDIChannelEvents during playback. --- Source/MIKMIDISequencer.m | 67 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 5f12e24c..3060ac92 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -13,6 +13,7 @@ #import "MIKMIDIClock.h" #import "MIKMIDITempoEvent.h" #import "MIKMIDINoteEvent.h" +#import "MIKMIDIChannelEvent.h" #import "MIKMIDINoteOnCommand.h" #import "MIKMIDINoteOffCommand.h" #import "MIKMIDIDeviceManager.h" @@ -194,12 +195,12 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam [self sendPendingNoteOffCommandsUpToMIDITimeStamp:actualToMIDITimeStamp]; // Get relevant tempo events - NSMutableDictionary *tempoEvents = [NSMutableDictionary dictionary]; - NSMutableDictionary *timeStampEvents = [NSMutableDictionary dictionary]; + NSMutableDictionary *tempoEventsByTimeStamp = [NSMutableDictionary dictionary]; + NSMutableDictionary *otherEventsByTimeStamp = [NSMutableDictionary dictionary]; for (MIKMIDITempoEvent *tempoEvent in [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]) { NSNumber *timeStampKey = @(tempoEvent.timeStamp + playbackOffset); - timeStampEvents[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; - tempoEvents[timeStampKey] = tempoEvent; + otherEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; + tempoEventsByTimeStamp[timeStampKey] = tempoEvent; } // Get other events @@ -207,34 +208,35 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIKMIDIDestinationEndpoint *destination = [self destinationEndpointForTrack:track]; for (MIKMIDIEvent *event in [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]) { NSNumber *timeStampKey = @(event.timeStamp + playbackOffset); - NSMutableArray *eventsAtTimeStamp = timeStampEvents[timeStampKey] ? timeStampEvents[timeStampKey] : [NSMutableArray array]; + NSMutableArray *eventsAtTimeStamp = otherEventsByTimeStamp[timeStampKey] ? otherEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimeStamp addObject:[MIKMIDIEventWithDestination eventWithDestination:destination event:event]]; - timeStampEvents[timeStampKey] = eventsAtTimeStamp; + otherEventsByTimeStamp[timeStampKey] = eventsAtTimeStamp; } } // Get click track events for (MIKMIDIEventWithDestination *destinationEvent in [self clickTrackEventsFromTimeStamp:fromMusicTimeStamp toTimeStamp:toMusicTimeStamp]) { NSNumber *timeStampKey = @(destinationEvent.event.timeStamp + playbackOffset); - NSMutableArray *eventsAtTimesStamp = timeStampEvents[timeStampKey] ? timeStampEvents[timeStampKey] : [NSMutableArray array]; + NSMutableArray *eventsAtTimesStamp = otherEventsByTimeStamp[timeStampKey] ? otherEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimesStamp addObject:destinationEvent]; - timeStampEvents[timeStampKey] = eventsAtTimesStamp; + otherEventsByTimeStamp[timeStampKey] = eventsAtTimesStamp; } // Schedule events MIDITimeStamp lastProcessedMIDITimeStamp = fromMIDITimeStamp; - for (NSNumber *timeStampKey in [timeStampEvents.allKeys sortedArrayUsingSelector:@selector(compare:)]) { + for (NSNumber *timeStampKey in [otherEventsByTimeStamp.allKeys sortedArrayUsingSelector:@selector(compare:)]) { MusicTimeStamp musicTimeStamp = timeStampKey.doubleValue; if (isLooping && (musicTimeStamp < loopStartTimeStamp || musicTimeStamp >= loopEndTimeStamp)) continue; MIDITimeStamp midiTimeStamp = [clock midiTimeStampForMusicTimeStamp:musicTimeStamp]; if (midiTimeStamp < MIKMIDIGetCurrentTimeStamp() && midiTimeStamp > fromMIDITimeStamp) continue; // prevents events that were just recorded from being scheduled - MIKMIDITempoEvent *tempoEventAtTimeStamp = tempoEvents[timeStampKey]; + + MIKMIDITempoEvent *tempoEventAtTimeStamp = tempoEventsByTimeStamp[timeStampKey]; if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm atMIDITimeStamp:midiTimeStamp]; - NSArray *events = timeStampEvents[timeStampKey]; + NSArray *events = otherEventsByTimeStamp[timeStampKey]; for (id eventObject in events) { if ([eventObject isKindOfClass:[MIKMIDIEventWithDestination class]]) { - [self scheduleEventWithDestination:eventObject atMIDITimeStamp:midiTimeStamp]; + [self scheduleEventWithDestination:eventObject]; } } @@ -263,40 +265,41 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } } -- (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationEvent atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +- (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationEvent { MIKMIDIEvent *event = destinationEvent.event; MIKMIDIDestinationEndpoint *destination = destinationEvent.destination; - NSMutableArray *commands = [NSMutableArray array]; NSMutableDictionary *pendingNoteOffs = self.pendingNoteOffs; NSMutableOrderedSet *pendingNoteOffTimeStamps = self.pendingNoteOffMIDITimeStamps; - if ([event isKindOfClass:[MIKMIDINoteEvent class]]) { - // Note On - MIKMIDINoteEvent *noteEvent = (MIKMIDINoteEvent *)event; - MIKMutableMIDINoteOnCommand *noteOn = [MIKMutableMIDINoteOnCommand commandForCommandType:MIKMIDICommandTypeNoteOn]; - noteOn.midiTimestamp = midiTimeStamp; - noteOn.channel = noteEvent.channel; - noteOn.note = noteEvent.note; - noteOn.velocity = noteEvent.velocity; - [commands addObject:noteOn]; - - // Note Off - MIKMutableMIDINoteOffCommand *noteOff = [MIKMutableMIDINoteOffCommand commandForCommandType:MIKMIDICommandTypeNoteOff]; - MIDITimeStamp noteOffTimeStamp = [self.clock midiTimeStampForMusicTimeStamp:noteEvent.endTimeStamp + self.playbackOffset]; - noteOff.midiTimestamp = noteOffTimeStamp; - noteOff.channel = noteEvent.channel; - noteOff.note = noteEvent.note; - noteOff.velocity = noteEvent.releaseVelocity; + NSArray *commands = nil; + if (event.eventType == MIKMIDIEventTypeMIDINoteMessage) { + commands = [MIKMIDICommand commandsFromNoteEvent:(MIKMIDINoteEvent *)event clock:self.clock]; + + // Add note off to pending note offs + MIKMIDINoteOffCommand *noteOff = [commands lastObject]; + MIDITimeStamp noteOffTimeStamp = noteOff.midiTimestamp + [self.clock midiTimeStampForMusicTimeStamp:self.playbackOffset]; NSMutableArray *pendingNoteOffsAtTimeStamp = pendingNoteOffs[@(noteOffTimeStamp)]; if (!pendingNoteOffsAtTimeStamp) pendingNoteOffsAtTimeStamp = [NSMutableArray array]; NSNumber *timeStampNumber = @(noteOffTimeStamp); [pendingNoteOffsAtTimeStamp addObject:[MIKMIDICommandWithDestination commandWithDestination:destination command:noteOff]]; pendingNoteOffs[@(noteOffTimeStamp)] = pendingNoteOffsAtTimeStamp; [pendingNoteOffTimeStamps addObject:timeStampNumber]; + } else if ([event isKindOfClass:[MIKMIDIChannelEvent class]]) { + MIKMIDICommand *command = [MIKMIDICommand commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:self.clock]; + commands = [NSArray arrayWithObjects:command, nil]; + } + + // Adjust commands' time stamps to account for our playback offset. + NSMutableArray *adjustedCommands = [NSMutableArray array]; + for (MIKMIDICommand *command in commands) { + MIKMutableMIDICommand *scratch = [command mutableCopy]; + MusicTimeStamp musicTimestamp = [self.clock musicTimeStampForMIDITimeStamp:scratch.midiTimestamp]; + scratch.midiTimestamp = [self.clock midiTimeStampForMusicTimeStamp:(musicTimestamp + self.playbackOffset)]; + [adjustedCommands addObject:scratch]; } - [self sendCommands:commands toDestinationEndpoint:destination]; + if ([adjustedCommands count]) [self sendCommands:adjustedCommands toDestinationEndpoint:destination]; } - (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp From 9db4a19cf26b57aecd16eaf50297611650e53fdb Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 17:02:21 -0700 Subject: [PATCH 068/284] Replaced +[MIKMIDIEvent minimumDataSize] subclass (protected) method with the more powerful +initialData. --- Source/MIKMIDIChannelEvent.m | 2 +- Source/MIKMIDIEvent.m | 10 +++------- Source/MIKMIDIEvent_SubclassMethods.h | 13 +++++++++---- Source/MIKMIDIMetaEvent.m | 2 +- Source/MIKMIDIMetaKeySignatureEvent.m | 7 ++++++- Source/MIKMIDIMetaTimeSignatureEvent.m | 7 ++++++- Source/MIKMIDINoteEvent.m | 2 +- Source/MIKMIDITempoEvent.m | 2 +- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index fced01e6..a42baacd 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -34,7 +34,7 @@ + (NSArray *)supportedMIDIEventTypes + (Class)immutableCounterpartClass { return [MIKMIDIChannelEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIChannelEvent class]; } + (BOOL)isMutable { return NO; } -+ (size_t)minimumDataSize { return sizeof(MIDIChannelMessage); } ++ (NSData *)initialData { return [NSData dataWithBytes:&(MIDIChannelMessage){0} length:sizeof(MIDIChannelMessage)]; } + (instancetype)channelEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDIChannelMessage)message; { diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index c99415fc..83642ffa 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -32,7 +32,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[]; } + (Class)immutableCounterpartClass; { return [MIKMIDIEvent class]; } + (Class)mutableCounterpartClass; { return [MIKMutableMIDIEvent class]; } + (BOOL)isMutable { return NO; } -+ (size_t)minimumDataSize { return 0; } ++ (NSData *)initialData { return [NSData data]; } + (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data { @@ -66,12 +66,8 @@ - (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMI _timeStamp = timeStamp; _eventType = eventType; - NSMutableData *internalData = [NSMutableData dataWithData:data]; - size_t minSize = [[self class] minimumDataSize]; - if ([internalData length] < minSize) { - [internalData increaseLengthBy:(minSize - [internalData length])]; - } - _internalData = internalData; + if (!data) data = [[self class] initialData]; + _internalData = [NSMutableData dataWithData:data]; } return self; } diff --git a/Source/MIKMIDIEvent_SubclassMethods.h b/Source/MIKMIDIEvent_SubclassMethods.h index 9fc62047..e5a4ef47 100644 --- a/Source/MIKMIDIEvent_SubclassMethods.h +++ b/Source/MIKMIDIEvent_SubclassMethods.h @@ -59,12 +59,17 @@ + (BOOL)isMutable; /** - * Subclasses of MIKMIDIEvent can override this to specify a minum internal data length - * necessary to hold their contents. For example, MIKMIDINoteEvent returns sizeof(MIDINoteMessage). + * Subclasses of MIKMIDIEvent can override this to provide initial "blank" data including any + * necessary fixed bytes for their class. For example, MIKMIDIChannelEvent subclasses return + * data with the first nibble set to the appropriate status/subtype for their class. * - * @return A size_t value indicating the minimum size in bytes required to hold the receiver's data. + * Overriding this method can also be used to ensure that the internal data for an empty event + * meets the required minimum length. + * + * @return An NSData instance containing properly-sized blank/empty state data required by the receiver. + * Must NOT be nil (empty data is OK). */ -+ (size_t)minimumDataSize; ++ (NSData *)initialData; /** * This is the property used internally by MIKMIDIEvent to store the raw data for diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index 4f5495ce..c66e7baa 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -21,7 +21,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMeta)]; } + (Class)immutableCounterpartClass { return [MIKMIDIMetaEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaEvent class]; } + (BOOL)isMutable { return NO; } -+ (size_t)minimumDataSize { return sizeof(MIDIMetaEvent); } ++ (NSData *)initialData { return [NSData dataWithBytes:&(MIDIMetaEvent){0} length:sizeof(MIDIMetaEvent)]; } - (NSString *)additionalEventDescription { diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index baade9ae..6c82961a 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -21,7 +21,12 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaKeySignatu + (Class)immutableCounterpartClass { return [MIKMIDIMetaKeySignatureEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaKeySignatureEvent class]; } + (BOOL)isMutable { return NO; } -+ (size_t)minimumDataSize { return [super minimumDataSize] + 2; /* Account for key and scale bytes */ } ++ (NSData *)initialData +{ + NSMutableData *superData = [[super initialData] mutableCopy]; + [superData increaseLengthBy:2]; // Account for key and scale bytes + return [superData copy]; +} + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index c0c375ae..16b1a6b4 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -21,7 +21,12 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMetaTimeSignat + (Class)immutableCounterpartClass { return [MIKMIDIMetaTimeSignatureEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTimeSignatureEvent class]; } + (BOOL)isMutable { return NO; } -+ (size_t)minimumDataSize { return [super minimumDataSize] + 4; /* Account for numerator, denominator, metronome, and 32nd note bytes */ } ++ (NSData *)initialData +{ + NSMutableData *superData = [[super initialData] mutableCopy]; + [superData increaseLengthBy:2]; // Account for numerator, denominator, metronome, and 32nd note bytes + return [superData copy]; +} + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 9462af80..7e69353f 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -22,7 +22,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDINoteMessag + (Class)immutableCounterpartClass { return [MIKMIDINoteEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDINoteEvent class]; } + (BOOL)isMutable { return NO; } -+ (size_t)minimumDataSize { return sizeof(MIDINoteMessage); } ++ (NSData *)initialData { return [NSData dataWithBytes:&(MIDINoteMessage){0} length:sizeof(MIDINoteMessage)]; } #pragma mark - Lifecycle diff --git a/Source/MIKMIDITempoEvent.m b/Source/MIKMIDITempoEvent.m index 4ca0a4d9..a3f94058 100644 --- a/Source/MIKMIDITempoEvent.m +++ b/Source/MIKMIDITempoEvent.m @@ -21,7 +21,7 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeExtendedTempo) + (Class)immutableCounterpartClass { return [MIKMIDITempoEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDITempoEvent class]; } + (BOOL)isMutable { return NO; } -+ (size_t)minimumDataSize { return sizeof(ExtendedTempoEvent); } ++ (NSData *)initialData { return [NSData dataWithBytes:&(ExtendedTempoEvent){0} length:sizeof(ExtendedTempoEvent)]; } + (instancetype)tempoEventWithTimeStamp:(MusicTimeStamp)timeStamp tempo:(Float64)bpm; { From d733c3df77f22f618082d9310db9511a0dd19e25 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Mar 2015 17:03:08 -0700 Subject: [PATCH 069/284] Issue #63: Implemented +initialData in MIKMIDIChannelEvent subclasses. Fixes e.g. [[MIKMIDIControlChangeEvent alloc] init]. --- Source/MIKMIDIControlChangeEvent.m | 10 ++++++++++ Source/MIKMIDIPitchBendChangeEvent.m | 10 ++++++++++ Source/MIKMIDIPolyphonicKeyPressureEvent.m | 10 ++++++++++ Source/MIKMIDIProgramChangeEvent.m | 10 ++++++++++ 4 files changed, 40 insertions(+) diff --git a/Source/MIKMIDIControlChangeEvent.m b/Source/MIKMIDIControlChangeEvent.m index eec4bfef..8fb3b217 100644 --- a/Source/MIKMIDIControlChangeEvent.m +++ b/Source/MIKMIDIControlChangeEvent.m @@ -36,6 +36,16 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIControlCha + (Class)immutableCounterpartClass { return [MIKMIDIControlChangeEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIControlChangeEvent class]; } + (BOOL)isMutable { return NO; } ++ (NSData *)initialData +{ + MIDIChannelMessage message = { + .status = MIKMIDIChannelEventTypeControlChange, + .data1 = 0, + .data2 = 0, + .reserved = 0, + }; + return [NSData dataWithBytes:&message length:sizeof(message)]; +} - (NSString *)additionalEventDescription { diff --git a/Source/MIKMIDIPitchBendChangeEvent.m b/Source/MIKMIDIPitchBendChangeEvent.m index c78108c0..ae74110c 100644 --- a/Source/MIKMIDIPitchBendChangeEvent.m +++ b/Source/MIKMIDIPitchBendChangeEvent.m @@ -35,6 +35,16 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIPitchBendC + (Class)immutableCounterpartClass { return [MIKMIDIPitchBendChangeEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIPitchBendChangeEvent class]; } + (BOOL)isMutable { return NO; } ++ (NSData *)initialData +{ + MIDIChannelMessage message = { + .status = MIKMIDIChannelEventTypePitchBendChange, + .data1 = 0, + .data2 = 0, + .reserved = 0, + }; + return [NSData dataWithBytes:&message length:sizeof(message)]; +} - (NSString *)additionalEventDescription { diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.m b/Source/MIKMIDIPolyphonicKeyPressureEvent.m index 94d6db8f..34fbcfec 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.m +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.m @@ -36,6 +36,16 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIPolyphonic + (Class)immutableCounterpartClass { return [MIKMIDIPolyphonicKeyPressureEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIPolyphonicKeyPressureEvent class]; } + (BOOL)isMutable { return NO; } ++ (NSData *)initialData +{ + MIDIChannelMessage message = { + .status = MIKMIDIChannelEventTypePolyphonicKeyPressure, + .data1 = 0, + .data2 = 0, + .reserved = 0, + }; + return [NSData dataWithBytes:&message length:sizeof(message)]; +} - (NSString *)additionalEventDescription { diff --git a/Source/MIKMIDIProgramChangeEvent.m b/Source/MIKMIDIProgramChangeEvent.m index 14cca191..54a7fe34 100644 --- a/Source/MIKMIDIProgramChangeEvent.m +++ b/Source/MIKMIDIProgramChangeEvent.m @@ -31,6 +31,16 @@ + (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIProgramCha + (Class)immutableCounterpartClass { return [MIKMIDIProgramChangeEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIProgramChangeEvent class]; } + (BOOL)isMutable { return NO; } ++ (NSData *)initialData +{ + MIDIChannelMessage message = { + .status = MIKMIDIChannelEventTypeProgramChange, + .data1 = 0, + .data2 = 0, + .reserved = 0, + }; + return [NSData dataWithBytes:&message length:sizeof(message)]; +} - (NSString *)additionalEventDescription { From 00fcaab510bb4da0b338cbc5acb830cbe6f64c94 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Mar 2015 10:12:18 -0700 Subject: [PATCH 070/284] Added link to General MIDI sound list to MIKMIDIProgramChangeEvent documentation. --- Source/MIKMIDIProgramChangeEvent.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/MIKMIDIProgramChangeEvent.h b/Source/MIKMIDIProgramChangeEvent.h index da081afd..2133b48c 100644 --- a/Source/MIKMIDIProgramChangeEvent.h +++ b/Source/MIKMIDIProgramChangeEvent.h @@ -22,6 +22,11 @@ /** * The program (aka patch) number. From 0-127. + * + * Assuming the device or synthesizer playing this event + * supports the General MIDI sound set, you can find + * a list of instruments by their program number here: + * http://www.midi.org/techspecs/gm1sound.php */ @property (nonatomic, readonly) NSUInteger programNumber; From 2dab181170d04696fbc155add5641ded02c3d8f0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Mar 2015 10:41:49 -0700 Subject: [PATCH 071/284] MIKMIDISequencer now creates separate default synths for each track. Fixes program change events affecting all tracks. --- Source/MIKMIDISequencer.h | 32 +++++++++++++++++++++-------- Source/MIKMIDISequencer.m | 42 ++++++++++++++++++--------------------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 47f8eed6..95d661b6 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -165,7 +165,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * Sets the destination endpoint for a track in the sequencer's sequence. * Calling this method is optional. By default, the sequencer will setup internal default endpoints - * so that playback "just works". + * connected to synthesizers so that playback "just works". * * @note If track is not contained by the receiver's sequence, this method does nothing. * @@ -177,14 +177,37 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * Returns the destination endpoint for a track in the sequencer's sequence. * + * MIKMIDISequencer will automatically create its own default endpoints connected to + * MIKMIDISynthesizers for any tracks not configured manually. This means that even if you + * haven't called -setDestinationEndpoint:forTrack:, you can use this method to retrieve + * the default endpoint for a given track. + * * @note If track is not contained by the receiver's sequence, this method returns nil. * * @param track An MIKMIDITrack instance. * * @return The destination endpoint associated with track, or nil if one can't be found. + * + * @see -setDestinationEndpoint:forTrack: + * @see -builtinSynthesizerForTrack: */ - (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track; +/** + * Returns synthesizer the receiver will use to synthesize MIDI during playback + * for any tracks whose MIDI has not been routed to a custom endpoint using + * -setDestinationEndpoint:forTrack:. For tracks where a custom endpoint has + * been set, this method returns nil. + * + * The caller is free to reconfigure the synthesizer(s) returned by this method, + * e.g. to load a custom soundfont file or select a different instrument. + * + * @param track The track for which the builtin synthesizer is desired. + * + * @return An MIKMIDISynthesizer instance, or nil if a builtin synthesizer for track doesn't exist. + */ +- (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track; + #pragma mark - Properties /** @@ -262,13 +285,6 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (nonatomic) MusicTimeStamp loopEndTimeStamp; -/** - * The synthesizer the receiver will use to synthesize MIDI during playback - * for any tracks whose MIDI has not been routed to a custom endpoint using - * -setDestinationEndpoint:forTrack:. - */ -@property (nonatomic, strong, readonly) MIKMIDISynthesizer *builtinSynthesizer; - /** * The metronome to send click track events to. */ diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 3060ac92..361538cd 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -74,10 +74,9 @@ @interface MIKMIDISequencer () @property (nonatomic) MusicTimeStamp startingTimeStamp; @property (nonatomic, strong) NSMapTable *tracksToDestinationsMap; +@property (nonatomic, strong) NSMapTable *tracksToDefaultSynthsMap; @property (nonatomic, strong) MIKMIDIClientDestinationEndpoint *metronomeEndpoint; -@property (nonatomic, strong, readonly) MIKMIDIClientDestinationEndpoint *builtinEndpoint; - @end @@ -94,6 +93,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence _preRoll = 4; _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; _tracksToDestinationsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; + _tracksToDefaultSynthsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; } return self; } @@ -513,12 +513,28 @@ - (MIKMIDINoteEvent *)pendingNoteEventWithNoteNumber:(NSNumber *)noteNumber chan - (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:(MIKMIDITrack *)track { [self.tracksToDestinationsMap setObject:endpoint forKey:track]; + [self.tracksToDefaultSynthsMap removeObjectForKey:track]; } - (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track { MIKMIDIDestinationEndpoint *result = [self.tracksToDestinationsMap objectForKey:track]; - return result ?: self.builtinEndpoint; + if (!result) { + // Create a default endpoint and synthesizer + NSString *name = [NSString stringWithFormat:@"<%@: %p> Default Endpoint %d", NSStringFromClass([self class]), self, (int)track.trackNumber]; + result = [[MIKMIDIClientDestinationEndpoint alloc] initWithName:name receivedMessagesHandler:nil]; + [self setDestinationEndpoint:result forTrack:track]; + + MIKMIDISynthesizer *synth = [MIKMIDIEndpointSynthesizer synthesizerWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)result]; + [self.tracksToDefaultSynthsMap setObject:synth forKey:synth]; + } + return result; +} + +- (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track +{ + [[self destinationEndpointForTrack:track] self]; // Will force creation of a synth if one doesn't exist, but should + return [self.tracksToDefaultSynthsMap objectForKey:track]; } #pragma mark - Click Track @@ -637,26 +653,6 @@ - (void)setMetronome:(MIKMIDIMetronome *)metronome } } -@synthesize builtinEndpoint = _builtinEndpoint; -- (MIKMIDIClientDestinationEndpoint *)builtinEndpoint -{ - if (!_builtinEndpoint) { - NSString *name = [NSString stringWithFormat:@"%@ (%p)", NSStringFromClass([self class]), self]; - _builtinEndpoint = [[MIKMIDIClientDestinationEndpoint alloc] initWithName:name receivedMessagesHandler:nil]; - if (_builtinEndpoint) [[self builtinSynthesizer] self]; // Create synth - } - return _builtinEndpoint; -} - -@synthesize builtinSynthesizer = _builtinSynthesizer; -- (MIKMIDISynthesizer *)builtinSynthesizer -{ - if (!_builtinSynthesizer) { - _builtinSynthesizer = [MIKMIDIEndpointSynthesizer synthesizerWithClientDestinationEndpoint:self.builtinEndpoint]; - } - return _builtinSynthesizer; -} - @end #pragma mark - From 8d9c4d7de5ea64832530e1c1639fb37309e95d5f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Mar 2015 10:58:33 -0700 Subject: [PATCH 072/284] Issue #63: Made MIKMIDIChannelEvent headers public in iOS framework target. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 4790b605..1d97f6d6 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -109,11 +109,11 @@ 9D74EF9517A713A100BEE89F /* NSUIApplication+MIKMIDI.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF6217A713A100BEE89F /* NSUIApplication+MIKMIDI.m */; }; 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */; }; 9D84951C1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9D84951D1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; }; + 9D84951D1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D84951E1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; 9D84951F1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */; }; 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9D8495231AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; }; + 9D8495231AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D8495201AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D8495241AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */; }; 9D8495251AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8495211AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.m */; }; 9D877D841A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D877D821A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -244,11 +244,11 @@ 9DED4E241AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; 9DED4E251AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; }; + 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; 9DEF1CAB1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; 9DEF1CAE1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9DEF1CAF1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; }; + 9DEF1CAF1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DEF1CB01AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */; }; 9DEF1CB11AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */; }; 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E771831841A004EE5F4 /* MIKMIDICommandThrottler.h */; settings = {ATTRIBUTES = (Public, ); }; }; From 075bc2f98f9dd22663b82afd6d78baae699f835a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Mar 2015 12:29:56 -0700 Subject: [PATCH 073/284] Issue #64: Made MIKMIDIPrivateUtilties.h private. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 1d97f6d6..892a9f7b 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -220,7 +220,7 @@ 9DAF8B801A7B00B100F46528 /* MIKMIDIUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5F17A713A100BEE89F /* MIKMIDIUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DAF8B811A7B00BB00F46528 /* MIKMIDICommandThrottler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E771831841A004EE5F4 /* MIKMIDICommandThrottler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DAF8B821A7B00BB00F46528 /* MIKMIDIClock.h in Headers */ = {isa = PBXBuildFile; fileRef = 833B73DC1A26346F00E0CC9F /* MIKMIDIClock.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9DAF8B831A7B00BB00F46528 /* MIKMIDIPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E7B18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DAF8B831A7B00BB00F46528 /* MIKMIDIPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E7B18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h */; }; 9DAF8B881A7B01FA00F46528 /* NSUIApplication+MIKMIDI.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF6117A713A100BEE89F /* NSUIApplication+MIKMIDI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DAF8B891A7B01FF00F46528 /* NSUIApplication+MIKMIDI.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF6217A713A100BEE89F /* NSUIApplication+MIKMIDI.m */; }; 9DAF8B8A1A7B028B00F46528 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DAF8B841A7B019900F46528 /* libxml2.dylib */; }; @@ -253,7 +253,7 @@ 9DEF1CB11AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */; }; 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E771831841A004EE5F4 /* MIKMIDICommandThrottler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DF99E7A1831841A004EE5F4 /* MIKMIDICommandThrottler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF99E781831841A004EE5F4 /* MIKMIDICommandThrottler.m */; }; - 9DF99E7D18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E7B18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DF99E7D18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DF99E7B18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h */; }; 9DF99E7E18318D44004EE5F4 /* MIKMIDIPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF99E7C18318D44004EE5F4 /* MIKMIDIPrivateUtilities.m */; }; /* End PBXBuildFile section */ From 47726691a4df8c5114aa9c8d2af47d74643b0271 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Mar 2015 14:29:45 -0700 Subject: [PATCH 074/284] Fixed bug that caused MIKMIDISynthesizer to always synthesize incoming commands on channel 0. --- Source/MIKMIDISynthesizer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index c97f1a2d..40dd653b 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -245,7 +245,7 @@ - (void)setGraph:(AUGraph)graph - (void)handleMIDIMessages:(NSArray *)commands { for (MIKMIDICommand *command in commands) { - OSStatus err = MusicDeviceMIDIEvent(self.instrumentUnit, command.commandType, command.dataByte1, command.dataByte2, 0); + OSStatus err = MusicDeviceMIDIEvent(self.instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, 0); if (err) NSLog(@"Unable to send MIDI command to synthesizer %@: %i", command, err); } } From 7a8c6048602d9bd4db838c6432da6438c2b1bb7a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Mar 2015 14:56:32 -0700 Subject: [PATCH 075/284] Added +[MIKMIDICommand commandsFromMIDIEvent:clock:] to category in MIKMIDIEvent.h/m. May move later. --- Source/MIKMIDIEvent.h | 12 ++++++++++++ Source/MIKMIDIEvent.m | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index d74af3f2..c60ddd73 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -198,4 +198,16 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) @property (nonatomic) MusicTimeStamp timeStamp; @property (nonatomic, strong, readwrite) NSMutableData *data; +@end + +#pragma mark - MIKMIDICommand+MIKMIDIEventToCommands + +#import + +@class MIKMIDIClock; + +@interface MIKMIDICommand (MIKMIDIEventToCommands) + ++ (NSArray *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(MIKMIDIClock *)clock; + @end \ No newline at end of file diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 83642ffa..04076f1a 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -216,4 +216,27 @@ + (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return [[self immutabl @dynamic data; @dynamic timeStamp; +@end + +#pragma mark - MIKMIDICommand+MIKMIDIEventToCommands + +#import "MIKMIDIClock.h" +#import "MIKMIDINoteEvent.h" +#import "MIKMIDIChannelEvent.h" + +@implementation MIKMIDICommand (MIKMIDIEventToCommands) + ++ (NSArray *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(MIKMIDIClock *)clock +{ + NSMutableArray *result = [NSMutableArray array]; + if ([event isKindOfClass:[MIKMIDINoteEvent class]]) { + NSArray *commands = [MIKMIDICommand commandsFromNoteEvent:(MIKMIDINoteEvent *)event clock:clock]; + if (commands) [result addObjectsFromArray:commands]; + } else if ([event isKindOfClass:[MIKMIDIChannelEvent class]]) { + MIKMIDICommand *command = [MIKMIDICommand commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:clock]; + if (command) [result addObject:command]; + } + return result; +} + @end \ No newline at end of file From 2706b25a8a9e78f0e1e2f59904c0a2aa8e0b23b1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Mar 2015 15:45:09 -0700 Subject: [PATCH 076/284] Issue #65: Added MIKMIDIPitchBendChangeCommand. --- .../project.pbxproj | 2 +- Framework/MIKMIDI.xcodeproj/project.pbxproj | 16 +++- Source/MIKMIDI.h | 1 + Source/MIKMIDIChannelEvent.h | 3 +- Source/MIKMIDIChannelEvent.m | 7 +- Source/MIKMIDIChannelVoiceCommand.m | 4 +- Source/MIKMIDIMetaEvent.h | 2 +- Source/MIKMIDIPitchBendChangeCommand.h | 42 +++++++++ Source/MIKMIDIPitchBendChangeCommand.m | 87 +++++++++++++++++++ 9 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 Source/MIKMIDIPitchBendChangeCommand.h create mode 100644 Source/MIKMIDIPitchBendChangeCommand.m diff --git a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index 7ab6f151..33fbaefc 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -96,12 +96,12 @@ 9DB2A5EA192D184D0047A3EB = { isa = PBXGroup; children = ( - 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */, 9DB2A62A192D18970047A3EB /* Source */, 9DB2A631192D189E0047A3EB /* Resources */, 9DB2A61A192D184D0047A3EB /* MIDI Files TestbedTests */, 9DB2A5F5192D184D0047A3EB /* Frameworks */, 9DB2A5F4192D184D0047A3EB /* Products */, + 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */, ); sourceTree = ""; }; diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 892a9f7b..06bd59bf 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -243,6 +243,10 @@ 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DED4E241AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; 9DED4E251AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */; }; + 9DED4E361AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E341AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DED4E371AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DED4E341AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DED4E381AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E351AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m */; }; + 9DED4E391AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DED4E351AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m */; }; 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DEF1CAA1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */; }; @@ -391,6 +395,8 @@ 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeEvent.h; sourceTree = ""; }; 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPitchBendChangeEvent.m; sourceTree = ""; }; + 9DED4E341AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeCommand.h; sourceTree = ""; }; + 9DED4E351AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPitchBendChangeCommand.m; sourceTree = ""; }; 9DEF1CA61AA6769E00E10273 /* MIKMIDIChannelEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelEvent.h; sourceTree = ""; }; 9DEF1CA71AA6769E00E10273 /* MIKMIDIChannelEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelEvent.m; sourceTree = ""; }; 9DEF1CAC1AA6800C00E10273 /* MIKMIDIControlChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIControlChangeEvent.h; sourceTree = ""; }; @@ -682,6 +688,10 @@ 9D74EF3217A713A100BEE89F /* MIKMIDIChannelVoiceCommand.m */, 9D74EF3617A713A100BEE89F /* MIKMIDIControlChangeCommand.h */, 9D74EF3717A713A100BEE89F /* MIKMIDIControlChangeCommand.m */, + 9DED4E341AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h */, + 9DED4E351AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m */, + 9D877DF91A670261001BA997 /* MIKMIDIProgramChangeCommand.h */, + 9D877DFA1A670261001BA997 /* MIKMIDIProgramChangeCommand.m */, 9D74EF4E17A713A100BEE89F /* MIKMIDINoteOnCommand.h */, 9D74EF4F17A713A100BEE89F /* MIKMIDINoteOnCommand.m */, 9D74EF4C17A713A100BEE89F /* MIKMIDINoteOffCommand.h */, @@ -690,8 +700,6 @@ 9D74EF5E17A713A100BEE89F /* MIKMIDISystemMessageCommand.m */, 9D74EF5B17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.h */, 9D74EF5C17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.m */, - 9D877DF91A670261001BA997 /* MIKMIDIProgramChangeCommand.h */, - 9D877DFA1A670261001BA997 /* MIKMIDIProgramChangeCommand.m */, ); name = Commands; sourceTree = ""; @@ -753,6 +761,7 @@ 839D937319C3A319007589C3 /* MIKMIDITempoEvent.h in Headers */, 839D936119C3A2F5007589C3 /* MIKMIDIMetaTimeSignatureEvent.h in Headers */, 9DB366F61A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */, + 9DED4E361AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h in Headers */, 839D935B19C3A2F5007589C3 /* MIKMIDIMetaMarkerTextEvent.h in Headers */, 9D877DFD1A6706E6001BA997 /* MIKMIDIProgramChangeCommand.h in Headers */, 839D936319C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, @@ -819,6 +828,7 @@ 9DAF8B701A7B00A700F46528 /* MIKMIDIMetaCuePointEvent.h in Headers */, 9DAF8B671A7B008A00F46528 /* MIKMIDIProgramChangeCommand.h in Headers */, 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */, + 9DED4E371AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h in Headers */, 9DB366F71A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */, 9DAF8B691A7B009100F46528 /* MIKMIDIMappingGenerator.h in Headers */, 9DAF8B641A7B008A00F46528 /* MIKMIDINoteOffCommand.h in Headers */, @@ -1002,6 +1012,7 @@ 839D936E19C3A30B007589C3 /* MIKMIDISequence.m in Sources */, 839D933419C3A2C9007589C3 /* MIKMIDIEvent.m in Sources */, 9DB366F21A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */, + 9DED4E381AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9D74EF8F17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.m in Sources */, 9D74EF9117A713A100BEE89F /* MIKMIDISystemMessageCommand.m in Sources */, 839D935419C3A2F5007589C3 /* MIKMIDIMetaEvent.m in Sources */, @@ -1021,6 +1032,7 @@ 9DAF8B221A7AFF5900F46528 /* MIKMIDIEntity.m in Sources */, 9DAF8B231A7AFF5900F46528 /* MIKMIDIPort.m in Sources */, 9DAF8B241A7AFF5900F46528 /* MIKMIDIInputPort.m in Sources */, + 9DED4E391AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9DAF8B251A7AFF5900F46528 /* MIKMIDIOutputPort.m in Sources */, 9DAF8B2C1A7AFF6700F46528 /* MIKMIDIErrors.m in Sources */, 9DAF8B2D1A7AFF6700F46528 /* MIKMIDICommand.m in Sources */, diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 7c5391e6..0f1fedf0 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -34,6 +34,7 @@ #import "MIKMIDIChannelVoiceCommand.h" #import "MIKMIDIControlChangeCommand.h" #import "MIKMIDIProgramChangeCommand.h" +#import "MIKMIDIPitchBendChangeCommand.h" #import "MIKMIDINoteOnCommand.h" #import "MIKMIDINoteOffCommand.h" #import "MIKMIDISystemExclusiveCommand.h" diff --git a/Source/MIKMIDIChannelEvent.h b/Source/MIKMIDIChannelEvent.h index 937c8b48..11ed8858 100644 --- a/Source/MIKMIDIChannelEvent.h +++ b/Source/MIKMIDIChannelEvent.h @@ -54,8 +54,7 @@ #pragma mark - -#import -#import +#import @class MIKMIDIClock; diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index a42baacd..382f9496 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -107,12 +107,17 @@ + (BOOL)isMutable { return YES; } #pragma mark - MIKMIDICommand+MIKMIDIChannelEventToCommands +#import +#import +#import + @implementation MIKMIDICommand (MIKMIDIChannelEventToCommands) + (instancetype)commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:(MIKMIDIClock *)clock { NSDictionary *classes = @{@(MIKMIDIEventTypeMIDIControlChangeMessage) : [MIKMIDIControlChangeCommand class], - @(MIKMIDIEventTypeMIDIProgramChangeMessage) : [MIKMIDIProgramChangeCommand class]}; + @(MIKMIDIEventTypeMIDIProgramChangeMessage) : [MIKMIDIProgramChangeCommand class], + @(MIKMIDIEventTypeMIDIPitchBendChangeMessage) : [MIKMIDIPitchBendChangeCommand class]}; Class commandClass = classes[@(event.eventType)]; if (!commandClass) return nil; diff --git a/Source/MIKMIDIChannelVoiceCommand.m b/Source/MIKMIDIChannelVoiceCommand.m index 97cf180e..7b3a4f1c 100644 --- a/Source/MIKMIDIChannelVoiceCommand.m +++ b/Source/MIKMIDIChannelVoiceCommand.m @@ -28,9 +28,7 @@ + (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } + (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypePolyphonicKeyPressure), - @(MIKMIDICommandTypeProgramChange), - @(MIKMIDICommandTypeChannelPressure), - @(MIKMIDICommandTypePitchWheelChange)]; + @(MIKMIDICommandTypeChannelPressure)]; } + (Class)immutableCounterpartClass; { return [MIKMIDIChannelVoiceCommand class]; } diff --git a/Source/MIKMIDIMetaEvent.h b/Source/MIKMIDIMetaEvent.h index 5ea84d8b..b931ad77 100644 --- a/Source/MIKMIDIMetaEvent.h +++ b/Source/MIKMIDIMetaEvent.h @@ -28,7 +28,7 @@ /** * The metadata for the event. */ -@property (nonatomic, readonly) NSData *metaData; +@property (nonatomic, strong, readonly) NSData *metaData; @end diff --git a/Source/MIKMIDIPitchBendChangeCommand.h b/Source/MIKMIDIPitchBendChangeCommand.h new file mode 100644 index 00000000..758e62cd --- /dev/null +++ b/Source/MIKMIDIPitchBendChangeCommand.h @@ -0,0 +1,42 @@ +// +// MIKMIDIPitchBendChangeCommand.h +// MIKMIDI +// +// Created by Andrew Madsen on 3/5/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import + +/** + * A MIDI pitch bend change command. + * + * On devices, pitch bends messages are usually generated using a wheel or lever. + */ +@interface MIKMIDIPitchBendChangeCommand : MIKMIDIChannelVoiceCommand + +/** + * A 14-bit value indicating the pitch bend. + * Center is 0x2000 (8192). + * Valid range is from 0-16383. + */ +@property (nonatomic, readonly) UInt16 pitchChange; + +@end + +@interface MIKMutableMIDIPitchBendChangeCommand : MIKMIDIPitchBendChangeCommand + +@property (nonatomic, readwrite) UInt16 pitchChange; + +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) NSUInteger value; + +@property (nonatomic, strong, readwrite) NSDate *timestamp; +@property (nonatomic, readwrite) MIKMIDICommandType commandType; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; +@property (nonatomic, copy, readwrite) NSData *data; + +@end diff --git a/Source/MIKMIDIPitchBendChangeCommand.m b/Source/MIKMIDIPitchBendChangeCommand.m new file mode 100644 index 00000000..b1516765 --- /dev/null +++ b/Source/MIKMIDIPitchBendChangeCommand.m @@ -0,0 +1,87 @@ +// +// MIKMIDIPitchBendChangeCommand.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/5/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIPitchBendChangeCommand.h" +#import "MIKMIDIChannelVoiceCommand_SubclassMethods.h" +#import "MIKMIDIUtilities.h" + +#if !__has_feature(objc_arc) +#error MIKMIDIPitchBendChangeCommand.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIPitchBendChangeCommand.m in the Build Phases for this target +#endif + +@interface MIKMIDIPitchBendChangeCommand () + +@end + +@implementation MIKMIDIPitchBendChangeCommand + ++ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } ++ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypePitchWheelChange)]; } ++ (Class)immutableCounterpartClass; { return [MIKMIDIPitchBendChangeCommand class]; } ++ (Class)mutableCounterpartClass; { return [MIKMutableMIDIPitchBendChangeCommand class]; } ++ (BOOL)isMutable { return NO; } + +- (NSString *)additionalCommandDescription +{ + return [NSString stringWithFormat:@"pitch change: %u", (unsigned)self.pitchChange]; +} + +#pragma mark - Properties + +- (UInt16)pitchChange +{ + UInt16 ms7 = (self.dataByte2 << 7) & 0x3F80; + UInt16 ls7 = self.dataByte1 & 0x007F; + return ms7 | ls7; +} + +- (void)setPitchChange:(UInt16)pitchChange +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + + pitchChange = MIN(pitchChange, 0x3FFF); + self.dataByte1 = pitchChange & 0x007F; + self.dataByte2 = pitchChange & 0x3F80; +} + +@end + +#pragma mark - + +@implementation MIKMutableMIDIPitchBendChangeCommand + ++ (BOOL)isMutable { return YES; } + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ channel %d", [super description], self.channel]; +} + +#pragma mark - Properties + +@dynamic pitchChange; + +// MIKMIDICommand already implements these. This keeps the compiler happy. +@dynamic channel; +@dynamic value; +@dynamic timestamp; +@dynamic dataByte1; +@dynamic dataByte2; +@dynamic midiTimestamp; +@dynamic data; + +@dynamic commandType; +- (void)setCommandType:(MIKMIDICommandType)commandType +{ + if ([self.internalData length] < 2) [self.internalData increaseLengthBy:2-[self.internalData length]]; + + UInt8 *data = (UInt8 *)[self.internalData mutableBytes]; + data[0] &= 0x0F | (commandType & 0xF0); // Need to avoid changing channel +} + +@end \ No newline at end of file From 20d5477bd8f022ebf01c2ff5cba02efc69fa877c Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 6 Mar 2015 11:04:55 -0600 Subject: [PATCH 077/284] Issue #66: Added additional init/convenience methods to MIKMIDISequence to give the option to convert channels in the MIDI data to tracks. --- Source/MIKMIDISequence.h | 60 ++++++++++++++++++++++++++++++++++++++-- Source/MIKMIDISequence.m | 31 +++++++++++++++++---- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 0b7c1a7d..53b66ceb 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -53,6 +53,20 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d */ + (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; +/** + * Creates and initilazes a new instance of MIKMIDISequence from a MIDI file. + * + * @param fileURL The URL of the MIDI file. + * @param convertMIDIChannelsToTracks Determines whether or not the track structure should be altered. When YES, the resulting sequence will + * contain a tempo track, 1 track for each MIDI Channel that is found in the MIDI file, and 1 track for SysEx or MetaEvents as the last track in + * the sequence. When NO, the track structure of the original MIDI file is left unaltered. + * @param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, + * you may pass in NULL. + * + * @return A new instance of MIKMIDISequence containing the loaded file's MIDI sequence, or nil if an error occured. + */ ++ (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; + /** * Initilazes a new instance of MIKMIDISequence from a MIDI file. * @@ -64,28 +78,70 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d */ - (instancetype)initWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; +/** + * Initilazes a new instance of MIKMIDISequence from a MIDI file. + * + * @param fileURL The URL of the MIDI file. + * @param convertMIDIChannelsToTracks Determines whether or not the track structure should be altered. When YES, the resulting sequence will + * contain a tempo track, 1 track for each MIDI Channel that is found in the MIDI file, and 1 track for SysEx or MetaEvents as the last track in + * the sequence. When NO, the track structure of the original MIDI file is left unaltered. + * @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, + * you may pass in NULL. + * + * @return A new instance of MIKMIDISequence containing the loaded file's MIDI sequence, or nil if an error occured. + */ +- (instancetype)initWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; + /** * Creates and initializes a new instance of MIKMIDISequence from MIDI data. * * @param data An NSData instance containing the data for the MIDI sequence/file. - * @param error If an + * @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. */ + (instancetype)sequenceWithData:(NSData *)data error:(NSError **)error; +/** + * Creates and initializes a new instance of MIKMIDISequence from MIDI data. + * + * @param data An NSData instance containing the data for the MIDI sequence/file. + * @param convertMIDIChannelsToTracks Determines whether or not the track structure should be altered. When YES, the resulting sequence will + * contain a tempo track, 1 track for each MIDI Channel that is found in the MIDI file, and 1 track for SysEx or MetaEvents as the last track in + * the sequence. When NO, the track structure of the original MIDI file is left unaltered. + * @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, + * + * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, + * you may pass in NULL. + */ ++ (instancetype)sequenceWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; + /** * Initializes a new instance of MIKMIDISequence from MIDI data. * * @param data An NSData instance containing the data for the MIDI sequence/file. - * @param error If an + * @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. */ - (instancetype)initWithData:(NSData *)data error:(NSError **)error; +/** + * Initializes a new instance of MIKMIDISequence from MIDI data. + * + * @param data An NSData instance containing the data for the MIDI sequence/file. + * @param convertMIDIChannelsToTracks Determines whether or not the track structure should be altered. When YES, the resulting sequence will + * contain a tempo track, 1 track for each MIDI Channel that is found in the MIDI file, and 1 track for SysEx or MetaEvents as the last track in + * the sequence. When NO, the track structure of the original MIDI file is left unaltered. + * @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, + * + * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, + * you may pass in NULL. + */ +- (instancetype)initWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; + /** * Writes the MIDI sequence in Standard MIDI File format to a file at the specified URL. * diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 8f91ec5f..f43e59d1 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -52,21 +52,41 @@ - (instancetype)init + (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; { - return [[self alloc] initWithFileAtURL:fileURL error:error]; + return [[self alloc] initWithFileAtURL:fileURL convertMIDIChannelsToTracks:NO error:error]; +} + ++ (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error +{ + return [[self alloc] initWithFileAtURL:fileURL convertMIDIChannelsToTracks:convertMIDIChannelsToTracks error:error]; } - (instancetype)initWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; { - NSData *data = [NSData dataWithContentsOfURL:fileURL options:0 error:error]; - return [self initWithData:data error:error]; + return [self initWithFileAtURL:fileURL convertMIDIChannelsToTracks:NO error:error]; +} + +- (instancetype)initWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error +{ + NSData *data = [NSData dataWithContentsOfURL:fileURL options:0 error:error]; + return [self initWithData:data convertMIDIChannelsToTracks:convertMIDIChannelsToTracks error:error]; } + (instancetype)sequenceWithData:(NSData *)data error:(NSError **)error { - return [[self alloc] initWithData:data error:error]; + return [[self alloc] initWithData:data convertMIDIChannelsToTracks:NO error:error]; +} + ++ (instancetype)sequenceWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error +{ + return [[self alloc] initWithData:data convertMIDIChannelsToTracks:convertMIDIChannelsToTracks error:error]; } - (instancetype)initWithData:(NSData *)data error:(NSError **)error +{ + return [self initWithData:data convertMIDIChannelsToTracks:NO error:error]; +} + +- (instancetype)initWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error { error = error ? error : &(NSError *__autoreleasing){ nil }; @@ -78,7 +98,8 @@ - (instancetype)initWithData:(NSData *)data error:(NSError **)error return nil; } - err = MusicSequenceFileLoadData(sequence, (__bridge CFDataRef)data, kMusicSequenceFile_MIDIType, 0); + MusicSequenceLoadFlags flags = convertMIDIChannelsToTracks ? kMusicSequenceLoadSMF_ChannelsToTracks : 0; + err = MusicSequenceFileLoadData(sequence, (__bridge CFDataRef)data, kMusicSequenceFile_MIDIType, flags); if (err) { NSLog(@"MusicSequenceFileLoadData() failed with error %d in %s.", err, __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; From b4e6695fa267950213d19f96a7e445fdb1c40460 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Mar 2015 11:14:15 -0700 Subject: [PATCH 078/284] Fixed framework visibility for several _Subclass.h headers. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 06bd59bf..40f3f240 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -62,7 +62,6 @@ 9D74EF6517A713A100BEE89F /* MIKMIDIChannelVoiceCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF3217A713A100BEE89F /* MIKMIDIChannelVoiceCommand.m */; }; 9D74EF6617A713A100BEE89F /* MIKMIDICommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3317A713A100BEE89F /* MIKMIDICommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF6717A713A100BEE89F /* MIKMIDICommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF3417A713A100BEE89F /* MIKMIDICommand.m */; }; - 9D74EF6817A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; }; 9D74EF6917A713A100BEE89F /* MIKMIDIControlChangeCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3617A713A100BEE89F /* MIKMIDIControlChangeCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF6A17A713A100BEE89F /* MIKMIDIControlChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF3717A713A100BEE89F /* MIKMIDIControlChangeCommand.m */; }; 9D74EF6B17A713A100BEE89F /* MIKMIDIDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3817A713A100BEE89F /* MIKMIDIDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -95,7 +94,6 @@ 9D74EF8717A713A100BEE89F /* MIKMIDIOutputPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF5417A713A100BEE89F /* MIKMIDIOutputPort.m */; }; 9D74EF8817A713A100BEE89F /* MIKMIDIPort.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5517A713A100BEE89F /* MIKMIDIPort.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF8917A713A100BEE89F /* MIKMIDIPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF5617A713A100BEE89F /* MIKMIDIPort.m */; }; - 9D74EF8A17A713A100BEE89F /* MIKMIDIPort_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5717A713A100BEE89F /* MIKMIDIPort_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF8B17A713A100BEE89F /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF8C17A713A100BEE89F /* MIKMIDISourceEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5917A713A100BEE89F /* MIKMIDISourceEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF8D17A713A100BEE89F /* MIKMIDISourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF5A17A713A100BEE89F /* MIKMIDISourceEndpoint.m */; }; @@ -236,6 +234,9 @@ 9DB366F71A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DB366F81A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */; }; 9DB366F91A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */; }; + 9DBEBD581AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DBEBD591AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 839D932D19C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; @@ -745,7 +746,6 @@ 839D937519C3A319007589C3 /* MIKMIDITrack.h in Headers */, 839D934F19C3A2F5007589C3 /* MIKMIDIMetaCopyrightEvent.h in Headers */, 839D935319C3A2F5007589C3 /* MIKMIDIMetaEvent.h in Headers */, - 9D74EF6817A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h in Headers */, 839D935719C3A2F5007589C3 /* MIKMIDIMetaKeySignatureEvent.h in Headers */, 9DEF1CA81AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9D8495221AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */, @@ -767,13 +767,13 @@ 839D936319C3A2F5007589C3 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, 839D933519C3A2C9007589C3 /* MIKMIDIEventIterator.h in Headers */, 839D935919C3A2F5007589C3 /* MIKMIDIMetaLyricEvent.h in Headers */, - 9D74EF8A17A713A100BEE89F /* MIKMIDIPort_SubclassMethods.h in Headers */, 839D935D19C3A2F5007589C3 /* MIKMIDIMetaSequenceEvent.h in Headers */, 839D935119C3A2F5007589C3 /* MIKMIDIMetaCuePointEvent.h in Headers */, 839D933319C3A2C9007589C3 /* MIKMIDIEvent.h in Headers */, 9DED4E221AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */, 9D74EF7D17A713A100BEE89F /* MIKMIDIMappingManager.h in Headers */, 9D74EF7F17A713A100BEE89F /* MIKMIDINoteOffCommand.h in Headers */, + 9DBEBD581AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */, 9D74EF8117A713A100BEE89F /* MIKMIDINoteOnCommand.h in Headers */, 9D74EF8317A713A100BEE89F /* MIKMIDIObject.h in Headers */, 9D74EF8617A713A100BEE89F /* MIKMIDIOutputPort.h in Headers */, @@ -823,6 +823,7 @@ 9DAF8B781A7B00A700F46528 /* MIKMIDIMetaTimeSignatureEvent.h in Headers */, 9DAF8B651A7B008A00F46528 /* MIKMIDISystemMessageCommand.h in Headers */, 9DAF8B951A7B050700F46528 /* MIKMIDIMappingXMLParser.h in Headers */, + 9DBEBD591AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */, 9DAF8B5F1A7B007300F46528 /* MIKMIDIClientDestinationEndpoint.h in Headers */, 9DAF8B611A7B008A00F46528 /* MIKMIDIChannelVoiceCommand.h in Headers */, 9DAF8B701A7B00A700F46528 /* MIKMIDIMetaCuePointEvent.h in Headers */, @@ -843,6 +844,7 @@ 9DAF8B7E1A7B00B100F46528 /* MIKMIDIMetronome.h in Headers */, 9DAF8B831A7B00BB00F46528 /* MIKMIDIPrivateUtilities.h in Headers */, 9DAF8B591A7B007300F46528 /* MIKMIDIInputPort.h in Headers */, + 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */, 9DAF8B561A7B007300F46528 /* MIKMIDIDevice.h in Headers */, 9DAF8B761A7B00A700F46528 /* MIKMIDIMetaSequenceEvent.h in Headers */, 9DAF8B791A7B00A700F46528 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, From 902083b86eb465b797e951f8f71bebe1d76ff6c1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Mar 2015 11:47:25 -0700 Subject: [PATCH 079/284] Issue #64: MIKMIDI.framework module now defines explicit submodules for _SubclassMethods headers. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 10 ++++++++++ Framework/module.modulemap | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 Framework/module.modulemap diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 40f3f240..602bdeda 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -392,6 +392,7 @@ 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizer.m; sourceTree = ""; tabWidth = 4; usesTabs = 1; wrapsLines = 1; }; 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizerInstrument.h; sourceTree = ""; }; 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizerInstrument.m; sourceTree = ""; }; + 9DBEBD5C1AAA27D100E59734 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = SOURCE_ROOT; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeEvent.h; sourceTree = ""; }; @@ -504,6 +505,7 @@ 9D74EEC017A712F100BEE89F /* en.lproj */, 9D74EEC317A712F100BEE89F /* MIKMIDI-Info.plist */, 9DAF8B8E1A7B04CA00F46528 /* MIKMIDI-iOS-Info.plist */, + 9DBEBD5C1AAA27D100E59734 /* module.modulemap */, ); name = Resources; path = MIKMIDI; @@ -1131,6 +1133,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.7; + MODULEMAP_FILE = ""; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; @@ -1158,6 +1161,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.7; + MODULEMAP_FILE = ""; SDKROOT = macosx; }; name = Release; @@ -1167,6 +1171,7 @@ buildSettings = { CLANG_WARN_DOCUMENTATION_COMMENTS = YES; COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; @@ -1174,6 +1179,7 @@ GCC_PREFIX_HEADER = "MIKMIDI-Prefix.pch"; INFOPLIST_FILE = "MIKMIDI-Info.plist"; LD_DYLIB_INSTALL_NAME = "@loader_path/../Frameworks/$(EXECUTABLE_PATH)"; + MODULEMAP_FILE = module.modulemap; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -1185,6 +1191,7 @@ buildSettings = { CLANG_WARN_DOCUMENTATION_COMMENTS = YES; COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; DEPLOYMENT_POSTPROCESSING = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -1193,6 +1200,7 @@ GCC_PREFIX_HEADER = "MIKMIDI-Prefix.pch"; INFOPLIST_FILE = "MIKMIDI-Info.plist"; LD_DYLIB_INSTALL_NAME = "@loader_path/../Frameworks/$(EXECUTABLE_PATH)"; + MODULEMAP_FILE = module.modulemap; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -1232,6 +1240,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = module.modulemap; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = MIKMIDI; SDKROOT = iphoneos; @@ -1272,6 +1281,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = module.modulemap; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = MIKMIDI; SDKROOT = iphoneos; diff --git a/Framework/module.modulemap b/Framework/module.modulemap new file mode 100644 index 00000000..76cef9a9 --- /dev/null +++ b/Framework/module.modulemap @@ -0,0 +1,21 @@ +framework module MIKMIDI { + umbrella header "MIKMIDI.h" + + export * + module * { export * } + + explicit module MIKMIDICommandSubclass { + header "MIKMIDICommand_SubclassMethods.h" + export * + } + + explicit module MIKMIDIEventSubclass { + header "MIKMIDIEvent_SubclassMethods.h" + export * + } + + explicit module MIKMIDISynthesizerSubclass { + header "MIKMIDISynthesizer_SubclassMethods.h" + export * + } +} From 539f8f22c3d1574cdfbffae0444891afc9e6db1a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Mar 2015 12:03:17 -0700 Subject: [PATCH 080/284] Issue #63: Added MIKMIDIChannelPressureEvent. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 +++ Source/MIKMIDIChannelEvent.m | 6 +- Source/MIKMIDIChannelPressureEvent.h | 40 ++++++++++ Source/MIKMIDIChannelPressureEvent.m | 87 +++++++++++++++++++++ 4 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 Source/MIKMIDIChannelPressureEvent.h create mode 100644 Source/MIKMIDIChannelPressureEvent.m diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 40f3f240..bbd6ab14 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -237,6 +237,10 @@ 9DBEBD581AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD591AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 839D932D19C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */; }; + 9DBEBD681AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */; }; + 9DBEBD691AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; + 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; @@ -392,6 +396,8 @@ 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizer.m; sourceTree = ""; tabWidth = 4; usesTabs = 1; wrapsLines = 1; }; 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizerInstrument.h; sourceTree = ""; }; 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizerInstrument.m; sourceTree = ""; }; + 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureEvent.h; sourceTree = ""; }; + 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureEvent.m; sourceTree = ""; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeEvent.h; sourceTree = ""; }; @@ -624,6 +630,8 @@ 9DEF1CAD1AA6800C00E10273 /* MIKMIDIControlChangeEvent.m */, 9D84951A1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h */, 9D84951B1AA7678700C52475 /* MIKMIDIProgramChangeEvent.m */, + 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */, + 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */, 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */, 9DED4E211AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.m */, ); @@ -780,6 +788,7 @@ 9D74EF8817A713A100BEE89F /* MIKMIDIPort.h in Headers */, 9D74EF8B17A713A100BEE89F /* MIKMIDIResponder.h in Headers */, 9D74EF8C17A713A100BEE89F /* MIKMIDISourceEndpoint.h in Headers */, + 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, @@ -852,6 +861,7 @@ 9DAF8B531A7B005C00F46528 /* MIKMIDIErrors.h in Headers */, 9D8495231AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */, 9DAF8B7F1A7B00B100F46528 /* MIKMIDISequencer.h in Headers */, + 9DBEBD681AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9DAF8B6E1A7B00A700F46528 /* MIKMIDIEventIterator.h in Headers */, 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, @@ -980,6 +990,7 @@ 9D74EF7017A713A100BEE89F /* MIKMIDIDeviceManager.m in Sources */, 9D74EF7217A713A100BEE89F /* MIKMIDIEndpoint.m in Sources */, 9DAE7D8D19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.m in Sources */, + 9DBEBD691AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */, 839D935C19C3A2F5007589C3 /* MIKMIDIMetaMarkerTextEvent.m in Sources */, 839D935019C3A2F5007589C3 /* MIKMIDIMetaCopyrightEvent.m in Sources */, 9D74EF7417A713A100BEE89F /* MIKMIDIEntity.m in Sources */, @@ -1071,6 +1082,7 @@ 9DEF1CAB1AA6769E00E10273 /* MIKMIDIChannelEvent.m in Sources */, 9DAF8B431A7AFF6B00F46528 /* MIKMIDIMetaSequenceEvent.m in Sources */, 9DAF8B441A7AFF6B00F46528 /* MIKMIDIMetaTextEvent.m in Sources */, + 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */, 9DAF8B451A7AFF6B00F46528 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */, 9DAF8B461A7AFF6B00F46528 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, 9DAF8B471A7AFF6B00F46528 /* MIKMIDINoteEvent.m in Sources */, diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index 382f9496..e6771a84 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -26,11 +26,7 @@ @interface MIKMIDIChannelEvent () @implementation MIKMIDIChannelEvent + (void)load { [MIKMIDIEvent registerSubclass:self]; } -+ (NSArray *)supportedMIDIEventTypes -{ - // We have subclasses for all but MIKMIDIEventTypeMIDIChannelPressureMessage. - return @[@(MIKMIDIEventTypeMIDIChannelPressureMessage)]; -} ++ (NSArray *)supportedMIDIEventTypes { return @[]; } + (Class)immutableCounterpartClass { return [MIKMIDIChannelEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIChannelEvent class]; } + (BOOL)isMutable { return NO; } diff --git a/Source/MIKMIDIChannelPressureEvent.h b/Source/MIKMIDIChannelPressureEvent.h new file mode 100644 index 00000000..be1dc309 --- /dev/null +++ b/Source/MIKMIDIChannelPressureEvent.h @@ -0,0 +1,40 @@ +// +// MIKMIDIChannelPressureEvent.h +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelEvent.h" + +/** + * A channel pressure (aftertouch) event. + * + * This event is different from MIKMIDIPolyphonicKeyPressureEvent. + * This event is used to indicate the single greatest pressure value + * (of all the current depressed keys). + */ +@interface MIKMIDIChannelPressureEvent : MIKMIDIChannelEvent + +/** + * The pressure of the event. From 0-127. + */ +@property (nonatomic, readonly) UInt8 pressure; + +@end + +/** + * The mutable counter part of MIKMIDIChannelPressureEvent + */ +@interface MIKMutableMIDIChannelPressureEvent : MIKMIDIChannelPressureEvent + +@property (nonatomic, readwrite) UInt8 pressure; + +@property (nonatomic, readwrite) MusicTimeStamp timeStamp; +@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIChannelPressureEvent.m b/Source/MIKMIDIChannelPressureEvent.m new file mode 100644 index 00000000..ba9eb728 --- /dev/null +++ b/Source/MIKMIDIChannelPressureEvent.m @@ -0,0 +1,87 @@ +// +// MIKMIDIChannelPressureEvent.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/4/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelPressureEvent.h" +#import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIUtilities.h" + +#if !__has_feature(objc_arc) +#error MIKMIDIChannelPressureEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIChannelPressureEvent.m in the Build Phases for this target +#endif + +@interface MIKMIDIChannelEvent (Protected) + +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) UInt8 dataByte1; +@property (nonatomic, readwrite) UInt8 dataByte2; + +@end + +@interface MIKMIDIChannelPressureEvent () + +@property (nonatomic, readwrite) UInt8 pressure; + +@end + +@implementation MIKMIDIChannelPressureEvent + ++ (void)load { [MIKMIDIEvent registerSubclass:self]; } ++ (NSArray *)supportedMIDIEventTypes { return @[@(MIKMIDIEventTypeMIDIChannelPressureMessage)]; } ++ (Class)immutableCounterpartClass { return [MIKMIDIChannelPressureEvent class]; } ++ (Class)mutableCounterpartClass { return [MIKMutableMIDIChannelPressureEvent class]; } ++ (BOOL)isMutable { return NO; } ++ (NSData *)initialData +{ + MIDIChannelMessage message = { + .status = MIKMIDIChannelEventTypeChannelPressure, + .data1 = 0, + .data2 = 0, + .reserved = 0, + }; + return [NSData dataWithBytes:&message length:sizeof(message)]; +} + +- (NSString *)additionalEventDescription +{ + return [NSString stringWithFormat:@"pressure: %u", (unsigned)self.pressure]; +} + +#pragma mark - Properties + ++ (NSSet *)keyPathsForValuesAffectingPressure +{ + return [NSSet setWithObjects:@"dataByte1", nil]; +} + +- (UInt8)pressure +{ + return self.dataByte1; +} + +- (void)setPressure:(UInt8)pressure +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + + self.dataByte1 = MIN(pressure, 127); +} + +@end + +@implementation MIKMutableMIDIChannelPressureEvent + +@dynamic pressure; + +@dynamic timeStamp; +@dynamic data; +@dynamic channel; +@dynamic dataByte1; +@dynamic dataByte2; + ++ (BOOL)isMutable { return YES; } + +@end \ No newline at end of file From fa9fe7e0c154d1381995334a8f38abf2a37b6e6c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Mar 2015 12:05:47 -0700 Subject: [PATCH 081/284] Issue #63: Added MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage and MIKMIDIEventTypeMIDIChannelPressureMessage support to +[MIKMIDICommand commandFromChannelEvent:clock:]. --- Source/MIKMIDIChannelEvent.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIChannelEvent.m b/Source/MIKMIDIChannelEvent.m index e6771a84..418c2a2c 100644 --- a/Source/MIKMIDIChannelEvent.m +++ b/Source/MIKMIDIChannelEvent.m @@ -103,16 +103,20 @@ + (BOOL)isMutable { return YES; } #pragma mark - MIKMIDICommand+MIKMIDIChannelEventToCommands +#import #import #import +#import #import @implementation MIKMIDICommand (MIKMIDIChannelEventToCommands) + (instancetype)commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:(MIKMIDIClock *)clock { - NSDictionary *classes = @{@(MIKMIDIEventTypeMIDIControlChangeMessage) : [MIKMIDIControlChangeCommand class], + NSDictionary *classes = @{@(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage) : [MIKMIDIPolyphonicKeyPressureEvent class], + @(MIKMIDIEventTypeMIDIControlChangeMessage) : [MIKMIDIControlChangeCommand class], @(MIKMIDIEventTypeMIDIProgramChangeMessage) : [MIKMIDIProgramChangeCommand class], + @(MIKMIDIEventTypeMIDIChannelPressureMessage) : [MIKMIDIChannelPressureEvent class], @(MIKMIDIEventTypeMIDIPitchBendChangeMessage) : [MIKMIDIPitchBendChangeCommand class]}; Class commandClass = classes[@(event.eventType)]; if (!commandClass) return nil; From 268232a94b42f7e910572f877202142a390a171f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Mar 2015 12:06:22 -0700 Subject: [PATCH 082/284] Issue #63: Made MIKMIDIChannelPressureEvent.h public and added it to MIKMIDI.h. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 ++-- Source/MIKMIDI.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index bbd6ab14..90d8fc42 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -237,8 +237,8 @@ 9DBEBD581AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD591AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 839D932D19C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */; }; - 9DBEBD681AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */; }; + 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DBEBD681AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD691AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 0f1fedf0..f0090642 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -54,6 +54,7 @@ #import "MIKMIDIPolyphonicKeyPressureEvent.h" #import "MIKMIDIControlChangeEvent.h" #import "MIKMIDIProgramChangeEvent.h" +#import "MIKMIDIChannelPressureEvent.h" #import "MIKMIDIPitchBendChangeEvent.h" // Meta Events From 7045bc84bf3db6df8fa251b07ea3186a148cb061 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Mar 2015 14:06:55 -0700 Subject: [PATCH 083/284] Moved two methods from MIKMIDITrack into MIKMIDITrack_Protected. --- Source/MIKMIDIPlayer.m | 1 + Source/MIKMIDITrack.h | 19 ++------------- Source/MIKMIDITrack.m | 43 +++++++++++++++++---------------- Source/MIKMIDITrack_Protected.h | 17 +++++++++++++ 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/Source/MIKMIDIPlayer.m b/Source/MIKMIDIPlayer.m index f0d25d25..a79de3a2 100644 --- a/Source/MIKMIDIPlayer.m +++ b/Source/MIKMIDIPlayer.m @@ -8,6 +8,7 @@ #import "MIKMIDIPlayer.h" #import "MIKMIDITrack.h" +#import "MIKMIDITrack_Protected.h" #import "MIKMIDISequence.h" #import "MIKMIDIMetronome.h" #import "MIKMIDINoteEvent.h" diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 233b8458..6e12b3b1 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -102,6 +102,8 @@ */ - (NSArray *)notesFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; +#pragma mark - Event + /** * Moves all of the MIDI events between startTimeStamp and endTimeStamp inclusively by the specified offset. * @@ -158,23 +160,6 @@ */ - (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp atTimeStamp:(MusicTimeStamp)destTimeStamp; -/** - * Sets a temporary length and loopInfo for the track. - * - * @param length The temporary length for the track. - * @param loopInfo The temporary loopInfo for the track. - * - * @note You should not call this method. It is exclusivley used by MIKMIDISequence when the sequence is being looped by a MIKMIDIPlayer. - */ -- (void)setTemporaryLength:(MusicTimeStamp)length andLoopInfo:(MusicTrackLoopInfo)loopInfo; - -/** - * Restores the length and loopInfo of the track to what it was before calling -setTemporaryLength:andLoopInfo:. - * - * @note You should not call this method. It is exclusively used by MIKMIDISequence when the sequence is being looped by a MIKMIDIPlayer. - */ -- (void)restoreLengthAndLoopInfo; - /** * The MIDI sequence the track belongs to. */ diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 37637de7..dc269579 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -190,6 +190,28 @@ - (BOOL)clearAllEvents #pragma mark - Getting Events +// All event getters pass through this method +- (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp +{ + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + if (![iterator seek:startTimeStamp]) return @[]; + + NSMutableArray *events = [NSMutableArray array]; + + while (iterator.hasCurrentEvent) { + MIKMIDIEvent *event = iterator.currentEvent; + if (!event || event.timeStamp > endTimeStamp) break; + + if (!eventClass || [event isKindOfClass:eventClass]) { + [events addObject:event]; + } + + [iterator moveToNextEvent]; + } + + return events; +} + - (NSArray *)eventsFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp { return [self eventsOfClass:Nil fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; @@ -200,27 +222,6 @@ - (NSArray *)notesFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(Musi return [self eventsOfClass:[MIKMIDINoteEvent class] fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; } -- (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp -{ - MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; - if (![iterator seek:startTimeStamp]) return @[]; - - NSMutableArray *events = [NSMutableArray array]; - - while (iterator.hasCurrentEvent) { - MIKMIDIEvent *event = iterator.currentEvent; - if (!event || event.timeStamp > endTimeStamp) break; - - if (!eventClass || [event isKindOfClass:eventClass]) { - [events addObject:event]; - } - - [iterator moveToNextEvent]; - } - - return events; -} - #pragma mark - Editing Events - (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)offsetTimeStamp diff --git a/Source/MIKMIDITrack_Protected.h b/Source/MIKMIDITrack_Protected.h index 9a2975c9..68755f98 100644 --- a/Source/MIKMIDITrack_Protected.h +++ b/Source/MIKMIDITrack_Protected.h @@ -21,4 +21,21 @@ */ + (instancetype)trackWithSequence:(MIKMIDISequence *)sequence musicTrack:(MusicTrack)musicTrack; +/** + * Sets a temporary length and loopInfo for the track. + * + * @param length The temporary length for the track. + * @param loopInfo The temporary loopInfo for the track. + * + * @note You should not call this method. It is exclusivley used by MIKMIDISequence when the sequence is being looped by a MIKMIDIPlayer. + */ +- (void)setTemporaryLength:(MusicTimeStamp)length andLoopInfo:(MusicTrackLoopInfo)loopInfo; + +/** + * Restores the length and loopInfo of the track to what it was before calling -setTemporaryLength:andLoopInfo:. + * + * @note You should not call this method. It is exclusively used by MIKMIDISequence when the sequence is being looped by a MIKMIDIPlayer. + */ +- (void)restoreLengthAndLoopInfo; + @end From a406bd1766911071706917d61c99c39966489758 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Mar 2015 19:56:57 -0700 Subject: [PATCH 084/284] Issue #35: Implemented -isEqual: and -hash on MIKMIDIEvent. --- Source/MIKMIDIEvent.m | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 04076f1a..dbdb7305 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -91,6 +91,21 @@ - (NSString *)description return [NSString stringWithFormat:@"%@ Timestamp: %f Type: %u, %@", [super description], self.timeStamp, (unsigned int)self.eventType, additionalDescription]; } +- (BOOL)isEqual:(id)object +{ + if (object == self) return YES; + if (![object isKindOfClass:[MIKMIDIEvent class]]) return NO; + + MIKMIDIEvent *otherEvent = (MIKMIDIEvent *)object; + if (otherEvent.eventType != self.eventType) return NO; + return self.timeStamp == otherEvent.timeStamp && [self.data isEqualToData:otherEvent.data]; +} + +- (NSUInteger)hash +{ + return (NSUInteger)(self.timeStamp + [self.data hash]); +} + #pragma mark - Private + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType andData:(NSData *)data From 77b361268acd595681e0f7a47fcdf039ee7a46c6 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 7 Mar 2015 09:14:53 -0700 Subject: [PATCH 085/284] Added tests target to MIKMIDI framework project. --- Framework/MIKMIDI Tests/Info.plist | 24 +++ .../MIKMIDI Tests/MIKMIDISequenceTests.m | 44 +++++ Framework/MIKMIDI Tests/MIKMIDI_Tests.m | 40 +++++ Framework/MIKMIDI Tests/bach.mid | Bin 0 -> 3943 bytes Framework/MIKMIDI.xcodeproj/project.pbxproj | 170 ++++++++++++++++++ 5 files changed, 278 insertions(+) create mode 100644 Framework/MIKMIDI Tests/Info.plist create mode 100644 Framework/MIKMIDI Tests/MIKMIDISequenceTests.m create mode 100644 Framework/MIKMIDI Tests/MIKMIDI_Tests.m create mode 100644 Framework/MIKMIDI Tests/bach.mid diff --git a/Framework/MIKMIDI Tests/Info.plist b/Framework/MIKMIDI Tests/Info.plist new file mode 100644 index 00000000..8934606d --- /dev/null +++ b/Framework/MIKMIDI Tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m new file mode 100644 index 00000000..0a0f8ce5 --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -0,0 +1,44 @@ +// +// MIKMIDISequenceTests.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/7/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import +#import + +@interface MIKMIDISequenceTests : XCTestCase + +@end + +@implementation MIKMIDISequenceTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown +{ + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testMIDIFileRead +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"bach" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + + // Make sure number of tracks is correct + XCTAssertEqual([sequence.tracks count], 3); + XCTAssertNotNil(sequence.tempoTrack); +} + +@end diff --git a/Framework/MIKMIDI Tests/MIKMIDI_Tests.m b/Framework/MIKMIDI Tests/MIKMIDI_Tests.m new file mode 100644 index 00000000..fe4a0ece --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDI_Tests.m @@ -0,0 +1,40 @@ +// +// MIKMIDI_Tests.m +// MIKMIDI Tests +// +// Created by Andrew Madsen on 3/7/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import + +@interface MIKMIDI_Tests : XCTestCase + +@end + +@implementation MIKMIDI_Tests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample { + // This is an example of a functional test case. + XCTAssert(YES, @"Pass"); +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/Framework/MIKMIDI Tests/bach.mid b/Framework/MIKMIDI Tests/bach.mid new file mode 100644 index 0000000000000000000000000000000000000000..03bfbd4585e33fdb8e4a20a7948bb201b1609259 GIT binary patch literal 3943 zcmbVO+iDa+5bc=Y1@y%aka-YaLfkdvy1B5qxtowR30XnsX+jcE3|UwL;+rOzCm%9D z|GB}3si_CgAG^WP z3#kAvbgwh8(v4-{u{2s z#sd0L)aV`}?jdqMhCPNAEhTmd?1DIlTJp4rKzud7i$l;I zIt0R{-{lD>A-pAFWR$W}m3<4zxA0K=bwBE9w7BK#7)eiapr=;2^u%wbd@@<;UwDs^ zK=I5Dzv6Yi;#XgL~kI9woxJDaYHCH@U!;5CjV!1q&YrixICma=3{F zE*7f1ZhU&S0t-mn`}%FEGQ!0V8PyoAhG=yzd{MFRRmI}|j6$-t^g1cs)soWt74=@Z z47J7|0?fe9z}8@WK<{C3@nDxAxJgrvi#8FqMvcUV=06n+gAT0;RMA9pD) zD(y$5Qyt`J@yLe&ODduq~coxEw7W`6vPhxWQ`BBh)8c8}t0c zXCaJR81iwiFq@(B!AoV+<)KSOua0uEc;q7w-lzY#YNZu@vXPf9Y^r?0DCY8)csq-` J`(^QN{sGkvmHz+$ literal 0 HcmV?d00001 diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 678b85ae..be5306c9 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -53,6 +53,10 @@ 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */; }; + 9D4DF1401AAB57430065F004 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */; }; + 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */; }; + 9D4DF14F1AAB57C90065F004 /* bach.mid in Resources */ = {isa = PBXBuildFile; fileRef = 9D4DF14E1AAB57C90065F004 /* bach.mid */; }; 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EEBF17A712E900BEE89F /* MIKMIDI-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EEBE17A712E900BEE89F /* MIKMIDI-Prefix.pch */; }; @@ -266,6 +270,16 @@ 9DF99E7E18318D44004EE5F4 /* MIKMIDIPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF99E7C18318D44004EE5F4 /* MIKMIDIPrivateUtilities.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 9D4DF1411AAB57430065F004 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D74EE9C17A7129300BEE89F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9D74EEA417A7129300BEE89F; + remoteInfo = MIKMIDI; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 833B73D81A262FE100E0CC9F /* MIKMIDISequencer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequencer.h; sourceTree = ""; }; 833B73D91A262FE100E0CC9F /* MIKMIDISequencer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequencer.m; sourceTree = ""; }; @@ -312,6 +326,11 @@ 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; + 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDI_Tests.m; sourceTree = ""; }; + 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequenceTests.m; sourceTree = ""; }; + 9D4DF14E1AAB57C90065F004 /* bach.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = bach.mid; sourceTree = ""; }; 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizer_SubclassMethods.h; sourceTree = ""; }; 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MIKMIDI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9D74EEA817A7129300BEE89F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; @@ -417,6 +436,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 9D4DF1371AAB57430065F004 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9D4DF1401AAB57430065F004 /* MIKMIDI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9D74EEA117A7129300BEE89F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -451,12 +478,40 @@ name = Files; sourceTree = ""; }; + 9D4DF13B1AAB57430065F004 /* MIKMIDI Tests */ = { + isa = PBXGroup; + children = ( + 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */, + 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */, + 9D4DF13C1AAB57430065F004 /* Supporting Files */, + 9D4DF1501AAB57CD0065F004 /* Resources */, + ); + path = "MIKMIDI Tests"; + sourceTree = ""; + }; + 9D4DF13C1AAB57430065F004 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 9D4DF13D1AAB57430065F004 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 9D4DF1501AAB57CD0065F004 /* Resources */ = { + isa = PBXGroup; + children = ( + 9D4DF14E1AAB57C90065F004 /* bach.mid */, + ); + name = Resources; + sourceTree = ""; + }; 9D74EE9B17A7129300BEE89F = { isa = PBXGroup; children = ( 9D74EF2F17A713A100BEE89F /* Source */, 9D74EEAF17A7129300BEE89F /* Supporting Files */, 9D74EEBD17A712D000BEE89F /* Resources */, + 9D4DF13B1AAB57430065F004 /* MIKMIDI Tests */, 9D74EEA717A7129300BEE89F /* Frameworks */, 9D74EEA617A7129300BEE89F /* Products */, ); @@ -467,6 +522,7 @@ children = ( 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */, 9DAF8B061A7AFF1100F46528 /* MIKMIDI.framework */, + 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */, ); name = Products; sourceTree = ""; @@ -882,6 +938,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 9D4DF1391AAB57430065F004 /* MIKMIDI Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9D4DF1431AAB57430065F004 /* Build configuration list for PBXNativeTarget "MIKMIDI Tests" */; + buildPhases = ( + 9D4DF1361AAB57430065F004 /* Sources */, + 9D4DF1371AAB57430065F004 /* Frameworks */, + 9D4DF1381AAB57430065F004 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 9D4DF1421AAB57430065F004 /* PBXTargetDependency */, + ); + name = "MIKMIDI Tests"; + productName = "MIKMIDI Tests"; + productReference = 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 9D74EEA417A7129300BEE89F /* MIKMIDI */ = { isa = PBXNativeTarget; buildConfigurationList = 9D74EEBA17A7129300BEE89F /* Build configuration list for PBXNativeTarget "MIKMIDI" */; @@ -927,6 +1001,9 @@ LastUpgradeCheck = 0500; ORGANIZATIONNAME = "Mixed In Key"; TargetAttributes = { + 9D4DF1391AAB57430065F004 = { + CreatedOnToolsVersion = 6.3; + }; 9DAF8B051A7AFF1100F46528 = { CreatedOnToolsVersion = 6.1.1; }; @@ -946,11 +1023,20 @@ targets = ( 9D74EEA417A7129300BEE89F /* MIKMIDI */, 9DAF8B051A7AFF1100F46528 /* MIKMIDI-iOS */, + 9D4DF1391AAB57430065F004 /* MIKMIDI Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 9D4DF1381AAB57430065F004 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9D4DF14F1AAB57C90065F004 /* bach.mid in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9D74EEA317A7129300BEE89F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -970,6 +1056,15 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 9D4DF1361AAB57430065F004 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */, + 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9D74EEA017A7129300BEE89F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1105,6 +1200,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 9D4DF1421AAB57430065F004 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9D74EEA417A7129300BEE89F /* MIKMIDI */; + targetProxy = 9D4DF1411AAB57430065F004 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 9D74EEC117A712F100BEE89F /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -1117,6 +1220,65 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 9D4DF1441AAB57430065F004 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = "MIKMIDI Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 9D4DF1451AAB57430065F004 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = "MIKMIDI Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; 9D74EEB817A7129300BEE89F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1308,6 +1470,14 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 9D4DF1431AAB57430065F004 /* Build configuration list for PBXNativeTarget "MIKMIDI Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9D4DF1441AAB57430065F004 /* Debug */, + 9D4DF1451AAB57430065F004 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; 9D74EE9F17A7129300BEE89F /* Build configuration list for PBXProject "MIKMIDI" */ = { isa = XCConfigurationList; buildConfigurations = ( From f60263131ec6ccbdc122174c1e9c386af536ed7b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 7 Mar 2015 11:05:49 -0700 Subject: [PATCH 086/284] Issue #35: Added -deleteCurrentEventWithError: to MIKMIDIEventIterator. --- Source/MIKMIDIEventIterator.h | 1 + Source/MIKMIDIEventIterator.m | 30 +++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Source/MIKMIDIEventIterator.h b/Source/MIKMIDIEventIterator.h index 78171e34..b231d69a 100644 --- a/Source/MIKMIDIEventIterator.h +++ b/Source/MIKMIDIEventIterator.h @@ -29,5 +29,6 @@ - (BOOL)seek:(MusicTimeStamp)timeStamp; - (BOOL)moveToNextEvent; - (BOOL)moveToPreviousEvent; +- (BOOL)deleteCurrentEventWithError:(NSError **)error; @end diff --git a/Source/MIKMIDIEventIterator.m b/Source/MIKMIDIEventIterator.m index ba39d3c7..304a22a1 100644 --- a/Source/MIKMIDIEventIterator.m +++ b/Source/MIKMIDIEventIterator.m @@ -62,21 +62,33 @@ - (BOOL)seek:(MusicTimeStamp)timeStamp { OSStatus err = MusicEventIteratorSeek(self.iterator, timeStamp); if (err) NSLog(@"MusicEventIteratorSeek() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; + return err == noErr; } - (BOOL)moveToNextEvent { OSStatus err = MusicEventIteratorNextEvent(self.iterator); if (err) NSLog(@"MusicEventIteratorNextEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; + return err == noErr; } - (BOOL)moveToPreviousEvent { OSStatus err = MusicEventIteratorPreviousEvent(self.iterator); if (err) NSLog(@"MusicEventIteratorPreviousEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; + return err == noErr; +} + +- (BOOL)deleteCurrentEventWithError:(NSError **)error +{ + error = error ? error : &(NSError *__autoreleasing){ nil }; + OSStatus err = MusicEventIteratorDeleteEvent(self.iterator); + if (err) { + NSLog(@"MusicEventIteratorDeleteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + return YES; } #pragma mark - Current Event @@ -101,26 +113,26 @@ - (MIKMIDIEvent *)currentEvent - (BOOL)hasPreviousEvent { - Boolean hasPreviousEvent = FALSE; + Boolean hasPreviousEvent = false; OSStatus err = MusicEventIteratorHasPreviousEvent(self.iterator, &hasPreviousEvent); if (err) NSLog(@"MusicEventIteratorHasPreviousEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return hasPreviousEvent ? YES : NO; + return hasPreviousEvent; } - (BOOL)hasCurrentEvent { - Boolean hasCurrentEvent = FALSE; + Boolean hasCurrentEvent = false; OSStatus err = MusicEventIteratorHasCurrentEvent(self.iterator, &hasCurrentEvent); if (err) NSLog(@"MusicEventIteratorHasCurrentEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return hasCurrentEvent ? YES : NO; + return hasCurrentEvent; } - (BOOL)hasNextEvent { - Boolean hasNextEvent = FALSE; + Boolean hasNextEvent = false; OSStatus err = MusicEventIteratorHasNextEvent(self.iterator, &hasNextEvent); if (err) NSLog(@"MusicEventIteratorHasNextEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return hasNextEvent ? YES : NO; + return hasNextEvent; } @end From c92fb5387e62fcf9534be70b44ecfccf4a07e843 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 7 Mar 2015 11:50:28 -0700 Subject: [PATCH 087/284] Issue #35: MIKMIDITrack is now basically KVO compliant for events. More tests required. --- .../MIKMIDI Tests/MIKMIDISequenceTests.m | 4 + Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 87 +++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 11 +- Source/MIKMIDIErrors.h | 6 + Source/MIKMIDITrack.h | 90 ++- Source/MIKMIDITrack.m | 684 ++++++++++++------ 6 files changed, 638 insertions(+), 244 deletions(-) create mode 100644 Framework/MIKMIDI Tests/MIKMIDITrackTests.m diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index 0a0f8ce5..acff558d 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -39,6 +39,10 @@ - (void)testMIDIFileRead // Make sure number of tracks is correct XCTAssertEqual([sequence.tracks count], 3); XCTAssertNotNil(sequence.tempoTrack); + + // Check that the number of events in each track is correct + XCTAssertEqual([[sequence.tracks[1] events] count], 242); + XCTAssertEqual([[sequence.tracks[2] events] count], 220); } @end diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m new file mode 100644 index 00000000..f80fab75 --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -0,0 +1,87 @@ +// +// MIKMIDITrackTests.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/7/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import +#import + +@interface MIKMIDITrackTests : XCTestCase + +@property BOOL eventsChangeNotificationReceived; + +@end + +@implementation MIKMIDITrackTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testBasicEventsAddRemove +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Test adding an event + MIKMIDIEvent *event = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + [track addEvent:event]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Adding an event to MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue([track.events containsObject:event], @"Adding an event to MIKMIDITrack failed."); + XCTAssertEqual([track.events count], 1, @"Adding an event to MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + + // Test removing an event + [track removeEvent:event]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Removing an event from MIKMIDITrack did not produce a KVO notification."); + XCTAssertFalse([track.events containsObject:event], @"Removing an event from MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + + // Test removing some events + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + [track addEvent:event]; + [track addEvent:event2]; + [track addEvent:event3]; + [track addEvent:event4]; + XCTAssertEqual([track.events count], 4, @"Adding 4 events to MIKMIDITrack failed."); + [track removeEvents:@[event2, event3]]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Removing some events from MIKMIDITrack did not produce a KVO notification."); + XCTAssertEqual([track.events count], 2, @"Removing some events from MIKMIDITrack failed."); + NSArray *remainingEvents = @[event, event4]; + XCTAssertEqualObjects(remainingEvents, track.events, @"Removing some events from MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + + // Test removing all events + [track addEvent:event]; + [track addEvent:event2]; + [track addEvent:event3]; + [track removeAllEvents]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Removing all events from MIKMIDITrack did not produce a KVO notification."); + XCTAssertEqual([track.events count], 0, @"Removing all events from MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if ([object isKindOfClass:[MIKMIDITrack class]] && [keyPath isEqualToString:@"events"]) { + self.eventsChangeNotificationReceived = YES; + } +} + +@end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index be5306c9..1ad810b5 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -57,6 +57,8 @@ 9D4DF1401AAB57430065F004 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */; }; 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */; }; 9D4DF14F1AAB57C90065F004 /* bach.mid in Resources */ = {isa = PBXBuildFile; fileRef = 9D4DF14E1AAB57C90065F004 /* bach.mid */; }; + 9D4DF1541AAB60490065F004 /* MIKMIDITrackTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */; }; + 9D4DF1561AAB74890065F004 /* MIKMIDIErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF4317A713A100BEE89F /* MIKMIDIErrors.m */; }; 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D5946CF1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EEBF17A712E900BEE89F /* MIKMIDI-Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EEBE17A712E900BEE89F /* MIKMIDI-Prefix.pch */; }; @@ -79,7 +81,6 @@ 9D74EF7317A713A100BEE89F /* MIKMIDIEntity.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF4017A713A100BEE89F /* MIKMIDIEntity.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF7417A713A100BEE89F /* MIKMIDIEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF4117A713A100BEE89F /* MIKMIDIEntity.m */; }; 9D74EF7517A713A100BEE89F /* MIKMIDIErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF4217A713A100BEE89F /* MIKMIDIErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9D74EF7617A713A100BEE89F /* MIKMIDIErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF4317A713A100BEE89F /* MIKMIDIErrors.m */; }; 9D74EF7717A713A100BEE89F /* MIKMIDIInputPort.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF4417A713A100BEE89F /* MIKMIDIInputPort.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D74EF7817A713A100BEE89F /* MIKMIDIInputPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF4517A713A100BEE89F /* MIKMIDIInputPort.m */; }; 9D74EF7917A713A100BEE89F /* MIKMIDIMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF4617A713A100BEE89F /* MIKMIDIMapping.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -331,6 +332,7 @@ 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDI_Tests.m; sourceTree = ""; }; 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequenceTests.m; sourceTree = ""; }; 9D4DF14E1AAB57C90065F004 /* bach.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = bach.mid; sourceTree = ""; }; + 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDITrackTests.m; sourceTree = ""; }; 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizer_SubclassMethods.h; sourceTree = ""; }; 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MIKMIDI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9D74EEA817A7129300BEE89F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; @@ -481,8 +483,9 @@ 9D4DF13B1AAB57430065F004 /* MIKMIDI Tests */ = { isa = PBXGroup; children = ( - 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */, 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */, + 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */, + 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */, 9D4DF13C1AAB57430065F004 /* Supporting Files */, 9D4DF1501AAB57CD0065F004 /* Resources */, ); @@ -1062,6 +1065,7 @@ files = ( 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */, 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */, + 9D4DF1541AAB60490065F004 /* MIKMIDITrackTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1093,7 +1097,6 @@ 9D74EF7417A713A100BEE89F /* MIKMIDIEntity.m in Sources */, 839D936219C3A2F5007589C3 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */, 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */, - 9D74EF7617A713A100BEE89F /* MIKMIDIErrors.m in Sources */, 9D74EF7817A713A100BEE89F /* MIKMIDIInputPort.m in Sources */, 9D74EF7A17A713A100BEE89F /* MIKMIDIMapping.m in Sources */, 9D74EF7C17A713A100BEE89F /* MIKMIDIMappingGenerator.m in Sources */, @@ -1106,6 +1109,7 @@ 9D74EF8017A713A100BEE89F /* MIKMIDINoteOffCommand.m in Sources */, 833B73DB1A262FE100E0CC9F /* MIKMIDISequencer.m in Sources */, 9D74EF8217A713A100BEE89F /* MIKMIDINoteOnCommand.m in Sources */, + 9D4DF1561AAB74890065F004 /* MIKMIDIErrors.m in Sources */, 9D877DFC1A670261001BA997 /* MIKMIDIProgramChangeCommand.m in Sources */, 9DF99E7A1831841A004EE5F4 /* MIKMIDICommandThrottler.m in Sources */, 9DF99E7E18318D44004EE5F4 /* MIKMIDIPrivateUtilities.m in Sources */, @@ -1477,6 +1481,7 @@ 9D4DF1451AAB57430065F004 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; 9D74EE9F17A7129300BEE89F /* Build configuration list for PBXProject "MIKMIDI" */ = { isa = XCConfigurationList; diff --git a/Source/MIKMIDIErrors.h b/Source/MIKMIDIErrors.h index ebb5c744..7f042a0f 100644 --- a/Source/MIKMIDIErrors.h +++ b/Source/MIKMIDIErrors.h @@ -37,6 +37,12 @@ typedef NS_ENUM(NSInteger, MIKMIDIErrorCode) { * The mapping file did not have the correct file extension (".midimap"). */ MIKMIDIMappingIncorrectFileExtensionErrorCode, + + /** + * An error ocurred during an operation on event(s) in an MIKMIDITrack + * because the event(s) could not be found in the track. + */ + MIKMIDITrackEventNotFoundErrorCode, }; /** diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 6e12b3b1..ccd3e260 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -24,47 +24,37 @@ @interface MIKMIDITrack : NSObject /** - * Inserts the specified MIDI event into the track. + * Inserts the specified MIDI event into the receiver. * * @param event The MIDI event to insert into the track. - * - * @return Whether or not inserting the MIDI event was succesful. */ -- (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event; +- (void)addEvent:(MIKMIDIEvent *)event; /** - * Removes the specified MIDI event from the track. + * Inserts the specified MIDI events into the receiver. * - * @param event The MIDI event to remove from the track. - * - * @return Whether or not removing the MIDI event from the track was succesful. + * @param events An NSArray containing the events to be added. */ -- (BOOL)removeMIDIEvent:(MIKMIDIEvent *)event; +- (void)addEvents:(NSArray *)events; /** - * Inserts MIDI events into the track. + * Removes the specified MIDI event from the receiver. * - * @param events An NSSet of MIKMIDIEvent to insert into the track. - * - * @return Whether or not inserting the MIDI events was succesful. + * @param event The MIDI event to remove from the track. */ -- (BOOL)insertMIDIEvents:(NSSet *)events; +- (void)removeEvent:(MIKMIDIEvent *)event; /** - * Removes MIDI events from a track. - * - * @param events An NSSet of MIKMIDIEvent to remove from the track. + * Removes the specified MIDI events from the receiver. * - * @return Whether or not removing the MIDI events was succesful. + * @param events An NSArray containing the events to be removed. */ -- (BOOL)removeMIDIEvents:(NSSet *)events; +- (void)removeEvents:(NSArray *)events; /** - * Removes all MIDI events from the track. - * - * @return Whether or not removing all of the MIDI events from the track was succesful. + * Removes all MIDI events from the receiver. */ -- (BOOL)clearAllEvents; +- (void)removeAllEvents; /** * Gets all of the MIDI events in the track starting from startTimeStamp and ending at endTimeStamp inclusively. @@ -102,7 +92,7 @@ */ - (NSArray *)notesFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; -#pragma mark - Event +#pragma mark - Event Manipulation /** * Moves all of the MIDI events between startTimeStamp and endTimeStamp inclusively by the specified offset. @@ -126,7 +116,7 @@ - (BOOL)clearEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp; /** - * Removes all of the MIDI events between startTimeStamp and endTimeStamp inclusively. Events that fall past the + * Removes all of the MIDI events between startTimeStamp and endTimeStamp inclusively. Events that fall past the * specified range will be moved back by the specified range time. * * @param startTimeStamp The starting time stamp for the range of the events to cut. @@ -171,12 +161,16 @@ @property (nonatomic, readonly) MusicTrack musicTrack; /** - * An array of MIKMIDIEvent containing all of the MIDI events for the track. + * An array of MIKMIDIEvent containing all of the MIDI events for the track, sorted by timestamp. + * + * This property can be observed using KVO. */ @property (nonatomic, copy) NSArray *events; /** - * An array of MIKMIDINoteEvent containing all of the MIDI note events for the track. + * An array of MIKMIDINoteEvent containing all of the MIDI note events for the track, sorted by timestamp. + * + * This property can be observed using KVO. */ @property (nonatomic, readonly) NSArray *notes; @@ -259,4 +253,46 @@ */ @property (nonatomic, strong, readwrite) MIKMIDIDestinationEndpoint *destinationEndpoint DEPRECATED_ATTRIBUTE; +/** + * @deprecated Use -addEvent: instead. + * + * Inserts the specified MIDI event into the track. + * + * @param event The MIDI event to insert into the track. + * + * @return Whether or not inserting the MIDI event was succesful. + */ +- (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event DEPRECATED_ATTRIBUTE; + +/** + * @deprecated Use -addEvent: instead. + * + * Inserts MIDI events into the track. + * + * @param events An NSSet of MIKMIDIEvent to insert into the track. + * + * @return Whether or not inserting the MIDI events was succesful. + */ +- (BOOL)insertMIDIEvents:(NSSet *)events DEPRECATED_ATTRIBUTE; + +/** + * @deprecated Use -removeEvent: instead. + * + * Removes MIDI events from a track. + * + * @param events An NSSet of MIKMIDIEvent to remove from the track. + * + * @return Whether or not removing the MIDI events was succesful. + */ +- (BOOL)removeMIDIEvents:(NSSet *)events DEPRECATED_ATTRIBUTE; + +/** + * @deprecated Use -removeAllEvents instead. + * + * Removes all MIDI events from the track. + * + * @return Whether or not removing all of the MIDI events from the track was succesful. + */ +- (BOOL)clearAllEvents DEPRECATED_ATTRIBUTE; + @end diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 04914e29..c4219e28 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -13,6 +13,7 @@ #import "MIKMIDITempoEvent.h" #import "MIKMIDIEventIterator.h" #import "MIKMIDIDestinationEndpoint.h" +#import "MIKMIDIErrors.h" #if !__has_feature(objc_arc) #error MIKMIDITrack.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target @@ -21,6 +22,8 @@ @interface MIKMIDITrack () @property (weak, nonatomic) MIKMIDISequence *sequence; +@property (nonatomic, strong) NSMutableSet *internalEvents; +@property (nonatomic, strong) NSArray *sortedEventsCache; @property (nonatomic) MusicTimeStamp restoredLength; @property (nonatomic) MusicTrackLoopInfo restoredLoopInfo; @@ -35,90 +38,167 @@ @implementation MIKMIDITrack - (instancetype)initWithSequence:(MIKMIDISequence *)sequence musicTrack:(MusicTrack)musicTrack { - if (self = [super init]) { - MusicSequence musicTrackSequence; - OSStatus err = MusicTrackGetSequence(musicTrack, &musicTrackSequence); - if (err) NSLog(@"MusicTrackGetSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - - if (musicTrackSequence != sequence.musicSequence) { - NSLog(@"ERROR: initWithSequence:musicTrack: requires the musicTrack's associated MusicSequence to be the same as sequence's musicSequence property."); - return nil; - } - - _musicTrack = musicTrack; - _sequence = sequence; - } - - return self; + if (self = [super init]) { + MusicSequence musicTrackSequence; + OSStatus err = MusicTrackGetSequence(musicTrack, &musicTrackSequence); + if (err) NSLog(@"MusicTrackGetSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + + if (musicTrackSequence != sequence.musicSequence) { + NSLog(@"ERROR: initWithSequence:musicTrack: requires the musicTrack's associated MusicSequence to be the same as sequence's musicSequence property."); + return nil; + } + + _internalEvents = [[NSMutableSet alloc] init]; + _musicTrack = musicTrack; + _sequence = sequence; + [self reloadAllEventsFromMusicTrack]; + } + + return self; } + (instancetype)trackWithSequence:(MIKMIDISequence *)sequence musicTrack:(MusicTrack)musicTrack { - return [[self alloc] initWithSequence:sequence musicTrack:musicTrack]; + return [[self alloc] initWithSequence:sequence musicTrack:musicTrack]; } - (instancetype)init { #ifdef DEBUG - @throw [NSException exceptionWithName:NSGenericException reason:@"Invalid initializer." userInfo:nil]; + @throw [NSException exceptionWithName:NSGenericException reason:@"Invalid initializer." userInfo:nil]; #endif - return nil; + return nil; } #pragma mark - Adding and Removing Events -- (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event +#pragma mark Public + +- (void)addEvent:(MIKMIDIEvent *)event { - OSStatus err = noErr; - MusicTrack track = self.musicTrack; - MusicTimeStamp timeStamp = event.timeStamp; - const void *data = [event.data bytes]; + if (!event) return; + if ([self.internalEvents containsObject:event]) return; // Don't allow duplicates + + NSError *error = nil; + if (![self insertMIDIEventInMusicTrack:event error:&error]) { + NSLog(@"Error adding %@ to %@: %@", event, self, error); + [self reloadAllEventsFromMusicTrack]; + return; + } + + [self addInternalEventsObject:event]; +} - switch (event.eventType) { - case MIKMIDIEventTypeNULL: - NSLog(@"Warning: %s attempted to insert NULL event.", __PRETTY_FUNCTION__); - break; +- (void)addEvents:(NSArray *)events +{ + NSMutableSet *scratch = [NSMutableSet setWithArray:events]; + [scratch minusSet:self.internalEvents]; // Don't allow duplicates + if (![scratch count]) return; + + NSError *error = nil; + for (MIKMIDIEvent *event in scratch) { + if (![self insertMIDIEventInMusicTrack:event error:&error]) { + NSLog(@"Error adding %@ to %@: %@", event, self, error); + [self reloadAllEventsFromMusicTrack]; + return; + } + } + + [self addInternalEvents:scratch]; +} - case MIKMIDIEventTypeExtendedNote: - err = MusicTrackNewExtendedNoteEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewExtendedNoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; +- (void)removeEvent:(MIKMIDIEvent *)event +{ + if (!event) return; + if (![self.internalEvents containsObject:event]) return; + + NSError *error = nil; + if (![self removeMIDIEventsFromMusicTrack:[NSSet setWithObject:event] error:&error]) { + NSLog(@"Error removing %@ from %@: %@", event, self, error); + [self reloadAllEventsFromMusicTrack]; + return; + } + + [self removeInternalEventsObject:event]; +} +- (void)removeEvents:(NSArray *)events +{ + if (![events count]) return; + NSMutableSet *scratch = [NSMutableSet setWithArray:events]; + [scratch intersectSet:self.internalEvents]; + + NSError *error = nil; + if (![self removeMIDIEventsFromMusicTrack:scratch error:&error]) { + NSLog(@"Error removing %@ from %@: %@", events, self, error); + [self reloadAllEventsFromMusicTrack]; + return; + } + + [self removeInternalEvents:scratch]; +} + +- (void)removeAllEvents +{ + [self clearEventsFromStartingTimeStamp:0 toEndingTimeStamp:kMusicTimeStamp_EndOfTrack]; +} + +#pragma mark Private + +- (BOOL)insertMIDIEventInMusicTrack:(MIKMIDIEvent *)event error:(NSError **)error +{ + error = error ? error : &(NSError *__autoreleasing){ nil }; + + OSStatus err = noErr; + MusicTrack track = self.musicTrack; + MusicTimeStamp timeStamp = event.timeStamp; + const void *data = [event.data bytes]; + + switch (event.eventType) { + case MIKMIDIEventTypeNULL: + NSLog(@"Warning: %s attempted to insert NULL event.", __PRETTY_FUNCTION__); + break; + + case MIKMIDIEventTypeExtendedNote: + err = MusicTrackNewExtendedNoteEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewExtendedNoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; + #if !TARGET_OS_IPHONE // Unavailable altogether on iOS. case MIKMIDIEventTypeExtendedControl: NSLog(@"Events of type MIKMIDIEventTypeExtendedControl are unsupported because the underlying CoreMIDI API is deprecated."); break; #endif - - case MIKMIDIEventTypeExtendedTempo: - err = MusicTrackNewExtendedTempoEvent(track, timeStamp, ((ExtendedTempoEvent *)data)->bpm); - if (err) NSLog(@"MusicTrackNewExtendedTempoEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; - - case MIKMIDIEventTypeUser: - err = MusicTrackNewUserEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewUserEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; - - case MIKMIDIEventTypeMIDINoteMessage: - err = MusicTrackNewMIDINoteEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewMIDINoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; - - case MIKMIDIEventTypeMIDIRawData: - err = MusicTrackNewMIDIRawDataEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewMIDIRawDataEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; - - case MIKMIDIEventTypeParameter: - err = MusicTrackNewParameterEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewParameterEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; - - case MIKMIDIEventTypeAUPreset: - err = MusicTrackNewAUPresetEvent(track, timeStamp, data); - if (err) NSLog(@"MusicTrackNewAUPresetEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - break; + + case MIKMIDIEventTypeExtendedTempo: + err = MusicTrackNewExtendedTempoEvent(track, timeStamp, ((ExtendedTempoEvent *)data)->bpm); + if (err) NSLog(@"MusicTrackNewExtendedTempoEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; + + case MIKMIDIEventTypeUser: + err = MusicTrackNewUserEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewUserEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; + + case MIKMIDIEventTypeMIDINoteMessage: + err = MusicTrackNewMIDINoteEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewMIDINoteEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; + + case MIKMIDIEventTypeMIDIRawData: + err = MusicTrackNewMIDIRawDataEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewMIDIRawDataEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; + + case MIKMIDIEventTypeParameter: + err = MusicTrackNewParameterEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewParameterEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; + + case MIKMIDIEventTypeAUPreset: + err = MusicTrackNewAUPresetEvent(track, timeStamp, data); + if (err) NSLog(@"MusicTrackNewAUPresetEvent() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + break; case MIKMIDIEventTypeMIDIChannelMessage: case MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage: @@ -151,50 +231,76 @@ - (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event break; default: err = -1; - NSLog(@"Warning: %s attempted to insert unknown event type %lu.", __PRETTY_FUNCTION__, event.eventType); - break; - } - - return !err; -} - -- (BOOL)removeMIDIEvent:(MIKMIDIEvent *)event -{ - MusicTimeStamp timeStamp = event.timeStamp; - NSMutableSet *events = [[self eventsFromTimeStamp:timeStamp toTimeStamp:timeStamp] mutableCopy]; - - if ([events containsObject:event]) { - [events removeObject:event]; - if (![self clearEventsFromStartingTimeStamp:timeStamp toEndingTimeStamp:timeStamp]) return NO; - if (![self insertMIDIEvents:events]) return NO;; - } - - return YES; -} - -- (BOOL)insertMIDIEvents:(NSSet *)events -{ - for (MIKMIDIEvent *event in events) { - if (![self insertMIDIEvent:event]) return NO; - } - return YES; -} - -- (BOOL)removeMIDIEvents:(NSSet *)events -{ - for (MIKMIDIEvent *event in events) { - if (![self removeMIDIEvent:event]) return NO; - } - return YES; + NSLog(@"Warning: %s attempted to insert unknown event type %lu.", __PRETTY_FUNCTION__, event.eventType); + break; + } + + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + return YES; } -- (BOOL)clearAllEvents +- (BOOL)removeMIDIEventsFromMusicTrack:(NSSet *)events error:(NSError **)error { - return [self clearEventsFromStartingTimeStamp:0 toEndingTimeStamp:kMusicTimeStamp_EndOfTrack]; + error = error ? error : &(NSError *__autoreleasing){ nil }; + if (![events count]) return YES; + + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]; + NSArray *sortedEvents = [events sortedArrayUsingDescriptors:@[sortDescriptor]]; + MusicTimeStamp startTimeStamp = [[sortedEvents firstObject] timeStamp]; + MusicTimeStamp endTimeStamp = [[sortedEvents lastObject] timeStamp]; + + // If there's only one event, or the start and end timestamps are otherwise the same, + // MusicTrackClear() will fail, so iterate the track and delete that way instead + if (startTimeStamp == endTimeStamp) { + BOOL success = NO; + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + while (iterator.hasCurrentEvent) { + MIKMIDIEvent *currentEvent = iterator.currentEvent; + if ([events containsObject:currentEvent]) { + if (![iterator deleteCurrentEventWithError:error]) return NO; + success = YES; + continue; // Move to next event is done by delete. + } + + [iterator moveToNextEvent]; + } + + *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDITrackEventNotFoundErrorCode userInfo:nil]; + return success; + } + + NSMutableSet *existingEvents = [NSMutableSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; + + if (![events isSubsetOfSet:existingEvents]) { + *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDITrackEventNotFoundErrorCode userInfo:nil]; + return NO; + } + [existingEvents minusSet:events]; + + OSStatus err = MusicTrackClear(self.musicTrack, startTimeStamp, endTimeStamp); + if (err) { + NSLog(@"MusicTrackClear() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + + // Read events that shouldn't have been removed. + for (MIKMIDIEvent *event in existingEvents) { + if (![self insertMIDIEventInMusicTrack:event error:error]) { + return NO; + } + } + + return YES; } #pragma mark - Getting Events +#pragma mark Public + // All event getters pass through this method - (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp { @@ -219,108 +325,205 @@ - (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)start - (NSArray *)eventsFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp { - return [self eventsOfClass:Nil fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + return [self eventsOfClass:Nil fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; } - (NSArray *)notesFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp { - return [self eventsOfClass:[MIKMIDINoteEvent class] fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + return [self eventsOfClass:[MIKMIDINoteEvent class] fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; } -#pragma mark - Editing Events +#pragma mark Private -- (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)offsetTimeStamp +- (void)reloadAllEventsFromMusicTrack { - MusicTimeStamp length = self.length; - if (!length || (startTimeStamp > length) || ![self.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + NSMutableSet *allEvents = [NSMutableSet set]; + while (iterator.hasCurrentEvent) { + MIKMIDIEvent *event = iterator.currentEvent; + [allEvents addObject:event]; + [iterator moveToNextEvent]; + } + + [self willChangeValueForKey:@"internalEvents"]; + [self.internalEvents intersectSet:allEvents]; + [self.internalEvents unionSet:allEvents]; + [self didChangeValueForKey:@"internalEvents"]; + + self.sortedEventsCache = nil; +} + +#pragma mark - Editing Events (Public) - OSStatus err = MusicTrackMoveEvents(self.musicTrack, startTimeStamp, endTimeStamp, offsetTimeStamp); - if (err) NSLog(@"MusicTrackMoveEvents() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; +- (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)offsetTimeStamp +{ + MusicTimeStamp length = self.length; + if (!length || (startTimeStamp > length) || ![self.events count]) return YES; + if (endTimeStamp > length) endTimeStamp = length; + + OSStatus err = MusicTrackMoveEvents(self.musicTrack, startTimeStamp, endTimeStamp, offsetTimeStamp); + if (err) { + NSLog(@"MusicTrackMoveEvents() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + [self reloadAllEventsFromMusicTrack]; + return NO; + } + + [self reloadAllEventsFromMusicTrack]; + + return YES; } - (BOOL)clearEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp { - MusicTimeStamp length = self.length; - if (!length || (startTimeStamp > length) || ![self.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; - - OSStatus err = MusicTrackClear(self.musicTrack, startTimeStamp, endTimeStamp); - if (err) NSLog(@"MusicTrackClear() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; + NSArray *events = [self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + + MusicTimeStamp length = self.length; + if (!length || (startTimeStamp > length) || ![self.events count]) return YES; + if (endTimeStamp > length) endTimeStamp = length; + + OSStatus err = MusicTrackClear(self.musicTrack, startTimeStamp, endTimeStamp); + if (err) { + NSLog(@"MusicTrackClear() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + [self reloadAllEventsFromMusicTrack]; + return NO; + } + + [self removeInternalEvents:[NSSet setWithArray:events]]; + return YES; } - (BOOL)cutEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp { - MusicTimeStamp length = self.length; - if (!length || (startTimeStamp > length) || ![self.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; - - OSStatus err = MusicTrackCut(self.musicTrack, startTimeStamp, endTimeStamp); - if (err) NSLog(@"MusicTrackCut() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; + NSArray *events = [self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + + MusicTimeStamp length = self.length; + if (!length || (startTimeStamp > length) || ![self.events count]) return YES; + if (endTimeStamp > length) endTimeStamp = length; + + OSStatus err = MusicTrackCut(self.musicTrack, startTimeStamp, endTimeStamp); + if (err) { + NSLog(@"MusicTrackCut() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + [self reloadAllEventsFromMusicTrack]; + return NO; + } + + [self removeInternalEvents:[NSSet setWithArray:events]]; + return YES; } - (BOOL)copyEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp andInsertAtTimeStamp:(MusicTimeStamp)destTimeStamp { - MusicTimeStamp length = origTrack.length; - if (!length || (startTimeStamp > length) || ![origTrack.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; - - OSStatus err = MusicTrackCopyInsert(origTrack.musicTrack, startTimeStamp, endTimeStamp, self.musicTrack, destTimeStamp); - if (err) NSLog(@"MusicTrackCopyInsert() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; + MusicTimeStamp length = origTrack.length; + if (!length || (startTimeStamp > length) || ![origTrack.events count]) return YES; + if (endTimeStamp > length) endTimeStamp = length; + + OSStatus err = MusicTrackCopyInsert(origTrack.musicTrack, startTimeStamp, endTimeStamp, self.musicTrack, destTimeStamp); + if (err) { + NSLog(@"MusicTrackCopyInsert() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + [self reloadAllEventsFromMusicTrack]; + return NO; + } + + [self reloadAllEventsFromMusicTrack]; + + return YES; } - (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp atTimeStamp:(MusicTimeStamp)destTimeStamp { - MusicTimeStamp length = origTrack.length; - if (!length || (startTimeStamp > length) || ![origTrack.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; - - OSStatus err = MusicTrackMerge(origTrack.musicTrack, startTimeStamp, endTimeStamp, self.musicTrack, destTimeStamp); - if (err) NSLog(@"MusicTrackMerge() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return !err; + MusicTimeStamp length = origTrack.length; + if (!length || (startTimeStamp > length) || ![origTrack.events count]) return YES; + if (endTimeStamp > length) endTimeStamp = length; + + OSStatus err = MusicTrackMerge(origTrack.musicTrack, startTimeStamp, endTimeStamp, self.musicTrack, destTimeStamp); + if (err) { + NSLog(@"MusicTrackMerge() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + [self reloadAllEventsFromMusicTrack]; + return NO; + } + + [self reloadAllEventsFromMusicTrack]; + + return YES; } #pragma mark - Temporary Length and Loop Info - (void)setTemporaryLength:(MusicTimeStamp)length andLoopInfo:(MusicTrackLoopInfo)loopInfo { - self.restoredLength = self.length; - self.restoredLoopInfo = self.loopInfo; - self.length = length; - self.loopInfo = loopInfo; - self.hasTemporaryLengthAndLoopInfo = YES; + self.restoredLength = self.length; + self.restoredLoopInfo = self.loopInfo; + self.length = length; + self.loopInfo = loopInfo; + self.hasTemporaryLengthAndLoopInfo = YES; } - (void)restoreLengthAndLoopInfo { - if (!self.hasTemporaryLengthAndLoopInfo) return; - - self.hasTemporaryLengthAndLoopInfo = NO; - self.length = self.restoredLength; - self.loopInfo = self.restoredLoopInfo; + if (!self.hasTemporaryLengthAndLoopInfo) return; + + self.hasTemporaryLengthAndLoopInfo = NO; + self.length = self.restoredLength; + self.loopInfo = self.restoredLoopInfo; } - #pragma mark - Properties -- (void)setEvents:(NSArray *)events ++ (NSSet *)keyPathsForValuesAffectingEvents { - [self clearAllEvents]; - [self insertMIDIEvents:[NSSet setWithArray:events]]; + return [NSSet setWithObjects:@"sortedEventsCache", nil]; } - (NSArray *)events { - return [self eventsFromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; + if (!self.sortedEventsCache) { + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]; + _sortedEventsCache = [self.internalEvents sortedArrayUsingDescriptors:@[sortDescriptor]]; + } + return self.sortedEventsCache; +} + +- (void)setEvents:(NSArray *)events +{ + self.internalEvents = [NSMutableSet setWithArray:events]; +} + +- (void)setInternalEvents:(NSMutableSet *)internalEvents +{ + if (internalEvents != _internalEvents) { + _internalEvents = internalEvents; + self.sortedEventsCache = nil; + } +} + +- (void)addInternalEventsObject:(MIKMIDIEvent *)event +{ + [self.internalEvents addObject:event]; + self.sortedEventsCache = nil; +} + +- (void)addInternalEvents:(NSSet *)events +{ + for (MIKMIDIEvent *event in events) { + [self addInternalEventsObject:event]; + } +} + +- (void)removeInternalEventsObject:(MIKMIDIEvent *)event +{ + [self.internalEvents removeObject:event]; + self.sortedEventsCache = nil; +} + +- (void)removeInternalEvents:(NSSet *)events +{ + [self.internalEvents minusSet:events]; + self.sortedEventsCache = nil; } - (NSArray *)notes { - return [self notesFromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; + return [self notesFromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; } - (NSInteger)trackNumber @@ -337,123 +540,123 @@ - (NSInteger)trackNumber - (BOOL)doesLoop { - return self.loopDuration > 0; + return self.loopDuration > 0; } - (SInt32)numberOfLoops { - return self.loopInfo.numberOfLoops; + return self.loopInfo.numberOfLoops; } - (void)setNumberOfLoops:(SInt32)numberOfLoops { - MusicTrackLoopInfo loopInfo = self.loopInfo; - - if (loopInfo.numberOfLoops != numberOfLoops) { - loopInfo.numberOfLoops = numberOfLoops; - self.loopInfo = loopInfo; - } + MusicTrackLoopInfo loopInfo = self.loopInfo; + + if (loopInfo.numberOfLoops != numberOfLoops) { + loopInfo.numberOfLoops = numberOfLoops; + self.loopInfo = loopInfo; + } } - (MusicTimeStamp)loopDuration { - return self.loopInfo.loopDuration; + return self.loopInfo.loopDuration; } - (void)setLoopDuration:(MusicTimeStamp)loopDuration { - MusicTrackLoopInfo loopInfo = self.loopInfo; - - if (loopInfo.loopDuration != loopDuration) { - loopInfo.loopDuration = loopDuration; - self.loopInfo = loopInfo; - } + MusicTrackLoopInfo loopInfo = self.loopInfo; + + if (loopInfo.loopDuration != loopDuration) { + loopInfo.loopDuration = loopDuration; + self.loopInfo = loopInfo; + } } - (MusicTrackLoopInfo)loopInfo { - MusicTrackLoopInfo info; - UInt32 infoSize = sizeof(info); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &info, &infoSize); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return info; + MusicTrackLoopInfo info; + UInt32 infoSize = sizeof(info); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &info, &infoSize); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return info; } - (void)setLoopInfo:(MusicTrackLoopInfo)loopInfo { - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &loopInfo, sizeof(loopInfo)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &loopInfo, sizeof(loopInfo)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); } - (MusicTimeStamp)offset { - MusicTimeStamp offset = 0; - UInt32 offsetLength = sizeof(offset); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, &offsetLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return offset; + MusicTimeStamp offset = 0; + UInt32 offsetLength = sizeof(offset); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, &offsetLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return offset; } - (void)setOffset:(MusicTimeStamp)offset { - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, sizeof(offset)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, sizeof(offset)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); } - (BOOL)isMuted { - Boolean isMuted = FALSE; - UInt32 isMutedLength = sizeof(isMuted); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &isMuted, &isMutedLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return isMuted ? YES : NO; + Boolean isMuted = FALSE; + UInt32 isMutedLength = sizeof(isMuted); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &isMuted, &isMutedLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return isMuted ? YES : NO; } - (void)setMuted:(BOOL)muted { - Boolean mutedBoolean = muted ? TRUE : FALSE; - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + Boolean mutedBoolean = muted ? TRUE : FALSE; + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); } - (BOOL)isSolo { - Boolean isSolo = FALSE; - UInt32 isSoloLength = sizeof(isSolo); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &isSolo, &isSoloLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return isSolo ? YES : NO; + Boolean isSolo = FALSE; + UInt32 isSoloLength = sizeof(isSolo); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &isSolo, &isSoloLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return isSolo ? YES : NO; } - (void)setSolo:(BOOL)solo { - Boolean soloBoolean = solo ? TRUE : FALSE; - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &soloBoolean, sizeof(soloBoolean)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + Boolean soloBoolean = solo ? TRUE : FALSE; + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &soloBoolean, sizeof(soloBoolean)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); } - (MusicTimeStamp)length { - MusicTimeStamp length = 0; - UInt32 lengthLength = sizeof(length); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, &lengthLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return length; + MusicTimeStamp length = 0; + UInt32 lengthLength = sizeof(length); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, &lengthLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return length; } - (void)setLength:(MusicTimeStamp)length { - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, sizeof(length)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, sizeof(length)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); } - (SInt16)timeResolution { - SInt16 resolution = 0; - UInt32 resolutionLength = sizeof(resolution); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TimeResolution, &resolution, &resolutionLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return resolution; + SInt16 resolution = 0; + UInt32 resolutionLength = sizeof(resolution); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TimeResolution, &resolution, &resolutionLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return resolution; } #pragma mark - Properties @@ -483,12 +686,65 @@ - (MIKMIDIDestinationEndpoint *)destinationEndpoint - (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)destinationEndpoint { NSLog(@"%s is deprecated. You should update your code to avoid calling this method. Use MIKMIDISequencer's API instead.", __PRETTY_FUNCTION__); + + if (destinationEndpoint != _destinationEndpoint) { + OSStatus err = MusicTrackSetDestMIDIEndpoint(self.musicTrack, (MIDIEndpointRef)destinationEndpoint.objectRef); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + _destinationEndpoint = destinationEndpoint; + } +} - if (destinationEndpoint != _destinationEndpoint) { - OSStatus err = MusicTrackSetDestMIDIEndpoint(self.musicTrack, (MIDIEndpointRef)destinationEndpoint.objectRef); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - _destinationEndpoint = destinationEndpoint; - } +- (BOOL)insertMIDIEvent:(MIKMIDIEvent *)event +{ + static BOOL deprectionMsgShown = NO; + if (!deprectionMsgShown) { + NSLog(@"WARNING: %s has been deprecated. Please use -addEvent: instead. This message will only be logged once.", __PRETTY_FUNCTION__); + deprectionMsgShown = YES; + } + + [self addEvent:event]; + return YES; +} + +- (BOOL)insertMIDIEvents:(NSSet *)events +{ + static BOOL deprectionMsgShown = NO; + if (!deprectionMsgShown) { + NSLog(@"WARNING: %s has been deprecated. Please use -addEvent: instead. This message will only be logged once.", __PRETTY_FUNCTION__); + deprectionMsgShown = YES; + } + + for (MIKMIDIEvent *event in events) { + [self addEvent:event]; + } + return YES; +} + +- (BOOL)removeMIDIEvents:(NSSet *)events +{ + static BOOL deprectionMsgShown = NO; + if (!deprectionMsgShown) { + NSLog(@"WARNING: %s has been deprecated. Please use -removeEvent: instead. This message will only be logged once.", __PRETTY_FUNCTION__); + deprectionMsgShown = YES; + } + + for (MIKMIDIEvent *event in events) { + [self removeEvent:event]; + } + return YES; +} + +- (BOOL)clearAllEvents +{ + static BOOL deprectionMsgShown = NO; + if (!deprectionMsgShown) { + NSLog(@"WARNING: %s has been deprecated. Please use -removeAllEvents instead. This message will only be logged once.", __PRETTY_FUNCTION__); + deprectionMsgShown = YES; + } + + [self removeAllEvents]; + return YES; } @end + From 5e7c4034d9c762467fa7e7977360498798920050 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 9 Mar 2015 16:44:30 -0600 Subject: [PATCH 088/284] Issue #35: Added -[MIKMIDIEventIterator moveCurrentEventTo:error]. --- Source/MIKMIDIEventIterator.h | 1 + Source/MIKMIDIEventIterator.m | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/Source/MIKMIDIEventIterator.h b/Source/MIKMIDIEventIterator.h index b231d69a..61e2c958 100644 --- a/Source/MIKMIDIEventIterator.h +++ b/Source/MIKMIDIEventIterator.h @@ -30,5 +30,6 @@ - (BOOL)moveToNextEvent; - (BOOL)moveToPreviousEvent; - (BOOL)deleteCurrentEventWithError:(NSError **)error; +- (BOOL)moveCurrentEventTo:(MusicTimeStamp)timestamp error:(NSError **)error; @end diff --git a/Source/MIKMIDIEventIterator.m b/Source/MIKMIDIEventIterator.m index 304a22a1..3655a129 100644 --- a/Source/MIKMIDIEventIterator.m +++ b/Source/MIKMIDIEventIterator.m @@ -91,6 +91,18 @@ - (BOOL)deleteCurrentEventWithError:(NSError **)error return YES; } +- (BOOL)moveCurrentEventTo:(MusicTimeStamp)timestamp error:(NSError **)error +{ + error = error ? error : &(NSError *__autoreleasing){ nil }; + OSStatus err = MusicEventIteratorSetEventTime(self.iterator, timestamp); + if (err) { + NSLog(@"MusicEventIteratorSetEventTime() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + return YES; +} + #pragma mark - Current Event - (MIKMIDIEvent *)currentEvent From 7f01918da7411bb07d0b784ea4273c4437d19735 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 9 Mar 2015 16:45:09 -0600 Subject: [PATCH 089/284] Issue #35: Fixed -[MIKMIDITrack moveEventsFromStartingTimeStamp:...] so it works when startTimeStamp == endTimeStamp. --- Source/MIKMIDITrack.h | 4 ++-- Source/MIKMIDITrack.m | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index ccd3e260..e62306ee 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -99,11 +99,11 @@ * * @param startTimeStamp The starting time stamp for the range of the events to move. * @param endTimeStamp The ending time stamp for the range of the events to move. - * @param offsetTimeStamp The amount to move the events + * @param timestampOffset The amount to move the events * * @return Whether or not moving the events was succesful. */ -- (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)offsetTimeStamp; +- (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)timestampOffset; /** * Removes all of the MIDI events between startTimeStamp and endTimeStamp inclusively. diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index c4219e28..c05d6233 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -355,13 +355,47 @@ - (void)reloadAllEventsFromMusicTrack #pragma mark - Editing Events (Public) -- (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)offsetTimeStamp +- (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)timestampOffset { MusicTimeStamp length = self.length; if (!length || (startTimeStamp > length) || ![self.events count]) return YES; if (endTimeStamp > length) endTimeStamp = length; - OSStatus err = MusicTrackMoveEvents(self.musicTrack, startTimeStamp, endTimeStamp, offsetTimeStamp); + if (startTimeStamp == endTimeStamp){ + // If the start and end timestamps are the same, + // MusicTrackMoveEvents() will fail, so iterate the track and move that way instead + NSMutableSet *eventsToMove = [NSMutableSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; + NSSet *eventsBeforeMoving = [eventsToMove copy]; + NSMutableSet *eventsAfterMoving = [NSMutableSet set]; + + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + while (iterator.hasCurrentEvent && [eventsToMove count] > 0) { + MIKMIDIEvent *currentEvent = iterator.currentEvent; + if (![eventsToMove containsObject:currentEvent]) { + [iterator moveToNextEvent]; + continue; + } + + MusicTimeStamp timestamp = currentEvent.timeStamp; + if (![iterator moveCurrentEventTo:timestamp+timestampOffset error:NULL]) { + [self reloadAllEventsFromMusicTrack]; + return NO; + } + MIKMutableMIDIEvent *movedEvent = [currentEvent mutableCopy]; + movedEvent.timeStamp += timestampOffset; + [eventsAfterMoving addObject:[movedEvent copy]]; + [eventsToMove removeObject:currentEvent]; + [iterator seek:timestamp]; // Move back to previous position + } + + [self removeInternalEvents:eventsBeforeMoving]; + [self addInternalEvents:eventsAfterMoving]; + + return YES; + } + + // We can just use MusicTrackMoveEvents() instead. + OSStatus err = MusicTrackMoveEvents(self.musicTrack, startTimeStamp, endTimeStamp, timestampOffset); if (err) { NSLog(@"MusicTrackMoveEvents() failed with error %d in %s.", err, __PRETTY_FUNCTION__); [self reloadAllEventsFromMusicTrack]; From e29e10b9d9c87c7efed4e14d901b9f326b726ab0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 9 Mar 2015 16:45:34 -0600 Subject: [PATCH 090/284] Issue #35: Added unit tests for -[MIKMIDITrack moveEventsFromStartingTimeStamp:...]. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 164 ++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index f80fab75..317b0c4a 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -77,6 +77,170 @@ - (void)testBasicEventsAddRemove [track removeObserver:self forKeyPath:@"events"]; } +- (void)testMovingSingleEvent +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event3, expectedEvent2AfterMove, event4]; + XCTAssertEqualObjects(track.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMovingMultipleEventsAtSameTimestamp +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:62 velocity:127 duration:1 channel:0]; + + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable + NSSet *expectedNewEvents = [NSSet setWithArray:@[event1, event4, expectedEvent2AfterMove, expectedEvent3AfterMove, event5]]; + NSSet *eventsAfterMoving = [NSSet setWithArray:track.events]; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMovingEventsInARange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track moveEventsFromStartingTimeStamp:1.5 toEndingTimeStamp:3.5 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:62 velocity:127 duration:1 channel:0]; + + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable + NSSet *expectedNewEvents = [NSSet setWithArray:@[event1, event4, expectedEvent2AfterMove, expectedEvent3AfterMove, event5]]; + NSSet *eventsAfterMoving = [NSSet setWithArray:track.events]; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMovingEventsPastTheEnd +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track moveEventsFromStartingTimeStamp:5 toEndingTimeStamp:7 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent5AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:9 note:64 velocity:127 duration:1 channel:0]; + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, expectedEvent5AfterMove]; + NSArray *eventsAfterMoving = track.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + + XCTAssertGreaterThanOrEqual(track.length, expectedEvent5AfterMove.timeStamp, @"Moving last event in track didn't properly update its length."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMovingEventBackwards +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track moveEventsFromStartingTimeStamp:4 toEndingTimeStamp:4 byAmount:-2]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, expectedEvent3AfterMove, event2, event4]; + XCTAssertEqualObjects(track.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMovingEventsInARangeBackwards +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:8 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:10 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track moveEventsFromStartingTimeStamp:5.5 toEndingTimeStamp:8.5 byAmount:-4]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable + NSArray *expectedNewEvents = @[event1, expectedEvent3AfterMove, expectedEvent4AfterMove, event2, event5]; + NSArray *eventsAfterMoving = track.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([object isKindOfClass:[MIKMIDITrack class]] && [keyPath isEqualToString:@"events"]) { From b448453e8f4c7d3580f82b2e1ac0e87d0b893b9c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 9 Mar 2015 16:51:27 -0600 Subject: [PATCH 091/284] Issue #35: Fixed another edge case for -[MIKMIDITrack moveEventsFromStartingTimeStamp:...] --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 6 +- Source/MIKMIDITrack.m | 64 ++++++++------------- 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 317b0c4a..04ab9637 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -77,6 +77,8 @@ - (void)testBasicEventsAddRemove [track removeObserver:self forKeyPath:@"events"]; } +#pragma mark - Moving Events + - (void)testMovingSingleEvent { MIKMIDISequence *sequence = [MIKMIDISequence sequence]; @@ -146,7 +148,7 @@ - (void)testMovingEventsInARange [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; { // Move event 2 to timestamp 5 - [track moveEventsFromStartingTimeStamp:1.5 toEndingTimeStamp:3.5 byAmount:3]; + [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:3 byAmount:3]; XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:62 velocity:127 duration:1 channel:0]; @@ -241,6 +243,8 @@ - (void)testMovingEventsInARangeBackwards [track removeObserver:self forKeyPath:@"events"]; } +#pragma mark - (KVO Test Helper) + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([object isKindOfClass:[MIKMIDITrack class]] && [keyPath isEqualToString:@"events"]) { diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index c05d6233..65ba66c3 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -357,53 +357,39 @@ - (void)reloadAllEventsFromMusicTrack - (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)timestampOffset { + // MusicTrackMoveEvents() fails in common edge cases, so iterate the track and move that way instead + MusicTimeStamp length = self.length; if (!length || (startTimeStamp > length) || ![self.events count]) return YES; if (endTimeStamp > length) endTimeStamp = length; - if (startTimeStamp == endTimeStamp){ - // If the start and end timestamps are the same, - // MusicTrackMoveEvents() will fail, so iterate the track and move that way instead - NSMutableSet *eventsToMove = [NSMutableSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; - NSSet *eventsBeforeMoving = [eventsToMove copy]; - NSMutableSet *eventsAfterMoving = [NSMutableSet set]; - - MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; - while (iterator.hasCurrentEvent && [eventsToMove count] > 0) { - MIKMIDIEvent *currentEvent = iterator.currentEvent; - if (![eventsToMove containsObject:currentEvent]) { - [iterator moveToNextEvent]; - continue; - } - - MusicTimeStamp timestamp = currentEvent.timeStamp; - if (![iterator moveCurrentEventTo:timestamp+timestampOffset error:NULL]) { - [self reloadAllEventsFromMusicTrack]; - return NO; - } - MIKMutableMIDIEvent *movedEvent = [currentEvent mutableCopy]; - movedEvent.timeStamp += timestampOffset; - [eventsAfterMoving addObject:[movedEvent copy]]; - [eventsToMove removeObject:currentEvent]; - [iterator seek:timestamp]; // Move back to previous position + NSMutableSet *eventsToMove = [NSMutableSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; + NSSet *eventsBeforeMoving = [eventsToMove copy]; + NSMutableSet *eventsAfterMoving = [NSMutableSet set]; + + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + while (iterator.hasCurrentEvent && [eventsToMove count] > 0) { + MIKMIDIEvent *currentEvent = iterator.currentEvent; + if (![eventsToMove containsObject:currentEvent]) { + [iterator moveToNextEvent]; + continue; } - [self removeInternalEvents:eventsBeforeMoving]; - [self addInternalEvents:eventsAfterMoving]; - - return YES; - } - - // We can just use MusicTrackMoveEvents() instead. - OSStatus err = MusicTrackMoveEvents(self.musicTrack, startTimeStamp, endTimeStamp, timestampOffset); - if (err) { - NSLog(@"MusicTrackMoveEvents() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - [self reloadAllEventsFromMusicTrack]; - return NO; + MusicTimeStamp timestamp = currentEvent.timeStamp; + if (![iterator moveCurrentEventTo:timestamp+timestampOffset error:NULL]) { + [self reloadAllEventsFromMusicTrack]; + return NO; + } + MIKMutableMIDIEvent *movedEvent = [currentEvent mutableCopy]; + movedEvent.timeStamp += timestampOffset; + [eventsAfterMoving addObject:[movedEvent copy]]; + [eventsToMove removeObject:currentEvent]; + [iterator seek:timestamp]; // Move back to previous position } - [self reloadAllEventsFromMusicTrack]; - + [self removeInternalEvents:eventsBeforeMoving]; + [self addInternalEvents:eventsAfterMoving]; + return YES; } From 2dbe4facc5f1179160f53dfd099be895fbc30964 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Mon, 9 Mar 2015 16:52:13 -0600 Subject: [PATCH 092/284] Issue #35: Added tests for -[MIKMIDITrack clearEventsFromStartingTimeStamp:toEndingTimeStamp:]. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 106 ++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 04ab9637..3c4c5eb5 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -243,6 +243,112 @@ - (void)testMovingEventsInARangeBackwards [track removeObserver:self forKeyPath:@"events"]; } +#pragma mark - Clearing Events + +- (void)testClearingSingleEvent +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event3, event4, event5]; + NSArray *eventsAfterMoving = track.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testClearingMultipleEventsAtSameTimestamp +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event4, event5]; + NSArray *eventsAfterMoving = track.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testClearingEventsInARange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track clearEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event5]; + NSArray *eventsAfterMoving = track.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testClearingEventsInAWiderRange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track clearEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event5]; + NSArray *eventsAfterMoving = track.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + #pragma mark - (KVO Test Helper) - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context From 6b2c7c66f92e88ed51c6e45684d028ef6fadcda4 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 14:48:25 -0600 Subject: [PATCH 093/284] Issue #35: Added tests for -[MIKMIDITrack cutEventsFromStartingTimeStamp:...]. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 132 ++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 3c4c5eb5..55b8da7c 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -349,6 +349,138 @@ - (void)testClearingEventsInAWiderRange [track removeObserver:self forKeyPath:@"events"]; } +#pragma mark - Cutting Events + +- (void)testCuttingSingleEvent +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [track cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event4, event5]; + NSArray *eventsAfterCutting = track.events; + XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCuttingSingleEventInWiderRange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [track cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:3.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent4AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, expectedEvent4AfterCut, expectedEvent5AfterCut]; + NSArray *eventsAfterCutting = track.events; + XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCuttingMultipleEventsAtSameTimestamp +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [track cutEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event4, event5]; + NSArray *eventsAfterCutting = track.events; + XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCuttingEventsInARange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [track cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; + NSArray *eventsAfterCut = track.events; + XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCuttingEventsInAWiderRange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + MIKMIDITrack *track = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + NSArray *allEvents = @[event1, event2, event3, event4, event5]; + [track addEvents:allEvents]; + + self.eventsChangeNotificationReceived = NO; + [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + // Move event 2 to timestamp 5 + [track cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; + NSArray *eventsAfterCut = track.events; + XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } + [track removeObserver:self forKeyPath:@"events"]; +} + #pragma mark - (KVO Test Helper) - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context From 5263c61bec1284159ce158cd2f97d1e2e2af730c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 14:48:59 -0600 Subject: [PATCH 094/284] Issue #35: Fixed bugs so that all tests once again pass, particularly -cutEvents... tests. --- Source/MIKMIDITrack.m | 95 +++++++++++-------------------------------- 1 file changed, 23 insertions(+), 72 deletions(-) diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 65ba66c3..4a3cd4df 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -247,54 +247,23 @@ - (BOOL)removeMIDIEventsFromMusicTrack:(NSSet *)events error:(NSError **)error error = error ? error : &(NSError *__autoreleasing){ nil }; if (![events count]) return YES; - NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]; - NSArray *sortedEvents = [events sortedArrayUsingDescriptors:@[sortDescriptor]]; - MusicTimeStamp startTimeStamp = [[sortedEvents firstObject] timeStamp]; - MusicTimeStamp endTimeStamp = [[sortedEvents lastObject] timeStamp]; - - // If there's only one event, or the start and end timestamps are otherwise the same, - // MusicTrackClear() will fail, so iterate the track and delete that way instead - if (startTimeStamp == endTimeStamp) { - BOOL success = NO; - MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; - while (iterator.hasCurrentEvent) { - MIKMIDIEvent *currentEvent = iterator.currentEvent; - if ([events containsObject:currentEvent]) { - if (![iterator deleteCurrentEventWithError:error]) return NO; - success = YES; - continue; // Move to next event is done by delete. - } - - [iterator moveToNextEvent]; + // MusicTrackClear() doesn't reliably clear events that fall on its boundaries, + // so we iterate the track and delete that way instead + BOOL success = NO; + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + while (iterator.hasCurrentEvent) { + MIKMIDIEvent *currentEvent = iterator.currentEvent; + if ([events containsObject:currentEvent]) { + if (![iterator deleteCurrentEventWithError:error]) return NO; + success = YES; + continue; // Move to next event is done by delete. } - *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDITrackEventNotFoundErrorCode userInfo:nil]; - return success; - } - - NSMutableSet *existingEvents = [NSMutableSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; - - if (![events isSubsetOfSet:existingEvents]) { - *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDITrackEventNotFoundErrorCode userInfo:nil]; - return NO; - } - [existingEvents minusSet:events]; - - OSStatus err = MusicTrackClear(self.musicTrack, startTimeStamp, endTimeStamp); - if (err) { - NSLog(@"MusicTrackClear() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; - return NO; - } - - // Read events that shouldn't have been removed. - for (MIKMIDIEvent *event in existingEvents) { - if (![self insertMIDIEventInMusicTrack:event error:error]) { - return NO; - } + [iterator moveToNextEvent]; } - return YES; + *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDITrackEventNotFoundErrorCode userInfo:nil]; + return success; } #pragma mark - Getting Events @@ -358,7 +327,8 @@ - (void)reloadAllEventsFromMusicTrack - (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)timestampOffset { // MusicTrackMoveEvents() fails in common edge cases, so iterate the track and move that way instead - + + if (timestampOffset == 0) return YES; // Nothing needs to be done MusicTimeStamp length = self.length; if (!length || (startTimeStamp > length) || ![self.events count]) return YES; if (endTimeStamp > length) endTimeStamp = length; @@ -389,46 +359,27 @@ - (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingT [self removeInternalEvents:eventsBeforeMoving]; [self addInternalEvents:eventsAfterMoving]; - + return YES; } - (BOOL)clearEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp { - NSArray *events = [self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; - - MusicTimeStamp length = self.length; - if (!length || (startTimeStamp > length) || ![self.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; - - OSStatus err = MusicTrackClear(self.musicTrack, startTimeStamp, endTimeStamp); - if (err) { - NSLog(@"MusicTrackClear() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - [self reloadAllEventsFromMusicTrack]; - return NO; - } - - [self removeInternalEvents:[NSSet setWithArray:events]]; - return YES; + NSSet *events = [NSSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; + BOOL success = [self removeMIDIEventsFromMusicTrack:events error:NULL]; + [self reloadAllEventsFromMusicTrack]; + return success; } - (BOOL)cutEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp { - NSArray *events = [self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; - MusicTimeStamp length = self.length; if (!length || (startTimeStamp > length) || ![self.events count]) return YES; if (endTimeStamp > length) endTimeStamp = length; - OSStatus err = MusicTrackCut(self.musicTrack, startTimeStamp, endTimeStamp); - if (err) { - NSLog(@"MusicTrackCut() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - [self reloadAllEventsFromMusicTrack]; - return NO; - } - - [self removeInternalEvents:[NSSet setWithArray:events]]; - return YES; + if (![self clearEventsFromStartingTimeStamp:startTimeStamp toEndingTimeStamp:endTimeStamp]) return NO; + MusicTimeStamp cutAmount = endTimeStamp - startTimeStamp; + return [self moveEventsFromStartingTimeStamp:endTimeStamp toEndingTimeStamp:kMusicTimeStamp_EndOfTrack byAmount:-cutAmount]; } - (BOOL)copyEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp andInsertAtTimeStamp:(MusicTimeStamp)destTimeStamp From e84ebb93be67c61a4fdb68cbc28be42fe555f5cc Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 15:36:10 -0600 Subject: [PATCH 095/284] Issue #35: Added tests for -[MIKMIDITrack copyEventsFromMIDITrack:...]. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 174 ++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 55b8da7c..593e5cf3 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -481,6 +481,180 @@ - (void)testCuttingEventsInAWiderRange [track removeObserver:self forKeyPath:@"events"]; } +#pragma mark - Copying Events + +- (void)testCopyingSingleEvent +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[event1, event2, event4, event5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; + NSArray *eventsAfterCopy = destTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCopyingSingleEventInWiderRange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[event1, event2, event4, event5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 andInsertAtTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent4AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, event3, expectedEvent4AfterCopy, expectedEvent5AfterCopy]; + NSArray *eventsAfterCopy = destTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCopyingMultipleEvents +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 andInsertAtTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent6 = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, expectedEvent5, expectedEvent6]; + NSArray *eventsAfterCopy = destTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCopyingMultipleEventsAtSameTimestamp +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterCopy = destTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testCopyingMultipleEventsInAWiderRange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 andInsertAtTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent6 = [MIKMIDINoteEvent noteEventWithTimeStamp:8 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, expectedEvent5, expectedEvent6]; + NSArray *eventsAfterCopy = destTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + #pragma mark - (KVO Test Helper) - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context From 8492574cea328791d3b4b194837d265e7a2ca1be Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 15:36:29 -0600 Subject: [PATCH 096/284] Issue #35: Reimplemented -copyEventsFromMIDITrack:... so that all tests pass. --- Source/MIKMIDITrack.m | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 4a3cd4df..f8dc5a5c 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -327,7 +327,7 @@ - (void)reloadAllEventsFromMusicTrack - (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)timestampOffset { // MusicTrackMoveEvents() fails in common edge cases, so iterate the track and move that way instead - + if (timestampOffset == 0) return YES; // Nothing needs to be done MusicTimeStamp length = self.length; if (!length || (startTimeStamp > length) || ![self.events count]) return YES; @@ -384,19 +384,28 @@ - (BOOL)cutEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTi - (BOOL)copyEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp andInsertAtTimeStamp:(MusicTimeStamp)destTimeStamp { - MusicTimeStamp length = origTrack.length; - if (!length || (startTimeStamp > length) || ![origTrack.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; + NSArray *sourceEvents = [origTrack eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + if (![sourceEvents count]) return YES; - OSStatus err = MusicTrackCopyInsert(origTrack.musicTrack, startTimeStamp, endTimeStamp, self.musicTrack, destTimeStamp); - if (err) { - NSLog(@"MusicTrackCopyInsert() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - [self reloadAllEventsFromMusicTrack]; - return NO; - } + MusicTimeStamp firstSourceTimeStamp = [[sourceEvents firstObject] timeStamp]; + + // Move existing events to make room for new events + if (![self moveEventsFromStartingTimeStamp:destTimeStamp + toEndingTimeStamp:kMusicTimeStamp_EndOfTrack + byAmount:(endTimeStamp - startTimeStamp)]) return NO; - [self reloadAllEventsFromMusicTrack]; + NSMutableSet *destinationEvents = [NSMutableSet set]; + for (MIKMIDIEvent *event in sourceEvents) { + MIKMutableMIDIEvent *mutableEvent = [event mutableCopy]; + mutableEvent.timeStamp = destTimeStamp + (event.timeStamp - firstSourceTimeStamp); + if (![self insertMIDIEventInMusicTrack:mutableEvent error:NULL]) { + [self reloadAllEventsFromMusicTrack]; + return NO; + } + [destinationEvents addObject:mutableEvent]; + } + [self addInternalEvents:destinationEvents]; return YES; } @@ -469,14 +478,14 @@ - (void)setInternalEvents:(NSMutableSet *)internalEvents - (void)addInternalEventsObject:(MIKMIDIEvent *)event { - [self.internalEvents addObject:event]; + [self.internalEvents addObject:[event copy]]; self.sortedEventsCache = nil; } - (void)addInternalEvents:(NSSet *)events { for (MIKMIDIEvent *event in events) { - [self addInternalEventsObject:event]; + [self addInternalEventsObject:[event copy]]; } } From 521b3e8fdb0a98be3a938486aab94f24f839de4e Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 16:16:33 -0600 Subject: [PATCH 097/284] Issue #35: Added tests for -[MIKMIDITrack mergemergeEventsFromMIDITrack:...]. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 168 ++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 593e5cf3..806b45ef 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -655,6 +655,174 @@ - (void)testCopyingMultipleEventsInAWiderRange [destTrack removeObserver:self forKeyPath:@"events"]; } +#pragma mark - Merging Events + +- (void)testMergingSingleEvent +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[event1, event2, event4, event5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; + NSArray *eventsAfterMerge = destTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMergingSingleEventInWiderRange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[event1, event2, event4, event5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 atTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; + NSArray *eventsAfterMerge = destTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMergingMultipleEvents +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 atTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterMerge = destTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMergingMultipleEventsAtSameTimestamp +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterMerge = destTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + +- (void)testMergingMultipleEventsInAWiderRange +{ + MIKMIDISequence *sequence = [MIKMIDISequence sequence]; + + MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; + + MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; + [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + + self.eventsChangeNotificationReceived = NO; + [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + { + [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 atTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterMerge = destTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } + [destTrack removeObserver:self forKeyPath:@"events"]; +} + #pragma mark - (KVO Test Helper) - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context From 8babf36ff3a7227eefb652d7eec5f173f513e443 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 16:17:51 -0600 Subject: [PATCH 098/284] Issue #35: Reimplemented -mergeEventsFromMIDITrack: so tests pass. -copyEventsFromMIDITrack:... now calls through to -mergeEvents... --- Source/MIKMIDITrack.m | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index f8dc5a5c..53475dc7 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -384,16 +384,21 @@ - (BOOL)cutEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTi - (BOOL)copyEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp andInsertAtTimeStamp:(MusicTimeStamp)destTimeStamp { - NSArray *sourceEvents = [origTrack eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; - if (![sourceEvents count]) return YES; - - MusicTimeStamp firstSourceTimeStamp = [[sourceEvents firstObject] timeStamp]; - // Move existing events to make room for new events if (![self moveEventsFromStartingTimeStamp:destTimeStamp toEndingTimeStamp:kMusicTimeStamp_EndOfTrack byAmount:(endTimeStamp - startTimeStamp)]) return NO; + return [self mergeEventsFromMIDITrack:origTrack fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp atTimeStamp:destTimeStamp]; +} + +- (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp atTimeStamp:(MusicTimeStamp)destTimeStamp +{ + NSArray *sourceEvents = [origTrack eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + if (![sourceEvents count]) return YES; + + MusicTimeStamp firstSourceTimeStamp = [[sourceEvents firstObject] timeStamp]; + NSMutableSet *destinationEvents = [NSMutableSet set]; for (MIKMIDIEvent *event in sourceEvents) { MIKMutableMIDIEvent *mutableEvent = [event mutableCopy]; @@ -409,24 +414,6 @@ - (BOOL)copyEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTi return YES; } -- (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp atTimeStamp:(MusicTimeStamp)destTimeStamp -{ - MusicTimeStamp length = origTrack.length; - if (!length || (startTimeStamp > length) || ![origTrack.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; - - OSStatus err = MusicTrackMerge(origTrack.musicTrack, startTimeStamp, endTimeStamp, self.musicTrack, destTimeStamp); - if (err) { - NSLog(@"MusicTrackMerge() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - [self reloadAllEventsFromMusicTrack]; - return NO; - } - - [self reloadAllEventsFromMusicTrack]; - - return YES; -} - #pragma mark - Temporary Length and Loop Info - (void)setTemporaryLength:(MusicTimeStamp)length andLoopInfo:(MusicTrackLoopInfo)loopInfo From 7623c8533d7fdedadf9c119c2365b89b3288880f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 20:43:15 -0600 Subject: [PATCH 099/284] Issue #35: Made MIKMIDITrack KVO compliant for the notes property. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 124 ++++++++++++++------ Source/MIKMIDITrack.m | 10 +- 2 files changed, 100 insertions(+), 34 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 806b45ef..6ee897a6 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -13,19 +13,20 @@ @interface MIKMIDITrackTests : XCTestCase @property BOOL eventsChangeNotificationReceived; +@property BOOL notesChangeNotificationReceived; @end @implementation MIKMIDITrackTests - (void)setUp { - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; } - (void)testBasicEventsAddRemove @@ -34,18 +35,19 @@ - (void)testBasicEventsAddRemove MIKMIDITrack *track = [sequence addTrack]; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Test adding an event MIKMIDIEvent *event = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; [track addEvent:event]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Adding an event to MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Adding an event to MIKMIDITrack did not produce a KVO notification."); XCTAssertTrue([track.events containsObject:event], @"Adding an event to MIKMIDITrack failed."); XCTAssertEqual([track.events count], 1, @"Adding an event to MIKMIDITrack failed."); self.eventsChangeNotificationReceived = NO; // Test removing an event [track removeEvent:event]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Removing an event from MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing an event from MIKMIDITrack did not produce a KVO notification."); XCTAssertFalse([track.events containsObject:event], @"Removing an event from MIKMIDITrack failed."); self.eventsChangeNotificationReceived = NO; @@ -59,7 +61,7 @@ - (void)testBasicEventsAddRemove [track addEvent:event4]; XCTAssertEqual([track.events count], 4, @"Adding 4 events to MIKMIDITrack failed."); [track removeEvents:@[event2, event3]]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Removing some events from MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing some events from MIKMIDITrack did not produce a KVO notification."); XCTAssertEqual([track.events count], 2, @"Removing some events from MIKMIDITrack failed."); NSArray *remainingEvents = @[event, event4]; XCTAssertEqualObjects(remainingEvents, track.events, @"Removing some events from MIKMIDITrack failed."); @@ -70,11 +72,12 @@ - (void)testBasicEventsAddRemove [track addEvent:event2]; [track addEvent:event3]; [track removeAllEvents]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Removing all events from MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing all events from MIKMIDITrack did not produce a KVO notification."); XCTAssertEqual([track.events count], 0, @"Removing all events from MIKMIDITrack failed."); self.eventsChangeNotificationReceived = NO; } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } #pragma mark - Moving Events @@ -92,15 +95,17 @@ - (void)testMovingSingleEvent self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; NSArray *expectedNewEvents = @[event1, event3, expectedEvent2AfterMove, event4]; XCTAssertEqualObjects(track.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testMovingMultipleEventsAtSameTimestamp @@ -117,10 +122,11 @@ - (void)testMovingMultipleEventsAtSameTimestamp self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:62 velocity:127 duration:1 channel:0]; @@ -130,6 +136,7 @@ - (void)testMovingMultipleEventsAtSameTimestamp XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testMovingEventsInARange @@ -146,10 +153,11 @@ - (void)testMovingEventsInARange self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:3 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:62 velocity:127 duration:1 channel:0]; @@ -159,6 +167,7 @@ - (void)testMovingEventsInARange XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testMovingEventsPastTheEnd @@ -175,10 +184,11 @@ - (void)testMovingEventsPastTheEnd self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track moveEventsFromStartingTimeStamp:5 toEndingTimeStamp:7 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent5AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:9 note:64 velocity:127 duration:1 channel:0]; NSArray *expectedNewEvents = @[event1, event2, event3, event4, expectedEvent5AfterMove]; @@ -188,6 +198,7 @@ - (void)testMovingEventsPastTheEnd XCTAssertGreaterThanOrEqual(track.length, expectedEvent5AfterMove.timeStamp, @"Moving last event in track didn't properly update its length."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testMovingEventBackwards @@ -203,15 +214,17 @@ - (void)testMovingEventBackwards self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track moveEventsFromStartingTimeStamp:4 toEndingTimeStamp:4 byAmount:-2]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; NSArray *expectedNewEvents = @[event1, expectedEvent3AfterMove, event2, event4]; XCTAssertEqualObjects(track.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testMovingEventsInARangeBackwards @@ -228,10 +241,11 @@ - (void)testMovingEventsInARangeBackwards self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track moveEventsFromStartingTimeStamp:5.5 toEndingTimeStamp:8.5 byAmount:-4]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; @@ -241,6 +255,7 @@ - (void)testMovingEventsInARangeBackwards XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } #pragma mark - Clearing Events @@ -259,16 +274,18 @@ - (void)testClearingSingleEvent self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event3, event4, event5]; NSArray *eventsAfterMoving = track.events; XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testClearingMultipleEventsAtSameTimestamp @@ -285,16 +302,18 @@ - (void)testClearingMultipleEventsAtSameTimestamp self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event4, event5]; NSArray *eventsAfterMoving = track.events; XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testClearingEventsInARange @@ -311,16 +330,18 @@ - (void)testClearingEventsInARange self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track clearEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event2, event5]; NSArray *eventsAfterMoving = track.events; XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testClearingEventsInAWiderRange @@ -337,16 +358,18 @@ - (void)testClearingEventsInAWiderRange self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track clearEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event2, event5]; NSArray *eventsAfterMoving = track.events; XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } #pragma mark - Cutting Events @@ -365,15 +388,17 @@ - (void)testCuttingSingleEvent self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [track cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event2, event4, event5]; NSArray *eventsAfterCutting = track.events; XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testCuttingSingleEventInWiderRange @@ -390,9 +415,10 @@ - (void)testCuttingSingleEventInWiderRange self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [track cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:3.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent4AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; @@ -401,6 +427,7 @@ - (void)testCuttingSingleEventInWiderRange XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testCuttingMultipleEventsAtSameTimestamp @@ -417,15 +444,17 @@ - (void)testCuttingMultipleEventsAtSameTimestamp self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [track cutEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event4, event5]; NSArray *eventsAfterCutting = track.events; XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testCuttingEventsInARange @@ -442,9 +471,10 @@ - (void)testCuttingEventsInARange self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [track cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; @@ -452,6 +482,7 @@ - (void)testCuttingEventsInARange XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } - (void)testCuttingEventsInAWiderRange @@ -468,10 +499,11 @@ - (void)testCuttingEventsInAWiderRange self.eventsChangeNotificationReceived = NO; [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { // Move event 2 to timestamp 5 [track cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:64 velocity:127 duration:1 channel:0]; NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; @@ -479,6 +511,7 @@ - (void)testCuttingEventsInAWiderRange XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); } [track removeObserver:self forKeyPath:@"events"]; + [track removeObserver:self forKeyPath:@"notes"]; } #pragma mark - Copying Events @@ -504,15 +537,17 @@ - (void)testCopyingSingleEvent self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; NSArray *eventsAfterCopy = destTrack.events; XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testCopyingSingleEventInWiderRange @@ -536,9 +571,10 @@ - (void)testCopyingSingleEventInWiderRange self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 andInsertAtTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); MIKMIDIEvent *expectedEvent4AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent5AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:64 velocity:127 duration:1 channel:0]; @@ -547,6 +583,7 @@ - (void)testCopyingSingleEventInWiderRange XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testCopyingMultipleEvents @@ -570,9 +607,10 @@ - (void)testCopyingMultipleEvents self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 andInsertAtTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; @@ -583,6 +621,7 @@ - (void)testCopyingMultipleEvents XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testCopyingMultipleEventsAtSameTimestamp @@ -606,9 +645,10 @@ - (void)testCopyingMultipleEventsAtSameTimestamp self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; @@ -617,6 +657,7 @@ - (void)testCopyingMultipleEventsAtSameTimestamp XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testCopyingMultipleEventsInAWiderRange @@ -640,9 +681,10 @@ - (void)testCopyingMultipleEventsInAWiderRange self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 andInsertAtTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; @@ -653,6 +695,7 @@ - (void)testCopyingMultipleEventsInAWiderRange XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } #pragma mark - Merging Events @@ -678,15 +721,17 @@ - (void)testMergingSingleEvent self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; NSArray *eventsAfterMerge = destTrack.events; XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testMergingSingleEventInWiderRange @@ -710,15 +755,17 @@ - (void)testMergingSingleEventInWiderRange self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 atTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; NSArray *eventsAfterMerge = destTrack.events; XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testMergingMultipleEvents @@ -742,9 +789,10 @@ - (void)testMergingMultipleEvents self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 atTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; @@ -753,6 +801,7 @@ - (void)testMergingMultipleEvents XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testMergingMultipleEventsAtSameTimestamp @@ -776,9 +825,10 @@ - (void)testMergingMultipleEventsAtSameTimestamp self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; @@ -787,6 +837,7 @@ - (void)testMergingMultipleEventsAtSameTimestamp XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } - (void)testMergingMultipleEventsInAWiderRange @@ -810,9 +861,10 @@ - (void)testMergingMultipleEventsInAWiderRange self.eventsChangeNotificationReceived = NO; [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; { [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 atTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; @@ -821,6 +873,7 @@ - (void)testMergingMultipleEventsInAWiderRange XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); } [destTrack removeObserver:self forKeyPath:@"events"]; + [destTrack removeObserver:self forKeyPath:@"notes"]; } #pragma mark - (KVO Test Helper) @@ -830,6 +883,11 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N if ([object isKindOfClass:[MIKMIDITrack class]] && [keyPath isEqualToString:@"events"]) { self.eventsChangeNotificationReceived = YES; } + + if ([object isKindOfClass:[MIKMIDITrack class]] && [keyPath isEqualToString:@"notes"]) { + self.notesChangeNotificationReceived = YES; + } } @end + diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 53475dc7..ad8f1346 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -488,9 +488,17 @@ - (void)removeInternalEvents:(NSSet *)events self.sortedEventsCache = nil; } ++ (NSSet *)keyPathsForValuesAffectingNotes +{ + return [NSSet setWithObjects:@"sortedEventsCache", nil]; +} + - (NSArray *)notes { - return [self notesFromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; + NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *b) { + return [(MIKMIDIEvent *)obj eventType] == MIKMIDIEventTypeMIDINoteMessage; + }]; + return [self.sortedEventsCache filteredArrayUsingPredicate:predicate]; } - (NSInteger)trackNumber From e3cda89b266c8000117f3d4eb222114a01db99ce Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Mar 2015 22:47:58 -0600 Subject: [PATCH 100/284] Fixed stupid bug that caused -[MIKMIDISequencer builtinSynthesizerForTrack:] to always return nil. --- Source/MIKMIDISequencer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 361538cd..7a154fc5 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -526,7 +526,7 @@ - (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)trac [self setDestinationEndpoint:result forTrack:track]; MIKMIDISynthesizer *synth = [MIKMIDIEndpointSynthesizer synthesizerWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)result]; - [self.tracksToDefaultSynthsMap setObject:synth forKey:synth]; + [self.tracksToDefaultSynthsMap setObject:synth forKey:track]; } return result; } From 418daac9f0848bdc3a7b77a30e4ff01e730c36c8 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 11 Mar 2015 10:34:50 -0600 Subject: [PATCH 101/284] Issue #35: Cleaned up tests. Doing setup and tear down in -setUp and -tearDown now. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 830 ++++++++------------ 1 file changed, 342 insertions(+), 488 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 6ee897a6..3bb53c97 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -15,512 +15,425 @@ @interface MIKMIDITrackTests : XCTestCase @property BOOL eventsChangeNotificationReceived; @property BOOL notesChangeNotificationReceived; +@property (nonatomic, strong) MIKMIDISequence *defaultSequence; +@property (nonatomic, strong) MIKMIDITrack *defaultTrack; + @end @implementation MIKMIDITrackTests -- (void)setUp { +- (void)setUp +{ [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. + + self.defaultSequence = [MIKMIDISequence sequence]; + self.defaultTrack = [self.defaultSequence addTrack]; + + [self.defaultTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; + [self.defaultTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; } - (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. + + [self.defaultTrack removeObserver:self forKeyPath:@"events"]; + [self.defaultTrack removeObserver:self forKeyPath:@"notes"]; + + self.defaultSequence = nil; + self.defaultTrack = nil; + [super tearDown]; } - (void)testBasicEventsAddRemove { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; - - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Test adding an event - MIKMIDIEvent *event = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; - [track addEvent:event]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Adding an event to MIKMIDITrack did not produce a KVO notification."); - XCTAssertTrue([track.events containsObject:event], @"Adding an event to MIKMIDITrack failed."); - XCTAssertEqual([track.events count], 1, @"Adding an event to MIKMIDITrack failed."); - self.eventsChangeNotificationReceived = NO; - - // Test removing an event - [track removeEvent:event]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing an event from MIKMIDITrack did not produce a KVO notification."); - XCTAssertFalse([track.events containsObject:event], @"Removing an event from MIKMIDITrack failed."); - self.eventsChangeNotificationReceived = NO; - - // Test removing some events - MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; - [track addEvent:event]; - [track addEvent:event2]; - [track addEvent:event3]; - [track addEvent:event4]; - XCTAssertEqual([track.events count], 4, @"Adding 4 events to MIKMIDITrack failed."); - [track removeEvents:@[event2, event3]]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing some events from MIKMIDITrack did not produce a KVO notification."); - XCTAssertEqual([track.events count], 2, @"Removing some events from MIKMIDITrack failed."); - NSArray *remainingEvents = @[event, event4]; - XCTAssertEqualObjects(remainingEvents, track.events, @"Removing some events from MIKMIDITrack failed."); - self.eventsChangeNotificationReceived = NO; - - // Test removing all events - [track addEvent:event]; - [track addEvent:event2]; - [track addEvent:event3]; - [track removeAllEvents]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing all events from MIKMIDITrack did not produce a KVO notification."); - XCTAssertEqual([track.events count], 0, @"Removing all events from MIKMIDITrack failed."); - self.eventsChangeNotificationReceived = NO; - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + // Test adding an event + MIKMIDIEvent *event = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; + [self.defaultTrack addEvent:event]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Adding an event to MIKMIDITrack did not produce a KVO notification."); + XCTAssertTrue([self.defaultTrack.events containsObject:event], @"Adding an event to MIKMIDITrack failed."); + XCTAssertEqual([self.defaultTrack.events count], 1, @"Adding an event to MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + + // Test removing an event + [self.defaultTrack removeEvent:event]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing an event from MIKMIDITrack did not produce a KVO notification."); + XCTAssertFalse([self.defaultTrack.events containsObject:event], @"Removing an event from MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + + // Test removing some events + MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + [self.defaultTrack addEvent:event]; + [self.defaultTrack addEvent:event2]; + [self.defaultTrack addEvent:event3]; + [self.defaultTrack addEvent:event4]; + XCTAssertEqual([self.defaultTrack.events count], 4, @"Adding 4 events to MIKMIDITrack failed."); + [self.defaultTrack removeEvents:@[event2, event3]]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing some events from MIKMIDITrack did not produce a KVO notification."); + XCTAssertEqual([self.defaultTrack.events count], 2, @"Removing some events from MIKMIDITrack failed."); + NSArray *remainingEvents = @[event, event4]; + XCTAssertEqualObjects(remainingEvents, self.defaultTrack.events , @"Removing some events from MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + + // Test removing all events + [self.defaultTrack addEvent:event]; + [self.defaultTrack addEvent:event2]; + [self.defaultTrack addEvent:event3]; + [self.defaultTrack removeAllEvents]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Removing all events from MIKMIDITrack did not produce a KVO notification."); + XCTAssertEqual([self.defaultTrack.events count], 0, @"Removing all events from MIKMIDITrack failed."); + self.eventsChangeNotificationReceived = NO; + } #pragma mark - Moving Events - (void)testMovingSingleEvent { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); - MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[event1, event3, expectedEvent2AfterMove, event4]; - XCTAssertEqualObjects(track.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event3, expectedEvent2AfterMove, event4]; + XCTAssertEqualObjects(self.defaultTrack.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } - (void)testMovingMultipleEventsAtSameTimestamp { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); - MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:62 velocity:127 duration:1 channel:0]; - - // Use sets, because order of events with the same timestamp is (acceptably) unpredictable - NSSet *expectedNewEvents = [NSSet setWithArray:@[event1, event4, expectedEvent2AfterMove, expectedEvent3AfterMove, event5]]; - NSSet *eventsAfterMoving = [NSSet setWithArray:track.events]; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:2 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:62 velocity:127 duration:1 channel:0]; + + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable + NSSet *expectedNewEvents = [NSSet setWithArray:@[event1, event4, expectedEvent2AfterMove, expectedEvent3AfterMove, event5]]; + NSSet *eventsAfterMoving = [NSSet setWithArray:self.defaultTrack.events]; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } - (void)testMovingEventsInARange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:3 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); - MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:62 velocity:127 duration:1 channel:0]; - - // Use sets, because order of events with the same timestamp is (acceptably) unpredictable - NSSet *expectedNewEvents = [NSSet setWithArray:@[event1, event4, expectedEvent2AfterMove, expectedEvent3AfterMove, event5]]; - NSSet *eventsAfterMoving = [NSSet setWithArray:track.events]; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack moveEventsFromStartingTimeStamp:2 toEndingTimeStamp:3 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:62 velocity:127 duration:1 channel:0]; + + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable + NSSet *expectedNewEvents = [NSSet setWithArray:@[event1, event4, expectedEvent2AfterMove, expectedEvent3AfterMove, event5]]; + NSSet *eventsAfterMoving = [NSSet setWithArray:self.defaultTrack.events]; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); } - (void)testMovingEventsPastTheEnd { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track moveEventsFromStartingTimeStamp:5 toEndingTimeStamp:7 byAmount:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); - MIKMIDIEvent *expectedEvent5AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:9 note:64 velocity:127 duration:1 channel:0]; - - NSArray *expectedNewEvents = @[event1, event2, event3, event4, expectedEvent5AfterMove]; - NSArray *eventsAfterMoving = track.events; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); - - XCTAssertGreaterThanOrEqual(track.length, expectedEvent5AfterMove.timeStamp, @"Moving last event in track didn't properly update its length."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack moveEventsFromStartingTimeStamp:5 toEndingTimeStamp:7 byAmount:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent5AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:9 note:64 velocity:127 duration:1 channel:0]; + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, expectedEvent5AfterMove]; + NSArray *eventsAfterMoving = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + + XCTAssertGreaterThanOrEqual(self.defaultTrack.length, expectedEvent5AfterMove.timeStamp, @"Moving last event in track didn't properly update its length."); + } - (void)testMovingEventBackwards { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track moveEventsFromStartingTimeStamp:4 toEndingTimeStamp:4 byAmount:-2]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); - MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[event1, expectedEvent3AfterMove, event2, event4]; - XCTAssertEqualObjects(track.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack moveEventsFromStartingTimeStamp:4 toEndingTimeStamp:4 byAmount:-2]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving an event in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, expectedEvent3AfterMove, event2, event4]; + XCTAssertEqualObjects(self.defaultTrack.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } - (void)testMovingEventsInARangeBackwards { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:8 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:10 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track moveEventsFromStartingTimeStamp:5.5 toEndingTimeStamp:8.5 byAmount:-4]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); - MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent4AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; - - // Use sets, because order of events with the same timestamp is (acceptably) unpredictable - NSArray *expectedNewEvents = @[event1, expectedEvent3AfterMove, expectedEvent4AfterMove, event2, event5]; - NSArray *eventsAfterMoving = track.events; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack moveEventsFromStartingTimeStamp:5.5 toEndingTimeStamp:8.5 byAmount:-4]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Moving events in MIKMIDITrack did not produce a KVO notification."); + MIKMIDIEvent *expectedEvent3AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; + + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable + NSArray *expectedNewEvents = @[event1, expectedEvent3AfterMove, expectedEvent4AfterMove, event2, event5]; + NSArray *eventsAfterMoving = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); + } #pragma mark - Clearing Events - (void)testClearingSingleEvent { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event3, event4, event5]; - NSArray *eventsAfterMoving = track.events; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event3, event4, event5]; + NSArray *eventsAfterMoving = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } - (void)testClearingMultipleEventsAtSameTimestamp { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event4, event5]; - NSArray *eventsAfterMoving = track.events; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack clearEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event4, event5]; + NSArray *eventsAfterMoving = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } - (void)testClearingEventsInARange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track clearEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event2, event5]; - NSArray *eventsAfterMoving = track.events; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack clearEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event5]; + NSArray *eventsAfterMoving = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } - (void)testClearingEventsInAWiderRange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track clearEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event2, event5]; - NSArray *eventsAfterMoving = track.events; - XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack clearEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Clearing events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event5]; + NSArray *eventsAfterMoving = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMoving, expectedNewEvents, @"Clearing an event in MIKMIDITrack failed."); + } #pragma mark - Cutting Events - (void)testCuttingSingleEvent { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [track cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event2, event4, event5]; - NSArray *eventsAfterCutting = track.events; - XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event4, event5]; + NSArray *eventsAfterCutting = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } - (void)testCuttingSingleEventInWiderRange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [track cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:3.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent4AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[event1, event2, expectedEvent4AfterCut, expectedEvent5AfterCut]; - NSArray *eventsAfterCutting = track.events; - XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:3.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent4AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, expectedEvent4AfterCut, expectedEvent5AfterCut]; + NSArray *eventsAfterCutting = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } - (void)testCuttingMultipleEventsAtSameTimestamp { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [track cutEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event4, event5]; - NSArray *eventsAfterCutting = track.events; - XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack cutEventsFromStartingTimeStamp:2 toEndingTimeStamp:2]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event4, event5]; + NSArray *eventsAfterCutting = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCutting, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } - (void)testCuttingEventsInARange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [track cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; - NSArray *eventsAfterCut = track.events; - XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack cutEventsFromStartingTimeStamp:3 toEndingTimeStamp:4]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; + NSArray *eventsAfterCut = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); + } - (void)testCuttingEventsInAWiderRange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - MIKMIDITrack *track = [sequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; NSArray *allEvents = @[event1, event2, event3, event4, event5]; - [track addEvents:allEvents]; + [self.defaultTrack addEvents:allEvents]; self.eventsChangeNotificationReceived = NO; - [track addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [track addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - // Move event 2 to timestamp 5 - [track cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:64 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; - NSArray *eventsAfterCut = track.events; - XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); - } - [track removeObserver:self forKeyPath:@"events"]; - [track removeObserver:self forKeyPath:@"notes"]; + + // Move event 2 to timestamp 5 + [self.defaultTrack cutEventsFromStartingTimeStamp:2.5 toEndingTimeStamp:4.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Cutting events in MIKMIDITrack did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent5AfterCut = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, expectedEvent5AfterCut]; + NSArray *eventsAfterCut = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCut, expectedNewEvents, @"Cutting an event in MIKMIDITrack failed."); } #pragma mark - Copying Events - (void)testCopyingSingleEvent { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -528,33 +441,27 @@ - (void)testCopyingSingleEvent MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[event1, event2, event4, event5]]; + [self.defaultTrack addEvents:@[event1, event2, event4, event5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; - NSArray *eventsAfterCopy = destTrack.events; - XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; + NSArray *eventsAfterCopy = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } - (void)testCopyingSingleEventInWiderRange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -562,35 +469,29 @@ - (void)testCopyingSingleEventInWiderRange MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[event1, event2, event4, event5]]; + [self.defaultTrack addEvents:@[event1, event2, event4, event5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 andInsertAtTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent4AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent5AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:64 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[event1, event2, event3, expectedEvent4AfterCopy, expectedEvent5AfterCopy]; - NSArray *eventsAfterCopy = destTrack.events; - XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 andInsertAtTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent4AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5AfterCopy = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[event1, event2, event3, expectedEvent4AfterCopy, expectedEvent5AfterCopy]; + NSArray *eventsAfterCopy = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } - (void)testCopyingMultipleEvents { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -598,37 +499,31 @@ - (void)testCopyingMultipleEvents MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + [self.defaultTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 andInsertAtTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent6 = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:64 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, expectedEvent5, expectedEvent6]; - NSArray *eventsAfterCopy = destTrack.events; - XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 andInsertAtTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent6 = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, expectedEvent5, expectedEvent6]; + NSArray *eventsAfterCopy = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } - (void)testCopyingMultipleEventsAtSameTimestamp { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -636,35 +531,28 @@ - (void)testCopyingMultipleEventsAtSameTimestamp MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + [self.defaultTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; - NSArray *eventsAfterCopy = destTrack.events; - XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterCopy = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } - (void)testCopyingMultipleEventsInAWiderRange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -672,39 +560,33 @@ - (void)testCopyingMultipleEventsInAWiderRange MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + [self.defaultTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 andInsertAtTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:63 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent6 = [MIKMIDINoteEvent noteEventWithTimeStamp:8 note:64 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, expectedEvent5, expectedEvent6]; - NSArray *eventsAfterCopy = destTrack.events; - XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 andInsertAtTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:7 note:63 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent6 = [MIKMIDINoteEvent noteEventWithTimeStamp:8 note:64 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, expectedEvent5, expectedEvent6]; + NSArray *eventsAfterCopy = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); + } #pragma mark - Merging Events - (void)testMergingSingleEvent { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -712,33 +594,27 @@ - (void)testMergingSingleEvent MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:4 note:63 velocity:127 duration:1 channel:0]; event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[event1, event2, event4, event5]]; + [self.defaultTrack addEvents:@[event1, event2, event4, event5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; - NSArray *eventsAfterMerge = destTrack.events; - XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; + NSArray *eventsAfterMerge = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } - (void)testMergingSingleEventInWiderRange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -746,33 +622,27 @@ - (void)testMergingSingleEventInWiderRange MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; event4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[event1, event2, event4, event5]]; + [self.defaultTrack addEvents:@[event1, event2, event4, event5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 atTimeStamp:3]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); - - NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; - NSArray *eventsAfterMerge = destTrack.events; - XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:3.5 atTimeStamp:3]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + NSArray *expectedNewEvents = @[event1, event2, event3, event4, event5]; + NSArray *eventsAfterMerge = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } - (void)testMergingMultipleEvents { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -780,35 +650,29 @@ - (void)testMergingMultipleEvents MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + [self.defaultTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 atTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; - NSArray *eventsAfterMerge = destTrack.events; - XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:4 atTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterMerge = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } - (void)testMergingMultipleEventsAtSameTimestamp { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -816,35 +680,29 @@ - (void)testMergingMultipleEventsAtSameTimestamp MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + [self.defaultTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; - NSArray *eventsAfterMerge = destTrack.events; - XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterMerge = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } - (void)testMergingMultipleEventsInAWiderRange { - MIKMIDISequence *sequence = [MIKMIDISequence sequence]; - - MIKMIDITrack *sourceTrack = [sequence addTrack]; + MIKMIDITrack *sourceTrack = [self.defaultSequence addTrack]; MIKMIDIEvent *event1 = [MIKMIDINoteEvent noteEventWithTimeStamp:0.5 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *event3 = [MIKMIDINoteEvent noteEventWithTimeStamp:3 note:62 velocity:127 duration:1 channel:0]; @@ -852,28 +710,24 @@ - (void)testMergingMultipleEventsInAWiderRange MIKMIDIEvent *event5 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:64 velocity:127 duration:1 channel:0]; [sourceTrack addEvents:@[event1, event2, event3, event4, event5]]; - MIKMIDITrack *destTrack = [sequence addTrack]; + MIKMIDIEvent *destEvent1 = [MIKMIDINoteEvent noteEventWithTimeStamp:1 note:60 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent2 = [MIKMIDINoteEvent noteEventWithTimeStamp:2 note:61 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:63 velocity:127 duration:1 channel:0]; MIKMIDIEvent *destEvent5 = [MIKMIDINoteEvent noteEventWithTimeStamp:6 note:64 velocity:127 duration:1 channel:0]; - [destTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; + [self.defaultTrack addEvents:@[destEvent1, destEvent2, destEvent4, destEvent5]]; self.eventsChangeNotificationReceived = NO; - [destTrack addObserver:self forKeyPath:@"events" options:0 context:NULL]; - [destTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; - { - [destTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 atTimeStamp:2.5]; - XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); - - MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; - MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; - NSArray *eventsAfterMerge = destTrack.events; - XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); - } - [destTrack removeObserver:self forKeyPath:@"events"]; - [destTrack removeObserver:self forKeyPath:@"notes"]; + + [self.defaultTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:2.5 toTimeStamp:4.5 atTimeStamp:2.5]; + XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + + MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; + MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:3.5 note:63 velocity:127 duration:1 channel:0]; + NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; + NSArray *eventsAfterMerge = self.defaultTrack.events; + XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); + } #pragma mark - (KVO Test Helper) From 0a201d2ed0b96e43e4a2ae1d11087cb224f6db6e Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 11 Mar 2015 11:27:03 -0600 Subject: [PATCH 102/284] Issue #35: Added tests for KVO compliance of other appropriate MIKMIDITrack properties. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 139 +++++++++++++++++++- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 3bb53c97..fca2cc64 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -17,6 +17,7 @@ @interface MIKMIDITrackTests : XCTestCase @property (nonatomic, strong) MIKMIDISequence *defaultSequence; @property (nonatomic, strong) MIKMIDITrack *defaultTrack; +@property (copy) NSString *receivedKVONotificationKeyPath; @end @@ -33,7 +34,11 @@ - (void)setUp [self.defaultTrack addObserver:self forKeyPath:@"notes" options:0 context:NULL]; } -- (void)tearDown { +- (void)tearDown +{ + self.receivedKVONotificationKeyPath = nil; + self.eventsChangeNotificationReceived = NO; + self.notesChangeNotificationReceived = NO; [self.defaultTrack removeObserver:self forKeyPath:@"events"]; [self.defaultTrack removeObserver:self forKeyPath:@"notes"]; @@ -730,17 +735,145 @@ - (void)testMergingMultipleEventsInAWiderRange } +#pragma mark - Other Properties + +- (void)testSettingNumberOfLoops +{ + [self.defaultTrack addObserver:self forKeyPath:@"numberOfLoops" options:0 context:NULL]; + { + self.defaultTrack.numberOfLoops = 42; + XCTAssertEqual(self.defaultTrack.numberOfLoops, 42, @"Setting numberOfLoops failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"numberOfLoops", @"Setting numberOfLoops did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"numberOfLoops"]; +} + +- (void)testSettingLoopDuration +{ + [self.defaultTrack addObserver:self forKeyPath:@"loopDuration" options:0 context:NULL]; + { + self.defaultTrack.loopDuration = 42; + XCTAssertEqual(self.defaultTrack.loopDuration, 42, @"Setting loopDuration failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"loopDuration", @"Setting loopDuration did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"loopDuration"]; +} + +- (void)testSettingLoopInfo +{ + [self.defaultTrack addObserver:self forKeyPath:@"loopInfo" options:0 context:NULL]; + { + MusicTrackLoopInfo info = { + .loopDuration = 42, + .numberOfLoops = 27, + }; + self.defaultTrack.loopInfo = info; + XCTAssertEqual(self.defaultTrack.loopInfo.loopDuration, info.loopDuration, @"Setting loopInfo failed."); + XCTAssertEqual(self.defaultTrack.loopInfo.numberOfLoops, info.numberOfLoops, @"Setting loopInfo failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"loopInfo", @"Setting loopInfo did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"loopInfo"]; +} + +- (void)testSettingOffset +{ + [self.defaultTrack addObserver:self forKeyPath:@"offset" options:0 context:NULL]; + { + self.defaultTrack.offset = 42; + XCTAssertEqual(self.defaultTrack.offset, 42, @"Setting offset failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"offset", @"Setting offset did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"offset"]; +} + +- (void)testSettingMuted +{ + [self.defaultTrack addObserver:self forKeyPath:@"muted" options:0 context:NULL]; + { + self.defaultTrack.muted = YES; + XCTAssertEqual(self.defaultTrack.muted, YES, @"Setting muted failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"muted", @"Setting muted did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"muted"]; +} + +- (void)testSettingSolo +{ + [self.defaultTrack addObserver:self forKeyPath:@"solo" options:0 context:NULL]; + { + self.defaultTrack.solo = YES; + XCTAssertEqual(self.defaultTrack.solo, YES, @"Setting solo failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"solo", @"Setting solo did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"solo"]; +} + +- (void)testSettingLength +{ + [self.defaultTrack addObserver:self forKeyPath:@"length" options:0 context:NULL]; + { + self.defaultTrack.length = 42; + XCTAssertEqual(self.defaultTrack.length, 42, @"Setting length failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"length", @"Setting length did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"length"]; +} + +- (void)testSettingLengthByAddingEvents +{ + [self.defaultTrack addObserver:self forKeyPath:@"length" options:0 context:NULL]; + { + MusicTimeStamp startingLength = self.defaultTrack.length; + MIKMIDINoteEvent *event = [MIKMIDINoteEvent noteEventWithTimeStamp:startingLength+127 note:60 velocity:127 duration:1 channel:0]; + [self.defaultTrack addEvent:event]; + XCTAssertGreaterThanOrEqual(self.defaultTrack.length, event.timeStamp, @"Setting length failed."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"length", @"Setting length did not produce a KVO notification."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"length"]; +} + +- (void)testDoesLoopKVO +{ + self.defaultTrack.loopDuration = 0; + XCTAssertFalse(self.defaultTrack.doesLoop, @"Setting loop duration to zero did not make doesLoop NO."); + [self.defaultTrack addObserver:self forKeyPath:@"doesLoop" options:0 context:NULL]; + { + self.defaultTrack.loopDuration = 42; + XCTAssertEqual(self.defaultTrack.loopDuration, 42, @"Setting loopDuration failed."); + XCTAssert(self.defaultTrack.doesLoop, @"Setting loop duration did not change doesLoop to YES."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"doesLoop", @"Setting loopDuration did not produce a KVO notification for doesLoop."); + + self.receivedKVONotificationKeyPath = nil; + + MusicTrackLoopInfo info = { + .loopDuration = 0, + .numberOfLoops = 1, + }; + self.defaultTrack.loopInfo = info; + XCTAssertEqual(self.defaultTrack.loopDuration, 0, @"Setting loopDuration via loop info failed."); + XCTAssertFalse(self.defaultTrack.doesLoop, @"Clearing loop duration via loop info did not change doesLoop to NO."); + XCTAssertEqualObjects(self.receivedKVONotificationKeyPath, @"doesLoop", @"Setting loop info did not produce a KVO notification for doesLoop."); + } + [self.defaultTrack removeObserver:self forKeyPath:@"doesLoop"]; +} + #pragma mark - (KVO Test Helper) - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([object isKindOfClass:[MIKMIDITrack class]] && [keyPath isEqualToString:@"events"]) { + if (![object isKindOfClass:[MIKMIDITrack class]]) return; + + if ([keyPath isEqualToString:@"events"]) { self.eventsChangeNotificationReceived = YES; + return; } - if ([object isKindOfClass:[MIKMIDITrack class]] && [keyPath isEqualToString:@"notes"]) { + if ([keyPath isEqualToString:@"notes"]) { self.notesChangeNotificationReceived = YES; + return; } + + self.receivedKVONotificationKeyPath = keyPath; } @end From 29384bfd5ffc299d2bbb6e7db085c0a18522e310 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 11 Mar 2015 11:27:29 -0600 Subject: [PATCH 103/284] Issue #35: MIKMIDITrack is now KVO compliant for all methods so-documented. --- Source/MIKMIDITrack.h | 24 ++++++++++++++++++------ Source/MIKMIDITrack.m | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index e62306ee..5bd7eeba 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -162,15 +162,15 @@ /** * An array of MIKMIDIEvent containing all of the MIDI events for the track, sorted by timestamp. - * - * This property can be observed using KVO. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic, copy) NSArray *events; /** * An array of MIKMIDINoteEvent containing all of the MIDI note events for the track, sorted by timestamp. * - * This property can be observed using KVO. + * This property can be observed using Key Value Observing. */ @property (nonatomic, readonly) NSArray *notes; @@ -181,13 +181,15 @@ /** * Whether the track is set to loop. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic, readonly) BOOL doesLoop; /** - * The number of times to play the designated portion of the music track. By default, a music track plays once. + * The number of times to play the designated portion of the music track. By default, a music track plays once. * - * This is a shortcut to the numberOfLoops member of the loopInfo property. + * This property can be observed using Key Value Observing. */ @property (nonatomic) SInt32 numberOfLoops; @@ -195,32 +197,42 @@ * The point in a MIDI track, measured in beats from the end of the MIDI track, at which to begin playback during looped playback. * That is, during looped playback, a MIDI track plays from (length – loopDuration) to length. * - * This is a shortcut to the loopDuration member of the loopInfo property. + * This property can be observed using Key Value Observing. */ @property (nonatomic) MusicTimeStamp loopDuration; /** * The loop info for the track. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic) MusicTrackLoopInfo loopInfo; /** * A MIDI track’s start time in terms of beat number. By default this value is 0. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic) MusicTimeStamp offset; /** * Whether or not the MIDI track is muted. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic, getter = isMuted) BOOL muted; /** * Whether or not the MIDI track is soloed. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic, getter = isSolo) BOOL solo; /** * The length of the MIDI track. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic) MusicTimeStamp length; diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index ad8f1346..5c4c307f 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -513,11 +513,21 @@ - (NSInteger)trackNumber return (NSInteger)trackNumber; } ++ (NSSet *)keyPathsForValuesAffectingDoesLoop +{ + return [NSSet setWithObjects:@"loopDuration", nil]; +} + - (BOOL)doesLoop { return self.loopDuration > 0; } ++ (NSSet *)keyPathsForValuesAffectingNumberOfLoops +{ + return [NSSet setWithObjects:@"loopInfo", nil]; +} + - (SInt32)numberOfLoops { return self.loopInfo.numberOfLoops; @@ -533,6 +543,11 @@ - (void)setNumberOfLoops:(SInt32)numberOfLoops } } ++ (NSSet *)keyPathsForValuesAffectingLoopDuration +{ + return [NSSet setWithObjects:@"loopInfo", nil]; +} + - (MusicTimeStamp)loopDuration { return self.loopInfo.loopDuration; @@ -610,6 +625,11 @@ - (void)setSolo:(BOOL)solo if (err) NSLog(@"MusicTrackSetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); } ++ (NSSet *)keyPathsForValuesAffectingLength +{ + return [NSSet setWithObjects:@"events", nil]; +} + - (MusicTimeStamp)length { MusicTimeStamp length = 0; From 598ef766430824c2d23e653e4776b9aec5a9c965 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 11 Mar 2015 11:40:06 -0600 Subject: [PATCH 104/284] Issue #35: Fixed use of newly-deprecated MIKMIDITrack methods through the codebase. --- Source/MIKMIDIPlayer.m | 6 +++--- Source/MIKMIDISequence.m | 18 ++++++++++-------- Source/MIKMIDISequencer.m | 12 ++---------- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/Source/MIKMIDIPlayer.m b/Source/MIKMIDIPlayer.m index a79de3a2..3eabf1b3 100644 --- a/Source/MIKMIDIPlayer.m +++ b/Source/MIKMIDIPlayer.m @@ -185,8 +185,8 @@ - (void)addClickTrackWhenNeededFromTimeStamp:(MusicTimeStamp)fromTimeStamp self.clickPlayer = [[MIKMIDIPlayer alloc] init]; self.clickPlayer->_isClickPlayer = YES; MIKMIDISequence *clickSequence = [MIKMIDISequence sequence]; - [clickSequence.tempoTrack insertMIDIEvents:[NSSet setWithArray:self.sequence.tempoEvents]]; - [clickSequence.tempoTrack insertMIDIEvents:[NSSet setWithArray:self.sequence.timeSignatureEvents]]; + [clickSequence.tempoTrack addEvents:self.sequence.tempoEvents]; + [clickSequence.tempoTrack addEvents:self.sequence.timeSignatureEvents]; self.clickPlayer.sequence = clickSequence; MIKMIDITrack *clickTrack = [clickSequence addTrack]; @@ -213,7 +213,7 @@ - (void)addClickTrackWhenNeededFromTimeStamp:(MusicTimeStamp)fromTimeStamp [clickEvents addObject:[MIKMIDINoteEvent noteEventWithTimeStamp:clickTimeStamp message:clickMessage]]; } - [clickTrack insertMIDIEvents:clickEvents]; + [clickTrack addEvents:[clickEvents allObjects]]; } #pragma mark - Properties diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index f43e59d1..14c72c60 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -257,15 +257,16 @@ - (NSArray *)tempoEvents - (BOOL)setOverallTempo:(Float64)bpm { NSArray *timeSignatureEvents = [self timeSignatureEvents]; - if (![self.tempoTrack clearAllEvents]) return NO; - if (timeSignatureEvents.count && ![self.tempoTrack insertMIDIEvents:[NSSet setWithArray:timeSignatureEvents]]) return NO; + [self.tempoTrack removeAllEvents]; + if ([self.tempoTrack.events count]) return NO; + [self.tempoTrack addEvents:timeSignatureEvents]; return [self setTempo:bpm atTimeStamp:0]; } - (BOOL)setTempo:(Float64)bpm atTimeStamp:(MusicTimeStamp)timeStamp { - MIKMIDITempoEvent *event = [MIKMIDITempoEvent tempoEventWithTimeStamp:timeStamp tempo:bpm]; - return [self.tempoTrack insertMIDIEvent:event]; + [self.tempoTrack addEvent:[MIKMIDITempoEvent tempoEventWithTimeStamp:timeStamp tempo:bpm]]; + return YES; } - (Float64)tempoAtTimeStamp:(MusicTimeStamp)timeStamp @@ -281,12 +282,12 @@ - (NSArray *)timeSignatureEvents return [self.tempoTrack eventsOfClass:[MIKMIDIMetaTimeSignatureEvent class] fromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; } - - (BOOL)setOverallTimeSignature:(MIKMIDITimeSignature)signature { NSArray *tempoEvents = [self tempoEvents]; - if (![self.tempoTrack clearAllEvents]) return NO; - if (tempoEvents.count && ![self.tempoTrack insertMIDIEvents:[NSSet setWithArray:tempoEvents]]) return NO; + [self.tempoTrack removeAllEvents]; + if ([self.tempoTrack.events count]) return NO; + [self.tempoTrack addEvents:tempoEvents]; return [self setTimeSignature:signature atTimeStamp:0]; } @@ -298,7 +299,8 @@ - (BOOL)setTimeSignature:(MIKMIDITimeSignature)signature atTimeStamp:(MusicTimeS event.denominator = signature.denominator; event.metronomePulse = self.tempoTrack.timeResolution; event.thirtySecondsPerQuarterNote = 8; - return [self.tempoTrack insertMIDIEvent:event]; + [self.tempoTrack addEvent:event]; + return YES; } - (MIKMIDITimeSignature)timeSignatureAtTimeStamp:(MusicTimeStamp)timeStamp diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 361538cd..afdb1d88 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -460,11 +460,7 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command event = [self pendingNoteEventWithNoteNumber:@(noteOffCommand.note) channel:noteOffCommand.channel releaseVelocity:noteOffCommand.velocity offTimeStamp:musicTimeStamp]; } - if (event) { - for (MIKMIDITrack *track in self.recordEnabledTracks) { - [track insertMIDIEvent:event]; - } - } + if (event) [self.recordEnabledTracks makeObjectsPerformSelector:@selector(addEvent:) withObject:event]; } - (void)recordAllPendingNoteEventsWithOffTimeStamp:(MusicTimeStamp)offTimeStamp @@ -481,11 +477,7 @@ - (void)recordAllPendingNoteEventsWithOffTimeStamp:(MusicTimeStamp)offTimeStamp } self.pendingRecordedNoteEvents = [NSMutableDictionary dictionary]; - if (events.count) { - for (MIKMIDITrack *track in self.recordEnabledTracks) { - [track insertMIDIEvents:events]; - } - } + if ([events count]) [self.recordEnabledTracks makeObjectsPerformSelector:@selector(addEvents:) withObject:events]; } - (MIKMIDINoteEvent *)pendingNoteEventWithNoteNumber:(NSNumber *)noteNumber channel:(UInt8)channel releaseVelocity:(UInt8)releaseVelocity offTimeStamp:(MusicTimeStamp)offTimeStamp From 9c57f8523460631e965c6699ea2e2b963ea4f85f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Mar 2015 17:01:54 -0600 Subject: [PATCH 105/284] Added -testMIDIFileReadPerformance test. --- .../MIKMIDI Tests/MIKMIDISequenceTests.m | 9 +++++ Framework/MIKMIDI Tests/Parallax-Loader.mid | Bin 0 -> 112150 bytes Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 +++ ...C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist | 24 +++++++++++++ .../Info.plist | 33 ++++++++++++++++++ 5 files changed, 70 insertions(+) create mode 100644 Framework/MIKMIDI Tests/Parallax-Loader.mid create mode 100644 Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist create mode 100644 Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index acff558d..d0226106 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -45,4 +45,13 @@ - (void)testMIDIFileRead XCTAssertEqual([[sequence.tracks[2] events] count], 220); } +- (void)testMIDIFileReadPerformance +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"Parallax-Loader" withExtension:@"mid"]; + [self measureBlock:^{ + [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:NULL]; + }]; +} + @end diff --git a/Framework/MIKMIDI Tests/Parallax-Loader.mid b/Framework/MIKMIDI Tests/Parallax-Loader.mid new file mode 100644 index 0000000000000000000000000000000000000000..95021b5e48f8863d2a64d28efbbf875fee7c49cd GIT binary patch literal 112150 zcmeHQTXR%Ll1{1ns6hofpu$23sS)S`G$0#nt_GbWA==$;w-9$Q27@hZurb(xFWBQb z;hBw?hkcmY)yzidU(Af{iBQBm?C$Jt#Ku1EPvB{P!R{OTRn|GDOSc{b(in++DV4Ia z@>FJ3WmRTYRaej694DeCYM|BhRrTz>kMZ9@dYoB5cKzP<+qbWO)xD?tt-II9Z``BD zXT58bLHXm0*=%F`DtdgW(foHRllyKnJ${*N^;ZtieqB4->*Lg?zts9o=!^cZufyx>=lXSLYx~)d`t(IV*VoTWEuZ?j>)G|2 z&=>t*e+{aypX=APDSkdmVSMlpH_d?Z`K>_ZV`QA$J(^F{if}ev{!h7&6Z3bxyBwdc~wy-(j4$8FG;! z=NWQ_A*UHK#*k5lyv6C83@Rnv@zr*hG>SYXNb>`wVW!3H#0oPkS0zu zNNcwT`mUw72Zk>l>8@VC_u$r@?l-RA{_^@m`U7qc{7XOmAs79cE^olC0eU~8_u8o` zr2baUZ{mCt=i4~nLI1EVYTrgpv(tg7eISPSa;Z%%X0KBkLuOO35C?Nv(&>)cyQyh* zx;tv$9m9LM)TS1**C~x5vng1JgSjl}7QF|eV_j0LCEoQG%8bfANun-4x zS<>l>+PkP}cDgfa-xv)3t&A+sr1h=aK->HI;o)FNo#m8xA9i?5ZcT^5V4 zm8xA9i!aeqi?K&stl7L$=`7Z4Ua52zYc{V`I-;dSOC>g`V^=P+1-eqT%VP1hQnkxs z@g-VHw3KM6#Ao(X`L`BpHm_7VqNPMjiIx&AHO+VL`sk**6mx=-F1zxC{ zz7HY*qDuN*{z{%PEh!&gOi1CiWAoj#oe3S&PTQoko!lh1J#jm^ilLL>n|1=o_#y|9 zgNz3|#iq$&IHFN!9Umqd<#pczQEJy^2GX#36+2#Mha*2huC2=`9ggyV+jV)A^4JL^ zqzpcmcqZl2G5D4~_AU6LUB~$n1HNcU`P>pqDi7ibDMQ~@vG}0RReZad;r7JeMJ>GQ zoe-bdP7q00Gy9XIN%^GlA_vni+zV!(Cgb4+nHP2+j@@-Jiudi9M?=Ye8>Lnbb}J71 z0)vD%=P?|}yw$H~FQ5w3kL?SlKkq)@wg(1n=Ltf6aayaFm{ie88;(CYX-OG5Gtk&^ClWfge?bcL zLC2wQzMIfD-%YBReIVdm#n4ZRj6IkXnSM-)OgobzgP-ta;J|0T%y(^(<<~Y9?R4;8 zXgNSWc1acggUCVrFuYh8Jtp%4t19G>I1_4q0GQKBsnkT}_1#CaihH(S%j z?;-vN_=`jiG9D7AAG}OrcU%9b$RU=4*xl@!5dVX9(TJTD$9cuxwDzXRLF51@ZR&>+ z|AU-=z=|p7KDrqz9%MYY<3aona{mJR zNnA#-auEN6_#edoz*lwT{fFB9sWjd#{s-|ti2p(CO?d|cM;$68?}6ASS>~04*qdT+ zy7s1w2N@6Uco6@C_#edofLS2#KUmk(bUD=Cusj+jZ&A}^)OR2malMY)el z=;PMV>kYkwoFb2Y{Uq?-4or%CS~>yG9@iZ105 zLwAw5NhvO1d|?dM8GTZk&c9fu>Ho2wo;jz}@_4?^?2~!RE12JY=xqtT0Zx&}oc=WM zt_3DVe#Mtg-~kYx-&M$BZRl<01rRCn;QzD0dp|HK@(U`RK%j`}j#|1Mp|?HskXpL; z0`ErPA+>a$ymSH&{w=h!v)-9KW&b((*CLQia|GJW4Es)A+xcHzydv z*rFg0x}Wo=1u61BN$JGtxP9l)zEx=Nx%mIH_J3H;V_h7qd4L<8`+hFIs83P#Fyj12)|rU5c0MOY!o20smtIF>%3J&dv5C zSlujgla2e}vDiJs$ zcCjzwXy}cEUN!X2h2E9W8xOtPoFWg%C>!WV7g{=TIqvDpb|k?1!BzpO?WHdR5BnOV zwwIo~G^KmV-5L!B-k!kgg6@7ci{cJ@4#6x3WvqDP?xqn3ip!x0O@m(Lvbk z{q>2O(l9%7)I)E>!IN$q?}J9NS2r6=^mte3?dBADd<9l=x}h`H3mV~A-{v7?`?Ejx zM>CdN{@^2s;$@yqNRbD}9|qpZz@*48dTHvZFrJS&sizpRw9y&E+$E4aT67Ejk$CKk zcLP%(weUxyJdEeDC=c_|=Iv9-KOOwwqjfxJ_i>6mIDlU@wRhtM%K99UVqE|RytV0) zHIv7ArE)4TvO#UgX|~Ly6f#Li(BeuX9gp+&O^Wd(qkb+k)b&CKL$w(&FTJIk!AnOu zo%zNKip>+;@L1xJB9C#6U2N(XJpqi)&^r`*XE;S37}%GeV0Rw!%cyh$r;OR#H4Lt> z!a?1=$0_pQ5&B$O2 z4R+G84m#RNuXoUqPCDE{hdL$@)aTBVAgmdn%QVbdKTw`3rZ}L35Z6Ho!or$+?a9K* z=>l1v(+le^e-eaE!}tuJjhW#?gaER9ASA`h_={ANrq{y3#4ws^a%XHG7PRrqR+F+33 z5Nr;o(;g0HQid-?hY|VDAbOM!a~wuRb;R^@9Aq{5W`^e(2YD{daH+|^Aa&F5={d+t zg&9OpwuU*R$U_Pbf+mDu8y*?SUx@ZYqDGc8WM&G_MhzF}kfDH>Ur(4rYAM_Yg|jh* zF;MWs90y=CNRh`kJ`M0CnkYvNW1!F$W>Q~KDO&&NTdSC&%Y5^el~yHc zC_`EsAg$#1^Jvuls4&z?VRs?m^y%}5*M%9xCiaCnq{!pDchHZcam%V`+4?YtNW}gy zgA{pSe3G`TI-_M}@QQwAP7FWDGbcF?)0zC7d~&IwEOl=Mi!7^jj?LFMO~A0@u!P>8 za~{OAwnSN6tBFSgU#rRSwVDiHtI5G=3x?b|J@>d@!a4UW^R;f>+FUI{Mjc^}1DY8Q zXqs_5ZfC@asG*1%(HUkqoS8$4Jj&k>avaVy`OeV5&oB=1T$!GM&7+fQO#=*=SQVymejG5zLrr{bZ1(zBe)HWVqDiS47>$ z?^R^NgW2!Z&l?_rz3*#Ag{x@>4?+B<^M{$vau~i6H?}w+%^<__)E6 z{K$|5iCc}f6*prNA7(%jBtFQ3B>vvWs&T^zNW4$B74O9)t~Y=rNL*_KN&J1c5#?yj z)tIp>h9pS5leHu-XVu#T5J+8$WpgoR?1CW)66YH&$#aGzNS%#kb0%vUJDpXhViIEn zs$l4(5ikCZ#F|PZS+)IR=t!(92%$_YWIk}U)X!ZKj7-{zwM(RayP&G z75#SqI2{?MW8*Y5{%b<$dOy(-qGLpd#%W}nj*e4+f&l*$;bGOIL@wgMbE^IF{tqf@RX^A2TdjVk)i+vwt<^)VzS8PT zt-fegpKJ9%tNU8r)9N#=KGo{3R(G`eq*dM4>SL`w((0C0H?{gus}Hogq1AY+dS9#e zw7RaZ(>(w0cLY%dP5dtuASGQL77Do!9D|R%f+3qt)qFbxNx-txjq+s#R61 zx3sEg^`=(kR#npK4Xuh=ozQATtK(V?Yc-@**s21p2DLh-)lscp*XoE?hqXGS)xlPE zK&$;)y{6TxTJ6(nuU4;U^|Dq2t!j@}yS3V-RY9wrTJ6wkyH@>L^|h*At$MWDrqx!h zy0zM(RbHzutvXxPX01B3+N4#xRvWcy)9NLyHfW_=RjXF(wOXf@uT_gyYqeUVmC~xY zRjt-)l~y^enzZt?%4(I-s!^+khTmEh$eOP82Sqha-0`->7xImc?$pj)gPsgf5JZgT^F_f=yvUit+s3bgs%OMlk}(D zdHSbf0rDNu(d6oQPfhre$ z81<+H%dv{!LJmEKV;|> zK{Kh#@A3H60&&C4*HO39m3}m?%PeqfBdT_#O^v^6>*Bj;E$Sw$#cj6VMH|fPM@e0L zcQ&VJvt<@?5Pqe9?JSV-Eb~|NmFQ<)1Z6#v^*7$D$bQ4_kz{{mZfMGWlK1?we-`@) zb|rM9*n?vK@|H>LeerLIKPU0FiT^6`^~wHO_Rr#vbv^Ckf0pbWBI7F*b8DWh`nGA^UL{mxxXOibL4!EoX?T?g50;l-88vh##jI1xU<*`VlT*j zNx2^?@f3-tNId0P<0;=#rIX4>?In6Xzp;~w=acim-%z!aO0V0i_q_h@pwe0X(Kp;@ z|B5P`sd(6lcWrL8U_$neVgrQ)Mi6_5>@s%{BEJjaKi5uD!oM2Jr2KlDu)Tbm#MsgD!Uwh@r=M=!tX_@ zY@l+%;kT2DM-zU}Q&m%Gr^7G0`$)p?IjU$X?r`|sOywg9zo)6(N~P@%zv!|<3BO}h zYNc|&!*2&w4_8{e$i#GB>aw0)u&Ro!!NpQAmMkIDn1prIQ+gu<=qLt0hL>*ly~?=m+emY9i&nV z9GDKj8>m`H_Sl-EM0AJ|6c12&4OKcEe%Di}*RtT3 z_ETjIl{Y#3uBT#e!tXw+Dk`-*{H~+YwuIkTsG_L2(c#ypa(BY-9x69u;5q!ZP$h3! z@GHBhv>F4?;kN~`jD%n4kkuG?7Mb-%!oJa0+o`mQs;v&cV4=gZ;1@BMS5alX!*7iR zzqE}iISc@Y-)1VdCH!Je<*4L4{DOtHgx@?>nyA>~@LOZSuVQRBp(7lA(TvuF-%V8V z&=C&5IjXKp_-&)2N0nxW-yBt1EDJs+Zz)UFW{2MbP|6e@ zO8L)CG?j~JvT&LH=fNON?i{Dy$nPcENmL-e(CZ@|2>b!$`>1c+@3ZjB#F*3{(e}{a zPJ}82uGJ6Bt+rN-Z|ZI_5PW1N{jOPM9 z)6dVMzr$^nBOm4~mQUv#^yijO5A`24OtEaKzYtdhWPqrDmnquaNBw6wGpF37UyQ8$ zg7k~b7nv_c3D2b`MUTn)63F^8$GR-@MfPL0{i^K8WS=bdi`Xyo*p++NlWSV;jfaZA zw~Y#B;?Q35_jVGSft=q)g_^O+knJgj+E&f#A5dfS+26Z|3TDH``1?RDUbbKo8v2Kb zjUCPprqqlnT5Hd7Vnds8%1(PuQlZwlTzWR8P&47v_OQ9_?|mEGrG_fkde^inHCORn zW3DC^>)so#(aQMa=~t$;Q@jd&V!c`ki1jMgt5~n%9d&nKwM7S>=h|{3-qBkIEfw0tdy8|{WAly>k}WW zYX!)Bk@;eML~^#ruBY|KgUpxu@{#pL)|cmCeGw~3tfXltY-LtH2eUHqtU4!qrC+39 zYC-dwFHuf(&wX-C=8Mc1nJ@F4Ad>YZkwERZQ5w6H8$*spoku<|nDr`Fl2}P%CG|%0 zXan)}iLXz5<8mK4o&}EALhd6cR+!uqH@ix?CoU)L<)polfV``Kmp$YpK^7kgjq{X`GU{eFu?-s3@^R!=C_C~I`OEl;U(euCA@kRchv!EB@al%H;g@jg zy%dz-m^Xrw{LHt2pk|rQ%vXRC{CY3{Bsk{HpCoVI_DS;QzG_0>yx{}oEU)*>dUIG_ zSBQ5G>9bVvJIslxnl#^agKc!)3V8GLcIpEXI}Vx8`nlU-j;MDZ`nOtaIUSU$4ro8}LkLvC|N zM#Op*>s8(clvl{as~}zlIY}ZXN#rC+q6g;QFNs${yb9t~cxJDHc&!r&h}T-Y*6}ao z%FRT%nOM64A-`bDd#zMY%6^Q`M$Yn=%6?4tW6!)F!_Ti{qS=>K5bxMY*(nvN$wE(V zO8tD*WP#L_`Zhah&x1{=pZSx0s8I&iH^3t5qg7Mt>)a%cWH=Lc&6IkWnZ&Qy!L^TH z=>c9Zj`!vprqow0sO3$mFPjaafnfEN`l4wPXW9A)l|Ii*@^9J!@j;9E>3MD&(C;@- zCa*)G#=WM=!j`pD>a#`=L*Tn{N`1N(M4G15-PPQ{&gLm~$FmJ=nNpuLOhSvK4YWTX zkG7$O+%^!ny>_zyD9*rv#>cBo#eM>fkGx6zx*h-W9?0BknCvqWul>Xw|0sKAO5I#D z2|jldr1s&ewBeB52idgY{*<~wlN<(u_{J543iS6^q2WM$FUt)#st>ZDDRteS(PmV- zrp#xYhC}%8<|YeUA-k)P9r-L&i0?{^6)I%+PBWwhsa=NDjF8{XqzyD@NASI!gc_I7 zGhINx2y_Dag(llWkn{OQOAP0`AgOa}fdDa`T|J}MA+Iyuj9!P-PB%;z3|pvrYOSS- z+GDFNO;kFGj_Ly9C=kg{dL2@&u1SRlNxg;P*aJybvIcERy-9XhLM~;+!Yc!-WCkrH z2*fwCGaBemACVAp(Vr~jLEwZL%n;^C&NdKYI-W_G-T~R>}UEPZE0g2n-+(SHV~Hc@;3>PZ~kV0d_Du{0{hGPKP zy@EM!g#0qGWaD88Ne!qO!xEMDteVkq5ZH}gGQMR9c^4~gKhn@F3NW)oBA!V-vci2k7lQdItgsbK@Ei zfn3^F*GdhKfo3#t#;}B#G=^gy1X^Payq=YVRl=+s)?wW40HPm{+m>9~ zK!|j0CT*Z!*a8`?iAS%(a5O5n*{EC~u4Z8tI!!;ZFjuwA=v@dl*F2+lL7)jjHp3FK r^>{qv5_h5~A+Idvc}-D5WEl)&3`8?5A+ko#4ogU_K`WxiFVp`6MwOSb literal 0 HcmV?d00001 diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 1ad810b5..334d1bba 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -246,6 +246,7 @@ 9DBEBD681AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD691AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; + 9DCDDB501AB2363C00F8347E /* Parallax-Loader.mid in Resources */ = {isa = PBXBuildFile; fileRef = 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; @@ -420,6 +421,7 @@ 9DBEBD5C1AAA27D100E59734 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = SOURCE_ROOT; }; 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureEvent.h; sourceTree = ""; }; 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureEvent.m; sourceTree = ""; }; + 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = "Parallax-Loader.mid"; sourceTree = ""; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeEvent.h; sourceTree = ""; }; @@ -504,6 +506,7 @@ isa = PBXGroup; children = ( 9D4DF14E1AAB57C90065F004 /* bach.mid */, + 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */, ); name = Resources; sourceTree = ""; @@ -1036,6 +1039,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9DCDDB501AB2363C00F8347E /* Parallax-Loader.mid in Resources */, 9D4DF14F1AAB57C90065F004 /* bach.mid in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist new file mode 100644 index 00000000..22a7751a --- /dev/null +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist @@ -0,0 +1,24 @@ + + + + + classNames + + MIKMIDISequenceTests + + testMIDIFileReadPerformance + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 1.43 + baselineIntegrationDisplayName + Local Baseline + maxPercentRelativeStandardDeviation + 30 + + + + + + diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist new file mode 100644 index 00000000..f0f3f4ac --- /dev/null +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/Info.plist @@ -0,0 +1,33 @@ + + + + + runDestinationsByUUID + + C1C9286E-763A-4210-B3E7-DF2205A6AA20 + + localComputer + + busSpeedInMHz + 100 + cpuCount + 1 + cpuKind + Intel Core i7 + cpuSpeedInMHz + 3400 + logicalCPUCoresPerPackage + 8 + modelCode + iMac13,2 + physicalCPUCoresPerPackage + 4 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + x86_64 + + + + From 0f0a0dd3bf33afa6e9954d12e705bf33a7af8854 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Mar 2015 17:04:24 -0600 Subject: [PATCH 106/284] Improved performance of loading an MIKMIDISequence from a file by about 10x (Parallax-Loader.mid). --- ...C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist | 4 +- Source/MIKMIDIEvent.m | 151 +++++++++++------- 2 files changed, 99 insertions(+), 56 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist index 22a7751a..0055c744 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist @@ -11,11 +11,11 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 1.43 + 1.38 baselineIntegrationDisplayName Local Baseline maxPercentRelativeStandardDeviation - 30 + 10 diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index dbdb7305..01d05b45 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -18,15 +18,6 @@ @implementation MIKMIDIEvent -+ (void)registerSubclass:(Class)subclass; -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - registeredMIKMIDIEventSubclasses = [[NSMutableSet alloc] init]; - }); - [registeredMIKMIDIEventSubclasses addObject:subclass]; -} - + (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type { return [[self supportedMIDIEventTypes] containsObject:@(type)]; } + (NSArray *)supportedMIDIEventTypes { return @[]; } + (Class)immutableCounterpartClass; { return [MIKMIDIEvent class]; } @@ -47,7 +38,7 @@ + (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(Musi - (id)init { MIKMIDIEventType eventType = (MIKMIDIEventType)[[[[self class] supportedMIDIEventTypes] firstObject] unsignedIntegerValue]; - return [self initWithTimeStamp:0 midiEventType:eventType data:nil]; + return [self initWithTimeStamp:0 midiEventType:eventType data:nil]; } - (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(NSData *)data @@ -65,7 +56,7 @@ - (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMI if (self) { _timeStamp = timeStamp; _eventType = eventType; - + if (!data) data = [[self class] initialData]; _internalData = [NSMutableData dataWithData:data]; } @@ -79,18 +70,20 @@ - (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMI - (NSString *)additionalEventDescription { - return @""; + return @""; } - (NSString *)description { - NSString *additionalDescription = [self additionalEventDescription]; - if ([additionalDescription length] > 0) { - additionalDescription = [NSString stringWithFormat:@"%@ ", additionalDescription]; - } - return [NSString stringWithFormat:@"%@ Timestamp: %f Type: %u, %@", [super description], self.timeStamp, (unsigned int)self.eventType, additionalDescription]; + NSString *additionalDescription = [self additionalEventDescription]; + if ([additionalDescription length] > 0) { + additionalDescription = [NSString stringWithFormat:@"%@ ", additionalDescription]; + } + return [NSString stringWithFormat:@"%@ Timestamp: %f Type: %u, %@", [super description], self.timeStamp, (unsigned int)self.eventType, additionalDescription]; } +#pragma mark - Equality + - (BOOL)isEqual:(id)object { if (object == self) return YES; @@ -98,50 +91,98 @@ - (BOOL)isEqual:(id)object MIKMIDIEvent *otherEvent = (MIKMIDIEvent *)object; if (otherEvent.eventType != self.eventType) return NO; - return self.timeStamp == otherEvent.timeStamp && [self.data isEqualToData:otherEvent.data]; + return (self.timeStamp == otherEvent.timeStamp && [self.internalData isEqualToData:otherEvent.internalData]); } - (NSUInteger)hash { - return (NSUInteger)(self.timeStamp + [self.data hash]); + MusicTimeStamp timestamp = self.timeStamp; + if (timestamp == 0) return [self.data hash]; + return (NSUInteger)(timestamp * [self.data hash]); } #pragma mark - Private -+ (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType andData:(NSData *)data +#pragma mark Subclass Management + ++ (void)registerSubclass:(Class)subclass; +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + registeredMIKMIDIEventSubclasses = [[NSMutableSet alloc] init]; + }); + [registeredMIKMIDIEventSubclasses addObject:subclass]; + [self cacheSubclassesByEvent]; +} + ++ (void)unregisterSubclass:(Class)subclass; +{ + [registeredMIKMIDIEventSubclasses removeObject:subclass]; + [self cacheSubclassesByEvent]; +} + ++ (NSMutableDictionary *)subclassesByEventCache { - NSDictionary *channelEventTypeToMIDITypeMap = @{@(MIKMIDIChannelEventTypePolyphonicKeyPressure) : @(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage), - @(MIKMIDIChannelEventTypeControlChange) : @(MIKMIDIEventTypeMIDIControlChangeMessage), - @(MIKMIDIChannelEventTypeProgramChange) : @(MIKMIDIEventTypeMIDIProgramChangeMessage), - @(MIKMIDIChannelEventTypeChannelPressure) : @(MIKMIDIEventTypeMIDIChannelPressureMessage), - @(MIKMIDIChannelEventTypePitchBendChange) : @(MIKMIDIEventTypeMIDIPitchBendChangeMessage)}; + static NSMutableDictionary *subclassesByEventCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ subclassesByEventCache = [[NSMutableDictionary alloc] init]; }); + return subclassesByEventCache; +} + ++ (void)cacheSubclassesByEvent +{ + [[self subclassesByEventCache] removeAllObjects]; - NSDictionary *metaTypeToMIDITypeMap = @{@(MIKMIDIMetaEventTypeSequenceNumber) : @(MIKMIDIEventTypeMetaSequence), - @(MIKMIDIMetaEventTypeTextEvent) : @(MIKMIDIEventTypeMetaText), - @(MIKMIDIMetaEventTypeCopyrightNotice) : @(MIKMIDIEventTypeMetaCopyright), - @(MIKMIDIMetaEventTypeTrackSequenceName) : @(MIKMIDIEventTypeMetaTrackSequenceName), - @(MIKMIDIMetaEventTypeInstrumentName) : @(MIKMIDIEventTypeMetaInstrumentName), - @(MIKMIDIMetaEventTypeLyricText) : @(MIKMIDIEventTypeMetaLyricText), - @(MIKMIDIMetaEventTypeMarkerText) : @(MIKMIDIEventTypeMetaMarkerText), - @(MIKMIDIMetaEventTypeCuePoint) : @(MIKMIDIEventTypeMetaCuePoint), - @(MIKMIDIMetaEventTypeMIDIChannelPrefix) : @(MIKMIDIEventTypeMetaMIDIChannelPrefix), - @(MIKMIDIMetaEventTypeEndOfTrack) : @(MIKMIDIEventTypeMetaEndOfTrack), - @(MIKMIDIMetaEventTypeTempoSetting) : @(MIKMIDIEventTypeMetaTempoSetting), - @(MIKMIDIMetaEventTypeSMPTEOffset) : @(MIKMIDIEventTypeMetaSMPTEOffset), - @(MIKMIDIMetaEventTypeTimeSignature) : @(MIKMIDIEventTypeMetaTimeSignature), - @(MIKMIDIMetaEventTypeKeySignature) : @(MIKMIDIEventTypeMetaKeySignature), - @(MIKMIDIMetaEventTypeSequencerSpecificEvent) : @(MIKMIDIEventTypeMetaSequenceSpecificEvent),}; + // Regenerate cache + for (Class eachSubclass in registeredMIKMIDIEventSubclasses) { + for (NSNumber *eventType in [eachSubclass supportedMIDIEventTypes]) { + [[self subclassesByEventCache] setObject:eachSubclass forKey:eventType]; + } + } +} + ++ (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType andData:(NSData *)data +{ + static NSDictionary *channelEventTypeToMIDITypeMap = nil; + static NSDictionary *metaTypeToMIDITypeMap = nil; + static NSDictionary *musicEventToMIDITypeMap = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + channelEventTypeToMIDITypeMap = @{@(MIKMIDIChannelEventTypePolyphonicKeyPressure) : @(MIKMIDIEventTypeMIDIPolyphonicKeyPressureMessage), + @(MIKMIDIChannelEventTypeControlChange) : @(MIKMIDIEventTypeMIDIControlChangeMessage), + @(MIKMIDIChannelEventTypeProgramChange) : @(MIKMIDIEventTypeMIDIProgramChangeMessage), + @(MIKMIDIChannelEventTypeChannelPressure) : @(MIKMIDIEventTypeMIDIChannelPressureMessage), + @(MIKMIDIChannelEventTypePitchBendChange) : @(MIKMIDIEventTypeMIDIPitchBendChangeMessage)}; + + metaTypeToMIDITypeMap = @{@(MIKMIDIMetaEventTypeSequenceNumber) : @(MIKMIDIEventTypeMetaSequence), + @(MIKMIDIMetaEventTypeTextEvent) : @(MIKMIDIEventTypeMetaText), + @(MIKMIDIMetaEventTypeCopyrightNotice) : @(MIKMIDIEventTypeMetaCopyright), + @(MIKMIDIMetaEventTypeTrackSequenceName) : @(MIKMIDIEventTypeMetaTrackSequenceName), + @(MIKMIDIMetaEventTypeInstrumentName) : @(MIKMIDIEventTypeMetaInstrumentName), + @(MIKMIDIMetaEventTypeLyricText) : @(MIKMIDIEventTypeMetaLyricText), + @(MIKMIDIMetaEventTypeMarkerText) : @(MIKMIDIEventTypeMetaMarkerText), + @(MIKMIDIMetaEventTypeCuePoint) : @(MIKMIDIEventTypeMetaCuePoint), + @(MIKMIDIMetaEventTypeMIDIChannelPrefix) : @(MIKMIDIEventTypeMetaMIDIChannelPrefix), + @(MIKMIDIMetaEventTypeEndOfTrack) : @(MIKMIDIEventTypeMetaEndOfTrack), + @(MIKMIDIMetaEventTypeTempoSetting) : @(MIKMIDIEventTypeMetaTempoSetting), + @(MIKMIDIMetaEventTypeSMPTEOffset) : @(MIKMIDIEventTypeMetaSMPTEOffset), + @(MIKMIDIMetaEventTypeTimeSignature) : @(MIKMIDIEventTypeMetaTimeSignature), + @(MIKMIDIMetaEventTypeKeySignature) : @(MIKMIDIEventTypeMetaKeySignature), + @(MIKMIDIMetaEventTypeSequencerSpecificEvent) : @(MIKMIDIEventTypeMetaSequenceSpecificEvent),}; + + musicEventToMIDITypeMap = @{@(kMusicEventType_NULL) : @(MIKMIDIEventTypeNULL), + @(kMusicEventType_ExtendedNote) : @(MIKMIDIEventTypeExtendedNote), + @(kMusicEventType_ExtendedTempo) : @(MIKMIDIEventTypeExtendedTempo), + @(kMusicEventType_User) : @(MIKMIDIEventTypeUser), + @(kMusicEventType_Meta) : @(MIKMIDIEventTypeMeta), + @(kMusicEventType_MIDINoteMessage) : @(MIKMIDIEventTypeMIDINoteMessage), + @(kMusicEventType_MIDIChannelMessage) : @(MIKMIDIEventTypeMIDIChannelMessage), + @(kMusicEventType_MIDIRawData) : @(MIKMIDIEventTypeMIDIRawData), + @(kMusicEventType_Parameter) : @(MIKMIDIEventTypeParameter), + @(kMusicEventType_AUPreset) : @(MIKMIDIEventTypeAUPreset),}; + + }); - NSDictionary *musicEventToMIDITypeMap = @{@(kMusicEventType_NULL) : @(MIKMIDIEventTypeNULL), - @(kMusicEventType_ExtendedNote) : @(MIKMIDIEventTypeExtendedNote), - @(kMusicEventType_ExtendedTempo) : @(MIKMIDIEventTypeExtendedTempo), - @(kMusicEventType_User) : @(MIKMIDIEventTypeUser), - @(kMusicEventType_Meta) : @(MIKMIDIEventTypeMeta), - @(kMusicEventType_MIDINoteMessage) : @(MIKMIDIEventTypeMIDINoteMessage), - @(kMusicEventType_MIDIChannelMessage) : @(MIKMIDIEventTypeMIDIChannelMessage), - @(kMusicEventType_MIDIRawData) : @(MIKMIDIEventTypeMIDIRawData), - @(kMusicEventType_Parameter) : @(MIKMIDIEventTypeParameter), - @(kMusicEventType_AUPreset) : @(MIKMIDIEventTypeAUPreset),}; if (musicEventType == kMusicEventType_Meta) { UInt8 metaEventType = *(UInt8 *)[data bytes]; return [metaTypeToMIDITypeMap[@(metaEventType)] unsignedIntegerValue]; @@ -155,7 +196,9 @@ + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType + (Class)subclassForEventType:(MIKMIDIEventType)eventType { - Class result = nil; + Class result = [[self subclassesByEventCache] objectForKey:@(eventType)]; + if (result) return result; + for (Class subclass in registeredMIKMIDIEventSubclasses) { if ([[subclass supportedMIDIEventTypes] containsObject:@(eventType)]) { result = subclass; @@ -167,7 +210,7 @@ + (Class)subclassForEventType:(MIKMIDIEventType)eventType + (Class)subclassForMusicEventType:(MusicEventType)eventType andData:(NSData *)data { - MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data]; + MIKMIDIEventType midiEventType = [[self class] mikEventTypeForMusicEventType:eventType andData:data]; return [self subclassForEventType:midiEventType]; } @@ -215,8 +258,8 @@ - (void)setData:(NSData *)data - (void)setTimeStamp:(MusicTimeStamp)timeStamp { - if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - _timeStamp = timeStamp; + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + _timeStamp = timeStamp; } @end From a70a1f05dceff57bdc43844f7a1fec5d673541ad Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Mar 2015 17:09:51 -0600 Subject: [PATCH 107/284] Fixed bug in tests. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index fca2cc64..94d0fcd9 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -547,12 +547,12 @@ - (void)testCopyingMultipleEventsAtSameTimestamp [self.defaultTrack copyEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 andInsertAtTimeStamp:2.5]; XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Copying events between MIKMIDITracks did not produce a KVO notification."); + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; - NSArray *eventsAfterCopy = self.defaultTrack.events; + NSSet *expectedNewEvents = [NSSet setWithArray:@[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]]; + NSSet *eventsAfterCopy = [NSSet setWithArray:self.defaultTrack.events]; XCTAssertEqualObjects(eventsAfterCopy, expectedNewEvents, @"Copying events between MIKMIDITracks failed."); - } - (void)testCopyingMultipleEventsInAWiderRange @@ -697,12 +697,12 @@ - (void)testMergingMultipleEventsAtSameTimestamp [self.defaultTrack mergeEventsFromMIDITrack:sourceTrack fromTimeStamp:3 toTimeStamp:3 atTimeStamp:2.5]; XCTAssertTrue(self.eventsChangeNotificationReceived && self.notesChangeNotificationReceived, @"Merging events between MIKMIDITracks did not produce a KVO notification."); + // Use sets, because order of events with the same timestamp is (acceptably) unpredictable MIKMIDIEvent *expectedEvent3 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:62 velocity:127 duration:1 channel:0]; MIKMIDIEvent *expectedEvent4 = [MIKMIDINoteEvent noteEventWithTimeStamp:2.5 note:63 velocity:127 duration:1 channel:0]; - NSArray *expectedNewEvents = @[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]; - NSArray *eventsAfterMerge = self.defaultTrack.events; + NSSet *expectedNewEvents = [NSSet setWithArray:@[destEvent1, destEvent2, expectedEvent3, expectedEvent4, destEvent4, destEvent5]]; + NSSet *eventsAfterMerge = [NSSet setWithArray:self.defaultTrack.events]; XCTAssertEqualObjects(eventsAfterMerge, expectedNewEvents, @"Merging events between MIKMIDITracks failed."); - } - (void)testMergingMultipleEventsInAWiderRange From b34e1e6a801371888c4712e9002b34744220ea3e Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 13 Mar 2015 11:13:25 -0600 Subject: [PATCH 108/284] Issue #68: Added regression test for -[MIKMIDISequencer builtinSynthesizerForTrack:] returning nil when it shouldn't. --- .../MIKMIDI Tests/MIKMIDISequencerTests.m | 48 +++++++++++++++++++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 ++ 2 files changed, 52 insertions(+) create mode 100644 Framework/MIKMIDI Tests/MIKMIDISequencerTests.m diff --git a/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m new file mode 100644 index 00000000..346ed788 --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDISequencerTests.m @@ -0,0 +1,48 @@ +// +// MIKMIDISequencerTests.m +// MIKMIDI +// +// Created by Andrew Madsen on 3/13/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import +#import + +@interface MIKMIDISequencerTests : XCTestCase + +@property (nonatomic, strong) MIKMIDISequencer *sequencer; + +@end + +@implementation MIKMIDISequencerTests + +- (void)setUp +{ + [super setUp]; + + self.sequencer = [MIKMIDISequencer sequencer]; +} + +- (void)tearDown +{ + [super tearDown]; +} + +- (void)testBuiltinSynthesizers +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"bach" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + + self.sequencer.sequence = sequence; + for (MIKMIDITrack *track in sequence.tracks) { + MIKMIDISynthesizer *synth = [self.sequencer builtinSynthesizerForTrack:track]; + XCTAssertNotNil(synth, @"-builtinSynthesizerForTrack: test failed, because it returned nil."); + } +} + +@end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 334d1bba..ad8fa2d8 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -247,6 +247,7 @@ 9DBEBD691AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DCDDB501AB2363C00F8347E /* Parallax-Loader.mid in Resources */ = {isa = PBXBuildFile; fileRef = 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */; }; + 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; @@ -422,6 +423,7 @@ 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureEvent.h; sourceTree = ""; }; 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureEvent.m; sourceTree = ""; }; 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = "Parallax-Loader.mid"; sourceTree = ""; }; + 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequencerTests.m; sourceTree = ""; }; 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 9DED4E201AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeEvent.h; sourceTree = ""; }; @@ -488,6 +490,7 @@ 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */, 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */, 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */, + 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */, 9D4DF13C1AAB57430065F004 /* Supporting Files */, 9D4DF1501AAB57CD0065F004 /* Resources */, ); @@ -1067,6 +1070,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */, 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */, 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */, 9D4DF1541AAB60490065F004 /* MIKMIDITrackTests.m in Sources */, From 4febc1b421d0f0c3f6ba9345da982b6c4f01a314 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 17 Mar 2015 10:26:48 -0600 Subject: [PATCH 109/284] Issue #67: Added tests and fixed MIKMIDISequence's KVO compliance for -tracks. --- .../MIKMIDI Tests/MIKMIDISequenceTests.m | 42 +++++++++++++- ...C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist | 2 +- Source/MIKMIDISequence.h | 32 ++++++----- Source/MIKMIDISequence.m | 55 +++++++++++++++---- 4 files changed, 103 insertions(+), 28 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index d0226106..ee206907 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -12,6 +12,9 @@ @interface MIKMIDISequenceTests : XCTestCase +@property (nonatomic, strong) MIKMIDISequence *sequence; +@property (nonatomic, strong) NSMutableSet *receivedNotificationKeyPaths; + @end @implementation MIKMIDISequenceTests @@ -19,12 +22,16 @@ @implementation MIKMIDISequenceTests - (void)setUp { [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. + + self.receivedNotificationKeyPaths = [NSMutableSet set]; + self.sequence = [MIKMIDISequence sequence]; + [self.sequence addObserver:self forKeyPath:@"tracks" options:0 context:NULL]; } - (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. + [self.sequence removeObserver:self forKeyPath:@"tracks"]; + [super tearDown]; } @@ -54,4 +61,35 @@ - (void)testMIDIFileReadPerformance }]; } +- (void)testKVOForAddingATrack +{ + XCTAssertNotNil(self.sequence); + + MIKMIDITrack *firstTrack = [self.sequence addTrack]; + XCTAssertNotNil(firstTrack, @"Creating an MIKMIDITrack failed."); + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"tracks"], @"KVO notification when adding a track not received."); +} + +- (void)testKVOForRemovingATrack +{ + MIKMIDITrack *firstTrack = [self.sequence addTrack]; + XCTAssertNotNil(firstTrack, @"Creating an MIKMIDITrack failed."); + MIKMIDITrack *secondTrack = [self.sequence addTrack]; + XCTAssertNotNil(secondTrack, @"Creating an MIKMIDITrack failed."); + + [self.receivedNotificationKeyPaths removeAllObjects]; + [self.sequence removeTrack:firstTrack]; + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"tracks"], @"KVO notification when removing a track not received."); + XCTAssertEqualObjects(self.sequence.tracks, @[secondTrack], @"Removing a track failed."); +} + +#pragma mark - KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (object == self.sequence) { + [self.receivedNotificationKeyPaths addObject:keyPath]; + } +} + @end diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist index 0055c744..7efa07a8 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist @@ -11,7 +11,7 @@ com.apple.XCTPerformanceMetric_WallClockTime baselineAverage - 1.38 + 0.14 baselineIntegrationDisplayName Local Baseline maxPercentRelativeStandardDeviation diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 53b66ceb..a9494c03 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -153,6 +153,8 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d */ - (BOOL)writeToURL:(NSURL *)fileURL error:(NSError **)error; +#pragma mark - Track Management + /** * Creates and adds a new MIDI track to the sequence. */ @@ -167,18 +169,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d */ - (BOOL)removeTrack:(MIKMIDITrack *)track; -/** - * A MusicTimeStamp that is less than the sequence's length, but is at an equivalent position in the looped sequence as loopedTimeStamp - * - * When the music sequence is being looped by an MIKMIDIPlayer, the time stamp of the player continuosly increases. This method can be - * used to find where in the MIDI sequence the looped playback is at. For example, in a sequence with a length of 16, - * calling this method with a loopedTimeStamp of 17 would return 1. - * - * @param loopedTimeStamp The time stamp that you would like an equivalent time stamp for. - * - * @return The MusicTimeStamp of the sequence that is in an equivalent position in the sequence as loopedTimeStamp. - */ -- (MusicTimeStamp)equivalentTimeStampForLoopedTimeStamp:(MusicTimeStamp)loopedTimeStamp; +#pragma mark - Tempo & Time Signature /** * Returns an array of MIKMIDIEvent from the tempo track. @@ -274,7 +265,22 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d */ - (MIKMIDITimeSignature)timeSignatureAtTimeStamp:(MusicTimeStamp)timeStamp; -// Properties +#pragma mark - Timing + +/** + * A MusicTimeStamp that is less than the sequence's length, but is at an equivalent position in the looped sequence as loopedTimeStamp + * + * When the music sequence is being looped by an MIKMIDIPlayer, the time stamp of the player continuosly increases. This method can be + * used to find where in the MIDI sequence the looped playback is at. For example, in a sequence with a length of 16, + * calling this method with a loopedTimeStamp of 17 would return 1. + * + * @param loopedTimeStamp The time stamp that you would like an equivalent time stamp for. + * + * @return The MusicTimeStamp of the sequence that is in an equivalent position in the sequence as loopedTimeStamp. + */ +- (MusicTimeStamp)equivalentTimeStampForLoopedTimeStamp:(MusicTimeStamp)loopedTimeStamp; + +#pragma mark - Properties /** * The tempo track for the sequence. Even in a new, empty sequence, diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 14c72c60..d7c2a66e 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -23,8 +23,8 @@ @interface MIKMIDISequence () @property (nonatomic) MusicSequence musicSequence; -@property (strong, nonatomic) MIKMIDITrack *tempoTrack; -@property (strong, nonatomic) NSArray *tracks; +@property (nonatomic, strong) MIKMIDITrack *tempoTrack; +@property (nonatomic, strong) NSMutableArray *internalTracks; @end @@ -155,7 +155,7 @@ - (instancetype)initWithMusicSequence:(MusicSequence)musicSequence error:(NSErro } [tracks addObject:[MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]]; } - self.tracks = tracks; + self.internalTracks = tracks; self.length = MIKMIDISequenceLongestTrackLength; } @@ -181,27 +181,22 @@ - (MIKMIDITrack *)addTrack } MIKMIDITrack *track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; - - if (track) { - NSMutableArray *tracks = [self.tracks mutableCopy]; - [tracks addObject:track]; - self.tracks = tracks; - } + [self addTracksObject:track]; return track; } - (BOOL)removeTrack:(MIKMIDITrack *)track { + if (!track) return NO; + OSStatus err = MusicSequenceDisposeTrack(self.musicSequence, track.musicTrack); if (err) { NSLog(@"MusicSequenceDisposeTrack() failed with error %d in %s.", err, __PRETTY_FUNCTION__); return NO; } - NSMutableArray *tracks = [self.tracks mutableCopy]; - [tracks removeObject:track]; - self.tracks = tracks; + [self removeTracksObject:track]; return YES; } @@ -324,6 +319,42 @@ - (NSString *)description #pragma mark - Properties ++ (NSSet *)keyPathsForValuesAffectingTracks +{ + return [NSSet setWithObjects:@"internalTracks", nil]; +} + +- (NSArray *)tracks +{ + return [self.internalTracks copy]; +} + +- (void)addTracksObject:(MIKMIDITrack *)track +{ + if (!track) return; + [self insertObject:track inTracksAtIndex:[self.internalTracks count]]; +} + +- (void)insertObject:(MIKMIDITrack *)track inTracksAtIndex:(NSUInteger)index +{ + if (!track) return; + [self.internalTracks insertObject:track atIndex:index]; +} + +- (void)removeTracksObject:(MIKMIDITrack *)track +{ + if (!track) return; + NSInteger index = [self.internalTracks indexOfObject:track]; + if (index == NSNotFound) return; + [self removeObjectFromInternalTracksAtIndex:index]; +} + +- (void)removeObjectFromInternalTracksAtIndex:(NSUInteger)index +{ + if (index >= [self.internalTracks count]) return; + [self.internalTracks removeObjectAtIndex:index]; +} + - (MusicTimeStamp)length { if (_length != MIKMIDISequenceLongestTrackLength) return _length; From 6952f45a05e2fcd0fc4ac531973708cc591c3078 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 20 Mar 2015 19:53:32 -0600 Subject: [PATCH 110/284] Fixed bug and added test for -[MIKMIDITrack notes] returning nil. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 14 +++++++++++++- Source/MIKMIDITrack.m | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 94d0fcd9..034f3d52 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -92,6 +92,19 @@ - (void)testBasicEventsAddRemove } +- (void)testLoadFromFile +{ + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSURL *testMIDIFileURL = [bundle URLForResource:@"bach" withExtension:@"mid"]; + NSError *error = nil; + MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:testMIDIFileURL convertMIDIChannelsToTracks:NO error:&error]; + XCTAssertNotNil(sequence); + XCTAssertEqual([sequence.tracks count], 3, @"Sequence loaded from file doesn't have expected number of tracks."); + + MIKMIDITrack *firstNotesTrack = sequence.tracks[1]; + XCTAssertTrue([firstNotesTrack.notes count] > 0, @"Notes track loaded from file is empty."); +} + #pragma mark - Moving Events - (void)testMovingSingleEvent @@ -111,7 +124,6 @@ - (void)testMovingSingleEvent MIKMIDIEvent *expectedEvent2AfterMove = [MIKMIDINoteEvent noteEventWithTimeStamp:5 note:61 velocity:127 duration:1 channel:0]; NSArray *expectedNewEvents = @[event1, event3, expectedEvent2AfterMove, event4]; XCTAssertEqualObjects(self.defaultTrack.events, expectedNewEvents, @"Moving an event in MIKMIDITrack failed."); - } - (void)testMovingMultipleEventsAtSameTimestamp diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 5c4c307f..f3b473f5 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -498,7 +498,7 @@ - (NSArray *)notes NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *b) { return [(MIKMIDIEvent *)obj eventType] == MIKMIDIEventTypeMIDINoteMessage; }]; - return [self.sortedEventsCache filteredArrayUsingPredicate:predicate]; + return [self.events filteredArrayUsingPredicate:predicate]; } - (NSInteger)trackNumber From aa30fd500b608e8769834e1424daa26a3846184e Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 24 Mar 2015 16:28:14 -0500 Subject: [PATCH 111/284] Issue #71: Don't compare componentFlags and componentFlagsMask in -isUsingAppleSynth --- Source/MIKMIDISynthesizer.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 40dd653b..0e963409 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -214,8 +214,6 @@ - (BOOL)isUsingAppleSynth if (description.componentManufacturer != appleSynthDescription.componentManufacturer) return NO; if (description.componentType != appleSynthDescription.componentType) return NO; if (description.componentSubType != appleSynthDescription.componentSubType) return NO; - if (description.componentFlags != appleSynthDescription.componentFlags) return NO; - if (description.componentFlagsMask != appleSynthDescription.componentFlagsMask) return NO; return YES; } From a8d86d2eff351a556ba699f68c245e0aa1b95c75 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 24 Mar 2015 16:42:00 -0600 Subject: [PATCH 112/284] Issue #67: Added tests and fixed KVO compliance for MIKMIDISequence's durationInSeconds and length properties. --- .../Source/MIKAppDelegate.m | 2 + .../MIKMIDI Tests/MIKMIDISequenceTests.m | 34 +++ Source/MIKMIDISequence.h | 8 +- Source/MIKMIDISequence.m | 262 +++++++++++------- 4 files changed, 203 insertions(+), 103 deletions(-) diff --git a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m index 02d4a44e..33ebd423 100644 --- a/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m +++ b/Examples/MIDI Files Testbed/Source/MIKAppDelegate.m @@ -9,6 +9,8 @@ #import "MIKAppDelegate.h" #import "MIKMainWindowController.h" +@import MIKMIDI; + @implementation MIKAppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)notification diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index ee206907..ec7c0580 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -26,11 +26,17 @@ - (void)setUp self.receivedNotificationKeyPaths = [NSMutableSet set]; self.sequence = [MIKMIDISequence sequence]; [self.sequence addObserver:self forKeyPath:@"tracks" options:0 context:NULL]; + [self.sequence addObserver:self forKeyPath:@"durationInSeconds" options:0 context:NULL]; + [self.sequence addObserver:self forKeyPath:@"length" options:0 context:NULL]; } - (void)tearDown { [self.sequence removeObserver:self forKeyPath:@"tracks"]; + [self.sequence removeObserver:self forKeyPath:@"durationInSeconds"]; + [self.sequence removeObserver:self forKeyPath:@"length"]; + + self.sequence = nil; [super tearDown]; } @@ -83,6 +89,34 @@ - (void)testKVOForRemovingATrack XCTAssertEqualObjects(self.sequence.tracks, @[secondTrack], @"Removing a track failed."); } +- (void)testLength +{ + MIKMIDITrack *firstTrack = [self.sequence addTrack]; + XCTAssertNotNil(firstTrack, @"Creating an MIKMIDITrack failed."); + MIKMIDITrack *secondTrack = [self.sequence addTrack]; + XCTAssertNotNil(secondTrack, @"Creating an MIKMIDITrack failed."); + MIKMIDITrack *thirdTrack = [self.sequence addTrack]; + XCTAssertNotNil(thirdTrack, @"Creating an MIKMIDITrack failed."); + + self.sequence.length = MIKMIDISequenceLongestTrackLength; + + [self.receivedNotificationKeyPaths removeAllObjects]; + [thirdTrack addEvent:[MIKMIDINoteEvent noteEventWithTimeStamp:100 note:60 velocity:127 duration:1 channel:0]]; + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"length"], @"KVO notification for length failed after adding event to child track."); + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"durationInSeconds"], @"KVO notification for durationInSeconds failed after adding event to child track."); + + [self.receivedNotificationKeyPaths removeAllObjects]; + [thirdTrack removeAllEvents]; + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"length"], @"KVO notification for length failed after removing events from child track."); + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"durationInSeconds"], @"KVO notification for durationInSeconds failed after removing events from child track."); + + [thirdTrack addEvent:[MIKMIDINoteEvent noteEventWithTimeStamp:100 note:60 velocity:127 duration:1 channel:0]]; + [self.receivedNotificationKeyPaths removeAllObjects]; + [self.sequence removeTrack:thirdTrack]; + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"length"], @"KVO notification for length failed after removing longest child track."); + XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"durationInSeconds"], @"KVO notification for durationInSeconds failed after removing longest child track."); +} + #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index a9494c03..bdc452df 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -291,11 +291,13 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d /** * The MIDI music tracks for the sequence. An array of MIKMIDITrack instances. * Does not include the tempo track. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic, readonly) NSArray *tracks; /** - * The underlaying MusicSequence that backs the instance of MIKMIDISequence. + * The underlying MusicSequence that backs the instance of MIKMIDISequence. */ @property (nonatomic, readonly) MusicSequence musicSequence; @@ -303,11 +305,15 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * The length of the sequence as a MusicTimeStamp. * * Set to MIKMIDISequenceLongestTrackLength to make the length equal to the length of the longest track. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic) MusicTimeStamp length; /** * The duration of the sequence in seconds. + * + * This property can be observed using Key Value Observing. */ @property (nonatomic, readonly) Float64 durationInSeconds; diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index d7c2a66e..c307adf0 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -18,6 +18,8 @@ #error MIKMIDISequence.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target #endif +void * MIKMIDISequenceKVOContext = &MIKMIDISequenceKVOContext; + const MusicTimeStamp MIKMIDISequenceLongestTrackLength = -1; @interface MIKMIDISequence () @@ -25,6 +27,7 @@ @interface MIKMIDISequence () @property (nonatomic) MusicSequence musicSequence; @property (nonatomic, strong) MIKMIDITrack *tempoTrack; @property (nonatomic, strong) NSMutableArray *internalTracks; +@property (nonatomic) MusicTimeStamp lengthDefinedByTracks; @end @@ -35,24 +38,24 @@ @implementation MIKMIDISequence + (instancetype)sequence { - return [[self alloc] init]; + return [[self alloc] init]; } - (instancetype)init { - MusicSequence sequence; - OSStatus err = NewMusicSequence(&sequence); - if (err) { - NSLog(@"NewMusicSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return nil; - } - - return [self initWithMusicSequence:sequence error:NULL]; + MusicSequence sequence; + OSStatus err = NewMusicSequence(&sequence); + if (err) { + NSLog(@"NewMusicSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return nil; + } + + return [self initWithMusicSequence:sequence error:NULL]; } + (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; { - return [[self alloc] initWithFileAtURL:fileURL convertMIDIChannelsToTracks:NO error:error]; + return [[self alloc] initWithFileAtURL:fileURL convertMIDIChannelsToTracks:NO error:error]; } + (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error @@ -73,7 +76,7 @@ - (instancetype)initWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:( + (instancetype)sequenceWithData:(NSData *)data error:(NSError **)error { - return [[self alloc] initWithData:data convertMIDIChannelsToTracks:NO error:error]; + return [[self alloc] initWithData:data convertMIDIChannelsToTracks:NO error:error]; } + (instancetype)sequenceWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error @@ -90,22 +93,22 @@ - (instancetype)initWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)co { error = error ? error : &(NSError *__autoreleasing){ nil }; - MusicSequence sequence; - OSStatus err = NewMusicSequence(&sequence); - if (err) { - NSLog(@"NewMusicSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + MusicSequence sequence; + OSStatus err = NewMusicSequence(&sequence); + if (err) { + NSLog(@"NewMusicSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; - return nil; - } - + return nil; + } + MusicSequenceLoadFlags flags = convertMIDIChannelsToTracks ? kMusicSequenceLoadSMF_ChannelsToTracks : 0; - err = MusicSequenceFileLoadData(sequence, (__bridge CFDataRef)data, kMusicSequenceFile_MIDIType, flags); - if (err) { - NSLog(@"MusicSequenceFileLoadData() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + err = MusicSequenceFileLoadData(sequence, (__bridge CFDataRef)data, kMusicSequenceFile_MIDIType, flags); + if (err) { + NSLog(@"MusicSequenceFileLoadData() failed with error %d in %s.", err, __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; - return nil; - } - + return nil; + } + return [self initWithMusicSequence:sequence error:error]; } @@ -164,98 +167,99 @@ - (instancetype)initWithMusicSequence:(MusicSequence)musicSequence error:(NSErro - (void)dealloc { - self.callBackBlock = nil; - OSStatus err = DisposeMusicSequence(_musicSequence); - if (err) NSLog(@"DisposeMusicSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + self.internalTracks = nil; // Unregister for KVO + self.callBackBlock = nil; + OSStatus err = DisposeMusicSequence(_musicSequence); + if (err) NSLog(@"DisposeMusicSequence() failed with error %d in %s.", err, __PRETTY_FUNCTION__); } #pragma mark - Adding and Removing Tracks - (MIKMIDITrack *)addTrack { - MusicTrack musicTrack; - OSStatus err = MusicSequenceNewTrack(self.musicSequence, &musicTrack); - if (err) { - NSLog(@"MusicSequenceNewTrack() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return nil; - } - - MIKMIDITrack *track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; + MusicTrack musicTrack; + OSStatus err = MusicSequenceNewTrack(self.musicSequence, &musicTrack); + if (err) { + NSLog(@"MusicSequenceNewTrack() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return nil; + } + + MIKMIDITrack *track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; [self addTracksObject:track]; - - return track; + + return track; } - (BOOL)removeTrack:(MIKMIDITrack *)track { if (!track) return NO; - OSStatus err = MusicSequenceDisposeTrack(self.musicSequence, track.musicTrack); - if (err) { - NSLog(@"MusicSequenceDisposeTrack() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return NO; - } - + OSStatus err = MusicSequenceDisposeTrack(self.musicSequence, track.musicTrack); + if (err) { + NSLog(@"MusicSequenceDisposeTrack() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return NO; + } + [self removeTracksObject:track]; - - return YES; + + return YES; } #pragma mark - File Saving - (BOOL)writeToURL:(NSURL *)fileURL error:(NSError *__autoreleasing *)error { - return [self.dataValue writeToURL:fileURL options:NSDataWritingAtomic error:error]; + return [self.dataValue writeToURL:fileURL options:NSDataWritingAtomic error:error]; } #pragma mark - Callback static void MIKSequenceCallback(void *inClientData, MusicSequence inSequence, MusicTrack inTrack, MusicTimeStamp inEventTime, const MusicEventUserData *inEventData, MusicTimeStamp inStartSliceBeat, MusicTimeStamp inEndSliceBeat) { - MIKMIDISequence *self = (__bridge MIKMIDISequence *)inClientData; - if (!self.callBackBlock) return; - - UInt32 trackIndex; - OSStatus err = MusicSequenceGetTrackIndex(inSequence, inTrack, &trackIndex); - if (err) { - NSLog(@"MusicSequenceGetTrackIndex() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return; - } - - MIKMIDITrack *track = self.tracks[trackIndex]; - if (track && self.callBackBlock) { - self.callBackBlock(track, inEventTime, inEventData, inStartSliceBeat, inEndSliceBeat); - } + MIKMIDISequence *self = (__bridge MIKMIDISequence *)inClientData; + if (!self.callBackBlock) return; + + UInt32 trackIndex; + OSStatus err = MusicSequenceGetTrackIndex(inSequence, inTrack, &trackIndex); + if (err) { + NSLog(@"MusicSequenceGetTrackIndex() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return; + } + + MIKMIDITrack *track = self.tracks[trackIndex]; + if (track && self.callBackBlock) { + self.callBackBlock(track, inEventTime, inEventData, inStartSliceBeat, inEndSliceBeat); + } } #pragma mark - Looping - (MusicTimeStamp)equivalentTimeStampForLoopedTimeStamp:(MusicTimeStamp)loopedTimeStamp { - MusicTimeStamp length = self.length; - - if (loopedTimeStamp > length) { - NSInteger numTimesLooped = loopedTimeStamp / length; - loopedTimeStamp -= (length * numTimesLooped); - } - - return loopedTimeStamp; + MusicTimeStamp length = self.length; + + if (loopedTimeStamp > length) { + NSInteger numTimesLooped = loopedTimeStamp / length; + loopedTimeStamp -= (length * numTimesLooped); + } + + return loopedTimeStamp; } #pragma mark - Tempo - (NSArray *)tempoEvents { - return [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; + return [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:kMusicTimeStamp_EndOfTrack]; } - (BOOL)setOverallTempo:(Float64)bpm { NSArray *timeSignatureEvents = [self timeSignatureEvents]; [self.tempoTrack removeAllEvents]; - if ([self.tempoTrack.events count]) return NO; + if ([self.tempoTrack.events count]) return NO; [self.tempoTrack addEvents:timeSignatureEvents]; - return [self setTempo:bpm atTimeStamp:0]; + return [self setTempo:bpm atTimeStamp:0]; } - (BOOL)setTempo:(Float64)bpm atTimeStamp:(MusicTimeStamp)timeStamp @@ -314,11 +318,73 @@ - (MIKMIDITimeSignature)timeSignatureAtTimeStamp:(MusicTimeStamp)timeStamp - (NSString *)description { - return [NSString stringWithFormat:@"%@ tempo track: %@ tracks: %@", [super description], self.tempoTrack, self.tracks]; + return [NSString stringWithFormat:@"%@ tempo track: %@ tracks: %@", [super description], self.tempoTrack, self.tracks]; +} + +#pragma mark - KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context != MIKMIDISequenceKVOContext) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + + if ([self.internalTracks containsObject:object] && + ([keyPath isEqualToString:@"length"] || [keyPath isEqualToString:@"offset"])) { + [self updateLengthDefinedByTracks]; + } +} + +- (void)updateLengthDefinedByTracks +{ + MusicTimeStamp length = 0; + for (MIKMIDITrack *track in self.tracks) { + MusicTimeStamp trackLength = track.length + track.offset; + if (trackLength > length) length = trackLength; + } + + self.lengthDefinedByTracks = length; } #pragma mark - Properties +- (void)setInternalTracks:(NSMutableArray *)internalTracks +{ + if (internalTracks != _internalTracks) { + for (MIKMIDITrack *track in _internalTracks) { + [track removeObserver:self forKeyPath:@"length"]; + [track removeObserver:self forKeyPath:@"offset"]; + } + + _internalTracks = internalTracks; + + for (MIKMIDITrack *track in _internalTracks) { + [track addObserver:self forKeyPath:@"length" options:NSKeyValueObservingOptionInitial context:MIKMIDISequenceKVOContext]; + [track addObserver:self forKeyPath:@"offset" options:NSKeyValueObservingOptionInitial context:MIKMIDISequenceKVOContext]; + } + } +} + +- (void)insertObject:(MIKMIDITrack *)track inInternalTracksAtIndex:(NSUInteger)index +{ + if (!track) return; + + [self.internalTracks insertObject:track atIndex:index]; + [track addObserver:self forKeyPath:@"length" options:NSKeyValueObservingOptionInitial context:MIKMIDISequenceKVOContext]; + [track addObserver:self forKeyPath:@"offset" options:NSKeyValueObservingOptionInitial context:MIKMIDISequenceKVOContext]; +} + +- (void)removeObjectFromInternalTracksAtIndex:(NSUInteger)index +{ + if (index >= [self.internalTracks count]) return; + MIKMIDITrack *track = self.internalTracks[index]; + [self.internalTracks removeObjectAtIndex:index]; + [track removeObserver:self forKeyPath:@"length"]; + [track removeObserver:self forKeyPath:@"offset"]; + [self updateLengthDefinedByTracks]; +} + + (NSSet *)keyPathsForValuesAffectingTracks { return [NSSet setWithObjects:@"internalTracks", nil]; @@ -332,13 +398,7 @@ - (NSArray *)tracks - (void)addTracksObject:(MIKMIDITrack *)track { if (!track) return; - [self insertObject:track inTracksAtIndex:[self.internalTracks count]]; -} - -- (void)insertObject:(MIKMIDITrack *)track inTracksAtIndex:(NSUInteger)index -{ - if (!track) return; - [self.internalTracks insertObject:track atIndex:index]; + [self insertObject:track inInternalTracksAtIndex:[self.internalTracks count]]; } - (void)removeTracksObject:(MIKMIDITrack *)track @@ -349,43 +409,41 @@ - (void)removeTracksObject:(MIKMIDITrack *)track [self removeObjectFromInternalTracksAtIndex:index]; } -- (void)removeObjectFromInternalTracksAtIndex:(NSUInteger)index ++ (NSSet *)keyPathsForValuesAffectingLength { - if (index >= [self.internalTracks count]) return; - [self.internalTracks removeObjectAtIndex:index]; + return [NSSet setWithObjects:@"lengthDefinedByTracks", nil]; } - (MusicTimeStamp)length { - if (_length != MIKMIDISequenceLongestTrackLength) return _length; - - MusicTimeStamp length = 0; - for (MIKMIDITrack *track in self.tracks) { - MusicTimeStamp trackLength = track.length + track.offset; - if (trackLength > length) length = trackLength; - } + if (_length != MIKMIDISequenceLongestTrackLength) return _length; + + return self.lengthDefinedByTracks; +} - return length; ++ (NSSet *)keyPathsForValuesAffectingDurationInSeconds +{ + return [NSSet setWithObjects:@"length", nil]; } - (Float64)durationInSeconds { - Float64 duration = 0; - OSStatus err = MusicSequenceGetSecondsForBeats(self.musicSequence, self.length, &duration); - if (err) NSLog(@"MusicSequenceGetSecondsForBeats() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return duration; + Float64 duration = 0; + OSStatus err = MusicSequenceGetSecondsForBeats(self.musicSequence, self.length, &duration); + if (err) NSLog(@"MusicSequenceGetSecondsForBeats() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return duration; } - (NSData *)dataValue { - CFDataRef data; - OSStatus err = MusicSequenceFileCreateData(self.musicSequence, kMusicSequenceFile_MIDIType, 0, 0, &data); - if (err) { - NSLog(@"MusicSequenceFileCreateData() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return nil; - } - - return (__bridge_transfer NSData *)data; + CFDataRef data; + OSStatus err = MusicSequenceFileCreateData(self.musicSequence, kMusicSequenceFile_MIDIType, 0, 0, &data); + if (err) { + NSLog(@"MusicSequenceFileCreateData() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return nil; + } + + return (__bridge_transfer NSData *)data; } #pragma mark - Deprecated From dcb48f169740a040aa9b4872e0fe0cd6a81f3207 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 24 Mar 2015 19:02:55 -0600 Subject: [PATCH 113/284] Issue #67: MIDI Files Testbed - Sequence is now in a scrollview and uses autolayout so it can control its width. --- .../project.pbxproj | 21 ++++ .../Resources/Base.lproj/MainWindow.xib | 100 ++++++++++++------ .../MIKFlippedScrollContentContainerView.h | 13 +++ .../MIKFlippedScrollContentContainerView.m | 15 +++ .../Source/MIKMIDISequenceView.m | 14 ++- 5 files changed, 128 insertions(+), 35 deletions(-) create mode 100644 Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.h create mode 100644 Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.m diff --git a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index 33fbaefc..1a990c86 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 9D54C5021A97E5D20050BB43 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */; }; 9D54C50B1A97E6F10050BB43 /* MIKMainWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D54C5091A97E6F10050BB43 /* MIKMainWindowController.m */; }; 9D54C50F1A97E7070050BB43 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9D54C50D1A97E7070050BB43 /* MainWindow.xib */; }; + 9D891A831ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D891A821ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.m */; }; 9DAE7D37192FC1B800B25DD7 /* MIKMIDISequenceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */; }; 9DB2A5F7192D184D0047A3EB /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DB2A5F6192D184D0047A3EB /* Cocoa.framework */; }; 9DB2A62F192D18970047A3EB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB2A62B192D18970047A3EB /* main.m */; }; @@ -42,6 +43,13 @@ remoteGlobalIDString = 9D74EEA417A7129300BEE89F; remoteInfo = MIKMIDI; }; + 9D891A861ABD0B4000855BD6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9D4DF13A1AAB57430065F004; + remoteInfo = "MIKMIDI Tests"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -49,6 +57,8 @@ 9D54C5081A97E6F10050BB43 /* MIKMainWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMainWindowController.h; sourceTree = ""; }; 9D54C5091A97E6F10050BB43 /* MIKMainWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMainWindowController.m; sourceTree = ""; }; 9D54C50E1A97E7070050BB43 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainWindow.xib; sourceTree = ""; }; + 9D891A811ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKFlippedScrollContentContainerView.h; sourceTree = ""; }; + 9D891A821ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKFlippedScrollContentContainerView.m; sourceTree = ""; }; 9DAE7D35192FC1B800B25DD7 /* MIKMIDISequenceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequenceView.h; sourceTree = ""; }; 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequenceView.m; sourceTree = ""; }; 9DB2A5F3192D184D0047A3EB /* MIDI Files Testbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MIDI Files Testbed.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -89,6 +99,7 @@ children = ( 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */, 9D54C5011A97E5C90050BB43 /* MIKMIDI.framework */, + 9D891A871ABD0B4000855BD6 /* MIKMIDI Tests.xctest */, ); name = Products; sourceTree = ""; @@ -162,6 +173,8 @@ 9DAE7D36192FC1B800B25DD7 /* MIKMIDISequenceView.m */, 9D54C5081A97E6F10050BB43 /* MIKMainWindowController.h */, 9D54C5091A97E6F10050BB43 /* MIKMainWindowController.m */, + 9D891A811ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.h */, + 9D891A821ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.m */, ); path = Source; sourceTree = ""; @@ -249,6 +262,13 @@ remoteRef = 9D54C5001A97E5C90050BB43 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 9D891A871ABD0B4000855BD6 /* MIKMIDI Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "MIKMIDI Tests.xctest"; + remoteRef = 9D891A861ABD0B4000855BD6 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ @@ -272,6 +292,7 @@ buildActionMask = 2147483647; files = ( 9DB2A630192D18970047A3EB /* MIKAppDelegate.m in Sources */, + 9D891A831ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.m in Sources */, 9DAE7D37192FC1B800B25DD7 /* MIKMIDISequenceView.m in Sources */, 9D54C50B1A97E6F10050BB43 /* MIKMainWindowController.m in Sources */, 9DB2A62F192D18970047A3EB /* main.m in Sources */, diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index 33d55fe6..0a487a8f 100644 --- a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -1,13 +1,13 @@ - + - + - + @@ -17,7 +17,7 @@ - + @@ -25,7 +25,7 @@ - - - - - - - - - - - - - - + @@ -74,6 +61,9 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + - + diff --git a/Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.h b/Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.h new file mode 100644 index 00000000..dc7ce386 --- /dev/null +++ b/Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.h @@ -0,0 +1,13 @@ +// +// MIKFlippedScrollContentContainerView.h +// MIDI Files Testbed +// +// Created by Andrew Madsen on 3/20/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import + +@interface MIKFlippedScrollContentContainerView : NSView + +@end diff --git a/Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.m b/Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.m new file mode 100644 index 00000000..44f0786d --- /dev/null +++ b/Examples/MIDI Files Testbed/Source/MIKFlippedScrollContentContainerView.m @@ -0,0 +1,15 @@ +// +// MIKFlippedScrollContentContainerView.m +// MIDI Files Testbed +// +// Created by Andrew Madsen on 3/20/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKFlippedScrollContentContainerView.h" + +@implementation MIKFlippedScrollContentContainerView + +- (BOOL)isFlipped { return YES; } + +@end diff --git a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m index bd18b598..552e721d 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m +++ b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m @@ -26,6 +26,16 @@ - (instancetype)initWithFrame:(NSRect)frame return self; } +#pragma mark - Layout + +- (NSSize)intrinsicContentSize +{ + double maxLength = [[self.sequence valueForKeyPath:@"tracks.@max.length"] doubleValue]; + return NSMakeSize(maxLength * [self pixelsPerTick], 250.0); +} + +#pragma mark - Drawing + - (void)drawRect:(NSRect)dirtyRect { if (self.dragInProgress) { @@ -116,8 +126,7 @@ - (NSColor *)colorForTrackAtIndex:(NSInteger)index - (CGFloat)pixelsPerTick { - double maxLength = [[self.sequence valueForKeyPath:@"tracks.@max.length"] doubleValue]; - return NSWidth([self bounds]) / maxLength; + return 15.0; } - (CGFloat)pixelsPerNote @@ -132,6 +141,7 @@ - (void)setSequence:(MIKMIDISequence *)sequence if (sequence != _sequence) { _sequence = sequence; [self setNeedsDisplay:YES]; + [self invalidateIntrinsicContentSize]; } } From 8ee755a87dc5decaa15e52e1afac1ba35a149fac Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 24 Mar 2015 20:10:15 -0600 Subject: [PATCH 114/284] Issue #67: MIDI Files Testbed - MIKMIDISequenceView now draws notes as they are added. --- .../Resources/Base.lproj/MainWindow.xib | 2 +- .../Source/MIKMIDISequenceView.m | 59 ++++++++++++++++++- Source/MIKMIDISequence.m | 19 +----- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib index 0a487a8f..b098e60d 100644 --- a/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib +++ b/Examples/MIDI Files Testbed/Resources/Base.lproj/MainWindow.xib @@ -164,7 +164,7 @@ - + diff --git a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m index 552e721d..40e98410 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m +++ b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m @@ -9,6 +9,8 @@ #import "MIKMIDISequenceView.h" #import +void * MIKMIDISequenceViewKVOContext = &MIKMIDISequenceViewKVOContext; + @interface MIKMIDISequenceView () @property (nonatomic) BOOL dragInProgress; @@ -26,6 +28,11 @@ - (instancetype)initWithFrame:(NSRect)frame return self; } +- (void)dealloc +{ + self.sequence = nil; +} + #pragma mark - Layout - (NSSize)intrinsicContentSize @@ -134,14 +141,62 @@ - (CGFloat)pixelsPerNote return NSHeight([self bounds]) / 127.0; } +#pragma mark - KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context != MIKMIDISequenceViewKVOContext) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + + if ([keyPath isEqualToString:@"length"]) { + [self invalidateIntrinsicContentSize]; + } + + if ([keyPath isEqualToString:@"tracks"]) { + [self unregisterForKVOOnTracks:change[NSKeyValueChangeOldKey]]; + [self registerForKVOOnTracks:change[NSKeyValueChangeNewKey]]; + + [self setNeedsDisplay:YES]; + } + + if ([object isKindOfClass:[MIKMIDITrack class]]) { + [self invalidateIntrinsicContentSize]; + [self setNeedsDisplay:YES]; + } +} + +- (void)registerForKVOOnTracks:(NSArray *)tracks +{ + for (MIKMIDITrack *track in tracks) { + [track addObserver:self forKeyPath:@"events" options:0 context:MIKMIDISequenceViewKVOContext]; + } +} + +- (void)unregisterForKVOOnTracks:(NSArray *)tracks +{ + for (MIKMIDITrack *track in tracks) { + [track removeObserver:self forKeyPath:@"events"]; + } +} + #pragma mark - Properties - (void)setSequence:(MIKMIDISequence *)sequence { if (sequence != _sequence) { + + [_sequence removeObserver:self forKeyPath:@"length"]; + [_sequence removeObserver:self forKeyPath:@"tracks"]; + [self unregisterForKVOOnTracks:_sequence.tracks]; + _sequence = sequence; - [self setNeedsDisplay:YES]; - [self invalidateIntrinsicContentSize]; + + [_sequence addObserver:self forKeyPath:@"length" options:NSKeyValueObservingOptionInitial context:MIKMIDISequenceViewKVOContext]; + NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew; + [_sequence addObserver:self forKeyPath:@"tracks" options:options context:MIKMIDISequenceViewKVOContext]; + if (_sequence) [self registerForKVOOnTracks:_sequence.tracks]; } } diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index c307adf0..24785402 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -185,7 +185,7 @@ - (MIKMIDITrack *)addTrack } MIKMIDITrack *track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; - [self addTracksObject:track]; + [self insertObject:track inInternalTracksAtIndex:[self.internalTracks count]]; return track; } @@ -200,7 +200,8 @@ - (BOOL)removeTrack:(MIKMIDITrack *)track return NO; } - [self removeTracksObject:track]; + NSInteger index = [self.internalTracks indexOfObject:track]; + if (index != NSNotFound) [self removeObjectFromInternalTracksAtIndex:index]; return YES; } @@ -395,20 +396,6 @@ - (NSArray *)tracks return [self.internalTracks copy]; } -- (void)addTracksObject:(MIKMIDITrack *)track -{ - if (!track) return; - [self insertObject:track inInternalTracksAtIndex:[self.internalTracks count]]; -} - -- (void)removeTracksObject:(MIKMIDITrack *)track -{ - if (!track) return; - NSInteger index = [self.internalTracks indexOfObject:track]; - if (index == NSNotFound) return; - [self removeObjectFromInternalTracksAtIndex:index]; -} - + (NSSet *)keyPathsForValuesAffectingLength { return [NSSet setWithObjects:@"lengthDefinedByTracks", nil]; From 2e122f50e98e0e05bb4ecae7a7098345300c9aea Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 7 Apr 2015 22:10:36 -0600 Subject: [PATCH 115/284] Updated podspec version to 1.1.0. --- MIKMIDI.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIKMIDI.podspec b/MIKMIDI.podspec index 1ce14d46..1b7139e0 100644 --- a/MIKMIDI.podspec +++ b/MIKMIDI.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'MIKMIDI' - s.version = '1.0.0' + s.version = '1.1.0' s.summary = 'Library useful for programmers writing Objective-C or Swift OS X or iOS apps that use MIDI.' s.description = <<-DESC MIKMIDI is a library intended to simplify implementing Objective-C or Swift apps From 2351f1338de4fed843ba9d0ee704ba0d5a32399f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 14 Apr 2015 20:13:49 -0600 Subject: [PATCH 116/284] MIDI Files Testbed: Merge fixup. --- .../project.pbxproj | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index f8c694c4..5d98745b 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -7,8 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 9D1D5C071AC4901100F9C2BD /* MIKMIDI.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9D54C5021A97E5D20050BB43 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */; }; 9D54C50B1A97E6F10050BB43 /* MIKMainWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D54C5091A97E6F10050BB43 /* MIKMainWindowController.m */; }; 9D54C50F1A97E7070050BB43 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9D54C50D1A97E7070050BB43 /* MainWindow.xib */; }; 9D891A831ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D891A821ABD0B3F00855BD6 /* MIKFlippedScrollContentContainerView.m */; }; @@ -20,6 +18,8 @@ 9DB2A63B192D189E0047A3EB /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A634192D189E0047A3EB /* Credits.rtf */; }; 9DB2A63C192D189E0047A3EB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A636192D189E0047A3EB /* InfoPlist.strings */; }; 9DB2A63D192D189E0047A3EB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DB2A638192D189E0047A3EB /* Images.xcassets */; }; + 9DB97F091ADDFEE200130849 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */; }; + 9DB97F0A1ADDFEE400130849 /* MIKMIDI.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 9D54C4FF1A97E5C90050BB43 /* MIKMIDI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -37,13 +37,6 @@ remoteGlobalIDString = 9DAF8B061A7AFF1100F46528; remoteInfo = "MIKMIDI-iOS"; }; - 9D54C5031A97E5E90050BB43 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = 9D74EEA417A7129300BEE89F; - remoteInfo = MIKMIDI; - }; 9D891A861ABD0B4000855BD6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; @@ -51,6 +44,13 @@ remoteGlobalIDString = 9D4DF13A1AAB57430065F004; remoteInfo = "MIKMIDI Tests"; }; + 9DB97F071ADDFED000130849 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 9D74EEA417A7129300BEE89F; + remoteInfo = MIKMIDI; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -60,7 +60,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 9D1D5C071AC4901100F9C2BD /* (null) in Copy Frameworks */, + 9DB97F0A1ADDFEE400130849 /* MIKMIDI.framework in Copy Frameworks */, ); name = "Copy Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -101,8 +101,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9D54C5021A97E5D20050BB43 /* MIKMIDI.framework in Frameworks */, 9DB2A5F7192D184D0047A3EB /* Cocoa.framework in Frameworks */, + 9DB97F091ADDFEE200130849 /* MIKMIDI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -127,7 +127,6 @@ 9DB2A61A192D184D0047A3EB /* MIDI Files TestbedTests */, 9DB2A5F5192D184D0047A3EB /* Frameworks */, 9DB2A5F4192D184D0047A3EB /* Products */, - 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */, ); sourceTree = ""; }; @@ -142,6 +141,7 @@ 9DB2A5F5192D184D0047A3EB /* Frameworks */ = { isa = PBXGroup; children = ( + 9D54C4F91A97E5C90050BB43 /* MIKMIDI.xcodeproj */, 9DB2A5F6192D184D0047A3EB /* Cocoa.framework */, 9DB2A615192D184D0047A3EB /* XCTest.framework */, 9DB2A5F8192D184D0047A3EB /* Other Frameworks */, @@ -222,7 +222,7 @@ buildRules = ( ); dependencies = ( - 9D54C5041A97E5E90050BB43 /* PBXTargetDependency */, + 9DB97F081ADDFED000130849 /* PBXTargetDependency */, ); name = "MIDI Files Testbed"; productName = "MIDI Files Testbed"; @@ -318,10 +318,10 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 9D54C5041A97E5E90050BB43 /* PBXTargetDependency */ = { + 9DB97F081ADDFED000130849 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = MIKMIDI; - targetProxy = 9D54C5031A97E5E90050BB43 /* PBXContainerItemProxy */; + targetProxy = 9DB97F071ADDFED000130849 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ From d1afc5acf20795af2cb80e5a97f7bd886ce9cade Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 21 Apr 2015 16:59:51 -0600 Subject: [PATCH 117/284] Issue #60: Refactored available instruments API. Disabled for now on iOS. Work in progress. --- Source/MIKMIDIErrors.h | 12 +++ Source/MIKMIDISynthesizer.h | 12 +++ Source/MIKMIDISynthesizer.m | 146 +++++++++++++++++++------- Source/MIKMIDISynthesizerInstrument.h | 44 +++++--- Source/MIKMIDISynthesizerInstrument.m | 86 +++++++-------- 5 files changed, 207 insertions(+), 93 deletions(-) diff --git a/Source/MIKMIDIErrors.h b/Source/MIKMIDIErrors.h index 7f042a0f..e3747567 100644 --- a/Source/MIKMIDIErrors.h +++ b/Source/MIKMIDIErrors.h @@ -21,6 +21,10 @@ typedef NS_ENUM(NSInteger, MIKMIDIErrorCode) { * Unknown error. */ MIKMIDIUnknownErrorCode = 1, + /** + * Invalid argument error. + */ + MIKMIDIInvalidArgumentError, /** * An error occurred because the connection to a device was lost. */ @@ -43,6 +47,14 @@ typedef NS_ENUM(NSInteger, MIKMIDIErrorCode) { * because the event(s) could not be found in the track. */ MIKMIDITrackEventNotFoundErrorCode, + + /** + * An error occurred selecting an instrument on MIKMIDISynthesizer, + * because the synthesizer's underlying audio unit does not support + * instrument selection, or MIKMIDI doesn't know how to select its + * instruments. + */ + MIKMIDISynthesizerDoesNotSupportInstrumentSelectionError, }; /** diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 3696933f..9da49422 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -47,6 +47,18 @@ */ - (instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription NS_DESIGNATED_INITIALIZER; +/** + * This synthesizer's available instruments. An array of + * MIKMIDISynthesizerInstrument instances. + * + * Note that this method currently always returns an empty array + * on iOS. See https://github.com/mixedinkey-opensource/MIKMIDI/issues/76 + * + * Instruments returned by this property can be selected using + * -selectInstrument: + */ +@property (nonatomic, readonly) NSArray *availableInstruments; + /** * Changes the instrument/voice used by the synthesizer. * diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index ed9c576b..7877200d 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -9,6 +9,7 @@ #import "MIKMIDISynthesizer.h" #import "MIKMIDICommand.h" #import "MIKMIDISynthesizer_SubclassMethods.h" +#import "MIKMIDIErrors.h" @implementation MIKMIDISynthesizer @@ -34,13 +35,64 @@ - (void)dealloc #pragma mark - Public -- (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument; +- (NSArray *)availableInstruments { - if (!instrument) return NO; - if (!self.isUsingAppleSynth) return NO; +#if TARGET_OS_IPHONE + return @[]; +#else + + AudioUnit audioUnit = [self instrumentUnit]; + NSMutableArray *result = [NSMutableArray array]; + + UInt32 instrumentCount; + UInt32 instrumentCountSize = sizeof(instrumentCount); - MusicDeviceInstrumentID instrumentID = instrument.instrumentID; - return [self sendBankSelectAndProgramChangeForInstrumentID:instrumentID error:NULL]; + OSStatus err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentCount, kAudioUnitScope_Global, 0, &instrumentCount, &instrumentCountSize); + if (err) { + NSLog(@"AudioUnitGetProperty() (Instrument Count) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return @[]; + } + +#if !TARGET_OS_IPHONE + if (self.componentDescription.componentSubType == kAudioUnitSubType_DLSSynth) { + for (UInt32 i = 0; i < instrumentCount; i++) { + MusicDeviceInstrumentID instrumentID; + UInt32 idSize = sizeof(instrumentID); + err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentNumber, kAudioUnitScope_Global, i, &instrumentID, &idSize); + if (err) { + NSLog(@"AudioUnitGetProperty() (Instrument Number) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + continue; + } + + char cName[256]; + UInt32 cNameSize = sizeof(cName); + OSStatus err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentName, kAudioUnitScope_Global, instrumentID, &cName, &cNameSize); + if (err) { + NSLog(@"AudioUnitGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); + return nil; + } + + NSString *name = [NSString stringWithCString:cName encoding:NSASCIIStringEncoding]; + MIKMIDISynthesizerInstrument *instrument = [MIKMIDISynthesizerInstrument instrumentWithID:instrumentID name:name]; + if (instrument) [result addObject:instrument]; + } + } else if (self.componentDescription.componentSubType == kAudioUnitSubType_MIDISynth) +#endif + { + } + + return result; +#endif +} + +- (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument error:(NSError **)error +{ + error = error ? error : &(NSError *__autoreleasing){ nil }; + if (!instrument) { + *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDIInvalidArgumentError userInfo:nil]; + return NO; + } + return [self sendBankSelectAndProgramChangeForInstrumentID:instrument.instrumentID error:error]; } - (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error @@ -78,23 +130,23 @@ - (BOOL)loadSoundfontFromFileAtURL:(NSURL *)fileURL error:(NSError **)error return NO; } #else - FSRef fsRef; - err = FSPathMakeRef((const UInt8*)[[fileURL path] cStringUsingEncoding:NSUTF8StringEncoding], &fsRef, 0); - if (err != noErr) { - *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; - return NO; - } - - err = AudioUnitSetProperty(self.instrumentUnit, - kMusicDeviceProperty_SoundBankFSRef, - kAudioUnitScope_Global, 0, - &fsRef, sizeof(fsRef)); - if (err != noErr) { - *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; - return NO; - } - return YES; + FSRef fsRef; + err = FSPathMakeRef((const UInt8*)[[fileURL path] cStringUsingEncoding:NSUTF8StringEncoding], &fsRef, 0); + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; + } + + err = AudioUnitSetProperty(self.instrumentUnit, + kMusicDeviceProperty_SoundBankFSRef, + kAudioUnitScope_Global, 0, + &fsRef, sizeof(fsRef)); + if (err != noErr) { + *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + return NO; } + return YES; +} #endif } @@ -106,27 +158,40 @@ - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)i for (UInt8 channel = 0; channel < 16; channel++) { // http://lists.apple.com/archives/coreaudio-api/2002/Sep/msg00015.html - UInt8 bankSelectMSB = (instrumentID >> 16) & 0x7F; - UInt8 bankSelectLSB = (instrumentID >> 8) & 0x7F; - UInt8 programChange = instrumentID & 0x7F; - UInt32 bankSelectStatus = 0xB0 | channel; - UInt32 programChangeStatus = 0xC0 | channel; - OSStatus err = MusicDeviceMIDIEvent(self.instrumentUnit, bankSelectStatus, 0x00, bankSelectMSB, 0); - if (err) { - NSLog(@"MusicDeviceMIDIEvent() (MSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); - *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; - return NO; - } + CFURLRef loadedSoundfontURL = NULL; + UInt32 size = sizeof(loadedSoundfontURL); + OSStatus err = AudioUnitGetProperty(self.instrumentUnit, + kMusicDeviceProperty_SoundBankURL, + kAudioUnitScope_Global, + 0, + &loadedSoundfontURL, + &size); - err = MusicDeviceMIDIEvent(self.instrumentUnit, bankSelectStatus, 0x20, bankSelectLSB, 0); - if (err) { - NSLog(@"MusicDeviceMIDIEvent() (LSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); - *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; - return NO; + if (loadedSoundfontURL) { + + UInt32 bankSelectStatus = 0xB0 | channel; + + UInt8 bankSelectMSB = (instrumentID >> 16) & 0x7F; + err = MusicDeviceMIDIEvent(self.instrumentUnit, bankSelectStatus, 0x00, bankSelectMSB, 0); + if (err) { + NSLog(@"MusicDeviceMIDIEvent() (MSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; + return NO; + } + + UInt8 bankSelectLSB = (instrumentID >> 8) & 0x7F; + err = MusicDeviceMIDIEvent(self.instrumentUnit, bankSelectStatus, 0x20, bankSelectLSB, 0); + if (err) { + NSLog(@"MusicDeviceMIDIEvent() (LSB Bank Select) failed with error %d in %s.", err, __PRETTY_FUNCTION__); + *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; + return NO; + } } + UInt32 programChangeStatus = 0xC0 | channel; + UInt8 programChange = instrumentID & 0x7F; err = MusicDeviceMIDIEvent(self.instrumentUnit, programChangeStatus, programChange, 0, 0); if (err) { NSLog(@"MusicDeviceMIDIEvent() (Program Change) failed with error %d in %s.", err, __PRETTY_FUNCTION__); @@ -232,7 +297,7 @@ + (AudioComponentDescription)appleSynthComponentDescription instrumentcd.componentManufacturer = kAudioUnitManufacturer_Apple; instrumentcd.componentType = kAudioUnitType_MusicDevice; #if TARGET_OS_IPHONE - instrumentcd.componentSubType = kAudioUnitSubType_Sampler; + instrumentcd.componentSubType = kAudioUnitSubType_MIDISynth; #else instrumentcd.componentSubType = kAudioUnitSubType_DLSSynth; #endif @@ -263,4 +328,9 @@ + (NSSet *)keyPathsForValuesAffectingInstrument { return [NSSet setWithObjects:@ - (AudioUnit)instrument { return self.instrumentUnit; } - (void)setInstrument:(AudioUnit)instrument { self.instrumentUnit = instrument; } +- (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument +{ + return [self selectInstrument:instrument error:NULL]; +} + @end diff --git a/Source/MIKMIDISynthesizerInstrument.h b/Source/MIKMIDISynthesizerInstrument.h index 2c973712..dd9abb51 100644 --- a/Source/MIKMIDISynthesizerInstrument.h +++ b/Source/MIKMIDISynthesizerInstrument.h @@ -14,34 +14,52 @@ */ @interface MIKMIDISynthesizerInstrument : NSObject -/** - * An array of available MIKMIDISynthesizerInstruments for use - * with MIKMIDIEndpointSynthesizer. - * - * @return An NSArray containing MIKMIDISynthesizerInstrument instances. - */ -+ (NSArray *)availableInstruments; - /** * Creates and initializes an MIKMIDISynthesizerInstrument with the corresponding instrument ID. * * @param instrumentID The MusicDeviceInstrumentID for the desired MIKMIDISynthesizerInstrument + * @param name The human readable name of the instrument. * - * @return A MIKMIDISynthesizerInstrument with the matching instrument ID, or nil if no instrument was found. + * @return A MIKMIDISynthesizerInstrument instance with the matching instrument ID, or nil if no instrument was found. */ -+ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID; ++ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID name:(NSString *)name; /** * The human readable name of the receiver. e.g. "Piano 1". */ -@property (readonly, copy, nonatomic) NSString *name; +@property (nonatomic, copy, readonly) NSString *name; /** * The Core Audio supplied instrumentID for the receiver. */ -@property (readonly, nonatomic) MusicDeviceInstrumentID instrumentID; +@property (nonatomic, readonly) MusicDeviceInstrumentID instrumentID; @end // For backwards compatibility with applications written against MIKMIDI 1.0.x -@compatibility_alias MIKMIDIEndpointSynthesizerInstrument MIKMIDISynthesizerInstrument; \ No newline at end of file +@compatibility_alias MIKMIDIEndpointSynthesizerInstrument MIKMIDISynthesizerInstrument; + +@interface MIKMIDISynthesizerInstrument (Deprecated) + +/** + * @deprecated Use -[MIKMIDISynthesizer availableInstruments] instead. + * + * An array of available MIKMIDISynthesizerInstruments for use + * with MIKMIDIEndpointSynthesizer. + * + * @return An NSArray containing MIKMIDISynthesizerInstrument instances. + */ ++ (NSArray *)availableInstruments DEPRECATED_ATTRIBUTE; + +/** + * @deprecated Use +instrumentWithID:inInstrumentUnit: instead. + * + * Creates and initializes an MIKMIDISynthesizerInstrument with the corresponding instrument ID. + * + * @param instrumentID The MusicDeviceInstrumentID for the desired MIKMIDISynthesizerInstrument + * + * @return A MIKMIDISynthesizerInstrument with the matching instrument ID, or nil if no instrument was found. + */ ++ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID DEPRECATED_ATTRIBUTE; + +@end \ No newline at end of file diff --git a/Source/MIKMIDISynthesizerInstrument.m b/Source/MIKMIDISynthesizerInstrument.m index f687d11b..83646545 100644 --- a/Source/MIKMIDISynthesizerInstrument.m +++ b/Source/MIKMIDISynthesizerInstrument.m @@ -10,7 +10,46 @@ @implementation MIKMIDISynthesizerInstrument -+ (AudioUnit)instrumentUnit ++ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID name:(NSString *)name +{ + return [[self alloc] initWithName:name instrumentID:instrumentID]; +} + +- (instancetype)initWithName:(NSString *)name instrumentID:(MusicDeviceInstrumentID)instrumentID +{ + self = [super init]; + if (self) { + _name = name; + _instrumentID = instrumentID; + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ %@ (%@)", [super description], self.name, @(self.instrumentID)]; +} + +- (BOOL)isEqual:(id)object +{ + if (object == self) return YES; + if (![object isMemberOfClass:[self class]]) return NO; + if (!self.instrumentID == [object instrumentID]) return NO; + return [self.name isEqualToString:[object name]]; +} + +- (NSUInteger)hash +{ + return (NSUInteger)self.instrumentID; +} + +@end + +#pragma mark - Deprecated + +@implementation MIKMIDISynthesizerInstrument (Deprecated) + ++ (AudioUnit)defaultInstrumentUnit { static AudioUnit instrumentUnit = NULL; static dispatch_once_t onceToken; @@ -41,7 +80,7 @@ + (NSArray *)availableInstruments static NSArray *availableInstruments = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - AudioUnit audioUnit = [self instrumentUnit]; + AudioUnit audioUnit = [self defaultInstrumentUnit]; NSMutableArray *result = [NSMutableArray array]; UInt32 instrumentCount; @@ -72,46 +111,9 @@ + (NSArray *)availableInstruments return availableInstruments; } -+ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID; -{ - char cName[256]; - UInt32 cNameSize = sizeof(cName); - OSStatus err = AudioUnitGetProperty([self instrumentUnit], kMusicDeviceProperty_InstrumentName, kAudioUnitScope_Global, instrumentID, &cName, &cNameSize); - if (err) { - NSLog(@"AudioUnitGetProperty() failed with error %d in %s.", err, __PRETTY_FUNCTION__); - return nil; - } - - NSString *name = [NSString stringWithCString:cName encoding:NSASCIIStringEncoding]; - return [[self alloc] initWithName:name instrumentID:instrumentID]; -} - -- (instancetype)initWithName:(NSString *)name instrumentID:(MusicDeviceInstrumentID)instrumentID -{ - self = [super init]; - if (self) { - _name = name; - _instrumentID = instrumentID; - } - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"%@", self.name]; -} - -- (BOOL)isEqual:(id)object -{ - if (object == self) return YES; - if (![object isMemberOfClass:[self class]]) return NO; - if (!self.instrumentID == [object instrumentID]) return NO; - return [self.name isEqualToString:[object name]]; -} - -- (NSUInteger)hash ++ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID { - return (NSUInteger)self.instrumentID; + return [self instrumentWithID:instrumentID name:nil]; } -@end +@end \ No newline at end of file From 7b5779ffded88e6235588205b11cc0be0975a4cb Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 22 Apr 2015 09:13:23 -0600 Subject: [PATCH 118/284] Fixed use of 'private' reserved keyword in MIKMIDIEndpoint.h causing it to be unusable from Objective-C++. --- Source/MIKMIDIEndpoint.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIEndpoint.h b/Source/MIKMIDIEndpoint.h index 5cb8e64e..b32d98a9 100644 --- a/Source/MIKMIDIEndpoint.h +++ b/Source/MIKMIDIEndpoint.h @@ -24,6 +24,6 @@ /** * Whether or not the endpoint is private or hidden. See kMIDIPropertyPrivate in MIDIServices.h. */ -@property (nonatomic, readonly, getter=isPrivate) BOOL private; +@property (nonatomic, readonly) BOOL isPrivate; @end From 1abe40fb7568659bd93d7b2b2eb4d2ce8e1f2b6d Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 22 Apr 2015 15:27:49 -0600 Subject: [PATCH 119/284] Fixed build errors introduced in previous merge commit. --- Source/MIKMIDISynthesizer.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 51af16cf..21349cb5 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -49,7 +49,7 @@ - (NSArray *)availableInstruments OSStatus err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentCount, kAudioUnitScope_Global, 0, &instrumentCount, &instrumentCountSize); if (err) { - NSLog(@"AudioUnitGetProperty() (Instrument Count) failed with error %@ in %s."@(err), __PRETTY_FUNCTION__); + NSLog(@"AudioUnitGetProperty() (Instrument Count) failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); return @[]; } @@ -60,7 +60,7 @@ - (NSArray *)availableInstruments UInt32 idSize = sizeof(instrumentID); err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentNumber, kAudioUnitScope_Global, i, &instrumentID, &idSize); if (err) { - NSLog(@"AudioUnitGetProperty() (Instrument Number) failed with error %@ in %s."@(err), __PRETTY_FUNCTION__); + NSLog(@"AudioUnitGetProperty() (Instrument Number) failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); continue; } @@ -68,7 +68,7 @@ - (NSArray *)availableInstruments UInt32 cNameSize = sizeof(cName); OSStatus err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentName, kAudioUnitScope_Global, instrumentID, &cName, &cNameSize); if (err) { - NSLog(@"AudioUnitGetProperty() failed with error %@ in %s."@(err), __PRETTY_FUNCTION__); + NSLog(@"AudioUnitGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); return nil; } From 93f162563df184c9a98e2c624e8e87374a125d92 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 22 Apr 2015 16:49:42 -0600 Subject: [PATCH 120/284] Switched back to AUSampler on iOS in MIKMIDISynthesizer, as AUMIDISynth doesn't seem to work on device on (64-bit?) 8.3. --- Source/MIKMIDISynthesizer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 21349cb5..d5f69236 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -297,7 +297,7 @@ + (AudioComponentDescription)appleSynthComponentDescription instrumentcd.componentManufacturer = kAudioUnitManufacturer_Apple; instrumentcd.componentType = kAudioUnitType_MusicDevice; #if TARGET_OS_IPHONE - instrumentcd.componentSubType = kAudioUnitSubType_MIDISynth; + instrumentcd.componentSubType = kAudioUnitSubType_Sampler; #else instrumentcd.componentSubType = kAudioUnitSubType_DLSSynth; #endif From 062a4dc41dc7704852910db37d6982ed91e597f4 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 23 Apr 2015 15:41:47 -0500 Subject: [PATCH 121/284] Issue #78: Fixed note off behavior in MIKMIDISequencer. Added -[MIKMIDIClock midiTimeStampsPerMusicTimeStamp:]. --- Source/MIKMIDIClock.h | 15 +++++++++++++++ Source/MIKMIDIClock.m | 5 +++++ Source/MIKMIDISequencer.m | 10 +++++----- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index e608ffd4..001ef363 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -84,5 +84,20 @@ */ - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; +/** + * Converts the specified number of beats in the MusicTimeStamp into the + * corresponding number of MIDITimeStamps. + * + * @param musicTimeStamp The number of beats to convert into MIDITimeStamps. + * + * @return The number of MIDITimeStamps that will occur during the specified number of beats. + * + * @note For this method to return any meaningful values, you must first call + * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * + * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: + */ +- (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; + @end diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 7e3e22ba..a76cb111 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -52,6 +52,11 @@ - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp return (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; } +- (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp +{ + return musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp; +} + #pragma mark - Class Methods + (Float64)secondsPerMIDITimeStamp diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 2dfdfb9e..66f5123a 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -274,11 +274,12 @@ - (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationE NSArray *commands = nil; if (event.eventType == MIKMIDIEventTypeMIDINoteMessage) { - commands = [MIKMIDICommand commandsFromNoteEvent:(MIKMIDINoteEvent *)event clock:self.clock]; + NSArray *noteCommands = [MIKMIDICommand commandsFromNoteEvent:(MIKMIDINoteEvent *)event clock:self.clock]; + commands = @[ [noteCommands firstObject] ]; // note on // Add note off to pending note offs - MIKMIDINoteOffCommand *noteOff = [commands lastObject]; - MIDITimeStamp noteOffTimeStamp = noteOff.midiTimestamp + [self.clock midiTimeStampForMusicTimeStamp:self.playbackOffset]; + MIKMIDINoteOffCommand *noteOff = [noteCommands lastObject]; + MIDITimeStamp noteOffTimeStamp = noteOff.midiTimestamp + [self.clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; NSMutableArray *pendingNoteOffsAtTimeStamp = pendingNoteOffs[@(noteOffTimeStamp)]; if (!pendingNoteOffsAtTimeStamp) pendingNoteOffsAtTimeStamp = [NSMutableArray array]; NSNumber *timeStampNumber = @(noteOffTimeStamp); @@ -294,8 +295,7 @@ - (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationE NSMutableArray *adjustedCommands = [NSMutableArray array]; for (MIKMIDICommand *command in commands) { MIKMutableMIDICommand *scratch = [command mutableCopy]; - MusicTimeStamp musicTimestamp = [self.clock musicTimeStampForMIDITimeStamp:scratch.midiTimestamp]; - scratch.midiTimestamp = [self.clock midiTimeStampForMusicTimeStamp:(musicTimestamp + self.playbackOffset)]; + scratch.midiTimestamp += [self.clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; [adjustedCommands addObject:scratch]; } From bcbb8ddf09ee6db6de8b88191b641c9a74aba0e3 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 24 Apr 2015 08:34:36 -0600 Subject: [PATCH 122/284] Added missing error handing to -[MIKMIDISynthesizer sendBakSelectAndProgramChangeForInstrumentID:error:]. Silences analyzer warning. --- Source/MIKMIDISynthesizer.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index d5f69236..73753725 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -168,6 +168,11 @@ - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)i 0, &loadedSoundfontURL, &size); + if (err) { + NSLog(@"AudioUnitGetProperty() (kMusicDeviceProperty_SoundBankURL) failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; + return NO; + } if (loadedSoundfontURL) { From e4c9e1390edaccbce1f38ab642571df60b5374f0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 24 Apr 2015 09:15:32 -0600 Subject: [PATCH 123/284] Further improved error handing in -[MIKMIDISynthesizer sendBakSelectAndProgramChangeForInstrumentID:error:]. --- Source/MIKMIDISynthesizer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 73753725..45fb7053 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -168,7 +168,7 @@ - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)i 0, &loadedSoundfontURL, &size); - if (err) { + if (err && err != kAudioUnitErr_InvalidProperty) { NSLog(@"AudioUnitGetProperty() (kMusicDeviceProperty_SoundBankURL) failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; return NO; From 8c41a7b468ce8308b1660e8bdb6394f2ad9880f9 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 1 May 2015 10:02:18 -0500 Subject: [PATCH 124/284] Issue #81: Added a tempo property to MIKMIDISequencer to be able to override the sequence's tempo. --- Source/MIKMIDISequencer.h | 6 +++++ Source/MIKMIDISequencer.m | 55 +++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 95d661b6..e475f86a 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -234,6 +234,12 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (readonly, nonatomic, getter=isRecording) BOOL recording; +/** + * The tempo the sequencer should play its sequence at. When set to 0, the sequence will be played using + * the tempo events from the sequence's tempo track. Default is 0. + */ +@property (nonatomic) Float64 tempo; + /** * The current playback position in the sequence. */ diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 66f5123a..8e368313 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -77,6 +77,8 @@ @interface MIKMIDISequencer () @property (nonatomic, strong) NSMapTable *tracksToDefaultSynthsMap; @property (nonatomic, strong) MIKMIDIClientDestinationEndpoint *metronomeEndpoint; +@property (nonatomic) BOOL needsCurrentTempoUpdate; + @end @@ -195,12 +197,30 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam [self sendPendingNoteOffCommandsUpToMIDITimeStamp:actualToMIDITimeStamp]; // Get relevant tempo events + NSMutableDictionary *allEventsByTimeStamp = [NSMutableDictionary dictionary]; NSMutableDictionary *tempoEventsByTimeStamp = [NSMutableDictionary dictionary]; - NSMutableDictionary *otherEventsByTimeStamp = [NSMutableDictionary dictionary]; - for (MIKMIDITempoEvent *tempoEvent in [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]) { - NSNumber *timeStampKey = @(tempoEvent.timeStamp + playbackOffset); - otherEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; - tempoEventsByTimeStamp[timeStampKey] = tempoEvent; + Float64 overrideTempo = self.tempo; + + if (!overrideTempo) { + NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + for (MIKMIDITempoEvent *tempoEvent in sequenceTempoEvents) { + NSNumber *timeStampKey = @(tempoEvent.timeStamp + playbackOffset); + allEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; + tempoEventsByTimeStamp[timeStampKey] = tempoEvent; + } + } + + if (self.needsCurrentTempoUpdate) { + if (!tempoEventsByTimeStamp.count) { + if (!overrideTempo) overrideTempo = [self.sequence tempoAtTimeStamp:fromMusicTimeStamp]; + if (!overrideTempo) overrideTempo = MIKMIDISequencerDefaultTempo; + + MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:fromMusicTimeStamp tempo:overrideTempo]; + NSNumber *timeStampKey = @(fromMusicTimeStamp); + allEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; + tempoEventsByTimeStamp[timeStampKey] = tempoEvent; + } + self.needsCurrentTempoUpdate = NO; } // Get other events @@ -208,23 +228,23 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIKMIDIDestinationEndpoint *destination = [self destinationEndpointForTrack:track]; for (MIKMIDIEvent *event in [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]) { NSNumber *timeStampKey = @(event.timeStamp + playbackOffset); - NSMutableArray *eventsAtTimeStamp = otherEventsByTimeStamp[timeStampKey] ? otherEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; + NSMutableArray *eventsAtTimeStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimeStamp addObject:[MIKMIDIEventWithDestination eventWithDestination:destination event:event]]; - otherEventsByTimeStamp[timeStampKey] = eventsAtTimeStamp; + allEventsByTimeStamp[timeStampKey] = eventsAtTimeStamp; } } // Get click track events for (MIKMIDIEventWithDestination *destinationEvent in [self clickTrackEventsFromTimeStamp:fromMusicTimeStamp toTimeStamp:toMusicTimeStamp]) { NSNumber *timeStampKey = @(destinationEvent.event.timeStamp + playbackOffset); - NSMutableArray *eventsAtTimesStamp = otherEventsByTimeStamp[timeStampKey] ? otherEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; + NSMutableArray *eventsAtTimesStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimesStamp addObject:destinationEvent]; - otherEventsByTimeStamp[timeStampKey] = eventsAtTimesStamp; + allEventsByTimeStamp[timeStampKey] = eventsAtTimesStamp; } // Schedule events MIDITimeStamp lastProcessedMIDITimeStamp = fromMIDITimeStamp; - for (NSNumber *timeStampKey in [otherEventsByTimeStamp.allKeys sortedArrayUsingSelector:@selector(compare:)]) { + for (NSNumber *timeStampKey in [allEventsByTimeStamp.allKeys sortedArrayUsingSelector:@selector(compare:)]) { MusicTimeStamp musicTimeStamp = timeStampKey.doubleValue; if (isLooping && (musicTimeStamp < loopStartTimeStamp || musicTimeStamp >= loopEndTimeStamp)) continue; MIDITimeStamp midiTimeStamp = [clock midiTimeStampForMusicTimeStamp:musicTimeStamp]; @@ -233,7 +253,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIKMIDITempoEvent *tempoEventAtTimeStamp = tempoEventsByTimeStamp[timeStampKey]; if (tempoEventAtTimeStamp) [self updateClockWithMusicTimeStamp:musicTimeStamp tempo:tempoEventAtTimeStamp.bpm atMIDITimeStamp:midiTimeStamp]; - NSArray *events = otherEventsByTimeStamp[timeStampKey]; + NSArray *events = allEventsByTimeStamp[timeStampKey]; for (id eventObject in events) { if ([eventObject isKindOfClass:[MIKMIDIEventWithDestination class]]) { [self scheduleEventWithDestination:eventObject]; @@ -342,6 +362,10 @@ - (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { + // Override tempo if neccessary + Float64 tempoOverride = self.tempo; + if (tempoOverride) tempo = tempoOverride; + MIKMIDIClock *clock = self.clock; NSMutableDictionary *historicalClocks = self.historicalClocks; if (!historicalClocks) { @@ -651,6 +675,15 @@ - (void)setMetronome:(MIKMIDIMetronome *)metronome } } +- (void)setTempo:(Float64)tempo +{ + if (tempo < 0) tempo = 0; + if (_tempo != tempo) { + _tempo = tempo; + if (self.isPlaying) self.needsCurrentTempoUpdate = YES; + } +} + @end #pragma mark - From 3351a6afda89ee015ea2214ecdec76dc0dcbefcf Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 11:52:13 -0600 Subject: [PATCH 125/284] Refactored NSUIApplication+MIKMIDI to move most code into a separate MIKMIDIResponderHierarchyManager class. --- Source/NSUIApplication+MIKMIDI.m | 202 +++++++++++++++++++++---------- 1 file changed, 136 insertions(+), 66 deletions(-) diff --git a/Source/NSUIApplication+MIKMIDI.m b/Source/NSUIApplication+MIKMIDI.m index 582d5d48..9f77a0f8 100644 --- a/Source/NSUIApplication+MIKMIDI.m +++ b/Source/NSUIApplication+MIKMIDI.m @@ -9,6 +9,7 @@ #import "NSUIApplication+MIKMIDI.h" #import "MIKMIDIResponder.h" #import "MIKMIDICommand.h" +#import #if !__has_feature(objc_arc) #error NSApplication+MIKMIDI.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for NSApplication+MIKMIDI.m in the Build Phases for this target @@ -41,69 +42,53 @@ static BOOL MIKObjectRespondsToMIDICommand(id object, MIKMIDICommand *command) return [object conformsToProtocol:@protocol(MIKMIDIResponder)] && [(id)object respondsToMIDICommand:command]; } -@implementation MIK_APPLICATION_CLASS (MIKMIDI) +@interface MIKMIDIResponderHierarchyManager : NSObject + +@property (nonatomic, strong) NSHashTable *registeredMIKMIDIResponders; +@property (nonatomic, strong) NSSet *registeredMIKMIDIRespondersAndSubresponders; + +@property (nonatomic, strong, readonly) NSSet *allMIDIResponders; + +@end -+ (NSHashTable *)registeredMIKMIDIResponders +@implementation MIKMIDIResponderHierarchyManager + ++ (NSPointerFunctionsOptions)hashTableOptions { - static NSHashTable *registeredMIKMIDIResponders = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - #if TARGET_OS_IPHONE - NSPointerFunctionsOptions options = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; + return NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality; #elif (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_7) - NSPointerFunctionsOptions options = NSPointerFunctionsZeroingWeakMemory | NSPointerFunctionsObjectPersonality; + return NSPointerFunctionsZeroingWeakMemory | NSPointerFunctionsObjectPersonality; #else - NSPointerFunctionsOptions options = NSHashTableWeakMemory | NSPointerFunctionsObjectPersonality; + return NSHashTableWeakMemory | NSPointerFunctionsObjectPersonality; #endif - registeredMIKMIDIResponders = [[NSHashTable alloc] initWithOptions:options capacity:0]; - }); - return registeredMIKMIDIResponders; } -- (void)registerMIDIResponder:(id)responder; +- (instancetype)init { - [[[self class] registeredMIKMIDIResponders] addObject:responder]; + self = [super init]; + if (self) { + NSPointerFunctionsOptions options = [[self class] hashTableOptions]; + _registeredMIKMIDIResponders = [[NSHashTable alloc] initWithOptions:options capacity:0]; + } + return self; } -- (void)unregisterMIDIResponder:(id)responder; +- (void)registerMIDIResponder:(id)responder; { - [[[self class] registeredMIKMIDIResponders] removeObject:responder]; + [self.registeredMIKMIDIResponders addObject:responder]; } -- (BOOL)respondsToMIDICommand:(MIKMIDICommand *)command; +- (void)unregisterMIDIResponder:(id)responder; { - NSSet *registeredResponders = [self respondersForCommand:command inResponders:[self registeredMIDIRespondersIncludingSubresponders]]; - if ([registeredResponders count]) return YES; - -#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS - NSSet *viewHierarchyResponders = [self respondersForCommand:command inResponders:[self registeredMIDIRespondersIncludingSubresponders]]; - return ([viewHierarchyResponders count] != 0); -#endif - return NO; + [self.registeredMIKMIDIResponders addObject:responder]; } -- (void)handleMIDICommand:(MIKMIDICommand *)command; -{ - NSSet *registeredResponders = [self respondersForCommand:command inResponders:[self registeredMIDIRespondersIncludingSubresponders]]; - for (id responder in registeredResponders) { - [responder handleMIDICommand:command]; - } - -#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS - NSMutableSet *viewHierarchyResponders = [[self respondersForCommand:command inResponders:[self MIDIRespondersInViewHierarchy]] mutableCopy]; - [viewHierarchyResponders minusSet:registeredResponders]; - - for (id responder in viewHierarchyResponders) { - NSLog(@"WARNING: Found responder %@ for command %@ by traversing view hierarchy. This path for finding MIDI responders is deprecated. Responders should be explicitly registered with NS/UIApplication.", responder, command); - [responder handleMIDICommand:command]; - } -#endif -} +#pragma mark - Public -- (id)MIDIResponderWithIdentifier:(NSString *)identifier; +- (id)MIDIResponderWithIdentifier:(NSString *)identifier { - NSSet *registeredResponders = [self registeredMIDIRespondersIncludingSubresponders]; + NSSet *registeredResponders = self.registeredMIKMIDIRespondersAndSubresponders; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"MIDIIdentifier LIKE %@", identifier]; NSSet *results = [registeredResponders filteredSetUsingPredicate:predicate]; id result = [results anyObject]; @@ -112,8 +97,7 @@ - (void)handleMIDICommand:(MIKMIDICommand *)command; if (!result) { NSMutableSet *viewHierarchyResponders = [[self MIDIRespondersInViewHierarchy] mutableCopy]; [viewHierarchyResponders minusSet:registeredResponders]; - results = [viewHierarchyResponders filteredSetUsingPredicate:predicate]; - + result = [[viewHierarchyResponders filteredSetUsingPredicate:predicate] anyObject]; if (result) { NSLog(@"WARNING: Found responder %@ for identifier %@ by traversing view hierarchy. This path for finding MIDI responders is deprecated. Responders should be explicitly registered with NS/UIApplication.", result, identifier); @@ -123,31 +107,52 @@ - (void)handleMIDICommand:(MIKMIDICommand *)command; return result; } -- (NSSet *)allMIDIResponders +#pragma mark - Private + +- (NSSet *)recursiveSubrespondersOfMIDIResponder:(id)responder { - NSMutableSet *result = [[self registeredMIDIRespondersIncludingSubresponders] mutableCopy]; -#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS - [result unionSet:[self MIDIRespondersInViewHierarchy]]; -#endif + NSMutableSet *result = [NSMutableSet setWithObject:responder]; + if (![responder respondsToSelector:@selector(subresponders)]) return result; + + NSArray *subresponders = [responder subresponders]; + [result addObjectsFromArray:subresponders]; + for (idsubresponder in subresponders) { + [result unionSet:[self recursiveSubrespondersOfMIDIResponder:subresponder]]; + } + return result; } -- (NSSet *)registeredMIDIRespondersIncludingSubresponders +#pragma mark - Properties + +- (NSSet *)registeredMIKMIDIRespondersAndSubresponders { NSMutableSet *result = [NSMutableSet set]; - for (id responder in [[self class] registeredMIKMIDIResponders]) { + for (id responder in self.registeredMIKMIDIResponders) { [result unionSet:[self recursiveSubrespondersOfMIDIResponder:responder]]; } - return result; + +#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS + [result unionSet:[self MIDIRespondersInViewHierarchy]]; +#endif + + return [result copy]; +} + +- (NSSet *)allMIDIResponders +{ + return self.registeredMIKMIDIRespondersAndSubresponders; } +#pragma mark - Deprecated + #if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS - (NSSet *)MIDIRespondersInViewHierarchy { NSMutableSet *result = [NSMutableSet set]; // Go through the entire view hierarchy - for (MIK_WINDOW_CLASS *window in [self windows]) { + for (MIK_WINDOW_CLASS *window in [[MIK_APPLICATION_CLASS sharedApplication] windows]) { if ([window conformsToProtocol:@protocol(MIKMIDIResponder)]) { [result unionSet:[self recursiveSubrespondersOfMIDIResponder:(id)window]]; } @@ -172,7 +177,76 @@ - (NSSet *)MIDIRespondersInViewHierarchy return result; } +#endif // MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS + +@end + +#pragma mark - + +@implementation MIK_APPLICATION_CLASS (MIKMIDI) + ++ (MIKMIDIResponderHierarchyManager *)mik_MIDIResponderHierarchyManager +{ + static MIKMIDIResponderHierarchyManager *manager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manager = [[MIKMIDIResponderHierarchyManager alloc] init]; + }); + return manager; +} + +- (void)registerMIDIResponder:(id)responder; +{ + [[[self class] mik_MIDIResponderHierarchyManager] registerMIDIResponder:responder]; +} + +- (void)unregisterMIDIResponder:(id)responder; +{ + [[[self class] mik_MIDIResponderHierarchyManager] unregisterMIDIResponder:responder]; +} + +- (BOOL)respondsToMIDICommand:(MIKMIDICommand *)command; +{ + MIKMIDIResponderHierarchyManager *manager = [[self class] mik_MIDIResponderHierarchyManager]; + + NSSet *registeredResponders = [self respondersForCommand:command inResponders:manager.allMIDIResponders]; + if ([registeredResponders count]) return YES; + +#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS + NSSet *viewHierarchyResponders = [self respondersForCommand:command inResponders:[self MIDIRespondersInViewHierarchy]]; + return ([viewHierarchyResponders count] != 0); #endif + return NO; +} + +- (void)handleMIDICommand:(MIKMIDICommand *)command; +{ + MIKMIDIResponderHierarchyManager *manager = [[self class] mik_MIDIResponderHierarchyManager]; + NSSet *registeredResponders = [self respondersForCommand:command inResponders:manager.allMIDIResponders]; + for (id responder in registeredResponders) { + [responder handleMIDICommand:command]; + } + +#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS + NSMutableSet *viewHierarchyResponders = [[self respondersForCommand:command inResponders:[self MIDIRespondersInViewHierarchy]] mutableCopy]; + [viewHierarchyResponders minusSet:registeredResponders]; + + for (id responder in viewHierarchyResponders) { + NSLog(@"WARNING: Found responder %@ for command %@ by traversing view hierarchy. This path for finding MIDI responders is deprecated. Responders should be explicitly registered with NS/UIApplication.", responder, command); + [responder handleMIDICommand:command]; + } +#endif +} + +- (id)MIDIResponderWithIdentifier:(NSString *)identifier; +{ + return [[[self class] mik_MIDIResponderHierarchyManager] MIDIResponderWithIdentifier:identifier]; +} + +- (NSSet *)allMIDIResponders +{ + return [[[self class] mik_MIDIResponderHierarchyManager] allMIDIResponders]; +} #pragma mark - Private @@ -183,18 +257,14 @@ - (NSSet *)respondersForCommand:(MIKMIDICommand *)command inResponders:(NSSet *) }]]; } -- (NSSet *)recursiveSubrespondersOfMIDIResponder:(id)responder +#pragma mark - Deprecated + +#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS +- (NSSet *)MIDIRespondersInViewHierarchy { - NSMutableSet *result = [NSMutableSet setWithObject:responder]; - if (![responder respondsToSelector:@selector(subresponders)]) return result; - - NSArray *subresponders = [responder subresponders]; - [result addObjectsFromArray:subresponders]; - for (idsubresponder in subresponders) { - [result unionSet:[self recursiveSubrespondersOfMIDIResponder:subresponder]]; - } - - return result; + return [[[self class] mik_MIDIResponderHierarchyManager] MIDIRespondersInViewHierarchy]; } +#endif // MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS + @end From 5dba0e03c40a27f1d253fbf67aa18e7deb50a8f3 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 12:17:44 -0600 Subject: [PATCH 126/284] Added unit tests for NSUIApplication+MIKMIDI. --- .../MIKMIDIResponderChainTests.m | 82 +++++++++++++++++++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 30 +++---- 2 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m diff --git a/Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m b/Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m new file mode 100644 index 00000000..11e6b001 --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m @@ -0,0 +1,82 @@ +// +// MIKMIDIResponderChainTests.m +// MIKMIDI +// +// Created by Andrew Madsen on 5/7/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import +#import + +@interface MIKMIDIDummyResponder : NSObject + +- (instancetype)initWithMIDIIdentifier:(NSString *)identifier subresponders:(NSArray *)subresponders; + +@property (nonatomic, strong, readonly) NSString *MIDIIdentifier; +@property (nonatomic, strong, readonly) NSArray *subresponders; + +@end + +@implementation MIKMIDIDummyResponder + +- (instancetype)initWithMIDIIdentifier:(NSString *)identifier subresponders:(NSArray *)subresponders; +{ + self = [super init]; + if (self) { + _MIDIIdentifier = identifier; + _subresponders = subresponders; + } + return self; +} + +- (BOOL)respondsToMIDICommand:(MIKMIDICommand *)command { return NO; } +- (void)handleMIDICommand:(MIKMIDICommand *)command { } + +@end + +@interface MIKMIDIResponderChainTests : XCTestCase + +@property (nonatomic, strong) MIKMIDIDummyResponder *dummyResponder; + +@end + +@implementation MIKMIDIResponderChainTests + +- (void)setUp +{ + [super setUp]; + + MIKMIDIDummyResponder *sub1 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub1" subresponders:nil]; + + MIKMIDIDummyResponder *sub2sub1 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2Sub1" subresponders:nil]; + MIKMIDIDummyResponder *sub2sub2 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2Sub2" subresponders:nil]; + MIKMIDIDummyResponder *sub2 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2" subresponders:@[sub2sub1, sub2sub2]]; + + MIKMIDIDummyResponder *sub3 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub3" subresponders:nil]; + + self.dummyResponder = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Dummy" subresponders:@[sub1, sub2, sub3]]; +} + +- (void)testRegistration +{ + NSApplication *app = [NSApplication sharedApplication]; + [app registerMIDIResponder:self.dummyResponder]; + + XCTAssertNotNil([app MIDIResponderWithIdentifier:@"Dummy"], @"Top level dummy responder not found."); + XCTAssertNotNil([app MIDIResponderWithIdentifier:@"Sub1"], @"First level dummy subresponder not found."); + XCTAssertNotNil([app MIDIResponderWithIdentifier:@"Sub2Sub2"], @"First level dummy subresponder not found."); +} + +- (void)testUncachedResponderSearchPerformance +{ + NSApplication *app = [NSApplication sharedApplication]; + [self measureBlock:^{ + for (NSUInteger i=0; i<50000; i++) { + [app MIDIResponderWithIdentifier:@"Sub2Sub1"]; + } + }]; +} + +@end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index a218f07f..5ef7b376 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; + 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */; }; 9D4DF1401AAB57430065F004 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */; }; @@ -330,7 +331,7 @@ 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; - 9D7027D21ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMacDebugQuickLookSupport.m; sourceTree = ""; }; + 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIKMIDI_Tests.m; sourceTree = ""; }; @@ -338,6 +339,7 @@ 9D4DF14E1AAB57C90065F004 /* bach.mid */ = {isa = PBXFileReference; lastKnownFileType = audio.midi; path = bach.mid; sourceTree = ""; }; 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDITrackTests.m; sourceTree = ""; }; 9D5946CD1A9FA7C200B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizer_SubclassMethods.h; sourceTree = ""; }; + 9D7027D21ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMacDebugQuickLookSupport.m; sourceTree = ""; }; 9D74EEA517A7129300BEE89F /* MIKMIDI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MIKMIDI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9D74EEA817A7129300BEE89F /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 9D74EEAB17A7129300BEE89F /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -486,22 +488,6 @@ name = Files; sourceTree = ""; }; - 9D7027D11ACC9D4C009AFAED /* Debugging */ = { - isa = PBXGroup; - children = ( - 9D7027D21ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m */, - ); - name = Debugging; - sourceTree = ""; - }; - 9D7027D11ACC9D4C009AFAED /* Debugging */ = { - isa = PBXGroup; - children = ( - 9D7027D21ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m */, - ); - name = Debugging; - sourceTree = ""; - }; 9D4DF13B1AAB57430065F004 /* MIKMIDI Tests */ = { isa = PBXGroup; children = ( @@ -509,6 +495,7 @@ 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */, 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */, 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */, + 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */, 9D4DF13C1AAB57430065F004 /* Supporting Files */, 9D4DF1501AAB57CD0065F004 /* Resources */, ); @@ -532,6 +519,14 @@ name = Resources; sourceTree = ""; }; + 9D7027D11ACC9D4C009AFAED /* Debugging */ = { + isa = PBXGroup; + children = ( + 9D7027D21ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m */, + ); + name = Debugging; + sourceTree = ""; + }; 9D74EE9B17A7129300BEE89F = { isa = PBXGroup; children = ( @@ -1091,6 +1086,7 @@ files = ( 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */, 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */, + 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */, 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */, 9D4DF1541AAB60490065F004 /* MIKMIDITrackTests.m in Sources */, ); From 0db61bb54319997cc619db5d702c477551115139 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 12:39:52 -0600 Subject: [PATCH 127/284] Issue #82: Added an option to cache the full MIDI responder hierarchy to NS/UIApplication+MIKMIDI. Also _greatly_ improved performance even in the default, uncached mode. --- .../MIKMIDIResponderChainTests.m | 17 ++- ...C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist | 23 ++++ Source/NSUIApplication+MIKMIDI.h | 51 ++++++-- Source/NSUIApplication+MIKMIDI.m | 113 +++++++++++++----- 4 files changed, 160 insertions(+), 44 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m b/Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m index 11e6b001..77efcc1e 100644 --- a/Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDIResponderChainTests.m @@ -52,7 +52,10 @@ - (void)setUp MIKMIDIDummyResponder *sub2sub1 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2Sub1" subresponders:nil]; MIKMIDIDummyResponder *sub2sub2 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2Sub2" subresponders:nil]; - MIKMIDIDummyResponder *sub2 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2" subresponders:@[sub2sub1, sub2sub2]]; + MIKMIDIDummyResponder *sub2sub3sub1 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2Sub3Sub1" subresponders:nil]; + MIKMIDIDummyResponder *sub2sub3 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2Sub3" subresponders:@[sub2sub3sub1]]; + MIKMIDIDummyResponder *sub2sub4 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2Sub4" subresponders:nil]; + MIKMIDIDummyResponder *sub2 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub2" subresponders:@[sub2sub1, sub2sub2, sub2sub3, sub2sub4]]; MIKMIDIDummyResponder *sub3 = [[MIKMIDIDummyResponder alloc] initWithMIDIIdentifier:@"Sub3" subresponders:nil]; @@ -72,6 +75,7 @@ - (void)testRegistration - (void)testUncachedResponderSearchPerformance { NSApplication *app = [NSApplication sharedApplication]; + app.shouldCacheMIKMIDISubresponders = NO; [self measureBlock:^{ for (NSUInteger i=0; i<50000; i++) { [app MIDIResponderWithIdentifier:@"Sub2Sub1"]; @@ -79,4 +83,15 @@ - (void)testUncachedResponderSearchPerformance }]; } +- (void)testCachedResponderSearchPerformance +{ + NSApplication *app = [NSApplication sharedApplication]; + app.shouldCacheMIKMIDISubresponders = YES; + [self measureBlock:^{ + for (NSUInteger i=0; i<50000; i++) { + [app MIDIResponderWithIdentifier:@"Sub2Sub1"]; + } + }]; +} + @end diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist index 7efa07a8..b22dc619 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcbaselines/9D4DF1391AAB57430065F004.xcbaseline/C1C9286E-763A-4210-B3E7-DF2205A6AA20.plist @@ -4,6 +4,29 @@ classNames + MIKMIDIResponderChainTests + + testCachedResponderSearchPerformance + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.05 + baselineIntegrationDisplayName + Local Baseline + + + testUncachedResponderSearchPerformance + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.94 + baselineIntegrationDisplayName + Local Baseline + + + MIKMIDISequenceTests testMIDIFileReadPerformance diff --git a/Source/NSUIApplication+MIKMIDI.h b/Source/NSUIApplication+MIKMIDI.h index d01dee0d..81284961 100644 --- a/Source/NSUIApplication+MIKMIDI.h +++ b/Source/NSUIApplication+MIKMIDI.h @@ -10,17 +10,17 @@ #if TARGET_OS_IPHONE - #import - #define MIK_APPLICATION_CLASS UIApplication - #define MIK_WINDOW_CLASS UIWindow - #define MIK_VIEW_CLASS UIView +#import +#define MIK_APPLICATION_CLASS UIApplication +#define MIK_WINDOW_CLASS UIWindow +#define MIK_VIEW_CLASS UIView #else - #import - #define MIK_APPLICATION_CLASS NSApplication - #define MIK_WINDOW_CLASS NSWindow - #define MIK_VIEW_CLASS NSView +#import +#define MIK_APPLICATION_CLASS NSApplication +#define MIK_WINDOW_CLASS NSWindow +#define MIK_VIEW_CLASS NSView #endif @@ -64,6 +64,20 @@ */ - (void)unregisterMIDIResponder:(id)responder; +/** + * When subresponder caching is enabled via shouldCacheMIKMIDISubresponders, + * This method will cause the cache to be invalidated and regenerated. If a previously + * registered MIDI responders' subresponders have changed, it can call this method + * to force the cache to be refreshed. + * + * If subresponder caching is disabled (the default), calling this method has no effect, as + * subresponders are dynamically searched on every call to -MIDIResponderWithIdentifier and + * -allMIDIResponders. + * + * @see shouldCacheMIKMIDISubresponders + */ +- (void)refreshRespondersAndSubresponders; + /** * NSApplication (OS X) or UIApplication (iOS) itself implements to methods in the MIKMIDIResponder protocol. * This method determines if any responder in the MIDI responder chain (registered responders and their subresponders) @@ -78,7 +92,7 @@ /** * When this method is invoked with a MIDI command, the application will search its registered MIDI responders, * for responders that respond to the command, then call their -handleMIDICommand: method. - * + * * Call this method from a MIDI source event handler block to automatically dispatch MIDI commands/messages * from that source to all interested registered responders. * @@ -91,7 +105,7 @@ * * @param identifier An NSString instance containing the MIDI identifier to search for. * - * @return An object that conforms to MIKMIDIResponder, or nil if no registered responder for the passed in identifier + * @return An object that conforms to MIKMIDIResponder, or nil if no registered responder for the passed in identifier * could be found. */ - (id)MIDIResponderWithIdentifier:(NSString *)identifier; @@ -103,4 +117,21 @@ */ - (NSSet *)allMIDIResponders; +// Properties + +/** + * When this option is set, the application will cache registered MIDI responders' subresponders. + * Setting this option can greatly improve performance of -MIDIResponderWithIdentifier. However, + * when set, registered responders' -subresponders method cannot dynamically return different results + * e.g. for each MIDI command received. + * + * The entire cache is automatically refreshed anytime a new MIDI responder is registered or unregistered. + * It can also be manually refreshed by calling -refreshRespondersAndSubresponders. + * + * For backwards compatibility the default for this option is NO, or no caching. + * + * @see -refreshRespondersAndSubresponders + */ +@property (nonatomic) BOOL shouldCacheMIKMIDISubresponders; + @end diff --git a/Source/NSUIApplication+MIKMIDI.m b/Source/NSUIApplication+MIKMIDI.m index 9f77a0f8..6d8524d9 100644 --- a/Source/NSUIApplication+MIKMIDI.m +++ b/Source/NSUIApplication+MIKMIDI.m @@ -44,11 +44,22 @@ static BOOL MIKObjectRespondsToMIDICommand(id object, MIKMIDICommand *command) @interface MIKMIDIResponderHierarchyManager : NSObject +// Public +- (void)refreshRespondersAndSubresponders; +- (id)MIDIResponderWithIdentifier:(NSString *)identifier; + +// Properties @property (nonatomic, strong) NSHashTable *registeredMIKMIDIResponders; -@property (nonatomic, strong) NSSet *registeredMIKMIDIRespondersAndSubresponders; +@property (nonatomic, strong, readonly) NSSet *registeredMIKMIDIRespondersAndSubresponders; + +@property (nonatomic, strong) NSHashTable *subrespondersCache; + +@property (nonatomic) BOOL shouldCacheMIKMIDISubresponders; @property (nonatomic, strong, readonly) NSSet *allMIDIResponders; +@property (nonatomic, strong, readonly) NSPredicate *midiIdentifierPredicate; + @end @implementation MIKMIDIResponderHierarchyManager @@ -70,6 +81,8 @@ - (instancetype)init if (self) { NSPointerFunctionsOptions options = [[self class] hashTableOptions]; _registeredMIKMIDIResponders = [[NSHashTable alloc] initWithOptions:options capacity:0]; + + _shouldCacheMIKMIDISubresponders = NO; } return self; } @@ -77,21 +90,33 @@ - (instancetype)init - (void)registerMIDIResponder:(id)responder; { [self.registeredMIKMIDIResponders addObject:responder]; + [self refreshRespondersAndSubresponders]; } - (void)unregisterMIDIResponder:(id)responder; { [self.registeredMIKMIDIResponders addObject:responder]; + [self refreshRespondersAndSubresponders]; } #pragma mark - Public +- (void)refreshRespondersAndSubresponders +{ + self.subrespondersCache = nil; +} + - (id)MIDIResponderWithIdentifier:(NSString *)identifier { NSSet *registeredResponders = self.registeredMIKMIDIRespondersAndSubresponders; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"MIDIIdentifier LIKE %@", identifier]; - NSSet *results = [registeredResponders filteredSetUsingPredicate:predicate]; - id result = [results anyObject]; + + id result = nil; + for (id responder in registeredResponders) { + if ([[responder MIDIIdentifier] isEqualToString:identifier]) { + result = responder; + break; + } + } #if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS if (!result) { @@ -127,6 +152,10 @@ - (NSSet *)recursiveSubrespondersOfMIDIResponder:(id)responder - (NSSet *)registeredMIKMIDIRespondersAndSubresponders { + if (self.shouldCacheMIKMIDISubresponders && self.subrespondersCache != nil) { + return [self.subrespondersCache setRepresentation]; + } + NSMutableSet *result = [NSMutableSet set]; for (id responder in self.registeredMIKMIDIResponders) { [result unionSet:[self recursiveSubrespondersOfMIDIResponder:responder]]; @@ -136,14 +165,35 @@ - (NSSet *)registeredMIKMIDIRespondersAndSubresponders [result unionSet:[self MIDIRespondersInViewHierarchy]]; #endif + if (self.shouldCacheMIKMIDISubresponders) { + // Load cache with result + NSPointerFunctionsOptions options = [[self class] hashTableOptions]; + self.subrespondersCache = [[NSHashTable alloc] initWithOptions:options capacity:0]; + for (id object in result) { [self.subrespondersCache addObject:object]; } + } + return [result copy]; } ++ (NSSet *)keyPathsForValuesAffectingAllMIDIResponders +{ + return [NSSet setWithObjects:@"registeredMIKMIDIRespondersAndSubresponders", nil]; +} + - (NSSet *)allMIDIResponders { return self.registeredMIKMIDIRespondersAndSubresponders; } +@synthesize midiIdentifierPredicate = _midiIdentifierPredicate; +- (NSPredicate *)midiIdentifierPredicate +{ + if (!_midiIdentifierPredicate) { + _midiIdentifierPredicate = [NSPredicate predicateWithFormat:@"MIDIIdentifier LIKE $MIDIIdentifier"]; + } + return _midiIdentifierPredicate; +} + #pragma mark - Deprecated #if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS @@ -185,29 +235,13 @@ - (NSSet *)MIDIRespondersInViewHierarchy @implementation MIK_APPLICATION_CLASS (MIKMIDI) -+ (MIKMIDIResponderHierarchyManager *)mik_MIDIResponderHierarchyManager -{ - static MIKMIDIResponderHierarchyManager *manager = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - manager = [[MIKMIDIResponderHierarchyManager alloc] init]; - }); - return manager; -} +- (void)registerMIDIResponder:(id)responder; { [self.mikmidi_responderHierarchyManager registerMIDIResponder:responder]; } -- (void)registerMIDIResponder:(id)responder; -{ - [[[self class] mik_MIDIResponderHierarchyManager] registerMIDIResponder:responder]; -} - -- (void)unregisterMIDIResponder:(id)responder; -{ - [[[self class] mik_MIDIResponderHierarchyManager] unregisterMIDIResponder:responder]; -} +- (void)unregisterMIDIResponder:(id)responder; { [self.mikmidi_responderHierarchyManager unregisterMIDIResponder:responder]; } - (BOOL)respondsToMIDICommand:(MIKMIDICommand *)command; { - MIKMIDIResponderHierarchyManager *manager = [[self class] mik_MIDIResponderHierarchyManager]; + MIKMIDIResponderHierarchyManager *manager = self.mikmidi_responderHierarchyManager; NSSet *registeredResponders = [self respondersForCommand:command inResponders:manager.allMIDIResponders]; if ([registeredResponders count]) return YES; @@ -221,7 +255,7 @@ - (BOOL)respondsToMIDICommand:(MIKMIDICommand *)command; - (void)handleMIDICommand:(MIKMIDICommand *)command; { - MIKMIDIResponderHierarchyManager *manager = [[self class] mik_MIDIResponderHierarchyManager]; + MIKMIDIResponderHierarchyManager *manager = self.mikmidi_responderHierarchyManager; NSSet *registeredResponders = [self respondersForCommand:command inResponders:manager.allMIDIResponders]; for (id responder in registeredResponders) { [responder handleMIDICommand:command]; @@ -240,13 +274,11 @@ - (void)handleMIDICommand:(MIKMIDICommand *)command; - (id)MIDIResponderWithIdentifier:(NSString *)identifier; { - return [[[self class] mik_MIDIResponderHierarchyManager] MIDIResponderWithIdentifier:identifier]; + return [self.mikmidi_responderHierarchyManager MIDIResponderWithIdentifier:identifier]; } -- (NSSet *)allMIDIResponders -{ - return [[[self class] mik_MIDIResponderHierarchyManager] allMIDIResponders]; -} +- (NSSet *)allMIDIResponders { return [self.mikmidi_responderHierarchyManager allMIDIResponders]; } +- (void)refreshRespondersAndSubresponders { [self.mikmidi_responderHierarchyManager refreshRespondersAndSubresponders]; } #pragma mark - Private @@ -257,14 +289,29 @@ - (NSSet *)respondersForCommand:(MIKMIDICommand *)command inResponders:(NSSet *) }]]; } -#pragma mark - Deprecated +#pragma mark - Properties -#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS -- (NSSet *)MIDIRespondersInViewHierarchy +- (MIKMIDIResponderHierarchyManager *)mikmidi_responderHierarchyManager +{ + static MIKMIDIResponderHierarchyManager *manager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manager = [[MIKMIDIResponderHierarchyManager alloc] init]; + }); + return manager; +} + ++ (NSSet *)keyPathsForValuesAffectingShouldCacheMIKMIDISubresponders { - return [[[self class] mik_MIDIResponderHierarchyManager] MIDIRespondersInViewHierarchy]; + return [NSSet setWithObject:@"mikmidi_responderHierarchyManager.shouldCacheMIKMIDISubresponders"]; } +- (BOOL)shouldCacheMIKMIDISubresponders { return [self.mikmidi_responderHierarchyManager shouldCacheMIKMIDISubresponders]; } +- (void)setShouldCacheMIKMIDISubresponders:(BOOL)flag { [self.mikmidi_responderHierarchyManager setShouldCacheMIKMIDISubresponders:flag]; } +#pragma mark - Deprecated + +#if MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS +- (NSSet *)MIDIRespondersInViewHierarchy { return [self.mikmidi_responderHierarchyManager MIDIRespondersInViewHierarchy]; } #endif // MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS @end From 2de7073c51b625f5696a23710b12c0bd03285ec5 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 12:42:09 -0600 Subject: [PATCH 128/284] Issue #82: Renamed -refreshRespondersAndSubresponders to -refreshMIDIRespondersAndSubresponders. --- Source/NSUIApplication+MIKMIDI.h | 2 +- Source/NSUIApplication+MIKMIDI.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/NSUIApplication+MIKMIDI.h b/Source/NSUIApplication+MIKMIDI.h index 81284961..8e3a3d1a 100644 --- a/Source/NSUIApplication+MIKMIDI.h +++ b/Source/NSUIApplication+MIKMIDI.h @@ -76,7 +76,7 @@ * * @see shouldCacheMIKMIDISubresponders */ -- (void)refreshRespondersAndSubresponders; +- (void)refreshMIDIRespondersAndSubresponders; /** * NSApplication (OS X) or UIApplication (iOS) itself implements to methods in the MIKMIDIResponder protocol. diff --git a/Source/NSUIApplication+MIKMIDI.m b/Source/NSUIApplication+MIKMIDI.m index 6d8524d9..cf472210 100644 --- a/Source/NSUIApplication+MIKMIDI.m +++ b/Source/NSUIApplication+MIKMIDI.m @@ -278,7 +278,7 @@ - (void)handleMIDICommand:(MIKMIDICommand *)command; } - (NSSet *)allMIDIResponders { return [self.mikmidi_responderHierarchyManager allMIDIResponders]; } -- (void)refreshRespondersAndSubresponders { [self.mikmidi_responderHierarchyManager refreshRespondersAndSubresponders]; } +- (void)refreshMIDIRespondersAndSubresponders { [self.mikmidi_responderHierarchyManager refreshRespondersAndSubresponders]; } #pragma mark - Private From 4cdb523cbd1db56e6a1eb23ea0069e0268620f59 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 14:29:55 -0600 Subject: [PATCH 129/284] Fixed broken -unregisterMIDIResponder:. --- Source/NSUIApplication+MIKMIDI.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NSUIApplication+MIKMIDI.m b/Source/NSUIApplication+MIKMIDI.m index cf472210..f59cbdae 100644 --- a/Source/NSUIApplication+MIKMIDI.m +++ b/Source/NSUIApplication+MIKMIDI.m @@ -95,7 +95,7 @@ - (void)registerMIDIResponder:(id)responder; - (void)unregisterMIDIResponder:(id)responder; { - [self.registeredMIKMIDIResponders addObject:responder]; + [self.registeredMIKMIDIResponders removeObject:responder]; [self refreshRespondersAndSubresponders]; } From 2af8e14a873d521f531ee47e7431e547c2412c0c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 14:49:58 -0600 Subject: [PATCH 130/284] Improved performance of MIKMIDIMappingManager's mappings lookup methods. --- Source/MIKMIDIMappingManager.m | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index a042e450..b85b1549 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -94,21 +94,35 @@ - (NSSet *)mappingsForControllerName:(NSString *)name; - (NSSet *)bundledMappingsForControllerName:(NSString *)name { if (![name length]) return [NSSet set]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"controllerName LIKE %@", name]; - return [self.bundledMappings filteredSetUsingPredicate:predicate]; + NSMutableSet *result = [NSMutableSet set]; + for (MIKMIDIMapping *mapping in self.bundledMappings) { + if ([mapping.controllerName isEqualToString:name]) { + [result addObject:mapping]; + } + } + return result; } - (NSSet *)userMappingsForControllerName:(NSString *)name { if (![name length]) return [NSSet set]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"controllerName LIKE %@", name]; - return [self.userMappings filteredSetUsingPredicate:predicate]; + NSMutableSet *result = [NSMutableSet set]; + for (MIKMIDIMapping *mapping in self.userMappings) { + if ([mapping.controllerName isEqualToString:name]) { + [result addObject:mapping]; + } + } + return result; } - (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@", mappingName]; - return [[self.mappings filteredSetUsingPredicate:predicate] anyObject]; + for (MIKMIDIMapping *mapping in self.mappings) { + if ([mapping.name isEqualToString:mappingName]) { + return mapping; + } + } + return nil; } - (MIKMIDIMapping *)importMappingFromFileAtURL:(NSURL *)URL overwritingExistingMapping:(BOOL)shouldOverwrite error:(NSError **)error; From 187eb64e63cf6380136015afc2e357e2ff86ed23 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 15:01:53 -0600 Subject: [PATCH 131/284] Improved performance of -[MIKMIDIMapping hash]. --- Source/MIKMIDIMapping.m | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index 225f10b5..e90ac784 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -296,11 +296,7 @@ - (NSUInteger)hash { NSUInteger result = [self.name hash]; result += [self.controllerName hash]; - - for (MIKMIDIMappingItem *mappingItem in self.mappingItems) { - result += [mappingItem hash]; - } - + result += [self.internalMappingItems count]; return result; } From de1a10493fa0a382d871d4f2977556b81e92f09a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 7 May 2015 15:10:36 -0600 Subject: [PATCH 132/284] Improved performance of MIKMIDIMapping's mapping items lookup methods. --- Source/MIKMIDIMapping.m | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index e90ac784..db31db24 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -306,20 +306,30 @@ - (NSString *)description } - (NSSet *)mappingItemsForMIDIResponder:(id)responder; -{ - NSPredicate *commandPredicate = [NSPredicate predicateWithFormat:@"commandIdentifier IN %@", [responder commandIdentifiers]]; - NSPredicate *responderPredicate = [NSPredicate predicateWithFormat:@"MIDIResponderIdentifier LIKE %@", [responder MIDIIdentifier]]; - NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[commandPredicate, responderPredicate]]; - NSSet *matches = [self.mappingItems filteredSetUsingPredicate:predicate]; +{ + NSString *MIDIIdentifer = [responder MIDIIdentifier]; + + NSMutableSet *matches = [NSMutableSet set]; + for (MIKMIDIMappingItem *item in self.internalMappingItems) { + if (![item.MIDIResponderIdentifier isEqualToString:MIDIIdentifer]) continue; + if (![[responder commandIdentifiers] containsObject:item.commandIdentifier]) continue; + [matches addObject:item]; + } + return matches; } - (NSSet *)mappingItemsForCommandIdentifier:(NSString *)identifier responder:(id)responder; { - NSPredicate *commandPredicate = [NSPredicate predicateWithFormat:@"commandIdentifier LIKE %@", identifier]; - NSPredicate *responderPredicate = [NSPredicate predicateWithFormat:@"MIDIResponderIdentifier LIKE %@", [responder MIDIIdentifier]]; - NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[commandPredicate, responderPredicate]]; - NSSet *matches = [self.mappingItems filteredSetUsingPredicate:predicate]; + NSString *MIDIIdentifer = [responder MIDIIdentifier]; + + NSMutableSet *matches = [NSMutableSet set]; + for (MIKMIDIMappingItem *item in self.internalMappingItems) { + if (![item.MIDIResponderIdentifier isEqualToString:MIDIIdentifer]) continue; + if (![item.commandIdentifier isEqualToString:identifier]) continue; + [matches addObject:item]; + } + return matches; } @@ -328,12 +338,15 @@ - (NSSet *)mappingItemsForMIDICommand:(MIKMIDIChannelVoiceCommand *)command; NSUInteger controlNumber = MIKMIDIControlNumberFromCommand(command); UInt8 channel = command.channel; MIKMIDICommandType commandType = command.commandType; + + NSMutableSet *matches = [NSMutableSet set]; + for (MIKMIDIMappingItem *item in self.internalMappingItems) { + if (item.controlNumber != controlNumber) continue; + if (item.channel != channel) continue; + if (item.commandType != commandType) continue; + [matches addObject:item]; + } - NSPredicate *controlNumberPredicate = [NSPredicate predicateWithFormat:@"controlNumber == %@", @(controlNumber)]; - NSPredicate *channelPredicate = [NSPredicate predicateWithFormat:@"channel == %@", @(channel)]; - NSPredicate *commandTypePredicate = [NSPredicate predicateWithFormat:@"commandType == %@", @(commandType)]; - NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[controlNumberPredicate, channelPredicate, commandTypePredicate]]; - NSSet *matches = [self.mappingItems filteredSetUsingPredicate:predicate]; return matches; } From 38c3e4c85b49687dbf011b9f8fc8977186ad2c54 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 14 May 2015 13:37:03 -0600 Subject: [PATCH 133/284] Issue #84: Added -[MIKMIDIMappingGenerator endMapping]. --- Source/MIKMIDIMappingGenerator.h | 5 +++++ Source/MIKMIDIMappingGenerator.m | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index 8e8d9e61..953030d4 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -87,6 +87,11 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem */ - (void)cancelCurrentCommandLearning; +/** + * Stops mapping generation, disconnecting from the device. + */ +- (void)endMapping; + // Properties #if !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 1a8203f4..7da7d541 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -146,6 +146,12 @@ - (void)cancelCurrentCommandLearning; [self finishMappingItem:nil error:error]; } +- (void)endMapping; +{ + [self disconnectFromDevice]; + self.device = nil; +} + #pragma mark - Private - (void)handleMIDICommand:(MIKMIDIChannelVoiceCommand *)command From d2cc34fbe4489d29bba596ee847db1dcc028fbdb Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 14 May 2015 13:37:32 -0600 Subject: [PATCH 134/284] Fixed retain cycle that caused MIKMIDIMappingGenerator to never be deallocated (see #84). --- Source/MIKMIDIMappingGenerator.m | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 7da7d541..5ba9ccaf 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -60,17 +60,18 @@ - (instancetype)initWithDevice:(MIKMIDIDevice *)device error:(NSError **)error; self.blockBasedObservers = [NSMutableArray array]; NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - __weak MIKMIDIMappingGenerator *weakSelf = self; + __weak typeof(self) weakSelf = self; id observer = [nc addObserverForName:MIKMIDIDeviceWasRemovedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + __strong typeof(self) strongSelf = weakSelf; MIKMIDIDevice *device = [[note userInfo] objectForKey:MIKMIDIDeviceKey]; - if (![device isEqual:self.device]) return; - [self disconnectFromDevice]; - weakSelf.device = nil; + if (![device isEqual:strongSelf.device]) return; + [strongSelf disconnectFromDevice]; + strongSelf.device = nil; NSError *error = [NSError MIKMIDIErrorWithCode:MIKMIDIDeviceConnectionLostErrorCode userInfo:nil]; - [weakSelf finishMappingItem:nil error:error]; + [strongSelf finishMappingItem:nil error:error]; }]; [self.blockBasedObservers addObject:observer]; } From c142b09d79652c2e0086ace8c073364f539b80a3 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 15 May 2015 16:44:39 -0600 Subject: [PATCH 135/284] MIKMIDICommands created with alloc/init now have their timestamp set to creation time. --- Source/MIKMIDICommand.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index e82c245e..6cc0dc24 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -112,6 +112,7 @@ - (id)initWithMIDIPacket:(MIDIPacket *)packet self.midiTimestamp = packet->timeStamp; self.internalData = [NSMutableData dataWithBytes:packet->data length:packet->length]; } else { + self.midiTimestamp = MIKMIDIGetCurrentTimeStamp(); self.internalData = [NSMutableData dataWithLength:3]; MIKMIDICommandType commandType = [[[[self class] supportedMIDICommandTypes] firstObject] unsignedCharValue]; ((UInt8 *)[self.internalData mutableBytes])[0] = commandType; From fdd702cd1f2d00c7b9c3eb212e06b7ce6456fa9a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 15 May 2015 16:46:49 -0600 Subject: [PATCH 136/284] Coalesced 14-bit commands now have the timestamp of their source LSB command. --- Source/MIKMIDIControlChangeCommand.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/MIKMIDIControlChangeCommand.m b/Source/MIKMIDIControlChangeCommand.m index 091035ef..8c34328f 100644 --- a/Source/MIKMIDIControlChangeCommand.m +++ b/Source/MIKMIDIControlChangeCommand.m @@ -44,6 +44,7 @@ + (instancetype)commandByCoalescingMSBCommand:(MIKMIDIControlChangeCommand *)msb if (lsbCommand.controllerNumber - msbCommand.controllerNumber != 32) return nil; MIKMIDIControlChangeCommand *result = [[MIKMIDIControlChangeCommand alloc] init]; + result.midiTimestamp = lsbCommand.midiTimestamp; result.internalData = [msbCommand.data mutableCopy]; result.fourteenBitCommand = YES; [result.internalData appendData:[lsbCommand.data subdataWithRange:NSMakeRange(2, 1)]]; From 79e90a42a4bf8f4f3fe51bf84e5c38e8183c4f6c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 19 May 2015 18:04:25 -0600 Subject: [PATCH 137/284] Added diagnosticLoggingEnabled property to MIKMIDIMappingGenerator. --- Source/MIKMIDIMappingGenerator.h | 7 +++++++ Source/MIKMIDIMappingGenerator.m | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index 953030d4..e85a8660 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -117,6 +117,13 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem */ @property (nonatomic, strong) MIKMIDIMapping *mapping; +/** + * Set this to YES to enable printing diagnostic messages to the console. This is intended + * to help with eg. debugging trouble mapping specific controllers. The default + * is NO, ie. logging is disabled. + */ +@property (nonatomic, getter=isDiagnosticLoggingEnabled) BOOL diagnosticLoggingEnabled; + @end diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 5ba9ccaf..d7dca50d 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -134,6 +134,10 @@ - (void)learnMappingForControl:(id)control self.numMessagesRequired = numMessages ? numMessages : [self defaultMinimumNumberOfMessagesRequiredForResponderType:controlResponderType]; self.timeoutInteveral = timeout ? timeout : 0.6; self.messagesTimeoutTimer = nil; + + if (self.isDiagnosticLoggingEnabled) { + NSLog(@"MIDI Mapping Generator: Beginning mapping of %@ (%@)", control, commandID); + } } - (void)cancelCurrentCommandLearning; @@ -409,11 +413,19 @@ - (MIKMIDIMappingItem *)mappingItemForCommandIdentifier:(NSString *)commandID in goto FINALIZE_RESULT_AND_RETURN; } + if (self.isDiagnosticLoggingEnabled) { + NSLog(@"MIDI Mapping Generator: Unable to create mapping for %@ (%@) from messages: %@", responder, commandID, messages); + } + return nil; FINALIZE_RESULT_AND_RETURN: result.commandType = [messages[0] commandType]; + if (self.isDiagnosticLoggingEnabled) { + NSLog(@"MIDI Mapping Generator: Created mapping item: %@ for %@ (%@) from messages: %@", result, responder, commandID, messages); + } + return result; } From 4a7c0c26f8b6ff3cc26fc90cd42dff014ab03ce7 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 20 May 2015 14:53:21 -0600 Subject: [PATCH 138/284] Split MIKMIDIMappingItem and MIKMIDIMappableResponder out into their own source files. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 18 ++ Source/MIKMIDI.h | 2 + Source/MIKMIDIMappableResponder.h | 144 +++++++++ Source/MIKMIDIMapping.h | 218 -------------- Source/MIKMIDIMapping.m | 299 +------------------ Source/MIKMIDIMappingGenerator.m | 1 + Source/MIKMIDIMappingItem.h | 95 ++++++ Source/MIKMIDIMappingItem.m | 308 ++++++++++++++++++++ Source/MIKMIDIUtilities.h | 3 +- 9 files changed, 571 insertions(+), 517 deletions(-) create mode 100644 Source/MIKMIDIMappableResponder.h create mode 100644 Source/MIKMIDIMappingItem.h create mode 100644 Source/MIKMIDIMappingItem.m diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 5ef7b376..b9bfe96f 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -52,6 +52,12 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; + 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D0895F01B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; + 9D0895F11B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; + 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */; }; @@ -331,6 +337,9 @@ 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; + 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; + 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; + 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -756,6 +765,9 @@ children = ( 9D74EF4617A713A100BEE89F /* MIKMIDIMapping.h */, 9D74EF4717A713A100BEE89F /* MIKMIDIMapping.m */, + 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */, + 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */, + 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */, 9DAF8B901A7B04F700F46528 /* MIKMIDIMappingXMLParser.h */, 9DAF8B911A7B04F700F46528 /* MIKMIDIMappingXMLParser.m */, 9D74EF4817A713A100BEE89F /* MIKMIDIMappingGenerator.h */, @@ -820,6 +832,7 @@ 9D74EF6317A713A100BEE89F /* MIKMIDI.h in Headers */, 9D74EF6417A713A100BEE89F /* MIKMIDIChannelVoiceCommand.h in Headers */, 9D74EF6617A713A100BEE89F /* MIKMIDICommand.h in Headers */, + 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */, 9D74EF6917A713A100BEE89F /* MIKMIDIControlChangeCommand.h in Headers */, 9D74EF6B17A713A100BEE89F /* MIKMIDIDestinationEndpoint.h in Headers */, 9D74EF6D17A713A100BEE89F /* MIKMIDIDevice.h in Headers */, @@ -843,6 +856,7 @@ 839D935519C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h in Headers */, 9DF99E7D18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h in Headers */, 839D935F19C3A2F5007589C3 /* MIKMIDIMetaTextEvent.h in Headers */, + 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */, 839D936919C3A303007589C3 /* MIKMIDIPlayer.h in Headers */, 839D936519C3A2F5007589C3 /* MIKMIDINoteEvent.h in Headers */, 839D936D19C3A30B007589C3 /* MIKMIDISequence.h in Headers */, @@ -893,6 +907,7 @@ 9DAF8B7A1A7B00A700F46528 /* MIKMIDINoteEvent.h in Headers */, 9DAF8B6C1A7B00A700F46528 /* MIKMIDITrack.h in Headers */, 9DAF8B661A7B008A00F46528 /* MIKMIDISystemExclusiveCommand.h in Headers */, + 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */, 9DAF8B681A7B009100F46528 /* MIKMIDIMapping.h in Headers */, 9DAF8B5B1A7B007300F46528 /* MIKMIDIEndpoint.h in Headers */, 9DAF8B5A1A7B007300F46528 /* MIKMIDIOutputPort.h in Headers */, @@ -916,6 +931,7 @@ 9DBEBD591AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */, 9DAF8B5F1A7B007300F46528 /* MIKMIDIClientDestinationEndpoint.h in Headers */, 9DAF8B611A7B008A00F46528 /* MIKMIDIChannelVoiceCommand.h in Headers */, + 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */, 9DAF8B701A7B00A700F46528 /* MIKMIDIMetaCuePointEvent.h in Headers */, 9DAF8B671A7B008A00F46528 /* MIKMIDIProgramChangeCommand.h in Headers */, 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */, @@ -1155,6 +1171,7 @@ 839D935419C3A2F5007589C3 /* MIKMIDIMetaEvent.m in Sources */, 9D7027D31ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m in Sources */, 9D74EF9317A713A100BEE89F /* MIKMIDIUtilities.m in Sources */, + 9D0895F01B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */, 9D74EF9517A713A100BEE89F /* NSUIApplication+MIKMIDI.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1222,6 +1239,7 @@ 9DAF8B4E1A7AFF7500F46528 /* MIKMIDICommandThrottler.m in Sources */, 9DAF8B4F1A7AFF7500F46528 /* MIKMIDIClock.m in Sources */, 9DAF8B501A7AFF7500F46528 /* MIKMIDIPrivateUtilities.m in Sources */, + 9D0895F11B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */, 9DEF1CB11AA6800C00E10273 /* MIKMIDIControlChangeEvent.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index f0090642..f9dcf675 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -79,6 +79,8 @@ // MIDI Mapping #import "MIKMIDIMapping.h" +#import "MIKMIDIMappingItem.h" +#import "MIKMIDIMappableResponder.h" #import "MIKMIDIMappingManager.h" #import "MIKMIDIMappingGenerator.h" diff --git a/Source/MIKMIDIMappableResponder.h b/Source/MIKMIDIMappableResponder.h new file mode 100644 index 00000000..478e4811 --- /dev/null +++ b/Source/MIKMIDIMappableResponder.h @@ -0,0 +1,144 @@ +// +// MIKMIDIMappableResponder.h +// MIKMIDI +// +// Created by Andrew Madsen on 5/20/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import "MIKMIDIResponder.h" + +/** + * Bit-mask constants used to specify MIDI responder types for mapping. + * Multiple responder types can be specified by ORing them together. + * @see -[MIKMIDIMappableResponder MIDIResponderTypeForCommandIdentifier:] + */ +typedef NS_OPTIONS(NSUInteger, MIKMIDIResponderType){ + /** + * Responder does not have a type. Cannot be mapped. + */ + MIKMIDIResponderTypeNone = 0, + + /** + * Type for a MIDI responder that can handle messages from a hardware absolute + * knob or slider. That is, one that sends control change messages with an absolute value + * depending on its position. + */ + MIKMIDIResponderTypeAbsoluteSliderOrKnob = 1 << 0, + + /** + * Type for a MIDI responder that can handle messages from a hardware relative + * knob. That is, a knob that sends a message for each "tick", and whose value + * depends on the direction (and possibly velocity) of the knob, rather than its + * absolute position. + */ + MIKMIDIResponderTypeRelativeKnob = 1 << 1, + + /** + * Type for a MIDI responder that can handle messages from a hardware turntable-like + * jog wheel. These are relative knobs, but typically have *much* higher resolution than + * a small relative knob. They may also have a touch/pressure sensitive top to detect when + * the user is touching, but not turning the wheel. + */ + MIKMIDIResponderTypeTurntableKnob = 1 << 2, + + /** + * Type for a MIDI responder that can handle messages from a hardware relative knob that + * sends messages to simulate an absolute knob. Relative knobs on (at least) Native Instruments + * controllers can be configured to send messages like an absolute knob. This can pose the problem + * of the knob continuing to turn past its limits (0 and 127) without additional messages being sent. + * These knobs can and will be mapped as a regular absolute knob for responders that include MIKMIDIResponderTypeAbsoluteSliderOrKnob + * but *not* MIKMIDIResponderTypeRelativeAbsoluteKnob in the type returned by -MIDIResponderTypeForCommandIdentifier: + */ + MIKMIDIResponderTypeRelativeAbsoluteKnob = 1 << 3, + + /** + * Type for a MIDI responder that can handle messages from a hardware button that sends a message when + * pressed down, and another message when released. + */ + MIKMIDIResponderTypePressReleaseButton = 1 << 4, + + /** + * Type for a MIDI responder that can handle messages from a hardware button that only sends a single + * message when pressed down, without sending a corresponding message upon release. + */ + MIKMIDIResponderTypePressButton = 1 << 5, + + /** + * Convenience type for a responder that can handle messages from any type of knob. + */ + MIKMIDIResponderTypeKnob = (MIKMIDIResponderTypeAbsoluteSliderOrKnob | MIKMIDIResponderTypeRelativeKnob | \ + MIKMIDIResponderTypeTurntableKnob | MIKMIDIResponderTypeRelativeAbsoluteKnob), + + /** + * Convenience type for a responder that can handle messages from any type of button. + */ + MIKMIDIResponderTypeButton = (MIKMIDIResponderTypePressButton | MIKMIDIResponderTypePressReleaseButton), + + /** + * Convenience type for a responder that can handle messages from any kind of control. + */ + MIKMIDIResponderTypeAll = NSUIntegerMax, +}; + +/** + * This protocol defines methods that that must be implemented by MIDI responder objects to be mapped + * using MIKMIDIMappingGenerator, and to whom MIDI messages will selectively be routed using a MIDI mapping + * during normal operation. + */ +@protocol MIKMIDIMappableResponder + +@required +/** + * The list of identifiers for all commands supported by the receiver. + * + * A MIDI responder may want to handle incoming MIDI message from more than one control. For example, a view displaying + * a list of songs may want to support commands for browsing up and down the list with buttons, or with a knob, as well as a button + * to load the selected song. These commands would be for example, KnobBrowse, BrowseUp, BrowseDown, and Load. This way, multiple physical + * controls can be mapped to different functions of the same MIDI responder. + * + * @return An NSArray containing NSString identifers for all MIDI mappable commands supported by the receiver. + */ +- (NSArray *)commandIdentifiers; + +/** + * The MIDI responder types the receiver will allow to be mapped to the command specified by commandID. + * + * In the example given for -commandIdentifers, the "KnobBrowse" might be mappable to any physical knob, + * while BrowseUp, BrowseDown, and Load are mappable to buttons. The responder would return MIKMIDIResponderTypeKnob + * for @"KnobBrowse" while returning MIKMIDIResponderTypeButton for the other commands. + * + * @param commandID A command identifier string. + * + * @return A MIKMIDIResponderType bitfield specifing one or more responder type(s). + * + * @see MIKMIDIResponderType + */ +- (MIKMIDIResponderType)MIDIResponderTypeForCommandIdentifier:(NSString *)commandID; // Optional. If not implemented, MIKMIDIResponderTypeAll will be assumed. + +@optional + +/** + * Whether the physical control mapped to the commandID in the receiver should + * be illuminated, or not. + * + * Many hardware MIDI devices, e.g. DJ controllers, have buttons that can light + * up to show state for the associated function. For example, the play button + * could be illuminated when the software is playing. This method allows mapped + * MIDI responder objects to communicate the desired state of the physical control + * mapped to them. + * + * Currently MIKMIDI doesn't provide automatic support for actually updating + * physical LED status. This must be implemented in application code. For most devices, + * this can be accomplished by sending a MIDI message _to_ the device. The MIDI message + * should identical to the message that the relevant control sends when pressed, with + * a non-zero value to illumniate the control, or zero to turn illumination off. + * + * @param commandID The commandID for which the associated illumination state is desired. + * + * @return YES if the associated control should be illuminated, NO otherwise. + */ +- (BOOL)illuminationStateForCommandIdentifier:(NSString *)commandID; + +@end diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index 290b9378..d305eb9d 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -11,79 +11,6 @@ #import "MIKMIDICommand.h" #import "MIKMIDIResponder.h" -/** - * Bit-mask constants used to specify MIDI responder types for mapping. - * Multiple responder types can be specified by ORing them together. - * @see -[MIKMIDIMappableResponder MIDIResponderTypeForCommandIdentifier:] - */ -typedef NS_OPTIONS(NSUInteger, MIKMIDIResponderType){ - /** - * Responder does not have a type. Cannot be mapped. - */ - MIKMIDIResponderTypeNone = 0, - - /** - * Type for a MIDI responder that can handle messages from a hardware absolute - * knob or slider. That is, one that sends control change messages with an absolute value - * depending on its position. - */ - MIKMIDIResponderTypeAbsoluteSliderOrKnob = 1 << 0, - - /** - * Type for a MIDI responder that can handle messages from a hardware relative - * knob. That is, a knob that sends a message for each "tick", and whose value - * depends on the direction (and possibly velocity) of the knob, rather than its - * absolute position. - */ - MIKMIDIResponderTypeRelativeKnob = 1 << 1, - - /** - * Type for a MIDI responder that can handle messages from a hardware turntable-like - * jog wheel. These are relative knobs, but typically have *much* higher resolution than - * a small relative knob. They may also have a touch/pressure sensitive top to detect when - * the user is touching, but not turning the wheel. - */ - MIKMIDIResponderTypeTurntableKnob = 1 << 2, - - /** - * Type for a MIDI responder that can handle messages from a hardware relative knob that - * sends messages to simulate an absolute knob. Relative knobs on (at least) Native Instruments - * controllers can be configured to send messages like an absolute knob. This can pose the problem - * of the knob continuing to turn past its limits (0 and 127) without additional messages being sent. - * These knobs can and will be mapped as a regular absolute knob for responders that include MIKMIDIResponderTypeAbsoluteSliderOrKnob - * but *not* MIKMIDIResponderTypeRelativeAbsoluteKnob in the type returned by -MIDIResponderTypeForCommandIdentifier: - */ - MIKMIDIResponderTypeRelativeAbsoluteKnob = 1 << 3, - - /** - * Type for a MIDI responder that can handle messages from a hardware button that sends a message when - * pressed down, and another message when released. - */ - MIKMIDIResponderTypePressReleaseButton = 1 << 4, - - /** - * Type for a MIDI responder that can handle messages from a hardware button that only sends a single - * message when pressed down, without sending a corresponding message upon release. - */ - MIKMIDIResponderTypePressButton = 1 << 5, - - /** - * Convenience type for a responder that can handle messages from any type of knob. - */ - MIKMIDIResponderTypeKnob = (MIKMIDIResponderTypeAbsoluteSliderOrKnob | MIKMIDIResponderTypeRelativeKnob | \ - MIKMIDIResponderTypeTurntableKnob | MIKMIDIResponderTypeRelativeAbsoluteKnob), - - /** - * Convenience type for a responder that can handle messages from any type of button. - */ - MIKMIDIResponderTypeButton = (MIKMIDIResponderTypePressButton | MIKMIDIResponderTypePressReleaseButton), - - /** - * Convenience type for a responder that can handle messages from any kind of control. - */ - MIKMIDIResponderTypeAll = NSUIntegerMax, -}; - @protocol MIKMIDIMappableResponder; @class MIKMIDIChannelVoiceCommand; @@ -303,148 +230,3 @@ typedef NS_OPTIONS(NSUInteger, MIKMIDIResponderType){ - (void)removeMappingItems:(NSSet *)mappingItems; @end - -/** - * MIKMIDIMappingItem contains information about a mapping between a physical MIDI control, - * and a single command supported by a particular MIDI responder object. - * - * MIKMIDIMappingItem specifies the command type, and MIDI channel for the commands sent by the - * mapped physical control along with the control's interaction type (e.g. knob, turntable, button, etc.). - * It also specifies the (software) MIDI responder to which incoming commands from the mapped control - * should be routed. - * - */ -@interface MIKMIDIMappingItem : NSObject - -/** - * Creates and initializes a new MIKMIDIMappingItem instance. - * - * @param MIDIResponderIdentifier The identifier for the MIDI responder object being mapped. - * @param commandIdentifier The identifer for the command to be mapped. - * - * @return An initialized MIKMIDIMappingItem instance. - */ -- (instancetype)initWithMIDIResponderIdentifier:(NSString *)MIDIResponderIdentifier andCommandIdentifier:(NSString *)commandIdentifier; - -/** - * Returns an NSString instance containing an XML representation of the receiver. - * The XML document returned by this method can be written to disk. - * - * @return An NSString containing an XML representation of the receiver. - * - * @see -writeToFileAtURL:error: - */ -- (NSString *)XMLStringRepresentation; - -// Properties - -/** - * The MIDI identifier for the (software) responder object being mapped. This is the same value as returned by calling -MIDIIdentifier - * on the responder to be mapped. - * - * This value can be used to retrieve the MIDI responder to which this mapping refers at runtime using - * -[NS/UIApplication MIDIResponderWithIdentifier]. - */ -@property (nonatomic, readonly) NSString *MIDIResponderIdentifier; - -/** - * The identifier for the command mapped by this mapping item. This will be one of the identifier's returned - * by the mapped responder's -commandIdentifiers method. - */ -@property (nonatomic, readonly) NSString *commandIdentifier; - -/** - * The interaction type for the physical control mapped by this item. This can be used to determine - * how to interpret the incoming MIDI messages mapped by this item. - */ -@property (nonatomic) MIKMIDIResponderType interactionType; - -/** - * If YES, value decreases as slider/knob goes left->right or top->bottom. - * This property is currently only relevant for knobs and sliders, and has no meaning for buttons or other responder types. - */ -@property (nonatomic, getter = isFlipped) BOOL flipped; - -/** - * The MIDI channel upon which commands are sent by the control mapped by this item. - */ -@property (nonatomic) NSInteger channel; - -/** - * The MIDI command type of commands sent by the control mapped by this item. - */ -@property (nonatomic) MIKMIDICommandType commandType; - -/** - * The control number of the control mapped by this item. - * This is either the note number (for Note On/Off commands) or controller number (for control change commands). - */ -@property (nonatomic) NSUInteger controlNumber; - -/** - * Optional additional key value pairs, which will be saved as attributes in this item's XML representation. Keys and values must be NSStrings. - */ -@property (nonatomic, copy) NSDictionary *additionalAttributes; - -@end - -/** - * This protocol defines methods that that must be implemented by MIDI responder objects to be mapped - * using MIKMIDIMappingGenerator, and to whom MIDI messages will selectively be routed using a MIDI mapping - * during normal operation. - */ -@protocol MIKMIDIMappableResponder - -@required -/** - * The list of identifiers for all commands supported by the receiver. - * - * A MIDI responder may want to handle incoming MIDI message from more than one control. For example, a view displaying - * a list of songs may want to support commands for browsing up and down the list with buttons, or with a knob, as well as a button - * to load the selected song. These commands would be for example, KnobBrowse, BrowseUp, BrowseDown, and Load. This way, multiple physical - * controls can be mapped to different functions of the same MIDI responder. - * - * @return An NSArray containing NSString identifers for all MIDI mappable commands supported by the receiver. - */ -- (NSArray *)commandIdentifiers; - -/** - * The MIDI responder types the receiver will allow to be mapped to the command specified by commandID. - * - * In the example given for -commandIdentifers, the "KnobBrowse" might be mappable to any physical knob, - * while BrowseUp, BrowseDown, and Load are mappable to buttons. The responder would return MIKMIDIResponderTypeKnob - * for @"KnobBrowse" while returning MIKMIDIResponderTypeButton for the other commands. - * - * @param commandID A command identifier string. - * - * @return A MIKMIDIResponderType bitfield specifing one or more responder type(s). - * - * @see MIKMIDIResponderType - */ -- (MIKMIDIResponderType)MIDIResponderTypeForCommandIdentifier:(NSString *)commandID; // Optional. If not implemented, MIKMIDIResponderTypeAll will be assumed. - -@optional - -/** - * Whether the physical control mapped to the commandID in the receiver should - * be illuminated, or not. - * - * Many hardware MIDI devices, e.g. DJ controllers, have buttons that can light - * up to show state for the associated function. For example, the play button - * could be illuminated when the software is playing. This method allows mapped - * MIDI responder objects to communicate the desired state of the physical control - * mapped to them. - * - * Currently MIKMIDI doesn't provide automatic support for actually updating - * physical LED status. This must be implemented in application code. For most devices, - * this can be accomplished by sending a MIDI message _to_ the device. The MIDI message - * should identical to the message that the relevant control sends when pressed, with - * a non-zero value to illumniate the control, or zero to turn illumination off. - * - * @param commandID The commandID for which the associated illumination state is desired. - * - * @return YES if the associated control should be illuminated, NO otherwise. - */ -- (BOOL)illuminationStateForCommandIdentifier:(NSString *)commandID; - -@end diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index db31db24..b56b3087 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -7,6 +7,7 @@ // #import "MIKMIDIMapping.h" +#import "MIKMIDIMappingItem.h" #import "MIKMIDICommand.h" #import "MIKMIDIChannelVoiceCommand.h" #import "MIKMIDIControlChangeCommand.h" @@ -443,301 +444,3 @@ - (NSString *)name } @end - -#pragma mark - - -@implementation MIKMIDIMappingItem - -- (instancetype)initWithMIDIResponderIdentifier:(NSString *)MIDIResponderIdentifier andCommandIdentifier:(NSString *)commandIdentifier; -{ - self = [super init]; - - if (self) { - _MIDIResponderIdentifier = [MIDIResponderIdentifier copy]; - _commandIdentifier = [commandIdentifier copy]; - } - - return self; -} - -- (id)init -{ - [NSException raise:NSInternalInconsistencyException format:@"-[MIKMIDIMappingItem init] is deprecated and should be replaced with a call to -initWithMIDIResponderIdentifier:andCommandIdentifier:."]; - return [self initWithMIDIResponderIdentifier:@"Unknown" andCommandIdentifier:@"Unknown"]; -} - -#if !TARGET_OS_IPHONE - -- (instancetype)initWithXMLElement:(NSXMLElement *)element; -{ - if (!element) { self = nil; return self; } - - NSError *error = nil; - - NSXMLElement *responderIdentifier = [[element nodesForXPath:@"ResponderIdentifier" error:&error] lastObject]; - if (!responderIdentifier) { - NSLog(@"Unable to read responder identifier from %@: %@", element, error); - self = nil; - return nil; - } - - NSXMLElement *commandIdentifier = [[element nodesForXPath:@"CommandIdentifier" error:&error] lastObject]; - if (!commandIdentifier) { - NSLog(@"Unable to read command identifier from %@: %@", element, error); - self = nil; - return nil; - } - - NSXMLElement *channel = [[element nodesForXPath:@"Channel" error:&error] lastObject]; - if (!channel) { - NSLog(@"Unable to read channel from %@: %@", element, error); - self = nil; - return nil; - } - - NSXMLElement *commandType = [[element nodesForXPath:@"CommandType" error:&error] lastObject]; - if (!commandType) { - NSLog(@"Unable to read command type from %@: %@", element, error); - } - - NSXMLElement *controlNumber = [[element nodesForXPath:@"ControlNumber" error:&error] lastObject]; - if (!controlNumber) { - NSLog(@"Unable to read control number from %@: %@", element, error); - self = nil; - return nil; - } - - NSXMLElement *interactionType = [[element nodesForXPath:@"@InteractionType" error:&error] lastObject]; - if (!interactionType) { - NSLog(@"Unable to read interaction type from %@: %@", element, error); - self = nil; - return nil; - } - - NSXMLElement *flippedStatus = [[element nodesForXPath:@"@Flipped" error:&error] lastObject]; - - NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; - for (NSXMLNode *attribute in [element attributes]) { - if (![[attribute stringValue] length]) continue; - if ([[attribute name] isEqualToString:@"InteractionType"]) continue; - if ([[attribute name] isEqualToString:@"Flipped"]) continue; - [attributes setObject:[attribute stringValue] forKey:[attribute name]]; - } - - self = [self initWithMIDIResponderIdentifier:[responderIdentifier stringValue] andCommandIdentifier:[commandIdentifier stringValue]]; - if (self) { - _channel = [[channel stringValue] integerValue]; - _commandType = [[commandType stringValue] integerValue]; - _controlNumber = [[controlNumber stringValue] integerValue]; - _interactionType = MIKMIDIMappingInteractionTypeForAttributeString([interactionType stringValue]); - _flipped = [[flippedStatus stringValue] boolValue]; - - _additionalAttributes = [attributes copy]; - } - return self; -} - -- (NSXMLDocument *)XMLRepresentation -{ - return [self privateXMLRepresentation]; -} - -- (NSXMLDocument *)privateXMLRepresentation -{ - NSXMLElement *responderIdentifier = [NSXMLElement elementWithName:@"ResponderIdentifier" stringValue:self.MIDIResponderIdentifier]; - NSXMLElement *commandIdentifier = [NSXMLElement elementWithName:@"CommandIdentifier" stringValue:self.commandIdentifier]; - NSXMLElement *channel = [NSXMLElement elementWithName:@"Channel"]; - [channel setStringValue:[@(self.channel) stringValue]]; - NSXMLElement *commandType = [NSXMLElement elementWithName:@"CommandType"]; - [commandType setStringValue:[@(self.commandType) stringValue]]; - NSXMLElement *controlNumber = [NSXMLElement elementWithName:@"ControlNumber"]; - [controlNumber setStringValue:[@(self.controlNumber) stringValue]]; - - NSXMLElement *interactionType = [[NSXMLElement alloc] initWithKind:NSXMLAttributeKind]; - [interactionType setName:@"InteractionType"]; - NSString *interactionTypeString = MIKMIDIMappingAttributeStringForInteractionType(self.interactionType); - [interactionType setStringValue:interactionTypeString]; - - NSXMLElement *flippedStatus = [[NSXMLElement alloc] initWithKind:NSXMLAttributeKind]; - [flippedStatus setName:@"Flipped"]; - NSString *flippedStatusString = self.flipped ? @"true" : @"false"; - [flippedStatus setStringValue:flippedStatusString]; - - NSMutableArray *attributes = [NSMutableArray arrayWithArray:@[interactionType, flippedStatus]]; - for (NSString *key in self.additionalAttributes) { - NSXMLElement *attributeElement = [[NSXMLElement alloc] initWithKind:NSXMLAttributeKind]; - NSString *stringValue = self.additionalAttributes[key]; - if (![stringValue isKindOfClass:[NSString class]]) { - NSLog(@"Ignoring additional attribute %@ : %@ because it is not a string.", key, stringValue); - continue; - } - [attributeElement setName:key]; - [attributeElement setStringValue:stringValue]; - [attributes addObject:attributeElement]; - } - - return [NSXMLElement elementWithName:@"MappingItem" - children:@[responderIdentifier, commandIdentifier, channel, commandType, controlNumber] - attributes:attributes]; -} - -#endif - -- (NSString *)XMLStringRepresentation -{ -#if !TARGET_OS_IPHONE - return [[self XMLRepresentation] XMLStringWithOptions:NSXMLNodePrettyPrint]; -#else - - int err = 0; - xmlTextWriterPtr writer = NULL; - xmlBufferPtr buffer = xmlBufferCreate(); - if (!buffer) { - NSLog(@"Unable to create XML buffer."); - goto CLEANUP_AND_EXIT; - } - - { - writer = xmlNewTextWriterMemory(buffer, 0); - if (!writer) { - xmlBufferFree(buffer); - NSLog(@"Unable to create XML writer."); - goto CLEANUP_AND_EXIT; - } - - xmlTextWriterSetIndent(writer, 1); - - err = xmlTextWriterStartElement(writer, BAD_CAST "MappingItem"); // - if (err < 0) { - NSLog(@"Unable to start XML MappingItem element: %i", err); - goto CLEANUP_AND_EXIT; - } - - NSString *interactionTypeString = MIKMIDIMappingAttributeStringForInteractionType(self.interactionType); - err = xmlTextWriterWriteAttribute(writer, BAD_CAST "InteractionType", BAD_CAST [interactionTypeString UTF8String]); - if (err < 0) { - NSLog(@"Unable to write InteractionType attribute for MappingItem element: %i", err); - goto CLEANUP_AND_EXIT; - } - - NSString *flippedStatusString = self.flipped ? @"true" : @"false"; - err = xmlTextWriterWriteAttribute(writer, BAD_CAST "Flipped", BAD_CAST [flippedStatusString UTF8String]); - if (err < 0) { - NSLog(@"Unable to write InteractionType attribute for MappingItem element: %i", err); - goto CLEANUP_AND_EXIT; - } - - for (NSString *key in self.additionalAttributes) { - NSString *stringValue = self.additionalAttributes[key]; - if (![stringValue isKindOfClass:[NSString class]]) { - NSLog(@"Ignoring additional attribute %@ : %@ because it is not a string.", key, stringValue); - continue; - } - - err = xmlTextWriterWriteAttribute(writer, BAD_CAST [key UTF8String], BAD_CAST [stringValue UTF8String]); - if (err < 0) { - NSLog(@"Unable to write MappingName attribute for Mapping element: %i", err); - goto CLEANUP_AND_EXIT; - } - } - - err = xmlTextWriterWriteElement(writer, BAD_CAST "ResponderIdentifier", BAD_CAST [self.MIDIResponderIdentifier UTF8String]); - if (err < 0) { - NSLog(@"Unable to write ResponderIdentifier element for mapping %@: %i", self, err); - goto CLEANUP_AND_EXIT; - } - - err = xmlTextWriterWriteElement(writer, BAD_CAST "CommandIdentifier", BAD_CAST [self.commandIdentifier UTF8String]); - if (err < 0) { - NSLog(@"Unable to write CommandIdentifier element for mapping %@: %i", self, err); - goto CLEANUP_AND_EXIT; - } - - err = xmlTextWriterWriteElement(writer, BAD_CAST "Channel", BAD_CAST [[@(self.channel) stringValue] UTF8String]); - if (err < 0) { - NSLog(@"Unable to write Channel element for mapping %@: %i", self, err); - goto CLEANUP_AND_EXIT; - } - - err = xmlTextWriterWriteElement(writer, BAD_CAST "CommandType", BAD_CAST [[@(self.commandType) stringValue] UTF8String]); - if (err < 0) { - NSLog(@"Unable to write CommandType element for mapping %@: %i", self, err); - goto CLEANUP_AND_EXIT; - } - - err = xmlTextWriterWriteElement(writer, BAD_CAST "ControlNumber", BAD_CAST [[@(self.controlNumber) stringValue] UTF8String]); - if (err < 0) { - NSLog(@"Unable to write ControlNumber element for mapping %@: %i", self, err); - goto CLEANUP_AND_EXIT; - } - - err = xmlTextWriterEndElement(writer); // - if (err < 0) { - NSLog(@"Unable to end XML MappingItem element: %i", err); - goto CLEANUP_AND_EXIT; - } - } - -CLEANUP_AND_EXIT: - if (writer) xmlFreeTextWriter(writer); - NSString *result = nil; - if (buffer && err >= 0) { - result = [[NSString alloc] initWithCString:(const char *)buffer->content encoding:NSUTF8StringEncoding]; - xmlBufferFree(buffer); - } - - return result; -#endif -} - -- (id)copyWithZone:(NSZone *)zone -{ - MIKMIDIMappingItem *result = [[MIKMIDIMappingItem alloc] initWithMIDIResponderIdentifier:self.MIDIResponderIdentifier andCommandIdentifier:self.commandIdentifier]; - result.interactionType = self.interactionType; - result.flipped = self.flipped; - result.channel = self.channel; - result.commandType = self.commandType; - result.controlNumber = self.controlNumber; - result.additionalAttributes = self.additionalAttributes; - - return result; -} - -- (BOOL)isEqual:(MIKMIDIMappingItem *)otherMappingItem -{ - if (self == otherMappingItem) return YES; - - if (self.controlNumber != otherMappingItem.controlNumber) return NO; - if (self.channel != otherMappingItem.channel) return NO; - if (self.commandType != otherMappingItem.commandType) return NO; - if (self.interactionType != otherMappingItem.interactionType) return NO; - if (self.flipped != otherMappingItem.flipped) return NO; - if (![self.MIDIResponderIdentifier isEqualToString:otherMappingItem.MIDIResponderIdentifier]) return NO; - if (![self.commandIdentifier isEqualToString:otherMappingItem.commandIdentifier]) return NO; - if (![self.additionalAttributes isEqualToDictionary:otherMappingItem.additionalAttributes]) return NO; - - return YES; -} - -- (NSUInteger)hash -{ - // Only depend on non-mutable properties - NSUInteger result = [_MIDIResponderIdentifier hash]; - result += [_commandIdentifier hash]; - - return result; -} - -- (NSString *)description -{ - NSMutableString *result = [NSMutableString stringWithFormat:@"%@ %@ %@ CommandID: %@ Channel %li MIDI Command %li Control Number %lu flipped %i", [super description], MIKMIDIMappingAttributeStringForInteractionType(self.interactionType), self.MIDIResponderIdentifier, self.commandIdentifier, (long)self.channel, (long)self.commandType, (unsigned long)self.controlNumber, (int)self.flipped]; - if ([self.additionalAttributes count]) { - for (NSString *key in self.additionalAttributes) { - NSString *value = self.additionalAttributes[key]; - [result appendFormat:@" %@: %@", key, value]; - } - } - return result; -} - -@end diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index d7dca50d..fb8a6f1f 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -10,6 +10,7 @@ #import "MIKMIDI.h" #import "MIKMIDIMapping.h" +#import "MIKMIDIMappingItem.h" #import "MIKMIDIPrivateUtilities.h" #if !__has_feature(objc_arc) diff --git a/Source/MIKMIDIMappingItem.h b/Source/MIKMIDIMappingItem.h new file mode 100644 index 00000000..d998003a --- /dev/null +++ b/Source/MIKMIDIMappingItem.h @@ -0,0 +1,95 @@ +// +// MIKMIDIMappingItem.h +// MIKMIDI +// +// Created by Andrew Madsen on 5/20/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import +#import "MIKMIDIMappableResponder.h" +#import "MIKMIDICommand.h" + +/** + * MIKMIDIMappingItem contains information about a mapping between a physical MIDI control, + * and a single command supported by a particular MIDI responder object. + * + * MIKMIDIMappingItem specifies the command type, and MIDI channel for the commands sent by the + * mapped physical control along with the control's interaction type (e.g. knob, turntable, button, etc.). + * It also specifies the (software) MIDI responder to which incoming commands from the mapped control + * should be routed. + * + */ +@interface MIKMIDIMappingItem : NSObject + +/** + * Creates and initializes a new MIKMIDIMappingItem instance. + * + * @param MIDIResponderIdentifier The identifier for the MIDI responder object being mapped. + * @param commandIdentifier The identifer for the command to be mapped. + * + * @return An initialized MIKMIDIMappingItem instance. + */ +- (instancetype)initWithMIDIResponderIdentifier:(NSString *)MIDIResponderIdentifier andCommandIdentifier:(NSString *)commandIdentifier; + +/** + * Returns an NSString instance containing an XML representation of the receiver. + * The XML document returned by this method can be written to disk. + * + * @return An NSString containing an XML representation of the receiver. + * + * @see -writeToFileAtURL:error: + */ +- (NSString *)XMLStringRepresentation; + +// Properties + +/** + * The MIDI identifier for the (software) responder object being mapped. This is the same value as returned by calling -MIDIIdentifier + * on the responder to be mapped. + * + * This value can be used to retrieve the MIDI responder to which this mapping refers at runtime using + * -[NS/UIApplication MIDIResponderWithIdentifier]. + */ +@property (nonatomic, readonly) NSString *MIDIResponderIdentifier; + +/** + * The identifier for the command mapped by this mapping item. This will be one of the identifier's returned + * by the mapped responder's -commandIdentifiers method. + */ +@property (nonatomic, readonly) NSString *commandIdentifier; + +/** + * The interaction type for the physical control mapped by this item. This can be used to determine + * how to interpret the incoming MIDI messages mapped by this item. + */ +@property (nonatomic) MIKMIDIResponderType interactionType; + +/** + * If YES, value decreases as slider/knob goes left->right or top->bottom. + * This property is currently only relevant for knobs and sliders, and has no meaning for buttons or other responder types. + */ +@property (nonatomic, getter = isFlipped) BOOL flipped; + +/** + * The MIDI channel upon which commands are sent by the control mapped by this item. + */ +@property (nonatomic) NSInteger channel; + +/** + * The MIDI command type of commands sent by the control mapped by this item. + */ +@property (nonatomic) MIKMIDICommandType commandType; + +/** + * The control number of the control mapped by this item. + * This is either the note number (for Note On/Off commands) or controller number (for control change commands). + */ +@property (nonatomic) NSUInteger controlNumber; + +/** + * Optional additional key value pairs, which will be saved as attributes in this item's XML representation. Keys and values must be NSStrings. + */ +@property (nonatomic, copy) NSDictionary *additionalAttributes; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIMappingItem.m b/Source/MIKMIDIMappingItem.m new file mode 100644 index 00000000..7ad1b101 --- /dev/null +++ b/Source/MIKMIDIMappingItem.m @@ -0,0 +1,308 @@ +// +// MIKMIDIMappingItem.m +// MIKMIDI +// +// Created by Andrew Madsen on 5/20/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIMappingItem.h" +#import "MIKMIDIPrivateUtilities.h" +#import "MIKMIDIUtilities.h" + +@implementation MIKMIDIMappingItem + +- (instancetype)initWithMIDIResponderIdentifier:(NSString *)MIDIResponderIdentifier andCommandIdentifier:(NSString *)commandIdentifier; +{ + self = [super init]; + + if (self) { + _MIDIResponderIdentifier = [MIDIResponderIdentifier copy]; + _commandIdentifier = [commandIdentifier copy]; + } + + return self; +} + +- (id)init +{ + [NSException raise:NSInternalInconsistencyException format:@"-[MIKMIDIMappingItem init] is deprecated and should be replaced with a call to -initWithMIDIResponderIdentifier:andCommandIdentifier:."]; + return [self initWithMIDIResponderIdentifier:@"Unknown" andCommandIdentifier:@"Unknown"]; +} + +#if !TARGET_OS_IPHONE + +- (instancetype)initWithXMLElement:(NSXMLElement *)element; +{ + if (!element) { self = nil; return self; } + + NSError *error = nil; + + NSXMLElement *responderIdentifier = [[element nodesForXPath:@"ResponderIdentifier" error:&error] lastObject]; + if (!responderIdentifier) { + NSLog(@"Unable to read responder identifier from %@: %@", element, error); + self = nil; + return nil; + } + + NSXMLElement *commandIdentifier = [[element nodesForXPath:@"CommandIdentifier" error:&error] lastObject]; + if (!commandIdentifier) { + NSLog(@"Unable to read command identifier from %@: %@", element, error); + self = nil; + return nil; + } + + NSXMLElement *channel = [[element nodesForXPath:@"Channel" error:&error] lastObject]; + if (!channel) { + NSLog(@"Unable to read channel from %@: %@", element, error); + self = nil; + return nil; + } + + NSXMLElement *commandType = [[element nodesForXPath:@"CommandType" error:&error] lastObject]; + if (!commandType) { + NSLog(@"Unable to read command type from %@: %@", element, error); + } + + NSXMLElement *controlNumber = [[element nodesForXPath:@"ControlNumber" error:&error] lastObject]; + if (!controlNumber) { + NSLog(@"Unable to read control number from %@: %@", element, error); + self = nil; + return nil; + } + + NSXMLElement *interactionType = [[element nodesForXPath:@"@InteractionType" error:&error] lastObject]; + if (!interactionType) { + NSLog(@"Unable to read interaction type from %@: %@", element, error); + self = nil; + return nil; + } + + NSXMLElement *flippedStatus = [[element nodesForXPath:@"@Flipped" error:&error] lastObject]; + + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + for (NSXMLNode *attribute in [element attributes]) { + if (![[attribute stringValue] length]) continue; + if ([[attribute name] isEqualToString:@"InteractionType"]) continue; + if ([[attribute name] isEqualToString:@"Flipped"]) continue; + [attributes setObject:[attribute stringValue] forKey:[attribute name]]; + } + + self = [self initWithMIDIResponderIdentifier:[responderIdentifier stringValue] andCommandIdentifier:[commandIdentifier stringValue]]; + if (self) { + _channel = [[channel stringValue] integerValue]; + _commandType = [[commandType stringValue] integerValue]; + _controlNumber = [[controlNumber stringValue] integerValue]; + _interactionType = MIKMIDIMappingInteractionTypeForAttributeString([interactionType stringValue]); + _flipped = [[flippedStatus stringValue] boolValue]; + + _additionalAttributes = [attributes copy]; + } + return self; +} + +- (NSXMLDocument *)XMLRepresentation +{ + return [self privateXMLRepresentation]; +} + +- (NSXMLDocument *)privateXMLRepresentation +{ + NSXMLElement *responderIdentifier = [NSXMLElement elementWithName:@"ResponderIdentifier" stringValue:self.MIDIResponderIdentifier]; + NSXMLElement *commandIdentifier = [NSXMLElement elementWithName:@"CommandIdentifier" stringValue:self.commandIdentifier]; + NSXMLElement *channel = [NSXMLElement elementWithName:@"Channel"]; + [channel setStringValue:[@(self.channel) stringValue]]; + NSXMLElement *commandType = [NSXMLElement elementWithName:@"CommandType"]; + [commandType setStringValue:[@(self.commandType) stringValue]]; + NSXMLElement *controlNumber = [NSXMLElement elementWithName:@"ControlNumber"]; + [controlNumber setStringValue:[@(self.controlNumber) stringValue]]; + + NSXMLElement *interactionType = [[NSXMLElement alloc] initWithKind:NSXMLAttributeKind]; + [interactionType setName:@"InteractionType"]; + NSString *interactionTypeString = MIKMIDIMappingAttributeStringForInteractionType(self.interactionType); + [interactionType setStringValue:interactionTypeString]; + + NSXMLElement *flippedStatus = [[NSXMLElement alloc] initWithKind:NSXMLAttributeKind]; + [flippedStatus setName:@"Flipped"]; + NSString *flippedStatusString = self.flipped ? @"true" : @"false"; + [flippedStatus setStringValue:flippedStatusString]; + + NSMutableArray *attributes = [NSMutableArray arrayWithArray:@[interactionType, flippedStatus]]; + for (NSString *key in self.additionalAttributes) { + NSXMLElement *attributeElement = [[NSXMLElement alloc] initWithKind:NSXMLAttributeKind]; + NSString *stringValue = self.additionalAttributes[key]; + if (![stringValue isKindOfClass:[NSString class]]) { + NSLog(@"Ignoring additional attribute %@ : %@ because it is not a string.", key, stringValue); + continue; + } + [attributeElement setName:key]; + [attributeElement setStringValue:stringValue]; + [attributes addObject:attributeElement]; + } + + return [NSXMLElement elementWithName:@"MappingItem" + children:@[responderIdentifier, commandIdentifier, channel, commandType, controlNumber] + attributes:attributes]; +} + +#endif + +- (NSString *)XMLStringRepresentation +{ +#if !TARGET_OS_IPHONE + return [[self XMLRepresentation] XMLStringWithOptions:NSXMLNodePrettyPrint]; +#else + + int err = 0; + xmlTextWriterPtr writer = NULL; + xmlBufferPtr buffer = xmlBufferCreate(); + if (!buffer) { + NSLog(@"Unable to create XML buffer."); + goto CLEANUP_AND_EXIT; + } + + { + writer = xmlNewTextWriterMemory(buffer, 0); + if (!writer) { + xmlBufferFree(buffer); + NSLog(@"Unable to create XML writer."); + goto CLEANUP_AND_EXIT; + } + + xmlTextWriterSetIndent(writer, 1); + + err = xmlTextWriterStartElement(writer, BAD_CAST "MappingItem"); // + if (err < 0) { + NSLog(@"Unable to start XML MappingItem element: %i", err); + goto CLEANUP_AND_EXIT; + } + + NSString *interactionTypeString = MIKMIDIMappingAttributeStringForInteractionType(self.interactionType); + err = xmlTextWriterWriteAttribute(writer, BAD_CAST "InteractionType", BAD_CAST [interactionTypeString UTF8String]); + if (err < 0) { + NSLog(@"Unable to write InteractionType attribute for MappingItem element: %i", err); + goto CLEANUP_AND_EXIT; + } + + NSString *flippedStatusString = self.flipped ? @"true" : @"false"; + err = xmlTextWriterWriteAttribute(writer, BAD_CAST "Flipped", BAD_CAST [flippedStatusString UTF8String]); + if (err < 0) { + NSLog(@"Unable to write InteractionType attribute for MappingItem element: %i", err); + goto CLEANUP_AND_EXIT; + } + + for (NSString *key in self.additionalAttributes) { + NSString *stringValue = self.additionalAttributes[key]; + if (![stringValue isKindOfClass:[NSString class]]) { + NSLog(@"Ignoring additional attribute %@ : %@ because it is not a string.", key, stringValue); + continue; + } + + err = xmlTextWriterWriteAttribute(writer, BAD_CAST [key UTF8String], BAD_CAST [stringValue UTF8String]); + if (err < 0) { + NSLog(@"Unable to write MappingName attribute for Mapping element: %i", err); + goto CLEANUP_AND_EXIT; + } + } + + err = xmlTextWriterWriteElement(writer, BAD_CAST "ResponderIdentifier", BAD_CAST [self.MIDIResponderIdentifier UTF8String]); + if (err < 0) { + NSLog(@"Unable to write ResponderIdentifier element for mapping %@: %i", self, err); + goto CLEANUP_AND_EXIT; + } + + err = xmlTextWriterWriteElement(writer, BAD_CAST "CommandIdentifier", BAD_CAST [self.commandIdentifier UTF8String]); + if (err < 0) { + NSLog(@"Unable to write CommandIdentifier element for mapping %@: %i", self, err); + goto CLEANUP_AND_EXIT; + } + + err = xmlTextWriterWriteElement(writer, BAD_CAST "Channel", BAD_CAST [[@(self.channel) stringValue] UTF8String]); + if (err < 0) { + NSLog(@"Unable to write Channel element for mapping %@: %i", self, err); + goto CLEANUP_AND_EXIT; + } + + err = xmlTextWriterWriteElement(writer, BAD_CAST "CommandType", BAD_CAST [[@(self.commandType) stringValue] UTF8String]); + if (err < 0) { + NSLog(@"Unable to write CommandType element for mapping %@: %i", self, err); + goto CLEANUP_AND_EXIT; + } + + err = xmlTextWriterWriteElement(writer, BAD_CAST "ControlNumber", BAD_CAST [[@(self.controlNumber) stringValue] UTF8String]); + if (err < 0) { + NSLog(@"Unable to write ControlNumber element for mapping %@: %i", self, err); + goto CLEANUP_AND_EXIT; + } + + err = xmlTextWriterEndElement(writer); // + if (err < 0) { + NSLog(@"Unable to end XML MappingItem element: %i", err); + goto CLEANUP_AND_EXIT; + } + } + +CLEANUP_AND_EXIT: + if (writer) xmlFreeTextWriter(writer); + NSString *result = nil; + if (buffer && err >= 0) { + result = [[NSString alloc] initWithCString:(const char *)buffer->content encoding:NSUTF8StringEncoding]; + xmlBufferFree(buffer); + } + + return result; +#endif +} + +- (id)copyWithZone:(NSZone *)zone +{ + MIKMIDIMappingItem *result = [[MIKMIDIMappingItem alloc] initWithMIDIResponderIdentifier:self.MIDIResponderIdentifier andCommandIdentifier:self.commandIdentifier]; + result.interactionType = self.interactionType; + result.flipped = self.flipped; + result.channel = self.channel; + result.commandType = self.commandType; + result.controlNumber = self.controlNumber; + result.additionalAttributes = self.additionalAttributes; + + return result; +} + +- (BOOL)isEqual:(MIKMIDIMappingItem *)otherMappingItem +{ + if (self == otherMappingItem) return YES; + + if (self.controlNumber != otherMappingItem.controlNumber) return NO; + if (self.channel != otherMappingItem.channel) return NO; + if (self.commandType != otherMappingItem.commandType) return NO; + if (self.interactionType != otherMappingItem.interactionType) return NO; + if (self.flipped != otherMappingItem.flipped) return NO; + if (![self.MIDIResponderIdentifier isEqualToString:otherMappingItem.MIDIResponderIdentifier]) return NO; + if (![self.commandIdentifier isEqualToString:otherMappingItem.commandIdentifier]) return NO; + if (![self.additionalAttributes isEqualToDictionary:otherMappingItem.additionalAttributes]) return NO; + + return YES; +} + +- (NSUInteger)hash +{ + // Only depend on non-mutable properties + NSUInteger result = [_MIDIResponderIdentifier hash]; + result += [_commandIdentifier hash]; + + return result; +} + +- (NSString *)description +{ + NSMutableString *result = [NSMutableString stringWithFormat:@"%@ %@ %@ CommandID: %@ Channel %li MIDI Command %li Control Number %lu flipped %i", [super description], MIKMIDIMappingAttributeStringForInteractionType(self.interactionType), self.MIDIResponderIdentifier, self.commandIdentifier, (long)self.channel, (long)self.commandType, (unsigned long)self.controlNumber, (int)self.flipped]; + if ([self.additionalAttributes count]) { + for (NSString *key in self.additionalAttributes) { + NSString *value = self.additionalAttributes[key]; + [result appendFormat:@" %@: %@", key, value]; + } + } + return result; +} + +@end + diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index 7d594494..636c5594 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -8,7 +8,8 @@ #import #import -#import "MIKMIDIMapping.h" +#import "MIKMIDIMappableResponder.h" +#import "MIKMIDICommand.h" NSString *MIKStringPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error); BOOL MIKSetStringPropertyOnMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSString *string, NSError *__autoreleasing*error); From 42b6de0a8d11645d3ebb9f541bfb25a4082ceca5 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 20 May 2015 15:30:36 -0600 Subject: [PATCH 139/284] Fixed bug that caused importing a mapping to sometimes fail if there was an existing user mapping with the same name. --- Source/MIKMIDIMappingManager.m | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index b85b1549..f8785994 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -115,9 +115,9 @@ - (NSSet *)userMappingsForControllerName:(NSString *)name return result; } -- (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; +- (MIKMIDIMapping *)userMappingWithName:(NSString *)mappingName; { - for (MIKMIDIMapping *mapping in self.mappings) { + for (MIKMIDIMapping *mapping in self.userMappings) { if ([mapping.name isEqualToString:mappingName]) { return mapping; } @@ -125,6 +125,22 @@ - (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; return nil; } +- (MIKMIDIMapping *)bundledMappingWithName:(NSString *)mappingName; +{ + for (MIKMIDIMapping *mapping in self.bundledMappings) { + if ([mapping.name isEqualToString:mappingName]) { + return mapping; + } + } + return nil; +} + +- (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; +{ + MIKMIDIMapping *result = [self userMappingWithName:mappingName]; + return result ?: [self bundledMappingWithName:mappingName]; +} + - (MIKMIDIMapping *)importMappingFromFileAtURL:(NSURL *)URL overwritingExistingMapping:(BOOL)shouldOverwrite error:(NSError **)error; { #if !TARGET_OS_IPHONE @@ -275,7 +291,7 @@ - (NSSet *)mappings { return [self.bundledMappings setByAddingObjectsFromSet:sel - (void)addUserMappingsObject:(MIKMIDIMapping *)mapping { - MIKMIDIMapping *existing = [self mappingWithName:mapping.name]; + MIKMIDIMapping *existing = [self userMappingWithName:mapping.name]; if (existing) [self.internalUserMappings removeObject:existing]; mapping.bundledMapping = NO; [self.internalUserMappings addObject:mapping]; From 413876ba2b3cb0e3d270d2da9bf6d4f3bba5fe8d Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 20 May 2015 17:07:15 -0500 Subject: [PATCH 140/284] MIKMIDISequencer now sends out a notification prior to looping playback. Also added syncedClock properties to MIKMIDIClock and MIKMIDISequencer. --- Source/MIKMIDIClock.h | 17 +++++++++++++ Source/MIKMIDIClock.m | 53 ++++++++++++++++++++++++++++++++++++++- Source/MIKMIDISequencer.h | 9 +++++++ Source/MIKMIDISequencer.m | 31 +++++++++++++++-------- 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 001ef363..a5b7f374 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -99,5 +99,22 @@ */ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; + +/** + * A readonly copy of the clock that remains synced with this instance. + * + * This clock can be queried and will always return the same timing information + * as the clock instance that dispensed the synced clock. + * + * Attempting to call -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced + * has no effect. + */ +- (MIKMIDIClock *)syncedClock; + +/** + * The tempo that was set in the last call to -setMusicTimeStamp:withTempo:atMIDITimeStamp: + */ +@property (readonly, nonatomic) Float64 tempo; + @end diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index a76cb111..40460030 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -13,13 +13,24 @@ #error MIKMIDIClock.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target #endif + +#pragma mark - +@interface MIKMIDISyncedClockProxy : NSProxy ++ (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock; +@property (readonly, nonatomic) MIKMIDIClock *masterClock; +@end + + +#pragma mark - @interface MIKMIDIClock () +@property (nonatomic) Float64 tempo; @property (nonatomic) MIDITimeStamp timeStampZero; @property (nonatomic) Float64 musicTimeStampsPerMIDITimeStamp; @property (nonatomic) Float64 midiTimeStampsPerMusicTimeStamp; @end +#pragma mark - @implementation MIKMIDIClock #pragma mark - Lifecycle @@ -37,6 +48,7 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp Float64 secondsPerMusicTimeStamp = 1.0 / (tempo / 60.0); Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; + self.tempo = tempo; self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; @@ -44,7 +56,8 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { - return (midiTimeStamp - self.timeStampZero) * self.musicTimeStampsPerMIDITimeStamp; + MIDITimeStamp timeStampZero = self.timeStampZero; + return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * self.musicTimeStampsPerMIDITimeStamp) : -((midiTimeStamp - timeStampZero) * self.musicTimeStampsPerMIDITimeStamp); } - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp @@ -87,4 +100,42 @@ - (id)copyWithZone:(NSZone *)zone return clock; } +#pragma mark - Synced Clock + +- (MIKMIDIClock *)syncedClock +{ + return (MIKMIDIClock *)[MIKMIDISyncedClockProxy syncedClockWithClock:self]; +} + @end + + +#pragma mark - +@implementation MIKMIDISyncedClockProxy + ++ (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock +{ + MIKMIDISyncedClockProxy *proxy = [self alloc]; + proxy->_masterClock = masterClock; + return proxy; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + SEL selector = invocation.selector; + if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; + + if (selector == @selector(syncedClock)) { + MIKMIDISyncedClockProxy *syncedClock = self; + return [invocation setReturnValue:&syncedClock]; + } + + [invocation invokeWithTarget:self.masterClock]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel +{ + return [self.masterClock methodSignatureForSelector:sel]; +} + +@end \ No newline at end of file diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index e475f86a..b8c88786 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -15,6 +15,7 @@ @class MIKMIDICommand; @class MIKMIDIDestinationEndpoint; @class MIKMIDISynthesizer; +@class MIKMIDIClock; /** * Types of click track statuses, that determine when the click track will be audible. @@ -311,4 +312,12 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (copy, nonatomic) NSSet *recordEnabledTracks; +/** + * An MIKMIDIClock that is synced with the sequencer's internal clock. + */ +@property (readonly, nonatomic) MIKMIDIClock *syncedClock; + @end + + +FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 8e368313..6e005821 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -29,6 +29,8 @@ #define MIKMIDISequencerDefaultTempo 120 +NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; + #pragma mark - @@ -91,6 +93,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence if (self = [super init]) { _sequence = sequence; _clock = [MIKMIDIClock clock]; + _syncedClock = [_clock syncedClock]; _loopEndTimeStamp = -1; _preRoll = 4; _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; @@ -275,6 +278,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIDITimeStamp loopStartMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:loopStartTimeStamp + loopLength]; [self updateClockWithMusicTimeStamp:loopStartTimeStamp tempo:tempo atMIDITimeStamp:loopStartMIDITimeStamp]; + + [[NSNotificationCenter defaultCenter] postNotificationName:MIKMIDISequencerWillLoopNotification object:self userInfo:nil]; [self processSequenceStartingFromMIDITimeStamp:loopStartMIDITimeStamp]; } } else if (!self.isRecording) { // Don't stop automatically during recording @@ -360,6 +365,20 @@ - (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp } } +- (MIKMIDIClock *)clockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + MIKMIDIClock *clock; + for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { + clock = self.historicalClocks[historicalClockTimeStamp]; + } else { + break; + } + } + if (!clock) clock = self.clock; + return clock; +} + - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { // Override tempo if neccessary @@ -450,16 +469,8 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command if (!self.isRecording) return; MIDITimeStamp midiTimeStamp = command.midiTimestamp; - MIKMIDIClock *clockAtTimeStamp; - for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { - if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { - clockAtTimeStamp = self.historicalClocks[historicalClockTimeStamp]; - } else { - break; - } - } - if (!clockAtTimeStamp) clockAtTimeStamp = self.clock; - + MIKMIDIClock *clockAtTimeStamp = [self clockForMIDITimeStamp:midiTimeStamp]; + MusicTimeStamp playbackOffset = self.playbackOffset; MusicTimeStamp musicTimeStamp = [clockAtTimeStamp musicTimeStampForMIDITimeStamp:midiTimeStamp] - playbackOffset; From 9cc5ed28098ecb338efa2104ba23d69d095a7a55 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 20 May 2015 17:08:42 -0500 Subject: [PATCH 141/284] Fixed typo in documentation --- Source/MIKMIDIClock.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index a5b7f374..f4abf183 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -107,7 +107,7 @@ * as the clock instance that dispensed the synced clock. * * Attempting to call -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced - * has no effect. + * clock has no effect. */ - (MIKMIDIClock *)syncedClock; From 63f3621ae9ad8bb29a3d39ce799fec451b3bb5a1 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 21 May 2015 10:16:38 -0500 Subject: [PATCH 142/284] Added -syncedClockForMIDITimeStamp: to MIKMIDISequencer --- Source/MIKMIDIClock.h | 6 +++--- Source/MIKMIDIClock.m | 6 ++++-- Source/MIKMIDISequencer.h | 33 +++++++++++++++++++++++++++++++++ Source/MIKMIDISequencer.m | 22 ++++++++++++++++------ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index f4abf183..0d74a7c9 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -106,10 +106,10 @@ * This clock can be queried and will always return the same timing information * as the clock instance that dispensed the synced clock. * - * Attempting to call -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced - * clock has no effect. + * Calling -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced clock + * has no effect. */ -- (MIKMIDIClock *)syncedClock; +@property (readonly, nonatomic) MIKMIDIClock *syncedClock; /** * The tempo that was set in the last call to -setMusicTimeStamp:withTempo:atMIDITimeStamp: diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 40460030..6bd9c9ca 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -100,11 +100,13 @@ - (id)copyWithZone:(NSZone *)zone return clock; } -#pragma mark - Synced Clock +#pragma mark - Properties +@synthesize syncedClock = _syncedClock; - (MIKMIDIClock *)syncedClock { - return (MIKMIDIClock *)[MIKMIDISyncedClockProxy syncedClockWithClock:self]; + if (!_syncedClock) _syncedClock = (MIKMIDIClock *)[MIKMIDISyncedClockProxy syncedClockWithClock:self]; + return _syncedClock; } @end diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index b8c88786..0973eb38 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -209,6 +209,31 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track; +#pragma mark - Clock + +/** + * Returns a clock synced to a clock that contains all of the timing information + * that was valid at the specified midiTimeStamp. + * + * Historical clocks older than 1 second may not be available, as the clock + * history is pruned by the sequencer prior to tempo changes or looping playback. + * + * Historical clocks are also not available after stopping playback, and + * immediately after setting currentTimeStamp. + * + * If no historical clock is available, this method will return the same + * synced clock as -syncedClock. + * + * @param midiTimeStamp The MIDITimeStamp that you would like a synced clock for. + * + * @return A clock synced to a clock that contains all of the timing information + * that was valid at the specified midiTimeStamp, or the current -syncedClock, + * if no historical clock could be found. + * + * @see -syncedClock + */ +- (MIKMIDIClock *)syncedClockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; + #pragma mark - Properties /** @@ -314,6 +339,14 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * An MIKMIDIClock that is synced with the sequencer's internal clock. + * + * Shortly before a tempo change, or before looping playback, this clock + * will already be updated with the future timing information. + * + * If you need historical timing information use -syncedClockForMIDITimeStamp: + * instead. + * + * @see -syncedClockForMIDITimeStamp: */ @property (readonly, nonatomic) MIKMIDIClock *syncedClock; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 6e005821..cd5f18df 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -27,7 +27,8 @@ #error MIKMIDISequencer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target #endif -#define MIKMIDISequencerDefaultTempo 120 +#define kDefaultTempo 120 +#define kDurationToKeepHistoricalClocks 1.0 NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; @@ -93,7 +94,6 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence if (self = [super init]) { _sequence = sequence; _clock = [MIKMIDIClock clock]; - _syncedClock = [_clock syncedClock]; _loopEndTimeStamp = -1; _preRoll = 4; _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; @@ -139,7 +139,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi self.startingTimeStamp = startingTimeStamp; Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; - if (!startingTempo) startingTempo = MIKMIDISequencerDefaultTempo; + if (!startingTempo) startingTempo = kDefaultTempo; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; self.playing = YES; @@ -216,7 +216,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (self.needsCurrentTempoUpdate) { if (!tempoEventsByTimeStamp.count) { if (!overrideTempo) overrideTempo = [self.sequence tempoAtTimeStamp:fromMusicTimeStamp]; - if (!overrideTempo) overrideTempo = MIKMIDISequencerDefaultTempo; + if (!overrideTempo) overrideTempo = kDefaultTempo; MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:fromMusicTimeStamp tempo:overrideTempo]; NSNumber *timeStampKey = @(fromMusicTimeStamp); @@ -273,7 +273,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (calculatedToMusicTimeStamp > toMusicTimeStamp) { [self recordAllPendingNoteEventsWithOffTimeStamp:loopEndTimeStamp]; Float64 tempo = [sequence tempoAtTimeStamp:loopStartTimeStamp]; - if (!tempo) tempo = MIKMIDISequencerDefaultTempo; + if (!tempo) tempo = kDefaultTempo; MusicTimeStamp loopLength = loopEndTimeStamp - loopStartTimeStamp; MIDITimeStamp loopStartMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:loopStartTimeStamp + loopLength]; @@ -379,6 +379,11 @@ - (MIKMIDIClock *)clockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp return clock; } +- (MIKMIDIClock *)syncedClockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + return [[self clockForMIDITimeStamp:midiTimeStamp] syncedClock]; +} + - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { // Override tempo if neccessary @@ -396,7 +401,7 @@ - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Floa NSMutableOrderedSet *historicalClockMIDITimeStamps = self.historicalClockMIDITimeStamps; // Remove clocks old enough to not be needed anymore - MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - [MIKMIDIClock midiTimeStampsPerTimeInterval:1]; + MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - [MIKMIDIClock midiTimeStampsPerTimeInterval:kDurationToKeepHistoricalClocks]; NSUInteger count = historicalClockMIDITimeStamps.count; NSMutableArray *timeStampsToRemove = [NSMutableArray array]; NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; @@ -695,6 +700,11 @@ - (void)setTempo:(Float64)tempo } } +- (MIKMIDIClock *)syncedClock +{ + return self.clock.syncedClock; +} + @end #pragma mark - From 34018e7428b507faa57b21f01ab8e4d2afe37c1c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 21 May 2015 09:49:33 -0600 Subject: [PATCH 143/284] Fixed build errors on iOS. Introduced in 4a7c0c2. --- Source/MIKMIDIMappingItem.m | 4 ++++ Source/MIKMIDIMappingXMLParser.m | 1 + 2 files changed, 5 insertions(+) diff --git a/Source/MIKMIDIMappingItem.m b/Source/MIKMIDIMappingItem.m index 7ad1b101..cdf81003 100644 --- a/Source/MIKMIDIMappingItem.m +++ b/Source/MIKMIDIMappingItem.m @@ -10,6 +10,10 @@ #import "MIKMIDIPrivateUtilities.h" #import "MIKMIDIUtilities.h" +#if TARGET_OS_IPHONE +#import +#endif + @implementation MIKMIDIMappingItem - (instancetype)initWithMIDIResponderIdentifier:(NSString *)MIDIResponderIdentifier andCommandIdentifier:(NSString *)commandIdentifier; diff --git a/Source/MIKMIDIMappingXMLParser.m b/Source/MIKMIDIMappingXMLParser.m index d09876d9..5be67ef6 100644 --- a/Source/MIKMIDIMappingXMLParser.m +++ b/Source/MIKMIDIMappingXMLParser.m @@ -8,6 +8,7 @@ #import "MIKMIDIMappingXMLParser.h" #import "MIKMIDIMapping.h" +#import "MIKMIDIMappingItem.h" #import "MIKMIDIUtilities.h" @interface NSString (MIKMIDIMappingXMLParserUtilities) From 3912ea62d56a86d1d3f001a6d0cf6bb45a669696 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 21 May 2015 11:25:12 -0500 Subject: [PATCH 144/284] Documentation improvements. --- Source/MIKMIDIClock.h | 7 +++++-- Source/MIKMIDISequencer.h | 25 +++++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 0d74a7c9..e17ab170 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -103,8 +103,8 @@ /** * A readonly copy of the clock that remains synced with this instance. * - * This clock can be queried and will always return the same timing information - * as the clock instance that dispensed the synced clock. + * This clock can be queried and will always return the same tempo and timing + * information as the clock instance that dispensed the synced clock. * * Calling -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced clock * has no effect. @@ -113,6 +113,9 @@ /** * The tempo that was set in the last call to -setMusicTimeStamp:withTempo:atMIDITimeStamp: + * or 0 if that method has not yet been called. + * + * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: */ @property (readonly, nonatomic) Float64 tempo; diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 0973eb38..92740eee 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -212,7 +212,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { #pragma mark - Clock /** - * Returns a clock synced to a clock that contains all of the timing information + * Returns a clock that contains all of the tempo and timing information * that was valid at the specified midiTimeStamp. * * Historical clocks older than 1 second may not be available, as the clock @@ -222,15 +222,16 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * immediately after setting currentTimeStamp. * * If no historical clock is available, this method will return the same - * synced clock as -syncedClock. + * clock as -syncedClock. * * @param midiTimeStamp The MIDITimeStamp that you would like a synced clock for. * - * @return A clock synced to a clock that contains all of the timing information + * @return A synced clock that contains all of the tempo and timing information * that was valid at the specified midiTimeStamp, or the current -syncedClock, * if no historical clock could be found. * * @see -syncedClock + * @see MIKMIDISequencerWillLoopNotification */ - (MIKMIDIClock *)syncedClockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; @@ -340,17 +341,25 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * An MIKMIDIClock that is synced with the sequencer's internal clock. * - * Shortly before a tempo change, or before looping playback, this clock - * will already be updated with the future timing information. - * - * If you need historical timing information use -syncedClockForMIDITimeStamp: - * instead. + * Shortly before looping playback, and shortly before a tempo change in + * the sequencer's sequence, this clock will already be updated with the + * future tempo and timing information. If you need older tempo and timing + * information, you should use -syncedClockForMIDITimeStamp: instead. * * @see -syncedClockForMIDITimeStamp: + * @see MIKMIDISequencerWillLoopNotification; */ @property (readonly, nonatomic) MIKMIDIClock *syncedClock; @end +/** + * Sent out shortly before playback loops. + * + * The sequencer's synced clock will already be updated with the + * post-loop tempo and timing information when this notification is sent. + * + * @see -syncedClock + */ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; From 22fb0e49be99621a760d3ba77a378d4ec837cc48 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 21 May 2015 13:38:33 -0500 Subject: [PATCH 145/284] Moved the historical MIDIClock logic from MIKMIDISequencer to MIKMIDIClock. --- Source/MIKMIDIClock.h | 23 ++++-- Source/MIKMIDIClock.m | 142 +++++++++++++++++++++++++++++++------- Source/MIKMIDISequencer.h | 39 +---------- Source/MIKMIDISequencer.m | 75 ++------------------ 4 files changed, 142 insertions(+), 137 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index e17ab170..01ca4739 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -14,10 +14,10 @@ * MIKMIDIClock provides the number of seconds per MIDITimeStamp, as well as the * number of MIDITimeStamps per a specified time interval. * - * Instances of MIKMIDIClock can also be used to convert between MIDITimeStamp + * Instances of MIKMIDIClock can be used to convert between MIDITimeStamp * and MusicTimeStamp. */ -@interface MIKMIDIClock : NSObject +@interface MIKMIDIClock : NSObject /** * Returns the number of MIDITimeStamps that would occur during a specified time interval. @@ -51,6 +51,12 @@ * @param tempo The beats per minute at which MusicTimeStamps should tick. * @param midiTimeStamp The MIDITimeStamp to synchronize the clock to. * + * @note When this method is called, historical tempo and timing information more than 1 second + * old is pruned. At that point, calls to -musicTimeStampForMIDITimeStamp:, + * -midiTimeStampForMusicTimeStamp:, -tempoAtMIDITimeStamp:, and -tempoAtMusicTimeStamp: + * with time stamps more than one second older than the time stamps set with this method + * may not necessarily return accurate information. + * * @see -musicTimeStampForMIDITimeStamp: * @see -midiTimeStampForMusicTimeStamp: */ @@ -109,15 +115,24 @@ * Calling -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced clock * has no effect. */ -@property (readonly, nonatomic) MIKMIDIClock *syncedClock; +- (MIKMIDIClock *)syncedClock; + +/** + * + */ +- (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; +- (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; /** * The tempo that was set in the last call to -setMusicTimeStamp:withTempo:atMIDITimeStamp: * or 0 if that method has not yet been called. * + * If you need earlier tempo information use either -tempoAtMIDITimeStamp: + * or -tempoAtMusicTimeStamp: + * * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: */ -@property (readonly, nonatomic) Float64 tempo; +@property (readonly, nonatomic) Float64 currentTempo; @end diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 6bd9c9ca..19b72162 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -7,6 +7,7 @@ // #import "MIKMIDIClock.h" +#import "MIKMIDIUtilities.h" #import #if !__has_feature(objc_arc) @@ -14,6 +15,9 @@ #endif +#define kDurationToKeepHistoricalClocks 1.0 + + #pragma mark - @interface MIKMIDISyncedClockProxy : NSProxy + (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock; @@ -23,10 +27,17 @@ + (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock; #pragma mark - @interface MIKMIDIClock () -@property (nonatomic) Float64 tempo; + +@property (nonatomic) Float64 currentTempo; @property (nonatomic) MIDITimeStamp timeStampZero; +@property (nonatomic) MIDITimeStamp lastSyncedMIDITimeStamp; + @property (nonatomic) Float64 musicTimeStampsPerMIDITimeStamp; @property (nonatomic) Float64 midiTimeStampsPerMusicTimeStamp; + +@property (nonatomic, strong) NSMutableDictionary *historicalClocks; +@property (nonatomic, strong) NSMutableOrderedSet *historicalClockMIDITimeStamps; + @end @@ -44,11 +55,57 @@ + (instancetype)clock - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { + if (self.lastSyncedMIDITimeStamp) { + // Add a clock to the historical clocks + NSMutableDictionary *historicalClocks = self.historicalClocks; + NSNumber *midiTimeStampNumber = @(midiTimeStamp); + NSMutableOrderedSet *historicalClockMIDITimeStamps = self.historicalClockMIDITimeStamps; + + if (!historicalClocks) { + historicalClocks = [NSMutableDictionary dictionary]; + self.historicalClocks = historicalClocks; + self.historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; + } else { + // Remove clocks old enough to not be needed anymore + MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - [MIKMIDIClock midiTimeStampsPerTimeInterval:kDurationToKeepHistoricalClocks]; + NSUInteger count = historicalClockMIDITimeStamps.count; + NSMutableArray *timeStampsToRemove = [NSMutableArray array]; + NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; + for (NSUInteger i = 0; i < count; i++) { + NSNumber *timeStampNumber = historicalClockMIDITimeStamps[i]; + MIDITimeStamp timeStamp = timeStampNumber.unsignedLongLongValue; + if (timeStamp <= oldTimeStamp) { + [timeStampsToRemove addObject:timeStampNumber]; + [indexesToRemove addIndex:i]; + } else { + break; + } + } + if (timeStampsToRemove.count) { + [historicalClocks removeObjectsForKeys:timeStampsToRemove]; + [historicalClockMIDITimeStamps removeObjectsAtIndexes:indexesToRemove]; + } + } + + // Add clock to history + MIKMIDIClock *historicalClock = [MIKMIDIClock clock]; + historicalClock.currentTempo = self.currentTempo; + historicalClock.timeStampZero = self.timeStampZero; + historicalClock.lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; + historicalClock.musicTimeStampsPerMIDITimeStamp = self.musicTimeStampsPerMIDITimeStamp; + historicalClock.midiTimeStampsPerMusicTimeStamp = self.midiTimeStampsPerMusicTimeStamp; + historicalClocks[midiTimeStampNumber] = historicalClock; + [historicalClockMIDITimeStamps addObject:midiTimeStampNumber]; + + } + + // Update new tempo and timing information Float64 secondsPerMIDITimeStamp = [[self class] secondsPerMIDITimeStamp]; Float64 secondsPerMusicTimeStamp = 1.0 / (tempo / 60.0); Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; - self.tempo = tempo; + self.currentTempo = tempo; + self.lastSyncedMIDITimeStamp = midiTimeStamp; self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; @@ -56,13 +113,32 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { - MIDITimeStamp timeStampZero = self.timeStampZero; - return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * self.musicTimeStampsPerMIDITimeStamp) : -((midiTimeStamp - timeStampZero) * self.musicTimeStampsPerMIDITimeStamp); + if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { + return [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; + } + + return [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; +} + +- (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp withClock:(MIKMIDIClock *)clock +{ + MIDITimeStamp timeStampZero = clock.timeStampZero; + return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp) : -((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp); } - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - return (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; + MIDITimeStamp midiTimeStamp = (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; + if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) return midiTimeStamp; + + NSDictionary *historicalClocks = self.historicalClocks; + for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; + midiTimeStamp = (musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; + if (midiTimeStamp >= clock.lastSyncedMIDITimeStamp) return midiTimeStamp; + } + + return midiTimeStamp; } - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp @@ -70,6 +146,42 @@ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp return musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp; } +#pragma mark - Tempo + +- (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) return self.currentTempo; + return [[self clockForMIDITimeStamp:midiTimeStamp] currentTempo]; +} + +- (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp +{ + return [self tempoAtMIDITimeStamp:[self midiTimeStampForMusicTimeStamp:musicTimeStamp]]; +} + +#pragma mark - Historical Clocks + +- (MIKMIDIClock *)clockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + MIKMIDIClock *clock = self; + NSDictionary *historicalClocks = self.historicalClocks; + for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { + clock = historicalClocks[historicalClockTimeStamp]; + } else { + break; + } + } + return clock; +} + +#pragma mark - Synced Clock + +- (MIKMIDIClock *)syncedClock +{ + return (MIKMIDIClock *)[MIKMIDISyncedClockProxy syncedClockWithClock:self]; +} + #pragma mark - Class Methods + (Float64)secondsPerMIDITimeStamp @@ -89,26 +201,6 @@ + (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval return (1.0 / [self secondsPerMIDITimeStamp]) * timeInterval; } -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - MIKMIDIClock *clock = [[[self class] alloc] init]; - clock.timeStampZero = self.timeStampZero; - clock.musicTimeStampsPerMIDITimeStamp = self.musicTimeStampsPerMIDITimeStamp; - clock.midiTimeStampsPerMusicTimeStamp = self.midiTimeStampsPerMusicTimeStamp; - return clock; -} - -#pragma mark - Properties - -@synthesize syncedClock = _syncedClock; -- (MIKMIDIClock *)syncedClock -{ - if (!_syncedClock) _syncedClock = (MIKMIDIClock *)[MIKMIDISyncedClockProxy syncedClockWithClock:self]; - return _syncedClock; -} - @end diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 92740eee..751e1e3a 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -209,32 +209,6 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track; -#pragma mark - Clock - -/** - * Returns a clock that contains all of the tempo and timing information - * that was valid at the specified midiTimeStamp. - * - * Historical clocks older than 1 second may not be available, as the clock - * history is pruned by the sequencer prior to tempo changes or looping playback. - * - * Historical clocks are also not available after stopping playback, and - * immediately after setting currentTimeStamp. - * - * If no historical clock is available, this method will return the same - * clock as -syncedClock. - * - * @param midiTimeStamp The MIDITimeStamp that you would like a synced clock for. - * - * @return A synced clock that contains all of the tempo and timing information - * that was valid at the specified midiTimeStamp, or the current -syncedClock, - * if no historical clock could be found. - * - * @see -syncedClock - * @see MIKMIDISequencerWillLoopNotification - */ -- (MIKMIDIClock *)syncedClockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; - #pragma mark - Properties /** @@ -341,13 +315,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * An MIKMIDIClock that is synced with the sequencer's internal clock. * - * Shortly before looping playback, and shortly before a tempo change in - * the sequencer's sequence, this clock will already be updated with the - * future tempo and timing information. If you need older tempo and timing - * information, you should use -syncedClockForMIDITimeStamp: instead. - * - * @see -syncedClockForMIDITimeStamp: - * @see MIKMIDISequencerWillLoopNotification; + @ @see -[MIKMIDIClock syncedClock] */ @property (readonly, nonatomic) MIKMIDIClock *syncedClock; @@ -356,10 +324,5 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * Sent out shortly before playback loops. - * - * The sequencer's synced clock will already be updated with the - * post-loop tempo and timing information when this notification is sent. - * - * @see -syncedClock */ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index cd5f18df..ef62337f 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -28,7 +28,7 @@ #endif #define kDefaultTempo 120 -#define kDurationToKeepHistoricalClocks 1.0 + NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; @@ -68,9 +68,6 @@ @interface MIKMIDISequencer () @property (nonatomic, strong) NSMutableDictionary *pendingNoteOffs; @property (nonatomic, strong) NSMutableOrderedSet *pendingNoteOffMIDITimeStamps; -@property (nonatomic, strong) NSMutableDictionary *historicalClocks; -@property (nonatomic, strong) NSMutableOrderedSet *historicalClockMIDITimeStamps; - @property (nonatomic, strong) NSMutableDictionary *pendingRecordedNoteEvents; @property (nonatomic) MusicTimeStamp playbackOffset; @@ -94,6 +91,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence if (self = [super init]) { _sequence = sequence; _clock = [MIKMIDIClock clock]; + _syncedClock = [_clock syncedClock]; _loopEndTimeStamp = -1; _preRoll = 4; _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; @@ -169,8 +167,6 @@ - (void)stop self.pendingNoteOffs = nil; self.pendingNoteOffMIDITimeStamps = nil; [self recordAllPendingNoteEventsWithOffTimeStamp:[self.clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; - self.historicalClocks = nil; - self.historicalClockMIDITimeStamps = nil; self.pendingRecordedNoteEvents = nil; self.looping = NO; _currentTimeStamp = (stopTimeStamp <= self.sequence.length + self.playbackOffset) ? stopTimeStamp : self.sequence.length; @@ -365,67 +361,12 @@ - (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp } } -- (MIKMIDIClock *)clockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp -{ - MIKMIDIClock *clock; - for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { - if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { - clock = self.historicalClocks[historicalClockTimeStamp]; - } else { - break; - } - } - if (!clock) clock = self.clock; - return clock; -} - -- (MIKMIDIClock *)syncedClockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp -{ - return [[self clockForMIDITimeStamp:midiTimeStamp] syncedClock]; -} - - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { // Override tempo if neccessary Float64 tempoOverride = self.tempo; if (tempoOverride) tempo = tempoOverride; - - MIKMIDIClock *clock = self.clock; - NSMutableDictionary *historicalClocks = self.historicalClocks; - if (!historicalClocks) { - historicalClocks = [NSMutableDictionary dictionary]; - self.historicalClocks = historicalClocks; - self.historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; - } else { - NSNumber *midiTimeStampNumber = @(midiTimeStamp); - NSMutableOrderedSet *historicalClockMIDITimeStamps = self.historicalClockMIDITimeStamps; - - // Remove clocks old enough to not be needed anymore - MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - [MIKMIDIClock midiTimeStampsPerTimeInterval:kDurationToKeepHistoricalClocks]; - NSUInteger count = historicalClockMIDITimeStamps.count; - NSMutableArray *timeStampsToRemove = [NSMutableArray array]; - NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; - for (NSUInteger i = 0; i < count; i++) { - NSNumber *timeStampNumber = historicalClockMIDITimeStamps[i]; - MIDITimeStamp timeStamp = timeStampNumber.unsignedLongLongValue; - if (timeStamp <= oldTimeStamp) { - [timeStampsToRemove addObject:timeStampNumber]; - [indexesToRemove addIndex:i]; - } else { - break; - } - } - if (timeStampsToRemove.count) { - [historicalClocks removeObjectsForKeys:timeStampsToRemove]; - [historicalClockMIDITimeStamps removeObjectsAtIndexes:indexesToRemove]; - } - - // Add clock to history - historicalClocks[midiTimeStampNumber] = [clock copy]; - [historicalClockMIDITimeStamps addObject:midiTimeStampNumber]; - } - - [clock setMusicTimeStamp:musicTimeStamp withTempo:tempo atMIDITimeStamp:midiTimeStamp]; + [self.clock setMusicTimeStamp:musicTimeStamp withTempo:tempo atMIDITimeStamp:midiTimeStamp]; } - (void)sendCommands:(NSArray *)commands toDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint @@ -474,10 +415,9 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command if (!self.isRecording) return; MIDITimeStamp midiTimeStamp = command.midiTimestamp; - MIKMIDIClock *clockAtTimeStamp = [self clockForMIDITimeStamp:midiTimeStamp]; - + MusicTimeStamp playbackOffset = self.playbackOffset; - MusicTimeStamp musicTimeStamp = [clockAtTimeStamp musicTimeStampForMIDITimeStamp:midiTimeStamp] - playbackOffset; + MusicTimeStamp musicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:midiTimeStamp] - playbackOffset; MIKMIDIEvent *event; if ([command isKindOfClass:[MIKMIDINoteOnCommand class]]) { // note On @@ -700,11 +640,6 @@ - (void)setTempo:(Float64)tempo } } -- (MIKMIDIClock *)syncedClock -{ - return self.clock.syncedClock; -} - @end #pragma mark - From d14f756ea5c5329a870132bc2d6ff9082d7a151a Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 21 May 2015 13:54:41 -0500 Subject: [PATCH 146/284] Added missing documentation --- Source/MIKMIDIClock.h | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 01ca4739..eb522669 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -69,7 +69,7 @@ * * @return The MusicTimeStamp that will occur at the same time as the specified MIDITimeStamp. * - * @note For this method to return any meaningful values, you must first call + * @note For this method to return any meaningful value, you must first call * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. * * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: @@ -83,7 +83,7 @@ * * @return The MIDITimeStamp that will occur at the same time as the specified MusicTimeStamp. * - * @note For this method to return any meaningful values, you must first call + * @note For this method to return any meaningful value, you must first call * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. * * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: @@ -98,29 +98,39 @@ * * @return The number of MIDITimeStamps that will occur during the specified number of beats. * - * @note For this method to return any meaningful values, you must first call + * @note For this method to return any meaningful value, you must first call * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. * * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: */ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; - /** - * A readonly copy of the clock that remains synced with this instance. - * - * This clock can be queried and will always return the same tempo and timing - * information as the clock instance that dispensed the synced clock. + * Returns the tempo of the clock at the specified MIDITimeStamp. + * + * @param midiTimeStamp The MIDITimeStamp you would like the clock's tempo for. * - * Calling -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced clock - * has no effect. + * @return The tempo of the clock at the specified MIDITimeStamp. + * + * @note For this method to return any meaningful value, you must first call + * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * + * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: */ -- (MIKMIDIClock *)syncedClock; +- (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; /** + * Returns the tempo of the clock at the specified MusicTimeStamp. * + * @param musicTimeStamp The MusicTimeStamp you would like the clock's tempo for. + * + * @return The tempo of the clock at the specified MusicTimeStamp. + * + * @note For this method to return any meaningful value, you must first call + * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * + * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: */ -- (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; - (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; /** From bf13e406ada3acf0f37b444a62dec22788067c27 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 21 May 2015 14:46:50 -0500 Subject: [PATCH 147/284] Re-added -syncedClock to MIKMIDIClock's header file after inadvertently removing it in a previous commit. --- Source/MIKMIDIClock.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index eb522669..fb3fb4c7 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -105,6 +105,18 @@ */ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; + +/** + * A readonly copy of the clock that remains synced with this instance. + * + * This clock can be queried and will always return the same tempo and timing + * information as the clock instance that dispensed the synced clock. + * + * Calling -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced clock + * has no effect. + */ +- (MIKMIDIClock *)syncedClock; + /** * Returns the tempo of the clock at the specified MIDITimeStamp. * From 901216ef2b66ce5b4c92988e54a46a4f6573fa31 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 22 May 2015 15:06:07 -0500 Subject: [PATCH 148/284] Fixed an issue with negative MusicTimeStamps in -musicTimeStampForMIDITimeStamp:withClock: --- Source/MIKMIDIClock.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 19b72162..c91026a9 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -123,7 +123,7 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp withClock:(MIKMIDIClock *)clock { MIDITimeStamp timeStampZero = clock.timeStampZero; - return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp) : -((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp); + return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp) : -((timeStampZero - midiTimeStamp) * clock.musicTimeStampsPerMIDITimeStamp); } - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp From d04c79dd58af655c3d82462df50cb7c2676e5b6f Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 22 May 2015 15:21:45 -0500 Subject: [PATCH 149/284] Changed lastProcessedMIDITimeStamp to latestScheduledMIDITimeStamp and made it public (readonly). --- Source/MIKMIDISequencer.h | 8 +++++++- Source/MIKMIDISequencer.m | 15 ++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 751e1e3a..d171a78f 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -315,10 +315,16 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * An MIKMIDIClock that is synced with the sequencer's internal clock. * - @ @see -[MIKMIDIClock syncedClock] + * @ @see -[MIKMIDIClock syncedClock] */ @property (readonly, nonatomic) MIKMIDIClock *syncedClock; + +/** + * The latest MIDITimeStamp the sequencer has looked ahead to to schedule MIDI events. + */ +@property (readonly, nonatomic) MIDITimeStamp latestScheduledMIDITimeStamp; + @end diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index ef62337f..2158ea71 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -62,7 +62,7 @@ @interface MIKMIDISequencer () @property (readonly, nonatomic) MusicTimeStamp actualLoopEndTimeStamp; -@property (nonatomic) MIDITimeStamp lastProcessedMIDITimeStamp; +@property (nonatomic) MIDITimeStamp latestScheduledMIDITimeStamp; @property (nonatomic, strong) NSTimer *processingTimer; @property (nonatomic, strong) NSMutableDictionary *pendingNoteOffs; @@ -143,7 +143,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi self.playing = YES; self.pendingNoteOffs = [NSMutableDictionary dictionary]; self.pendingNoteOffMIDITimeStamps = [NSMutableOrderedSet orderedSet]; - self.lastProcessedMIDITimeStamp = midiTimeStamp - 1; + self.latestScheduledMIDITimeStamp = midiTimeStamp - 1; self.processingTimer = [NSTimer timerWithTimeInterval:0.05 target:self selector:@selector(processingTimerFired:) @@ -242,7 +242,6 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } // Schedule events - MIDITimeStamp lastProcessedMIDITimeStamp = fromMIDITimeStamp; for (NSNumber *timeStampKey in [allEventsByTimeStamp.allKeys sortedArrayUsingSelector:@selector(compare:)]) { MusicTimeStamp musicTimeStamp = timeStampKey.doubleValue; if (isLooping && (musicTimeStamp < loopStartTimeStamp || musicTimeStamp >= loopEndTimeStamp)) continue; @@ -258,11 +257,9 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam [self scheduleEventWithDestination:eventObject]; } } - - lastProcessedMIDITimeStamp = midiTimeStamp; } - self.lastProcessedMIDITimeStamp = lastProcessedMIDITimeStamp; + self.latestScheduledMIDITimeStamp = actualToMIDITimeStamp; // Handle looping or stopping at the end of the sequence if (isLooping) { @@ -280,7 +277,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } } else if (!self.isRecording) { // Don't stop automatically during recording MIDITimeStamp systemTimeStamp = MIKMIDIGetCurrentTimeStamp(); - if ((systemTimeStamp > lastProcessedMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= sequence.length + playbackOffset)) { + if ((systemTimeStamp > actualToMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= sequence.length + playbackOffset)) { [self stop]; } } @@ -328,7 +325,7 @@ - (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp MIDITimeStamp allPendingNotesOffTimeStamp = 0; if (toTimeStamp == 0) { // All notes off toTimeStamp = ULONG_LONG_MAX; - allPendingNotesOffTimeStamp = MAX(self.lastProcessedMIDITimeStamp + 1, MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.001]); + allPendingNotesOffTimeStamp = MAX(self.latestScheduledMIDITimeStamp + 1, MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.001]); } NSMapTable *noteOffDestinationsToCommands = [NSMapTable strongToStrongObjectsMapTable]; @@ -559,7 +556,7 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp - (void)processingTimerFired:(NSTimer *)timer { - [self processSequenceStartingFromMIDITimeStamp:self.lastProcessedMIDITimeStamp + 1]; + [self processSequenceStartingFromMIDITimeStamp:self.latestScheduledMIDITimeStamp + 1]; } #pragma mark - Properties From 73ca83cfb5321ae5f5fc80c67217c9f364d6ba04 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 22 May 2015 17:06:51 -0500 Subject: [PATCH 150/284] Issue #90: Added overriddenSequenceLength property to MIKMIDISequencer. --- Source/MIKMIDISequencer.h | 10 ++++++++++ Source/MIKMIDISequencer.m | 18 +++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index d171a78f..6e2d43c0 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -241,11 +241,21 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (nonatomic) Float64 tempo; +/** + * The length the that the sequencer should consider its sequence to be. When set to 0, the sequencer + * will use sequence.length instead. + * + * This can be handy if you want to alter the duration of playback to be shorter or longer + * than the sequence's length without affecting the sequence itself. + */ +@property (nonatomic) MusicTimeStamp overriddenSequenceLength; + /** * The current playback position in the sequence. */ @property (nonatomic) MusicTimeStamp currentTimeStamp; + /** * The amount of time (in beats) to pre-roll the sequence before recording. * For example, if preRoll is set to 4 and you begin recording, the sequence diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 2158ea71..af58c5cd 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -79,6 +79,8 @@ @interface MIKMIDISequencer () @property (nonatomic) BOOL needsCurrentTempoUpdate; +@property (readonly, nonatomic) MusicTimeStamp sequenceLength; + @end @@ -169,7 +171,7 @@ - (void)stop [self recordAllPendingNoteEventsWithOffTimeStamp:[self.clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; self.pendingRecordedNoteEvents = nil; self.looping = NO; - _currentTimeStamp = (stopTimeStamp <= self.sequence.length + self.playbackOffset) ? stopTimeStamp : self.sequence.length; + _currentTimeStamp = (stopTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopTimeStamp : self.sequenceLength; self.playbackOffset = 0; self.playing = NO; self.recording = NO; @@ -189,7 +191,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MusicTimeStamp calculatedToMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:toMIDITimeStamp]; BOOL isLooping = (self.shouldLoop && !self.isLooping && calculatedToMusicTimeStamp > loopStartTimeStamp && loopEndTimeStamp > loopStartTimeStamp); self.looping = isLooping; - MusicTimeStamp toMusicTimeStamp = MIN(calculatedToMusicTimeStamp, isLooping ? loopEndTimeStamp : sequence.length); + MusicTimeStamp toMusicTimeStamp = MIN(calculatedToMusicTimeStamp, isLooping ? loopEndTimeStamp : self.sequenceLength); // Send pending note off commands MIDITimeStamp actualToMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:toMusicTimeStamp]; @@ -277,7 +279,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } } else if (!self.isRecording) { // Don't stop automatically during recording MIDITimeStamp systemTimeStamp = MIKMIDIGetCurrentTimeStamp(); - if ((systemTimeStamp > actualToMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= sequence.length + playbackOffset)) { + if ((systemTimeStamp > actualToMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= self.sequenceLength + playbackOffset)) { [self stop]; } } @@ -567,7 +569,7 @@ - (MusicTimeStamp)currentTimeStamp if (self.isPlaying) { MusicTimeStamp timeStamp = [self.clock musicTimeStampForMIDITimeStamp:MIKMIDIGetCurrentTimeStamp()]; MusicTimeStamp playbackOffset = self.playbackOffset; - _currentTimeStamp = (timeStamp <= self.sequence.length + playbackOffset) ? timeStamp - playbackOffset : self.sequence.length; + _currentTimeStamp = (timeStamp <= self.sequenceLength + playbackOffset) ? timeStamp - playbackOffset : self.sequenceLength; } return _currentTimeStamp; } @@ -586,7 +588,7 @@ - (void)setCurrentTimeStamp:(MusicTimeStamp)currentTimeStamp - (MusicTimeStamp)actualLoopEndTimeStamp { - return (_loopEndTimeStamp < 0) ? self.sequence.length : _loopEndTimeStamp; + return (_loopEndTimeStamp < 0) ? self.sequenceLength : _loopEndTimeStamp; } - (void)setPreRoll:(MusicTimeStamp)preRoll @@ -637,6 +639,12 @@ - (void)setTempo:(Float64)tempo } } +- (MusicTimeStamp)sequenceLength +{ + MusicTimeStamp length = self.overriddenSequenceLength; + return length ? length : self.sequence.length; +} + @end #pragma mark - From 3ca5993d28d2c1c400bfbf51efed9ba6bec8455c Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 22 May 2015 22:23:52 -0500 Subject: [PATCH 151/284] Fixed issue where rounding errors in floating point math sometimes caused old MIDITimeStamps to be dispensed in -midiTimeStampForMusicTimeStamp: --- Source/MIKMIDIClock.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index c91026a9..2d569e28 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -128,14 +128,14 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp wi - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - MIDITimeStamp midiTimeStamp = (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; + MIDITimeStamp midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) return midiTimeStamp; NSDictionary *historicalClocks = self.historicalClocks; for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; - midiTimeStamp = (musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; - if (midiTimeStamp >= clock.lastSyncedMIDITimeStamp) return midiTimeStamp; + MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; + if (historicalMIDITimeStamp >= clock.lastSyncedMIDITimeStamp) return historicalMIDITimeStamp; } return midiTimeStamp; From b47ac814deba71a0ec8e4ca92182a88bd4f4aaa1 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Mon, 1 Jun 2015 13:56:49 -0500 Subject: [PATCH 152/284] Removed unnecessary division --- Source/MIKMIDIClock.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 2d569e28..cc679b31 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -96,12 +96,11 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp historicalClock.midiTimeStampsPerMusicTimeStamp = self.midiTimeStampsPerMusicTimeStamp; historicalClocks[midiTimeStampNumber] = historicalClock; [historicalClockMIDITimeStamps addObject:midiTimeStampNumber]; - } // Update new tempo and timing information Float64 secondsPerMIDITimeStamp = [[self class] secondsPerMIDITimeStamp]; - Float64 secondsPerMusicTimeStamp = 1.0 / (tempo / 60.0); + Float64 secondsPerMusicTimeStamp = 60.0 / tempo; Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; self.currentTempo = tempo; From ad7c948a34c8ad278204c57de0b41763b53ee830 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 3 Jun 2015 09:20:26 -0500 Subject: [PATCH 153/284] No longer creating endpoints for tracks that don't contain any events. Also now removing tracks from the tracksToDestinationsMap and tracksToDefaultSynthsMap when they are no longer part of the sequence. --- Source/MIKMIDISequencer.m | 44 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index af58c5cd..629edf25 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -91,7 +91,7 @@ @implementation MIKMIDISequencer - (instancetype)initWithSequence:(MIKMIDISequence *)sequence { if (self = [super init]) { - _sequence = sequence; + self.sequence = sequence; _clock = [MIKMIDIClock clock]; _syncedClock = [_clock syncedClock]; _loopEndTimeStamp = -1; @@ -118,6 +118,11 @@ + (instancetype)sequencer return [[self alloc] init]; } +- (void)dealloc +{ + self.sequence = nil; // remove KVO +} + #pragma mark - Playback - (void)startPlayback @@ -226,8 +231,9 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam // Get other events for (MIKMIDITrack *track in sequence.tracks) { - MIKMIDIDestinationEndpoint *destination = [self destinationEndpointForTrack:track]; - for (MIKMIDIEvent *event in [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]) { + NSArray *events = [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + MIKMIDIDestinationEndpoint *destination = events.count ? [self destinationEndpointForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed + for (MIKMIDIEvent *event in events) { NSNumber *timeStampKey = @(event.timeStamp + playbackOffset); NSMutableArray *eventsAtTimeStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimeStamp addObject:[MIKMIDIEventWithDestination eventWithDestination:destination event:event]]; @@ -561,6 +567,29 @@ - (void)processingTimerFired:(NSTimer *)timer [self processSequenceStartingFromMIDITimeStamp:self.latestScheduledMIDITimeStamp + 1]; } +#pragma mark - KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + NSSet *currentTracks = [NSSet setWithArray:self.sequence.tracks]; + + NSMapTable *tracksToDestinationMap = self.tracksToDestinationsMap; + NSMutableSet *tracksToRemoveFromDestinationMap = [NSMutableSet setWithArray:[[tracksToDestinationMap keyEnumerator] allObjects]]; + [tracksToRemoveFromDestinationMap minusSet:currentTracks]; + + for (MIKMIDITrack *track in tracksToRemoveFromDestinationMap) { + [tracksToDestinationMap removeObjectForKey:track]; + } + + NSMapTable *tracksToSynthsMap = self.tracksToDefaultSynthsMap; + NSMutableSet *tracksToRemoveFromSynthsMap = [NSMutableSet setWithArray:[[tracksToSynthsMap keyEnumerator] allObjects]]; + [tracksToRemoveFromSynthsMap minusSet:currentTracks]; + + for (MIKMIDITrack *track in tracksToRemoveFromSynthsMap) { + [tracksToSynthsMap removeObjectForKey:track]; + } +} + #pragma mark - Properties @synthesize currentTimeStamp = _currentTimeStamp; @@ -645,6 +674,15 @@ - (MusicTimeStamp)sequenceLength return length ? length : self.sequence.length; } +- (void)setSequence:(MIKMIDISequence *)sequence +{ + if (_sequence != sequence) { + [_sequence removeObserver:self forKeyPath:@"tracks"]; + _sequence = sequence; + [_sequence addObserver:self forKeyPath:@"tracks" options:NSKeyValueObservingOptionInitial context:NULL]; + } +} + @end #pragma mark - From ee0af417650782893ebdf39d4483f44dcbb1e62f Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 4 Jun 2015 16:59:05 -0500 Subject: [PATCH 154/284] Fixed currentTimeStamp behavior --- Source/MIKMIDISequencer.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 629edf25..557a4567 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -176,7 +176,10 @@ - (void)stop [self recordAllPendingNoteEventsWithOffTimeStamp:[self.clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; self.pendingRecordedNoteEvents = nil; self.looping = NO; - _currentTimeStamp = (stopTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopTimeStamp : self.sequenceLength; + + MusicTimeStamp stopMusicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; + _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; + self.playbackOffset = 0; self.playing = NO; self.recording = NO; @@ -605,13 +608,13 @@ - (MusicTimeStamp)currentTimeStamp - (void)setCurrentTimeStamp:(MusicTimeStamp)currentTimeStamp { - _currentTimeStamp = currentTimeStamp; - if (self.isPlaying) { BOOL isRecording = self.isRecording; [self stop]; if (isRecording) [self prepareForRecordingWithPreRoll:NO]; - [self startPlaybackAtTimeStamp:_currentTimeStamp]; + [self startPlaybackAtTimeStamp:currentTimeStamp]; + } else { + _currentTimeStamp = currentTimeStamp; } } From 17b344822d077996793a883b6df54b48cf8820d0 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 5 Jun 2015 13:10:38 -0500 Subject: [PATCH 155/284] Fixed more issues with MIKMIDISequencer's currentTimeStamp. Deprecated -setMusicTimeSTamp:withTempo:atMIDITimeStamp in MIKMIDIClock and replaced with -syncMusicTimeStamp:withMIDITimeStamp:tempo: and -unsyncMusicTimeStampsTemposFromMIDITimeStamps, and added ready property. --- Source/MIKMIDIClock.h | 87 ++++++++++++++++++++++++++++----------- Source/MIKMIDIClock.m | 32 ++++++++++++-- Source/MIKMIDISequencer.m | 17 +++++--- 3 files changed, 101 insertions(+), 35 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index fb3fb4c7..f45466e5 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -44,12 +44,11 @@ /** * Internally synchronizes the musicTimeStamp with the midiTimeStamp using the specified tempo. - * This method must be called at least once before -musicTimeStampForMIDITimeStamp: and - * -midiTimeStampForMusicTimeStamp: will return any meaningful values. + * This method must be called for the clock to become ready to use. * * @param musicTimeStamp The MusicTimeStamp to synchronize the clock to. - * @param tempo The beats per minute at which MusicTimeStamps should tick. * @param midiTimeStamp The MIDITimeStamp to synchronize the clock to. + * @param tempo The beats per minute at which MusicTimeStamps should tick. * * @note When this method is called, historical tempo and timing information more than 1 second * old is pruned. At that point, calls to -musicTimeStampForMIDITimeStamp:, @@ -57,10 +56,18 @@ * with time stamps more than one second older than the time stamps set with this method * may not necessarily return accurate information. * - * @see -musicTimeStampForMIDITimeStamp: - * @see -midiTimeStampForMusicTimeStamp: + * @see -unsyncMusicTimeStampsTemposFromMIDITimeStamps + * @see -isReady + */ +- (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo:(Float64)tempo; + +/** + * Internally unsynchronizes the tempo and MusictimeSTamp information with MIDITimeStamps. + * + * @see -syncMusicTimeStamp:withMIDITimeStamp:tempo: + * @see -isReady */ -- (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; +- (void)unsyncMusicTimeStampsTemposFromMIDITimeStamps; /** * Converts the specified MIDITimeStamp into the corresponding MusicTimeStamp. @@ -69,10 +76,9 @@ * * @return The MusicTimeStamp that will occur at the same time as the specified MIDITimeStamp. * - * @note For this method to return any meaningful value, you must first call - * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * @note If the clock is not ready this method will return 0. * - * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: + * @see -isReady */ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; @@ -83,10 +89,9 @@ * * @return The MIDITimeStamp that will occur at the same time as the specified MusicTimeStamp. * - * @note For this method to return any meaningful value, you must first call - * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * @note If the clock is not ready this method will return 0. * - * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: + * @see -isReady */ - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; @@ -98,10 +103,9 @@ * * @return The number of MIDITimeStamps that will occur during the specified number of beats. * - * @note For this method to return any meaningful value, you must first call - * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * @note If the clock is not ready this method will return 0. * - * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: + * @see -isReady */ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; @@ -112,7 +116,8 @@ * This clock can be queried and will always return the same tempo and timing * information as the clock instance that dispensed the synced clock. * - * Calling -setMusicTimeStamp:withTempo:atMusicTimeStamp on the synced clock + * Calling -syncMusicTimeStamp:withMIDITimeStamp:tempo: or + * -unsyncMusicTimeStampsTemposFromMIDITimeStamps on the synced clock * has no effect. */ - (MIKMIDIClock *)syncedClock; @@ -124,10 +129,9 @@ * * @return The tempo of the clock at the specified MIDITimeStamp. * - * @note For this method to return any meaningful value, you must first call - * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * @note If the clock is not ready this method will return 0. * - * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: + * @see -isReady */ - (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp; @@ -138,23 +142,56 @@ * * @return The tempo of the clock at the specified MusicTimeStamp. * - * @note For this method to return any meaningful value, you must first call - * -setMusicTimeStamp:withTempo:atMIDITimeStamp: at least once. + * @note If the clock is not ready this method will return 0. * - * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: + * @see -isReady */ - (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp; /** - * The tempo that was set in the last call to -setMusicTimeStamp:withTempo:atMIDITimeStamp: - * or 0 if that method has not yet been called. + * Whether or not the clock has synchronized MusicTimeStamps and MIDITimeStamps + * and is ready to use for getting tempo and timing information. + * + * @see -syncMusicTimeStamp:withMIDITimeStamp:tempo: + * @see -unsyncMusicTimeStampsTemposFromMIDITimeStamps + */ +@property (readonly, nonatomic, getter=isReady) BOOL ready; + +/** + * The tempo that was set in the last call to -syncMusicTimeStamp:withMIDITimeStamp:tempo: + * or 0 if the clock is not ready. * * If you need earlier tempo information use either -tempoAtMIDITimeStamp: * or -tempoAtMusicTimeStamp: * - * @see -setMusicTimeStamp:withTempo:atMIDITimeStamp: + * @see -isReady */ @property (readonly, nonatomic) Float64 currentTempo; +#pragma mark - Deprecated Methods + +/** + * @deprecated This method is deprecated. Use -[MIKMIDIClock + * syncMusicTimeStamp:withMIDITimeStamp:tempo:] instead. + * + * Internally synchronizes the musicTimeStamp with the midiTimeStamp using the specified tempo. + * This method must be called at least once before -musicTimeStampForMIDITimeStamp: and + * -midiTimeStampForMusicTimeStamp: will return any meaningful values. + * + * @param musicTimeStamp The MusicTimeStamp to synchronize the clock to. + * @param tempo The beats per minute at which MusicTimeStamps should tick. + * @param midiTimeStamp The MIDITimeStamp to synchronize the clock to. + * + * @note When this method is called, historical tempo and timing information more than 1 second + * old is pruned. At that point, calls to -musicTimeStampForMIDITimeStamp:, + * -midiTimeStampForMusicTimeStamp:, -tempoAtMIDITimeStamp:, and -tempoAtMusicTimeStamp: + * with time stamps more than one second older than the time stamps set with this method + * may not necessarily return accurate information. + * + * @see -musicTimeStampForMIDITimeStamp: + * @see -midiTimeStampForMusicTimeStamp: + */ +- (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp DEPRECATED_ATTRIBUTE; + @end diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index cc679b31..5cbd29f6 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -38,6 +38,8 @@ @interface MIKMIDIClock () @property (nonatomic, strong) NSMutableDictionary *historicalClocks; @property (nonatomic, strong) NSMutableOrderedSet *historicalClockMIDITimeStamps; +@property (nonatomic, getter=isReady) BOOL ready; + @end @@ -53,7 +55,7 @@ + (instancetype)clock #pragma mark - Time Stamps -- (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +- (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo:(Float64)tempo { if (self.lastSyncedMIDITimeStamp) { // Add a clock to the historical clocks @@ -108,10 +110,20 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; + self.ready = YES; +} + +- (void)unsyncMusicTimeStampsTemposFromMIDITimeStamps +{ + self.ready = NO; + self.currentTempo = 0; + self.historicalClocks = nil; + self.historicalClockMIDITimeStamps = nil; } - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { + if (!self.isReady) return 0; if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { return [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; } @@ -121,12 +133,14 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp withClock:(MIKMIDIClock *)clock { + if (!self.isReady) return 0; MIDITimeStamp timeStampZero = clock.timeStampZero; return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp) : -((timeStampZero - midiTimeStamp) * clock.musicTimeStampsPerMIDITimeStamp); } - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { + if (!self.isReady) return 0; MIDITimeStamp midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) return midiTimeStamp; @@ -142,20 +156,21 @@ - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - return musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp; + return self.isReady ? (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) : 0; } #pragma mark - Tempo - (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { + if (!self.isReady) return 0; if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) return self.currentTempo; return [[self clockForMIDITimeStamp:midiTimeStamp] currentTempo]; } - (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - return [self tempoAtMIDITimeStamp:[self midiTimeStampForMusicTimeStamp:musicTimeStamp]]; + return self.isReady ? [self tempoAtMIDITimeStamp:[self midiTimeStampForMusicTimeStamp:musicTimeStamp]] : 0; } #pragma mark - Historical Clocks @@ -200,6 +215,13 @@ + (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval return (1.0 / [self secondsPerMIDITimeStamp]) * timeInterval; } +#pragma mark - Deprecated Methods + +- (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + [self syncMusicTimeStamp:musicTimeStamp withMIDITimeStamp:midiTimeStamp tempo:tempo]; +} + @end @@ -216,7 +238,9 @@ + (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock - (void)forwardInvocation:(NSInvocation *)invocation { SEL selector = invocation.selector; - if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; + if (selector == @selector(syncMusicTimeStamp:withMIDITimeStamp:tempo:)) return; + if (selector == @selector(unsyncMusicTimeStampsTemposFromMIDITimeStamps)) return; + if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; // deprecated if (selector == @selector(syncedClock)) { MIKMIDISyncedClockProxy *syncedClock = self; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 557a4567..e98d26c0 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -28,6 +28,7 @@ #endif #define kDefaultTempo 120 +#define kBufferDuration 0.1 NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; @@ -169,17 +170,20 @@ - (void)stop MIDITimeStamp stopTimeStamp = MIKMIDIGetCurrentTimeStamp(); if (!self.isPlaying) return; + MIKMIDIClock *clock = self.clock; self.processingTimer = nil; [self sendPendingNoteOffCommandsUpToMIDITimeStamp:0]; self.pendingNoteOffs = nil; self.pendingNoteOffMIDITimeStamps = nil; - [self recordAllPendingNoteEventsWithOffTimeStamp:[self.clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; + [self recordAllPendingNoteEventsWithOffTimeStamp:[clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; self.pendingRecordedNoteEvents = nil; self.looping = NO; - MusicTimeStamp stopMusicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; + MusicTimeStamp stopMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; + [clock unsyncMusicTimeStampsTemposFromMIDITimeStamps]; + self.playbackOffset = 0; self.playing = NO; self.recording = NO; @@ -187,7 +191,7 @@ - (void)stop - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStamp { - MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.1]; + MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:kBufferDuration]; if (toMIDITimeStamp < fromMIDITimeStamp) return; MIKMIDIClock *clock = self.clock; @@ -374,7 +378,7 @@ - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Floa // Override tempo if neccessary Float64 tempoOverride = self.tempo; if (tempoOverride) tempo = tempoOverride; - [self.clock setMusicTimeStamp:musicTimeStamp withTempo:tempo atMIDITimeStamp:midiTimeStamp]; + [self.clock syncMusicTimeStamp:musicTimeStamp withMIDITimeStamp:midiTimeStamp tempo:tempo]; } - (void)sendCommands:(NSArray *)commands toDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint @@ -598,8 +602,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N @synthesize currentTimeStamp = _currentTimeStamp; - (MusicTimeStamp)currentTimeStamp { - if (self.isPlaying) { - MusicTimeStamp timeStamp = [self.clock musicTimeStampForMIDITimeStamp:MIKMIDIGetCurrentTimeStamp()]; + MIKMIDIClock *clock = self.clock; + if (clock.isReady) { + MusicTimeStamp timeStamp = [clock musicTimeStampForMIDITimeStamp:MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:kBufferDuration]]; MusicTimeStamp playbackOffset = self.playbackOffset; _currentTimeStamp = (timeStamp <= self.sequenceLength + playbackOffset) ? timeStamp - playbackOffset : self.sequenceLength; } From e888d13065d79f0695808e6b1741a437ee49cdff Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 5 Jun 2015 13:14:39 -0500 Subject: [PATCH 156/284] Fixed typo in documentation, improved method name. --- Source/MIKMIDIClock.h | 4 ++-- Source/MIKMIDIClock.m | 2 +- Source/MIKMIDISequencer.m | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index f45466e5..161c45f7 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -62,12 +62,12 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo:(Float64)tempo; /** - * Internally unsynchronizes the tempo and MusictimeSTamp information with MIDITimeStamps. + * Internally unsynchronizes the tempo and MusicTimeStamp information with MIDITimeStamps. * * @see -syncMusicTimeStamp:withMIDITimeStamp:tempo: * @see -isReady */ -- (void)unsyncMusicTimeStampsTemposFromMIDITimeStamps; +- (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps; /** * Converts the specified MIDITimeStamp into the corresponding MusicTimeStamp. diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 5cbd29f6..55a89f32 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -113,7 +113,7 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID self.ready = YES; } -- (void)unsyncMusicTimeStampsTemposFromMIDITimeStamps +- (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps { self.ready = NO; self.currentTempo = 0; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index e98d26c0..40c91dc0 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -182,7 +182,7 @@ - (void)stop MusicTimeStamp stopMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; - [clock unsyncMusicTimeStampsTemposFromMIDITimeStamps]; + [clock unsyncMusicTimeStampsAndTemposFromMIDITimeStamps]; self.playbackOffset = 0; self.playing = NO; From 8eb0cb2dc5a6acc430110839428dbfdd35d47655 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 5 Jun 2015 13:55:45 -0500 Subject: [PATCH 157/284] Improved the last currentTimeStamp fix. --- Source/MIKMIDISequencer.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 40c91dc0..cc5f2779 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -28,7 +28,6 @@ #endif #define kDefaultTempo 120 -#define kBufferDuration 0.1 NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; @@ -191,7 +190,7 @@ - (void)stop - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStamp { - MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:kBufferDuration]; + MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.1]; if (toMIDITimeStamp < fromMIDITimeStamp) return; MIKMIDIClock *clock = self.clock; @@ -287,6 +286,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIDITimeStamp loopStartMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:loopStartTimeStamp + loopLength]; [self updateClockWithMusicTimeStamp:loopStartTimeStamp tempo:tempo atMIDITimeStamp:loopStartMIDITimeStamp]; + self.startingTimeStamp = loopStartTimeStamp; [[NSNotificationCenter defaultCenter] postNotificationName:MIKMIDISequencerWillLoopNotification object:self userInfo:nil]; [self processSequenceStartingFromMIDITimeStamp:loopStartMIDITimeStamp]; } @@ -604,9 +604,9 @@ - (MusicTimeStamp)currentTimeStamp { MIKMIDIClock *clock = self.clock; if (clock.isReady) { - MusicTimeStamp timeStamp = [clock musicTimeStampForMIDITimeStamp:MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:kBufferDuration]]; + MusicTimeStamp timeStamp = [clock musicTimeStampForMIDITimeStamp:MIKMIDIGetCurrentTimeStamp()]; MusicTimeStamp playbackOffset = self.playbackOffset; - _currentTimeStamp = (timeStamp <= self.sequenceLength + playbackOffset) ? timeStamp - playbackOffset : self.sequenceLength; + _currentTimeStamp = MAX(((timeStamp <= self.sequenceLength + playbackOffset) ? timeStamp - playbackOffset : self.sequenceLength), self.startingTimeStamp); } return _currentTimeStamp; } From 9f580dc6409adfb3215c6a222c4ee5d0711402e3 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 9 Jun 2015 09:31:54 -0500 Subject: [PATCH 158/284] Issue #92: MIKMIDISequencer now processes its sequence on a separate queue. --- Source/MIKMIDISequencer.m | 81 ++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index cc5f2779..ec953d5c 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -63,7 +63,6 @@ @interface MIKMIDISequencer () @property (readonly, nonatomic) MusicTimeStamp actualLoopEndTimeStamp; @property (nonatomic) MIDITimeStamp latestScheduledMIDITimeStamp; -@property (nonatomic, strong) NSTimer *processingTimer; @property (nonatomic, strong) NSMutableDictionary *pendingNoteOffs; @property (nonatomic, strong) NSMutableOrderedSet *pendingNoteOffMIDITimeStamps; @@ -81,6 +80,9 @@ @interface MIKMIDISequencer () @property (readonly, nonatomic) MusicTimeStamp sequenceLength; +@property (nonatomic) dispatch_queue_t processingQueue; +@property (nonatomic) dispatch_source_t processingTimer; + @end @@ -121,6 +123,7 @@ + (instancetype)sequencer - (void)dealloc { self.sequence = nil; // remove KVO + self.processingTimer = NULL; } #pragma mark - Playback @@ -140,23 +143,34 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi { if (self.isPlaying) [self stop]; - MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; - self.startingTimeStamp = startingTimeStamp; + NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; + self.processingQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + dispatch_sync(self.processingQueue, ^{ + MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; + self.startingTimeStamp = startingTimeStamp; - Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; - if (!startingTempo) startingTempo = kDefaultTempo; - [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; + Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; + if (!startingTempo) startingTempo = kDefaultTempo; + [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; + }); self.playing = YES; - self.pendingNoteOffs = [NSMutableDictionary dictionary]; - self.pendingNoteOffMIDITimeStamps = [NSMutableOrderedSet orderedSet]; - self.latestScheduledMIDITimeStamp = midiTimeStamp - 1; - self.processingTimer = [NSTimer timerWithTimeInterval:0.05 - target:self - selector:@selector(processingTimerFired:) - userInfo:nil - repeats:YES]; - [self.processingTimer fire]; + + dispatch_sync(self.processingQueue, ^{ + self.pendingNoteOffs = [NSMutableDictionary dictionary]; + self.pendingNoteOffMIDITimeStamps = [NSMutableOrderedSet orderedSet]; + self.latestScheduledMIDITimeStamp = midiTimeStamp - 1; + dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.processingQueue); + if (!timer) return NSLog(@"Unable to create processing timer for %@.", [self class]); + self.processingTimer = timer; + + dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.05, 0.05); + dispatch_source_set_event_handler(timer, ^{ + [self processSequenceStartingFromMIDITimeStamp:self.latestScheduledMIDITimeStamp + 1]; + }); + + dispatch_resume(timer); + }); } - (void)resumePlayback @@ -169,20 +183,24 @@ - (void)stop MIDITimeStamp stopTimeStamp = MIKMIDIGetCurrentTimeStamp(); if (!self.isPlaying) return; - MIKMIDIClock *clock = self.clock; - self.processingTimer = nil; - [self sendPendingNoteOffCommandsUpToMIDITimeStamp:0]; - self.pendingNoteOffs = nil; - self.pendingNoteOffMIDITimeStamps = nil; - [self recordAllPendingNoteEventsWithOffTimeStamp:[clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; - self.pendingRecordedNoteEvents = nil; - self.looping = NO; + dispatch_sync(self.processingQueue, ^{ + self.processingTimer = NULL; + + MIKMIDIClock *clock = self.clock; + [self sendPendingNoteOffCommandsUpToMIDITimeStamp:0]; + self.pendingNoteOffs = nil; + self.pendingNoteOffMIDITimeStamps = nil; + [self recordAllPendingNoteEventsWithOffTimeStamp:[clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; + self.pendingRecordedNoteEvents = nil; + self.looping = NO; - MusicTimeStamp stopMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; - _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; + MusicTimeStamp stopMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; + _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; - [clock unsyncMusicTimeStampsAndTemposFromMIDITimeStamps]; + [clock unsyncMusicTimeStampsAndTemposFromMIDITimeStamps]; + }); + self.processingQueue = NULL; self.playbackOffset = 0; self.playing = NO; self.recording = NO; @@ -199,7 +217,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MusicTimeStamp loopStartTimeStamp = self.loopStartTimeStamp + playbackOffset; MusicTimeStamp loopEndTimeStamp = self.actualLoopEndTimeStamp + playbackOffset; MusicTimeStamp fromMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:fromMIDITimeStamp]; - MusicTimeStamp calculatedToMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:toMIDITimeStamp]; + MusicTimeStamp calculatedToMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:toMIDITimeStamp];\ BOOL isLooping = (self.shouldLoop && !self.isLooping && calculatedToMusicTimeStamp > loopStartTimeStamp && loopEndTimeStamp > loopStartTimeStamp); self.looping = isLooping; MusicTimeStamp toMusicTimeStamp = MIN(calculatedToMusicTimeStamp, isLooping ? loopEndTimeStamp : self.sequenceLength); @@ -633,12 +651,13 @@ - (void)setPreRoll:(MusicTimeStamp)preRoll _preRoll = (preRoll >= 0) ? preRoll : 0; } -- (void)setProcessingTimer:(NSTimer *)processingTimer +- (void)setProcessingTimer:(dispatch_source_t)processingTimer { - if (processingTimer != _processingTimer) { - [_processingTimer invalidate]; + if (_processingTimer != processingTimer) { + if (_processingTimer) { + dispatch_source_cancel(_processingTimer); + } _processingTimer = processingTimer; - if (_processingTimer) [[NSRunLoop currentRunLoop] addTimer:_processingTimer forMode:NSRunLoopCommonModes]; } } From dade71b44e47d34378844c24e08eb32201b04c79 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 9 Jun 2015 09:32:23 -0500 Subject: [PATCH 159/284] Issue #93: MIKMIDIClock is now thread-safe --- Source/MIKMIDIClock.m | 195 ++++++++++++++++++++++++++---------------- 1 file changed, 121 insertions(+), 74 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 55a89f32..c1d0db86 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -40,6 +40,8 @@ @interface MIKMIDIClock () @property (nonatomic, getter=isReady) BOOL ready; +@property (nonatomic) dispatch_queue_t clockQueue; + @end @@ -53,82 +55,102 @@ + (instancetype)clock return [[self alloc] init]; } +- (instancetype)init +{ + if (self = [super init]) { + NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; + self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + } + return self; +} + #pragma mark - Time Stamps - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo:(Float64)tempo { - if (self.lastSyncedMIDITimeStamp) { - // Add a clock to the historical clocks - NSMutableDictionary *historicalClocks = self.historicalClocks; - NSNumber *midiTimeStampNumber = @(midiTimeStamp); - NSMutableOrderedSet *historicalClockMIDITimeStamps = self.historicalClockMIDITimeStamps; - - if (!historicalClocks) { - historicalClocks = [NSMutableDictionary dictionary]; - self.historicalClocks = historicalClocks; - self.historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; - } else { - // Remove clocks old enough to not be needed anymore - MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - [MIKMIDIClock midiTimeStampsPerTimeInterval:kDurationToKeepHistoricalClocks]; - NSUInteger count = historicalClockMIDITimeStamps.count; - NSMutableArray *timeStampsToRemove = [NSMutableArray array]; - NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; - for (NSUInteger i = 0; i < count; i++) { - NSNumber *timeStampNumber = historicalClockMIDITimeStamps[i]; - MIDITimeStamp timeStamp = timeStampNumber.unsignedLongLongValue; - if (timeStamp <= oldTimeStamp) { - [timeStampsToRemove addObject:timeStampNumber]; - [indexesToRemove addIndex:i]; - } else { - break; + dispatch_sync(self.clockQueue, ^{ + if (self.lastSyncedMIDITimeStamp) { + // Add a clock to the historical clocks + NSMutableDictionary *historicalClocks = self.historicalClocks; + NSNumber *midiTimeStampNumber = @(midiTimeStamp); + NSMutableOrderedSet *historicalClockMIDITimeStamps = self.historicalClockMIDITimeStamps; + + if (!historicalClocks) { + historicalClocks = [NSMutableDictionary dictionary]; + self.historicalClocks = historicalClocks; + self.historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; + } else { + // Remove clocks old enough to not be needed anymore + MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - [MIKMIDIClock midiTimeStampsPerTimeInterval:kDurationToKeepHistoricalClocks]; + NSUInteger count = historicalClockMIDITimeStamps.count; + NSMutableArray *timeStampsToRemove = [NSMutableArray array]; + NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; + for (NSUInteger i = 0; i < count; i++) { + NSNumber *timeStampNumber = historicalClockMIDITimeStamps[i]; + MIDITimeStamp timeStamp = timeStampNumber.unsignedLongLongValue; + if (timeStamp <= oldTimeStamp) { + [timeStampsToRemove addObject:timeStampNumber]; + [indexesToRemove addIndex:i]; + } else { + break; + } + } + if (timeStampsToRemove.count) { + [historicalClocks removeObjectsForKeys:timeStampsToRemove]; + [historicalClockMIDITimeStamps removeObjectsAtIndexes:indexesToRemove]; } } - if (timeStampsToRemove.count) { - [historicalClocks removeObjectsForKeys:timeStampsToRemove]; - [historicalClockMIDITimeStamps removeObjectsAtIndexes:indexesToRemove]; - } - } - // Add clock to history - MIKMIDIClock *historicalClock = [MIKMIDIClock clock]; - historicalClock.currentTempo = self.currentTempo; - historicalClock.timeStampZero = self.timeStampZero; - historicalClock.lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; - historicalClock.musicTimeStampsPerMIDITimeStamp = self.musicTimeStampsPerMIDITimeStamp; - historicalClock.midiTimeStampsPerMusicTimeStamp = self.midiTimeStampsPerMusicTimeStamp; - historicalClocks[midiTimeStampNumber] = historicalClock; - [historicalClockMIDITimeStamps addObject:midiTimeStampNumber]; - } + // Add clock to history + MIKMIDIClock *historicalClock = [MIKMIDIClock clock]; + historicalClock.currentTempo = self.currentTempo; + historicalClock.timeStampZero = self.timeStampZero; + historicalClock.lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; + historicalClock.musicTimeStampsPerMIDITimeStamp = self.musicTimeStampsPerMIDITimeStamp; + historicalClock.midiTimeStampsPerMusicTimeStamp = self.midiTimeStampsPerMusicTimeStamp; + historicalClocks[midiTimeStampNumber] = historicalClock; + [historicalClockMIDITimeStamps addObject:midiTimeStampNumber]; + } - // Update new tempo and timing information - Float64 secondsPerMIDITimeStamp = [[self class] secondsPerMIDITimeStamp]; - Float64 secondsPerMusicTimeStamp = 60.0 / tempo; - Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; - - self.currentTempo = tempo; - self.lastSyncedMIDITimeStamp = midiTimeStamp; - self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); - self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; - self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; - self.ready = YES; + // Update new tempo and timing information + Float64 secondsPerMIDITimeStamp = [[self class] secondsPerMIDITimeStamp]; + Float64 secondsPerMusicTimeStamp = 60.0 / tempo; + Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; + + self.currentTempo = tempo; + self.lastSyncedMIDITimeStamp = midiTimeStamp; + self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); + self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; + self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; + self.ready = YES; + }); } - (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps { - self.ready = NO; - self.currentTempo = 0; - self.historicalClocks = nil; - self.historicalClockMIDITimeStamps = nil; + dispatch_sync(self.clockQueue, ^{ + self.ready = NO; + self.currentTempo = 0; + self.historicalClocks = nil; + self.historicalClockMIDITimeStamps = nil; + }); } - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { - if (!self.isReady) return 0; - if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { - return [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; - } + __block MusicTimeStamp musicTimeStamp = 0; + + dispatch_sync(self.clockQueue, ^{ + if (self.isReady) { + if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { + musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; + } else { + musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; + } + } + }); - return [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; + return musicTimeStamp; } - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp withClock:(MIKMIDIClock *)clock @@ -140,37 +162,62 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp wi - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - if (!self.isReady) return 0; - MIDITimeStamp midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; - if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) return midiTimeStamp; - - NSDictionary *historicalClocks = self.historicalClocks; - for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { - MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; - MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; - if (historicalMIDITimeStamp >= clock.lastSyncedMIDITimeStamp) return historicalMIDITimeStamp; - } + __block MIDITimeStamp midiTimeStamp = 0; + + dispatch_sync(self.clockQueue, ^{ + if (self.isReady) { + midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; + + if (midiTimeStamp < self.lastSyncedMIDITimeStamp) { + NSDictionary *historicalClocks = self.historicalClocks; + for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; + MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; + if (historicalMIDITimeStamp >= clock.lastSyncedMIDITimeStamp) { + midiTimeStamp = historicalMIDITimeStamp; + break; + } + } + } + } + }); return midiTimeStamp; } - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - return self.isReady ? (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) : 0; + __block MIDITimeStamp midiTimeStamps = 0; + + dispatch_sync(self.clockQueue, ^{ + midiTimeStamps = self.isReady ? (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) : 0; + }); + + return midiTimeStamps; } #pragma mark - Tempo - (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { - if (!self.isReady) return 0; - if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) return self.currentTempo; - return [[self clockForMIDITimeStamp:midiTimeStamp] currentTempo]; + __block Float64 tempo = 0; + + dispatch_sync(self.clockQueue, ^{ + if (self.isReady) { + if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { + tempo = self.currentTempo; + } else { + tempo = [[self clockForMIDITimeStamp:midiTimeStamp] currentTempo]; + } + } + }); + + return tempo; } - (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - return self.isReady ? [self tempoAtMIDITimeStamp:[self midiTimeStampForMusicTimeStamp:musicTimeStamp]] : 0; + return [self tempoAtMIDITimeStamp:[self midiTimeStampForMusicTimeStamp:musicTimeStamp]]; } #pragma mark - Historical Clocks From 7ab4c39a97f8f36e0146287d2ca2abb17c72b069 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 9 Jun 2015 09:46:04 -0500 Subject: [PATCH 160/284] Minor formatting change --- Source/MIKMIDIClock.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index c1d0db86..b34d8044 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -190,7 +190,7 @@ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp __block MIDITimeStamp midiTimeStamps = 0; dispatch_sync(self.clockQueue, ^{ - midiTimeStamps = self.isReady ? (musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) : 0; + if (self.isReady) midiTimeStamps = musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp; }); return midiTimeStamps; From a22c67929b4830a100716d32b55b0680d7d7e385 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 9 Jun 2015 12:21:38 -0500 Subject: [PATCH 161/284] MIKMIDIClock's historical clocks no longer create their own queues. --- Source/MIKMIDIClock.m | 51 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index b34d8044..013009da 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -56,19 +56,40 @@ + (instancetype)clock } - (instancetype)init +{ + return [self initWithQueue:YES]; +} + +- (instancetype)initWithQueue:(BOOL)createQueue { if (self = [super init]) { - NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + if (createQueue) { + NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; + self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + } } return self; } +#pragma mark - Queue + +- (void)dispatchToClockQueue:(void (^)())block +{ + if (!block) return; + + dispatch_queue_t queue = self.clockQueue; + if (queue) { + dispatch_sync(queue, block); + } else { + block(); + } +} + #pragma mark - Time Stamps - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo:(Float64)tempo { - dispatch_sync(self.clockQueue, ^{ + [self dispatchToClockQueue:^{ if (self.lastSyncedMIDITimeStamp) { // Add a clock to the historical clocks NSMutableDictionary *historicalClocks = self.historicalClocks; @@ -102,7 +123,7 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID } // Add clock to history - MIKMIDIClock *historicalClock = [MIKMIDIClock clock]; + MIKMIDIClock *historicalClock = [[MIKMIDIClock alloc] initWithQueue:NO]; historicalClock.currentTempo = self.currentTempo; historicalClock.timeStampZero = self.timeStampZero; historicalClock.lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; @@ -123,24 +144,24 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; self.ready = YES; - }); + }]; } - (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps { - dispatch_sync(self.clockQueue, ^{ + [self dispatchToClockQueue:^{ self.ready = NO; self.currentTempo = 0; self.historicalClocks = nil; self.historicalClockMIDITimeStamps = nil; - }); + }]; } - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { __block MusicTimeStamp musicTimeStamp = 0; - dispatch_sync(self.clockQueue, ^{ + [self dispatchToClockQueue:^{ if (self.isReady) { if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; @@ -148,7 +169,7 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; } } - }); + }]; return musicTimeStamp; } @@ -164,7 +185,7 @@ - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { __block MIDITimeStamp midiTimeStamp = 0; - dispatch_sync(self.clockQueue, ^{ + [self dispatchToClockQueue:^{ if (self.isReady) { midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; @@ -180,7 +201,7 @@ - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp } } } - }); + }]; return midiTimeStamp; } @@ -189,9 +210,9 @@ - (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { __block MIDITimeStamp midiTimeStamps = 0; - dispatch_sync(self.clockQueue, ^{ + [self dispatchToClockQueue:^{ if (self.isReady) midiTimeStamps = musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp; - }); + }]; return midiTimeStamps; } @@ -202,7 +223,7 @@ - (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { __block Float64 tempo = 0; - dispatch_sync(self.clockQueue, ^{ + [self dispatchToClockQueue:^{ if (self.isReady) { if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { tempo = self.currentTempo; @@ -210,7 +231,7 @@ - (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo = [[self clockForMIDITimeStamp:midiTimeStamp] currentTempo]; } } - }); + }]; return tempo; } From 80eb882edacb6495b9b7456a1c8282d84092081b Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 10 Jun 2015 10:03:53 -0500 Subject: [PATCH 162/284] Issue #94: Added a way for MIKMIDISequencer subclasses to modify parsed MIDI commands before they get sent to their destinations. --- Source/MIKMIDISequencer.h | 17 +++++++++++++++++ Source/MIKMIDISequencer.m | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 6e2d43c0..5d341cfa 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -161,6 +161,23 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ - (void)recordMIDICommand:(MIKMIDICommand *)command; +/** + * Allows subclasses to modify the MIDI commands that are about to be + * scheduled with a destination endpoint. + * + * @param commandsToBeScheduled An array of MIKMIDICommands that are about + * to be scheduled. + * + * @param endpoint The destination endpoint the commands will be sent to after + * they are modified. + * + * @note You should not call this method directly. It is made public solely to + * give subclasses a chance to alter or override any MIDI commands parsed from the + * MIDI sequence before they get sent to their destination endpoint. + * + */ +- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forEndpoint:(MIKMIDIDestinationEndpoint *)endpoint; + #pragma mark - Configuration /** diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index ec953d5c..cf3c5be8 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -401,12 +401,16 @@ - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Floa - (void)sendCommands:(NSArray *)commands toDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint { + commands = [self modifiedMIDICommandsFromCommandsToBeScheduled:commands forEndpoint:endpoint]; + NSError *error; if (commands.count && ![[MIKMIDIDeviceManager sharedDeviceManager] sendCommands:commands toEndpoint:endpoint error:&error]) { NSLog(@"%@: An error occurred scheduling the commands %@ for destination endpoint %@. %@", NSStringFromClass([self class]), commands, endpoint, error); } } +- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forEndpoint:(MIKMIDIDestinationEndpoint *)endpoint { return commandsToBeScheduled; } + #pragma mark - Recording - (void)startRecording From 9e2f1da428c8ac146474df82f30e9d7dd8618c04 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 10 Jun 2015 10:41:25 -0500 Subject: [PATCH 163/284] Removed unnecessary method call. --- Source/MIKMIDISequencer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index cf3c5be8..b7b05189 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -242,7 +242,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (self.needsCurrentTempoUpdate) { if (!tempoEventsByTimeStamp.count) { - if (!overrideTempo) overrideTempo = [self.sequence tempoAtTimeStamp:fromMusicTimeStamp]; + if (!overrideTempo) overrideTempo = [sequence tempoAtTimeStamp:fromMusicTimeStamp]; if (!overrideTempo) overrideTempo = kDefaultTempo; MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:fromMusicTimeStamp tempo:overrideTempo]; From 8f6e6d0a5627aaa7a62673e9bbbf1f857be98244 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 10 Jun 2015 13:32:24 -0500 Subject: [PATCH 164/284] Issue #95: MIKMIDIClock now checks if you're converting the last synced MusicTimeStamp or MIDITimeStamp and returns the correct exact value. This fixes some issues with floating point precision that could cause MIKMIDISequencer to skip the first events in a sequence in some situations. --- Source/MIKMIDIClock.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 013009da..0d11755a 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -31,6 +31,7 @@ @interface MIKMIDIClock () @property (nonatomic) Float64 currentTempo; @property (nonatomic) MIDITimeStamp timeStampZero; @property (nonatomic) MIDITimeStamp lastSyncedMIDITimeStamp; +@property (nonatomic) MusicTimeStamp lastSyncedMusicTimeStamp; @property (nonatomic) Float64 musicTimeStampsPerMIDITimeStamp; @property (nonatomic) Float64 midiTimeStampsPerMusicTimeStamp; @@ -140,6 +141,7 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID self.currentTempo = tempo; self.lastSyncedMIDITimeStamp = midiTimeStamp; + self.lastSyncedMusicTimeStamp = musicTimeStamp; self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; @@ -159,6 +161,7 @@ - (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { + if (midiTimeStamp == self.lastSyncedMIDITimeStamp) return self.lastSyncedMusicTimeStamp; __block MusicTimeStamp musicTimeStamp = 0; [self dispatchToClockQueue:^{ @@ -183,6 +186,7 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp wi - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { + if (musicTimeStamp == self.lastSyncedMusicTimeStamp) return self.lastSyncedMIDITimeStamp; __block MIDITimeStamp midiTimeStamp = 0; [self dispatchToClockQueue:^{ From 8ca9f37d242e424c2e3fece156b70a93d0f8233a Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 10 Jun 2015 17:20:30 -0500 Subject: [PATCH 165/284] Fixed timer frequency --- Source/MIKMIDISequencer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index b7b05189..efce32a3 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -164,7 +164,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi if (!timer) return NSLog(@"Unable to create processing timer for %@.", [self class]); self.processingTimer = timer; - dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.05, 0.05); + dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.05 * NSEC_PER_SEC, 0.05 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ [self processSequenceStartingFromMIDITimeStamp:self.latestScheduledMIDITimeStamp + 1]; }); @@ -366,7 +366,7 @@ - (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp if (!noteOffs.count) return; NSMutableOrderedSet *noteOffTimeStamps = self.pendingNoteOffMIDITimeStamps; for (NSNumber *midiTimeStampNumber in [noteOffTimeStamps copy]) { - MIDITimeStamp timeStamp = [midiTimeStampNumber unsignedLongLongValue]; + MIDITimeStamp timeStamp = midiTimeStampNumber.unsignedLongLongValue; if (timeStamp > toTimeStamp) continue; NSArray *noteOffsAtTimeStamp = noteOffs[midiTimeStampNumber]; From 588778041d25a74a7d20b6182c184ba5ebad9bfe Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 11 Jun 2015 12:46:32 -0500 Subject: [PATCH 166/284] Issue #96: The looping property now works properly. --- Source/MIKMIDISequencer.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index efce32a3..37a93206 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -217,9 +217,9 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MusicTimeStamp loopStartTimeStamp = self.loopStartTimeStamp + playbackOffset; MusicTimeStamp loopEndTimeStamp = self.actualLoopEndTimeStamp + playbackOffset; MusicTimeStamp fromMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:fromMIDITimeStamp]; - MusicTimeStamp calculatedToMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:toMIDITimeStamp];\ - BOOL isLooping = (self.shouldLoop && !self.isLooping && calculatedToMusicTimeStamp > loopStartTimeStamp && loopEndTimeStamp > loopStartTimeStamp); - self.looping = isLooping; + MusicTimeStamp calculatedToMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:toMIDITimeStamp]; + BOOL isLooping = (self.shouldLoop && calculatedToMusicTimeStamp > loopStartTimeStamp && loopEndTimeStamp > loopStartTimeStamp); + if (isLooping != self.isLooping) self.looping = isLooping; MusicTimeStamp toMusicTimeStamp = MIN(calculatedToMusicTimeStamp, isLooping ? loopEndTimeStamp : self.sequenceLength); // Send pending note off commands From 7ec4129228cbe8cda5b5f448cfac8eca98b3ff65 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 11 Jun 2015 15:29:47 -0600 Subject: [PATCH 167/284] Added 'mapping' property to MIKMIDIMappingItem. --- Source/MIKMIDIMapping.h | 19 +++++++++++++++++-- Source/MIKMIDIMapping.m | 20 ++++++++++++++++---- Source/MIKMIDIMappingItem.h | 8 ++++++++ Source/MIKMIDIMappingItem.m | 6 ++++++ 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index d305eb9d..6285be61 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -154,14 +154,29 @@ /** * The mapping items that map controls to a specific command identifier supported by a MIDI responder. * - * @param identifier An NSString containing one of the responder's supported command identifiers. + * @param commandID An NSString containing one of the responder's supported command identifiers. * @param responder An object that coforms to the MIKMIDIMappableResponder protocol. * * @return An NSSet containing MIKMIDIMappingItems for the responder and command identifer, or an empty set if none are found. * * @see -[ commandIdentifiers] + * @see -mappingItemsForCommandIdentifier:responderWithIdentifier: */ -- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)identifier responder:(id)responder; +- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)commandID responder:(id)responder; + +/** + * The mapping items that map controls to a specific command identifier supported by a MIDI responder with a given + * identifier. + * + * @param commandID An NSString containing one of the responder's supported command identifiers. + * @param responderID An NSString + * + * @return An NSSet containing MIKMIDIMappingItems for the responder and command identifer, or an empty set if none are found. + * + * @see -[ commandIdentifiers] + * @see -mappingItemsForCommandIdentifier:responder: + */ +- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)commandID responderWithIdentifier:(NSString *)responderID; /** * The mapping items for a particular MIDI command (corresponding to a physical control). diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index b56b3087..029f0232 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -32,6 +32,8 @@ - (instancetype)initWithXMLElement:(NSXMLElement *)element; - (NSXMLElement *)XMLRepresentation; #endif +@property (nonatomic, weak, readwrite) MIKMIDIMapping *mapping; + @end @interface MIKMIDIMapping () @@ -320,14 +322,18 @@ - (NSSet *)mappingItemsForMIDIResponder:(id)responder; return matches; } -- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)identifier responder:(id)responder; +- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)commandID responder:(id)responder; { NSString *MIDIIdentifer = [responder MIDIIdentifier]; + return [self mappingItemsForCommandIdentifier:commandID responderWithIdentifier:MIDIIdentifer]; +} +- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)commandID responderWithIdentifier:(NSString *)responderID +{ NSMutableSet *matches = [NSMutableSet set]; for (MIKMIDIMappingItem *item in self.internalMappingItems) { - if (![item.MIDIResponderIdentifier isEqualToString:MIDIIdentifer]) continue; - if (![item.commandIdentifier isEqualToString:identifier]) continue; + if (![item.MIDIResponderIdentifier isEqualToString:responderID]) continue; + if (![item.commandIdentifier isEqualToString:commandID]) continue; [matches addObject:item]; } @@ -382,7 +388,7 @@ - (BOOL)loadPropertiesFromXMLDocument:(NSXMLDocument *)xmlDocument for (NSXMLElement *element in mappingItemElements) { MIKMIDIMappingItem *item = [[MIKMIDIMappingItem alloc] initWithXMLElement:element]; if (!item) continue; - [self.internalMappingItems addObject:item]; + [self addMappingItemsObject:item]; } NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; @@ -420,21 +426,27 @@ - (NSSet *)mappingItems { return [self.internalMappingItems copy]; } - (void)addMappingItemsObject:(MIKMIDIMappingItem *)mappingItem { [self.internalMappingItems addObject:mappingItem]; + mappingItem.mapping = self; } - (void)addMappingItems:(NSSet *)mappingItems { [self.internalMappingItems unionSet:mappingItems]; + [mappingItems setValue:self forKey:@"mapping"]; } - (void)removeMappingItemsObject:(MIKMIDIMappingItem *)mappingItem { + mappingItem.mapping = nil; [self.internalMappingItems removeObject:mappingItem]; } - (void)removeMappingItems:(NSSet *)mappingItems { + NSMutableSet *removedMappingItems = [self.internalMappingItems mutableCopy]; [self.internalMappingItems minusSet:mappingItems]; + [removedMappingItems minusSet:self.internalMappingItems]; + for (MIKMIDIMappingItem *item in removedMappingItems) { item.mapping = nil; } } - (NSString *)name diff --git a/Source/MIKMIDIMappingItem.h b/Source/MIKMIDIMappingItem.h index d998003a..f9dfaaff 100644 --- a/Source/MIKMIDIMappingItem.h +++ b/Source/MIKMIDIMappingItem.h @@ -10,6 +10,8 @@ #import "MIKMIDIMappableResponder.h" #import "MIKMIDICommand.h" +@class MIKMIDIMapping; + /** * MIKMIDIMappingItem contains information about a mapping between a physical MIDI control, * and a single command supported by a particular MIDI responder object. @@ -92,4 +94,10 @@ */ @property (nonatomic, copy) NSDictionary *additionalAttributes; +/** + * The MIDI Mapping the receiver belongs to. May be nil if the mappping item hasn't been added to a mapping yet, + * or its mapping has been deallocated. + */ +@property (nonatomic, weak, readonly) MIKMIDIMapping *mapping; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMappingItem.m b/Source/MIKMIDIMappingItem.m index cdf81003..b19ef584 100644 --- a/Source/MIKMIDIMappingItem.m +++ b/Source/MIKMIDIMappingItem.m @@ -14,6 +14,12 @@ #import #endif +@interface MIKMIDIMappingItem () + +@property (nonatomic, weak, readwrite) MIKMIDIMapping *mapping; + +@end + @implementation MIKMIDIMappingItem - (instancetype)initWithMIDIResponderIdentifier:(NSString *)MIDIResponderIdentifier andCommandIdentifier:(NSString *)commandIdentifier; From 14a7f52820c95183bab97cb8df7791b1ede7b031 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 12 Jun 2015 08:54:22 -0500 Subject: [PATCH 168/284] Rewrote MIKMIDISequencer's pending note off machinery. This fixes issue #79. --- Source/MIKMIDINoteEvent.h | 2 + Source/MIKMIDINoteEvent.m | 16 ++-- Source/MIKMIDISequencer.m | 149 ++++++++++++++++++++++++-------------- 3 files changed, 106 insertions(+), 61 deletions(-) diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 40c479c9..61986c7c 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -116,5 +116,7 @@ @interface MIKMIDICommand (MIKMIDINoteEventToCommands) + (NSArray *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; ++ (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; ++ (MIKMIDINoteOffCommand *)noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; @end \ No newline at end of file diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 71b07c5f..acee0f5d 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -180,21 +180,27 @@ @implementation MIKMIDICommand (MIKMIDINoteEventToCommands) + (NSArray *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock { - // Note On + return @[[self noteOnCommandFromNoteEvent:noteEvent clock:clock], [self noteOffCommandFromNoteEvent:noteEvent clock:clock]]; +} + ++ (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock +{ MIKMutableMIDINoteOnCommand *noteOn = [MIKMutableMIDINoteOnCommand commandForCommandType:MIKMIDICommandTypeNoteOn]; noteOn.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:noteEvent.timeStamp]; noteOn.channel = noteEvent.channel; noteOn.note = noteEvent.note; noteOn.velocity = noteEvent.velocity; - - // Note Off + return [noteOn copy]; +} + + +(MIKMIDINoteOffCommand *)noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock +{ MIKMutableMIDINoteOffCommand *noteOff = [MIKMutableMIDINoteOffCommand commandForCommandType:MIKMIDICommandTypeNoteOff]; noteOff.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:noteEvent.endTimeStamp]; noteOff.channel = noteEvent.channel; noteOff.note = noteEvent.note; noteOff.velocity = noteEvent.releaseVelocity; - - return @[[noteOn copy], [noteOff copy]]; + return [noteOff copy]; } @end diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 37a93206..f84457a1 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -38,7 +38,9 @@ @interface MIKMIDIEventWithDestination : NSObject @property (nonatomic, strong) MIKMIDIEvent *event; @property (nonatomic, strong) MIKMIDIDestinationEndpoint *destination; +@property (nonatomic, readonly) BOOL representsNoteOff; + (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event; ++ (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event representsNoteOff:(BOOL)representsNoteOff; @end @@ -49,6 +51,12 @@ + (instancetype)commandWithDestination:(MIKMIDIDestinationEndpoint *)destination @end +@interface MIKMIDIPendingNoteOffsForTimeStamp : NSObject +@property (nonatomic, strong) NSMutableArray *noteEventsWithEndTimeStamp; +@property (nonatomic) MusicTimeStamp endTimeStamp; ++ (instancetype)pendingNoteOffWithEndTimeStamp:(MusicTimeStamp)endTimeStamp; +@end + #pragma mark - @@ -65,7 +73,6 @@ @interface MIKMIDISequencer () @property (nonatomic) MIDITimeStamp latestScheduledMIDITimeStamp; @property (nonatomic, strong) NSMutableDictionary *pendingNoteOffs; -@property (nonatomic, strong) NSMutableOrderedSet *pendingNoteOffMIDITimeStamps; @property (nonatomic, strong) NSMutableDictionary *pendingRecordedNoteEvents; @@ -158,7 +165,6 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi dispatch_sync(self.processingQueue, ^{ self.pendingNoteOffs = [NSMutableDictionary dictionary]; - self.pendingNoteOffMIDITimeStamps = [NSMutableOrderedSet orderedSet]; self.latestScheduledMIDITimeStamp = midiTimeStamp - 1; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.processingQueue); if (!timer) return NSLog(@"Unable to create processing timer for %@.", [self class]); @@ -187,10 +193,9 @@ - (void)stop self.processingTimer = NULL; MIKMIDIClock *clock = self.clock; - [self sendPendingNoteOffCommandsUpToMIDITimeStamp:0]; - self.pendingNoteOffs = nil; - self.pendingNoteOffMIDITimeStamps = nil; [self recordAllPendingNoteEventsWithOffTimeStamp:[clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; + MusicTimeStamp allPendingNotesOffTimeStamp = MAX(self.latestScheduledMIDITimeStamp + 1, MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.001]); + [self sendAllPendingNoteOffsWithMIDITimeStamp:allPendingNotesOffTimeStamp]; self.pendingRecordedNoteEvents = nil; self.looping = NO; @@ -224,7 +229,6 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam // Send pending note off commands MIDITimeStamp actualToMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:toMusicTimeStamp]; - [self sendPendingNoteOffCommandsUpToMIDITimeStamp:actualToMIDITimeStamp]; // Get relevant tempo events NSMutableDictionary *allEventsByTimeStamp = [NSMutableDictionary dictionary]; @@ -253,6 +257,20 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam self.needsCurrentTempoUpdate = NO; } + // Get pending note off events + NSMutableDictionary *pendingNoteOffs = self.pendingNoteOffs; + for (NSNumber *timeStampKey in [pendingNoteOffs copy]) { + MusicTimeStamp pendingNoteOffsMusicTimeStamp = timeStampKey.doubleValue; + if (pendingNoteOffsMusicTimeStamp < fromMusicTimeStamp) continue; + if (pendingNoteOffsMusicTimeStamp > toMusicTimeStamp) continue; + if (isLooping && (pendingNoteOffsMusicTimeStamp == loopEndTimeStamp)) continue; // These pending note offs will be handled right before we loop + + NSMutableArray *eventsAtTimeStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; + [eventsAtTimeStamp addObject:pendingNoteOffs[timeStampKey]]; + allEventsByTimeStamp[timeStampKey] = eventsAtTimeStamp; + [pendingNoteOffs removeObjectForKey:timeStampKey]; + } + // Get other events for (MIKMIDITrack *track in sequence.tracks) { NSArray *events = [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; @@ -287,6 +305,10 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam for (id eventObject in events) { if ([eventObject isKindOfClass:[MIKMIDIEventWithDestination class]]) { [self scheduleEventWithDestination:eventObject]; + } else if ([eventObject isKindOfClass:[MIKMIDIPendingNoteOffsForTimeStamp class]]) { + for (MIKMIDIEventWithDestination *noteOffEvent in [eventObject noteEventsWithEndTimeStamp]) { + [self scheduleEventWithDestination:noteOffEvent]; + } } } } @@ -302,6 +324,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MusicTimeStamp loopLength = loopEndTimeStamp - loopStartTimeStamp; MIDITimeStamp loopStartMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:loopStartTimeStamp + loopLength]; + [self sendAllPendingNoteOffsWithMIDITimeStamp:loopStartMIDITimeStamp]; [self updateClockWithMusicTimeStamp:loopStartTimeStamp tempo:tempo atMIDITimeStamp:loopStartMIDITimeStamp]; self.startingTimeStamp = loopStartTimeStamp; @@ -320,75 +343,65 @@ - (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationE { MIKMIDIEvent *event = destinationEvent.event; MIKMIDIDestinationEndpoint *destination = destinationEvent.destination; - NSMutableDictionary *pendingNoteOffs = self.pendingNoteOffs; - NSMutableOrderedSet *pendingNoteOffTimeStamps = self.pendingNoteOffMIDITimeStamps; + MIKMIDIClock *clock = self.clock; + MIKMIDICommand *command; - NSArray *commands = nil; if (event.eventType == MIKMIDIEventTypeMIDINoteMessage) { - NSArray *noteCommands = [MIKMIDICommand commandsFromNoteEvent:(MIKMIDINoteEvent *)event clock:self.clock]; - commands = @[ [noteCommands firstObject] ]; // note on - - // Add note off to pending note offs - MIKMIDINoteOffCommand *noteOff = [noteCommands lastObject]; - MIDITimeStamp noteOffTimeStamp = noteOff.midiTimestamp + [self.clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; - NSMutableArray *pendingNoteOffsAtTimeStamp = pendingNoteOffs[@(noteOffTimeStamp)]; - if (!pendingNoteOffsAtTimeStamp) pendingNoteOffsAtTimeStamp = [NSMutableArray array]; - NSNumber *timeStampNumber = @(noteOffTimeStamp); - [pendingNoteOffsAtTimeStamp addObject:[MIKMIDICommandWithDestination commandWithDestination:destination command:noteOff]]; - pendingNoteOffs[@(noteOffTimeStamp)] = pendingNoteOffsAtTimeStamp; - [pendingNoteOffTimeStamps addObject:timeStampNumber]; + if (destinationEvent.representsNoteOff) { + command = [MIKMIDICommand noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)event clock:clock]; + } else { + MIKMIDINoteEvent *noteEvent = (MIKMIDINoteEvent *)event; + command = [MIKMIDICommand noteOnCommandFromNoteEvent:noteEvent clock:clock]; + + // Add note off to pending note offs + MusicTimeStamp endTimeStamp = noteEvent.endTimeStamp; + NSMutableDictionary *pendingNoteOffs = self.pendingNoteOffs; + MIKMIDIPendingNoteOffsForTimeStamp *pendingNoteOffsForEndTimeStamp = pendingNoteOffs[@(endTimeStamp)]; + if (!pendingNoteOffsForEndTimeStamp) { + pendingNoteOffsForEndTimeStamp = [MIKMIDIPendingNoteOffsForTimeStamp pendingNoteOffWithEndTimeStamp:endTimeStamp]; + pendingNoteOffs[@(endTimeStamp)] = pendingNoteOffsForEndTimeStamp; + } + [pendingNoteOffsForEndTimeStamp.noteEventsWithEndTimeStamp addObject:[MIKMIDIEventWithDestination eventWithDestination:destination event:event representsNoteOff:YES]]; + } } else if ([event isKindOfClass:[MIKMIDIChannelEvent class]]) { - MIKMIDICommand *command = [MIKMIDICommand commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:self.clock]; - commands = [NSArray arrayWithObjects:command, nil]; + command = [MIKMIDICommand commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:clock]; } - // Adjust commands' time stamps to account for our playback offset. - NSMutableArray *adjustedCommands = [NSMutableArray array]; - for (MIKMIDICommand *command in commands) { - MIKMutableMIDICommand *scratch = [command mutableCopy]; - scratch.midiTimestamp += [self.clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; - [adjustedCommands addObject:scratch]; + // Adjust command time stamp to account for our playback offset. + if (command) { + MIKMutableMIDICommand *adjustedCommand = [command mutableCopy]; + adjustedCommand.midiTimestamp += [clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; + [self sendCommands:@[adjustedCommand] toDestinationEndpoint:destination]; } - - if ([adjustedCommands count]) [self sendCommands:adjustedCommands toDestinationEndpoint:destination]; } -- (void)sendPendingNoteOffCommandsUpToMIDITimeStamp:(MIDITimeStamp)toTimeStamp +- (void)sendAllPendingNoteOffsWithMIDITimeStamp:(MIDITimeStamp)offTimeStamp { - MIDITimeStamp allPendingNotesOffTimeStamp = 0; - if (toTimeStamp == 0) { // All notes off - toTimeStamp = ULONG_LONG_MAX; - allPendingNotesOffTimeStamp = MAX(self.latestScheduledMIDITimeStamp + 1, MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.001]); - } - - NSMapTable *noteOffDestinationsToCommands = [NSMapTable strongToStrongObjectsMapTable]; NSMutableDictionary *noteOffs = self.pendingNoteOffs; if (!noteOffs.count) return; - NSMutableOrderedSet *noteOffTimeStamps = self.pendingNoteOffMIDITimeStamps; - for (NSNumber *midiTimeStampNumber in [noteOffTimeStamps copy]) { - MIDITimeStamp timeStamp = midiTimeStampNumber.unsignedLongLongValue; - if (timeStamp > toTimeStamp) continue; - - NSArray *noteOffsAtTimeStamp = noteOffs[midiTimeStampNumber]; - for (MIKMIDICommandWithDestination *destinationCommand in noteOffsAtTimeStamp) { - MIKMIDIDestinationEndpoint *destination = destinationCommand.destination; + + NSMapTable *noteOffDestinationsToCommands = [NSMapTable strongToStrongObjectsMapTable]; + MIKMIDIClock *clock = self.clock; + + for (NSNumber *musicTimeStampNumber in noteOffs) { + MIKMIDIPendingNoteOffsForTimeStamp *pendingNoteOffs = noteOffs[musicTimeStampNumber]; + for (MIKMIDIEventWithDestination *noteOffEventWithDestination in pendingNoteOffs.noteEventsWithEndTimeStamp) { + MIKMIDINoteEvent *event = (MIKMIDINoteEvent *)noteOffEventWithDestination.event; + MIKMIDIDestinationEndpoint *destination = noteOffEventWithDestination.destination; NSMutableArray *noteOffCommandsForDestination = [noteOffDestinationsToCommands objectForKey:destination] ? [noteOffDestinationsToCommands objectForKey:destination] : [NSMutableArray array]; - MIKMIDICommand *command = destinationCommand.command; - if (allPendingNotesOffTimeStamp) { - MIKMutableMIDICommand *mutableCommand = [command mutableCopy]; - mutableCommand.midiTimestamp = allPendingNotesOffTimeStamp; - command = mutableCommand; - } - [noteOffCommandsForDestination addObject:command]; + + MIKMutableMIDICommand *noteOffCommand = [[MIKMIDICommand noteOffCommandFromNoteEvent:event clock:clock] mutableCopy]; + noteOffCommand.midiTimestamp = offTimeStamp; + [noteOffCommandsForDestination addObject:noteOffCommand]; [noteOffDestinationsToCommands setObject:noteOffCommandsForDestination forKey:destination]; } - [noteOffTimeStamps removeObject:midiTimeStampNumber]; - [noteOffs removeObjectForKey:midiTimeStampNumber]; } for (MIKMIDIDestinationEndpoint *endpoint in [[noteOffDestinationsToCommands keyEnumerator] allObjects]) { [self sendCommands:[noteOffDestinationsToCommands objectForKey:endpoint] toDestinationEndpoint:endpoint]; } + + [noteOffs removeAllObjects]; } - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp @@ -721,10 +734,16 @@ - (void)setSequence:(MIKMIDISequence *)sequence @implementation MIKMIDIEventWithDestination + (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event +{ + return [self eventWithDestination:destination event:event representsNoteOff:NO]; +} + ++ (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event representsNoteOff:(BOOL)representsNoteOff { MIKMIDIEventWithDestination *destinationEvent = [[self alloc] init]; destinationEvent->_event = event; destinationEvent->_destination = destination; + destinationEvent->_representsNoteOff = representsNoteOff; return destinationEvent; } @@ -742,3 +761,21 @@ + (instancetype)commandWithDestination:(MIKMIDIDestinationEndpoint *)destination } @end + + +@implementation MIKMIDIPendingNoteOffsForTimeStamp + ++ (instancetype)pendingNoteOffWithEndTimeStamp:(MusicTimeStamp)endTimeStamp +{ + MIKMIDIPendingNoteOffsForTimeStamp *noteOff = [[self alloc] init]; + noteOff->_endTimeStamp = endTimeStamp; + return noteOff; +} + +- (NSMutableArray *)noteEventsWithEndTimeStamp +{ + if (!_noteEventsWithEndTimeStamp) _noteEventsWithEndTimeStamp = [NSMutableArray array]; + return _noteEventsWithEndTimeStamp; +} + +@end From 64f254246c10b37fdd0498fe22744db5937756de Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 12 Jun 2015 10:50:26 -0500 Subject: [PATCH 169/284] Added MIKMIDISequencerEndOfSequenceLoopEndTimeStamp constant to make setting the loopEndTimeStamp to the end of the sequence easier to read. --- Source/MIKMIDISequencer.h | 9 ++++++++- Source/MIKMIDISequencer.m | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 5d341cfa..0714f35a 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -315,7 +315,8 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * The loop's ending time stamp during looped playback. * * @note To have the loop end at the end of the sequence, regardless of - * sequence length, set this value to less than 0. The default is -1. + * sequence length, set this value to MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. + * The default is MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. */ @property (nonatomic) MusicTimeStamp loopEndTimeStamp; @@ -359,3 +360,9 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * Sent out shortly before playback loops. */ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; + +/** + * Set loopEndTimeStamp to this to have the loop end at the end of the + * sequence regardless of sequence length. + */ +FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index f84457a1..d6a7397c 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -31,6 +31,7 @@ NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; +const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp = -1; #pragma mark - @@ -103,7 +104,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence self.sequence = sequence; _clock = [MIKMIDIClock clock]; _syncedClock = [_clock syncedClock]; - _loopEndTimeStamp = -1; + _loopEndTimeStamp = MIKMIDISequencerEndOfSequenceLoopEndTimeStamp; _preRoll = 4; _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; _tracksToDestinationsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; From 1392cfe52ddacaffa24d345d6bbd19210309b1ce Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 12 Jun 2015 11:46:23 -0600 Subject: [PATCH 170/284] Fixed compile warnings on iOS. --- Source/MIKMIDIClock.h | 6 +++--- Source/MIKMIDIClock.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 161c45f7..3432e810 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -56,7 +56,7 @@ * with time stamps more than one second older than the time stamps set with this method * may not necessarily return accurate information. * - * @see -unsyncMusicTimeStampsTemposFromMIDITimeStamps + * @see -unsyncMusicTimeStampsAndTemposFromMIDITimeStamps * @see -isReady */ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo:(Float64)tempo; @@ -117,7 +117,7 @@ * information as the clock instance that dispensed the synced clock. * * Calling -syncMusicTimeStamp:withMIDITimeStamp:tempo: or - * -unsyncMusicTimeStampsTemposFromMIDITimeStamps on the synced clock + * -unsyncMusicTimeStampsAndTemposFromMIDITimeStamps on the synced clock * has no effect. */ - (MIKMIDIClock *)syncedClock; @@ -153,7 +153,7 @@ * and is ready to use for getting tempo and timing information. * * @see -syncMusicTimeStamp:withMIDITimeStamp:tempo: - * @see -unsyncMusicTimeStampsTemposFromMIDITimeStamps + * @see -unsyncMusicTimeStampsAndTemposFromMIDITimeStamps */ @property (readonly, nonatomic, getter=isReady) BOOL ready; diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 0d11755a..23a9970e 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -311,7 +311,7 @@ - (void)forwardInvocation:(NSInvocation *)invocation { SEL selector = invocation.selector; if (selector == @selector(syncMusicTimeStamp:withMIDITimeStamp:tempo:)) return; - if (selector == @selector(unsyncMusicTimeStampsTemposFromMIDITimeStamps)) return; + if (selector == @selector(unsyncMusicTimeStampsAndTemposFromMIDITimeStamps)) return; if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; // deprecated if (selector == @selector(syncedClock)) { From c8c7f13a71a6cdea20d27ceb97c89f851d676c48 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 12 Jun 2015 14:14:45 -0500 Subject: [PATCH 171/284] Made loopStartTimeStamp, and loopEndTimeStamp readonly and added -setLoopStartTimeStamp:endLoopTimeStamp: to ensure that loop points are set simultaneously and make sense. --- Source/MIKMIDISequencer.h | 76 +++++++++++++++++++++++++-------------- Source/MIKMIDISequencer.m | 24 +++++++++++++ 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 0714f35a..d830e995 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -104,6 +104,45 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ - (void)resumePlayback; +/** + * Stops all playback and recording. + */ +- (void)stop; + +/** + * Allows subclasses to modify the MIDI commands that are about to be + * scheduled with a destination endpoint. + * + * @param commandsToBeScheduled An array of MIKMIDICommands that are about + * to be scheduled. + * + * @param endpoint The destination endpoint the commands will be sent to after + * they are modified. + * + * @note You should not call this method directly. It is made public solely to + * give subclasses a chance to alter or override any MIDI commands parsed from the + * MIDI sequence before they get sent to their destination endpoint. + * + */ +- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forEndpoint:(MIKMIDIDestinationEndpoint *)endpoint; + +/** + * Sets the loopStartTimeStamp and loopEndTimeStamp properties. + * + * @param loopStartTimeStamp The MusicTimeStamp to begin looping at. + * + * @param loopEndTimeStamp The MusicTimeStamp to end looping at. To have + * the loop end at the end of the sequence, regardless of sequence length, + * pass in MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. + * + * @see loopStartTimeStamp + * @see loopEndTimeStamp + * @see loop + * @see looping + */ +- (void)setLoopStartTimeStamp:(MusicTimeStamp)loopStartTimeStamp endTimeStamp:(MusicTimeStamp)loopEndTimeStamp; + + #pragma mark - Recording /** @@ -144,11 +183,6 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ - (void)resumeRecording; -/** - * Stops all playback and recording. - */ -- (void)stop; - /** * Records a MIDI command to the record enabled tracks. * @@ -161,23 +195,6 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ - (void)recordMIDICommand:(MIKMIDICommand *)command; -/** - * Allows subclasses to modify the MIDI commands that are about to be - * scheduled with a destination endpoint. - * - * @param commandsToBeScheduled An array of MIKMIDICommands that are about - * to be scheduled. - * - * @param endpoint The destination endpoint the commands will be sent to after - * they are modified. - * - * @note You should not call this method directly. It is made public solely to - * give subclasses a chance to alter or override any MIDI commands parsed from the - * MIDI sequence before they get sent to their destination endpoint. - * - */ -- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forEndpoint:(MIKMIDIDestinationEndpoint *)endpoint; - #pragma mark - Configuration /** @@ -302,23 +319,28 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * @see loop * @see loopStartTimeStamp * @see loopEndTimeStamp + * @see -setLoopStartTimeStamp:loopEndTimeStamp: * @see currentTimeStamp */ @property (readonly, nonatomic, getter=isLooping) BOOL looping; /** * The loop's beginning time stamp during looped playback. + * + * @see -setLoopStartTimeStamp:loopEndTimeStamp: */ -@property (nonatomic) MusicTimeStamp loopStartTimeStamp; +@property (readonly, nonatomic) MusicTimeStamp loopStartTimeStamp; /** * The loop's ending time stamp during looped playback. * - * @note To have the loop end at the end of the sequence, regardless of - * sequence length, set this value to MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. - * The default is MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. + * @note When this is set to MIKMIDISequencerEndOfSequenceLoopEndTimeStamp + * the loopEndTimeStamp will be treated as if it is set to the length of the + * sequence. The default is MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. + * + * @see -setLoopStartTimeStamp:loopEndTimeStamp: */ -@property (nonatomic) MusicTimeStamp loopEndTimeStamp; +@property (readonly, nonatomic) MusicTimeStamp loopEndTimeStamp; /** * The metronome to send click track events to. diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index d6a7397c..d83d57f0 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -603,6 +603,30 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp return clickEvents; } +#pragma mark - Loop Points + +- (void)setLoopStartTimeStamp:(MusicTimeStamp)loopStartTimeStamp endTimeStamp:(MusicTimeStamp)loopEndTimeStamp +{ + if (loopEndTimeStamp != MIKMIDISequencerEndOfSequenceLoopEndTimeStamp && (loopStartTimeStamp >= loopEndTimeStamp)) return; + + [self willChangeValueForKey:@"loopStartTimeStamp"]; + [self didChangeValueForKey:@"loopStartTimeStamp"]; + + dispatch_queue_t queue = self.processingQueue; + if (queue) { + dispatch_sync(queue, ^{ + _loopStartTimeStamp = loopStartTimeStamp; + _loopEndTimeStamp = loopEndTimeStamp; + }); + } else { + _loopStartTimeStamp = loopStartTimeStamp; + _loopEndTimeStamp = loopEndTimeStamp; + } + + [self willChangeValueForKey:@"loopEndTimeStamp"]; + [self didChangeValueForKey:@"loopEndTimeStamp"]; +} + #pragma mark - Timer - (void)processingTimerFired:(NSTimer *)timer From 5ff1f1044d3a365a67f9702ec2f2c83f913cde52 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 12 Jun 2015 14:21:37 -0500 Subject: [PATCH 172/284] -sendAllPendingNoteOffsWithMIDITimeStamp: now runs all of its note off commands through -modifiedMIDICommandsFromCommandsToBeScheduled:forEndpoint: --- Source/MIKMIDISequencer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index d83d57f0..fd99bc8c 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -399,7 +399,8 @@ - (void)sendAllPendingNoteOffsWithMIDITimeStamp:(MIDITimeStamp)offTimeStamp } for (MIKMIDIDestinationEndpoint *endpoint in [[noteOffDestinationsToCommands keyEnumerator] allObjects]) { - [self sendCommands:[noteOffDestinationsToCommands objectForKey:endpoint] toDestinationEndpoint:endpoint]; + NSArray *commands = [self modifiedMIDICommandsFromCommandsToBeScheduled:[noteOffDestinationsToCommands objectForKey:endpoint] forEndpoint:endpoint]; + [self sendCommands:commands toDestinationEndpoint:endpoint]; } [noteOffs removeAllObjects]; From e7402fd47942c0f600d246b6480f063c244ed254 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 12 Jun 2015 14:28:19 -0500 Subject: [PATCH 173/284] Fixed hang when sequencer would stop itself at the end of the sequence. --- Source/MIKMIDISequencer.m | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index fd99bc8c..d17d9655 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -152,8 +152,11 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi if (self.isPlaying) [self stop]; NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - self.processingQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); - dispatch_sync(self.processingQueue, ^{ + + dispatch_queue_t queue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + self.processingQueue = queue; + + dispatch_sync(queue, ^{ MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; self.startingTimeStamp = startingTimeStamp; @@ -164,7 +167,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi self.playing = YES; - dispatch_sync(self.processingQueue, ^{ + dispatch_sync(queue, ^{ self.pendingNoteOffs = [NSMutableDictionary dictionary]; self.latestScheduledMIDITimeStamp = midiTimeStamp - 1; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.processingQueue); @@ -186,11 +189,16 @@ - (void)resumePlayback } - (void)stop +{ + [self stopWithDispatchToProcessingQueue:YES]; +} + +- (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue { MIDITimeStamp stopTimeStamp = MIKMIDIGetCurrentTimeStamp(); if (!self.isPlaying) return; - dispatch_sync(self.processingQueue, ^{ + void (^stopPlayback)() = ^{ self.processingTimer = NULL; MIKMIDIClock *clock = self.clock; @@ -204,7 +212,9 @@ - (void)stop _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; [clock unsyncMusicTimeStampsAndTemposFromMIDITimeStamps]; - }); + }; + + dispatchToProcessingQueue ? dispatch_sync(self.processingQueue, stopPlayback) : stopPlayback(); self.processingQueue = NULL; self.playbackOffset = 0; @@ -335,7 +345,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } else if (!self.isRecording) { // Don't stop automatically during recording MIDITimeStamp systemTimeStamp = MIKMIDIGetCurrentTimeStamp(); if ((systemTimeStamp > actualToMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= self.sequenceLength + playbackOffset)) { - [self stop]; + [self stopWithDispatchToProcessingQueue:NO]; } } } From a1f26238a5cec1a72517e114e335907718b3fd57 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Sat, 13 Jun 2015 12:27:30 -0500 Subject: [PATCH 174/284] Moved checks for converting the lastSyncedMusicTimeStamp and lastSyncedMIDITimeStamps into the clock's queue, and added a check for the lastSyncedMIDITimeStamp in -musicTimeStampForMIDITimeStamp:withClock:. --- Source/MIKMIDIClock.m | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 23a9970e..ded119db 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -161,16 +161,18 @@ - (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { - if (midiTimeStamp == self.lastSyncedMIDITimeStamp) return self.lastSyncedMusicTimeStamp; __block MusicTimeStamp musicTimeStamp = 0; [self dispatchToClockQueue:^{ - if (self.isReady) { - if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { - musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; - } else { - musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; - } + if (!self.isReady) return; + + MIDITimeStamp lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; + if (midiTimeStamp == lastSyncedMIDITimeStamp) { + musicTimeStamp = self.lastSyncedMusicTimeStamp; + } else if (midiTimeStamp > lastSyncedMIDITimeStamp) { + musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; + } else { + musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; } }]; @@ -179,29 +181,29 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp withClock:(MIKMIDIClock *)clock { - if (!self.isReady) return 0; + if (midiTimeStamp == clock.lastSyncedMIDITimeStamp) return clock.lastSyncedMusicTimeStamp; MIDITimeStamp timeStampZero = clock.timeStampZero; return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp) : -((timeStampZero - midiTimeStamp) * clock.musicTimeStampsPerMIDITimeStamp); } - (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - if (musicTimeStamp == self.lastSyncedMusicTimeStamp) return self.lastSyncedMIDITimeStamp; __block MIDITimeStamp midiTimeStamp = 0; [self dispatchToClockQueue:^{ - if (self.isReady) { - midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; - - if (midiTimeStamp < self.lastSyncedMIDITimeStamp) { - NSDictionary *historicalClocks = self.historicalClocks; - for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { - MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; - MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; - if (historicalMIDITimeStamp >= clock.lastSyncedMIDITimeStamp) { - midiTimeStamp = historicalMIDITimeStamp; - break; - } + if (!self.isReady) return; + if (musicTimeStamp == self.lastSyncedMusicTimeStamp) { midiTimeStamp = self.lastSyncedMIDITimeStamp; return; } + + midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; + + if (midiTimeStamp < self.lastSyncedMIDITimeStamp) { + NSDictionary *historicalClocks = self.historicalClocks; + for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; + MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; + if (historicalMIDITimeStamp >= clock.lastSyncedMIDITimeStamp) { + midiTimeStamp = historicalMIDITimeStamp; + break; } } } From 915774a9190b0f731e45df0f47f55250737af082 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Mon, 15 Jun 2015 14:28:36 -0500 Subject: [PATCH 175/284] Renamed actualLoopEndTimeStamp to effectiveLoopEndTimeStamp and made it public in MIKMIDISequencer. Removed redundant code in MIKMIDIClock. --- Source/MIKMIDIClock.m | 4 +--- Source/MIKMIDISequencer.h | 12 +++++++++++- Source/MIKMIDISequencer.m | 8 ++++---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index ded119db..1f5f036b 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -167,9 +167,7 @@ - (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp if (!self.isReady) return; MIDITimeStamp lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; - if (midiTimeStamp == lastSyncedMIDITimeStamp) { - musicTimeStamp = self.lastSyncedMusicTimeStamp; - } else if (midiTimeStamp > lastSyncedMIDITimeStamp) { + if (midiTimeStamp >= lastSyncedMIDITimeStamp) { musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; } else { musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index d830e995..ff08c543 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -332,16 +332,26 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { @property (readonly, nonatomic) MusicTimeStamp loopStartTimeStamp; /** - * The loop's ending time stamp during looped playback. + * The loop's ending time stamp during looped playback, or MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. * * @note When this is set to MIKMIDISequencerEndOfSequenceLoopEndTimeStamp * the loopEndTimeStamp will be treated as if it is set to the length of the * sequence. The default is MIKMIDISequencerEndOfSequenceLoopEndTimeStamp. * + * @see effectiveLoopEndTimeStamp * @see -setLoopStartTimeStamp:loopEndTimeStamp: */ @property (readonly, nonatomic) MusicTimeStamp loopEndTimeStamp; +/** + * The loop's ending time stamp during looped playback. + * + * @note When loopEndTimeStamp is set to MIKMIDISequencerEndOfSequenceLoopEndTimeStamp, + * this will return the same length as the sequence.length. Otherwise loopEndTimeStamp + * will be returned. + */ +@property (readonly, nonatomic) MusicTimeStamp effectiveLoopEndTimeStamp; + /** * The metronome to send click track events to. */ diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index d17d9655..5c3fd105 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -69,8 +69,6 @@ @interface MIKMIDISequencer () @property (nonatomic, getter=isRecording) BOOL recording; @property (nonatomic, getter=isLooping) BOOL looping; -@property (readonly, nonatomic) MusicTimeStamp actualLoopEndTimeStamp; - @property (nonatomic) MIDITimeStamp latestScheduledMIDITimeStamp; @property (nonatomic, strong) NSMutableDictionary *pendingNoteOffs; @@ -231,7 +229,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam MIKMIDISequence *sequence = self.sequence; MusicTimeStamp playbackOffset = self.playbackOffset; MusicTimeStamp loopStartTimeStamp = self.loopStartTimeStamp + playbackOffset; - MusicTimeStamp loopEndTimeStamp = self.actualLoopEndTimeStamp + playbackOffset; + MusicTimeStamp loopEndTimeStamp = self.effectiveLoopEndTimeStamp + playbackOffset; MusicTimeStamp fromMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:fromMIDITimeStamp]; MusicTimeStamp calculatedToMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:toMIDITimeStamp]; BOOL isLooping = (self.shouldLoop && calculatedToMusicTimeStamp > loopStartTimeStamp && loopEndTimeStamp > loopStartTimeStamp); @@ -647,6 +645,8 @@ - (void)processingTimerFired:(NSTimer *)timer #pragma mark - KVO ++ (NSSet *)keyPathsForValuesAffectingEffectiveLoopEndTimeStamp { return [NSSet setWithObjects:@"loopEndTimeStamp", @"sequence.length", nil]; } + - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSSet *currentTracks = [NSSet setWithArray:self.sequence.tracks]; @@ -694,7 +694,7 @@ - (void)setCurrentTimeStamp:(MusicTimeStamp)currentTimeStamp } } -- (MusicTimeStamp)actualLoopEndTimeStamp +- (MusicTimeStamp)effectiveLoopEndTimeStamp { return (_loopEndTimeStamp < 0) ? self.sequenceLength : _loopEndTimeStamp; } From c578bf79fe6e43344d692217b6f24ad9a83e3444 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 16 Jun 2015 08:58:18 -0500 Subject: [PATCH 176/284] Added createSynthsAndEndpointsIfNeeded property to make the automatic creation of endpoints and synths optional (the default is YES). --- Source/MIKMIDISequencer.h | 15 +++++++++++++++ Source/MIKMIDISequencer.m | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index ff08c543..b8fbe404 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -225,6 +225,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * * @see -setDestinationEndpoint:forTrack: * @see -builtinSynthesizerForTrack: + * @see createSynthsAndEndpointsIfNeeded */ - (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track; @@ -352,6 +353,20 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (readonly, nonatomic) MusicTimeStamp effectiveLoopEndTimeStamp; + +/** + * Whether or not the sequencer should create synthesizers and endpoints + * for MIDI tracks that are not assigned an endpoint. + * + * When this property is YES, -destinationEndpointForTrack: will create an + * endpoint and a synthesizer for any track that has MIDI commands sent to it + * and doesn't already have an assigned endpoint. The default for this property + * is YES. + * + * @see -destinationEndpointForTrack: + */ +@property (nonatomic, getter=shouldCreateSynthsAndEndpointsIfNeeded) BOOL createSynthsAndEndpointsIfNeeded; + /** * The metronome to send click track events to. */ diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 5c3fd105..e15dfd4e 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -107,6 +107,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; _tracksToDestinationsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; _tracksToDefaultSynthsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; + _createSynthsAndEndpointsIfNeeded = YES; } return self; } @@ -424,6 +425,7 @@ - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Floa - (void)sendCommands:(NSArray *)commands toDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint { + if (!endpoint) return; commands = [self modifiedMIDICommandsFromCommandsToBeScheduled:commands forEndpoint:endpoint]; NSError *error; @@ -548,7 +550,7 @@ - (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:( - (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track { MIKMIDIDestinationEndpoint *result = [self.tracksToDestinationsMap objectForKey:track]; - if (!result) { + if (!result && self.createSynthsAndEndpointsIfNeeded) { // Create a default endpoint and synthesizer NSString *name = [NSString stringWithFormat:@"<%@: %p> Default Endpoint %d", NSStringFromClass([self class]), self, (int)track.trackNumber]; result = [[MIKMIDIClientDestinationEndpoint alloc] initWithName:name receivedMessagesHandler:nil]; From bb7bcaba9a14f71818eb3b001ca232186063e78d Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 16 Jun 2015 10:43:42 -0500 Subject: [PATCH 177/284] Fixed issue with patch selection when using the DLS synth --- Source/MIKMIDISynthesizer.m | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 45fb7053..f1baa460 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -173,9 +173,9 @@ - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)i *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; return NO; } - - if (loadedSoundfontURL) { - + + if (loadedSoundfontURL || [self isUsingAppleDLSSynth]) { + UInt32 bankSelectStatus = 0xB0 | channel; UInt8 bankSelectMSB = (instrumentID >> 16) & 0x7F; @@ -296,6 +296,15 @@ - (BOOL)isUsingAppleSynth return YES; } +- (BOOL)isUsingAppleDLSSynth +{ + AudioComponentDescription description = self.componentDescription; + if (description.componentManufacturer != kAudioUnitManufacturer_Apple) return NO; + if (description.componentType != kAudioUnitType_MusicDevice) return NO; + if (description.componentSubType != kAudioUnitSubType_DLSSynth) return NO; + return YES; +} + + (AudioComponentDescription)appleSynthComponentDescription { AudioComponentDescription instrumentcd = (AudioComponentDescription){0}; From 0028e3e6892c40e2a597e1dbb4ed4068a51b3299 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 24 Jun 2015 08:59:07 -0500 Subject: [PATCH 178/284] Now setting _sequence on the processingQueue. --- Source/MIKMIDISequencer.m | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index e15dfd4e..f6c61239 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -760,7 +760,14 @@ - (void)setSequence:(MIKMIDISequence *)sequence { if (_sequence != sequence) { [_sequence removeObserver:self forKeyPath:@"tracks"]; - _sequence = sequence; + + dispatch_queue_t queue = self.processingQueue; + if (queue) { + dispatch_sync(queue, ^{ _sequence = sequence; }); + } else { + _sequence = sequence; + } + [_sequence addObserver:self forKeyPath:@"tracks" options:NSKeyValueObservingOptionInitial context:NULL]; } } From 0d10e524cd619ca1c7cf05e55bb2bf4ecf57dfd9 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 24 Jun 2015 10:33:01 -0500 Subject: [PATCH 179/284] Now disposing of music tracks before disposing the music sequence. --- Source/MIKMIDISequence.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 2f4ab15a..6bc65923 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -167,8 +167,15 @@ - (instancetype)initWithMusicSequence:(MusicSequence)musicSequence error:(NSErro - (void)dealloc { + NSArray *tracks = self.internalTracks; self.internalTracks = nil; // Unregister for KVO self.callBackBlock = nil; + + for (MIKMIDITrack *track in tracks) { + OSStatus err = MusicSequenceDisposeTrack(_musicSequence, track.musicTrack); + if (err) NSLog(@"MusicSequenceDisposeTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + } + OSStatus err = DisposeMusicSequence(_musicSequence); if (err) NSLog(@"DisposeMusicSequence() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); } From 5b5ba1bbc75b4250d10c8cbfb0593fc0560a3ac8 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 24 Jun 2015 15:16:56 -0600 Subject: [PATCH 180/284] MIKMIDIMapping equality now considers additional attributes. --- Source/MIKMIDIMapping.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index 029f0232..5ad0044d 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -291,6 +291,7 @@ - (BOOL)isEqual:(MIKMIDIMapping *)otherMapping if (self == otherMapping) return YES; if (![self.name isEqualToString:otherMapping.name]) return NO; if (![self.controllerName isEqualToString:otherMapping.controllerName]) return NO; + if (![self.additionalAttributes isEqualToDictionary:otherMapping.additionalAttributes]) return NO; return [self.mappingItems isEqualToSet:otherMapping.mappingItems]; } @@ -299,6 +300,7 @@ - (NSUInteger)hash { NSUInteger result = [self.name hash]; result += [self.controllerName hash]; + result += [self.additionalAttributes hash]; result += [self.internalMappingItems count]; return result; } From 64b460c861e2a99e543d8e8762b7af8357b8af47 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 24 Jun 2015 15:17:36 -0600 Subject: [PATCH 181/284] MIKMIDIMappingManager now uses -[MIKMIDIMapping isEqual:] instead of simply name to ignore duplicate user mappings. --- Source/MIKMIDIMappingManager.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index f8785994..9d2a9f02 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -291,8 +291,6 @@ - (NSSet *)mappings { return [self.bundledMappings setByAddingObjectsFromSet:sel - (void)addUserMappingsObject:(MIKMIDIMapping *)mapping { - MIKMIDIMapping *existing = [self userMappingWithName:mapping.name]; - if (existing) [self.internalUserMappings removeObject:existing]; mapping.bundledMapping = NO; [self.internalUserMappings addObject:mapping]; From fe40f6b0c8ae67725c5bac81fb23e5ac3b4c5689 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 24 Jun 2015 16:01:47 -0600 Subject: [PATCH 182/284] Added support for multiple MIDI mappings with the same name (differentiated using -isEqual:). --- Source/MIKMIDIMapping.m | 2 +- Source/MIKMIDIMappingManager.h | 19 ++++++++++++++++-- Source/MIKMIDIMappingManager.m | 35 ++++++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index 5ad0044d..3bf64017 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -307,7 +307,7 @@ - (NSUInteger)hash - (NSString *)description { - return [NSString stringWithFormat:@"%@ %@ for %@ Mapping Items: %@", [super description], self.name, self.controllerName, self.mappingItems]; + return [NSString stringWithFormat:@"%@ %@ for %@ Mapping Items: %@ Additional Attributes: %@", [super description], self.name, self.controllerName, self.mappingItems, self.additionalAttributes]; } - (NSSet *)mappingItemsForMIDIResponder:(id)responder; diff --git a/Source/MIKMIDIMappingManager.h b/Source/MIKMIDIMappingManager.h index 62c9d5e2..995feacb 100644 --- a/Source/MIKMIDIMappingManager.h +++ b/Source/MIKMIDIMappingManager.h @@ -68,9 +68,9 @@ * * @param mappingName NSString representing the mapping name for the desired mapping. * - * @return An MIKMIDIMapping instance, or nil if no mapping could be found. + * @return An array of MIKMIDIMapping instances, or an empty array if no mapping could be found. */ -- (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; +- (NSArray *)mappingsWithName:(NSString *)mappingName; #if !TARGET_OS_IPHONE /** @@ -143,3 +143,18 @@ @property (nonatomic, strong, readonly) NSSet *mappings; @end + +@interface MIKMIDIMappingManager (Deprecated) + +/** + * Used to obtaining a mapping file with a given mapping name. + * + * @param mappingName NSString representing the mapping name for the desired mapping. + * + * @return An MIKMIDIMapping instance, or nil if no mapping could be found. + * + * @deprecated Deprecated. Use -mappingsWithName: instead. + */ +- (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName DEPRECATED_ATTRIBUTE; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index 9d2a9f02..9ed346eb 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -115,30 +115,33 @@ - (NSSet *)userMappingsForControllerName:(NSString *)name return result; } -- (MIKMIDIMapping *)userMappingWithName:(NSString *)mappingName; +- (NSArray *)userMappingWithName:(NSString *)mappingName; { + NSMutableArray *result = [NSMutableArray array]; for (MIKMIDIMapping *mapping in self.userMappings) { if ([mapping.name isEqualToString:mappingName]) { - return mapping; + [result addObject:mapping]; } } - return nil; + return result; } -- (MIKMIDIMapping *)bundledMappingWithName:(NSString *)mappingName; +- (NSArray *)bundledMappingsWithName:(NSString *)mappingName; { + NSMutableArray *result = [NSMutableArray array]; for (MIKMIDIMapping *mapping in self.bundledMappings) { if ([mapping.name isEqualToString:mappingName]) { - return mapping; + [result addObject:mapping]; } } - return nil; + return result; } -- (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; +- (NSArray *)mappingsWithName:(NSString *)mappingName; { - MIKMIDIMapping *result = [self userMappingWithName:mappingName]; - return result ?: [self bundledMappingWithName:mappingName]; + NSMutableArray *result = [NSMutableArray arrayWithArray:[self userMappingWithName:mappingName]]; + [result addObjectsFromArray:[self bundledMappingsWithName:mappingName]]; + return result; } - (MIKMIDIMapping *)importMappingFromFileAtURL:(NSURL *)URL overwritingExistingMapping:(BOOL)shouldOverwrite error:(NSError **)error; @@ -174,7 +177,7 @@ - (void)saveMappingsToDisk { #if !TARGET_OS_IPHONE for (MIKMIDIMapping *mapping in self.userMappings) { - NSURL *fileURL = [self fileURLForMapping:mapping shouldBeUnique:NO]; + NSURL *fileURL = [self fileURLForMapping:mapping shouldBeUnique:YES]; if (!fileURL) { NSLog(@"Unable to saving mapping %@ to disk. No file path could be generated", mapping); continue; @@ -314,3 +317,15 @@ - (void)removeUserMappingsObject:(MIKMIDIMapping *)mapping } @end + +#pragma mark - Deprecated + +@implementation MIKMIDIMappingManager (Deprecated) + +- (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; +{ + MIKMIDIMapping *result = [[self userMappingWithName:mappingName] firstObject]; + return result ?: [[self bundledMappingsWithName:mappingName] firstObject]; +} + +@end \ No newline at end of file From 4c366bada31994340b1dc3af0be8aaec59ca6aa1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 25 Jun 2015 11:21:32 -0600 Subject: [PATCH 183/284] Fixed bug that caused multiple copies of a single MIDI mapping to be written to disk. --- Source/MIKMIDIMappingManager.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index 9ed346eb..b0a32b6b 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -177,7 +177,7 @@ - (void)saveMappingsToDisk { #if !TARGET_OS_IPHONE for (MIKMIDIMapping *mapping in self.userMappings) { - NSURL *fileURL = [self fileURLForMapping:mapping shouldBeUnique:YES]; + NSURL *fileURL = [self fileURLForMapping:mapping shouldBeUnique:NO]; if (!fileURL) { NSLog(@"Unable to saving mapping %@ to disk. No file path could be generated", mapping); continue; @@ -261,6 +261,9 @@ - (NSURL *)fileURLForMapping:(MIKMIDIMapping *)mapping shouldBeUnique:(BOOL)uniq NSFileManager *fm = [NSFileManager defaultManager]; unsigned long numberSuffix = 0; while ([fm fileExistsAtPath:[result path]]) { + MIKMIDIMapping *existingMapping = [[MIKMIDIMapping alloc] initWithFileAtURL:result error:NULL]; + if ([existingMapping isEqualTo:mapping]) break; + if (numberSuffix > 1000) return nil; // Don't go crazy NSString *name = [mapping.name stringByAppendingFormat:@" %lu", ++numberSuffix]; filename = [name stringByAppendingPathExtension:kMIKMIDIMappingFileExtension]; From 5eaf4f0f4d5ba1896581d7a84cf02c3d9398b4e0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 25 Jun 2015 16:42:23 -0600 Subject: [PATCH 184/284] Fixed crash when MIKMIDICommandThrottler is fed a factor of 0. --- Source/MIKMIDICommandThrottler.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/MIKMIDICommandThrottler.m b/Source/MIKMIDICommandThrottler.m index 32bc7b45..1ebde04c 100644 --- a/Source/MIKMIDICommandThrottler.m +++ b/Source/MIKMIDICommandThrottler.m @@ -35,6 +35,8 @@ - (id)init - (BOOL)shouldPassCommand:(MIKMIDIChannelVoiceCommand *)command forThrottlingFactor:(NSUInteger)factor { + if (factor <= 1) return YES; + // Increment current count id key = [self throttleCounterKeyForCommand:command]; NSNumber *count = self.throttleCounters[key]; From f5ecff445898c4021200e93b2b9229774a875e00 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 25 Jun 2015 17:53:05 -0600 Subject: [PATCH 185/284] Fixed minor typo in method name. --- Source/MIKMIDIMappingManager.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index b0a32b6b..ccf5c6a6 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -115,7 +115,7 @@ - (NSSet *)userMappingsForControllerName:(NSString *)name return result; } -- (NSArray *)userMappingWithName:(NSString *)mappingName; +- (NSArray *)userMappingsWithName:(NSString *)mappingName; { NSMutableArray *result = [NSMutableArray array]; for (MIKMIDIMapping *mapping in self.userMappings) { @@ -139,7 +139,7 @@ - (NSArray *)bundledMappingsWithName:(NSString *)mappingName; - (NSArray *)mappingsWithName:(NSString *)mappingName; { - NSMutableArray *result = [NSMutableArray arrayWithArray:[self userMappingWithName:mappingName]]; + NSMutableArray *result = [NSMutableArray arrayWithArray:[self userMappingsWithName:mappingName]]; [result addObjectsFromArray:[self bundledMappingsWithName:mappingName]]; return result; } @@ -327,7 +327,7 @@ @implementation MIKMIDIMappingManager (Deprecated) - (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName; { - MIKMIDIMapping *result = [[self userMappingWithName:mappingName] firstObject]; + MIKMIDIMapping *result = [[self userMappingsWithName:mappingName] firstObject]; return result ?: [[self bundledMappingsWithName:mappingName] firstObject]; } From 5cb9e3d0523e0f2fe22c3b9e42648f57e0f9953a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 26 Jun 2015 16:30:40 -0600 Subject: [PATCH 186/284] Added convenience methods to create MIKMIDINoteOn/OffCommand instances. --- Source/MIKMIDINoteOffCommand.h | 15 +++++++++++++++ Source/MIKMIDINoteOffCommand.m | 14 ++++++++++++++ Source/MIKMIDINoteOnCommand.h | 15 +++++++++++++++ Source/MIKMIDINoteOnCommand.m | 14 ++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/Source/MIKMIDINoteOffCommand.h b/Source/MIKMIDINoteOffCommand.h index 69880aa3..869dd567 100644 --- a/Source/MIKMIDINoteOffCommand.h +++ b/Source/MIKMIDINoteOffCommand.h @@ -13,6 +13,21 @@ */ @interface MIKMIDINoteOffCommand : MIKMIDIChannelVoiceCommand +/** + * Convenience method for creating a note off command. + * + * @param note The note number for the command. Must be between 0 and 127. + * @param velocity The velocity for the command. Must be between 0 and 127. + * @param channel The channel for the command. Must be between 0 and 15. + * @param timestamp The timestamp for the command. Pass nil to use the current date/time. + * + * @return An initialized MIKMIDINoteOffCommand instance. + */ ++ (instancetype)noteOffCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + timestamp:(NSDate *)timestamp; + /** * The note number for the message. In the range 0-127. */ diff --git a/Source/MIKMIDINoteOffCommand.m b/Source/MIKMIDINoteOffCommand.m index 1f243351..a74eb0b3 100644 --- a/Source/MIKMIDINoteOffCommand.m +++ b/Source/MIKMIDINoteOffCommand.m @@ -28,6 +28,20 @@ + (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeNoteOff)]; + (Class)immutableCounterpartClass; { return [MIKMIDINoteOffCommand class]; } + (Class)mutableCounterpartClass; { return [MIKMutableMIDINoteOffCommand class]; } ++ (instancetype)noteOffCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + timestamp:(NSDate *)timestamp +{ + MIKMutableMIDINoteOffCommand *result = [[MIKMutableMIDINoteOffCommand alloc] init]; + result.note = note; + result.velocity = velocity; + result.channel = channel; + result.timestamp = timestamp ?: [NSDate date]; + + return [self isMutable] ? result : [result copy]; +} + - (NSString *)additionalCommandDescription { return [NSString stringWithFormat:@"%@ note: %lu velocity: %lu", [super additionalCommandDescription], (unsigned long)self.note, (unsigned long)self.velocity]; diff --git a/Source/MIKMIDINoteOnCommand.h b/Source/MIKMIDINoteOnCommand.h index ec47226b..b7f023b6 100644 --- a/Source/MIKMIDINoteOnCommand.h +++ b/Source/MIKMIDINoteOnCommand.h @@ -13,6 +13,21 @@ */ @interface MIKMIDINoteOnCommand : MIKMIDIChannelVoiceCommand +/** + * Convenience method for creating a note on command. + * + * @param note The note number for the command. Must be between 0 and 127. + * @param velocity The velocity for the command. Must be between 0 and 127. + * @param channel The channel for the command. Must be between 0 and 15. + * @param timestamp The timestamp for the command. Pass nil to use the current date/time. + * + * @return An initialized MIKMIDINoteOnCommand instance. + */ ++ (instancetype)noteOnCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + timestamp:(NSDate *)timestamp; + /** * The note number for the message. In the range 0-127. */ diff --git a/Source/MIKMIDINoteOnCommand.m b/Source/MIKMIDINoteOnCommand.m index 2e88474c..690a0a9c 100644 --- a/Source/MIKMIDINoteOnCommand.m +++ b/Source/MIKMIDINoteOnCommand.m @@ -29,6 +29,20 @@ + (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeNoteOn)]; + (Class)immutableCounterpartClass; { return [MIKMIDINoteOnCommand class]; } + (Class)mutableCounterpartClass; { return [MIKMutableMIDINoteOnCommand class]; } ++ (instancetype)noteOnCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + timestamp:(NSDate *)timestamp +{ + MIKMutableMIDINoteOnCommand *result = [[MIKMutableMIDINoteOnCommand alloc] init]; + result.note = note; + result.velocity = velocity; + result.channel = channel; + result.timestamp = timestamp ?: [NSDate date]; + + return [self isMutable] ? result : [result copy]; +} + #pragma mark - Properties - (NSUInteger)note { return self.dataByte1; } From ff5754da30c9dcb3d3f1317ad989a27ac8fcdeb9 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 26 Jun 2015 17:03:49 -0600 Subject: [PATCH 187/284] Added method to MIKMIDIMappingGeneratorDelegate to allow interception and modification of incoming MIDI during mapping. --- Source/MIKMIDIMappingGenerator.h | 14 +++++++++++++- Source/MIKMIDIMappingGenerator.m | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index e85a8660..0098784c 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -65,7 +65,7 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem * * @return An initialized MIKMIDIMappingGenerator instance, or nil if an error occurred. */ -- (instancetype)initWithDevice:(MIKMIDIDevice *)device error:(NSError **)error; +- (instancetype)initWithDevice:(MIKMIDIDevice *)device error:(NSError **)error NS_DESIGNATED_INITIALIZER; /** * Begins mapping a given MIDIResponder. This method returns immediately. @@ -205,4 +205,16 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMappingGeneratorRemapBehavior) { shouldRemoveExistingMappingItems:(NSSet *)mappingItems forResponderBeingMapped:(id)responder; +/** + * The delegate can implement this to do some transformation of incoming commands in order to customize + * mapping. For instance, controls can be dynamically remapped, or incoming commands can be selectively ignored. + * Most users of MIKMIDIMappingGenerator will not need to use this. + * + * @param command An incoming MIKMIDICommand. + * + * @return A processed/modified copy of the incoming command, or nil to ignore the command. + */ +- (MIKMIDIChannelVoiceCommand *)mappingGenerator:(MIKMIDIMappingGenerator *)generator + commandByProcessingIncomingCommand:(MIKMIDIChannelVoiceCommand *)command; + @end \ No newline at end of file diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index fb8a6f1f..79eb4d62 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -82,7 +82,7 @@ - (instancetype)initWithDevice:(MIKMIDIDevice *)device error:(NSError **)error; - (id)init { [NSException raise:NSInternalInconsistencyException format:@"-initWithDevice: is the designated initializer for %@", NSStringFromClass([self class])]; - self = nil; + self = [self initWithDevice:nil error:NULL]; // Keep the compiler happy return nil; } @@ -516,7 +516,19 @@ - (BOOL)connectToDevice:(NSError **)error id connectionToken = [manager connectInput:source error:error eventHandler:^(MIKMIDISourceEndpoint *source, NSArray *commands) { for (MIKMIDICommand *command in commands) { if (![command isKindOfClass:[MIKMIDIChannelVoiceCommand class]]) continue; - [weakSelf handleMIDICommand:(MIKMIDIChannelVoiceCommand *)command]; + + MIKMIDICommand *processedCommand = command; + if ([weakSelf.delegate respondsToSelector:@selector(mappingGenerator:commandByProcessingIncomingCommand:)]) { + processedCommand = [weakSelf.delegate mappingGenerator:self + commandByProcessingIncomingCommand:(MIKMIDIChannelVoiceCommand *)command]; + if (!processedCommand) continue; + if (![processedCommand isKindOfClass:[MIKMIDIChannelVoiceCommand class]]) { + [NSException raise:NSInternalInconsistencyException format:@"-mappingGenerator:commandByProcessingCommand: must only return instances of MIKMIDIChannelVoiceCommand or one of its subclasses."]; + continue; + } + } + + [weakSelf handleMIDICommand:(MIKMIDIChannelVoiceCommand *)processedCommand]; } }]; self.connectionToken = connectionToken; From cb3066778fb733de4927e36363d931b84bcd1c21 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 30 Jun 2015 15:38:24 -0500 Subject: [PATCH 188/284] MIKMIDISequence and MIKMIDITrack now dispatch work to the sequencer's processing queue as needed for improved thread safety. Deprecated MIKMIDIPlayer. Added possible workaround for Issue #100. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 + Source/MIKMIDIPlayer.h | 1 + Source/MIKMIDISequence+MIKMIDIPrivate.h | 22 + Source/MIKMIDISequence.h | 37 +- Source/MIKMIDISequence.m | 111 ++++- Source/MIKMIDISequencer+MIKMIDIPrivate.h | 16 + Source/MIKMIDISequencer.h | 8 +- Source/MIKMIDISequencer.m | 57 ++- Source/MIKMIDITrack+MIKMIDIPrivate.h | 18 + Source/MIKMIDITrack.h | 59 +-- Source/MIKMIDITrack.m | 459 ++++++++++++-------- 11 files changed, 540 insertions(+), 260 deletions(-) create mode 100644 Source/MIKMIDISequence+MIKMIDIPrivate.h create mode 100644 Source/MIKMIDISequencer+MIKMIDIPrivate.h create mode 100644 Source/MIKMIDITrack+MIKMIDIPrivate.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index b9bfe96f..49e1787c 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 833B73DB1A262FE100E0CC9F /* MIKMIDISequencer.m in Sources */ = {isa = PBXBuildFile; fileRef = 833B73D91A262FE100E0CC9F /* MIKMIDISequencer.m */; }; 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */ = {isa = PBXBuildFile; fileRef = 833B73DC1A26346F00E0CC9F /* MIKMIDIClock.h */; settings = {ATTRIBUTES = (Public, ); }; }; 833B73DF1A26346F00E0CC9F /* MIKMIDIClock.m in Sources */ = {isa = PBXBuildFile; fileRef = 833B73DD1A26346F00E0CC9F /* MIKMIDIClock.m */; }; + 835124E51B42D16E00202312 /* MIKMIDISequencer+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 835124E31B42D16E00202312 /* MIKMIDISequencer+MIKMIDIPrivate.h */; }; 839D933219C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 839D932D19C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 839D933319C3A2C9007589C3 /* MIKMIDIEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 839D932E19C3A2C9007589C3 /* MIKMIDIEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 839D933419C3A2C9007589C3 /* MIKMIDIEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 839D932F19C3A2C9007589C3 /* MIKMIDIEvent.m */; }; @@ -52,6 +53,8 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; + 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */; }; + 83FB36121B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB36101B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h */; }; 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F01B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; @@ -296,6 +299,7 @@ 833B73D91A262FE100E0CC9F /* MIKMIDISequencer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequencer.m; sourceTree = ""; }; 833B73DC1A26346F00E0CC9F /* MIKMIDIClock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClock.h; sourceTree = ""; }; 833B73DD1A26346F00E0CC9F /* MIKMIDIClock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClock.m; sourceTree = ""; }; + 835124E31B42D16E00202312 /* MIKMIDISequencer+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequencer+MIKMIDIPrivate.h"; sourceTree = ""; }; 839D932D19C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEvent_SubclassMethods.h; sourceTree = ""; }; 839D932E19C3A2C9007589C3 /* MIKMIDIEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEvent.h; sourceTree = ""; }; 839D932F19C3A2C9007589C3 /* MIKMIDIEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEvent.m; sourceTree = ""; }; @@ -337,6 +341,8 @@ 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; + 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequence+MIKMIDIPrivate.h"; sourceTree = ""; }; + 83FB36101B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDITrack+MIKMIDIPrivate.h"; sourceTree = ""; }; 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; @@ -489,7 +495,9 @@ children = ( 839D936B19C3A30B007589C3 /* MIKMIDISequence.h */, 839D936C19C3A30B007589C3 /* MIKMIDISequence.m */, + 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */, 839D937119C3A319007589C3 /* MIKMIDITrack.h */, + 83FB36101B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h */, 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */, 839D937219C3A319007589C3 /* MIKMIDITrack.m */, 9DEE37BF1A9D66C2007B7FC7 /* Events */, @@ -657,6 +665,7 @@ 839D936819C3A303007589C3 /* MIKMIDIPlayer.m */, 833B73D81A262FE100E0CC9F /* MIKMIDISequencer.h */, 833B73D91A262FE100E0CC9F /* MIKMIDISequencer.m */, + 835124E31B42D16E00202312 /* MIKMIDISequencer+MIKMIDIPrivate.h */, ); name = Sequencing; sourceTree = ""; @@ -840,6 +849,7 @@ 9D84951C1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */, 9D877D841A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.h in Headers */, 9D74EF7117A713A100BEE89F /* MIKMIDIEndpoint.h in Headers */, + 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */, 9D74EF7317A713A100BEE89F /* MIKMIDIEntity.h in Headers */, 9D74EF7517A713A100BEE89F /* MIKMIDIErrors.h in Headers */, 9D74EF7717A713A100BEE89F /* MIKMIDIInputPort.h in Headers */, @@ -854,6 +864,7 @@ 839D933219C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h in Headers */, 9D5946CE1A9FA7C800B5ACFB /* MIKMIDISynthesizer_SubclassMethods.h in Headers */, 839D935519C3A2F5007589C3 /* MIKMIDIMetaInstrumentNameEvent.h in Headers */, + 835124E51B42D16E00202312 /* MIKMIDISequencer+MIKMIDIPrivate.h in Headers */, 9DF99E7D18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h in Headers */, 839D935F19C3A2F5007589C3 /* MIKMIDIMetaTextEvent.h in Headers */, 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */, @@ -894,6 +905,7 @@ 83BC19BB1A23CD0D004F384F /* MIKMIDIMetronome.h in Headers */, 9D74EF9217A713A100BEE89F /* MIKMIDIUtilities.h in Headers */, 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */, + 83FB36121B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h in Headers */, 9DAE7D8E19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h in Headers */, 9D74EF9417A713A100BEE89F /* NSUIApplication+MIKMIDI.h in Headers */, ); diff --git a/Source/MIKMIDIPlayer.h b/Source/MIKMIDIPlayer.h index 604aa55a..b6c5a257 100644 --- a/Source/MIKMIDIPlayer.h +++ b/Source/MIKMIDIPlayer.h @@ -15,6 +15,7 @@ /** * MIKMIDIPlayer can be used to play an MIKMIDISequence. */ +__attribute((deprecated("use MIKMIDISequencer instead"))) @interface MIKMIDIPlayer : NSObject /** diff --git a/Source/MIKMIDISequence+MIKMIDIPrivate.h b/Source/MIKMIDISequence+MIKMIDIPrivate.h new file mode 100644 index 00000000..e7f70094 --- /dev/null +++ b/Source/MIKMIDISequence+MIKMIDIPrivate.h @@ -0,0 +1,22 @@ +// +// MIKMIDISequence+MIKMIDIPrivate.h +// MIKMIDI +// +// Created by Chris Flesner on 6/30/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import + +@class MIKMIDISequencer; + + +@interface MIKMIDISequence (MIKMIDIPrivate) + +@property (weak, nonatomic) MIKMIDISequencer *sequencer; + +@property (readonly, nonatomic) MusicTimeStamp private_length; + +- (Float64)private_tempoAtTimeStamp:(MusicTimeStamp)timeStamp; + +@end diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index bdc452df..5a45258a 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -23,15 +23,16 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d } @class MIKMIDITrack; +@class MIKMIDISequencer; @class MIKMIDIDestinationEndpoint; /** * Instances of MIKMIDISequence contain a collection of MIDI tracks. MIKMIDISequences may be thought * of as MIDI "songs". They can be loaded from and saved to MIDI files. They can also be played - * using an MIKMIDIPlayer. + * using an MIKMIDISequencer. * * @see MIKMIDITrack - * @see MIKMIDIPlayer + * @see MIKMIDISequencer */ @interface MIKMIDISequence : NSObject @@ -265,22 +266,12 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d */ - (MIKMIDITimeSignature)timeSignatureAtTimeStamp:(MusicTimeStamp)timeStamp; -#pragma mark - Timing +#pragma mark - Properties /** - * A MusicTimeStamp that is less than the sequence's length, but is at an equivalent position in the looped sequence as loopedTimeStamp - * - * When the music sequence is being looped by an MIKMIDIPlayer, the time stamp of the player continuosly increases. This method can be - * used to find where in the MIDI sequence the looped playback is at. For example, in a sequence with a length of 16, - * calling this method with a loopedTimeStamp of 17 would return 1. - * - * @param loopedTimeStamp The time stamp that you would like an equivalent time stamp for. - * - * @return The MusicTimeStamp of the sequence that is in an equivalent position in the sequence as loopedTimeStamp. + * The sequencer this sequence is assigned to for playback. */ -- (MusicTimeStamp)equivalentTimeStampForLoopedTimeStamp:(MusicTimeStamp)loopedTimeStamp; - -#pragma mark - Properties +@property (nonatomic, readonly) MIKMIDISequencer *sequencer; /** * The tempo track for the sequence. Even in a new, empty sequence, @@ -384,6 +375,22 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d */ - (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTimeStamp)timeStamp DEPRECATED_ATTRIBUTE; +/** + * @deprecated This method is only useful in the context of using a sequence in MIKMIDIPlayer, which has been deprecated. + * + * A MusicTimeStamp that is less than the sequence's length, but is at an equivalent position in the looped sequence as loopedTimeStamp + * + * When the music sequence is being looped by an MIKMIDIPlayer, the time stamp of the player continuosly increases. This method can be + * used to find where in the MIDI sequence the looped playback is at. For example, in a sequence with a length of 16, + * calling this method with a loopedTimeStamp of 17 would return 1. + * + * @param loopedTimeStamp The time stamp that you would like an equivalent time stamp for. + * + * @return The MusicTimeStamp of the sequence that is in an equivalent position in the sequence as loopedTimeStamp. + */ +- (MusicTimeStamp)equivalentTimeStampForLoopedTimeStamp:(MusicTimeStamp)loopedTimeStamp DEPRECATED_ATTRIBUTE; + + @end diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 6bc65923..b3b1c760 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -13,6 +13,9 @@ #import "MIKMIDITempoEvent.h" #import "MIKMIDIMetaTimeSignatureEvent.h" #import "MIKMIDIDestinationEndpoint.h" +#import "MIKMIDISequence+MIKMIDIPrivate.h" +#import "MIKMIDITrack+MIKMIDIPrivate.h" +#import "MIKMIDISequencer+MIKMIDIPrivate.h" #if !__has_feature(objc_arc) #error MIKMIDISequence.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target @@ -23,6 +26,9 @@ const MusicTimeStamp MIKMIDISequenceLongestTrackLength = -1; @interface MIKMIDISequence () +{ + MIKMIDISequencer *_sequencer; +} @property (nonatomic) MusicSequence musicSequence; @property (nonatomic, strong) MIKMIDITrack *tempoTrack; @@ -180,37 +186,54 @@ - (void)dealloc if (err) NSLog(@"DisposeMusicSequence() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); } +#pragma mark - Sequencer Synchronization + +- (void)dispatchSyncToSequencerProcessingQueueAsNeeded:(void (^)())block +{ + if (!block) return; + + MIKMIDISequencer *sequencer = self.sequencer; + if (sequencer) { + [sequencer dispatchSyncToProcessingQueueAsNeeded:block]; + } else { + block(); + } +} + #pragma mark - Adding and Removing Tracks - (MIKMIDITrack *)addTrack { - MusicTrack musicTrack; - OSStatus err = MusicSequenceNewTrack(self.musicSequence, &musicTrack); - if (err) { - NSLog(@"MusicSequenceNewTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - return nil; - } + __block MIKMIDITrack *track; - MIKMIDITrack *track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; - [self insertObject:track inInternalTracksAtIndex:[self.internalTracks count]]; + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + MusicTrack musicTrack; + OSStatus err = MusicSequenceNewTrack(self.musicSequence, &musicTrack); + if (err) return NSLog(@"MusicSequenceNewTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - return track; + track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; + [self insertObject:track inInternalTracksAtIndex:[self.internalTracks count]]; + }]; + + return track; } - (BOOL)removeTrack:(MIKMIDITrack *)track { if (!track) return NO; - - OSStatus err = MusicSequenceDisposeTrack(self.musicSequence, track.musicTrack); - if (err) { - NSLog(@"MusicSequenceDisposeTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - return NO; - } - NSInteger index = [self.internalTracks indexOfObject:track]; - if (index != NSNotFound) [self removeObjectFromInternalTracksAtIndex:index]; + __block BOOL success = NO; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + OSStatus err = MusicSequenceDisposeTrack(self.musicSequence, track.musicTrack); + if (err) return NSLog(@"MusicSequenceDisposeTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + + NSInteger index = [self.internalTracks indexOfObject:track]; + if (index != NSNotFound) [self removeObjectFromInternalTracksAtIndex:index]; + success = YES; + }]; - return YES; + return success; } #pragma mark - File Saving @@ -278,8 +301,13 @@ - (BOOL)setTempo:(Float64)bpm atTimeStamp:(MusicTimeStamp)timeStamp - (Float64)tempoAtTimeStamp:(MusicTimeStamp)timeStamp { - NSArray *events = [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; - return [[events lastObject] bpm]; + __block Float64 tempo = 0; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + tempo = [self private_tempoAtTimeStamp:timeStamp]; + }]; + + return tempo; } #pragma mark - Time Signature @@ -348,7 +376,7 @@ - (void)updateLengthDefinedByTracks { MusicTimeStamp length = 0; for (MIKMIDITrack *track in self.tracks) { - MusicTimeStamp trackLength = track.length + track.offset; + MusicTimeStamp trackLength = track.private_length + track.offset; if (trackLength > length) length = trackLength; } @@ -408,11 +436,23 @@ + (NSSet *)keyPathsForValuesAffectingLength return [NSSet setWithObjects:@"lengthDefinedByTracks", nil]; } +@synthesize length = _length; - (MusicTimeStamp)length { - if (_length != MIKMIDISequenceLongestTrackLength) return _length; - - return self.lengthDefinedByTracks; + __block MusicTimeStamp length = 0; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + length = self.private_length; + }]; + + return length; +} + +- (void)setLength:(MusicTimeStamp)length +{ + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + _length = length; + }]; } + (NSSet *)keyPathsForValuesAffectingDurationInSeconds @@ -440,6 +480,8 @@ - (NSData *)dataValue return (__bridge_transfer NSData *)data; } +- (MIKMIDISequencer *)sequencer { return _sequencer; } + #pragma mark - Deprecated + (instancetype)sequenceWithData:(NSData *)data @@ -498,3 +540,24 @@ - (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTim } @end + + + +#pragma mark - +@implementation MIKMIDISequence (MIKMIDIPrivate) + +- (Float64)private_tempoAtTimeStamp:(MusicTimeStamp)timeStamp +{ + NSArray *events = [self.tempoTrack private_eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; + return [[events lastObject] bpm]; +} + +- (void)setSequencer:(MIKMIDISequencer *)sequencer { _sequencer = sequencer; } + +- (MusicTimeStamp)private_length +{ + if (_length != MIKMIDISequenceLongestTrackLength) return _length; + return self.lengthDefinedByTracks; +} + +@end diff --git a/Source/MIKMIDISequencer+MIKMIDIPrivate.h b/Source/MIKMIDISequencer+MIKMIDIPrivate.h new file mode 100644 index 00000000..f5880b74 --- /dev/null +++ b/Source/MIKMIDISequencer+MIKMIDIPrivate.h @@ -0,0 +1,16 @@ +// +// MIKMIDISequencer+MIKMIDIPrivate.h +// MIKMIDI +// +// Created by Chris Flesner on 6/30/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import + + +@interface MIKMIDISequencer (MIKMIDIPrivate) + +- (void)dispatchSyncToProcessingQueueAsNeeded:(void (^)())block; + +@end diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index b8fbe404..b9f27f18 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -37,10 +37,9 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * MIKMIDISequencer can be used to play and record to an MIKMIDISequence. * - * @note MIKMIDISequencer currently only supports the playback and recording - * of MIDI note events. If you need to playback other events from a MIKMIDISequence, - * use MIKMIDIPlayer for now, keeping in mind that once MIKMIDISequencer is - * fully functional, MIKMIDIPlayer will be deprecated. + * @note Recording and using the click track may not yet be fully functional, and should + * be considered experimental in the meantime. Please submit issues and/or pull requests + * when you find areas that don't work as expected. */ @interface MIKMIDISequencer : NSObject @@ -384,6 +383,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * Each incoming event is added to every track in this set. * * @see recording + * */ @property (copy, nonatomic) NSSet *recordEnabledTracks; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index f6c61239..948221f0 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -22,6 +22,10 @@ #import "MIKMIDIClientDestinationEndpoint.h" #import "MIKMIDIUtilities.h" #import "MIKMIDISynthesizer.h" +#import "MIKMIDISequencer+MIKMIDIPrivate.h" +#import "MIKMIDISequence+MIKMIDIPrivate.h" +#import "MIKMIDITrack+MIKMIDIPrivate.h" + #if !__has_feature(objc_arc) #error MIKMIDISequencer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target @@ -159,7 +163,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; self.startingTimeStamp = startingTimeStamp; - Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; + Float64 startingTempo = [self.sequence private_tempoAtTimeStamp:startingTimeStamp]; if (!startingTempo) startingTempo = kDefaultTempo; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; }); @@ -245,8 +249,8 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam NSMutableDictionary *tempoEventsByTimeStamp = [NSMutableDictionary dictionary]; Float64 overrideTempo = self.tempo; - if (!overrideTempo) { - NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + if (!overrideTempo) { + NSArray *sequenceTempoEvents = [sequence.tempoTrack private_eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; for (MIKMIDITempoEvent *tempoEvent in sequenceTempoEvents) { NSNumber *timeStampKey = @(tempoEvent.timeStamp + playbackOffset); allEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; @@ -256,7 +260,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (self.needsCurrentTempoUpdate) { if (!tempoEventsByTimeStamp.count) { - if (!overrideTempo) overrideTempo = [sequence tempoAtTimeStamp:fromMusicTimeStamp]; + if (!overrideTempo) overrideTempo = [sequence private_tempoAtTimeStamp:fromMusicTimeStamp]; if (!overrideTempo) overrideTempo = kDefaultTempo; MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:fromMusicTimeStamp tempo:overrideTempo]; @@ -283,7 +287,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam // Get other events for (MIKMIDITrack *track in sequence.tracks) { - NSArray *events = [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + NSArray *events = [track private_eventsOfClass:Nil fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; MIKMIDIDestinationEndpoint *destination = events.count ? [self destinationEndpointForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed for (MIKMIDIEvent *event in events) { NSNumber *timeStampKey = @(event.timeStamp + playbackOffset); @@ -329,7 +333,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (isLooping) { if (calculatedToMusicTimeStamp > toMusicTimeStamp) { [self recordAllPendingNoteEventsWithOffTimeStamp:loopEndTimeStamp]; - Float64 tempo = [sequence tempoAtTimeStamp:loopStartTimeStamp]; + Float64 tempo = [sequence private_tempoAtTimeStamp:loopStartTimeStamp]; if (!tempo) tempo = kDefaultTempo; MusicTimeStamp loopLength = loopEndTimeStamp - loopStartTimeStamp; @@ -623,16 +627,10 @@ - (void)setLoopStartTimeStamp:(MusicTimeStamp)loopStartTimeStamp endTimeStamp:(M [self willChangeValueForKey:@"loopStartTimeStamp"]; [self didChangeValueForKey:@"loopStartTimeStamp"]; - dispatch_queue_t queue = self.processingQueue; - if (queue) { - dispatch_sync(queue, ^{ - _loopStartTimeStamp = loopStartTimeStamp; - _loopEndTimeStamp = loopEndTimeStamp; - }); - } else { + [self dispatchSyncToProcessingQueueAsNeeded:^{ _loopStartTimeStamp = loopStartTimeStamp; _loopEndTimeStamp = loopEndTimeStamp; - } + }]; [self willChangeValueForKey:@"loopEndTimeStamp"]; [self didChangeValueForKey:@"loopEndTimeStamp"]; @@ -753,7 +751,7 @@ - (void)setTempo:(Float64)tempo - (MusicTimeStamp)sequenceLength { MusicTimeStamp length = self.overriddenSequenceLength; - return length ? length : self.sequence.length; + return length ? length : self.sequence.private_length; } - (void)setSequence:(MIKMIDISequence *)sequence @@ -761,12 +759,11 @@ - (void)setSequence:(MIKMIDISequence *)sequence if (_sequence != sequence) { [_sequence removeObserver:self forKeyPath:@"tracks"]; - dispatch_queue_t queue = self.processingQueue; - if (queue) { - dispatch_sync(queue, ^{ _sequence = sequence; }); - } else { + [self dispatchSyncToProcessingQueueAsNeeded:^{ + if (_sequence.sequencer == self) _sequence.sequencer = nil; _sequence = sequence; - } + _sequence.sequencer = self; + }]; [_sequence addObserver:self forKeyPath:@"tracks" options:NSKeyValueObservingOptionInitial context:NULL]; } @@ -774,8 +771,26 @@ - (void)setSequence:(MIKMIDISequence *)sequence @end -#pragma mark - +#pragma mark - +@implementation MIKMIDISequencer (MIKMIDIPrivate) + +- (void)dispatchSyncToProcessingQueueAsNeeded:(void (^)())block +{ + if (!block) return; + + dispatch_queue_t processingQueue = self.processingQueue; + if (processingQueue) { + dispatch_sync(processingQueue, block); + } else { + block(); + } +} + +@end + + +#pragma mark - @implementation MIKMIDIEventWithDestination + (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event diff --git a/Source/MIKMIDITrack+MIKMIDIPrivate.h b/Source/MIKMIDITrack+MIKMIDIPrivate.h new file mode 100644 index 00000000..753d6709 --- /dev/null +++ b/Source/MIKMIDITrack+MIKMIDIPrivate.h @@ -0,0 +1,18 @@ +// +// MIKMIDITrack+MIKMIDIPrivate.h +// MIKMIDI +// +// Created by Chris Flesner on 6/30/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import + + +@interface MIKMIDITrack (MIKMIDIPrivate) + +@property (readonly, nonatomic) MusicTimeStamp private_length; + +- (NSArray *)private_eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; + +@end diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 5bd7eeba..ef59e9c6 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -180,72 +180,81 @@ @property (nonatomic, readonly) NSInteger trackNumber; /** - * Whether the track is set to loop. + * A MIDI track’s start time in terms of beat number. By default this value is 0. * * This property can be observed using Key Value Observing. + * + * @note This property is not yet used by MIKMIDISequencer (Issue #99). If this property + * is required for your playback situation, you should stick with MIKMIDIPlayer in the meantime. */ -@property (nonatomic, readonly) BOOL doesLoop; +@property (nonatomic) MusicTimeStamp offset; /** - * The number of times to play the designated portion of the music track. By default, a music track plays once. + * Whether or not the MIDI track is muted. * * This property can be observed using Key Value Observing. + * + * @note This property is not yet used by MIKMIDISequencer (Issue #99). If this property + * is required for your playback situation, you should stick with MIKMIDIPlayer in the meantime. */ -@property (nonatomic) SInt32 numberOfLoops; +@property (nonatomic, getter = isMuted) BOOL muted; /** - * The point in a MIDI track, measured in beats from the end of the MIDI track, at which to begin playback during looped playback. - * That is, during looped playback, a MIDI track plays from (length – loopDuration) to length. + * Whether or not the MIDI track is soloed. * * This property can be observed using Key Value Observing. + * + * @note This property is not yet used by MIKMIDISequencer (Issue #99). If this property + * is required for your playback situation, you should stick with MIKMIDIPlayer in the meantime. */ -@property (nonatomic) MusicTimeStamp loopDuration; +@property (nonatomic, getter = isSolo) BOOL solo; /** - * The loop info for the track. + * The length of the MIDI track. * * This property can be observed using Key Value Observing. */ -@property (nonatomic) MusicTrackLoopInfo loopInfo; +@property (nonatomic) MusicTimeStamp length; /** - * A MIDI track’s start time in terms of beat number. By default this value is 0. + * The time resolution for a sequence of MIDI events. For example, this value can indicate the time resolution that was specified + * by the MIDI file used to construct a sequence. * - * This property can be observed using Key Value Observing. + * If you create a MIDI sequence programmatically, the value is set to 480. If you create a MIDI sequence from a MIDI file, + * the value is set to the time resolution specified in the MIDI file. */ -@property (nonatomic) MusicTimeStamp offset; +@property (nonatomic, readonly) SInt16 timeResolution; + +#pragma mark - Deprecated /** - * Whether or not the MIDI track is muted. + * Whether the track is set to loop. * * This property can be observed using Key Value Observing. */ -@property (nonatomic, getter = isMuted) BOOL muted; +@property (nonatomic, readonly) BOOL doesLoop DEPRECATED_ATTRIBUTE; /** - * Whether or not the MIDI track is soloed. + * The number of times to play the designated portion of the music track. By default, a music track plays once. * * This property can be observed using Key Value Observing. */ -@property (nonatomic, getter = isSolo) BOOL solo; +@property (nonatomic) SInt32 numberOfLoops DEPRECATED_ATTRIBUTE; /** - * The length of the MIDI track. + * The point in a MIDI track, measured in beats from the end of the MIDI track, at which to begin playback during looped playback. + * That is, during looped playback, a MIDI track plays from (length – loopDuration) to length. * * This property can be observed using Key Value Observing. */ -@property (nonatomic) MusicTimeStamp length; +@property (nonatomic) MusicTimeStamp loopDuration DEPRECATED_ATTRIBUTE; /** - * The time resolution for a sequence of MIDI events. For example, this value can indicate the time resolution that was specified - * by the MIDI file used to construct a sequence. + * The loop info for the track. * - * If you create a MIDI sequence programmatically, the value is set to 480. If you create a MIDI sequence from a MIDI file, - * the value is set to the time resolution specified in the MIDI file. + * This property can be observed using Key Value Observing. */ -@property (nonatomic, readonly) SInt16 timeResolution; - -#pragma mark - Deprecated +@property (nonatomic) MusicTrackLoopInfo loopInfo DEPRECATED_ATTRIBUTE; /** * Gets the track's track number in it's owning MIDI sequence. diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index cc3c6319..5fae4050 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -14,6 +14,9 @@ #import "MIKMIDIEventIterator.h" #import "MIKMIDIDestinationEndpoint.h" #import "MIKMIDIErrors.h" +#import "MIKMIDITrack+MIKMIDIPrivate.h" +#import "MIKMIDISequencer+MIKMIDIPrivate.h" + #if !__has_feature(objc_arc) #error MIKMIDITrack.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target @@ -70,72 +73,94 @@ - (instancetype)init return nil; } +#pragma mark - Sequencer Synchronization + +- (void)dispatchSyncToSequencerProcessingQueueAsNeeded:(void (^)())block +{ + if (!block) return; + + MIKMIDISequencer *sequencer = self.sequence.sequencer; + if (sequencer) { + [sequencer dispatchSyncToProcessingQueueAsNeeded:block]; + } else { + block(); + } +} + #pragma mark - Adding and Removing Events #pragma mark Public - (void)addEvent:(MIKMIDIEvent *)event { - if (!event) return; - if ([self.internalEvents containsObject:event]) return; // Don't allow duplicates - - NSError *error = nil; - if (![self insertMIDIEventInMusicTrack:event error:&error]) { - NSLog(@"Error adding %@ to %@: %@", event, self, error); - [self reloadAllEventsFromMusicTrack]; - return; - } - - [self addInternalEventsObject:event]; -} + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + if (!event) return; + if ([self.internalEvents containsObject:event]) return; // Don't allow duplicates -- (void)addEvents:(NSArray *)events -{ - NSMutableSet *scratch = [NSMutableSet setWithArray:events]; - [scratch minusSet:self.internalEvents]; // Don't allow duplicates - if (![scratch count]) return; - - NSError *error = nil; - for (MIKMIDIEvent *event in scratch) { + NSError *error = nil; if (![self insertMIDIEventInMusicTrack:event error:&error]) { NSLog(@"Error adding %@ to %@: %@", event, self, error); [self reloadAllEventsFromMusicTrack]; return; } - } - - [self addInternalEvents:scratch]; + + [self addInternalEventsObject:event]; + }]; +} + +- (void)addEvents:(NSArray *)events +{ + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + NSMutableSet *scratch = [NSMutableSet setWithArray:events]; + [scratch minusSet:self.internalEvents]; // Don't allow duplicates + if (![scratch count]) return; + + NSError *error = nil; + for (MIKMIDIEvent *event in scratch) { + if (![self insertMIDIEventInMusicTrack:event error:&error]) { + NSLog(@"Error adding %@ to %@: %@", event, self, error); + [self reloadAllEventsFromMusicTrack]; + return; + } + } + + [self addInternalEvents:scratch]; + }]; } - (void)removeEvent:(MIKMIDIEvent *)event { - if (!event) return; - if (![self.internalEvents containsObject:event]) return; - - NSError *error = nil; - if (![self removeMIDIEventsFromMusicTrack:[NSSet setWithObject:event] error:&error]) { - NSLog(@"Error removing %@ from %@: %@", event, self, error); - [self reloadAllEventsFromMusicTrack]; - return; - } - - [self removeInternalEventsObject:event]; + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + if (!event) return; + if (![self.internalEvents containsObject:event]) return; + + NSError *error = nil; + if (![self removeMIDIEventsFromMusicTrack:[NSSet setWithObject:event] error:&error]) { + NSLog(@"Error removing %@ from %@: %@", event, self, error); + [self reloadAllEventsFromMusicTrack]; + return; + } + + [self removeInternalEventsObject:event]; + }]; } - (void)removeEvents:(NSArray *)events { - if (![events count]) return; - NSMutableSet *scratch = [NSMutableSet setWithArray:events]; - [scratch intersectSet:self.internalEvents]; - - NSError *error = nil; - if (![self removeMIDIEventsFromMusicTrack:scratch error:&error]) { - NSLog(@"Error removing %@ from %@: %@", events, self, error); - [self reloadAllEventsFromMusicTrack]; - return; - } - - [self removeInternalEvents:scratch]; + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + if (![events count]) return; + NSMutableSet *scratch = [NSMutableSet setWithArray:events]; + [scratch intersectSet:self.internalEvents]; + + NSError *error = nil; + if (![self removeMIDIEventsFromMusicTrack:scratch error:&error]) { + NSLog(@"Error removing %@ from %@: %@", events, self, error); + [self reloadAllEventsFromMusicTrack]; + return; + } + + [self removeInternalEvents:scratch]; + }]; } - (void)removeAllEvents @@ -270,26 +295,16 @@ - (BOOL)removeMIDIEventsFromMusicTrack:(NSSet *)events error:(NSError **)error #pragma mark Public -// All event getters pass through this method +// All public event getters pass through this method - (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp { - MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; - if (![iterator seek:startTimeStamp]) return @[]; - - NSMutableArray *events = [NSMutableArray array]; - - while (iterator.hasCurrentEvent) { - MIKMIDIEvent *event = iterator.currentEvent; - if (!event || event.timeStamp > endTimeStamp) break; - - if (!eventClass || [event isKindOfClass:eventClass]) { - [events addObject:event]; - } + __block NSArray *events; - [iterator moveToNextEvent]; - } + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + events = [self private_eventsOfClass:eventClass fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + }]; - return events; + return events; } - (NSArray *)eventsFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp @@ -325,25 +340,36 @@ - (void)reloadAllEventsFromMusicTrack #pragma mark - Editing Events (Public) - (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)timestampOffset +{ + __block BOOL success = NO; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + success = [self private_moveEventsFromStartingTimeStamp:startTimeStamp toEndingTimeStamp:endTimeStamp byAmount:timestampOffset]; + }]; + + return success; +} + +- (BOOL)private_moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp byAmount:(MusicTimeStamp)timestampOffset { // MusicTrackMoveEvents() fails in common edge cases, so iterate the track and move that way instead - + if (timestampOffset == 0) return YES; // Nothing needs to be done - MusicTimeStamp length = self.length; - if (!length || (startTimeStamp > length) || ![self.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; + MusicTimeStamp length = self.length; + if (!length || (startTimeStamp > length) || ![self.internalEvents count]) return YES; + if (endTimeStamp > length) endTimeStamp = length; NSMutableSet *eventsToMove = [NSMutableSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; NSSet *eventsBeforeMoving = [eventsToMove copy]; NSMutableSet *eventsAfterMoving = [NSMutableSet set]; - + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; while (iterator.hasCurrentEvent && [eventsToMove count] > 0) { MIKMIDIEvent *currentEvent = iterator.currentEvent; if (![eventsToMove containsObject:currentEvent]) { [iterator moveToNextEvent]; continue; -} + } MusicTimeStamp timestamp = currentEvent.timeStamp; if (![iterator moveCurrentEventTo:timestamp+timestampOffset error:NULL]) { @@ -356,14 +382,25 @@ - (BOOL)moveEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingT [eventsToMove removeObject:currentEvent]; [iterator seek:timestamp]; // Move back to previous position } - + [self removeInternalEvents:eventsBeforeMoving]; [self addInternalEvents:eventsAfterMoving]; - + return YES; } - (BOOL)clearEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp +{ + __block BOOL success = NO; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + success = [self private_clearEventsFromStartingTimeStamp:startTimeStamp toEndingTimeStamp:endTimeStamp]; + }]; + + return success; +} + +- (BOOL)private_clearEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp { NSSet *events = [NSSet setWithArray:[self eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]]; BOOL success = [self removeMIDIEventsFromMusicTrack:events error:NULL]; @@ -373,26 +410,51 @@ - (BOOL)clearEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEnding - (BOOL)cutEventsFromStartingTimeStamp:(MusicTimeStamp)startTimeStamp toEndingTimeStamp:(MusicTimeStamp)endTimeStamp { - MusicTimeStamp length = self.length; - if (!length || (startTimeStamp > length) || ![self.events count]) return YES; - if (endTimeStamp > length) endTimeStamp = length; + __block BOOL success = NO; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + MusicTimeStamp length = self.length; + if (!length || (startTimeStamp > length) || ![self.internalEvents count]) { success = YES; return; } + + MusicTimeStamp actualEndTimeStamp = endTimeStamp; + if (actualEndTimeStamp > length) actualEndTimeStamp = length; - if (![self clearEventsFromStartingTimeStamp:startTimeStamp toEndingTimeStamp:endTimeStamp]) return NO; - MusicTimeStamp cutAmount = endTimeStamp - startTimeStamp; - return [self moveEventsFromStartingTimeStamp:endTimeStamp toEndingTimeStamp:kMusicTimeStamp_EndOfTrack byAmount:-cutAmount]; + if (![self private_clearEventsFromStartingTimeStamp:startTimeStamp toEndingTimeStamp:actualEndTimeStamp]) return; + MusicTimeStamp cutAmount = actualEndTimeStamp - startTimeStamp; + success = [self private_moveEventsFromStartingTimeStamp:actualEndTimeStamp toEndingTimeStamp:kMusicTimeStamp_EndOfTrack byAmount:-cutAmount]; + }]; + + return success; } - (BOOL)copyEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp andInsertAtTimeStamp:(MusicTimeStamp)destTimeStamp { - // Move existing events to make room for new events - if (![self moveEventsFromStartingTimeStamp:destTimeStamp - toEndingTimeStamp:kMusicTimeStamp_EndOfTrack - byAmount:(endTimeStamp - startTimeStamp)]) return NO; + __block BOOL success = NO; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + // Move existing events to make room for new events + if (![self private_moveEventsFromStartingTimeStamp:destTimeStamp + toEndingTimeStamp:kMusicTimeStamp_EndOfTrack + byAmount:(endTimeStamp - startTimeStamp)]) return; - return [self mergeEventsFromMIDITrack:origTrack fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp atTimeStamp:destTimeStamp]; + success = [self private_mergeEventsFromMIDITrack:origTrack fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp atTimeStamp:destTimeStamp]; + }]; + + return success; } - (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp atTimeStamp:(MusicTimeStamp)destTimeStamp +{ + __block BOOL success = NO; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + success = [self private_mergeEventsFromMIDITrack:origTrack fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp atTimeStamp:destTimeStamp]; + }]; + + return success; +} + +- (BOOL)private_mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp atTimeStamp:(MusicTimeStamp)destTimeStamp { NSArray *sourceEvents = [origTrack eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; if (![sourceEvents count]) return YES; @@ -406,7 +468,7 @@ - (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicT if (![self insertMIDIEventInMusicTrack:mutableEvent error:NULL]) { [self reloadAllEventsFromMusicTrack]; return NO; -} + } [destinationEvents addObject:mutableEvent]; } @@ -418,20 +480,26 @@ - (BOOL)mergeEventsFromMIDITrack:(MIKMIDITrack *)origTrack fromTimeStamp:(MusicT - (void)setTemporaryLength:(MusicTimeStamp)length andLoopInfo:(MusicTrackLoopInfo)loopInfo { - self.restoredLength = self.length; - self.restoredLoopInfo = self.loopInfo; - self.length = length; - self.loopInfo = loopInfo; - self.hasTemporaryLengthAndLoopInfo = YES; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + self.restoredLength = self.length; + self.restoredLoopInfo = self.loopInfo; + self.length = length; + self.loopInfo = loopInfo; + self.hasTemporaryLengthAndLoopInfo = YES; +#pragma clang diagnostic pop } - (void)restoreLengthAndLoopInfo { - if (!self.hasTemporaryLengthAndLoopInfo) return; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + if (!self.hasTemporaryLengthAndLoopInfo) return; - self.hasTemporaryLengthAndLoopInfo = NO; - self.length = self.restoredLength; - self.loopInfo = self.restoredLoopInfo; + self.hasTemporaryLengthAndLoopInfo = NO; + self.length = self.restoredLength; + self.loopInfo = self.restoredLoopInfo; +#pragma clang diagnostic pop } #pragma mark - Properties @@ -443,16 +511,24 @@ + (NSSet *)keyPathsForValuesAffectingEvents - (NSArray *)events { - if (!self.sortedEventsCache) { - NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]; - _sortedEventsCache = [self.internalEvents sortedArrayUsingDescriptors:@[sortDescriptor]]; -} - return self.sortedEventsCache; + __block NSArray *events; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + if (!self.sortedEventsCache) { + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"timeStamp" ascending:YES]; + _sortedEventsCache = [self.internalEvents sortedArrayUsingDescriptors:@[sortDescriptor]]; + } + events = self.sortedEventsCache; + }]; + + return events; } - (void)setEvents:(NSArray *)events { - self.internalEvents = [NSMutableSet setWithArray:events]; + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + self.internalEvents = [NSMutableSet setWithArray:events]; + }]; } - (void)setInternalEvents:(NSMutableSet *)internalEvents @@ -513,71 +589,6 @@ - (NSInteger)trackNumber return (NSInteger)trackNumber; } -+ (NSSet *)keyPathsForValuesAffectingDoesLoop -{ - return [NSSet setWithObjects:@"loopDuration", nil]; -} - -- (BOOL)doesLoop -{ - return self.loopDuration > 0; -} - -+ (NSSet *)keyPathsForValuesAffectingNumberOfLoops -{ - return [NSSet setWithObjects:@"loopInfo", nil]; -} - -- (SInt32)numberOfLoops -{ - return self.loopInfo.numberOfLoops; -} - -- (void)setNumberOfLoops:(SInt32)numberOfLoops -{ - MusicTrackLoopInfo loopInfo = self.loopInfo; - - if (loopInfo.numberOfLoops != numberOfLoops) { - loopInfo.numberOfLoops = numberOfLoops; - self.loopInfo = loopInfo; - } -} - -+ (NSSet *)keyPathsForValuesAffectingLoopDuration -{ - return [NSSet setWithObjects:@"loopInfo", nil]; -} - -- (MusicTimeStamp)loopDuration -{ - return self.loopInfo.loopDuration; -} - -- (void)setLoopDuration:(MusicTimeStamp)loopDuration -{ - MusicTrackLoopInfo loopInfo = self.loopInfo; - - if (loopInfo.loopDuration != loopDuration) { - loopInfo.loopDuration = loopDuration; - self.loopInfo = loopInfo; - } -} - -- (MusicTrackLoopInfo)loopInfo -{ - MusicTrackLoopInfo info; - UInt32 infoSize = sizeof(info); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &info, &infoSize); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - return info; -} - -- (void)setLoopInfo:(MusicTrackLoopInfo)loopInfo -{ - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &loopInfo, sizeof(loopInfo)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); -} - - (MusicTimeStamp)offset { MusicTimeStamp offset = 0; @@ -632,17 +643,22 @@ + (NSSet *)keyPathsForValuesAffectingLength - (MusicTimeStamp)length { - MusicTimeStamp length = 0; - UInt32 lengthLength = sizeof(length); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, &lengthLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - return length; + __block MusicTimeStamp length = 0; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + length = self.private_length; + }]; + + return length; } - (void)setLength:(MusicTimeStamp)length { - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, sizeof(length)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + MusicTimeStamp newLength = length; + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &newLength, sizeof(length)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + }]; } - (SInt16)timeResolution @@ -654,10 +670,73 @@ - (SInt16)timeResolution return resolution; } -#pragma mark - Properties - #pragma mark - Deprecated ++ (NSSet *)keyPathsForValuesAffectingDoesLoop +{ + return [NSSet setWithObjects:@"loopDuration", nil]; +} + +- (BOOL)doesLoop +{ + return self.loopDuration > 0; +} + ++ (NSSet *)keyPathsForValuesAffectingNumberOfLoops +{ + return [NSSet setWithObjects:@"loopInfo", nil]; +} + +- (SInt32)numberOfLoops +{ + return self.loopInfo.numberOfLoops; +} + +- (void)setNumberOfLoops:(SInt32)numberOfLoops +{ + MusicTrackLoopInfo loopInfo = self.loopInfo; + + if (loopInfo.numberOfLoops != numberOfLoops) { + loopInfo.numberOfLoops = numberOfLoops; + self.loopInfo = loopInfo; + } +} + ++ (NSSet *)keyPathsForValuesAffectingLoopDuration +{ + return [NSSet setWithObjects:@"loopInfo", nil]; +} + +- (MusicTimeStamp)loopDuration +{ + return self.loopInfo.loopDuration; +} + +- (void)setLoopDuration:(MusicTimeStamp)loopDuration +{ + MusicTrackLoopInfo loopInfo = self.loopInfo; + + if (loopInfo.loopDuration != loopDuration) { + loopInfo.loopDuration = loopDuration; + self.loopInfo = loopInfo; + } +} + +- (MusicTrackLoopInfo)loopInfo +{ + MusicTrackLoopInfo info; + UInt32 infoSize = sizeof(info); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &info, &infoSize); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + return info; +} + +- (void)setLoopInfo:(MusicTrackLoopInfo)loopInfo +{ + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_LoopInfo, &loopInfo, sizeof(loopInfo)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); +} + - (BOOL)getTrackNumber:(UInt32 *)trackNumber { static BOOL deprectionMsgShown = NO; @@ -743,3 +822,41 @@ - (BOOL)clearAllEvents @end + +#pragma mark - +@implementation MIKMIDITrack (MIKMIDIPrivate) + +- (NSArray *)private_eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp +{ + if (!self.internalEvents.count) return @[]; // possible WORKAROUND for Issue #100 + + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + if (![iterator seek:startTimeStamp]) return @[]; + + NSMutableArray *events = [NSMutableArray array]; + + while (iterator.hasCurrentEvent) { + MIKMIDIEvent *event = iterator.currentEvent; + if (!event || event.timeStamp > endTimeStamp) break; + + if (!eventClass || [event isKindOfClass:eventClass]) { + [events addObject:event]; + } + + [iterator moveToNextEvent]; + } + + return events; +} + +- (MusicTimeStamp)private_length +{ + MusicTimeStamp length = 0; + UInt32 lengthLength = sizeof(length); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, &lengthLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + return length; +} + +@end + From c118f5c0ba39849da43068605fd0e3f7e2f2ff93 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 30 Jun 2015 20:50:02 -0600 Subject: [PATCH 189/284] Updated modulemap file to make private headers explicitly private. Fixes warning. --- Framework/module.modulemap | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Framework/module.modulemap b/Framework/module.modulemap index 76cef9a9..527ef514 100644 --- a/Framework/module.modulemap +++ b/Framework/module.modulemap @@ -1,6 +1,10 @@ framework module MIKMIDI { umbrella header "MIKMIDI.h" + private header "MIKMIDIPort_SubclassMethods.h" + private header "MIKMIDIPrivateUtilities.h" + private header "MIKMIDISynthesizer_SubclassMethods.h" + export * module * { export * } @@ -13,7 +17,7 @@ framework module MIKMIDI { header "MIKMIDIEvent_SubclassMethods.h" export * } - + explicit module MIKMIDISynthesizerSubclass { header "MIKMIDISynthesizer_SubclassMethods.h" export * From a75adf9afc7ca948a1bcfa55828a5c2e366cf058 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 1 Jul 2015 09:39:15 -0600 Subject: [PATCH 190/284] Fix for (harmless) static analyzer warning. --- Source/MIKMIDIMappingGenerator.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 79eb4d62..a53a887e 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -83,6 +83,7 @@ - (id)init { [NSException raise:NSInternalInconsistencyException format:@"-initWithDevice: is the designated initializer for %@", NSStringFromClass([self class])]; self = [self initWithDevice:nil error:NULL]; // Keep the compiler happy + [self self]; // Keep the compiler happy part 2 return nil; } From fbf6428e6da096706d56fb98f56914f33ac626cc Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 1 Jul 2015 12:56:43 -0500 Subject: [PATCH 191/284] Changed timer start to DISPATCH_TIME_NOW. Moved KVO registering when setting sequence to the processing queue. --- Source/MIKMIDISequencer.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 948221f0..37e4d480 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -177,7 +177,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi if (!timer) return NSLog(@"Unable to create processing timer for %@.", [self class]); self.processingTimer = timer; - dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.05 * NSEC_PER_SEC, 0.05 * NSEC_PER_SEC); + dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC, 0.05 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ [self processSequenceStartingFromMIDITimeStamp:self.latestScheduledMIDITimeStamp + 1]; }); @@ -757,15 +757,15 @@ - (MusicTimeStamp)sequenceLength - (void)setSequence:(MIKMIDISequence *)sequence { if (_sequence != sequence) { - [_sequence removeObserver:self forKeyPath:@"tracks"]; - [self dispatchSyncToProcessingQueueAsNeeded:^{ + [_sequence removeObserver:self forKeyPath:@"tracks"]; + if (_sequence.sequencer == self) _sequence.sequencer = nil; _sequence = sequence; _sequence.sequencer = self; - }]; - [_sequence addObserver:self forKeyPath:@"tracks" options:NSKeyValueObservingOptionInitial context:NULL]; + [_sequence addObserver:self forKeyPath:@"tracks" options:NSKeyValueObservingOptionInitial context:NULL]; + }]; } } From 75cfa721996b4d1e457355579bdd656582071407 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 1 Jul 2015 14:29:58 -0500 Subject: [PATCH 192/284] MIKMIDISequencer now checks if its already on the processing queue before dispatching to it. This prevents potential deadlocks in client code that might need to look at sequence or track properties while in the processingQueue (which they can end up in from KVO notifications), and it also cleans up a lot of code that had to set or get things on the current queue in MIKMIDISequence and Track. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 -- Source/MIKMIDISequence+MIKMIDIPrivate.h | 4 -- Source/MIKMIDISequence.m | 28 ++++----- Source/MIKMIDISequencer.m | 23 +++++--- Source/MIKMIDITrack+MIKMIDIPrivate.h | 18 ------ Source/MIKMIDITrack.m | 64 +++++++-------------- 6 files changed, 47 insertions(+), 94 deletions(-) delete mode 100644 Source/MIKMIDITrack+MIKMIDIPrivate.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 49e1787c..a51d66cc 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -54,7 +54,6 @@ 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */; }; - 83FB36121B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB36101B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h */; }; 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F01B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; @@ -342,7 +341,6 @@ 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequence+MIKMIDIPrivate.h"; sourceTree = ""; }; - 83FB36101B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDITrack+MIKMIDIPrivate.h"; sourceTree = ""; }; 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; @@ -497,7 +495,6 @@ 839D936C19C3A30B007589C3 /* MIKMIDISequence.m */, 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */, 839D937119C3A319007589C3 /* MIKMIDITrack.h */, - 83FB36101B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h */, 9D76DCEA1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h */, 839D937219C3A319007589C3 /* MIKMIDITrack.m */, 9DEE37BF1A9D66C2007B7FC7 /* Events */, @@ -905,7 +902,6 @@ 83BC19BB1A23CD0D004F384F /* MIKMIDIMetronome.h in Headers */, 9D74EF9217A713A100BEE89F /* MIKMIDIUtilities.h in Headers */, 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */, - 83FB36121B42E90900F91DCD /* MIKMIDITrack+MIKMIDIPrivate.h in Headers */, 9DAE7D8E19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h in Headers */, 9D74EF9417A713A100BEE89F /* NSUIApplication+MIKMIDI.h in Headers */, ); diff --git a/Source/MIKMIDISequence+MIKMIDIPrivate.h b/Source/MIKMIDISequence+MIKMIDIPrivate.h index e7f70094..1e4333da 100644 --- a/Source/MIKMIDISequence+MIKMIDIPrivate.h +++ b/Source/MIKMIDISequence+MIKMIDIPrivate.h @@ -15,8 +15,4 @@ @property (weak, nonatomic) MIKMIDISequencer *sequencer; -@property (readonly, nonatomic) MusicTimeStamp private_length; - -- (Float64)private_tempoAtTimeStamp:(MusicTimeStamp)timeStamp; - @end diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index b3b1c760..6bd6e6dd 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -14,7 +14,6 @@ #import "MIKMIDIMetaTimeSignatureEvent.h" #import "MIKMIDIDestinationEndpoint.h" #import "MIKMIDISequence+MIKMIDIPrivate.h" -#import "MIKMIDITrack+MIKMIDIPrivate.h" #import "MIKMIDISequencer+MIKMIDIPrivate.h" #if !__has_feature(objc_arc) @@ -304,7 +303,8 @@ - (Float64)tempoAtTimeStamp:(MusicTimeStamp)timeStamp __block Float64 tempo = 0; [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ - tempo = [self private_tempoAtTimeStamp:timeStamp]; + NSArray *events = [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; + tempo = [[events lastObject] bpm]; }]; return tempo; @@ -376,7 +376,7 @@ - (void)updateLengthDefinedByTracks { MusicTimeStamp length = 0; for (MIKMIDITrack *track in self.tracks) { - MusicTimeStamp trackLength = track.private_length + track.offset; + MusicTimeStamp trackLength = track.length + track.offset; if (trackLength > length) length = trackLength; } @@ -428,7 +428,13 @@ + (NSSet *)keyPathsForValuesAffectingTracks - (NSArray *)tracks { - return [self.internalTracks copy]; + __block NSArray *tracks; + + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ + tracks = self.internalTracks; + }]; + + return tracks; } + (NSSet *)keyPathsForValuesAffectingLength @@ -442,7 +448,7 @@ - (MusicTimeStamp)length __block MusicTimeStamp length = 0; [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ - length = self.private_length; + length = (_length == MIKMIDISequenceLongestTrackLength) ? self.lengthDefinedByTracks : _length; }]; return length; @@ -546,18 +552,6 @@ - (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTim #pragma mark - @implementation MIKMIDISequence (MIKMIDIPrivate) -- (Float64)private_tempoAtTimeStamp:(MusicTimeStamp)timeStamp -{ - NSArray *events = [self.tempoTrack private_eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; - return [[events lastObject] bpm]; -} - - (void)setSequencer:(MIKMIDISequencer *)sequencer { _sequencer = sequencer; } -- (MusicTimeStamp)private_length -{ - if (_length != MIKMIDISequenceLongestTrackLength) return _length; - return self.lengthDefinedByTracks; -} - @end diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 37e4d480..4749eb03 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -24,7 +24,6 @@ #import "MIKMIDISynthesizer.h" #import "MIKMIDISequencer+MIKMIDIPrivate.h" #import "MIKMIDISequence+MIKMIDIPrivate.h" -#import "MIKMIDITrack+MIKMIDIPrivate.h" #if !__has_feature(objc_arc) @@ -33,7 +32,6 @@ #define kDefaultTempo 120 - NSString * const MIKMIDISequencerWillLoopNotification = @"MIKMIDISequencerWillLoopNotification"; const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp = -1; @@ -66,6 +64,10 @@ + (instancetype)pendingNoteOffWithEndTimeStamp:(MusicTimeStamp)endTimeStamp; #pragma mark - @interface MIKMIDISequencer () +{ + void *_processingQueueKey; + void *_processingQueueContext; +} @property (readonly, nonatomic) MIKMIDIClock *clock; @@ -112,6 +114,8 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence _tracksToDestinationsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; _tracksToDefaultSynthsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; _createSynthsAndEndpointsIfNeeded = YES; + _processingQueueKey = &_processingQueueKey; + _processingQueueContext = &_processingQueueContext; } return self; } @@ -157,13 +161,14 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; dispatch_queue_t queue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + dispatch_queue_set_specific(queue, &_processingQueueKey, &_processingQueueContext, NULL); self.processingQueue = queue; dispatch_sync(queue, ^{ MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; self.startingTimeStamp = startingTimeStamp; - Float64 startingTempo = [self.sequence private_tempoAtTimeStamp:startingTimeStamp]; + Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; if (!startingTempo) startingTempo = kDefaultTempo; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; }); @@ -250,7 +255,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam Float64 overrideTempo = self.tempo; if (!overrideTempo) { - NSArray *sequenceTempoEvents = [sequence.tempoTrack private_eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; for (MIKMIDITempoEvent *tempoEvent in sequenceTempoEvents) { NSNumber *timeStampKey = @(tempoEvent.timeStamp + playbackOffset); allEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; @@ -260,7 +265,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (self.needsCurrentTempoUpdate) { if (!tempoEventsByTimeStamp.count) { - if (!overrideTempo) overrideTempo = [sequence private_tempoAtTimeStamp:fromMusicTimeStamp]; + if (!overrideTempo) overrideTempo = [sequence tempoAtTimeStamp:fromMusicTimeStamp]; if (!overrideTempo) overrideTempo = kDefaultTempo; MIKMIDITempoEvent *tempoEvent = [MIKMIDITempoEvent tempoEventWithTimeStamp:fromMusicTimeStamp tempo:overrideTempo]; @@ -287,7 +292,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam // Get other events for (MIKMIDITrack *track in sequence.tracks) { - NSArray *events = [track private_eventsOfClass:Nil fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + NSArray *events = [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; MIKMIDIDestinationEndpoint *destination = events.count ? [self destinationEndpointForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed for (MIKMIDIEvent *event in events) { NSNumber *timeStampKey = @(event.timeStamp + playbackOffset); @@ -333,7 +338,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam if (isLooping) { if (calculatedToMusicTimeStamp > toMusicTimeStamp) { [self recordAllPendingNoteEventsWithOffTimeStamp:loopEndTimeStamp]; - Float64 tempo = [sequence private_tempoAtTimeStamp:loopStartTimeStamp]; + Float64 tempo = [sequence tempoAtTimeStamp:loopStartTimeStamp]; if (!tempo) tempo = kDefaultTempo; MusicTimeStamp loopLength = loopEndTimeStamp - loopStartTimeStamp; @@ -751,7 +756,7 @@ - (void)setTempo:(Float64)tempo - (MusicTimeStamp)sequenceLength { MusicTimeStamp length = self.overriddenSequenceLength; - return length ? length : self.sequence.private_length; + return length ? length : self.sequence.length; } - (void)setSequence:(MIKMIDISequence *)sequence @@ -780,7 +785,7 @@ - (void)dispatchSyncToProcessingQueueAsNeeded:(void (^)())block if (!block) return; dispatch_queue_t processingQueue = self.processingQueue; - if (processingQueue) { + if (processingQueue && dispatch_get_specific(_processingQueueKey) != _processingQueueContext) { dispatch_sync(processingQueue, block); } else { block(); diff --git a/Source/MIKMIDITrack+MIKMIDIPrivate.h b/Source/MIKMIDITrack+MIKMIDIPrivate.h deleted file mode 100644 index 753d6709..00000000 --- a/Source/MIKMIDITrack+MIKMIDIPrivate.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// MIKMIDITrack+MIKMIDIPrivate.h -// MIKMIDI -// -// Created by Chris Flesner on 6/30/15. -// Copyright (c) 2015 Mixed In Key. All rights reserved. -// - -#import - - -@interface MIKMIDITrack (MIKMIDIPrivate) - -@property (readonly, nonatomic) MusicTimeStamp private_length; - -- (NSArray *)private_eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; - -@end diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 5fae4050..74c38349 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -14,7 +14,6 @@ #import "MIKMIDIEventIterator.h" #import "MIKMIDIDestinationEndpoint.h" #import "MIKMIDIErrors.h" -#import "MIKMIDITrack+MIKMIDIPrivate.h" #import "MIKMIDISequencer+MIKMIDIPrivate.h" @@ -301,7 +300,25 @@ - (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)start __block NSArray *events; [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ - events = [self private_eventsOfClass:eventClass fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + if (!self.internalEvents.count) { events = @[]; return; } // possible WORKAROUND for Issue #100 + + MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; + if (![iterator seek:startTimeStamp]) { events = @[]; return; } + + NSMutableArray *mutableEvents = [NSMutableArray array]; + + while (iterator.hasCurrentEvent) { + MIKMIDIEvent *event = iterator.currentEvent; + if (!event || event.timeStamp > endTimeStamp) break; + + if (!eventClass || [event isKindOfClass:eventClass]) { + [mutableEvents addObject:event]; + } + + [iterator moveToNextEvent]; + } + + events = mutableEvents; }]; return events; @@ -646,7 +663,9 @@ - (MusicTimeStamp)length __block MusicTimeStamp length = 0; [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ - length = self.private_length; + UInt32 lengthLength = sizeof(length); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, &lengthLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); }]; return length; @@ -821,42 +840,3 @@ - (BOOL)clearAllEvents } @end - - -#pragma mark - -@implementation MIKMIDITrack (MIKMIDIPrivate) - -- (NSArray *)private_eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp -{ - if (!self.internalEvents.count) return @[]; // possible WORKAROUND for Issue #100 - - MIKMIDIEventIterator *iterator = [MIKMIDIEventIterator iteratorForTrack:self]; - if (![iterator seek:startTimeStamp]) return @[]; - - NSMutableArray *events = [NSMutableArray array]; - - while (iterator.hasCurrentEvent) { - MIKMIDIEvent *event = iterator.currentEvent; - if (!event || event.timeStamp > endTimeStamp) break; - - if (!eventClass || [event isKindOfClass:eventClass]) { - [events addObject:event]; - } - - [iterator moveToNextEvent]; - } - - return events; -} - -- (MusicTimeStamp)private_length -{ - MusicTimeStamp length = 0; - UInt32 lengthLength = sizeof(length); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, &lengthLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - return length; -} - -@end - From 8cb3b0a27feb99a8f5bdb64152b195d12e6a256a Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 1 Jul 2015 14:46:30 -0500 Subject: [PATCH 193/284] Now sending KVO for properties changed on the processing queue on the processing queue. --- Source/MIKMIDISequencer.m | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 4749eb03..baea99b4 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -629,16 +629,16 @@ - (void)setLoopStartTimeStamp:(MusicTimeStamp)loopStartTimeStamp endTimeStamp:(M { if (loopEndTimeStamp != MIKMIDISequencerEndOfSequenceLoopEndTimeStamp && (loopStartTimeStamp >= loopEndTimeStamp)) return; - [self willChangeValueForKey:@"loopStartTimeStamp"]; - [self didChangeValueForKey:@"loopStartTimeStamp"]; - [self dispatchSyncToProcessingQueueAsNeeded:^{ + [self willChangeValueForKey:@"loopStartTimeStamp"]; + [self didChangeValueForKey:@"loopStartTimeStamp"]; + _loopStartTimeStamp = loopStartTimeStamp; _loopEndTimeStamp = loopEndTimeStamp; - }]; - [self willChangeValueForKey:@"loopEndTimeStamp"]; - [self didChangeValueForKey:@"loopEndTimeStamp"]; + [self willChangeValueForKey:@"loopEndTimeStamp"]; + [self didChangeValueForKey:@"loopEndTimeStamp"]; + }]; } #pragma mark - Timer @@ -650,6 +650,7 @@ - (void)processingTimerFired:(NSTimer *)timer #pragma mark - KVO ++ (BOOL)automaticallyNotifiesObserversOfSequence { return NO; } + (NSSet *)keyPathsForValuesAffectingEffectiveLoopEndTimeStamp { return [NSSet setWithObjects:@"loopEndTimeStamp", @"sequence.length", nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context @@ -763,6 +764,7 @@ - (void)setSequence:(MIKMIDISequence *)sequence { if (_sequence != sequence) { [self dispatchSyncToProcessingQueueAsNeeded:^{ + [self willChangeValueForKey:@"sequence"]; [_sequence removeObserver:self forKeyPath:@"tracks"]; if (_sequence.sequencer == self) _sequence.sequencer = nil; @@ -770,6 +772,7 @@ - (void)setSequence:(MIKMIDISequence *)sequence _sequence.sequencer = self; [_sequence addObserver:self forKeyPath:@"tracks" options:NSKeyValueObservingOptionInitial context:NULL]; + [self didChangeValueForKey:@"sequence"]; }]; } } From 25acb287d28043bac396bbd61a69c1aa3bca0e9f Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 2 Jul 2015 10:34:17 -0500 Subject: [PATCH 194/284] Fixed incorrect willChange/didChange call order for loopStartTimeStamp and loopEndTimeStamp. --- Source/MIKMIDISequencer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index baea99b4..59ddcfc4 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -631,12 +631,12 @@ - (void)setLoopStartTimeStamp:(MusicTimeStamp)loopStartTimeStamp endTimeStamp:(M [self dispatchSyncToProcessingQueueAsNeeded:^{ [self willChangeValueForKey:@"loopStartTimeStamp"]; - [self didChangeValueForKey:@"loopStartTimeStamp"]; + [self willChangeValueForKey:@"loopEndTimeStamp"]; _loopStartTimeStamp = loopStartTimeStamp; _loopEndTimeStamp = loopEndTimeStamp; - [self willChangeValueForKey:@"loopEndTimeStamp"]; + [self didChangeValueForKey:@"loopStartTimeStamp"]; [self didChangeValueForKey:@"loopEndTimeStamp"]; }]; } From a65e82a541ffa5e37cd1fcce123580c556770d3d Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 3 Jul 2015 10:58:14 -0500 Subject: [PATCH 195/284] Issue #36: Added MIKMIDICommandScheduler protocol, which MIKMIDISynthesizer and MIKMIDIDestinationEndpoint conform to. MIKMIDISequencer now uses command schedulers rather than destination endpoints for scheduling MIDI commands. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 + Source/MIKMIDICommandScheduler.h | 22 ++++ Source/MIKMIDIDestinationEndpoint.h | 3 +- Source/MIKMIDIDestinationEndpoint.m | 11 ++ Source/MIKMIDIMetronome.h | 8 +- Source/MIKMIDIMetronome.m | 16 --- Source/MIKMIDISequencer.h | 92 ++++++++++---- Source/MIKMIDISequencer.m | 108 ++++++++-------- Source/MIKMIDISynthesizer.h | 5 +- Source/MIKMIDISynthesizer.m | 130 ++++++++++++++++++++ 10 files changed, 293 insertions(+), 106 deletions(-) create mode 100644 Source/MIKMIDICommandScheduler.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index a51d66cc..1a9ef4e8 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8308F6321B46C482004307AD /* MIKMIDICommandScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8308F6311B46C482004307AD /* MIKMIDICommandScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */ = {isa = PBXBuildFile; fileRef = 833B73D81A262FE100E0CC9F /* MIKMIDISequencer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 833B73DB1A262FE100E0CC9F /* MIKMIDISequencer.m in Sources */ = {isa = PBXBuildFile; fileRef = 833B73D91A262FE100E0CC9F /* MIKMIDISequencer.m */; }; 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */ = {isa = PBXBuildFile; fileRef = 833B73DC1A26346F00E0CC9F /* MIKMIDIClock.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -294,6 +295,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8308F6311B46C482004307AD /* MIKMIDICommandScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommandScheduler.h; sourceTree = ""; }; 833B73D81A262FE100E0CC9F /* MIKMIDISequencer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequencer.h; sourceTree = ""; }; 833B73D91A262FE100E0CC9F /* MIKMIDISequencer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequencer.m; sourceTree = ""; }; 833B73DC1A26346F00E0CC9F /* MIKMIDIClock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClock.h; sourceTree = ""; }; @@ -658,6 +660,7 @@ 9DAF8B001A7AFB1900F46528 /* Sequencing */ = { isa = PBXGroup; children = ( + 8308F6311B46C482004307AD /* MIKMIDICommandScheduler.h */, 839D936719C3A303007589C3 /* MIKMIDIPlayer.h */, 839D936819C3A303007589C3 /* MIKMIDIPlayer.m */, 833B73D81A262FE100E0CC9F /* MIKMIDISequencer.h */, @@ -893,6 +896,7 @@ 9D74EF8C17A713A100BEE89F /* MIKMIDISourceEndpoint.h in Headers */, 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, + 8308F6321B46C482004307AD /* MIKMIDICommandScheduler.h in Headers */, 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, diff --git a/Source/MIKMIDICommandScheduler.h b/Source/MIKMIDICommandScheduler.h new file mode 100644 index 00000000..abfd4e56 --- /dev/null +++ b/Source/MIKMIDICommandScheduler.h @@ -0,0 +1,22 @@ +// +// MIKMIDICommandScheduler.h +// MIKMIDI +// +// Created by Chris Flesner on 7/3/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + +#import + + +/** + * Objects that conform to this protocol can be used as a destination for MIDI commands to + * be sent to by MIKMIDISequencer. + * + * @see MIKMIDISequencer + */ +@protocol MIKMIDICommandScheduler + +- (void)scheduleMIDICommands:(NSArray *)commands; + +@end diff --git a/Source/MIKMIDIDestinationEndpoint.h b/Source/MIKMIDIDestinationEndpoint.h index a7e12f04..31ed3de3 100644 --- a/Source/MIKMIDIDestinationEndpoint.h +++ b/Source/MIKMIDIDestinationEndpoint.h @@ -7,6 +7,7 @@ // #import "MIKMIDIEndpoint.h" +#import "MIKMIDICommandScheduler.h" /** * MIKMIDIDestinationEndpoint represents a source (input) MIDI endpoint. @@ -26,7 +27,7 @@ * @see -[MIKMIDIDeviceManager sendCommands:toEndpoint:error:] * @see MIKMIDIClientDestinationEndpoint */ -@interface MIKMIDIDestinationEndpoint : MIKMIDIEndpoint +@interface MIKMIDIDestinationEndpoint : MIKMIDIEndpoint /** * Unschedules previously-sent events. Events that have been scheduled with timestamps diff --git a/Source/MIKMIDIDestinationEndpoint.m b/Source/MIKMIDIDestinationEndpoint.m index bbdec0d5..6a9a82ef 100644 --- a/Source/MIKMIDIDestinationEndpoint.m +++ b/Source/MIKMIDIDestinationEndpoint.m @@ -8,6 +8,7 @@ #import "MIKMIDIDestinationEndpoint.h" #import "MIKMIDIObject_SubclassMethods.h" +#import "MIKMIDIDeviceManager.h" #if !__has_feature(objc_arc) #error MIKMIDIDestinationEndpoint.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIDestinationEndpoint.m in the Build Phases for this target @@ -26,4 +27,14 @@ - (void)unscheduleAllPendingEvents MIDIFlushOutput(self.objectRef); } +#pragma mark - MIKMIDICommandScheduler + +- (void)scheduleMIDICommands:(NSArray *)commands +{ + NSError *error; + if (commands.count && ![[MIKMIDIDeviceManager sharedDeviceManager] sendCommands:commands toEndpoint:self error:&error]) { + NSLog(@"%@: An error occurred scheduling the commands %@ for destination endpoint %@. %@", NSStringFromClass([self class]), commands, self, error); + } +} + @end diff --git a/Source/MIKMIDIMetronome.h b/Source/MIKMIDIMetronome.h index bdf14e1b..37a15072 100644 --- a/Source/MIKMIDIMetronome.h +++ b/Source/MIKMIDIMetronome.h @@ -9,6 +9,10 @@ #import "MIKMIDIEndpointSynthesizer.h" +/** + * This class is only a subclass of MIKMIDIEndpointSynthesizer so it continues to function with MIKMIDIPlayer while + * it still exists. Once MIKMIDIPlayer is removed from the code base, expect this to become a subclass of MIKMIDISynthesizer. + */ @interface MIKMIDIMetronome : MIKMIDIEndpointSynthesizer @property (nonatomic) MIDINoteMessage tickMessage; @@ -16,10 +20,12 @@ @end + @interface MIKMIDIMetronome (Private) /** - * This should not be called directly. Consider it private to MIKMIDI. + * This should not be called directly, but may be overridden by subclasses to setup the metronome instrument + * in a custom manner. */ - (BOOL)setupMetronome; diff --git a/Source/MIKMIDIMetronome.m b/Source/MIKMIDIMetronome.m index afd5e65f..7a40557f 100644 --- a/Source/MIKMIDIMetronome.m +++ b/Source/MIKMIDIMetronome.m @@ -48,20 +48,4 @@ - (instancetype)init return self; } -- (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination componentDescription:(AudioComponentDescription)componentDescription -{ - if (self = [super initWithClientDestinationEndpoint:destination componentDescription:componentDescription]) { - if (![self setupMetronome]) return nil; - } - return self; -} - -- (instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source componentDescription:(AudioComponentDescription)componentDescription -{ - if (self = [super initWithMIDISource:source componentDescription:componentDescription]) { - if (![self setupMetronome]) return nil; - } - return self; -} - @end diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index b9f27f18..81143e81 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -16,6 +16,7 @@ @class MIKMIDIDestinationEndpoint; @class MIKMIDISynthesizer; @class MIKMIDIClock; +@protocol MIKMIDICommandScheduler; /** * Types of click track statuses, that determine when the click track will be audible. @@ -110,20 +111,20 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * Allows subclasses to modify the MIDI commands that are about to be - * scheduled with a destination endpoint. + * scheduled with a command scheduler. * * @param commandsToBeScheduled An array of MIKMIDICommands that are about * to be scheduled. * - * @param endpoint The destination endpoint the commands will be sent to after - * they are modified. + * @param scheduler The command scheduler the commands will be scheduled with + * after they are modified. * * @note You should not call this method directly. It is made public solely to * give subclasses a chance to alter or override any MIDI commands parsed from the * MIDI sequence before they get sent to their destination endpoint. * */ -- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forEndpoint:(MIKMIDIDestinationEndpoint *)endpoint; +- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forCommandScheduler:(id)scheduler; /** * Sets the loopStartTimeStamp and loopEndTimeStamp properties. @@ -197,41 +198,42 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { #pragma mark - Configuration /** - * Sets the destination endpoint for a track in the sequencer's sequence. - * Calling this method is optional. By default, the sequencer will setup internal default endpoints - * connected to synthesizers so that playback "just works". + * Sets the command scheduler for a track in the sequencer's sequence. + * Calling this method is optional. By default, the sequencer will setup internal synthesizers + * so that playback "just works". * * @note If track is not contained by the receiver's sequence, this method does nothing. * - * @param endpoint The MIKMIDIDestinationEndpoint instance to which events in track should be sent during playback. - * @param track An MIKMIDITrack instance. + * @param commandScheduler An object that conforms to MIKMIDICommandScheduler with which events + * in track should be scheduled during playback. + * @param track An MIKMIDITrack instance. */ -- (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:(MIKMIDITrack *)track; +- (void)setCommandScheduler:(id)commandScheduler forTrack:(MIKMIDITrack *)track; /** - * Returns the destination endpoint for a track in the sequencer's sequence. + * Returns the command scheduler for a track in the sequencer's sequence. * - * MIKMIDISequencer will automatically create its own default endpoints connected to - * MIKMIDISynthesizers for any tracks not configured manually. This means that even if you - * haven't called -setDestinationEndpoint:forTrack:, you can use this method to retrieve - * the default endpoint for a given track. + * MIKMIDISequencer will automatically create its own default synthesizers connected + * for any tracks not configured manually. This means that even if you haven't called + * -setCommandScheduler:forTrack:, you can use this method to retrieve + * the default command scheduler for a given track. * * @note If track is not contained by the receiver's sequence, this method returns nil. * * @param track An MIKMIDITrack instance. * - * @return The destination endpoint associated with track, or nil if one can't be found. + * @return The command scheduler associated with track, or nil if one can't be found. * - * @see -setDestinationEndpoint:forTrack: + * @see -setCommandScheduler:forTrack: * @see -builtinSynthesizerForTrack: - * @see createSynthsAndEndpointsIfNeeded + * @see createSynthsIfNeeded */ -- (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track; +- (id)commandSchedulerForTrack:(MIKMIDITrack *)track; /** * Returns synthesizer the receiver will use to synthesize MIDI during playback - * for any tracks whose MIDI has not been routed to a custom endpoint using - * -setDestinationEndpoint:forTrack:. For tracks where a custom endpoint has + * for any tracks whose MIDI has not been routed to a custom scheduler using + * -setCommandScheduler:forTrack:. For tracks where a custom scheduler has * been set, this method returns nil. * * The caller is free to reconfigure the synthesizer(s) returned by this method, @@ -357,14 +359,14 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * Whether or not the sequencer should create synthesizers and endpoints * for MIDI tracks that are not assigned an endpoint. * - * When this property is YES, -destinationEndpointForTrack: will create an - * endpoint and a synthesizer for any track that has MIDI commands sent to it - * and doesn't already have an assigned endpoint. The default for this property + * When this property is YES, -commandSchedulerForTrack: will create a + * synthesizer for any track that has MIDI commands scheduled for it + * and doesn't already have an assigned scheduler. The default for this property * is YES. * - * @see -destinationEndpointForTrack: + * @see -commandSchedulerForTrack: */ -@property (nonatomic, getter=shouldCreateSynthsAndEndpointsIfNeeded) BOOL createSynthsAndEndpointsIfNeeded; +@property (nonatomic, getter=shouldCreateSynthsIfNeeded) BOOL createSynthsIfNeeded; /** * The metronome to send click track events to. @@ -400,6 +402,44 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { */ @property (readonly, nonatomic) MIDITimeStamp latestScheduledMIDITimeStamp; +#pragma mark - Deprecated + +/** + * @deprecated Use -setCommandScheduler:forTrack: instead. + * + * Sets the destination endpoint for a track in the sequencer's sequence. + * Calling this method is optional. By default, the sequencer will setup internal default endpoints + * connected to synthesizers so that playback "just works". + * + * @note If track is not contained by the receiver's sequence, this method does nothing. + * + * @param endpoint The MIKMIDIDestinationEndpoint instance to which events in track should be sent during playback. + * @param track An MIKMIDITrack instance. + */ +- (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:(MIKMIDITrack *)track __attribute((deprecated("use -setCommandScheduler:forTrack: instead"))); + +/** + * @deprecated Use -commandSchedulerForTrack: instead. + * + * Returns the destination endpoint for a track in the sequencer's sequence. + * + * MIKMIDISequencer will automatically create its own default endpoints connected to + * MIKMIDISynthesizers for any tracks not configured manually. This means that even if you + * haven't called -setDestinationEndpoint:forTrack:, you can use this method to retrieve + * the default endpoint for a given track. + * + * @note If track is not contained by the receiver's sequence, this method returns nil. + * + * @param track An MIKMIDITrack instance. + * + * @return The destination endpoint associated with track, or nil if one can't be found. + * + * @see -setDestinationEndpoint:forTrack: + * @see -builtinSynthesizerForTrack: + * @see createSynthsAndEndpointsIfNeeded + */ +- (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track __attribute((deprecated("use -setCommandScheduler:forTrack: instead"))); + @end diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 59ddcfc4..fb00a3e6 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -19,11 +19,12 @@ #import "MIKMIDIDeviceManager.h" #import "MIKMIDIMetronome.h" #import "MIKMIDIMetaTimeSignatureEvent.h" -#import "MIKMIDIClientDestinationEndpoint.h" #import "MIKMIDIUtilities.h" #import "MIKMIDISynthesizer.h" #import "MIKMIDISequencer+MIKMIDIPrivate.h" #import "MIKMIDISequence+MIKMIDIPrivate.h" +#import "MIKMIDICommandScheduler.h" +#import "MIKMIDIDestinationEndpoint.h" #if !__has_feature(objc_arc) @@ -40,17 +41,17 @@ @interface MIKMIDIEventWithDestination : NSObject @property (nonatomic, strong) MIKMIDIEvent *event; -@property (nonatomic, strong) MIKMIDIDestinationEndpoint *destination; +@property (nonatomic, strong) id destination; @property (nonatomic, readonly) BOOL representsNoteOff; -+ (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event; -+ (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event representsNoteOff:(BOOL)representsNoteOff; ++ (instancetype)eventWithDestination:(id)destination event:(MIKMIDIEvent *)event; ++ (instancetype)eventWithDestination:(id)destination event:(MIKMIDIEvent *)event representsNoteOff:(BOOL)representsNoteOff; @end @interface MIKMIDICommandWithDestination : NSObject @property (nonatomic, strong) MIKMIDICommand *command; -@property (nonatomic, strong) MIKMIDIDestinationEndpoint *destination; -+ (instancetype)commandWithDestination:(MIKMIDIDestinationEndpoint *)destination command:(MIKMIDICommand *)command; +@property (nonatomic, strong) id destination; ++ (instancetype)commandWithDestination:(id)destination command:(MIKMIDICommand *)command; @end @@ -86,7 +87,6 @@ @interface MIKMIDISequencer () @property (nonatomic, strong) NSMapTable *tracksToDestinationsMap; @property (nonatomic, strong) NSMapTable *tracksToDefaultSynthsMap; -@property (nonatomic, strong) MIKMIDIClientDestinationEndpoint *metronomeEndpoint; @property (nonatomic) BOOL needsCurrentTempoUpdate; @@ -113,7 +113,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence _clickTrackStatus = MIKMIDISequencerClickTrackStatusEnabledInRecord; _tracksToDestinationsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; _tracksToDefaultSynthsMap = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; - _createSynthsAndEndpointsIfNeeded = YES; + _createSynthsIfNeeded = YES; _processingQueueKey = &_processingQueueKey; _processingQueueContext = &_processingQueueContext; } @@ -159,7 +159,6 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi if (self.isPlaying) [self stop]; NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - dispatch_queue_t queue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(queue, &_processingQueueKey, &_processingQueueContext, NULL); self.processingQueue = queue; @@ -293,7 +292,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam // Get other events for (MIKMIDITrack *track in sequence.tracks) { NSArray *events = [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; - MIKMIDIDestinationEndpoint *destination = events.count ? [self destinationEndpointForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed + id destination = events.count ? [self commandSchedulerForTrack:track] : nil; // only get the destination if there's events so we don't create a destination endpoint if not needed for (MIKMIDIEvent *event in events) { NSNumber *timeStampKey = @(event.timeStamp + playbackOffset); NSMutableArray *eventsAtTimeStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; @@ -361,7 +360,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam - (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationEvent { MIKMIDIEvent *event = destinationEvent.event; - MIKMIDIDestinationEndpoint *destination = destinationEvent.destination; + id destination = destinationEvent.destination; MIKMIDIClock *clock = self.clock; MIKMIDICommand *command; @@ -390,7 +389,7 @@ - (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationE if (command) { MIKMutableMIDICommand *adjustedCommand = [command mutableCopy]; adjustedCommand.midiTimestamp += [clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; - [self sendCommands:@[adjustedCommand] toDestinationEndpoint:destination]; + [self scheduleCommands:@[adjustedCommand] withCommandScheduler:destination]; } } @@ -406,7 +405,7 @@ - (void)sendAllPendingNoteOffsWithMIDITimeStamp:(MIDITimeStamp)offTimeStamp MIKMIDIPendingNoteOffsForTimeStamp *pendingNoteOffs = noteOffs[musicTimeStampNumber]; for (MIKMIDIEventWithDestination *noteOffEventWithDestination in pendingNoteOffs.noteEventsWithEndTimeStamp) { MIKMIDINoteEvent *event = (MIKMIDINoteEvent *)noteOffEventWithDestination.event; - MIKMIDIDestinationEndpoint *destination = noteOffEventWithDestination.destination; + id destination = noteOffEventWithDestination.destination; NSMutableArray *noteOffCommandsForDestination = [noteOffDestinationsToCommands objectForKey:destination] ? [noteOffDestinationsToCommands objectForKey:destination] : [NSMutableArray array]; MIKMutableMIDICommand *noteOffCommand = [[MIKMIDICommand noteOffCommandFromNoteEvent:event clock:clock] mutableCopy]; @@ -416,9 +415,8 @@ - (void)sendAllPendingNoteOffsWithMIDITimeStamp:(MIDITimeStamp)offTimeStamp } } - for (MIKMIDIDestinationEndpoint *endpoint in [[noteOffDestinationsToCommands keyEnumerator] allObjects]) { - NSArray *commands = [self modifiedMIDICommandsFromCommandsToBeScheduled:[noteOffDestinationsToCommands objectForKey:endpoint] forEndpoint:endpoint]; - [self sendCommands:commands toDestinationEndpoint:endpoint]; + for (id scheduler in [[noteOffDestinationsToCommands keyEnumerator] allObjects]) { + [self scheduleCommands:[noteOffDestinationsToCommands objectForKey:scheduler] withCommandScheduler:scheduler]; } [noteOffs removeAllObjects]; @@ -432,18 +430,12 @@ - (void)updateClockWithMusicTimeStamp:(MusicTimeStamp)musicTimeStamp tempo:(Floa [self.clock syncMusicTimeStamp:musicTimeStamp withMIDITimeStamp:midiTimeStamp tempo:tempo]; } -- (void)sendCommands:(NSArray *)commands toDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint +- (void)scheduleCommands:(NSArray *)commands withCommandScheduler:(id)scheduler { - if (!endpoint) return; - commands = [self modifiedMIDICommandsFromCommandsToBeScheduled:commands forEndpoint:endpoint]; - - NSError *error; - if (commands.count && ![[MIKMIDIDeviceManager sharedDeviceManager] sendCommands:commands toEndpoint:endpoint error:&error]) { - NSLog(@"%@: An error occurred scheduling the commands %@ for destination endpoint %@. %@", NSStringFromClass([self class]), commands, endpoint, error); - } + [scheduler scheduleMIDICommands:[self modifiedMIDICommandsFromCommandsToBeScheduled:commands forCommandScheduler:scheduler]]; } -- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forEndpoint:(MIKMIDIDestinationEndpoint *)endpoint { return commandsToBeScheduled; } +- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forCommandScheduler:(id)scheduler { return commandsToBeScheduled; } #pragma mark - Recording @@ -550,30 +542,27 @@ - (MIKMIDINoteEvent *)pendingNoteEventWithNoteNumber:(NSNumber *)noteNumber chan #pragma mark - Configuration -- (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:(MIKMIDITrack *)track +- (void)setCommandScheduler:(id)commandScheduler forTrack:(MIKMIDITrack *)track { - [self.tracksToDestinationsMap setObject:endpoint forKey:track]; + [self.tracksToDestinationsMap setObject:commandScheduler forKey:track]; [self.tracksToDefaultSynthsMap removeObjectForKey:track]; } -- (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track +- (id)commandSchedulerForTrack:(MIKMIDITrack *)track { - MIKMIDIDestinationEndpoint *result = [self.tracksToDestinationsMap objectForKey:track]; - if (!result && self.createSynthsAndEndpointsIfNeeded) { - // Create a default endpoint and synthesizer - NSString *name = [NSString stringWithFormat:@"<%@: %p> Default Endpoint %d", NSStringFromClass([self class]), self, (int)track.trackNumber]; - result = [[MIKMIDIClientDestinationEndpoint alloc] initWithName:name receivedMessagesHandler:nil]; - [self setDestinationEndpoint:result forTrack:track]; - - MIKMIDISynthesizer *synth = [MIKMIDIEndpointSynthesizer synthesizerWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)result]; - [self.tracksToDefaultSynthsMap setObject:synth forKey:track]; + id result = [self.tracksToDestinationsMap objectForKey:track]; + if (!result && self.shouldCreateSynthsIfNeeded) { + // Create a default synthesizer + result = [[MIKMIDISynthesizer alloc] init]; + [self setCommandScheduler:result forTrack:track]; + [self.tracksToDefaultSynthsMap setObject:result forKey:track]; } return result; } - (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track { - [[self destinationEndpointForTrack:track] self]; // Will force creation of a synth if one doesn't exist, but should + [[self commandSchedulerForTrack:track] self]; // Will force creation of a synth if one doesn't exist, but should return [self.tracksToDefaultSynthsMap objectForKey:track]; } @@ -588,9 +577,9 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp if (!self.isRecording && clickTrackStatus != MIKMIDISequencerClickTrackStatusAlwaysEnabled) return nil; NSMutableArray *clickEvents = [NSMutableArray array]; - MIDINoteMessage tickMessage = self.metronome.tickMessage; - MIDINoteMessage tockMessage = self.metronome.tockMessage; - MIKMIDIDestinationEndpoint *destination = self.metronomeEndpoint; + MIKMIDIMetronome *metronome = self.metronome; + MIDINoteMessage tickMessage = metronome.tickMessage; + MIDINoteMessage tockMessage = metronome.tockMessage; MIKMIDISequence *sequence = self.sequence; MusicTimeStamp playbackOffset = self.playbackOffset; @@ -614,7 +603,7 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp BOOL isTick = !((adjustedTimeStamp + timeSignature.numerator) % (timeSignature.numerator)); MIDINoteMessage clickMessage = isTick ? tickMessage : tockMessage; MIKMIDINoteEvent *noteEvent = [MIKMIDINoteEvent noteEventWithTimeStamp:clickTimeStamp - playbackOffset message:clickMessage]; - [clickEvents addObject:[MIKMIDIEventWithDestination eventWithDestination:destination event:noteEvent]]; + [clickEvents addObject:[MIKMIDIEventWithDestination eventWithDestination:metronome event:noteEvent]]; } clickTimeStamp += 4.0 / timeSignature.denominator; @@ -720,31 +709,17 @@ - (void)setProcessingTimer:(dispatch_source_t)processingTimer } } -- (MIKMIDIClientDestinationEndpoint *)metronomeEndpoint -{ - if (!_metronomeEndpoint) _metronomeEndpoint = [[MIKMIDIClientDestinationEndpoint alloc] initWithName:@"MIKMIDIClickTrackEndpoint" receivedMessagesHandler:NULL]; - return _metronomeEndpoint; -} - @synthesize metronome = _metronome; - (MIKMIDIMetronome *)metronome { #if (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0) || !TARGET_OS_IPHONE - if (!_metronome) _metronome = [[MIKMIDIMetronome alloc] initWithClientDestinationEndpoint:self.metronomeEndpoint]; + if (!_metronome) _metronome = [[MIKMIDIMetronome alloc] init]; return _metronome; #else return nil; #endif } -- (void)setMetronome:(MIKMIDIMetronome *)metronome -{ - if (_metronome != metronome) { - _metronome = metronome; - _metronomeEndpoint = (MIKMIDIClientDestinationEndpoint *)metronome.endpoint; - } -} - - (void)setTempo:(Float64)tempo { if (tempo < 0) tempo = 0; @@ -777,6 +752,19 @@ - (void)setSequence:(MIKMIDISequence *)sequence } } +#pragma mark - Deprecated + +- (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:(MIKMIDITrack *)track +{ + [self setCommandScheduler:endpoint forTrack:track]; +} + +- (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track +{ + id commandScheduler = [self commandSchedulerForTrack:track]; + return [commandScheduler isKindOfClass:[MIKMIDIDestinationEndpoint class]] ? commandScheduler : nil; +} + @end @@ -801,12 +789,12 @@ - (void)dispatchSyncToProcessingQueueAsNeeded:(void (^)())block #pragma mark - @implementation MIKMIDIEventWithDestination -+ (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event ++ (instancetype)eventWithDestination:(id)destination event:(MIKMIDIEvent *)event { return [self eventWithDestination:destination event:event representsNoteOff:NO]; } -+ (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination event:(MIKMIDIEvent *)event representsNoteOff:(BOOL)representsNoteOff ++ (instancetype)eventWithDestination:(id)destination event:(MIKMIDIEvent *)event representsNoteOff:(BOOL)representsNoteOff { MIKMIDIEventWithDestination *destinationEvent = [[self alloc] init]; destinationEvent->_event = event; @@ -820,7 +808,7 @@ + (instancetype)eventWithDestination:(MIKMIDIDestinationEndpoint *)destination e @implementation MIKMIDICommandWithDestination -+ (instancetype)commandWithDestination:(MIKMIDIDestinationEndpoint *)destination command:(MIKMIDICommand *)command ++ (instancetype)commandWithDestination:(id)destination command:(MIKMIDICommand *)command { MIKMIDICommandWithDestination *destinationCommand = [[self alloc] init]; destinationCommand->_destination = destination; diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 9da49422..9cdb4716 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -8,7 +8,8 @@ #import #import -#import +#import "MIKMIDISynthesizerInstrument.h" +#import "MIKMIDICommandScheduler.h" /** * MIKMIDISynthesizer provides a simple way to synthesize MIDI messages to @@ -23,7 +24,7 @@ * @see MIKMIDIEndpointSynthesizer * */ -@interface MIKMIDISynthesizer : NSObject +@interface MIKMIDISynthesizer : NSObject /** * Initializes an MIKMIDISynthesizer instance which uses the default diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index f1baa460..f14d8984 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -10,6 +10,15 @@ #import "MIKMIDICommand.h" #import "MIKMIDISynthesizer_SubclassMethods.h" #import "MIKMIDIErrors.h" +#import "MIKMIDIClock.h" + + +@interface MIKMIDISynthesizer () +@property (strong, nonatomic) NSMutableDictionary *scheduledCommandsByTimeStamp; +@property (strong, nonatomic) NSMutableIndexSet *scheduledCommandTimeStamps; +@property (nonatomic) dispatch_queue_t scheduledCommandQueue; +@end + @implementation MIKMIDISynthesizer @@ -318,6 +327,111 @@ + (AudioComponentDescription)appleSynthComponentDescription return instrumentcd; } +#pragma mark - MIKMIDICommandScheduler + +- (void)scheduleMIDICommands:(NSArray *)commands +{ + dispatch_queue_t queue = self.scheduledCommandQueue; + if (!queue) { + NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; + queue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + self.scheduledCommandQueue = queue; + } + + dispatch_sync(queue, ^{ + NSMutableDictionary *commandsByTimeStamp = self.scheduledCommandsByTimeStamp; + if (!commandsByTimeStamp) { + commandsByTimeStamp = [NSMutableDictionary dictionaryWithCapacity:commands.count]; + self.scheduledCommandsByTimeStamp = commandsByTimeStamp; + } + + NSMutableIndexSet *commandTimeStamps = self.scheduledCommandTimeStamps; + if (!commandTimeStamps) { + commandTimeStamps = [NSMutableIndexSet indexSet]; + self.scheduledCommandTimeStamps = commandTimeStamps; + } + + for (MIKMIDICommand *command in commands) { + MIDITimeStamp timeStamp = command.midiTimestamp; + NSNumber *timeStampNumber = @(timeStamp); + NSMutableArray *commandsAtTimeStamp = commandsByTimeStamp[timeStampNumber]; + if (!commandsAtTimeStamp) { + commandsAtTimeStamp = [NSMutableArray array]; + commandsByTimeStamp[timeStampNumber] = commandsAtTimeStamp; + [commandTimeStamps addIndex:timeStamp]; + } + + [commandsAtTimeStamp addObject:command]; + } + }); +} + +#pragma mark - Callbacks + +static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRefCon, + AudioUnitRenderActionFlags * ioActionFlags, + const AudioTimeStamp * inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList * ioData) +{ + if (*ioActionFlags & kAudioUnitRenderAction_PreRender) { + if (!(inTimeStamp->mFlags & kAudioTimeStampHostTimeValid)) return noErr; + if (!(inTimeStamp->mFlags & kAudioTimeStampSampleTimeValid)) return noErr; + + MIKMIDISynthesizer *synth = (__bridge MIKMIDISynthesizer *)inRefCon; + dispatch_queue_t queue = synth.scheduledCommandQueue; + if (!queue) return noErr; // no commands have been scheduled with this synth + + AudioUnit instrumentUnit = synth.instrumentUnit; + AudioStreamBasicDescription LPCMASBD; + UInt32 sizeOfLPCMASBD = sizeof(LPCMASBD); + OSStatus err = AudioUnitGetProperty(instrumentUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &LPCMASBD, &sizeOfLPCMASBD); + if (err) { + NSLog(@"Unable to get stream description for instrument unit %p: %i", instrumentUnit, err); + return err; + } + + NSTimeInterval timeUntilNextCallback = inNumberFrames / LPCMASBD.mSampleRate; + MIDITimeStamp toTimeStamp = inTimeStamp->mHostTime + [MIKMIDIClock midiTimeStampsPerTimeInterval:timeUntilNextCallback]; + + __block NSMutableArray *commandsToSend; + dispatch_sync(queue, ^{ + NSMutableDictionary *commandsByTimeStamp = synth.scheduledCommandsByTimeStamp; + if (!commandsByTimeStamp.count) return; + + NSMutableIndexSet *commandTimeStamps = synth.scheduledCommandTimeStamps; + commandsToSend = [NSMutableArray array]; + + NSRange range = NSMakeRange(0, toTimeStamp); + [commandTimeStamps enumerateRangesInRange:range options:0 usingBlock:^(NSRange range, BOOL *stop) { + MIDITimeStamp rangeStart = range.location; + MIDITimeStamp rangeEnd = rangeStart + range.length; + + for (MIDITimeStamp timeStamp = rangeStart; timeStamp < rangeEnd; timeStamp++) { + NSNumber *timeStampNumber = @(timeStamp); + [commandsToSend addObjectsFromArray:commandsByTimeStamp[timeStampNumber]]; + [commandsByTimeStamp removeObjectForKey:timeStampNumber]; + } + }]; + [commandTimeStamps removeIndexesInRange:range]; + }); + + for (MIKMIDICommand *command in commandsToSend) { + MIDITimeStamp sendTimeStamp = MAX(command.midiTimestamp, inTimeStamp->mHostTime); + MIDITimeStamp timeStampOffset = sendTimeStamp - inTimeStamp->mHostTime; + Float64 sampleOffset = [MIKMIDIClock secondsPerMIDITimeStamp] * timeStampOffset * LPCMASBD.mSampleRate; + + OSStatus err = MusicDeviceMIDIEvent(instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, sampleOffset); + if (err) { + NSLog(@"Unable to schedule MIDI command %@ for instrument unit %p: %i", command, instrumentUnit, err); + return err; + } + } + } + return noErr; +} + #pragma mark - Properties - (void)setGraph:(AUGraph)graph @@ -336,6 +450,22 @@ - (void)handleMIDIMessages:(NSArray *)commands } } +- (void)setInstrumentUnit:(AudioUnit)instrumentUnit +{ + if (_instrumentUnit != instrumentUnit) { + OSStatus err; + if (_instrumentUnit) { + err = AudioUnitRemoveRenderNotify(_instrumentUnit, MIKMIDISynthesizerInstrumentUnitRenderCallback, (__bridge void *)self); + if (err) NSLog(@"Unable to remove render notify from instrument unit %p: %i", _instrumentUnit, err); + } + + _instrumentUnit = instrumentUnit; + + err = AudioUnitAddRenderNotify(_instrumentUnit, MIKMIDISynthesizerInstrumentUnitRenderCallback, (__bridge void *)self); + if (err) NSLog(@"Unable to add render notify to instrument unit %p: %i", _instrumentUnit, err); + } +} + #pragma mark - Deprecated + (NSSet *)keyPathsForValuesAffectingInstrument { return [NSSet setWithObjects:@"instrumentUnit", nil]; } From a6fcbe4b94a7d007e58819f9e8f832a5bfb3f4c2 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 3 Jul 2015 11:21:56 -0500 Subject: [PATCH 196/284] Now setting instrumentUnit to NULL on dealloc so render callback is removed. --- Source/MIKMIDISynthesizer.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index f14d8984..4c7bbb34 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -39,6 +39,7 @@ - (instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componen - (void)dealloc { + self.instrumentUnit = NULL; self.graph = NULL; } @@ -461,8 +462,10 @@ - (void)setInstrumentUnit:(AudioUnit)instrumentUnit _instrumentUnit = instrumentUnit; - err = AudioUnitAddRenderNotify(_instrumentUnit, MIKMIDISynthesizerInstrumentUnitRenderCallback, (__bridge void *)self); - if (err) NSLog(@"Unable to add render notify to instrument unit %p: %i", _instrumentUnit, err); + if (_instrumentUnit) { + err = AudioUnitAddRenderNotify(_instrumentUnit, MIKMIDISynthesizerInstrumentUnitRenderCallback, (__bridge void *)self); + if (err) NSLog(@"Unable to add render notify to instrument unit %p: %i", _instrumentUnit, err); + } } } From 6c7b1e83d66968c593b2044c7aca312f38836562 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 3 Jul 2015 12:00:49 -0500 Subject: [PATCH 197/284] Minimized the time spent in the render callback utilizing our current solution for scheduling MIDI commands in MIKMIDISynthesizer. A better solution still needs to be architected to alleviate all concerns, but this at least helps. --- Source/MIKMIDIClock.m | 4 ++- Source/MIKMIDISynthesizer.m | 65 +++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 1f5f036b..77d5d873 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -284,7 +284,9 @@ + (Float64)secondsPerMIDITimeStamp + (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval { - return (1.0 / [self secondsPerMIDITimeStamp]) * timeInterval; + static Float64 midiTimeStampsPerSecond = 0; + if (!midiTimeStampsPerSecond) midiTimeStampsPerSecond = (1.0 / [self secondsPerMIDITimeStamp]); + return midiTimeStampsPerSecond * timeInterval; } #pragma mark - Deprecated Methods diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 4c7bbb34..4b8d8454 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -14,9 +14,11 @@ @interface MIKMIDISynthesizer () -@property (strong, nonatomic) NSMutableDictionary *scheduledCommandsByTimeStamp; -@property (strong, nonatomic) NSMutableIndexSet *scheduledCommandTimeStamps; -@property (nonatomic) dispatch_queue_t scheduledCommandQueue; +{ + NSMutableDictionary *_scheduledCommandsByTimeStamp; + NSMutableIndexSet *_scheduledCommandTimeStamps; + dispatch_queue_t _scheduledCommandQueue; +} @end @@ -332,27 +334,27 @@ + (AudioComponentDescription)appleSynthComponentDescription - (void)scheduleMIDICommands:(NSArray *)commands { - dispatch_queue_t queue = self.scheduledCommandQueue; + dispatch_queue_t queue = _scheduledCommandQueue; if (!queue) { NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - queue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); - self.scheduledCommandQueue = queue; + queue = dispatch_queue_create(queueLabel.UTF8String, dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH)); + _scheduledCommandQueue = queue; } - dispatch_sync(queue, ^{ - NSMutableDictionary *commandsByTimeStamp = self.scheduledCommandsByTimeStamp; - if (!commandsByTimeStamp) { - commandsByTimeStamp = [NSMutableDictionary dictionaryWithCapacity:commands.count]; - self.scheduledCommandsByTimeStamp = commandsByTimeStamp; - } + for (MIKMIDICommand *command in commands) { + dispatch_sync(queue, ^{ + NSMutableDictionary *commandsByTimeStamp = _scheduledCommandsByTimeStamp; + if (!commandsByTimeStamp) { + commandsByTimeStamp = [NSMutableDictionary dictionaryWithCapacity:commands.count]; + _scheduledCommandsByTimeStamp = commandsByTimeStamp; + } - NSMutableIndexSet *commandTimeStamps = self.scheduledCommandTimeStamps; - if (!commandTimeStamps) { - commandTimeStamps = [NSMutableIndexSet indexSet]; - self.scheduledCommandTimeStamps = commandTimeStamps; - } + NSMutableIndexSet *commandTimeStamps = _scheduledCommandTimeStamps; + if (!commandTimeStamps) { + commandTimeStamps = [NSMutableIndexSet indexSet]; + _scheduledCommandTimeStamps = commandTimeStamps; + } - for (MIKMIDICommand *command in commands) { MIDITimeStamp timeStamp = command.midiTimestamp; NSNumber *timeStampNumber = @(timeStamp); NSMutableArray *commandsAtTimeStamp = commandsByTimeStamp[timeStampNumber]; @@ -363,8 +365,8 @@ - (void)scheduleMIDICommands:(NSArray *)commands } [commandsAtTimeStamp addObject:command]; - } - }); + }); + } } #pragma mark - Callbacks @@ -381,7 +383,7 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef if (!(inTimeStamp->mFlags & kAudioTimeStampSampleTimeValid)) return noErr; MIKMIDISynthesizer *synth = (__bridge MIKMIDISynthesizer *)inRefCon; - dispatch_queue_t queue = synth.scheduledCommandQueue; + dispatch_queue_t queue = synth->_scheduledCommandQueue; if (!queue) return noErr; // no commands have been scheduled with this synth AudioUnit instrumentUnit = synth.instrumentUnit; @@ -393,15 +395,25 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef return err; } + static NSTimeInterval lastTimeUntilNextCallback = 0; + static MIDITimeStamp lastMIDITimeStampsUntilNextCallback = 0; NSTimeInterval timeUntilNextCallback = inNumberFrames / LPCMASBD.mSampleRate; - MIDITimeStamp toTimeStamp = inTimeStamp->mHostTime + [MIKMIDIClock midiTimeStampsPerTimeInterval:timeUntilNextCallback]; + MIDITimeStamp midiTimeStampsUntilNextCallback = lastMIDITimeStampsUntilNextCallback; + + if (lastTimeUntilNextCallback != timeUntilNextCallback) { + midiTimeStampsUntilNextCallback = [MIKMIDIClock midiTimeStampsPerTimeInterval:timeUntilNextCallback]; + lastTimeUntilNextCallback = timeUntilNextCallback; + lastMIDITimeStampsUntilNextCallback = midiTimeStampsUntilNextCallback; + } + + MIDITimeStamp toTimeStamp = inTimeStamp->mHostTime + midiTimeStampsUntilNextCallback; __block NSMutableArray *commandsToSend; dispatch_sync(queue, ^{ - NSMutableDictionary *commandsByTimeStamp = synth.scheduledCommandsByTimeStamp; + NSMutableDictionary *commandsByTimeStamp = synth->_scheduledCommandsByTimeStamp; if (!commandsByTimeStamp.count) return; - NSMutableIndexSet *commandTimeStamps = synth.scheduledCommandTimeStamps; + NSMutableIndexSet *commandTimeStamps = synth->_scheduledCommandTimeStamps; commandsToSend = [NSMutableArray array]; NSRange range = NSMakeRange(0, toTimeStamp); @@ -418,10 +430,13 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef [commandTimeStamps removeIndexesInRange:range]; }); + static NSTimeInterval secondsPerMIDITimeStamp = 0; + if (!secondsPerMIDITimeStamp) secondsPerMIDITimeStamp = [MIKMIDIClock secondsPerMIDITimeStamp]; + for (MIKMIDICommand *command in commandsToSend) { MIDITimeStamp sendTimeStamp = MAX(command.midiTimestamp, inTimeStamp->mHostTime); MIDITimeStamp timeStampOffset = sendTimeStamp - inTimeStamp->mHostTime; - Float64 sampleOffset = [MIKMIDIClock secondsPerMIDITimeStamp] * timeStampOffset * LPCMASBD.mSampleRate; + Float64 sampleOffset = secondsPerMIDITimeStamp * timeStampOffset * LPCMASBD.mSampleRate; OSStatus err = MusicDeviceMIDIEvent(instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, sampleOffset); if (err) { From e8af044d88780304d14f0f6ba06b0368af963350 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 3 Jul 2015 12:05:58 -0500 Subject: [PATCH 198/284] Removed another method call from the render callback. --- Source/MIKMIDISynthesizer.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 4b8d8454..046c7f07 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -434,7 +434,8 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef if (!secondsPerMIDITimeStamp) secondsPerMIDITimeStamp = [MIKMIDIClock secondsPerMIDITimeStamp]; for (MIKMIDICommand *command in commandsToSend) { - MIDITimeStamp sendTimeStamp = MAX(command.midiTimestamp, inTimeStamp->mHostTime); + MIDITimeStamp sendTimeStamp = command.midiTimestamp; + if (sendTimeStamp < inTimeStamp->mHostTime) sendTimeStamp = inTimeStamp->mHostTime; MIDITimeStamp timeStampOffset = sendTimeStamp - inTimeStamp->mHostTime; Float64 sampleOffset = secondsPerMIDITimeStamp * timeStampOffset * LPCMASBD.mSampleRate; From 2a0d0842e44383ca4d5e6380f9bc5fc3a8444766 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 3 Jul 2015 13:14:58 -0500 Subject: [PATCH 199/284] Now using quality of service API for MIKMIDIClock and MIKMIDISequencer's queues. --- Source/MIKMIDIClock.m | 2 +- Source/MIKMIDISequencer.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 77d5d873..8f103da1 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -66,7 +66,7 @@ - (instancetype)initWithQueue:(BOOL)createQueue if (self = [super init]) { if (createQueue) { NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH)); } } return self; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index fb00a3e6..cc4ef953 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -159,7 +159,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi if (self.isPlaying) [self stop]; NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - dispatch_queue_t queue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL); + dispatch_queue_t queue = dispatch_queue_create(queueLabel.UTF8String, dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH)); dispatch_queue_set_specific(queue, &_processingQueueKey, &_processingQueueContext, NULL); self.processingQueue = queue; From fba696cbe76df61d93c42aabbaec2ba3d8acf57f Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 3 Jul 2015 14:47:42 -0500 Subject: [PATCH 200/284] Only use the QoS API when available. --- Source/MIKMIDIClock.m | 10 +++++++++- Source/MIKMIDISequencer.m | 10 +++++++++- Source/MIKMIDISynthesizer.m | 10 +++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 8f103da1..5da58cd3 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -66,7 +66,15 @@ - (instancetype)initWithQueue:(BOOL)createQueue if (self = [super init]) { if (createQueue) { NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH)); + dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; + +#if defined (__MAC_10_10) || defined (__IPHONE_8_0) + if (dispatch_queue_attr_make_with_qos_class != NULL) { + attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH); + } +#endif + + self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, attr); } } return self; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index cc4ef953..ed02c142 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -159,7 +159,15 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi if (self.isPlaying) [self stop]; NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - dispatch_queue_t queue = dispatch_queue_create(queueLabel.UTF8String, dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH)); + dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; + +#if defined (__MAC_10_10) || defined (__IPHONE_8_0) + if (dispatch_queue_attr_make_with_qos_class != NULL) { + attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH); + } +#endif + + dispatch_queue_t queue = dispatch_queue_create(queueLabel.UTF8String, attr); dispatch_queue_set_specific(queue, &_processingQueueKey, &_processingQueueContext, NULL); self.processingQueue = queue; diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 046c7f07..3b37fd5a 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -337,7 +337,15 @@ - (void)scheduleMIDICommands:(NSArray *)commands dispatch_queue_t queue = _scheduledCommandQueue; if (!queue) { NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; - queue = dispatch_queue_create(queueLabel.UTF8String, dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH)); + dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; + +#if defined (__MAC_10_10) || defined (__IPHONE_8_0) + if (dispatch_queue_attr_make_with_qos_class != NULL) { + attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH); + } +#endif + + queue = dispatch_queue_create(queueLabel.UTF8String, attr); _scheduledCommandQueue = queue; } From f86d7b487173fd55baf7a31a8900139f406781d2 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 5 Jul 2015 21:24:38 -0600 Subject: [PATCH 201/284] Added MIKMIDIPort_SubclassMethods.h to MIKMIDI framework targets. --- .../MIDI Files Testbed.xcodeproj/project.pbxproj | 2 -- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index 5d98745b..93a37cc1 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -372,7 +372,6 @@ 9DB2A622192D184D0047A3EB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -410,7 +409,6 @@ 9DB2A623192D184D0047A3EB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 1a9ef4e8..6ba16f4d 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -134,6 +134,8 @@ 9D877D851A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877D831A6237CC001BA997 /* MIKMIDIClientSourceEndpoint.m */; }; 9D877DFC1A670261001BA997 /* MIKMIDIProgramChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D877DFA1A670261001BA997 /* MIKMIDIProgramChangeCommand.m */; }; 9D877DFD1A6706E6001BA997 /* MIKMIDIProgramChangeCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D877DF91A670261001BA997 /* MIKMIDIProgramChangeCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D9FBCCB1B4A29A5009A7936 /* MIKMIDIPort_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5717A713A100BEE89F /* MIKMIDIPort_SubclassMethods.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 9D9FBCCC1B4A29A5009A7936 /* MIKMIDIPort_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5717A713A100BEE89F /* MIKMIDIPort_SubclassMethods.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9DAE7D8D19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DAE7D8B19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.m */; }; 9DAE7D8E19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DAE7D8C19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DAF8B1F1A7AFF5900F46528 /* MIKMIDIDeviceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D74EF3D17A713A100BEE89F /* MIKMIDIDeviceManager.m */; }; @@ -908,6 +910,7 @@ 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */, 9DAE7D8E19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h in Headers */, 9D74EF9417A713A100BEE89F /* NSUIApplication+MIKMIDI.h in Headers */, + 9D9FBCCB1B4A29A5009A7936 /* MIKMIDIPort_SubclassMethods.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -962,6 +965,7 @@ 9DAF8B7E1A7B00B100F46528 /* MIKMIDIMetronome.h in Headers */, 9DAF8B831A7B00BB00F46528 /* MIKMIDIPrivateUtilities.h in Headers */, 9DAF8B591A7B007300F46528 /* MIKMIDIInputPort.h in Headers */, + 9D9FBCCC1B4A29A5009A7936 /* MIKMIDIPort_SubclassMethods.h in Headers */, 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */, 9DAF8B561A7B007300F46528 /* MIKMIDIDevice.h in Headers */, 9DAF8B761A7B00A700F46528 /* MIKMIDIMetaSequenceEvent.h in Headers */, From a71d9b2c0810047f82b369e573a4cca08bb8696c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 5 Jul 2015 21:25:18 -0600 Subject: [PATCH 202/284] Fixed (accidental) incorrect usage of OS X-only '-isEqualTo:' instead of '-isEqual:'. --- Source/MIKMIDIMappingManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index ccf5c6a6..e088ad39 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -262,7 +262,7 @@ - (NSURL *)fileURLForMapping:(MIKMIDIMapping *)mapping shouldBeUnique:(BOOL)uniq unsigned long numberSuffix = 0; while ([fm fileExistsAtPath:[result path]]) { MIKMIDIMapping *existingMapping = [[MIKMIDIMapping alloc] initWithFileAtURL:result error:NULL]; - if ([existingMapping isEqualTo:mapping]) break; + if ([existingMapping isEqual:mapping]) break; if (numberSuffix > 1000) return nil; // Don't go crazy NSString *name = [mapping.name stringByAppendingFormat:@" %lu", ++numberSuffix]; From 8fd4fd5492b896c6f204c1fe195ff99933f97b08 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 5 Jul 2015 21:25:42 -0600 Subject: [PATCH 203/284] Fixed build error on iOS. --- Source/MIKMIDISynthesizer.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 3b37fd5a..6fad0d1f 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -310,10 +310,11 @@ - (BOOL)isUsingAppleSynth - (BOOL)isUsingAppleDLSSynth { + AudioComponentDescription appleCD = [[self class] appleSynthComponentDescription]; AudioComponentDescription description = self.componentDescription; - if (description.componentManufacturer != kAudioUnitManufacturer_Apple) return NO; - if (description.componentType != kAudioUnitType_MusicDevice) return NO; - if (description.componentSubType != kAudioUnitSubType_DLSSynth) return NO; + if (description.componentManufacturer != appleCD.componentManufacturer) return NO; + if (description.componentType != appleCD.componentType) return NO; + if (description.componentSubType != appleCD.componentSubType) return NO; return YES; } @@ -340,7 +341,7 @@ - (void)scheduleMIDICommands:(NSArray *)commands dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; #if defined (__MAC_10_10) || defined (__IPHONE_8_0) - if (dispatch_queue_attr_make_with_qos_class != NULL) { + if (&dispatch_queue_attr_make_with_qos_class != NULL) { attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH); } #endif From 03aa2294de7e2153ca8615b16a1c1a0ea23f7682 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 5 Jul 2015 21:26:08 -0600 Subject: [PATCH 204/284] Fixed compiler warnings on check for dispatch_queue_attr_make_with_qos_class function availability. --- Framework/MIKMIDI Tests/MIKMIDISequenceTests.m | 5 +++++ Source/MIKMIDIClock.m | 2 +- Source/MIKMIDISequencer.m | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index ec7c0580..17ab5cfc 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -117,6 +117,11 @@ - (void)testLength XCTAssertTrue([self.receivedNotificationKeyPaths containsObject:@"durationInSeconds"], @"KVO notification for durationInSeconds failed after removing longest child track."); } +- (void)testSetTimeSignature +{ + [self.sequence setTimeSignature:MIKMIDITimeSignatureMake(2, 4) atTimeStamp:0]; +} + #pragma mark - KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 5da58cd3..7f14eddb 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -69,7 +69,7 @@ - (instancetype)initWithQueue:(BOOL)createQueue dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; #if defined (__MAC_10_10) || defined (__IPHONE_8_0) - if (dispatch_queue_attr_make_with_qos_class != NULL) { + if (&dispatch_queue_attr_make_with_qos_class != NULL) { attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, DISPATCH_QUEUE_PRIORITY_HIGH); } #endif diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index ed02c142..a58a3c6c 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -162,7 +162,7 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; #if defined (__MAC_10_10) || defined (__IPHONE_8_0) - if (dispatch_queue_attr_make_with_qos_class != NULL) { + if (&dispatch_queue_attr_make_with_qos_class != NULL) { attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, DISPATCH_QUEUE_PRIORITY_HIGH); } #endif From c3c6c8eacda46875fde56c3e41cdb2f4abb1bc9b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sun, 5 Jul 2015 21:28:28 -0600 Subject: [PATCH 205/284] Fixed module build errors on iOS. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 2 ++ Framework/module.modulemap | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 6ba16f4d..07e3c439 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -261,6 +261,7 @@ 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */; }; 9DCDDB501AB2363C00F8347E /* Parallax-Loader.mid in Resources */ = {isa = PBXBuildFile; fileRef = 9DCDDB4F1AB2363C00F8347E /* Parallax-Loader.mid */; }; 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */; }; + 9DD48D481B4A2D4A00A50942 /* MIKMIDICommandScheduler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8308F6311B46C482004307AD /* MIKMIDICommandScheduler.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DE259E519A7B4F800DA93E9 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E419A7B4F800DA93E9 /* AudioUnit.framework */; }; 9DE259E619A7B50100DA93E9 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D74EF2D17A7133900BEE89F /* CoreMIDI.framework */; }; 9DE259E819A7B50600DA93E9 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DE259E719A7B50600DA93E9 /* AudioToolbox.framework */; }; @@ -983,6 +984,7 @@ 9DAF8B821A7B00BB00F46528 /* MIKMIDIClock.h in Headers */, 9DAF8B6B1A7B00A700F46528 /* MIKMIDISequence.h in Headers */, 9DAF8B511A7B004A00F46528 /* MIKMIDI.h in Headers */, + 9DD48D481B4A2D4A00A50942 /* MIKMIDICommandScheduler.h in Headers */, 9DAF8B811A7B00BB00F46528 /* MIKMIDICommandThrottler.h in Headers */, 9DAF8B6A1A7B009100F46528 /* MIKMIDIMappingManager.h in Headers */, 9DAF8B741A7B00A700F46528 /* MIKMIDIMetaLyricEvent.h in Headers */, diff --git a/Framework/module.modulemap b/Framework/module.modulemap index 527ef514..0dd65af2 100644 --- a/Framework/module.modulemap +++ b/Framework/module.modulemap @@ -2,7 +2,6 @@ framework module MIKMIDI { umbrella header "MIKMIDI.h" private header "MIKMIDIPort_SubclassMethods.h" - private header "MIKMIDIPrivateUtilities.h" private header "MIKMIDISynthesizer_SubclassMethods.h" export * From 248c31213658ca38f8df75b75af1470f694d49f4 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 11 Aug 2015 16:25:06 -0500 Subject: [PATCH 206/284] Fixed no-ARC errors so they show the correct file name rather than MIKMIDIMappingManager.m. --- Source/MIKMIDIClock.m | 2 +- Source/MIKMIDIEndpointSynthesizer.m | 2 +- Source/MIKMIDIEvent.m | 2 +- Source/MIKMIDIEventIterator.m | 2 +- Source/MIKMIDIMetaCuePointEvent.m | 2 +- Source/MIKMIDIMetaEvent.m | 2 +- Source/MIKMIDIMetaInstrumentNameEvent.m | 2 +- Source/MIKMIDIMetaKeySignatureEvent.m | 2 +- Source/MIKMIDIMetaLyricEvent.m | 2 +- Source/MIKMIDIMetaMarkerTextEvent.m | 2 +- Source/MIKMIDIMetaSequenceEvent.m | 2 +- Source/MIKMIDIMetaTextEvent.m | 2 +- Source/MIKMIDIMetaTimeSignatureEvent.m | 2 +- Source/MIKMIDIMetaTrackSequenceNameEvent.m | 2 +- Source/MIKMIDIMetronome.m | 2 +- Source/MIKMIDINoteEvent.m | 2 +- Source/MIKMIDIPlayer.m | 2 +- Source/MIKMIDISequence.m | 2 +- Source/MIKMIDISequencer.m | 2 +- Source/MIKMIDITempoEvent.m | 2 +- Source/MIKMIDITrack.m | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 7f14eddb..1c09f062 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -11,7 +11,7 @@ #import #if !__has_feature(objc_arc) -#error MIKMIDIClock.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIClock.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIClock.m in the Build Phases for this target #endif diff --git a/Source/MIKMIDIEndpointSynthesizer.m b/Source/MIKMIDIEndpointSynthesizer.m index 05ca5e3f..3631f167 100644 --- a/Source/MIKMIDIEndpointSynthesizer.m +++ b/Source/MIKMIDIEndpointSynthesizer.m @@ -12,7 +12,7 @@ #import "MIKMIDIClientDestinationEndpoint.h" #if !__has_feature(objc_arc) -#error MIKMIDIEndpointSynthesizer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIEndpointSynthesizer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIEndpointSynthesizer.m in the Build Phases for this target #endif @interface MIKMIDIEndpointSynthesizer () diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 01d05b45..37767edf 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIEvent.m in the Build Phases for this target #endif static NSMutableSet *registeredMIKMIDIEventSubclasses; diff --git a/Source/MIKMIDIEventIterator.m b/Source/MIKMIDIEventIterator.m index d48b3ed1..a6b0050d 100644 --- a/Source/MIKMIDIEventIterator.m +++ b/Source/MIKMIDIEventIterator.m @@ -11,7 +11,7 @@ #import "MIKMIDIEvent.h" #if !__has_feature(objc_arc) -#error MIKMIDIEventIterator.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIEventIterator.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIEventIterator.m in the Build Phases for this target #endif @interface MIKMIDIEventIterator () diff --git a/Source/MIKMIDIMetaCuePointEvent.m b/Source/MIKMIDIMetaCuePointEvent.m index 501a288a..418d1bd5 100644 --- a/Source/MIKMIDIMetaCuePointEvent.m +++ b/Source/MIKMIDIMetaCuePointEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaCuePointEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaCuePointEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaCuePointEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaCuePointEvent diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index c66e7baa..0e96286d 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaEvent diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.m b/Source/MIKMIDIMetaInstrumentNameEvent.m index fe8f22ce..fdf1575f 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.m +++ b/Source/MIKMIDIMetaInstrumentNameEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaInstrumentNameEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaInstrumentNameEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaInstrumentNameEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaInstrumentNameEvent diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index 6c82961a..00303e7d 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaKeySignatureEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaKeySignatureEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaKeySignatureEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaKeySignatureEvent diff --git a/Source/MIKMIDIMetaLyricEvent.m b/Source/MIKMIDIMetaLyricEvent.m index 5f20a1bc..18560f65 100644 --- a/Source/MIKMIDIMetaLyricEvent.m +++ b/Source/MIKMIDIMetaLyricEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaLyricEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaLyricEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaLyricEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaLyricEvent diff --git a/Source/MIKMIDIMetaMarkerTextEvent.m b/Source/MIKMIDIMetaMarkerTextEvent.m index f8522189..75664c86 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.m +++ b/Source/MIKMIDIMetaMarkerTextEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaMarkerTextEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaMarkerTextEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaMarkerTextEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaMarkerTextEvent diff --git a/Source/MIKMIDIMetaSequenceEvent.m b/Source/MIKMIDIMetaSequenceEvent.m index c0c5b73f..e10ed22d 100644 --- a/Source/MIKMIDIMetaSequenceEvent.m +++ b/Source/MIKMIDIMetaSequenceEvent.m @@ -9,7 +9,7 @@ #import "MIKMIDIMetaSequenceEvent.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaSequenceEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaSequenceEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaSequenceEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaSequenceEvent diff --git a/Source/MIKMIDIMetaTextEvent.m b/Source/MIKMIDIMetaTextEvent.m index c62e6cd5..2ff5d018 100644 --- a/Source/MIKMIDIMetaTextEvent.m +++ b/Source/MIKMIDIMetaTextEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaTextEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaTextEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaTextEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaTextEvent diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index 85e87b19..c5561bae 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaTimeSignatureEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaTimeSignatureEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaTimeSignatureEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaTimeSignatureEvent diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.m b/Source/MIKMIDIMetaTrackSequenceNameEvent.m index fbc30fd9..e483a225 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.m +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetaTrackSequenceNameEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetaTrackSequenceNameEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetaTrackSequenceNameEvent.m in the Build Phases for this target #endif @implementation MIKMIDIMetaTrackSequenceNameEvent diff --git a/Source/MIKMIDIMetronome.m b/Source/MIKMIDIMetronome.m index 7a40557f..7f0c1362 100644 --- a/Source/MIKMIDIMetronome.m +++ b/Source/MIKMIDIMetronome.m @@ -11,7 +11,7 @@ #import "MIKMIDINoteEvent.h" #if !__has_feature(objc_arc) -#error MIKMIDIMetronome.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIMetronome.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMetronome.m in the Build Phases for this target #endif @implementation MIKMIDIMetronome diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index acee0f5d..1ecdce21 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -12,7 +12,7 @@ #import "MIKMIDIClock.h" #if !__has_feature(objc_arc) -#error MIKMIDINoteEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDINoteEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDINoteEvent.m in the Build Phases for this target #endif @implementation MIKMIDINoteEvent diff --git a/Source/MIKMIDIPlayer.m b/Source/MIKMIDIPlayer.m index 475f7102..e9ef4735 100644 --- a/Source/MIKMIDIPlayer.m +++ b/Source/MIKMIDIPlayer.m @@ -15,7 +15,7 @@ #import "MIKMIDIClientDestinationEndpoint.h" #if !__has_feature(objc_arc) -#error MIKMIDIPlayer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDIPlayer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIPlayer.m in the Build Phases for this target #endif @interface MIKMIDIPlayer () diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 6bd6e6dd..09642772 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -17,7 +17,7 @@ #import "MIKMIDISequencer+MIKMIDIPrivate.h" #if !__has_feature(objc_arc) -#error MIKMIDISequence.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDISequence.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDISequence.m in the Build Phases for this target #endif void * MIKMIDISequenceKVOContext = &MIKMIDISequenceKVOContext; diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index a58a3c6c..36588ef6 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -28,7 +28,7 @@ #if !__has_feature(objc_arc) -#error MIKMIDISequencer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDISequencer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDISequencer.m in the Build Phases for this target #endif #define kDefaultTempo 120 diff --git a/Source/MIKMIDITempoEvent.m b/Source/MIKMIDITempoEvent.m index a3f94058..4dfe51dc 100644 --- a/Source/MIKMIDITempoEvent.m +++ b/Source/MIKMIDITempoEvent.m @@ -11,7 +11,7 @@ #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) -#error MIKMIDITempoEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDITempoEvent.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDITempoEvent.m in the Build Phases for this target #endif @implementation MIKMIDITempoEvent diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 74c38349..18afde10 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -18,7 +18,7 @@ #if !__has_feature(objc_arc) -#error MIKMIDITrack.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target +#error MIKMIDITrack.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDITrack.m in the Build Phases for this target #endif @interface MIKMIDITrack () From 6f1ef8fc61cefae3bc9386434e5bb0800910dc8a Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 11 Aug 2015 17:08:53 -0500 Subject: [PATCH 207/284] Added the ability for MIKMIDIMappingManager subclasses to determine file names for user mappings. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 4 +++ Framework/module.modulemap | 5 ++++ Source/MIKMIDIMappingManager.m | 5 +++- .../MIKMIDIMappingManager_SubclassMethods.h | 27 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 Source/MIKMIDIMappingManager_SubclassMethods.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 07e3c439..d57dd563 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; + 83C850C91B7AA47C001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */; }; 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -345,6 +346,7 @@ 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; + 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingManager_SubclassMethods.h; sourceTree = ""; }; 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequence+MIKMIDIPrivate.h"; sourceTree = ""; }; 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; @@ -785,6 +787,7 @@ 9D74EF4817A713A100BEE89F /* MIKMIDIMappingGenerator.h */, 9D74EF4917A713A100BEE89F /* MIKMIDIMappingGenerator.m */, 9D74EF4A17A713A100BEE89F /* MIKMIDIMappingManager.h */, + 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */, 9D74EF4B17A713A100BEE89F /* MIKMIDIMappingManager.m */, ); name = Mapping; @@ -900,6 +903,7 @@ 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, 8308F6321B46C482004307AD /* MIKMIDICommandScheduler.h in Headers */, + 83C850C91B7AA47C001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */, 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, diff --git a/Framework/module.modulemap b/Framework/module.modulemap index 0dd65af2..8437bfbe 100644 --- a/Framework/module.modulemap +++ b/Framework/module.modulemap @@ -21,4 +21,9 @@ framework module MIKMIDI { header "MIKMIDISynthesizer_SubclassMethods.h" export * } + + explicit module MIKMIDIMappingManager { + header "MIKMIDIMappingManager_SubclassMethods.h" + export * + } } diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index e088ad39..cd90ed1d 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -15,6 +15,7 @@ #import "MIKMIDIMappingManager.h" #import "MIKMIDIMapping.h" #import "MIKMIDIErrors.h" +#import "MIKMIDIMappingManager_SubclassMethods.h" #if !__has_feature(objc_arc) #error MIKMIDIMappingManager.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target @@ -253,7 +254,7 @@ - (void)loadBundledMappings - (NSURL *)fileURLForMapping:(MIKMIDIMapping *)mapping shouldBeUnique:(BOOL)unique { NSURL *mappingsFolder = [self userMappingsFolder]; - NSString *filename = [mapping.name stringByAppendingPathExtension:kMIKMIDIMappingFileExtension]; + NSString *filename = [[self fileNameForMapping:mapping] stringByAppendingPathExtension:kMIKMIDIMappingFileExtension]; NSURL *result = [mappingsFolder URLByAppendingPathComponent:filename]; @@ -274,6 +275,8 @@ - (NSURL *)fileURLForMapping:(MIKMIDIMapping *)mapping shouldBeUnique:(BOOL)uniq return result; } +- (NSString *)fileNameForMapping:(MIKMIDIMapping *)mapping { return mapping.name; } + #pragma mark - Properties + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key diff --git a/Source/MIKMIDIMappingManager_SubclassMethods.h b/Source/MIKMIDIMappingManager_SubclassMethods.h new file mode 100644 index 00000000..9e8fe6da --- /dev/null +++ b/Source/MIKMIDIMappingManager_SubclassMethods.h @@ -0,0 +1,27 @@ +// +// MIKMIDIMappingManager_SubclassMethods.h +// MIKMIDI +// +// Created by Chris Flesner on 8/11/15. +// Copyright (c) 2015 Mixed In Key. All rights reserved. +// + + +#import "MIKMIDIMappingManager.h" + +@class MIKMIDIMapping; + + +@interface MIKMIDIMappingManager () + +/** + * Used to determine the file name for a user mapping. This file name does *not* include the + * file extension, which will be added by the caller. + * + * @param mapping The mapping a file name is needed for. + * + * @return A file name for the mapping. + */ +- (NSString *)fileNameForMapping:(MIKMIDIMapping *)mapping; + +@end From 9d7475d73e9d4b956649665f723329657a93b7c5 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 19 Aug 2015 17:25:23 -0600 Subject: [PATCH 208/284] Fixed bug where -[MIKMIDIDevice/MIKMIDIEntity isEqual:] would falsely return true when comparing virtual devices/entities with the same first source endpoint even when the other endpoing differed. --- Source/MIKMIDIDevice.m | 6 ++++++ Source/MIKMIDIEntity.m | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/Source/MIKMIDIDevice.m b/Source/MIKMIDIDevice.m index 47beae92..956bc253 100644 --- a/Source/MIKMIDIDevice.m +++ b/Source/MIKMIDIDevice.m @@ -70,6 +70,12 @@ - (instancetype)initWithVirtualEndpoints:(NSArray *)endpoints; return self; } +- (BOOL)isEqual:(id)object +{ + if (![super isEqual:object]) return NO; + return [self.entities isEqualToArray:[(MIKMIDIDevice *)object entities]]; +} + #pragma mark - Public - (NSString *)description diff --git a/Source/MIKMIDIEntity.m b/Source/MIKMIDIEntity.m index 673c468e..26917251 100644 --- a/Source/MIKMIDIEntity.m +++ b/Source/MIKMIDIEntity.m @@ -85,6 +85,13 @@ - (instancetype)initWithVirtualEndpoints:(NSArray *)endpoints; return self; } +- (BOOL)isEqual:(id)object +{ + if (![super isEqual:object]) return NO; + return [self.sources isEqualToArray:[(MIKMIDIEntity *)object sources]] && + [self.destinations isEqualToArray:[(MIKMIDIEntity *)object destinations]]; +} + - (NSString *)description { NSMutableString *result = [NSMutableString stringWithFormat:@"%@:\r Sources: {\r", [super description]]; From cafd2ac3208602c6a49f5d37dd27dfe2e836a9d5 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 21 Aug 2015 11:54:18 -0600 Subject: [PATCH 209/284] Improved tendency for MIKMIDIMappingGenerator to detect absolute knob turned through a long range as a turntable. --- Source/MIKMIDIMappingGenerator.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index a53a887e..a3647a96 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -292,6 +292,14 @@ - (BOOL)fillInTurntableKnobMappingItem:(MIKMIDIMappingItem **)mappingItem fromMe MIKMIDIChannelVoiceCommand *firstMessage = [messages objectAtIndex:0]; + NSInteger minValue, maxValue = firstMessage.value; + for (MIKMIDIChannelVoiceCommand *command in messages) { + minValue = MIN(command.value, minValue); + maxValue = MAX(command.value, maxValue); + } + NSInteger range = maxValue - minValue; + if (range > 25) return NO; // Probably not a turntable, more likely an absolute knob + MIKMIDIMappingItem *result = *mappingItem; result.interactionType = MIKMIDIResponderTypeTurntableKnob; result.channel = firstMessage.channel; From 108070dfa1beaeb5733bc34c906be956a5e7f704 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 21 Aug 2015 15:16:32 -0600 Subject: [PATCH 210/284] Lowered threshold for number of messages to be considered a turntable. --- Source/MIKMIDIMappingGenerator.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index a3647a96..3967bb53 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -470,7 +470,7 @@ - (void)finishMappingItem:(MIKMIDIMappingItem *)mappingItemOrNil error:(NSError - (NSUInteger)defaultMinimumNumberOfMessagesRequiredForResponderType:(MIKMIDIResponderType)responderType { - if (responderType & MIKMIDIResponderTypeTurntableKnob) return 50; + if (responderType & MIKMIDIResponderTypeTurntableKnob) return 40; if (responderType & MIKMIDIResponderTypeAbsoluteSliderOrKnob) return 5; return 3; } From 16890185cf223feaa4587ff275bcc8c33884c1f1 Mon Sep 17 00:00:00 2001 From: Patrick Machielse Date: Tue, 25 Aug 2015 14:19:02 +0200 Subject: [PATCH 211/284] Fixed warning in MIKMIDIMappingGenerator. --- Source/MIKMIDIMappingGenerator.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 3967bb53..2db5a5e0 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -292,7 +292,7 @@ - (BOOL)fillInTurntableKnobMappingItem:(MIKMIDIMappingItem **)mappingItem fromMe MIKMIDIChannelVoiceCommand *firstMessage = [messages objectAtIndex:0]; - NSInteger minValue, maxValue = firstMessage.value; + NSInteger minValue = firstMessage.value, maxValue = firstMessage.value; for (MIKMIDIChannelVoiceCommand *command in messages) { minValue = MIN(command.value, minValue); maxValue = MAX(command.value, maxValue); From 95b36346aa6faf93a70a0cd77f693ed8c827e0e9 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 26 Aug 2015 11:07:24 -0600 Subject: [PATCH 212/284] Better heuristic for excluding fully-turned absolute knobs from turntable detection code (last attempt was prone to false negatives with some turntables). --- Source/MIKMIDIMappingGenerator.m | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 2db5a5e0..b13d7c4c 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -290,21 +290,25 @@ - (BOOL)fillInTurntableKnobMappingItem:(MIKMIDIMappingItem **)mappingItem fromMe if ([messages count] < [self defaultMinimumNumberOfMessagesRequiredForResponderType:MIKMIDIResponderTypeTurntableKnob]) return NO; - MIKMIDIChannelVoiceCommand *firstMessage = [messages objectAtIndex:0]; - - NSInteger minValue = firstMessage.value, maxValue = firstMessage.value; - for (MIKMIDIChannelVoiceCommand *command in messages) { - minValue = MIN(command.value, minValue); - maxValue = MAX(command.value, maxValue); + MIKMIDIControlChangeCommand *firstMessage = [messages firstObject]; + + float avgChangePerMessage = 0; + int maxChangePerMessage = 0; + int lastValue = (int)[firstMessage controllerValue]; + for (MIKMIDIControlChangeCommand *command in messages) { + int change = (int)command.value - lastValue; + avgChangePerMessage += (float)change / (float)[messages count]; + maxChangePerMessage = MAX(abs(change), maxChangePerMessage); + lastValue = (int)command.value; } - NSInteger range = maxValue - minValue; - if (range > 25) return NO; // Probably not a turntable, more likely an absolute knob + + if (fabsf(avgChangePerMessage) > 0.9 && maxChangePerMessage < 63) return NO; // Probably not a turntable, more likely an absolute knob MIKMIDIMappingItem *result = *mappingItem; result.interactionType = MIKMIDIResponderTypeTurntableKnob; result.channel = firstMessage.channel; result.controlNumber = MIKMIDIControlNumberFromCommand(firstMessage); - result.flipped = ([(MIKMIDIChannelVoiceCommand *)[messages lastObject] value] < 64); + result.flipped = ([(MIKMIDIControlChangeCommand *)[messages lastObject] value] < 64); return YES; } From 2f5683e08ae444ed4737fdd51700679a3ba37dda Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 26 Aug 2015 16:27:23 -0500 Subject: [PATCH 213/284] Now checking for equal isBundledMapping in isEqual: --- Source/MIKMIDIMapping.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index 3bf64017..f86adbc3 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -292,6 +292,7 @@ - (BOOL)isEqual:(MIKMIDIMapping *)otherMapping if (![self.name isEqualToString:otherMapping.name]) return NO; if (![self.controllerName isEqualToString:otherMapping.controllerName]) return NO; if (![self.additionalAttributes isEqualToDictionary:otherMapping.additionalAttributes]) return NO; + if (self.isBundledMapping != otherMapping.isBundledMapping) return NO; return [self.mappingItems isEqualToSet:otherMapping.mappingItems]; } From a7fb3acd8f416963546f597d99e93bf4b0668920 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Thu, 27 Aug 2015 09:50:52 -0500 Subject: [PATCH 214/284] Added +[MIKMIDIMapping userMappingFromBundledMapping:] --- Source/MIKMIDIMapping.h | 11 +++++++++++ Source/MIKMIDIMapping.m | 7 +++++++ Source/MIKMIDIMappingManager.m | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index 6285be61..6afd4d8d 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -101,6 +101,17 @@ */ - (instancetype)initWithFileAtURL:(NSURL *)url; +/** + * Creates and initializes an MIKMIDIMapping object that is the same as the passed in bundled mapping + * but with isBundledMapping set to NO. + * + * @param bundledMapping The bundled mapping you would like to make a user mapping copy of. + * + * @return An initialized MIKMIDIMapping instance that is the same as the passed in mapping but + * with isBundledMapping set to NO. + */ ++ (instancetype)userMappingFromBundledMapping:(MIKMIDIMapping *)bundledMapping; + #if !TARGET_OS_IPHONE /** * Returns an NSXMLDocument representation of the receiver. diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index f86adbc3..af70b29d 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -114,6 +114,13 @@ - (id)copyWithZone:(NSZone *)zone return result; } ++ (instancetype)userMappingFromBundledMapping:(MIKMIDIMapping *)bundledMapping +{ + MIKMIDIMapping *userMapping = [bundledMapping copy]; + userMapping.bundledMapping = NO; + return userMapping; +} + #if !TARGET_OS_IPHONE - (NSXMLDocument *)XMLRepresentation diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index cd90ed1d..c03e4644 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -300,7 +300,7 @@ - (NSSet *)mappings { return [self.bundledMappings setByAddingObjectsFromSet:sel - (void)addUserMappingsObject:(MIKMIDIMapping *)mapping { - mapping.bundledMapping = NO; + if (mapping.isBundledMapping) mapping = [MIKMIDIMapping userMappingFromBundledMapping:mapping]; [self.internalUserMappings addObject:mapping]; [self saveMappingsToDisk]; From d277f5179267f9505d7d14b344dcee47cf95bb52 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 27 Aug 2015 13:05:55 -0600 Subject: [PATCH 215/284] MIKMIDIMappingGenerator now waits to remove existing items for a remapped control (if delegate allows it) until after a new control has been mapped. --- Source/MIKMIDIMappingGenerator.m | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index b13d7c4c..f75e5952 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -109,15 +109,6 @@ - (void)learnMappingForControl:(id)control self.currentMappingCompletionBlock = completionBlock; self.existingMappingItems = [self.mapping mappingItemsForCommandIdentifier:commandID responder:control]; - // Determine if existing mapping items for this control should be removed. - BOOL shouldRemoveExisting = YES; - if ([self.existingMappingItems count] && - [self.delegate respondsToSelector:@selector(mappingGenerator:shouldRemoveExistingMappingItems:forResponderBeingMapped:)]) { - shouldRemoveExisting = [self.delegate mappingGenerator:self - shouldRemoveExistingMappingItems:self.existingMappingItems - forResponderBeingMapped:self.controlBeingLearned]; - } - if (shouldRemoveExisting && [self.existingMappingItems count]) [self.mapping removeMappingItems:self.existingMappingItems]; MIKMIDIResponderType controlResponderType = MIKMIDIResponderTypeAll; if ([control respondsToSelector:@selector(MIDIResponderTypeForCommandIdentifier:)]) { @@ -145,9 +136,7 @@ - (void)learnMappingForControl:(id)control - (void)cancelCurrentCommandLearning; { if (!self.commandIdentifierBeingLearned) return; - - if ([self.existingMappingItems count]) [self.mapping addMappingItems:self.existingMappingItems]; - + NSDictionary *userInfo = [self.existingMappingItems count] ? @{@"PreviouslyExistingMappings" : self.existingMappingItems} : nil; NSError *error = [NSError MIKMIDIErrorWithCode:NSUserCancelledError userInfo:userInfo]; [self finishMappingItem:nil error:error]; @@ -465,6 +454,19 @@ - (void)finishMappingItem:(MIKMIDIMappingItem *)mappingItemOrNil error:(NSError NSArray *receivedMessages = [self.receivedMessages copy]; [self.receivedMessages removeAllObjects]; self.messagesTimeoutTimer = nil; + + // Determine if existing mapping items for this control should be removed. + BOOL shouldRemoveExisting = mappingItemOrNil != nil; + if (mappingItemOrNil && + [self.existingMappingItems count] && + [self.delegate respondsToSelector:@selector(mappingGenerator:shouldRemoveExistingMappingItems:forResponderBeingMapped:)]) { + shouldRemoveExisting = [self.delegate mappingGenerator:self + shouldRemoveExistingMappingItems:self.existingMappingItems + forResponderBeingMapped:self.controlBeingLearned]; + } + if (shouldRemoveExisting && [self.existingMappingItems count]) [self.mapping removeMappingItems:self.existingMappingItems]; + self.existingMappingItems = nil; + if (mappingItemOrNil) [self.mapping addMappingItemsObject:mappingItemOrNil]; if (completionBlock) completionBlock(mappingItemOrNil, receivedMessages, errorOrNil); From feb84c5e7a741bf8601ff9e2fc88e7d17f772014 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 27 Aug 2015 17:08:39 -0600 Subject: [PATCH 216/284] MIKMIDIMappingGenerator always waits for a timeout after messages stop coming in to finish mapping a control. --- Source/MIKMIDIMappingGenerator.m | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index f75e5952..bc0c9ebe 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -28,7 +28,6 @@ @interface MIKMIDIMappingGenerator () @property (nonatomic) NSTimeInterval timeoutInteveral; @property (nonatomic, strong) NSTimer *messagesTimeoutTimer; -@property (nonatomic) NSUInteger numMessagesRequired; @property (nonatomic, strong) NSMutableArray *receivedMessages; @property (nonatomic, strong) id connectionToken; @@ -124,7 +123,6 @@ - (void)learnMappingForControl:(id)control self.controlBeingLearned = control; self.commandIdentifierBeingLearned = commandID; self.responderTypeOfControlBeingLearned = controlResponderType; - self.numMessagesRequired = numMessages ? numMessages : [self defaultMinimumNumberOfMessagesRequiredForResponderType:controlResponderType]; self.timeoutInteveral = timeout ? timeout : 0.6; self.messagesTimeoutTimer = nil; @@ -206,13 +204,6 @@ - (void)handleMIDICommand:(MIKMIDIChannelVoiceCommand *)command selector:@selector(timeoutTimerFired:) userInfo:nil repeats:NO]; - - if ([self.receivedMessages count] > self.numMessagesRequired) { // Don't try to finish unless we've received several messages (eg. from a knob) already - MIKMIDIMappingItem *mappingItem = [self mappingItemForCommandIdentifier:self.commandIdentifierBeingLearned - inControl:self.controlBeingLearned - fromReceivedMessages:self.receivedMessages]; - if (mappingItem) [self finishMappingItem:mappingItem error:nil]; - } } #pragma mark Messages to Mapping Item From 5dd7f87c77abff8d5542e9638cb059e9a6d0029f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 27 Aug 2015 17:15:13 -0600 Subject: [PATCH 217/284] MIKMIDIMappingGenerator's -mappingGenerator:behaviorForRemappingControlMappedWithItems:... method is now called only after successfully detecting a new mapping. --- Source/MIKMIDIMappingGenerator.m | 63 +++++++++++++++++--------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index bc0c9ebe..600a1812 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -150,32 +150,6 @@ - (void)endMapping; - (void)handleMIDICommand:(MIKMIDIChannelVoiceCommand *)command { - NSSet *existingMappingItemsForOtherControls = [self existingMappingItemsForRespondersOtherThanCurrentForCommand:command]; - - if ([existingMappingItemsForOtherControls count]) { - MIKMIDIMappingGeneratorRemapBehavior behavior = MIKMIDIMappingGeneratorRemapDefault; - if ([self.delegate respondsToSelector:@selector(mappingGenerator:behaviorForRemappingControlMappedWithItems:toNewResponder:commandIdentifier:)]) { - behavior = [self.delegate mappingGenerator:self - behaviorForRemappingControlMappedWithItems:existingMappingItemsForOtherControls - toNewResponder:self.controlBeingLearned - commandIdentifier:self.commandIdentifierBeingLearned]; - } - - switch (behavior) { - default: - case MIKMIDIMappingGeneratorRemapDisallow: - return; // Ignore this command - break; - case MIKMIDIMappingGeneratorRemapAllowDuplicate: - // Do nothing special - break; - case MIKMIDIMappingGeneratorRemapReplace: - // Remove the existing mapping items. - [self.mapping removeMappingItems:existingMappingItemsForOtherControls]; - break; - } - } - if ([self.receivedMessages count]) { // If we get a message from a different controller number, channel, // or command type (not counting note on vs note off), restart the mapping @@ -428,6 +402,33 @@ - (void)timeoutTimerFired:(NSTimer *)timer MIKMIDIMappingItem *mappingItem = [self mappingItemForCommandIdentifier:self.commandIdentifierBeingLearned inControl:self.controlBeingLearned fromReceivedMessages:self.receivedMessages]; + + NSSet *existingMappingItemsForOtherControls = [self existingMappingItemsForResponderMappedTo:mappingItem]; + + if (mappingItem && [existingMappingItemsForOtherControls count]) { + MIKMIDIMappingGeneratorRemapBehavior behavior = MIKMIDIMappingGeneratorRemapDefault; + if ([self.delegate respondsToSelector:@selector(mappingGenerator:behaviorForRemappingControlMappedWithItems:toNewResponder:commandIdentifier:)]) { + behavior = [self.delegate mappingGenerator:self + behaviorForRemappingControlMappedWithItems:existingMappingItemsForOtherControls + toNewResponder:self.controlBeingLearned + commandIdentifier:self.commandIdentifierBeingLearned]; + } + + switch (behavior) { + default: + case MIKMIDIMappingGeneratorRemapDisallow: + mappingItem = nil; // Discard this mapping item + break; + case MIKMIDIMappingGeneratorRemapAllowDuplicate: + // Do nothing special + break; + case MIKMIDIMappingGeneratorRemapReplace: + // Remove the existing mapping items. + [self.mapping removeMappingItems:existingMappingItemsForOtherControls]; + break; + } + } + if (mappingItem) { [self finishMappingItem:mappingItem error:nil]; } else { @@ -486,11 +487,15 @@ - (BOOL)command:(MIKMIDIChannelVoiceCommand *)command1 isSameTypeChannelNumberAs return YES; } -- (NSSet *)existingMappingItemsForRespondersOtherThanCurrentForCommand:(MIKMIDIChannelVoiceCommand *)command +- (NSSet *)existingMappingItemsForResponderMappedTo:(MIKMIDIMappingItem *)mappingItem { - if (!command) return [NSMutableSet set]; + if (!mappingItem) return [NSMutableSet set]; + + MIKMutableMIDIChannelVoiceCommand *matchingCommand = [MIKMutableMIDIChannelVoiceCommand commandForCommandType:mappingItem.commandType]; + matchingCommand.channel = mappingItem.channel; + matchingCommand.dataByte1 = mappingItem.controlNumber; - NSSet *existingMappingItems = [self.mapping mappingItemsForMIDICommand:command]; + NSSet *existingMappingItems = [self.mapping mappingItemsForMIDICommand:matchingCommand]; NSMutableSet *result = [existingMappingItems mutableCopy]; if ([self.commandIdentifierBeingLearned length] && self.controlBeingLearned) { NSSet *existingForCurrentResponder = [self.mapping mappingItemsForCommandIdentifier:self.commandIdentifierBeingLearned responder:self.controlBeingLearned]; From d65f6af5ced46481548dc94f3889f66836785b4f Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Mon, 31 Aug 2015 12:49:43 -0500 Subject: [PATCH 218/284] Added -legacyFileNamesForUserMappingsObject: to ensure deletion of user mappings with an older naming scheme in -removeUserMappingsObject:. --- Source/MIKMIDIMappingManager.m | 51 ++++++++++++++----- .../MIKMIDIMappingManager_SubclassMethods.h | 16 ++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index c03e4644..062cab1f 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -253,29 +253,34 @@ - (void)loadBundledMappings - (NSURL *)fileURLForMapping:(MIKMIDIMapping *)mapping shouldBeUnique:(BOOL)unique { - NSURL *mappingsFolder = [self userMappingsFolder]; - NSString *filename = [[self fileNameForMapping:mapping] stringByAppendingPathExtension:kMIKMIDIMappingFileExtension]; - - NSURL *result = [mappingsFolder URLByAppendingPathComponent:filename]; - + NSURL *fileURL = [self fileURLWithBaseFilename:[self fileNameForMapping:mapping]]; + if (unique) { + NSURL *mappingsFolder = [self userMappingsFolder]; NSFileManager *fm = [NSFileManager defaultManager]; unsigned long numberSuffix = 0; - while ([fm fileExistsAtPath:[result path]]) { - MIKMIDIMapping *existingMapping = [[MIKMIDIMapping alloc] initWithFileAtURL:result error:NULL]; + while ([fm fileExistsAtPath:[fileURL path]]) { + MIKMIDIMapping *existingMapping = [[MIKMIDIMapping alloc] initWithFileAtURL:fileURL error:NULL]; if ([existingMapping isEqual:mapping]) break; if (numberSuffix > 1000) return nil; // Don't go crazy NSString *name = [mapping.name stringByAppendingFormat:@" %lu", ++numberSuffix]; - filename = [name stringByAppendingPathExtension:kMIKMIDIMappingFileExtension]; - result = [mappingsFolder URLByAppendingPathComponent:filename]; + NSString *filename = [name stringByAppendingPathExtension:kMIKMIDIMappingFileExtension]; + fileURL = [mappingsFolder URLByAppendingPathComponent:filename]; } } - return result; + return fileURL; +} + +- (NSURL *)fileURLWithBaseFilename:(NSString *)baseFileName +{ + NSString *filename = [baseFileName stringByAppendingPathExtension:kMIKMIDIMappingFileExtension]; + return [[self userMappingsFolder] URLByAppendingPathComponent:filename]; } - (NSString *)fileNameForMapping:(MIKMIDIMapping *)mapping { return mapping.name; } +- (NSArray *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping { return nil; } #pragma mark - Properties @@ -314,11 +319,31 @@ - (void)removeUserMappingsObject:(MIKMIDIMapping *)mapping // Remove XML file for mapping from disk NSURL *mappingURL = [self fileURLForMapping:mapping shouldBeUnique:NO]; - if (!mappingURL) return; + NSArray *legacyFilenames = [self legacyFileNamesForUserMappingsObject:mapping]; + if (!mappingURL && !legacyFilenames.count) return; + + NSMutableArray *possibleURLs = [NSMutableArray array]; + if (mappingURL) [possibleURLs addObject:mappingURL]; + + for (NSString *filename in legacyFilenames) { + [possibleURLs addObject:[self fileURLWithBaseFilename:filename]]; + } + NSFileManager *fm = [NSFileManager defaultManager]; NSError *error = nil; - if (![fm removeItemAtURL:mappingURL error:&error]) { - NSLog(@"Error removing mapping file for MIDI mapping %@: %@", mapping, error); + BOOL removedAtLeastOneFile = NO; + for (NSURL *url in possibleURLs) { + if (![fm fileExistsAtPath:url.path]) continue; + + if ([fm removeItemAtURL:url error:&error]) { + removedAtLeastOneFile = YES; + } else { + NSLog(@"Error removing mapping file for MIDI mapping %@: %@", mapping, error); + } + } + + if (!removedAtLeastOneFile) { + NSLog(@"No mapping files were found to delete for the mapping named \"%@\"", mapping.name); } } diff --git a/Source/MIKMIDIMappingManager_SubclassMethods.h b/Source/MIKMIDIMappingManager_SubclassMethods.h index 9e8fe6da..d6855266 100644 --- a/Source/MIKMIDIMappingManager_SubclassMethods.h +++ b/Source/MIKMIDIMappingManager_SubclassMethods.h @@ -24,4 +24,20 @@ */ - (NSString *)fileNameForMapping:(MIKMIDIMapping *)mapping; +/** + * When deleting user mappings, this method is called as a way to provide any additional + * file names that the mapping may have had in past versions of -fileNameForMapping: + * + * If you have changed the naming scheme that -fileNameForMapping: uses in any user-reaching + * code, you will probably want to implement this method as well, so users will be able to + * properly delete mappings with the old naming scheme. + * + * Just as with -fileNameForMapping:, the file names should *not* include the file extension. + * + * @param mapping The mapping to return legacy file names for. + * + * @return An array of legacy file names, or nil. + */ +- (NSArray *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping; + @end From bdb7ff69aadf2e97906c5b4176a5a0f972997f1f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 1 Sep 2015 14:43:31 -0600 Subject: [PATCH 219/284] Issue #102: Added ability to suspend/resume MIKMIDIMappingGenerator. --- Source/MIKMIDIMappingGenerator.h | 16 ++++++++++++++++ Source/MIKMIDIMappingGenerator.m | 20 +++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index 0098784c..b71226b9 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -87,6 +87,22 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem */ - (void)cancelCurrentCommandLearning; +/** + * Temporarily suspends mapping without discarding state. Unlike -cancelCurrentCommandLearning, or -endMapping, + * mapping can be resumed exactly where it left off by calling -resumeMapping. Incoming MIDI is simply + * ignored while mapping is suspended. + * + * Note that if mapping is not currently in progress, this method has no effect. + */ +- (void)suspendMapping; + +/** + * Resumes mapping after it was previously suspended using -suspendMapping. + * + * Note that if mapping was not previously in progress and currently suspended, this has no effect. + */ +- (void)resumeMapping; + /** * Stops mapping generation, disconnecting from the device. */ diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 600a1812..cb55fbdc 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -33,6 +33,8 @@ @interface MIKMIDIMappingGenerator () @property (nonatomic, strong) id connectionToken; @property (nonatomic, strong) NSMutableArray *blockBasedObservers; +@property (nonatomic, getter=isMappingSuspended) BOOL mappingSuspended; + @end @implementation MIKMIDIMappingGenerator @@ -134,22 +136,38 @@ - (void)learnMappingForControl:(id)control - (void)cancelCurrentCommandLearning; { if (!self.commandIdentifierBeingLearned) return; - + + self.mappingSuspended = NO; + NSDictionary *userInfo = [self.existingMappingItems count] ? @{@"PreviouslyExistingMappings" : self.existingMappingItems} : nil; NSError *error = [NSError MIKMIDIErrorWithCode:NSUserCancelledError userInfo:userInfo]; [self finishMappingItem:nil error:error]; } +- (void)suspendMapping +{ + if (!self.commandIdentifierBeingLearned) return; + self.mappingSuspended = YES; +} + +- (void)resumeMapping +{ + self.mappingSuspended = NO; +} + - (void)endMapping; { [self disconnectFromDevice]; self.device = nil; + self.mappingSuspended = NO; } #pragma mark - Private - (void)handleMIDICommand:(MIKMIDIChannelVoiceCommand *)command { + if (self.isMappingSuspended) return; // Ignore input while mapping is suspended + if ([self.receivedMessages count]) { // If we get a message from a different controller number, channel, // or command type (not counting note on vs note off), restart the mapping From a8ff5aafb0fd2e88ffaeee4f5aeca5ec5ca3856d Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 2 Sep 2015 14:35:40 -0600 Subject: [PATCH 220/284] Updated to latest Xcode7 recommended project settings. --- Framework/MIKMIDI Tests/Info.plist | 2 +- Framework/MIKMIDI-Info.plist | 2 +- Framework/MIKMIDI-iOS-Info.plist | 2 +- Framework/MIKMIDI.xcodeproj/project.pbxproj | 9 ++++++++- .../xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme | 13 ++++++++----- .../xcshareddata/xcschemes/MIKMIDI.xcscheme | 13 ++++++++----- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Framework/MIKMIDI Tests/Info.plist b/Framework/MIKMIDI Tests/Info.plist index 8934606d..ba72822e 100644 --- a/Framework/MIKMIDI Tests/Info.plist +++ b/Framework/MIKMIDI Tests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Framework/MIKMIDI-Info.plist b/Framework/MIKMIDI-Info.plist index abc32f32..2677192d 100644 --- a/Framework/MIKMIDI-Info.plist +++ b/Framework/MIKMIDI-Info.plist @@ -9,7 +9,7 @@ CFBundleIconFile CFBundleIdentifier - com.mixedinkey.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Framework/MIKMIDI-iOS-Info.plist b/Framework/MIKMIDI-iOS-Info.plist index 804c9f41..d3de8eef 100644 --- a/Framework/MIKMIDI-iOS-Info.plist +++ b/Framework/MIKMIDI-iOS-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index d57dd563..efa7eea8 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -1059,7 +1059,7 @@ 9D74EE9C17A7129300BEE89F /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0500; + LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Mixed In Key"; TargetAttributes = { 9D4DF1391AAB57430065F004 = { @@ -1315,6 +1315,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -1343,6 +1344,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -1361,6 +1363,7 @@ CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -1422,6 +1425,7 @@ INFOPLIST_FILE = "MIKMIDI-Info.plist"; LD_DYLIB_INSTALL_NAME = "@loader_path/../Frameworks/$(EXECUTABLE_PATH)"; MODULEMAP_FILE = module.modulemap; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -1443,6 +1447,7 @@ INFOPLIST_FILE = "MIKMIDI-Info.plist"; LD_DYLIB_INSTALL_NAME = "@loader_path/../Frameworks/$(EXECUTABLE_PATH)"; MODULEMAP_FILE = module.modulemap; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -1485,6 +1490,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = module.modulemap; MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = MIKMIDI; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -1527,6 +1533,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MODULEMAP_FILE = module.modulemap; MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = MIKMIDI; SDKROOT = iphoneos; SKIP_INSTALL = YES; diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme index eabf64ec..dc322756 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> + + + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -48,15 +48,18 @@ ReferencedContainer = "container:MIKMIDI.xcodeproj"> + + Date: Sat, 5 Sep 2015 11:41:45 -0600 Subject: [PATCH 221/284] Ignoring deprecation warnings in MIKMIDITrackTests. We should continue testing these methods until they're actually removed. --- Framework/MIKMIDI Tests/MIKMIDITrackTests.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m index 034f3d52..833b1019 100644 --- a/Framework/MIKMIDI Tests/MIKMIDITrackTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDITrackTests.m @@ -21,6 +21,9 @@ @interface MIKMIDITrackTests : XCTestCase @end +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + @implementation MIKMIDITrackTests - (void)setUp @@ -890,3 +893,5 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N @end + +#pragma clang diagnostic pop \ No newline at end of file From c0d1ad2d243d5288d6cabe70a252c1a92c6fa227 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Fri, 11 Sep 2015 15:53:46 -0500 Subject: [PATCH 222/284] Switched to using CoreFoundation rather than Obj-C in MIKMIDISynthesizer's command scheduling mechanism. Deprecated +[MIKMIDIClock secondsPerMIDITimeStamp] and +midiTimeStampsPerTimeInterval: in favor of C functions that accomplish the same task. --- Source/MIKMIDIClock.h | 45 +++++++++++----- Source/MIKMIDIClock.m | 20 +++++-- Source/MIKMIDISequencer.m | 6 +-- Source/MIKMIDISynthesizer.m | 104 +++++++++++++++++++++++------------- 4 files changed, 119 insertions(+), 56 deletions(-) diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 3432e810..68966534 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -9,16 +9,6 @@ #import #import - -/** - * MIKMIDIClock provides the number of seconds per MIDITimeStamp, as well as the - * number of MIDITimeStamps per a specified time interval. - * - * Instances of MIKMIDIClock can be used to convert between MIDITimeStamp - * and MusicTimeStamp. - */ -@interface MIKMIDIClock : NSObject - /** * Returns the number of MIDITimeStamps that would occur during a specified time interval. * @@ -26,14 +16,24 @@ * * @return The number of MIDITimeStamps that would occur in the specified time interval. */ -+ (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval; +Float64 MIKMIDIClockMIDITimeStampsPerTimeInterval(NSTimeInterval timeInterval); /** * Returns the number of seconds per each MIDITimeStamp. * * @return Then number of seconds per each MIDITimeStamp. */ -+ (Float64)secondsPerMIDITimeStamp; +Float64 MIKMIDIClockSecondsPerMIDITimeStamp(); + + +/** + * MIKMIDIClock provides the number of seconds per MIDITimeStamp, as well as the + * number of MIDITimeStamps per a specified time interval. + * + * Instances of MIKMIDIClock can be used to convert between MIDITimeStamp + * and MusicTimeStamp. + */ +@interface MIKMIDIClock : NSObject /** * Creates and initializes a new instance of MIKMIDIClock. @@ -193,5 +193,26 @@ */ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)tempo atMIDITimeStamp:(MIDITimeStamp)midiTimeStamp DEPRECATED_ATTRIBUTE; +/** + * @deprecated This method is deprecated. Use MIKMIDIClockMIDITimeStampsPerTimeInterval() instead. + * + * Returns the number of seconds per each MIDITimeStamp. + * + * @return Then number of seconds per each MIDITimeStamp. + */ ++ (Float64)secondsPerMIDITimeStamp DEPRECATED_ATTRIBUTE; + +/** + * @deprecated This method is deprecated. Use MIKMIDIClockSecondsPerMIDITimeStamp() instead. + * + * Returns the number of MIDITimeStamps that would occur during a specified time interval. + * + * @param timeInterval The number of seconds to convert into number of MIDITimeStamps. + * + * @return The number of MIDITimeStamps that would occur in the specified time interval. + */ ++ (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval DEPRECATED_ATTRIBUTE; + + @end diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 1c09f062..77234a31 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -111,7 +111,7 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID self.historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; } else { // Remove clocks old enough to not be needed anymore - MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - [MIKMIDIClock midiTimeStampsPerTimeInterval:kDurationToKeepHistoricalClocks]; + MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - MIKMIDIClockMIDITimeStampsPerTimeInterval(kDurationToKeepHistoricalClocks); NSUInteger count = historicalClockMIDITimeStamps.count; NSMutableArray *timeStampsToRemove = [NSMutableArray array]; NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; @@ -143,7 +143,7 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID } // Update new tempo and timing information - Float64 secondsPerMIDITimeStamp = [[self class] secondsPerMIDITimeStamp]; + Float64 secondsPerMIDITimeStamp = MIKMIDIClockSecondsPerMIDITimeStamp(); Float64 secondsPerMusicTimeStamp = 60.0 / tempo; Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; @@ -279,6 +279,16 @@ - (MIKMIDIClock *)syncedClock #pragma mark - Class Methods + (Float64)secondsPerMIDITimeStamp +{ + return MIKMIDIClockSecondsPerMIDITimeStamp(); +} + ++ (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval +{ + return MIKMIDIClockMIDITimeStampsPerTimeInterval(timeInterval); +} + +Float64 MIKMIDIClockSecondsPerMIDITimeStamp() { static Float64 secondsPerMIDITimeStamp; static dispatch_once_t onceToken; @@ -288,12 +298,14 @@ + (Float64)secondsPerMIDITimeStamp secondsPerMIDITimeStamp = (timeBaseInfoData.numer / timeBaseInfoData.denom) / 1.0e9; }); return secondsPerMIDITimeStamp; + } -+ (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval + +Float64 MIKMIDIClockMIDITimeStampsPerTimeInterval(NSTimeInterval timeInterval) { static Float64 midiTimeStampsPerSecond = 0; - if (!midiTimeStampsPerSecond) midiTimeStampsPerSecond = (1.0 / [self secondsPerMIDITimeStamp]); + if (!midiTimeStampsPerSecond) midiTimeStampsPerSecond = (1.0 / MIKMIDIClockSecondsPerMIDITimeStamp()); return midiTimeStampsPerSecond * timeInterval; } diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 36588ef6..bbc2a8b6 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -150,7 +150,7 @@ - (void)startPlayback - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp { - MIDITimeStamp midiTimeStamp = MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.001]; + MIDITimeStamp midiTimeStamp = MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(0.001); [self startPlaybackAtTimeStamp:timeStamp MIDITimeStamp:midiTimeStamp]; } @@ -218,7 +218,7 @@ - (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue MIKMIDIClock *clock = self.clock; [self recordAllPendingNoteEventsWithOffTimeStamp:[clock musicTimeStampForMIDITimeStamp:stopTimeStamp]]; - MusicTimeStamp allPendingNotesOffTimeStamp = MAX(self.latestScheduledMIDITimeStamp + 1, MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.001]); + MusicTimeStamp allPendingNotesOffTimeStamp = MAX(self.latestScheduledMIDITimeStamp + 1, MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(0.001)); [self sendAllPendingNoteOffsWithMIDITimeStamp:allPendingNotesOffTimeStamp]; self.pendingRecordedNoteEvents = nil; self.looping = NO; @@ -239,7 +239,7 @@ - (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStamp { - MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + [MIKMIDIClock midiTimeStampsPerTimeInterval:0.1]; + MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(0.1); if (toMIDITimeStamp < fromMIDITimeStamp) return; MIKMIDIClock *clock = self.clock; diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 6fad0d1f..e0df09c8 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -15,8 +15,10 @@ @interface MIKMIDISynthesizer () { - NSMutableDictionary *_scheduledCommandsByTimeStamp; - NSMutableIndexSet *_scheduledCommandTimeStamps; + CFMutableDictionaryRef _scheduledCommandsByTimeStamp; + CFMutableSetRef _scheduledCommandTimeStampsSet; + CFMutableArrayRef _scheduledCommandTimeStampsArray; + dispatch_queue_t _scheduledCommandQueue; } @end @@ -43,6 +45,21 @@ - (void)dealloc { self.instrumentUnit = NULL; self.graph = NULL; + + if (_scheduledCommandsByTimeStamp) { + CFRelease(_scheduledCommandsByTimeStamp); + _scheduledCommandsByTimeStamp = NULL; + } + + if (_scheduledCommandTimeStampsSet) { + CFRelease(_scheduledCommandTimeStampsSet); + _scheduledCommandTimeStampsSet = NULL; + } + + if (_scheduledCommandTimeStampsArray) { + CFRelease(_scheduledCommandTimeStampsArray); + _scheduledCommandTimeStampsArray = NULL; + } } #pragma mark - Public @@ -352,28 +369,32 @@ - (void)scheduleMIDICommands:(NSArray *)commands for (MIKMIDICommand *command in commands) { dispatch_sync(queue, ^{ - NSMutableDictionary *commandsByTimeStamp = _scheduledCommandsByTimeStamp; - if (!commandsByTimeStamp) { - commandsByTimeStamp = [NSMutableDictionary dictionaryWithCapacity:commands.count]; - _scheduledCommandsByTimeStamp = commandsByTimeStamp; + NSUInteger count = commands.count; + if (!_scheduledCommandsByTimeStamp) { + _scheduledCommandsByTimeStamp = CFDictionaryCreateMutable(NULL, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } - - NSMutableIndexSet *commandTimeStamps = _scheduledCommandTimeStamps; - if (!commandTimeStamps) { - commandTimeStamps = [NSMutableIndexSet indexSet]; - _scheduledCommandTimeStamps = commandTimeStamps; + if (!_scheduledCommandTimeStampsSet) { + _scheduledCommandTimeStampsSet = CFSetCreateMutable(NULL, count, &kCFTypeSetCallBacks); + } + if (!_scheduledCommandTimeStampsArray) { + _scheduledCommandTimeStampsArray = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks); } MIDITimeStamp timeStamp = command.midiTimestamp; - NSNumber *timeStampNumber = @(timeStamp); - NSMutableArray *commandsAtTimeStamp = commandsByTimeStamp[timeStampNumber]; + void *timeStampNumber = (__bridge void*)@(timeStamp); + CFMutableArrayRef commandsAtTimeStamp = (CFMutableArrayRef)CFDictionaryGetValue(_scheduledCommandsByTimeStamp, timeStampNumber); if (!commandsAtTimeStamp) { - commandsAtTimeStamp = [NSMutableArray array]; - commandsByTimeStamp[timeStampNumber] = commandsAtTimeStamp; - [commandTimeStamps addIndex:timeStamp]; + commandsAtTimeStamp = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + CFDictionarySetValue(_scheduledCommandsByTimeStamp, timeStampNumber, (void *)commandsAtTimeStamp); + CFRelease(commandsAtTimeStamp); + + if (!CFSetContainsValue(_scheduledCommandTimeStampsSet, timeStampNumber)) { + CFSetAddValue(_scheduledCommandTimeStampsSet, timeStampNumber); + CFArrayAppendValue(_scheduledCommandTimeStampsArray, timeStampNumber); + } } - [commandsAtTimeStamp addObject:command]; + CFArrayAppendValue(commandsAtTimeStamp, (__bridge void *)command); }); } } @@ -410,39 +431,46 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef MIDITimeStamp midiTimeStampsUntilNextCallback = lastMIDITimeStampsUntilNextCallback; if (lastTimeUntilNextCallback != timeUntilNextCallback) { - midiTimeStampsUntilNextCallback = [MIKMIDIClock midiTimeStampsPerTimeInterval:timeUntilNextCallback]; + midiTimeStampsUntilNextCallback = MIKMIDIClockMIDITimeStampsPerTimeInterval(timeUntilNextCallback); lastTimeUntilNextCallback = timeUntilNextCallback; lastMIDITimeStampsUntilNextCallback = midiTimeStampsUntilNextCallback; } MIDITimeStamp toTimeStamp = inTimeStamp->mHostTime + midiTimeStampsUntilNextCallback; + CFMutableArrayRef commandsToSend = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);; - __block NSMutableArray *commandsToSend; dispatch_sync(queue, ^{ - NSMutableDictionary *commandsByTimeStamp = synth->_scheduledCommandsByTimeStamp; - if (!commandsByTimeStamp.count) return; + CFMutableDictionaryRef commandsByTimeStamp = synth->_scheduledCommandsByTimeStamp; + if (!commandsByTimeStamp || !CFDictionaryGetCount(commandsByTimeStamp)) return; - NSMutableIndexSet *commandTimeStamps = synth->_scheduledCommandTimeStamps; - commandsToSend = [NSMutableArray array]; - NSRange range = NSMakeRange(0, toTimeStamp); - [commandTimeStamps enumerateRangesInRange:range options:0 usingBlock:^(NSRange range, BOOL *stop) { - MIDITimeStamp rangeStart = range.location; - MIDITimeStamp rangeEnd = rangeStart + range.length; + CFMutableSetRef commandTimeStampsSet = synth->_scheduledCommandTimeStampsSet; + CFMutableArrayRef commandTimeStampsArray = synth->_scheduledCommandTimeStampsArray; + if (!commandTimeStampsSet || !commandTimeStampsArray) return; - for (MIDITimeStamp timeStamp = rangeStart; timeStamp < rangeEnd; timeStamp++) { - NSNumber *timeStampNumber = @(timeStamp); - [commandsToSend addObjectsFromArray:commandsByTimeStamp[timeStampNumber]]; - [commandsByTimeStamp removeObjectForKey:timeStampNumber]; - } - }]; - [commandTimeStamps removeIndexesInRange:range]; + CFArrayRef commandTimeStampsArrayCopy = CFArrayCreateCopy(NULL, commandTimeStampsArray); + CFIndex count = CFArrayGetCount(commandTimeStampsArrayCopy); + for (CFIndex i = 0; i < count; i++) { + NSNumber *timeStampNumber = (__bridge NSNumber *)CFArrayGetValueAtIndex(commandTimeStampsArrayCopy, i); + MIDITimeStamp timeStamp = timeStampNumber.unsignedIntegerValue; + if (timeStamp >= toTimeStamp) break; + + CFMutableArrayRef commandsAtTimeStamp = (CFMutableArrayRef)CFDictionaryGetValue(commandsByTimeStamp, (__bridge void*)timeStampNumber); + CFArrayAppendArray(commandsToSend, commandsAtTimeStamp, CFRangeMake(0, CFArrayGetCount(commandsAtTimeStamp))); + + CFDictionaryRemoveValue(commandsByTimeStamp, (__bridge void *)timeStampNumber); + CFSetRemoveValue(commandTimeStampsSet, (__bridge void*)timeStampNumber); + CFArrayRemoveValueAtIndex(commandTimeStampsArray, 0); + } + CFRelease(commandTimeStampsArrayCopy); }); - static NSTimeInterval secondsPerMIDITimeStamp = 0; - if (!secondsPerMIDITimeStamp) secondsPerMIDITimeStamp = [MIKMIDIClock secondsPerMIDITimeStamp]; + NSTimeInterval secondsPerMIDITimeStamp = MIKMIDIClockSecondsPerMIDITimeStamp(); + + CFIndex commandCount = CFArrayGetCount(commandsToSend); + for (CFIndex i = 0; i < commandCount; i++) { + MIKMIDICommand *command = (__bridge MIKMIDICommand *)CFArrayGetValueAtIndex(commandsToSend, i); - for (MIKMIDICommand *command in commandsToSend) { MIDITimeStamp sendTimeStamp = command.midiTimestamp; if (sendTimeStamp < inTimeStamp->mHostTime) sendTimeStamp = inTimeStamp->mHostTime; MIDITimeStamp timeStampOffset = sendTimeStamp - inTimeStamp->mHostTime; @@ -454,6 +482,8 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef return err; } } + + CFRelease(commandsToSend); } return noErr; } From 0a06eeb46333e5888cfbfbee8539af404fd9173c Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Mon, 14 Sep 2015 17:12:25 -0500 Subject: [PATCH 223/284] Began optimizing MIKMIDIClock for better efficiency when used in CoreAudio callbacks. --- Source/MIKMIDIClock.m | 247 ++++++++++++++++++++++++++---------------- 1 file changed, 155 insertions(+), 92 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 77234a31..3ac556fa 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -27,22 +27,23 @@ + (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock; #pragma mark - @interface MIKMIDIClock () +{ + Float64 _currentTempo; + MIDITimeStamp _timeStampZero; + MIDITimeStamp _lastSyncedMIDITimeStamp; + MusicTimeStamp _lastSyncedMusicTimeStamp; -@property (nonatomic) Float64 currentTempo; -@property (nonatomic) MIDITimeStamp timeStampZero; -@property (nonatomic) MIDITimeStamp lastSyncedMIDITimeStamp; -@property (nonatomic) MusicTimeStamp lastSyncedMusicTimeStamp; + Float64 _musicTimeStampsPerMIDITimeStamp; + Float64 _midiTimeStampsPerMusicTimeStamp; -@property (nonatomic) Float64 musicTimeStampsPerMIDITimeStamp; -@property (nonatomic) Float64 midiTimeStampsPerMusicTimeStamp; + NSMutableDictionary *_historicalClocks; + NSMutableOrderedSet *_historicalClockMIDITimeStamps; -@property (nonatomic, strong) NSMutableDictionary *historicalClocks; -@property (nonatomic, strong) NSMutableOrderedSet *historicalClockMIDITimeStamps; + dispatch_queue_t _clockQueue; +} @property (nonatomic, getter=isReady) BOOL ready; -@property (nonatomic) dispatch_queue_t clockQueue; - @end @@ -74,7 +75,7 @@ - (instancetype)initWithQueue:(BOOL)createQueue } #endif - self.clockQueue = dispatch_queue_create(queueLabel.UTF8String, attr); + _clockQueue = dispatch_queue_create(queueLabel.UTF8String, attr); } } return self; @@ -82,11 +83,11 @@ - (instancetype)initWithQueue:(BOOL)createQueue #pragma mark - Queue -- (void)dispatchToClockQueue:(void (^)())block +static void dispatchToClockQueue(MIKMIDIClock *self, void(^block)()) { if (!block) return; - dispatch_queue_t queue = self.clockQueue; + dispatch_queue_t queue = self->_clockQueue; if (queue) { dispatch_sync(queue, block); } else { @@ -98,17 +99,18 @@ - (void)dispatchToClockQueue:(void (^)())block - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MIDITimeStamp)midiTimeStamp tempo:(Float64)tempo { - [self dispatchToClockQueue:^{ - if (self.lastSyncedMIDITimeStamp) { + [self willChangeValueForKey:@"ready"]; + dispatchToClockQueue(self, ^{ + if (_lastSyncedMIDITimeStamp) { // Add a clock to the historical clocks - NSMutableDictionary *historicalClocks = self.historicalClocks; + NSMutableDictionary *historicalClocks = _historicalClocks; NSNumber *midiTimeStampNumber = @(midiTimeStamp); - NSMutableOrderedSet *historicalClockMIDITimeStamps = self.historicalClockMIDITimeStamps; + NSMutableOrderedSet *historicalClockMIDITimeStamps = _historicalClockMIDITimeStamps; if (!historicalClocks) { historicalClocks = [NSMutableDictionary dictionary]; - self.historicalClocks = historicalClocks; - self.historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; + _historicalClocks = historicalClocks; + _historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; } else { // Remove clocks old enough to not be needed anymore MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - MIKMIDIClockMIDITimeStampsPerTimeInterval(kDurationToKeepHistoricalClocks); @@ -133,11 +135,11 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID // Add clock to history MIKMIDIClock *historicalClock = [[MIKMIDIClock alloc] initWithQueue:NO]; - historicalClock.currentTempo = self.currentTempo; - historicalClock.timeStampZero = self.timeStampZero; - historicalClock.lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; - historicalClock.musicTimeStampsPerMIDITimeStamp = self.musicTimeStampsPerMIDITimeStamp; - historicalClock.midiTimeStampsPerMusicTimeStamp = self.midiTimeStampsPerMusicTimeStamp; + historicalClock->_currentTempo = _currentTempo; + historicalClock->_timeStampZero = _timeStampZero; + historicalClock->_lastSyncedMIDITimeStamp = _lastSyncedMIDITimeStamp; + historicalClock->_musicTimeStampsPerMIDITimeStamp = _musicTimeStampsPerMIDITimeStamp; + historicalClock->_midiTimeStampsPerMusicTimeStamp = _midiTimeStampsPerMusicTimeStamp; historicalClocks[midiTimeStampNumber] = historicalClock; [historicalClockMIDITimeStamps addObject:midiTimeStampNumber]; } @@ -147,119 +149,147 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID Float64 secondsPerMusicTimeStamp = 60.0 / tempo; Float64 midiTimeStampsPerMusicTimeStamp = secondsPerMusicTimeStamp / secondsPerMIDITimeStamp; - self.currentTempo = tempo; - self.lastSyncedMIDITimeStamp = midiTimeStamp; - self.lastSyncedMusicTimeStamp = musicTimeStamp; - self.timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); - self.midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; - self.musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; - self.ready = YES; - }]; + _currentTempo = tempo; + _lastSyncedMIDITimeStamp = midiTimeStamp; + _lastSyncedMusicTimeStamp = musicTimeStamp; + _timeStampZero = midiTimeStamp - (musicTimeStamp * midiTimeStampsPerMusicTimeStamp); + _midiTimeStampsPerMusicTimeStamp = midiTimeStampsPerMusicTimeStamp; + _musicTimeStampsPerMIDITimeStamp = secondsPerMIDITimeStamp / secondsPerMusicTimeStamp; + _ready = YES; + }); + [self didChangeValueForKey:@"ready"]; } - (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps { - [self dispatchToClockQueue:^{ - self.ready = NO; - self.currentTempo = 0; - self.historicalClocks = nil; - self.historicalClockMIDITimeStamps = nil; - }]; + [self willChangeValueForKey:@"ready"]; + dispatchToClockQueue(self, ^{ + _ready = NO; + _currentTempo = 0; + _historicalClocks = nil; + _historicalClockMIDITimeStamps = nil; + }); + [self didChangeValueForKey:@"ready"]; } -- (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +static MusicTimeStamp musicTimeStampForMIDITimeStamp(MIKMIDIClock *self, MIDITimeStamp midiTimeStamp) { __block MusicTimeStamp musicTimeStamp = 0; - [self dispatchToClockQueue:^{ - if (!self.isReady) return; + dispatchToClockQueue(self, ^{ + if (!self->_ready) return; - MIDITimeStamp lastSyncedMIDITimeStamp = self.lastSyncedMIDITimeStamp; + MIDITimeStamp lastSyncedMIDITimeStamp = self->_lastSyncedMIDITimeStamp; if (midiTimeStamp >= lastSyncedMIDITimeStamp) { - musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:self]; + musicTimeStamp = musicTimeStampForMIDITimeStampWithHistoricalClock(midiTimeStamp, self); } else { - musicTimeStamp = [self musicTimeStampForMIDITimeStamp:midiTimeStamp withClock:[self clockForMIDITimeStamp:midiTimeStamp]]; + musicTimeStamp = musicTimeStampForMIDITimeStampWithHistoricalClock(midiTimeStamp, clockForMIDITimeStamp(self, midiTimeStamp)); } - }]; + }); return musicTimeStamp; } -- (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp withClock:(MIKMIDIClock *)clock +- (MusicTimeStamp)musicTimeStampForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp { - if (midiTimeStamp == clock.lastSyncedMIDITimeStamp) return clock.lastSyncedMusicTimeStamp; - MIDITimeStamp timeStampZero = clock.timeStampZero; - return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * clock.musicTimeStampsPerMIDITimeStamp) : -((timeStampZero - midiTimeStamp) * clock.musicTimeStampsPerMIDITimeStamp); + return musicTimeStampForMIDITimeStamp(self, midiTimeStamp); } -- (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp +static MusicTimeStamp musicTimeStampForMIDITimeStampWithHistoricalClock(MIDITimeStamp midiTimeStamp, MIKMIDIClock *clock) +{ + if (midiTimeStamp == clock->_lastSyncedMIDITimeStamp) return clock->_lastSyncedMusicTimeStamp; + MIDITimeStamp timeStampZero = clock->_timeStampZero; + return (midiTimeStamp >= timeStampZero) ? ((midiTimeStamp - timeStampZero) * clock->_musicTimeStampsPerMIDITimeStamp) : -((timeStampZero - midiTimeStamp) * clock->_musicTimeStampsPerMIDITimeStamp); +} + +static MIDITimeStamp midiTimeStampForMusicTimeStamp(MIKMIDIClock *self, MusicTimeStamp musicTimeStamp) { __block MIDITimeStamp midiTimeStamp = 0; - [self dispatchToClockQueue:^{ - if (!self.isReady) return; - if (musicTimeStamp == self.lastSyncedMusicTimeStamp) { midiTimeStamp = self.lastSyncedMIDITimeStamp; return; } + dispatchToClockQueue(self, ^{ + if (!self->_ready) return; + if (musicTimeStamp == self->_lastSyncedMusicTimeStamp) { midiTimeStamp = self->_lastSyncedMIDITimeStamp; return; } - midiTimeStamp = round(musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp) + self.timeStampZero; + midiTimeStamp = round(musicTimeStamp * self->_midiTimeStampsPerMusicTimeStamp) + self->_timeStampZero; - if (midiTimeStamp < self.lastSyncedMIDITimeStamp) { - NSDictionary *historicalClocks = self.historicalClocks; - for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + if (midiTimeStamp < self->_lastSyncedMIDITimeStamp) { + NSDictionary *historicalClocks = self->_historicalClocks; + for (NSNumber *historicalClockTimeStamp in [[self->_historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; - MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock.midiTimeStampsPerMusicTimeStamp) + clock.timeStampZero; - if (historicalMIDITimeStamp >= clock.lastSyncedMIDITimeStamp) { + MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock->_midiTimeStampsPerMusicTimeStamp) + clock->_timeStampZero; + if (historicalMIDITimeStamp >= clock->_lastSyncedMIDITimeStamp) { midiTimeStamp = historicalMIDITimeStamp; break; } } } - }]; + }); return midiTimeStamp; } -- (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp +- (MIDITimeStamp)midiTimeStampForMusicTimeStamp:(MusicTimeStamp)musicTimeStamp +{ + return midiTimeStampForMusicTimeStamp(self, musicTimeStamp); +} + +static MIDITimeStamp midiTimeStampsPerMusicTimeStamp(MIKMIDIClock *self, MusicTimeStamp musicTimeStamp) { __block MIDITimeStamp midiTimeStamps = 0; - [self dispatchToClockQueue:^{ - if (self.isReady) midiTimeStamps = musicTimeStamp * self.midiTimeStampsPerMusicTimeStamp; - }]; + dispatchToClockQueue(self, ^{ + if (self->_ready) midiTimeStamps = musicTimeStamp * self->_midiTimeStampsPerMusicTimeStamp; + }); return midiTimeStamps; } +- (MIDITimeStamp)midiTimeStampsPerMusicTimeStamp:(MusicTimeStamp)musicTimeStamp +{ + return midiTimeStampsPerMusicTimeStamp(self, musicTimeStamp); +} + #pragma mark - Tempo -- (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +static Float64 tempoAtMIDITimeStamp(MIKMIDIClock *self, MIDITimeStamp midiTimeStamp) { __block Float64 tempo = 0; - [self dispatchToClockQueue:^{ - if (self.isReady) { - if (midiTimeStamp >= self.lastSyncedMIDITimeStamp) { - tempo = self.currentTempo; + dispatchToClockQueue(self, ^{ + if (self->_ready) { + if (midiTimeStamp >= self->_lastSyncedMIDITimeStamp) { + tempo = self->_currentTempo; } else { - tempo = [[self clockForMIDITimeStamp:midiTimeStamp] currentTempo]; + tempo = [clockForMIDITimeStamp(self, midiTimeStamp) currentTempo]; } } - }]; + }); return tempo; } +- (Float64)tempoAtMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + return tempoAtMIDITimeStamp(self, midiTimeStamp); +} + +Float64 tempoAtMusicTimeStamp(MIKMIDIClock *self, MusicTimeStamp musicTimeStamp) +{ + return tempoAtMIDITimeStamp(self, midiTimeStampForMusicTimeStamp(self, musicTimeStamp)); +} + - (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp { - return [self tempoAtMIDITimeStamp:[self midiTimeStampForMusicTimeStamp:musicTimeStamp]]; + return tempoAtMusicTimeStamp(self, musicTimeStamp); } #pragma mark - Historical Clocks -- (MIKMIDIClock *)clockForMIDITimeStamp:(MIDITimeStamp)midiTimeStamp +static MIKMIDIClock *clockForMIDITimeStamp(MIKMIDIClock *self, MIDITimeStamp midiTimeStamp) { MIKMIDIClock *clock = self; - NSDictionary *historicalClocks = self.historicalClocks; - for (NSNumber *historicalClockTimeStamp in [[self.historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { + NSDictionary *historicalClocks = self->_historicalClocks; + for (NSNumber *historicalClockTimeStamp in [[self->_historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { clock = historicalClocks[historicalClockTimeStamp]; } else { @@ -276,17 +306,7 @@ - (MIKMIDIClock *)syncedClock return (MIKMIDIClock *)[MIKMIDISyncedClockProxy syncedClockWithClock:self]; } -#pragma mark - Class Methods - -+ (Float64)secondsPerMIDITimeStamp -{ - return MIKMIDIClockSecondsPerMIDITimeStamp(); -} - -+ (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval -{ - return MIKMIDIClockMIDITimeStampsPerTimeInterval(timeInterval); -} +#pragma mark - Functions Float64 MIKMIDIClockSecondsPerMIDITimeStamp() { @@ -301,7 +321,6 @@ Float64 MIKMIDIClockSecondsPerMIDITimeStamp() } - Float64 MIKMIDIClockMIDITimeStampsPerTimeInterval(NSTimeInterval timeInterval) { static Float64 midiTimeStampsPerSecond = 0; @@ -316,6 +335,16 @@ - (void)setMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withTempo:(Float64)temp [self syncMusicTimeStamp:musicTimeStamp withMIDITimeStamp:midiTimeStamp tempo:tempo]; } ++ (Float64)secondsPerMIDITimeStamp +{ + return MIKMIDIClockSecondsPerMIDITimeStamp(); +} + ++ (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval +{ + return MIKMIDIClockMIDITimeStampsPerTimeInterval(timeInterval); +} + @end @@ -332,21 +361,55 @@ + (instancetype)syncedClockWithClock:(MIKMIDIClock *)masterClock - (void)forwardInvocation:(NSInvocation *)invocation { SEL selector = invocation.selector; - if (selector == @selector(syncMusicTimeStamp:withMIDITimeStamp:tempo:)) return; - if (selector == @selector(unsyncMusicTimeStampsAndTemposFromMIDITimeStamps)) return; - if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; // deprecated - if (selector == @selector(syncedClock)) { + // Optimizations + if (selector == @selector(midiTimeStampForMusicTimeStamp:)) { + MusicTimeStamp musicTimeStamp; + [invocation getArgument:&musicTimeStamp atIndex:2]; + + MIDITimeStamp midiTimeStamp = midiTimeStampForMusicTimeStamp(_masterClock, musicTimeStamp); + return [invocation setReturnValue:&midiTimeStamp]; + } else if (selector == @selector(musicTimeStampForMIDITimeStamp:)) { + MIDITimeStamp midiTimeStamp; + [invocation getArgument:&midiTimeStamp atIndex:2]; + + MusicTimeStamp musicTimeStamp = musicTimeStampForMIDITimeStamp(_masterClock, midiTimeStamp); + return [invocation setReturnValue:&musicTimeStamp]; + } else if (selector == @selector(tempoAtMIDITimeStamp:)) { + MIDITimeStamp midiTimeStamp; + [invocation getArgument:&midiTimeStamp atIndex:2]; + + Float64 tempo = tempoAtMIDITimeStamp(_masterClock, midiTimeStamp); + return [invocation setReturnValue:&tempo]; + } else if (selector == @selector(tempoAtMusicTimeStamp:)) { + MusicTimeStamp musicTimeStamp; + [invocation getArgument:&musicTimeStamp atIndex:2]; + + Float64 tempo = tempoAtMusicTimeStamp(_masterClock, musicTimeStamp); + return [invocation setReturnValue:&tempo]; + } else if (selector == @selector(midiTimeStampsPerMusicTimeStamp:)) { + MusicTimeStamp musicTimeStamp; + [invocation getArgument:&musicTimeStamp atIndex:2]; + + MIDITimeStamp midiTimeStamps = midiTimeStampsPerMusicTimeStamp(_masterClock, musicTimeStamp); + return [invocation setReturnValue:&midiTimeStamps]; + } else if (selector == @selector(syncedClock)) { MIKMIDISyncedClockProxy *syncedClock = self; return [invocation setReturnValue:&syncedClock]; } + // Ignored selectors + if (selector == @selector(syncMusicTimeStamp:withMIDITimeStamp:tempo:)) return; + if (selector == @selector(unsyncMusicTimeStampsAndTemposFromMIDITimeStamps)) return; + if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; // deprecated + + // Pass through remaining selectors [invocation invokeWithTarget:self.masterClock]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { - return [self.masterClock methodSignatureForSelector:sel]; + return [_masterClock methodSignatureForSelector:sel]; } @end \ No newline at end of file From afa8caa0c083f365f6c92fb202a114d48a4de112 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Tue, 15 Sep 2015 08:59:43 -0500 Subject: [PATCH 224/284] Finished optimizing MIKMIDIClock. --- Source/MIKMIDIClock.m | 114 ++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 33 deletions(-) diff --git a/Source/MIKMIDIClock.m b/Source/MIKMIDIClock.m index 3ac556fa..efac24ed 100644 --- a/Source/MIKMIDIClock.m +++ b/Source/MIKMIDIClock.m @@ -36,8 +36,9 @@ @interface MIKMIDIClock () Float64 _musicTimeStampsPerMIDITimeStamp; Float64 _midiTimeStampsPerMusicTimeStamp; - NSMutableDictionary *_historicalClocks; - NSMutableOrderedSet *_historicalClockMIDITimeStamps; + CFMutableDictionaryRef _historicalClocks; + CFMutableSetRef _historicalClockMIDITimeStampsSet; + CFMutableArrayRef _historicalClockMIDITimeStampsArray; dispatch_queue_t _clockQueue; } @@ -81,6 +82,11 @@ - (instancetype)initWithQueue:(BOOL)createQueue return self; } +- (void)dealloc +{ + releaseHistoricalClocks(self); +} + #pragma mark - Queue static void dispatchToClockQueue(MIKMIDIClock *self, void(^block)()) @@ -103,34 +109,48 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID dispatchToClockQueue(self, ^{ if (_lastSyncedMIDITimeStamp) { // Add a clock to the historical clocks - NSMutableDictionary *historicalClocks = _historicalClocks; NSNumber *midiTimeStampNumber = @(midiTimeStamp); - NSMutableOrderedSet *historicalClockMIDITimeStamps = _historicalClockMIDITimeStamps; - if (!historicalClocks) { - historicalClocks = [NSMutableDictionary dictionary]; - _historicalClocks = historicalClocks; - _historicalClockMIDITimeStamps = [NSMutableOrderedSet orderedSet]; + if (!_historicalClocks) { + _historicalClocks = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + _historicalClockMIDITimeStampsSet = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); + _historicalClockMIDITimeStampsArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } else { // Remove clocks old enough to not be needed anymore MIDITimeStamp oldTimeStamp = MIKMIDIGetCurrentTimeStamp() - MIKMIDIClockMIDITimeStampsPerTimeInterval(kDurationToKeepHistoricalClocks); - NSUInteger count = historicalClockMIDITimeStamps.count; - NSMutableArray *timeStampsToRemove = [NSMutableArray array]; - NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; - for (NSUInteger i = 0; i < count; i++) { - NSNumber *timeStampNumber = historicalClockMIDITimeStamps[i]; + + CFIndex count = CFArrayGetCount(_historicalClockMIDITimeStampsArray); + CFMutableArrayRef timeStampsToRemove = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + CFMutableSetRef indexesToRemoveSet = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks); + CFMutableArrayRef indexesToRemoveArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + + for (CFIndex i = 0; i < count; i++) { + NSNumber *timeStampNumber = (__bridge NSNumber *)CFArrayGetValueAtIndex(_historicalClockMIDITimeStampsArray, i); MIDITimeStamp timeStamp = timeStampNumber.unsignedLongLongValue; if (timeStamp <= oldTimeStamp) { - [timeStampsToRemove addObject:timeStampNumber]; - [indexesToRemove addIndex:i]; + void *timeStampValue = (__bridge void *)timeStampNumber; + + CFArrayAppendValue(timeStampsToRemove, timeStampValue); + if (!CFSetContainsValue(indexesToRemoveSet, timeStampValue)) { + CFSetAddValue(indexesToRemoveSet, timeStampValue); + CFArrayAppendValue(indexesToRemoveArray, timeStampValue); + } } else { break; } } - if (timeStampsToRemove.count) { - [historicalClocks removeObjectsForKeys:timeStampsToRemove]; - [historicalClockMIDITimeStamps removeObjectsAtIndexes:indexesToRemove]; + + CFIndex timeStampsToRemoveCount = CFArrayGetCount(timeStampsToRemove); + for (CFIndex i = (timeStampsToRemoveCount - 1); i >= 0; i--) { + const void *timeStampValue = CFArrayGetValueAtIndex(timeStampsToRemove, i); + CFDictionaryRemoveValue(_historicalClocks, timeStampValue); + CFSetRemoveValue(_historicalClockMIDITimeStampsSet, timeStampValue); + CFArrayRemoveValueAtIndex(_historicalClockMIDITimeStampsArray, i); } + + CFRelease(timeStampsToRemove); + CFRelease(indexesToRemoveSet); + CFRelease(indexesToRemoveArray); } // Add clock to history @@ -140,8 +160,14 @@ - (void)syncMusicTimeStamp:(MusicTimeStamp)musicTimeStamp withMIDITimeStamp:(MID historicalClock->_lastSyncedMIDITimeStamp = _lastSyncedMIDITimeStamp; historicalClock->_musicTimeStampsPerMIDITimeStamp = _musicTimeStampsPerMIDITimeStamp; historicalClock->_midiTimeStampsPerMusicTimeStamp = _midiTimeStampsPerMusicTimeStamp; - historicalClocks[midiTimeStampNumber] = historicalClock; - [historicalClockMIDITimeStamps addObject:midiTimeStampNumber]; + + void *midiTimeStampValue = (__bridge void *)midiTimeStampNumber; + CFDictionaryAddValue(_historicalClocks, midiTimeStampValue, (__bridge void *)historicalClock); + + if (!CFSetContainsValue(_historicalClockMIDITimeStampsSet, midiTimeStampValue)) { + CFSetAddValue(_historicalClockMIDITimeStampsSet, midiTimeStampValue); + CFArrayAppendValue(_historicalClockMIDITimeStampsArray, midiTimeStampValue); + } } // Update new tempo and timing information @@ -166,8 +192,7 @@ - (void)unsyncMusicTimeStampsAndTemposFromMIDITimeStamps dispatchToClockQueue(self, ^{ _ready = NO; _currentTempo = 0; - _historicalClocks = nil; - _historicalClockMIDITimeStamps = nil; + releaseHistoricalClocks(self); }); [self didChangeValueForKey:@"ready"]; } @@ -212,10 +237,12 @@ static MIDITimeStamp midiTimeStampForMusicTimeStamp(MIKMIDIClock *self, MusicTim midiTimeStamp = round(musicTimeStamp * self->_midiTimeStampsPerMusicTimeStamp) + self->_timeStampZero; - if (midiTimeStamp < self->_lastSyncedMIDITimeStamp) { - NSDictionary *historicalClocks = self->_historicalClocks; - for (NSNumber *historicalClockTimeStamp in [[self->_historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { - MIKMIDIClock *clock = historicalClocks[historicalClockTimeStamp]; + if (midiTimeStamp < self->_lastSyncedMIDITimeStamp && self->_historicalClockMIDITimeStampsArray) { + CFIndex historicalClockMIDITimeStampsCount = CFArrayGetCount(self->_historicalClockMIDITimeStampsArray); + for (CFIndex i = (historicalClockMIDITimeStampsCount - 1); i >= 0; i--) { + const void *midiTimeStampValue = CFArrayGetValueAtIndex(self->_historicalClockMIDITimeStampsArray, i); + + MIKMIDIClock *clock = (__bridge MIKMIDIClock *)CFDictionaryGetValue(self->_historicalClocks, midiTimeStampValue); MIDITimeStamp historicalMIDITimeStamp = round(musicTimeStamp * clock->_midiTimeStampsPerMusicTimeStamp) + clock->_timeStampZero; if (historicalMIDITimeStamp >= clock->_lastSyncedMIDITimeStamp) { midiTimeStamp = historicalMIDITimeStamp; @@ -288,17 +315,38 @@ - (Float64)tempoAtMusicTimeStamp:(MusicTimeStamp)musicTimeStamp static MIKMIDIClock *clockForMIDITimeStamp(MIKMIDIClock *self, MIDITimeStamp midiTimeStamp) { MIKMIDIClock *clock = self; - NSDictionary *historicalClocks = self->_historicalClocks; - for (NSNumber *historicalClockTimeStamp in [[self->_historicalClockMIDITimeStamps reverseObjectEnumerator] allObjects]) { - if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { - clock = historicalClocks[historicalClockTimeStamp]; - } else { - break; + + if (self->_historicalClockMIDITimeStampsArray) { + CFIndex count = CFArrayGetCount(self->_historicalClockMIDITimeStampsArray); + for (CFIndex i = (count - 1); i >= 0; i--) { + NSNumber *historicalClockTimeStamp = (__bridge NSNumber *)CFArrayGetValueAtIndex(self->_historicalClockMIDITimeStampsArray, i); + if ([historicalClockTimeStamp unsignedLongLongValue] > midiTimeStamp) { + clock = (__bridge MIKMIDIClock *)CFDictionaryGetValue(self->_historicalClocks, (__bridge void *)historicalClockTimeStamp); + } else { + break; + } } } + return clock; } +static void releaseHistoricalClocks(MIKMIDIClock *self) +{ + if (self->_historicalClocks) { + CFRelease(self->_historicalClocks); + self->_historicalClocks = NULL; + } + if (self->_historicalClockMIDITimeStampsSet) { + CFRelease(self->_historicalClockMIDITimeStampsSet); + self->_historicalClockMIDITimeStampsSet = NULL; + } + if (self->_historicalClockMIDITimeStampsArray) { + CFRelease(self->_historicalClockMIDITimeStampsArray); + self->_historicalClockMIDITimeStampsArray = NULL; + } +} + #pragma mark - Synced Clock - (MIKMIDIClock *)syncedClock @@ -404,7 +452,7 @@ - (void)forwardInvocation:(NSInvocation *)invocation if (selector == @selector(setMusicTimeStamp:withTempo:atMIDITimeStamp:)) return; // deprecated // Pass through remaining selectors - [invocation invokeWithTarget:self.masterClock]; + [invocation invokeWithTarget:_masterClock]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel From a4f44efaf2dac8d52ac65d6471d33029bd27c562 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Wed, 21 Oct 2015 11:35:13 -0500 Subject: [PATCH 225/284] Now setting latestScheduledMIDITimeStamp to the current time stamp when starting playing rather than -1. This fixes issues with observers using the MIDI clock to find the equivalent MusicTimeStamp of that MIDITimeStamp. --- Source/MIKMIDISequencer.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index bbc2a8b6..67e9d00a 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -184,14 +184,14 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi dispatch_sync(queue, ^{ self.pendingNoteOffs = [NSMutableDictionary dictionary]; - self.latestScheduledMIDITimeStamp = midiTimeStamp - 1; + self.latestScheduledMIDITimeStamp = midiTimeStamp; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.processingQueue); if (!timer) return NSLog(@"Unable to create processing timer for %@.", [self class]); self.processingTimer = timer; dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.05 * NSEC_PER_SEC, 0.05 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ - [self processSequenceStartingFromMIDITimeStamp:self.latestScheduledMIDITimeStamp + 1]; + [self processSequenceStartingFromMIDITimeStamp:self.latestScheduledMIDITimeStamp]; }); dispatch_resume(timer); From 234ce47074758d25957e0c378fe36446f389ee22 Mon Sep 17 00:00:00 2001 From: Chris Flesner Date: Mon, 2 Nov 2015 16:29:43 -0600 Subject: [PATCH 226/284] Fixed issue where MIKMIDITrack instances would always return a length of 0, unless otherwise set. --- Source/MIKMIDITrack.h | 4 +++- Source/MIKMIDITrack.m | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index ef59e9c6..49310fca 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -212,7 +212,9 @@ /** * The length of the MIDI track. * - * This property can be observed using Key Value Observing. + * This property can be observed using Key Value Observing. + * + * @note This property will automatically get updated whenever the tracks events are changed. */ @property (nonatomic) MusicTimeStamp length; diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 18afde10..9ef9703f 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -655,29 +655,29 @@ - (void)setSolo:(BOOL)solo + (NSSet *)keyPathsForValuesAffectingLength { - return [NSSet setWithObjects:@"events", nil]; + return [NSSet setWithObjects:@"sortedEventsCache", nil]; } - (MusicTimeStamp)length { - __block MusicTimeStamp length = 0; + if (_length == -1) { + MusicTimeStamp lastStamp = 0; - [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ - UInt32 lengthLength = sizeof(length); - OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &length, &lengthLength); - if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - }]; + for (MIKMIDIEvent *event in self.events) { + MusicTimeStamp endStamp = [event respondsToSelector:@selector(endTimeStamp)] ? [(MIKMIDINoteEvent *)event endTimeStamp] : event.timeStamp; + if (endStamp > lastStamp) lastStamp = endStamp; + } - return length; + _length = lastStamp; + } + + return _length; } -- (void)setLength:(MusicTimeStamp)length +- (void)setSortedEventsCache:(NSArray *)sortedEventsCache { - [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ - MusicTimeStamp newLength = length; - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_TrackLength, &newLength, sizeof(length)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - }]; + _sortedEventsCache = sortedEventsCache; + _length = -1; } - (SInt16)timeResolution From ce917eee7da4a10e2aa266e6e9b2d816402b230b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 09:01:56 -0700 Subject: [PATCH 227/284] Issue #108: Added generics annotations to MIKMIDIDeviceManager, MIKMIDIDevice, and MIKMIDIEntity. --- Source/MIKMIDIDevice.h | 10 +++++++--- Source/MIKMIDIDevice.m | 4 +++- Source/MIKMIDIDeviceManager.h | 10 ++++++---- Source/MIKMIDIEntity.h | 12 ++++++++---- Source/MIKMIDIUtilities.h | 9 +++++++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Source/MIKMIDIDevice.h b/Source/MIKMIDIDevice.h index b2337ae7..f7aa5295 100644 --- a/Source/MIKMIDIDevice.h +++ b/Source/MIKMIDIDevice.h @@ -7,6 +7,10 @@ // #import "MIKMIDIObject.h" +#import "MIKMIDIUtilities.h" + +@class MIKMIDIEntity; +@class MIKMIDIEndpoint; /** * MIKMIDIDevice represents a MIDI device such as a DJ controller, MIDI piano keyboard, etc. @@ -84,7 +88,7 @@ * * @return An initialized MIKMIDIDevice instance. */ -+ (instancetype)deviceWithVirtualEndpoints:(NSArray *)endpoints; ++ (instancetype)deviceWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; /** * Creates and initializes a "virtual" MIKMIDIDevice instance from one or more virtual endpoints. @@ -104,7 +108,7 @@ * * @return An initialized MIKMIDIDevice instance. */ -- (instancetype)initWithVirtualEndpoints:(NSArray *)endpoints; +- (instancetype)initWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; /** * The manufacturer of the MIDI device. @@ -121,6 +125,6 @@ * receiver. Entities contain logically related source and destination endpoints. Often * a device will only have one entity. */ -@property (nonatomic, strong, readonly) NSArray *entities; +@property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDIEntity *) *entities; @end diff --git a/Source/MIKMIDIDevice.m b/Source/MIKMIDIDevice.m index 956bc253..d9035339 100644 --- a/Source/MIKMIDIDevice.m +++ b/Source/MIKMIDIDevice.m @@ -9,6 +9,8 @@ #import "MIKMIDIDevice.h" #import "MIKMIDIObject_SubclassMethods.h" #import "MIKMIDIEntity.h" +#import "MIKMIDISourceEndpoint.h" +#import "MIKMIDIDestinationEndpoint.h" #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) @@ -123,7 +125,7 @@ - (MIDIObjectRef)objectRef { if (self.isVirtual) { MIKMIDIEntity *entity = [self.entities firstObject]; - MIKMIDIObject *endpoint = [entity.sources count] ? [entity.sources firstObject] : [entity.destinations firstObject]; + MIKMIDIEndpoint *endpoint = [entity.sources count] ? [entity.sources firstObject] : [entity.destinations firstObject]; return endpoint.objectRef; } return [super objectRef]; diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index 711ac9bb..3387bcdb 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -8,7 +8,9 @@ #import #import "MIKMIDIInputPort.h" +#import "MIKMIDIUtilities.h" +@class MIKMIDIDevice; @class MIKMIDISourceEndpoint; @class MIKMIDIClientSourceEndpoint; @class MIKMIDIDestinationEndpoint; @@ -130,7 +132,7 @@ extern NSString * const MIKMIDIEndpointKey; * @see MIKMIDIDeviceWasAddedNotification * @see MIKMIDIDeviceWasRemovedNotification */ -@property (nonatomic, readonly) NSArray *availableDevices; +@property (nonatomic, readonly) MIKArrayOf(MIKMIDIDevice *) *availableDevices; /** * An NSArray containing MIKMIDISourceEndpoint instances representing virtual MIDI sources (inputs) on the system. @@ -142,7 +144,7 @@ extern NSString * const MIKMIDIEndpointKey; * @see MIKMIDIVirtualEndpointWasAddedNotification * @see MIKMIDIVirtualEndpointWasRemovedNotification */ -@property (nonatomic, readonly) NSArray *virtualSources; +@property (nonatomic, readonly) MIKArrayOf(MIKMIDISourceEndpoint *) *virtualSources; /** * An NSArray containing MIKMIDIDestinationEndpoint instances representing virtual @@ -155,12 +157,12 @@ extern NSString * const MIKMIDIEndpointKey; * @see MIKMIDIVirtualEndpointWasAddedNotification * @see MIKMIDIVirtualEndpointWasRemovedNotification */ -@property (nonatomic, readonly) NSArray *virtualDestinations; // Array of MIKMIDIDestinationEndpoints +@property (nonatomic, readonly) MIKArrayOf(MIKMIDIDestinationEndpoint *) *virtualDestinations; /** * An NSArray of MIKMIDISourceEndpoint instances that are connected to at least one event handler. */ -@property (nonatomic, readonly) NSArray *connectedInputSources; // Array of MIKMIDISourceEndpoints +@property (nonatomic, readonly) MIKArrayOf(MIKMIDISourceEndpoint *) *connectedInputSources; @end diff --git a/Source/MIKMIDIEntity.h b/Source/MIKMIDIEntity.h index 96d378df..15f29f70 100644 --- a/Source/MIKMIDIEntity.h +++ b/Source/MIKMIDIEntity.h @@ -7,8 +7,12 @@ // #import "MIKMIDIObject.h" +#import "MIKMIDIUtilities.h" @class MIKMIDIDevice; +@class MIKMIDIEndpoint; +@class MIKMIDISourceEndpoint; +@class MIKMIDIDestinationEndpoint; /** * MIKMIDIEntity represents a logical grouping of endpoints within a MIDI device. It essentially @@ -30,7 +34,7 @@ * * @see +[MIKMIDIDevice deviceWithVirtualEndpoints:] */ -+ (instancetype)entityWithVirtualEndpoints:(NSArray *)endpoints; ++ (instancetype)entityWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; /** * Creates and initializes a "virtual" MIKMIDIEntity instance from one or more virtual endpoints. @@ -43,7 +47,7 @@ * * @see -[MIKMIDIDevice initWithVirtualEndpoints:] */ -- (instancetype)initWithVirtualEndpoints:(NSArray *)endpoints; +- (instancetype)initWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; /** * The device that contains the receiver. May be nil if the receiver is a virtual entity not contained @@ -55,12 +59,12 @@ * The source (input) endpoints contained by the receiver. * An array of MIKMIDISourceEndpoint instances. */ -@property (nonatomic, readonly) NSArray *sources; +@property (nonatomic, readonly) MIKArrayOf(MIKMIDISourceEndpoint *) *sources; /** * The destination (output) endpoints contained by the receiver. * An array of MIKMIDIDestinationEndpoint instances. */ -@property (nonatomic, readonly) NSArray *destinations; +@property (nonatomic, readonly) MIKArrayOf(MIKMIDIDestinationEndpoint *) *destinations; @end diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index 636c5594..089675b1 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -11,6 +11,14 @@ #import "MIKMIDIMappableResponder.h" #import "MIKMIDICommand.h" +#ifndef MIKArrayOf +#if __has_feature(objc_generics) +#define MIKArrayOf(TYPE) NSArray +#else +#define MIKArrayOf(TYPE) NSArray +#endif +#endif // #ifndef MIKArrayOf + NSString *MIKStringPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error); BOOL MIKSetStringPropertyOnMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSString *string, NSError *__autoreleasing*error); @@ -57,3 +65,4 @@ NSString *MIKMIDINoteLetterForMIDINoteNumber(UInt8 noteNumber); */ NSString *MIKMIDINoteLetterAndOctaveForMIDINote(UInt8 noteNumber); + From 84e2950c0e939d84769e2b2194416658d7b4b459 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 09:58:56 -0700 Subject: [PATCH 228/284] Added some nullability annotations. --- Source/MIKMIDIEntity.h | 10 +++++++--- Source/MIKMIDIObject.h | 13 +++++++++---- Source/MIKMIDIObject.m | 4 ++-- Source/MIKMIDIUtilities.h | 9 +++++++++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/Source/MIKMIDIEntity.h b/Source/MIKMIDIEntity.h index 15f29f70..84af9812 100644 --- a/Source/MIKMIDIEntity.h +++ b/Source/MIKMIDIEntity.h @@ -14,6 +14,8 @@ @class MIKMIDISourceEndpoint; @class MIKMIDIDestinationEndpoint; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIEntity represents a logical grouping of endpoints within a MIDI device. It essentially * acts as a simple container for endpoints. @@ -34,7 +36,7 @@ * * @see +[MIKMIDIDevice deviceWithVirtualEndpoints:] */ -+ (instancetype)entityWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; ++ (nullable instancetype)entityWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; /** * Creates and initializes a "virtual" MIKMIDIEntity instance from one or more virtual endpoints. @@ -47,13 +49,13 @@ * * @see -[MIKMIDIDevice initWithVirtualEndpoints:] */ -- (instancetype)initWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; +- (nullable instancetype)initWithVirtualEndpoints:(MIKArrayOf(MIKMIDIEndpoint *) *)endpoints; /** * The device that contains the receiver. May be nil if the receiver is a virtual entity not contained * by a virtual device. */ -@property (nonatomic, weak, readonly) MIKMIDIDevice *device; +@property (nonatomic, weak, readonly, nullable) MIKMIDIDevice *device; /** * The source (input) endpoints contained by the receiver. @@ -68,3 +70,5 @@ @property (nonatomic, readonly) MIKArrayOf(MIKMIDIDestinationEndpoint *) *destinations; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIObject.h b/Source/MIKMIDIObject.h index 121020c6..184a3343 100644 --- a/Source/MIKMIDIObject.h +++ b/Source/MIKMIDIObject.h @@ -8,6 +8,9 @@ #import #import +#import "MIKMIDIUtilities.h" + +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDIObject is the base class for all of MIKMIDI's Objective-C wrapper classes for CoreMIDI classes. @@ -27,7 +30,7 @@ * * @return An instance of the appropriate subclass of MIKMIDIObject. */ -+ (instancetype)MIDIObjectWithObjectRef:(MIDIObjectRef)objectRef; // Returns a subclass of MIKMIDIObject (device, entity, or endpoint) ++ (nullable instancetype)MIDIObjectWithObjectRef:(MIDIObjectRef)objectRef; // Returns a subclass of MIKMIDIObject (device, entity, or endpoint) /** * Creates and initializes an MIKMIDIObject instance. Returns an instance of a @@ -38,7 +41,7 @@ * * @return An instance of the appropriate subclass of MIKMIDIObject. */ -- (id)initWithObjectRef:(MIDIObjectRef)objectRef; +- (nullable instancetype)initWithObjectRef:(MIDIObjectRef)objectRef; /** * Used to get a dictionary containing key/value pairs corresponding to @@ -69,13 +72,13 @@ /** * The name of the receiver. Devices, entities, and endpoints may all have names. */ -@property (nonatomic, strong) NSString *name; +@property (nonatomic, strong, nullable) NSString *name; /** * The Apple-recommended user-visible name of the receiver. May be * identical to the value returned by -name. */ -@property (nonatomic, strong, readonly) NSString *displayName; +@property (nonatomic, strong, readonly, nullable) NSString *displayName; /** * Indicates whether the object is "virtual". This has slightly different meanings @@ -97,3 +100,5 @@ @property (nonatomic, readonly) BOOL isVirtual; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIObject.m b/Source/MIKMIDIObject.m index 17652f45..5cc3a930 100644 --- a/Source/MIKMIDIObject.m +++ b/Source/MIKMIDIObject.m @@ -109,8 +109,8 @@ - (NSDictionary *)propertiesDictionary { CFPropertyListRef properties = NULL; OSStatus err = MIDIObjectGetProperties(self.objectRef, &properties, true); - if (err) return nil; - if (![(__bridge id)properties isKindOfClass:[NSDictionary class]]) return nil; + if (err) return @{}; + if (![(__bridge id)properties isKindOfClass:[NSDictionary class]]) return @{}; return (__bridge NSDictionary *)properties; } diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index 089675b1..d2a9b693 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -11,6 +11,15 @@ #import "MIKMIDIMappableResponder.h" #import "MIKMIDICommand.h" +// Keep older versions of the compiler happy +#ifndef NS_ASSUME_NONNULL_BEGIN +#define NS_ASSUME_NONNULL_BEGIN +#define NS_ASSUME_NONNULL_END +#define nullable +#define nonnullable +#define __nullable +#endif + #ifndef MIKArrayOf #if __has_feature(objc_generics) #define MIKArrayOf(TYPE) NSArray From c72f864d83e95fa00ba93d1e3c368f6df767352a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 10:04:55 -0700 Subject: [PATCH 229/284] Issue #39 & #108: Moved conditional macros for nullability and generics into new MIKMIDICompilerCompatibility.h header. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 6 +++++ Source/MIKMIDI.h | 1 + Source/MIKMIDICompilerCompatibility.h | 29 +++++++++++++++++++++ Source/MIKMIDIDevice.h | 2 +- Source/MIKMIDIDeviceManager.h | 2 +- Source/MIKMIDIEntity.h | 2 +- Source/MIKMIDIObject.h | 2 +- Source/MIKMIDIUtilities.h | 17 ------------ 8 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 Source/MIKMIDICompilerCompatibility.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index efa7eea8..134c39bc 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -56,6 +56,8 @@ 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; 83C850C91B7AA47C001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */; }; + 9D07CAC71BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D07CAC81BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F01B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; @@ -348,6 +350,7 @@ 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingManager_SubclassMethods.h; sourceTree = ""; }; 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequence+MIKMIDIPrivate.h"; sourceTree = ""; }; + 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICompilerCompatibility.h; sourceTree = ""; }; 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; @@ -831,6 +834,7 @@ 833B73DD1A26346F00E0CC9F /* MIKMIDIClock.m */, 9DF99E7B18318D44004EE5F4 /* MIKMIDIPrivateUtilities.h */, 9DF99E7C18318D44004EE5F4 /* MIKMIDIPrivateUtilities.m */, + 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */, 9D7027D11ACC9D4C009AFAED /* Debugging */, ); name = Utilities; @@ -896,6 +900,7 @@ 9DBEBD581AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */, 9D74EF8117A713A100BEE89F /* MIKMIDINoteOnCommand.h in Headers */, 9D74EF8317A713A100BEE89F /* MIKMIDIObject.h in Headers */, + 9D07CAC71BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */, 9D74EF8617A713A100BEE89F /* MIKMIDIOutputPort.h in Headers */, 9D74EF8817A713A100BEE89F /* MIKMIDIPort.h in Headers */, 9D74EF8B17A713A100BEE89F /* MIKMIDIResponder.h in Headers */, @@ -985,6 +990,7 @@ 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 9DAF8B571A7B007300F46528 /* MIKMIDIEntity.h in Headers */, 9DAF8B621A7B008A00F46528 /* MIKMIDIControlChangeCommand.h in Headers */, + 9D07CAC81BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */, 9DAF8B821A7B00BB00F46528 /* MIKMIDIClock.h in Headers */, 9DAF8B6B1A7B00A700F46528 /* MIKMIDISequence.h in Headers */, 9DAF8B511A7B004A00F46528 /* MIKMIDI.h in Headers */, diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index f9dcf675..5a77cefc 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -92,3 +92,4 @@ // Utilities #import "MIKMIDIUtilities.h" #import "MIKMIDIErrors.h" +#import "MIKMIDICompilerCompatibility.h" diff --git a/Source/MIKMIDICompilerCompatibility.h b/Source/MIKMIDICompilerCompatibility.h new file mode 100644 index 00000000..0e9b82d6 --- /dev/null +++ b/Source/MIKMIDICompilerCompatibility.h @@ -0,0 +1,29 @@ +// +// MIKMIDICompilerCompatibility.h +// MIKMIDI +// +// Created by Andrew Madsen on 11/4/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +/* + This header contains macros used to adopt new compiler features without breaking support for building MIKMIDI + with older compiler versions. + */ + +// Keep older versions of the compiler happy +#ifndef NS_ASSUME_NONNULL_BEGIN +#define NS_ASSUME_NONNULL_BEGIN +#define NS_ASSUME_NONNULL_END +#define nullable +#define nonnullable +#define __nullable +#endif + +#ifndef MIKArrayOf +#if __has_feature(objc_generics) +#define MIKArrayOf(TYPE) NSArray +#else +#define MIKArrayOf(TYPE) NSArray +#endif +#endif // #ifndef MIKArrayOf diff --git a/Source/MIKMIDIDevice.h b/Source/MIKMIDIDevice.h index f7aa5295..b4df1297 100644 --- a/Source/MIKMIDIDevice.h +++ b/Source/MIKMIDIDevice.h @@ -7,7 +7,7 @@ // #import "MIKMIDIObject.h" -#import "MIKMIDIUtilities.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIEntity; @class MIKMIDIEndpoint; diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index 3387bcdb..d6086aff 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -8,7 +8,7 @@ #import #import "MIKMIDIInputPort.h" -#import "MIKMIDIUtilities.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIDevice; @class MIKMIDISourceEndpoint; diff --git a/Source/MIKMIDIEntity.h b/Source/MIKMIDIEntity.h index 84af9812..02a99888 100644 --- a/Source/MIKMIDIEntity.h +++ b/Source/MIKMIDIEntity.h @@ -7,7 +7,7 @@ // #import "MIKMIDIObject.h" -#import "MIKMIDIUtilities.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIDevice; @class MIKMIDIEndpoint; diff --git a/Source/MIKMIDIObject.h b/Source/MIKMIDIObject.h index 184a3343..d7026d35 100644 --- a/Source/MIKMIDIObject.h +++ b/Source/MIKMIDIObject.h @@ -8,7 +8,7 @@ #import #import -#import "MIKMIDIUtilities.h" +#import "MIKMIDICompilerCompatibility.h" NS_ASSUME_NONNULL_BEGIN diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index d2a9b693..d249f9cb 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -11,23 +11,6 @@ #import "MIKMIDIMappableResponder.h" #import "MIKMIDICommand.h" -// Keep older versions of the compiler happy -#ifndef NS_ASSUME_NONNULL_BEGIN -#define NS_ASSUME_NONNULL_BEGIN -#define NS_ASSUME_NONNULL_END -#define nullable -#define nonnullable -#define __nullable -#endif - -#ifndef MIKArrayOf -#if __has_feature(objc_generics) -#define MIKArrayOf(TYPE) NSArray -#else -#define MIKArrayOf(TYPE) NSArray -#endif -#endif // #ifndef MIKArrayOf - NSString *MIKStringPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error); BOOL MIKSetStringPropertyOnMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSString *string, NSError *__autoreleasing*error); From e5ecc2d1ab05d6390e053ce1a066fbdfa9f1e89b Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 10:08:03 -0700 Subject: [PATCH 230/284] Issue #39 & #108: Added nullability and more generics annotations to MIKMIDIDeviceManager. --- Source/MIKMIDIDeviceManager.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index d6086aff..5ad5e51b 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -16,6 +16,8 @@ @class MIKMIDIDestinationEndpoint; @class MIKMIDICommand; +NS_ASSUME_NONNULL_BEGIN + // Notifications /** * Posted whenever a device is added (connected) to the system. @@ -83,7 +85,7 @@ extern NSString * const MIKMIDIEndpointKey; * * @return A connection token to be used to disconnect the input, or nil if an error occurred. The connection token is opaque. */ -- (id)connectInput:(MIKMIDISourceEndpoint *)endpoint error:(NSError **)error eventHandler:(MIKMIDIEventHandlerBlock)eventHandler; +- (nullable id)connectInput:(MIKMIDISourceEndpoint *)endpoint error:(NSError **)error eventHandler:(MIKMIDIEventHandlerBlock)eventHandler; /** * Disconnects a previously connected MIDI input/source endpoint. The connectionToken argument @@ -105,7 +107,7 @@ extern NSString * const MIKMIDIEndpointKey; * * @return YES if the commands were successfully sent, NO if an error occurred. */ -- (BOOL)sendCommands:(NSArray *)commands toEndpoint:(MIKMIDIDestinationEndpoint *)endpoint error:(NSError **)error; +- (BOOL)sendCommands:(MIKArrayOf(MIKMIDICommand *) *)commands toEndpoint:(MIKMIDIDestinationEndpoint *)endpoint error:(NSError **)error; /** @@ -118,7 +120,7 @@ extern NSString * const MIKMIDIEndpointKey; * * @return YES if the commands were successfully sent, NO if an error occurred. */ -- (BOOL)sendCommands:(NSArray *)commands toVirtualEndpoint:(MIKMIDIClientSourceEndpoint *)endpoint error:(NSError **)error; +- (BOOL)sendCommands:(MIKArrayOf(MIKMIDICommand *) *)commands toVirtualEndpoint:(MIKMIDIClientSourceEndpoint *)endpoint error:(NSError **)error; @@ -166,3 +168,5 @@ extern NSString * const MIKMIDIEndpointKey; @property (nonatomic, readonly) MIKArrayOf(MIKMIDISourceEndpoint *) *connectedInputSources; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file From ef212c6801965dc8a4a89991880fd853a1e681cb Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 10:19:21 -0700 Subject: [PATCH 231/284] Issue #39 & #108: Added nullability and more generics annotations to MIKMIDIInputPort. --- Source/MIKMIDICompilerCompatibility.h | 2 ++ Source/MIKMIDIInputPort.h | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDICompilerCompatibility.h b/Source/MIKMIDICompilerCompatibility.h index 0e9b82d6..344483a8 100644 --- a/Source/MIKMIDICompilerCompatibility.h +++ b/Source/MIKMIDICompilerCompatibility.h @@ -23,7 +23,9 @@ #ifndef MIKArrayOf #if __has_feature(objc_generics) #define MIKArrayOf(TYPE) NSArray +#define MIKArrayOfKindOf(TYPE) NSArray<__kindof TYPE> #else #define MIKArrayOf(TYPE) NSArray +#define MIKArrayOfKindOf(TYPE) NSArray #endif #endif // #ifndef MIKArrayOf diff --git a/Source/MIKMIDIInputPort.h b/Source/MIKMIDIInputPort.h index 582f579a..8704f698 100644 --- a/Source/MIKMIDIInputPort.h +++ b/Source/MIKMIDIInputPort.h @@ -7,11 +7,15 @@ // #import "MIKMIDIPort.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIEndpoint; @class MIKMIDISourceEndpoint; +@class MIKMIDICommand; -typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, NSArray *commands); // commands in an array of MIKMIDICommands +NS_ASSUME_NONNULL_BEGIN + +typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, MIKArrayOf(MIKMIDICommand *) *commands); // commands in an array of MIKMIDICommands /** * MIKMIDIInputPort is an Objective-C wrapper for CoreMIDI's MIDIPort class, and is only for source ports. @@ -23,7 +27,7 @@ typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, NSArray * - (BOOL)connectToSource:(MIKMIDISourceEndpoint *)source error:(NSError **)error; - (void)disconnectFromSource:(MIKMIDISourceEndpoint *)source; -@property (nonatomic, strong, readonly) NSArray *connectedSources; +@property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDIEndpoint *) *connectedSources; @property (nonatomic, strong, readonly) NSSet *eventHandlers; - (id)addEventHandler:(MIKMIDIEventHandlerBlock)eventHandler; // Returns a token @@ -33,3 +37,5 @@ typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, NSArray * @property (nonatomic) BOOL coalesces14BitControlChangeCommands; // Default is YES @end + +NS_ASSUME_NONNULL_END \ No newline at end of file From f71e39994af64010915d396dbd55f4af6436ff94 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 15:00:59 -0700 Subject: [PATCH 232/284] Issue #109: Refactored MIKMIDIInputPort and MIKMIDIDeviceManager. Simplifies public endpoint disconnection API. --- Source/MIKMIDICompilerCompatibility.h | 8 ++ Source/MIKMIDIDeviceManager.h | 19 +++- Source/MIKMIDIDeviceManager.m | 73 +++++------- Source/MIKMIDIEndpointSynthesizer.m | 2 +- Source/MIKMIDIInputPort.h | 11 +- Source/MIKMIDIInputPort.m | 155 ++++++++++++++++++-------- Source/MIKMIDIMappingGenerator.m | 5 +- Source/MIKMIDIOutputPort.h | 2 +- 8 files changed, 169 insertions(+), 106 deletions(-) diff --git a/Source/MIKMIDICompilerCompatibility.h b/Source/MIKMIDICompilerCompatibility.h index 344483a8..de4b6e6b 100644 --- a/Source/MIKMIDICompilerCompatibility.h +++ b/Source/MIKMIDICompilerCompatibility.h @@ -22,10 +22,18 @@ #ifndef MIKArrayOf #if __has_feature(objc_generics) + #define MIKArrayOf(TYPE) NSArray #define MIKArrayOfKindOf(TYPE) NSArray<__kindof TYPE> + +#define MIKMapTableOf(KEYTYPE, OBJTYPE) NSMapTable + #else + #define MIKArrayOf(TYPE) NSArray #define MIKArrayOfKindOf(TYPE) NSArray + +#define MIKMapTableOf(KEYTYPE, OBJTYPE) NSMapTable + #endif #endif // #ifndef MIKArrayOf diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index 5ad5e51b..2627424a 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -92,10 +92,9 @@ extern NSString * const MIKMIDIEndpointKey; * must be a token previously returned by -connectInput:error:eventHandler:. Only the * event handler block passed into the call that returned the token will be disconnected. * - * @param endpoint The MIKMIDISourceEndpoint instance from which to disconnect. * @param connectionToken The connection token returned by -connectInput:error:eventHandler: when the input was connected. */ -- (void)disconnectInput:(MIKMIDISourceEndpoint *)endpoint forConnectionToken:(id)connectionToken; +- (void)disconnectConnectionforToken:(id)connectionToken; /** * Used to send MIDI messages/commands from your application to a MIDI output endpoint. @@ -169,4 +168,20 @@ extern NSString * const MIKMIDIEndpointKey; @end +@interface MIKMIDIDeviceManager (Deprecated) + +/** + * @deprecated Use disconnectConnectionforToken: instead. This method now simply calls through to that one. + * + * Disconnects a previously connected MIDI input/source endpoint. The connectionToken argument + * must be a token previously returned by -connectInput:error:eventHandler:. Only the + * event handler block passed into the call that returned the token will be disconnected. + * + * @param endpoint This argument is ignored. + * @param connectionToken The connection token returned by -connectInput:error:eventHandler: when the input was connected. + */ +- (void)disconnectInput:(nullable MIKMIDISourceEndpoint *)endpoint forConnectionToken:(id)connectionToken DEPRECATED_ATTRIBUTE; + +@end + NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIDeviceManager.m b/Source/MIKMIDIDeviceManager.m index c17771bd..79813c10 100644 --- a/Source/MIKMIDIDeviceManager.m +++ b/Source/MIKMIDIDeviceManager.m @@ -47,7 +47,7 @@ - (void)removeInternalVirtualSourcesObject:(MIKMIDISourceEndpoint *)source; - (void)addInternalVirtualDestinationsObject:(MIKMIDIDestinationEndpoint *)destination; - (void)removeInternalVirtualDestinationsObject:(MIKMIDIDestinationEndpoint *)destination; -@property (nonatomic, strong) NSMutableSet *internalConnectedInputPorts; +@property (nonatomic, strong) MIKMIDIInputPort *inputPort; @property (nonatomic, strong) MIKMIDIOutputPort *outputPort; @end @@ -72,7 +72,6 @@ - (id)init [self createClient]; [self retrieveAvailableDevices]; [self retrieveVirtualEndpoints]; - self.internalConnectedInputPorts = [[NSMutableSet alloc] init]; } return self; } @@ -91,26 +90,12 @@ - (id)copyWithZone:(NSZone *)zone - (id)connectInput:(MIKMIDISourceEndpoint *)endpoint error:(NSError **)error eventHandler:(MIKMIDIEventHandlerBlock)eventHandler { - MIKMIDIInputPort *port = [self inputPortConnectedToEndpoint:endpoint]; - if (!port) { - port = [[MIKMIDIInputPort alloc] initWithClient:self.client name:endpoint.name]; - if (![port connectToSource:endpoint error:error]) return nil; - } - - [self addInternalConnectedInputPortsObject:port]; - return [port addEventHandler:eventHandler]; + return [self.inputPort connectToSource:endpoint error:error eventHandler:eventHandler]; } -- (void)disconnectInput:(MIKMIDISourceEndpoint *)endpoint forConnectionToken:(id)connectionToken +- (void)disconnectConnectionforToken:(id)connectionToken { - MIKMIDIInputPort *port = [self inputPortConnectedToEndpoint:endpoint]; - if (!port) return; // Not connected - - [port removeEventHandlerForToken:connectionToken]; - if (![[port eventHandlers] count]) { - [port disconnectFromSource:endpoint]; - [self removeInternalConnectedInputPortsObject:port]; - } + [self.inputPort disconnectConnectionForToken:connectionToken]; } - (BOOL)sendCommands:(NSArray *)commands toEndpoint:(MIKMIDIDestinationEndpoint *)endpoint error:(NSError **)error; @@ -118,7 +103,6 @@ - (BOOL)sendCommands:(NSArray *)commands toEndpoint:(MIKMIDIDestinationEndpoint return [self.outputPort sendCommands:commands toDestination:endpoint error:error]; } - - (BOOL)sendCommands:(NSArray *)commands toVirtualEndpoint:(MIKMIDIClientSourceEndpoint *)endpoint error:(NSError **)error { return [endpoint sendCommands:commands error:error]; @@ -173,15 +157,6 @@ - (void)retrieveVirtualEndpoints self.internalVirtualDestinations = destinations; } -- (MIKMIDIInputPort *)inputPortConnectedToEndpoint:(MIKMIDIEndpoint *)endpoint -{ - for (MIKMIDIInputPort *port in self.internalConnectedInputPorts) { - if (![port isKindOfClass:[MIKMIDIInputPort class]]) continue; - if ([port.connectedSources containsObject:endpoint]) return port; - } - return nil; -} - #pragma mark - Callbacks - (void)handleMIDIObjectPropertyChangeNotification:(MIDIObjectPropertyChangeNotification *)notification @@ -368,10 +343,6 @@ + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key keyPaths = [keyPaths setByAddingObject:@"internalVirtualDestinations"]; } - if ([key isEqualToString:@"connectedInputSources"]) { - keyPaths = [keyPaths setByAddingObject:@"internalConnectedInputPorts"]; - } - return keyPaths; } @@ -411,33 +382,43 @@ - (void)removeInternalVirtualDestinationsObject:(MIKMIDIDestinationEndpoint *)de [self.internalVirtualDestinations removeObject:destination]; } -- (NSArray *)connectedInputSources ++ (NSSet *)keyPathsForValuesAffectingConnectedInputSources { - NSMutableSet *result = [NSMutableSet set]; - for (MIKMIDIInputPort *port in self.internalConnectedInputPorts) { - NSArray *connectedSources = port.connectedSources; - if (![connectedSources count]) continue; - [result addObjectsFromArray:connectedSources]; - } - return [result allObjects]; + return [NSSet setWithObjects:@"connectedSources", nil]; } -- (void)addInternalConnectedInputPortsObject:(MIKMIDIInputPort *)port +- (NSArray *)connectedInputSources { - [_internalConnectedInputPorts addObject:port]; + NSArray *result = self.inputPort.connectedSources; + if (!result) result = @[]; + return result; } -- (void)removeInternalConnectedInputPortsObject:(MIKMIDIInputPort *)port +- (MIKMIDIInputPort *)inputPort { - [_internalConnectedInputPorts removeObject:port]; + if (!_inputPort) { + _inputPort = [[MIKMIDIInputPort alloc] initWithClient:self.client name:@"InputPort"]; + } + return _inputPort; } - (MIKMIDIOutputPort *)outputPort { if (!_outputPort) { - self.outputPort = [[MIKMIDIOutputPort alloc] initWithClient:self.client name:@"OutputPort"]; + _outputPort = [[MIKMIDIOutputPort alloc] initWithClient:self.client name:@"OutputPort"]; } return _outputPort; } @end + +#pragma mark - + +@implementation MIKMIDIDeviceManager (Deprecated) + +- (void)disconnectInput:(MIKMIDISourceEndpoint *)endpoint forConnectionToken:(id)connectionToken +{ + [self disconnectConnectionforToken:connectionToken]; +} + +@end \ No newline at end of file diff --git a/Source/MIKMIDIEndpointSynthesizer.m b/Source/MIKMIDIEndpointSynthesizer.m index 3631f167..098f6de1 100644 --- a/Source/MIKMIDIEndpointSynthesizer.m +++ b/Source/MIKMIDIEndpointSynthesizer.m @@ -95,7 +95,7 @@ - (void)dealloc { if (_endpoint) { if ([_endpoint isKindOfClass:[MIKMIDISourceEndpoint class]]) { - [[MIKMIDIDeviceManager sharedDeviceManager] disconnectInput:(MIKMIDISourceEndpoint *)self.endpoint forConnectionToken:self.connectionToken]; + [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionforToken:self.connectionToken]; } // Don't need to do anything for a destination endpoint. __weak reference in the messages handler will automatically nil out. } diff --git a/Source/MIKMIDIInputPort.h b/Source/MIKMIDIInputPort.h index 8704f698..9a770c4e 100644 --- a/Source/MIKMIDIInputPort.h +++ b/Source/MIKMIDIInputPort.h @@ -24,16 +24,13 @@ typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, MIKArrayO */ @interface MIKMIDIInputPort : MIKMIDIPort -- (BOOL)connectToSource:(MIKMIDISourceEndpoint *)source error:(NSError **)error; -- (void)disconnectFromSource:(MIKMIDISourceEndpoint *)source; +- (id)connectToSource:(MIKMIDISourceEndpoint *)source + error:(NSError **)error + eventHandler:(MIKMIDIEventHandlerBlock)eventHandler; +- (void)disconnectConnectionForToken:(id)token; @property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDIEndpoint *) *connectedSources; -@property (nonatomic, strong, readonly) NSSet *eventHandlers; -- (id)addEventHandler:(MIKMIDIEventHandlerBlock)eventHandler; // Returns a token -- (void)removeEventHandlerForToken:(id)token; -- (void)removeAllEventHandlers; - @property (nonatomic) BOOL coalesces14BitControlChangeCommands; // Default is YES @end diff --git a/Source/MIKMIDIInputPort.m b/Source/MIKMIDIInputPort.m index c0804080..04997613 100644 --- a/Source/MIKMIDIInputPort.m +++ b/Source/MIKMIDIInputPort.m @@ -19,10 +19,19 @@ #error MIKMIDIInputPort.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIInputPort.m in the Build Phases for this target #endif +@interface MIKMIDIConnectionTokenAndEventHandler : NSObject + +- (instancetype)initWithConnectionToken:(NSString *)token eventHandler:(MIKMIDIEventHandlerBlock)eventHandler; + +@property (nonatomic, strong, readonly) NSString *connectionToken; +@property (nonatomic, strong, readonly) MIKMIDIEventHandlerBlock eventHandler; + +@end + @interface MIKMIDIInputPort () @property (nonatomic, strong) NSMutableArray *internalSources; -@property (nonatomic, strong, readwrite) NSMutableDictionary *eventHandlersByToken; +@property (nonatomic, strong, readwrite) MIKMapTableOf(MIKMIDIEndpoint *, NSMutableArray *) *handlerTokenPairsByEndpoint; @property (nonatomic, strong) NSMutableArray *bufferedMSBCommands; @property (nonatomic) dispatch_queue_t bufferedCommandQueue; @@ -30,9 +39,6 @@ @interface MIKMIDIInputPort () @end @implementation MIKMIDIInputPort -{ - NSMutableSet *_eventHandlers; -} - (id)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name { @@ -47,7 +53,7 @@ - (id)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name &port); if (error != noErr) { self = nil; return nil; } self.portRef = port; // MIKMIDIPort will take care of disposing of the port when needed - _eventHandlersByToken = [[NSMutableDictionary alloc] init]; + _handlerTokenPairsByEndpoint = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; _internalSources = [[NSMutableArray alloc] init]; _coalesces14BitControlChangeCommands = YES; @@ -67,6 +73,38 @@ - (void)dealloc #pragma mark - Public +- (id)connectToSource:(MIKMIDISourceEndpoint *)source + error:(NSError **)error + eventHandler:(MIKMIDIEventHandlerBlock)eventHandler +{ + error = error ?: &(NSError *__autoreleasing){ nil }; + if (![self.connectedSources containsObject:source] && + ![self connectToSource:source error:error]) { + return nil; + } + + NSString *uuidString = [self createNewConnectionToken]; + [self addConnectionToken:uuidString andEventHandler:eventHandler forSource:source]; + return uuidString; +} + +- (void)disconnectConnectionForToken:(id)token +{ + MIKMIDISourceEndpoint *source = [self sourceEndpointForConnectionToken:token]; + if (!source) return; // Already disconnected? + + [self removeEventHandlerForConnectionToken:token source:source]; + + NSArray *handlerPairs = [self.handlerTokenPairsByEndpoint objectForKey:source]; + if (![handlerPairs count]) { + [self disconnectFromSource:source]; + } +} + +#pragma mark - Private + +#pragma mark Connection / Disconnection + - (BOOL)connectToSource:(MIKMIDISourceEndpoint *)source error:(NSError **)error; { if ([self.connectedSources containsObject:source]) return YES; @@ -88,39 +126,61 @@ - (void)disconnectFromSource:(MIKMIDISourceEndpoint *)source [self removeInternalSourcesObject:source]; } -- (id)addEventHandler:(MIKMIDIEventHandlerBlock)eventHandler; // Returns a token +#pragma mark Event Handler Management + +- (NSString *)createNewConnectionToken { - CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); - NSString *uuidString = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); - CFRelease(uuid); - while ([self.eventHandlersByToken valueForKey:uuidString]) { - // Very unlikely, but just to be safe - uuid = CFUUIDCreate(kCFAllocatorDefault); - uuidString = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); - CFRelease(uuid); - } - - [self willChangeValueForKey:@"eventHandlers"]; - self.eventHandlersByToken[uuidString] = [eventHandler copy]; - [self didChangeValueForKey:@"eventHandlers"]; + NSString *uuidString = nil; + do { // Very unlikely, but just to be safe + uuidString = [[NSUUID UUID] UUIDString]; + MIKMIDIConnectionTokenAndEventHandler *existingPair = nil; + for (MIKMIDIConnectionTokenAndEventHandler *pair in self.handlerTokenPairsByEndpoint.objectEnumerator) { + if ([pair.connectionToken isEqualToString:uuidString]) { + existingPair = pair; + break; + } + } + if (!existingPair) break; + } while (1); return uuidString; } -- (void)removeEventHandlerForToken:(id)token; +- (void)addConnectionToken:(NSString *)connectionToken andEventHandler:(MIKMIDIEventHandlerBlock)eventHandler forSource:(MIKMIDISourceEndpoint *)source { - [self willChangeValueForKey:@"eventHandlers"]; - [self.eventHandlersByToken removeObjectForKey:token]; - [self didChangeValueForKey:@"eventHandlers"]; + MIKMIDIConnectionTokenAndEventHandler *tokenHandlerPair = + [[MIKMIDIConnectionTokenAndEventHandler alloc] initWithConnectionToken:connectionToken eventHandler:eventHandler]; + NSMutableArray *tokenPairs = [self.handlerTokenPairsByEndpoint objectForKey:source]; + if (!tokenPairs) { + tokenPairs = [NSMutableArray array]; + [self.handlerTokenPairsByEndpoint setObject:tokenPairs forKey:source]; + } + [tokenPairs addObject:tokenHandlerPair]; } -- (void)removeAllEventHandlers; +- (void)removeEventHandlerForConnectionToken:(NSString *)connectionToken source:(MIKMIDISourceEndpoint *)source { - [self willChangeValueForKey:@"eventHandlers"]; - [self.eventHandlersByToken removeAllObjects]; - [self didChangeValueForKey:@"eventHandlers"]; + NSMutableArray *handlerPairs = [self.handlerTokenPairsByEndpoint objectForKey:source]; + for (MIKMIDIConnectionTokenAndEventHandler *pair in [handlerPairs copy]) { + if ([pair.connectionToken isEqual:connectionToken]) { + [handlerPairs removeObject:pair]; + } + } } -#pragma mark - Private +- (MIKMIDISourceEndpoint *)sourceEndpointForConnectionToken:(NSString *)token +{ + for (MIKMIDISourceEndpoint *source in self.handlerTokenPairsByEndpoint.objectEnumerator) { + NSArray *handlerPairs = [self.handlerTokenPairsByEndpoint objectForKey:source]; + for (MIKMIDIConnectionTokenAndEventHandler *handlerPair in handlerPairs) { + if ([handlerPair.connectionToken isEqual:token]) { + return source; + } + } + } + return nil; +} + +#pragma mark Coaelescing - (BOOL)commandIsPossibleMSBOf14BitCommand:(MIKMIDICommand *)command { @@ -164,11 +224,14 @@ - (NSArray *)commandsByCoalescingCommands:(NSArray *)commands return [coalescedCommands copy]; } +#pragma mark Command Handling + - (void)sendCommands:(NSArray *)commands toEventHandlersFromSource:(MIKMIDISourceEndpoint *)source { dispatch_async(dispatch_get_main_queue(), ^{ - for (MIKMIDIEventHandlerBlock handler in self.eventHandlers) { - handler(source, commands); + NSArray *handlerPairs = [self.handlerTokenPairsByEndpoint objectForKey:source]; + for (MIKMIDIConnectionTokenAndEventHandler *handlerTokenPair in handlerPairs) { + handlerTokenPair.eventHandler(source, commands); } }); } @@ -225,16 +288,7 @@ void MIKMIDIPortReadCallback(const MIDIPacketList *pktList, void *readProcRefCon #pragma mark - Properties -+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key -{ - NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; - - if ([key isEqualToString:@"connectedSources"]) { - keyPaths = [keyPaths setByAddingObject:@"internalSources"]; - } - - return keyPaths; -} ++ (NSSet *)keyPathsForValuesAffectingConnectedSources { return [NSSet setWithObjects:@"internalSources", nil]; } - (NSArray *)connectedSources { return [self.internalSources copy]; } @@ -248,11 +302,6 @@ - (void)removeInternalSourcesObject:(MIKMIDISourceEndpoint *)source [self.internalSources removeObject:source]; } -- (NSSet *)eventHandlers -{ - return [NSSet setWithArray:[self.eventHandlersByToken allValues]]; -} - @synthesize bufferedCommandQueue = _bufferedCommandQueue; - (void)setCommandsBufferQueue:(dispatch_queue_t)commandsBufferQueue @@ -263,3 +312,19 @@ - (void)setCommandsBufferQueue:(dispatch_queue_t)commandsBufferQueue } @end + +#pragma mark - + +@implementation MIKMIDIConnectionTokenAndEventHandler + +- (instancetype)initWithConnectionToken:(NSString *)token eventHandler:(MIKMIDIEventHandlerBlock)eventHandler +{ + self = [super init]; + if (self) { + _connectionToken = [token copy]; + _eventHandler = [eventHandler copy]; + } + return self; +} + +@end \ No newline at end of file diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index cb55fbdc..8bb54f8e 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -566,10 +566,7 @@ - (BOOL)connectToDevice:(NSError **)error - (void)disconnectFromDevice { - NSArray *sources = [self.device.entities valueForKeyPath:@"@unionOfArrays.sources"]; - if (![sources count]) return; - MIKMIDISourceEndpoint *source = [sources objectAtIndex:0]; - [[MIKMIDIDeviceManager sharedDeviceManager] disconnectInput:source forConnectionToken:self.connectionToken]; + [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionforToken:self.connectionToken]; } #pragma mark - Properties diff --git a/Source/MIKMIDIOutputPort.h b/Source/MIKMIDIOutputPort.h index bca1e24d..ee5c6d5a 100644 --- a/Source/MIKMIDIOutputPort.h +++ b/Source/MIKMIDIOutputPort.h @@ -12,7 +12,7 @@ @class MIKMIDIDestinationEndpoint; /** - * MIKMIDIInputPort is an Objective-C wrapper for CoreMIDI's MIDIPort class, and is only for destination ports. + * MIKMIDIOutputPort is an Objective-C wrapper for CoreMIDI's MIDIPort class, and is only for destination ports. * It is not intended for use by clients/users of of MIKMIDI. Rather, it should be thought of as an * MIKMIDI private class. */ From 68467f87391b1887de90d60b0ebf03c0405430f6 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 15:23:44 -0700 Subject: [PATCH 233/284] Issue #109: Fixed exception thrown when connecting to second (and subsequent) endpoints in MIKMIDIInputPort. --- Source/MIKMIDIInputPort.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDIInputPort.m b/Source/MIKMIDIInputPort.m index 04997613..7579dfb2 100644 --- a/Source/MIKMIDIInputPort.m +++ b/Source/MIKMIDIInputPort.m @@ -82,7 +82,7 @@ - (id)connectToSource:(MIKMIDISourceEndpoint *)source ![self connectToSource:source error:error]) { return nil; } - + NSString *uuidString = [self createNewConnectionToken]; [self addConnectionToken:uuidString andEventHandler:eventHandler forSource:source]; return uuidString; @@ -134,10 +134,12 @@ - (NSString *)createNewConnectionToken do { // Very unlikely, but just to be safe uuidString = [[NSUUID UUID] UUIDString]; MIKMIDIConnectionTokenAndEventHandler *existingPair = nil; - for (MIKMIDIConnectionTokenAndEventHandler *pair in self.handlerTokenPairsByEndpoint.objectEnumerator) { - if ([pair.connectionToken isEqualToString:uuidString]) { - existingPair = pair; - break; + for (NSArray *handlerPairs in self.handlerTokenPairsByEndpoint.objectEnumerator) { + for (MIKMIDIConnectionTokenAndEventHandler *pair in handlerPairs) { + if ([pair.connectionToken isEqualToString:uuidString]) { + existingPair = pair; + break; + } } } if (!existingPair) break; From bfbc326fd3f8018e2936925a6ea49b5d7d5374e1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 4 Nov 2015 15:50:32 -0700 Subject: [PATCH 234/284] Issue #109: Removed a little dead code in MIKMIDIInputPort. --- Source/MIKMIDIInputPort.m | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Source/MIKMIDIInputPort.m b/Source/MIKMIDIInputPort.m index 7579dfb2..659f5ec5 100644 --- a/Source/MIKMIDIInputPort.m +++ b/Source/MIKMIDIInputPort.m @@ -194,20 +194,6 @@ - (BOOL)commandIsPossibleMSBOf14BitCommand:(MIKMIDICommand *)command return controlChange.controllerNumber < 32; } -- (BOOL)command:(MIKMIDICommand *)lsbCommand isPossibleLSBOfMSBCommand:(MIKMIDICommand *)msbCommand; -{ - if (lsbCommand.commandType != MIKMIDICommandTypeControlChange) return NO; - if (msbCommand.commandType != MIKMIDICommandTypeControlChange) return NO; - - MIKMIDIControlChangeCommand *lsbControlChange = (MIKMIDIControlChangeCommand *)lsbCommand; - MIKMIDIControlChangeCommand *msbControlChange = (MIKMIDIControlChangeCommand *)msbCommand; - - if (msbControlChange.controllerNumber > 31) return NO; - if (lsbControlChange.controllerNumber < 32 || lsbControlChange.controllerNumber > 63) return NO; - - return (lsbControlChange.controllerNumber - msbControlChange.controllerNumber) == 32; -} - - (NSArray *)commandsByCoalescingCommands:(NSArray *)commands { NSMutableArray *coalescedCommands = [commands mutableCopy]; From 95bc4656e69ed8a2a442c51da48a4ce37125e869 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Nov 2015 10:13:38 -0700 Subject: [PATCH 235/284] Added a mechanism for default error descriptions defined in a single place. --- Source/MIKMIDIErrors.h | 15 ++++++++++++--- Source/MIKMIDIErrors.m | 14 ++++++++++++++ Source/MIKMIDIMappingGenerator.m | 3 +-- Source/MIKMIDISynthesizer.m | 2 +- Source/MIKMIDITrack.m | 2 +- Source/MIKMIDIUtilities.m | 3 ++- 6 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Source/MIKMIDIErrors.h b/Source/MIKMIDIErrors.h index e3747567..9b32ac64 100644 --- a/Source/MIKMIDIErrors.h +++ b/Source/MIKMIDIErrors.h @@ -7,6 +7,9 @@ // #import +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * Error domain for errors generated by MIKMIDI. @@ -57,19 +60,25 @@ typedef NS_ENUM(NSInteger, MIKMIDIErrorCode) { MIKMIDISynthesizerDoesNotSupportInstrumentSelectionError, }; +NSString *MIKMIDIDefaultLocalizedErrorDescriptionForErrorCode(MIKMIDIErrorCode code); + /** * Category to ease creation of NSError instances in the MIKMIDI error domain. */ @interface NSError (MIKMIDI) /** - * Creates an NSError instance whose domain is MIKMIDIErrorDomain. + * Creates an NSError instance whose domain is MIKMIDIErrorDomain. If the userInfo dictionary + * is nil, or does not contain a value for NSLocalizedDescriptionKey, a default description + * provided by MIKMIDIDefaultLocalizedErrorDescriptionForErrorCode() will be inserted. * * @param code An error code. Should be one of the values defined for MIKMIDIErrorCode. * @param userInfo A user info dictionary. Can pass nil. * * @return An initialized NSError instance whose domain is MIKMIDIErrorDomain. */ -+ (instancetype)MIKMIDIErrorWithCode:(MIKMIDIErrorCode)code userInfo:(NSDictionary *)userInfo; ++ (instancetype)MIKMIDIErrorWithCode:(MIKMIDIErrorCode)code userInfo:(nullable NSDictionary *)userInfo; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIErrors.m b/Source/MIKMIDIErrors.m index 885028d7..b554000d 100644 --- a/Source/MIKMIDIErrors.m +++ b/Source/MIKMIDIErrors.m @@ -14,10 +14,24 @@ NSString * const MIKMIDIErrorDomain = @"MIKMIDIErrorDomain"; +NSString *MIKMIDIDefaultLocalizedErrorDescriptionForErrorCode(MIKMIDIErrorCode code) +{ + NSDictionary *descriptions = + @{@(MIKMIDIDeviceHasNoSourcesErrorCode) : NSLocalizedString(@"MIDI Device has no sources.", @"MIDI Device has no sources."), + @(MIKMIDIUnknownErrorCode) : NSLocalizedString(@"An unknown MIDI error occurred.", @"An unknown MIDI error occurred.")}; + return descriptions[@(code)] ?: NSLocalizedString(@"A MIDI error occurred.", @"Generic error description"); +} + @implementation NSError (MIKMIDI) + (instancetype)MIKMIDIErrorWithCode:(MIKMIDIErrorCode)code userInfo:(NSDictionary *)userInfo; { + if (!userInfo) userInfo = @{}; + if (!userInfo[NSLocalizedDescriptionKey]) { + NSMutableDictionary *scratch = [userInfo mutableCopy]; + scratch[NSLocalizedDescriptionKey] = MIKMIDIDefaultLocalizedErrorDescriptionForErrorCode(code); + userInfo = scratch; + } return [NSError errorWithDomain:MIKMIDIErrorDomain code:code userInfo:userInfo]; } diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 8bb54f8e..ab1c27c5 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -534,8 +534,7 @@ - (BOOL)connectToDevice:(NSError **)error NSArray *sources = [self.device.entities valueForKeyPath:@"@unionOfArrays.sources"]; if (![sources count]) { - NSString *description = NSLocalizedString(@"MIDI Device has no sources", @"MIDI Device has no sources"); - *error = [NSError MIKMIDIErrorWithCode:MIKMIDIDeviceHasNoSourcesErrorCode userInfo:@{NSLocalizedDescriptionKey: description}]; + *error = [NSError MIKMIDIErrorWithCode:MIKMIDIDeviceHasNoSourcesErrorCode userInfo:nil]; return NO; } MIKMIDISourceEndpoint *source = [sources objectAtIndex:0]; diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index e0df09c8..8a2ec338 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -118,7 +118,7 @@ - (BOOL)selectInstrument:(MIKMIDISynthesizerInstrument *)instrument error:(NSErr { error = error ? error : &(NSError *__autoreleasing){ nil }; if (!instrument) { - *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDIInvalidArgumentError userInfo:nil]; + *error = [NSError MIKMIDIErrorWithCode:MIKMIDIInvalidArgumentError userInfo:nil]; return NO; } return [self sendBankSelectAndProgramChangeForInstrumentID:instrument.instrumentID error:error]; diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 9ef9703f..0c0f9ad6 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -286,7 +286,7 @@ - (BOOL)removeMIDIEventsFromMusicTrack:(NSSet *)events error:(NSError **)error [iterator moveToNextEvent]; } - *error = [NSError errorWithDomain:MIKMIDIErrorDomain code:MIKMIDITrackEventNotFoundErrorCode userInfo:nil]; + *error = [NSError MIKMIDIErrorWithCode:MIKMIDITrackEventNotFoundErrorCode userInfo:nil]; return success; } diff --git a/Source/MIKMIDIUtilities.m b/Source/MIKMIDIUtilities.m index 52919926..590e2294 100644 --- a/Source/MIKMIDIUtilities.m +++ b/Source/MIKMIDIUtilities.m @@ -7,6 +7,7 @@ // #import "MIKMIDIUtilities.h" +#import "MIKMIDIErrors.h" #if !__has_feature(objc_arc) #error MIKMIDIUtilities.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIUtilities.m in the Build Phases for this target @@ -77,7 +78,7 @@ MIDIObjectType MIKMIDIObjectTypeOfObject(MIDIObjectRef object, NSError *__autore } if (resultObject != object) { - *error = [NSError errorWithDomain:@"MIKMIDIErrorDomain" code:-1 userInfo:nil]; + *error = [NSError MIKMIDIErrorWithCode:MIKMIDIUnknownErrorCode userInfo:nil]; return -2; } From 65c7eb5c9e2bb267979b36589abc06b0940ced1a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Nov 2015 12:11:43 -0700 Subject: [PATCH 236/284] Issue #106: Added whole-device connection method to MIKMIDIDeviceManager. --- Source/MIKMIDIDeviceManager.h | 34 +++++++++++++++++++++++++++------- Source/MIKMIDIDeviceManager.m | 31 +++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index 2627424a..ed2a356d 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -75,11 +75,27 @@ extern NSString * const MIKMIDIEndpointKey; + (instancetype)sharedDeviceManager; /** - * Used to connect to a MIDI input/source endpoint. Returns a token that must be kept and passed into the - * -disconnectInput:forConnectionToken: method. + * Used to connect to a MIDI device. Returns a token that must be kept and passed into the + * -disconnectConnectionforToken: method. * - * @param endpoint An MIKMIDISourceEndpoint instance that should be connected. - * @param error If an error occurs, upon returns contains an NSError object that describes the problem. + * When a connection is made using this method, all of the devices valid source endpoints are connected to. To + * connect to specific endpoints only, use -connectInput:error:eventHandler: + * + * @param device An MIKMIDIDevice instance that should be connected. + * @param error If an error occurs, upon returns contains an NSError object that describes the problem. + * If you are not interested in possible errors, you may pass in NULL. + * @param eventHandler A block which will be called anytime incoming MIDI messages are received from the device. + * + * @return A connection token to be used to disconnect the input, or nil if an error occurred. The connection token is opaque. + */ +- (nullable id)connectDevice:(MIKMIDIDevice *)device error:(NSError **)error eventHandler:(MIKMIDIEventHandlerBlock)eventHandler; + +/** + * Used to connect to a single MIDI input/source endpoint. Returns a token that must be kept and passed into the + * -disconnectConnectionforToken: method. + * + * @param endpoint An MIKMIDISourceEndpoint instance that should be connected. + * @param error If an error occurs, upon returns contains an NSError object that describes the problem. * If you are not interested in possible errors, you may pass in NULL. * @param eventHandler A block which will be called anytime incoming MIDI messages are received from the endpoint. * @@ -88,9 +104,9 @@ extern NSString * const MIKMIDIEndpointKey; - (nullable id)connectInput:(MIKMIDISourceEndpoint *)endpoint error:(NSError **)error eventHandler:(MIKMIDIEventHandlerBlock)eventHandler; /** - * Disconnects a previously connected MIDI input/source endpoint. The connectionToken argument - * must be a token previously returned by -connectInput:error:eventHandler:. Only the - * event handler block passed into the call that returned the token will be disconnected. + * Disconnects a previously connected MIDI device or input/source endpoint. The connectionToken argument + * must be a token previously returned by -connectDevice:error:eventHandler: or -connectInput:error:eventHandler:. + * Only the event handler block passed into the call that returned the token will be disconnected. * * @param connectionToken The connection token returned by -connectInput:error:eventHandler: when the input was connected. */ @@ -160,6 +176,10 @@ extern NSString * const MIKMIDIEndpointKey; */ @property (nonatomic, readonly) MIKArrayOf(MIKMIDIDestinationEndpoint *) *virtualDestinations; +/** + * An NSArray of MIKMIDIDevice instances that are connected to at least one event handler. + */ +@property (nonatomic, readonly) MIKArrayOf(MIKMIDIDevice *) *connectedDevices; /** * An NSArray of MIKMIDISourceEndpoint instances that are connected to at least one event handler. diff --git a/Source/MIKMIDIDeviceManager.m b/Source/MIKMIDIDeviceManager.m index 79813c10..c36d9fdd 100644 --- a/Source/MIKMIDIDeviceManager.m +++ b/Source/MIKMIDIDeviceManager.m @@ -14,6 +14,7 @@ #import "MIKMIDIInputPort.h" #import "MIKMIDIOutputPort.h" #import "MIKMIDIClientSourceEndpoint.h" +#import "MIKMIDIErrors.h" #if !__has_feature(objc_arc) #error MIKMIDIDeviceManager.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIDeviceManager.m in the Build Phases for this target @@ -88,14 +89,40 @@ - (id)copyWithZone:(NSZone *)zone #pragma mark - Public +- (nullable id)connectDevice:(MIKMIDIDevice *)device error:(NSError **)error eventHandler:(MIKMIDIEventHandlerBlock)eventHandler +{ + error = error ?: &(NSError *__autoreleasing){ nil }; + NSMutableArray *sources = [device.entities valueForKeyPath:@"@unionOfArrays.sources"]; + if (![sources count]) { + *error = [NSError MIKMIDIErrorWithCode:MIKMIDIDeviceHasNoSourcesErrorCode userInfo:nil]; + return nil; + } + + NSMutableArray *tokens = [NSMutableArray array]; + for (MIKMIDISourceEndpoint *source in sources) { + id token = [self.inputPort connectToSource:source error:error eventHandler:eventHandler]; + if (!token) { + for (id token in tokens) { [self disconnectConnectionforToken:token]; } + return nil; + } + [tokens addObject:token]; + } + + return tokens; +} + - (id)connectInput:(MIKMIDISourceEndpoint *)endpoint error:(NSError **)error eventHandler:(MIKMIDIEventHandlerBlock)eventHandler { - return [self.inputPort connectToSource:endpoint error:error eventHandler:eventHandler]; + id result = [self.inputPort connectToSource:endpoint error:error eventHandler:eventHandler]; + if (!result) return nil; + return @[result]; } - (void)disconnectConnectionforToken:(id)connectionToken { - [self.inputPort disconnectConnectionForToken:connectionToken]; + for (id token in (NSArray *)connectionToken) { + [self.inputPort disconnectConnectionForToken:token]; + } } - (BOOL)sendCommands:(NSArray *)commands toEndpoint:(MIKMIDIDestinationEndpoint *)endpoint error:(NSError **)error; From c19b2953717a548786ae3e6ba06d291bf72d26c0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Nov 2015 13:27:14 -0700 Subject: [PATCH 237/284] MIDI Testbed: Fixed use of now-deprecated method. --- Examples/MIDI Testbed/Sources/MIKAppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/MIDI Testbed/Sources/MIKAppDelegate.m b/Examples/MIDI Testbed/Sources/MIKAppDelegate.m index 1746dbae..1fb627e6 100644 --- a/Examples/MIDI Testbed/Sources/MIKAppDelegate.m +++ b/Examples/MIDI Testbed/Sources/MIKAppDelegate.m @@ -64,7 +64,7 @@ - (void)disconnectFromSource:(MIKMIDISourceEndpoint *)source if (!source) return; id token = [self.connectionTokensForSources objectForKey:source]; if (!token) return; - [self.midiDeviceManager disconnectInput:source forConnectionToken:token]; + [self.midiDeviceManager disconnectConnectionforToken:token]; } - (void)connectToDevice:(MIKMIDIDevice *)device From ea52fe9bc3b8d4aebf37ad8605a241d0cf4f19b7 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 5 Nov 2015 15:02:25 -0700 Subject: [PATCH 238/284] Issue #112: KVO notifications for changes to MIKMIDIDeviceManager's availableDevices, virtualSources, and virtualDestinations properties now have correct value for NSKeyValueChangeKindKey. --- Source/MIKMIDIDeviceManager.m | 60 ++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/Source/MIKMIDIDeviceManager.m b/Source/MIKMIDIDeviceManager.m index 79813c10..94f26d45 100644 --- a/Source/MIKMIDIDeviceManager.m +++ b/Source/MIKMIDIDeviceManager.m @@ -327,64 +327,74 @@ void MIKMIDIDeviceManagerNotifyCallback(const MIDINotification *message, void *r #pragma mark - Properties -+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key -{ - NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; - - if ([key isEqualToString:@"availableDevices"]) { - keyPaths = [keyPaths setByAddingObject:@"internalDevices"]; - } - - if ([key isEqualToString:@"virtualSources"]) { - keyPaths = [keyPaths setByAddingObject:@"internalVirtualSources"]; - } - - if ([key isEqualToString:@"virtualDestinations"]) { - keyPaths = [keyPaths setByAddingObject:@"internalVirtualDestinations"]; - } - - return keyPaths; -} ++ (BOOL)automaticallyNotifiesObserversOfAvailableDevices { return NO; } - (NSArray *)availableDevices { return [self.internalDevices copy]; } - (void)addInternalDevicesObject:(MIKMIDIDevice *)device; { - [self.internalDevices addObject:device]; + NSUInteger index = self.internalDevices.count; + [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"availableDevices"]; + [self.internalDevices insertObject:device atIndex:index]; + [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"availableDevices"]; } - (void)removeInternalDevicesObject:(MIKMIDIDevice *)device; { - [self.internalDevices removeObject:device]; + NSUInteger index = [self.internalDevices indexOfObject:device]; + if (index == NSNotFound) return; + [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"availableDevices"]; + [self.internalDevices removeObjectAtIndex:index]; + [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"availableDevices"]; } ++ (BOOL)automaticallyNotifiesObserversOfInternalVirtualSources { return NO; } + - (NSArray *)virtualSources { return [self.internalVirtualSources copy]; } - (void)addInternalVirtualSourcesObject:(MIKMIDISourceEndpoint *)source { - [self.internalVirtualSources addObject:source]; + NSUInteger index = [self.internalVirtualSources indexOfObject:source]; + if (index == NSNotFound) return; + [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualSources"]; + [self.internalVirtualSources removeObjectAtIndex:index]; + [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualSources"]; } - (void)removeInternalVirtualSourcesObject:(MIKMIDISourceEndpoint *)source { - [self.internalVirtualSources removeObject:source]; + NSUInteger index = [self.internalVirtualSources indexOfObject:source]; + if (index == NSNotFound) return; + [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualSources"]; + [self.internalVirtualSources removeObjectAtIndex:index]; + [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualSources"]; } ++ (BOOL)automaticallyNotifiesObserversOfVirtualSources { return NO; } + - (NSArray *)virtualDestinations { return [self.internalVirtualDestinations copy]; } - (void)addInternalVirtualDestinationsObject:(MIKMIDIDestinationEndpoint *)destination { - [self.internalVirtualDestinations addObject:destination]; + NSUInteger index = [self.internalVirtualDestinations indexOfObject:destination]; + if (index == NSNotFound) return; + [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualDestinations"]; + [self.internalVirtualDestinations removeObjectAtIndex:index]; + [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualDestinations"]; } - (void)removeInternalVirtualDestinationsObject:(MIKMIDIDestinationEndpoint *)destination { - [self.internalVirtualDestinations removeObject:destination]; + NSUInteger index = [self.internalVirtualDestinations indexOfObject:destination]; + if (index == NSNotFound) return; + [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualDestinations"]; + [self.internalVirtualDestinations removeObjectAtIndex:index]; + [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualDestinations"]; } + (NSSet *)keyPathsForValuesAffectingConnectedInputSources { - return [NSSet setWithObjects:@"connectedSources", nil]; + return [NSSet setWithObjects:@"inputPort.connectedSources", nil]; } - (NSArray *)connectedInputSources From 85af1732a5f1f3295e88fd2a93f96e2b512668b3 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 11:33:08 -0700 Subject: [PATCH 239/284] Moved definition of MIKMIDIEventHandlerBlock from MIKMIDIInputPort to MIKMIDISourceEndpoint. --- Source/MIKMIDIInputPort.h | 4 +--- Source/MIKMIDISourceEndpoint.h | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Source/MIKMIDIInputPort.h b/Source/MIKMIDIInputPort.h index 9a770c4e..a2d31340 100644 --- a/Source/MIKMIDIInputPort.h +++ b/Source/MIKMIDIInputPort.h @@ -7,16 +7,14 @@ // #import "MIKMIDIPort.h" +#import "MIKMIDISourceEndpoint.h" #import "MIKMIDICompilerCompatibility.h" @class MIKMIDIEndpoint; -@class MIKMIDISourceEndpoint; @class MIKMIDICommand; NS_ASSUME_NONNULL_BEGIN -typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, MIKArrayOf(MIKMIDICommand *) *commands); // commands in an array of MIKMIDICommands - /** * MIKMIDIInputPort is an Objective-C wrapper for CoreMIDI's MIDIPort class, and is only for source ports. * It is not intended for use by clients/users of of MIKMIDI. Rather, it should be thought of as an diff --git a/Source/MIKMIDISourceEndpoint.h b/Source/MIKMIDISourceEndpoint.h index cdb8a804..6a0027e5 100644 --- a/Source/MIKMIDISourceEndpoint.h +++ b/Source/MIKMIDISourceEndpoint.h @@ -7,6 +7,20 @@ // #import "MIKMIDIEndpoint.h" +#import "MIKMIDICompilerCompatibility.h" + +@class MIKMIDISourceEndpoint; +@class MIKMIDICommand; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Block used by various MIKMIDI APIs that deliver incoming MIDI messages. + * + * @param source The source endpoint from which MIDI messagse were received. + * @param commands An NSArray containing received MIKMIDICommand instances. + */ +typedef void(^MIKMIDIEventHandlerBlock)(MIKMIDISourceEndpoint *source, MIKArrayOf(MIKMIDICommand *) *commands); // commands in an array of MIKMIDICommands /** * MIKMIDISourceEndpoint represents a source (input) MIDI endpoint. @@ -26,3 +40,5 @@ @interface MIKMIDISourceEndpoint : MIKMIDIEndpoint @end + +NS_ASSUME_NONNULL_END \ No newline at end of file From deabcea58e1a972e073864966a72f2b6aafb1434 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 11:33:48 -0700 Subject: [PATCH 240/284] Issue #108: Added additional generics macros. --- Source/MIKMIDICompilerCompatibility.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/MIKMIDICompilerCompatibility.h b/Source/MIKMIDICompilerCompatibility.h index de4b6e6b..fc081e76 100644 --- a/Source/MIKMIDICompilerCompatibility.h +++ b/Source/MIKMIDICompilerCompatibility.h @@ -26,6 +26,11 @@ #define MIKArrayOf(TYPE) NSArray #define MIKArrayOfKindOf(TYPE) NSArray<__kindof TYPE> +#define MIKMutableArrayOf(TYPE) NSMutableArray + +#define MIKSetOf(TYPE) NSSet +#define MIKMutableSetOf(TYPE) NSMutableSet + #define MIKMapTableOf(KEYTYPE, OBJTYPE) NSMapTable #else @@ -33,6 +38,11 @@ #define MIKArrayOf(TYPE) NSArray #define MIKArrayOfKindOf(TYPE) NSArray +#define MIKMutableArrayOf(TYPE) NSMutableArray + +#define MIKSetOf(TYPE) NSSet +#define MIKMutableSetOf(TYPE) NSMutableSet + #define MIKMapTableOf(KEYTYPE, OBJTYPE) NSMapTable #endif From 8bc16380dbbf025cde0a323c16324ae0e78017b7 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 11:34:15 -0700 Subject: [PATCH 241/284] Issue #106: Added MIKMIDIConnectionManager for managing and persisting connected devices. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 + Source/MIKMIDI.h | 1 + Source/MIKMIDIConnectionManager.h | 177 ++++++++++ Source/MIKMIDIConnectionManager.m | 358 ++++++++++++++++++++ Source/MIKMIDIDevice.m | 6 + 5 files changed, 554 insertions(+) create mode 100644 Source/MIKMIDIConnectionManager.h create mode 100644 Source/MIKMIDIConnectionManager.m diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 134c39bc..5a2c2a12 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -58,6 +58,10 @@ 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */; }; 9D07CAC71BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D07CAC81BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D07CB221BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CB201BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D07CB231BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CB201BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D07CB241BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D07CB211BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m */; }; + 9D07CB251BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D07CB211BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m */; }; 9D0895EE1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F01B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; @@ -351,6 +355,8 @@ 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingManager_SubclassMethods.h; sourceTree = ""; }; 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequence+MIKMIDIPrivate.h"; sourceTree = ""; }; 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICompilerCompatibility.h; sourceTree = ""; }; + 9D07CB201BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIConnectionManager.h; sourceTree = ""; }; + 9D07CB211BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIConnectionManager.m; sourceTree = ""; }; 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; @@ -749,6 +755,8 @@ children = ( 9D74EF3C17A713A100BEE89F /* MIKMIDIDeviceManager.h */, 9D74EF3D17A713A100BEE89F /* MIKMIDIDeviceManager.m */, + 9D07CB201BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h */, + 9D07CB211BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m */, 9D74EF5017A713A100BEE89F /* MIKMIDIObject.h */, 9D74EF5117A713A100BEE89F /* MIKMIDIObject.m */, 9D74EF5217A713A100BEE89F /* MIKMIDIObject_SubclassMethods.h */, @@ -902,6 +910,7 @@ 9D74EF8317A713A100BEE89F /* MIKMIDIObject.h in Headers */, 9D07CAC71BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */, 9D74EF8617A713A100BEE89F /* MIKMIDIOutputPort.h in Headers */, + 9D07CB221BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h in Headers */, 9D74EF8817A713A100BEE89F /* MIKMIDIPort.h in Headers */, 9D74EF8B17A713A100BEE89F /* MIKMIDIResponder.h in Headers */, 9D74EF8C17A713A100BEE89F /* MIKMIDISourceEndpoint.h in Headers */, @@ -959,6 +968,7 @@ 9D0895EF1B0D29F200A5872E /* MIKMIDIMappingItem.h in Headers */, 9DAF8B701A7B00A700F46528 /* MIKMIDIMetaCuePointEvent.h in Headers */, 9DAF8B671A7B008A00F46528 /* MIKMIDIProgramChangeCommand.h in Headers */, + 9D07CB231BEC13E400C4ABB0 /* MIKMIDIConnectionManager.h in Headers */, 9DED4E231AA77DAC00DA8356 /* MIKMIDIPitchBendChangeEvent.h in Headers */, 9DED4E371AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h in Headers */, 9DB366F71A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */, @@ -1196,6 +1206,7 @@ 9DED4E381AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9D74EF8F17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.m in Sources */, 9D74EF9117A713A100BEE89F /* MIKMIDISystemMessageCommand.m in Sources */, + 9D07CB241BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m in Sources */, 839D935419C3A2F5007589C3 /* MIKMIDIMetaEvent.m in Sources */, 9D7027D31ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m in Sources */, 9D74EF9317A713A100BEE89F /* MIKMIDIUtilities.m in Sources */, @@ -1264,6 +1275,7 @@ 9DAF8B4C1A7AFF7500F46528 /* MIKMIDISequencer.m in Sources */, 9DB366F31A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */, 9DAF8B4D1A7AFF7500F46528 /* MIKMIDIUtilities.m in Sources */, + 9D07CB251BEC13E400C4ABB0 /* MIKMIDIConnectionManager.m in Sources */, 9DAF8B4E1A7AFF7500F46528 /* MIKMIDICommandThrottler.m in Sources */, 9DAF8B4F1A7AFF7500F46528 /* MIKMIDIClock.m in Sources */, 9DAF8B501A7AFF7500F46528 /* MIKMIDIPrivateUtilities.m in Sources */, diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 5a77cefc..67baa167 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -19,6 +19,7 @@ // MIDI Device support #import "MIKMIDIDevice.h" #import "MIKMIDIDeviceManager.h" +#import "MIKMIDIConnectionManager.h" #import "MIKMIDIEntity.h" diff --git a/Source/MIKMIDIConnectionManager.h b/Source/MIKMIDIConnectionManager.h new file mode 100644 index 00000000..fc61dffd --- /dev/null +++ b/Source/MIKMIDIConnectionManager.h @@ -0,0 +1,177 @@ +// +// MIKMIDIConnectionManager.h +// MIKMIDI +// +// Created by Andrew Madsen on 11/5/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import +#import "MIKMIDICompilerCompatibility.h" +#import "MIKMIDISourceEndpoint.h" + +@class MIKMIDIDevice; + +@protocol MIKMIDIConnectionManagerDelegate; + +NS_ASSUME_NONNULL_BEGIN + +/** + * MIKMIDIConnectionManager can be used to manage a set of connected devices. It can be configured to automatically + * connect to devices as they are added, and disconnect from them as they are removed. It also supports saving + * the list of connected to NSUserDefaults and restoring them upon relaunch. + * + * The use of MIKMIDIConnectionManager is optional. It is meant to be useful in implementing functionality that + * many MIDI device enabled apps need. However, simple connection to devices or MIDI endpoints can be done with + * MIKMIDIDeviceManager directly, if desired. + */ +@interface MIKMIDIConnectionManager : NSObject + +/** + * This method will throw an exception if called. Use -initWithName: instead. + * + * @return nil + */ +- (instancetype)init NS_UNAVAILABLE; + +/** + * Initializes an instance of MIKMIDIConnectionManager. The passed in name is used to independently + * store and load the connection manager's configuration using NSUserDefaults. The passed in name + * should be unique across your application, and the same from launch to launch. + * + * @param name The name to give the connection manager. + * + * @return An initialized MIKMIDIConnectionManager instance. + */ +- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER; + +/** + * Connect to the specified device. When MIDI messages are received, the connection manager's event handler + * block will be executed. + * + * @param device An MIKMIDIDevice instance. + * @param error If an error occurs, upon returns contains an NSError object that describes the problem. + * If you are not interested in possible errors, you may pass in NULL. + * + * @return YES if the connection was successful, NO if an error occurred. + */ +- (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error; + +/** + * Disconnect from a connected device. No further messages from the specified device will be processed. + * + * Note that you don't need to call this method when a previously-connected device was removed from the system. + * Disconnection in that situation is handled automatically. + * + * @param device An MIKMIDIDevice instance. + */ +- (void)disconnectFromDevice:(MIKMIDIDevice *)device; + +/** + * This method can be used to determine if the receiver is connected to a given MIDI device. + * + * @param device An MIKMIDIDevice instance. + * + * @return YES if the receiver is connected to and processing MIDI input from the device, NO otherwise. + */ +- (BOOL)isConnectedToDevice:(MIKMIDIDevice *)device; + +/** + * If YES (the default), the connection manager will automatically save its configuration at appropriate + * times. If this property is NO, -saveConfiguration can still be used to manually trigger saving the + * receiver's configuration. Note that -loadConfiguration must always be called manually, e.g. at launch. + */ +@property (nonatomic) BOOL automaticallySavesConfiguration; + +/** + * Save the receiver's list of connected devices to disk for later restoration. + */ +- (void)saveConfiguration; + +/** + * Load and reconnect to the devices previously saved to disk by a call to -saveConfiguration. For + * this to work, the receiver's name must be the same as it was upon the previous call to -saveConfiguration. + * + * @note: This method will only connect to new devices. It will not disconnect from devices not found in the + * saved configuration. + */ +- (void)loadConfiguration; + +/** + * The name of the receiver. Used for configuration save/load. + */ +@property (nonatomic, copy, readonly) NSString *name; + +/** + * An MIKMIDIEventHandlerBlock to be called with incoming MIDI messages from any connected device. + * + * If you need to determine which device sent the passed in messages, call source.entity.device on the + * passed in MIKMIDISourceEndpoint argument. + */ +@property (nonatomic, copy, null_resettable) MIKMIDIEventHandlerBlock eventHandler; + +/** + * A delegate, used to customize MIKMIDIConnectionManager behavior. + */ +@property (nonatomic, weak) iddelegate; + +/** + * Controls whether the receiver's availableDevices property includes virtual devices (i.e. devices made + * up of automatically paired virtual sources and destinations). + * + * If this property is YES (the default), the connection manager will attempt to automtically related + * associated virtual sources and destinations and create "virtual" MIKMIDIDevice instances for them. + * + * If this property is NO, the connection manager's availableDevices array will _only_ contain non-virtual + * MIKMIDIDevices. + * + * For most applications, this should be left at the default YES, as even many physical MIDI devices present as + * "virtual" devices in software. + * + * @note: The caveat here is that this relies on some heuristics to match up source endpoints with destination endpoints. + * These heuristics are based on the way certain common MIDI devices behave, but may not be universal, and therefore + * may miss, or fail to properly associate endpoints for some devices. If this is a problem for your application, + * you should obtain and connect to virtual sources/endpoints using MIKMIDIDeviceManager directly instead. + */ +@property (nonatomic) BOOL includesVirtualDevices; // Default is YES + +/** + * An array of available MIDI devices. + * + * This property is observable using Key Value Observing. + */ +@property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDIDevice *) *availableDevices; + +/** + * The set of MIDI devices to which the receiver is connected. + * + * This property is observable using Key Value Observing. + */ +@property (nonatomic, strong, readonly) MIKSetOf(MIKMIDIDevice *) *connectedDevices; + +@end + +/** + * Protocol containing method(s) to be implemented by delegates of MIKMIDIConnectionManager. + */ +@protocol MIKMIDIConnectionManagerDelegate + +@optional + +/** + * A connection manager's delegate can implement this method to determine whether or not to automatically connect + * to a newly added MIDI device. + * + * If this method is not implemented or returns NO, the device will be connected to if the most + * + * @param manager An instance of MIKMIDIConnectionManager. + * @param device The newly added MIDI device. + * + * @return YES to connect to device, NO to leave it unconnected. + */ +- (BOOL)connectionManager:(MIKMIDIConnectionManager *)manager shouldConnectToNewlyAddedDevice:(MIKMIDIDevice *)device; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m new file mode 100644 index 00000000..f24bddd6 --- /dev/null +++ b/Source/MIKMIDIConnectionManager.m @@ -0,0 +1,358 @@ +// +// MIKMIDIConnectionManager.m +// MIKMIDI +// +// Created by Andrew Madsen on 11/5/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIConnectionManager.h" +#import "MIKMIDIDeviceManager.h" +#import "MIKMIDIDevice.h" + +void *MIKMIDIConnectionManagerKVOContext = &MIKMIDIConnectionManagerKVOContext; + +NSString * const MIKMIDIConnectionManagerConnectedDevicesKey = @"MIKMIDIConnectionManagerConnectedDevicesKey"; + +@interface MIKMIDIConnectionManager () + +@property (nonatomic, strong, readwrite) MIKArrayOf(MIKMIDIDevice *) *availableDevices; + +@property (nonatomic, strong, readonly) MIKMutableSetOf(MIKMIDIDevice *) *internalConnectedDevices; +@property (nonatomic, strong, readonly) MIKMIDIEventHandlerBlock internalEventHandler; +@property (nonatomic, strong, readonly) MIKMapTableOf(MIKMIDIDevice *, id) *connectionTokensByDevice; + +@property (nonatomic, readonly) MIKMIDIDeviceManager *deviceManager; + +@end + +@implementation MIKMIDIConnectionManager + +- (instancetype)init +{ + [NSException raise:NSInternalInconsistencyException format:@"-initWithName: is the designated initializer for %@", NSStringFromClass([self class])]; + return nil; +} + +- (instancetype)initWithName:(NSString *)name +{ + self = [super init]; + if (self) { + _name = [name copy]; + _internalConnectedDevices = [[NSMutableSet alloc] init]; + + __weak typeof(self) weakSelf = self; + _internalEventHandler = ^(MIKMIDISourceEndpoint *endpoint, NSArray *commands) { + weakSelf.eventHandler(endpoint, commands); + }; + + _connectionTokensByDevice = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; + + NSKeyValueObservingOptions options = NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; + [self.deviceManager addObserver:self forKeyPath:@"availableDevices" options:options context:MIKMIDIConnectionManagerKVOContext]; + [self.deviceManager addObserver:self forKeyPath:@"virtualSources" options:options context:MIKMIDIConnectionManagerKVOContext]; + [self.deviceManager addObserver:self forKeyPath:@"virtualDestinations" options:options context:MIKMIDIConnectionManagerKVOContext]; + + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(deviceWasPluggedIn:) name:MIKMIDIDeviceWasAddedNotification object:nil]; + [nc addObserver:self selector:@selector(deviceWasUnplugged:) name:MIKMIDIDeviceWasRemovedNotification object:nil]; + [nc addObserver:self selector:@selector(endpointWasPluggedIn:) name:MIKMIDIVirtualEndpointWasAddedNotification object:nil]; + [nc addObserver:self selector:@selector(endpointWasUnplugged:) name:MIKMIDIVirtualEndpointWasRemovedNotification object:nil]; + } + return self; +} + +- (void)dealloc +{ + [self.deviceManager removeObserver:self forKeyPath:@"availableDevices" context:MIKMIDIConnectionManagerKVOContext]; + [self.deviceManager removeObserver:self forKeyPath:@"virtualSources" context:MIKMIDIConnectionManagerKVOContext]; + [self.deviceManager removeObserver:self forKeyPath:@"virtualDestinations" context:MIKMIDIConnectionManagerKVOContext]; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - Public + +#pragma mark Device Connection / Disconnection + +- (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error +{ + if ([self isConnectedToDevice:device]) return YES; + error = error ?: &(NSError *__autoreleasing){ nil }; + + id token = [self.deviceManager connectDevice:device error:error eventHandler:self.internalEventHandler]; + if (!token) return NO; + + [self.connectionTokensByDevice setObject:token forKey:device]; + + if (self.automaticallySavesConfiguration) [self saveConfiguration]; + + return YES; +} + +- (void)disconnectFromDevice:(MIKMIDIDevice *)device +{ + if (![self isConnectedToDevice:device]) return; + + id token = [self.connectionTokensByDevice objectForKey:device]; + if (!token) return; + + [self.deviceManager disconnectConnectionforToken:token]; + + if (self.automaticallySavesConfiguration) [self saveConfiguration]; +} + +- (BOOL)isConnectedToDevice:(MIKMIDIDevice *)device; +{ + return [self.connectedDevices containsObject:device]; +} + +#pragma mark Configuration Persistence + +- (void)saveConfiguration +{ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + + NSMutableDictionary *configuration = [NSMutableDictionary dictionaryWithDictionary:[self savedConfiguration]]; + + // Save connected device names + NSMutableArray *connectedDeviceNames = configuration[MIKMIDIConnectionManagerConnectedDevicesKey]; + if (!connectedDeviceNames) { + connectedDeviceNames = [NSMutableArray array]; + configuration[MIKMIDIConnectionManagerConnectedDevicesKey] = connectedDeviceNames; + } + + // For devices that were connected in saved configuration but are now unavailable, leave them + // connected in the configuration so they'll reconnect automatically. + for (MIKMIDIDevice *device in self.availableDevices) { + NSString *name = device.name; + if (![name length]) continue; + if ([self isConnectedToDevice:device]) { + [connectedDeviceNames addObject:name]; + } else { + [connectedDeviceNames removeObject:name]; + } + } + + [userDefaults setObject:configuration forKey:[self userDefaultsConfigurationKey]]; +} + +- (void)loadConfiguration +{ + for (MIKMIDIDevice *device in self.availableDevices) { + if ([self deviceIsConnectedInSavedConfiguration:device]) { + NSError *error = nil; + if (![self connectToDevice:device error:&error]) { + NSLog(@"Unable to connect to MIDI device %@: %@", device, error); + return; + } + } + } +} + +#pragma mark - Private + +- (void)updateAvailableDevices +{ + NSArray *regularDevices = self.deviceManager.availableDevices; + NSMutableSet *result = [NSMutableSet setWithArray:regularDevices]; + + if (self.includesVirtualDevices) { + NSMutableSet *endpointsInDevices = [NSMutableSet set]; + for (MIKMIDIDevice *device in regularDevices) { + NSSet *sources = [NSSet setWithArray:[device.entities valueForKeyPath:@"@distinctUnionOfArrays.sources"]]; + NSSet *destinations = [NSSet setWithArray:[device.entities valueForKeyPath:@"@distinctUnionOfArrays.destinations"]]; + [endpointsInDevices unionSet:sources]; + [endpointsInDevices unionSet:destinations]; + } + + NSMutableSet *devicelessSources = [NSMutableSet setWithArray:self.deviceManager.virtualSources]; + NSMutableSet *devicelessDestinations = [NSMutableSet setWithArray:self.deviceManager.virtualDestinations]; + [devicelessSources minusSet:endpointsInDevices]; + [devicelessDestinations minusSet:endpointsInDevices]; + + // Now we need to try to associate each source with its corresponding destination on the same device + NSMapTable *destinationToSourceMap = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory]; + NSMapTable *deviceNamesBySource = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory]; + + for (MIKMIDIEndpoint *source in devicelessSources) { + NSString *sourceName = [self deviceNameFromVirtualEndpoint:source]; + for (MIKMIDIEndpoint *destination in devicelessDestinations) { + NSString *destinationName = [self deviceNameFromVirtualEndpoint:destination]; + if ([sourceName isEqualToString:destinationName]) { // Source and destination match + [destinationToSourceMap setObject:destination forKey:source]; + [deviceNamesBySource setObject:sourceName forKey:source]; + break; + } + } + } + + for (MIKMIDIEndpoint *source in destinationToSourceMap) { + MIKMIDIEndpoint *destination = [destinationToSourceMap objectForKey:source]; + [devicelessSources removeObject:source]; + [devicelessDestinations removeObject:destination]; + + MIKMIDIDevice *device = [MIKMIDIDevice deviceWithVirtualEndpoints:@[source, destination]]; + device.name = [deviceNamesBySource objectForKey:source]; + if (device) [result addObject:device]; + } + for (MIKMIDIEndpoint *endpoint in devicelessSources) { + MIKMIDIDevice *device = [MIKMIDIDevice deviceWithVirtualEndpoints:@[endpoint]]; + if (device) [result addObject:device]; + } + for (MIKMIDIEndpoint *endpoint in devicelessSources) { + MIKMIDIDevice *device = [MIKMIDIDevice deviceWithVirtualEndpoints:@[endpoint]]; + if (device) [result addObject:device]; + } + } + + self.availableDevices = [result copy]; +} + +- (MIKMIDIDevice *)firstAvailableDeviceWithName:(NSString *)deviceName +{ + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", deviceName]; + return [[self.availableDevices filteredArrayUsingPredicate:predicate] firstObject]; +} + +- (void)connectToNewlyAddedDeviceIfAppropriate:(MIKMIDIDevice *)device +{ + if (!device) return; + + BOOL shouldConnect = [self deviceIsConnectedInSavedConfiguration:device]; + + if ([self.delegate respondsToSelector:@selector(connectionManager:shouldConnectToNewlyAddedDevice:)]) { + shouldConnect = [self.delegate connectionManager:self shouldConnectToNewlyAddedDevice:device]; + } + + if (shouldConnect) { + NSError *error = nil; + if (![self connectToDevice:device error:&error]) { + NSLog(@"Unable to connect to MIDI device %@: %@", device, error); + return; + } + } +} + +#pragma mark Configuration Persistence + +- (NSString *)userDefaultsConfigurationKey +{ + NSString *name = self.name; + if (![name length]) name = NSStringFromClass([self class]); + return [NSString stringWithFormat:@"%@SavedMIDIConnectionConfiguration", name]; +} + +- (NSDictionary *)savedConfiguration +{ + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + return [userDefaults objectForKey:[self userDefaultsConfigurationKey]]; +} + +- (BOOL)deviceIsConnectedInSavedConfiguration:(MIKMIDIDevice *)device +{ + NSString *deviceName = device.name; + if (![deviceName length]) return NO; + + NSDictionary *configuration = [self savedConfiguration]; + NSArray *connectedDeviceNames = configuration[MIKMIDIConnectionManagerConnectedDevicesKey]; + return [connectedDeviceNames containsObject:deviceName]; +} + +#pragma mark Virtual Endpoints + +- (NSString *)deviceNameFromVirtualEndpoint:(MIKMIDIEndpoint *)endpoint +{ + NSString *name = endpoint.name; + if (![name length]) name = [endpoint description]; + NSCharacterSet *whitespace = [NSCharacterSet whitespaceCharacterSet]; + NSMutableArray *nameComponents = [[name componentsSeparatedByCharactersInSet:whitespace] mutableCopy]; + [nameComponents removeLastObject]; + return [nameComponents componentsJoinedByString:@" "]; +} + +- (MIKMIDIDevice *)deviceContainingEndpoint:(MIKMIDIEndpoint *)endpoint +{ + if (!endpoint) return nil; + NSMutableSet *devices = [self.availableDevices mutableCopy]; + [devices unionSet:self.connectedDevices]; + for (MIKMIDIDevice *device in devices) { + NSMutableSet *deviceEndpoints = [NSMutableSet setWithArray:[device.entities valueForKeyPath:@"@distinctUnionOfArrays.sources"]]; + [deviceEndpoints unionSet:[NSSet setWithArray:[device.entities valueForKeyPath:@"@distinctUnionOfArrays.destinations"]]]; + if ([deviceEndpoints containsObject:endpoint]) return device; + } + return nil; +} + +#pragma mark - Notifications + +- (void)deviceWasPluggedIn:(NSNotification *)notification +{ + MIKMIDIDevice *device = [notification userInfo][MIKMIDIDeviceKey]; + [self connectToNewlyAddedDeviceIfAppropriate:device]; +} + +- (void)deviceWasUnplugged:(NSNotification *)notification +{ + MIKMIDIDevice *unpluggedDevice = [notification userInfo][MIKMIDIDeviceKey]; + [self disconnectFromDevice:unpluggedDevice]; +} + +- (void)endpointWasPluggedIn:(NSNotification *)notification +{ + MIKMIDIEndpoint *pluggedInEndpoint = [notification userInfo][MIKMIDIEndpointKey]; + MIKMIDIDevice *pluggedInDevice = [self deviceContainingEndpoint:pluggedInEndpoint]; + [self connectToNewlyAddedDeviceIfAppropriate:pluggedInDevice]; +} + +- (void)endpointWasUnplugged:(NSNotification *)notification +{ + MIKMIDIEndpoint *unpluggedEndpoint = [notification userInfo][MIKMIDIEndpointKey]; + MIKMIDIDevice *unpluggedDevice = [self deviceContainingEndpoint:unpluggedEndpoint]; + if (unpluggedDevice) [self disconnectFromDevice:unpluggedDevice]; +} + +#pragma mark - KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context != MIKMIDIConnectionManagerKVOContext) { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + return; + } + + if (object != self.deviceManager) return; + + if ([keyPath isEqualToString:@"availableDevices"]) { + [self updateAvailableDevices]; + } + + if (self.includesVirtualDevices && + ([keyPath isEqualToString:@"virtualSources"] || [keyPath isEqualToString:@"virtualDestinations"])) { + [self updateAvailableDevices]; + } +} + +#pragma mark - Properties + +- (MIKMIDIDeviceManager *)deviceManager { return [MIKMIDIDeviceManager sharedDeviceManager]; } + +- (MIKMIDIEventHandlerBlock)eventHandler +{ + return _eventHandler ?: ^(MIKMIDISourceEndpoint *s, NSArray *c){}; +} + +- (void)setIncludesVirtualDevices:(BOOL)includesVirtualDevices +{ + if (includesVirtualDevices != _includesVirtualDevices) { + _includesVirtualDevices = includesVirtualDevices; + [self updateAvailableDevices]; + } +} + +- (MIKSetOf(MIKMIDIDevice *) *)connectedDevices +{ + return [self.internalConnectedDevices copy]; +} + +@end diff --git a/Source/MIKMIDIDevice.m b/Source/MIKMIDIDevice.m index d9035339..e110eb80 100644 --- a/Source/MIKMIDIDevice.m +++ b/Source/MIKMIDIDevice.m @@ -78,6 +78,12 @@ - (BOOL)isEqual:(id)object return [self.entities isEqualToArray:[(MIKMIDIDevice *)object entities]]; } +- (NSUInteger)hash +{ + if (!self.isVirtual) return (NSUInteger)self.uniqueID; + return [self.entities hash]; +} + #pragma mark - Public - (NSString *)description From ce9de640162079fda4e8cf8ba5cd6faf5706def8 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 14:21:17 -0700 Subject: [PATCH 242/284] Issue #106: Improved MIKMIDIConnectionManager API particularly around initial connection behavior. --- Source/MIKMIDIConnectionManager.h | 38 ++++++++++++++++---- Source/MIKMIDIConnectionManager.m | 60 ++++++++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/Source/MIKMIDIConnectionManager.h b/Source/MIKMIDIConnectionManager.h index fc61dffd..af68d6f2 100644 --- a/Source/MIKMIDIConnectionManager.h +++ b/Source/MIKMIDIConnectionManager.h @@ -39,11 +39,15 @@ NS_ASSUME_NONNULL_BEGIN * store and load the connection manager's configuration using NSUserDefaults. The passed in name * should be unique across your application, and the same from launch to launch. * - * @param name The name to give the connection manager. + * @param name The name to give the connection manager. + * @param delegate The delegate of the connection manager + * @param eventHandler An MIKMIDIEventHandlerBlock to be called with incoming MIDI messages from any connected device. * * @return An initialized MIKMIDIConnectionManager instance. */ -- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithName:(NSString *)name + delegate:(nullable id)delegate + eventHandler:(nullable MIKMIDIEventHandlerBlock)eventHandler NS_DESIGNATED_INITIALIZER; /** * Connect to the specified device. When MIDI messages are received, the connection manager's event handler @@ -113,7 +117,7 @@ NS_ASSUME_NONNULL_BEGIN /** * A delegate, used to customize MIKMIDIConnectionManager behavior. */ -@property (nonatomic, weak) iddelegate; +@property (nonatomic, weak, nullable) iddelegate; /** * Controls whether the receiver's availableDevices property includes virtual devices (i.e. devices made @@ -151,6 +155,26 @@ NS_ASSUME_NONNULL_BEGIN @end + +/** + * Specifies behavior for connecting to a newly connected device. See + * -connectionManager:shouldConnectToNewlyAddedDevice: + */ +typedef NS_ENUM(NSInteger, MIKMIDIAutoConnectBehavior) { + + /** Do not connect to the newly added device */ + MIKMIDIAutoConnectBehaviorDoNotConnect, + + /** Connect to the newly added device */ + MIKMIDIAutoConnectBehaviorConnect, + + /** Connect to the newly added device only if it was previously connected (ie. in the saved configuration data) */ + MIKMIDIAutoConnectBehaviorConnectOnlyIfPreviouslyConnected, + + /** Connect to the newly added device if it was previously connected, or is unknown in the configuration data.*/ + MIKMIDIAutoConnectBehaviorConnectIfPreviouslyConnectedOrNew, +}; + /** * Protocol containing method(s) to be implemented by delegates of MIKMIDIConnectionManager. */ @@ -160,16 +184,16 @@ NS_ASSUME_NONNULL_BEGIN /** * A connection manager's delegate can implement this method to determine whether or not to automatically connect - * to a newly added MIDI device. + * to a newly added MIDI device. See MIKMIDIAutoConnectBehavior for possible return values. * - * If this method is not implemented or returns NO, the device will be connected to if the most + * If this method is not implemented, the default behavior is MIKMIDIAutoConnectBehaviorConnectIfPreviouslyConnectedOrNew. * * @param manager An instance of MIKMIDIConnectionManager. * @param device The newly added MIDI device. * - * @return YES to connect to device, NO to leave it unconnected. + * @return One of the values defined in MIKMIDIAutoConnectBehavior. */ -- (BOOL)connectionManager:(MIKMIDIConnectionManager *)manager shouldConnectToNewlyAddedDevice:(MIKMIDIDevice *)device; +- (MIKMIDIAutoConnectBehavior)connectionManager:(MIKMIDIConnectionManager *)manager shouldConnectToNewlyAddedDevice:(MIKMIDIDevice *)device; @end diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index f24bddd6..3ff79212 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -13,6 +13,7 @@ void *MIKMIDIConnectionManagerKVOContext = &MIKMIDIConnectionManagerKVOContext; NSString * const MIKMIDIConnectionManagerConnectedDevicesKey = @"MIKMIDIConnectionManagerConnectedDevicesKey"; +NSString * const MIKMIDIConnectionManagerUnconnectedDevicesKey = @"MIKMIDIConnectionManagerUnconnectedDevicesKey"; @interface MIKMIDIConnectionManager () @@ -34,11 +35,15 @@ - (instancetype)init return nil; } -- (instancetype)initWithName:(NSString *)name +- (instancetype)initWithName:(NSString *)name delegate:(id)delegate eventHandler:(MIKMIDIEventHandlerBlock)eventHandler { self = [super init]; if (self) { _name = [name copy]; + _delegate = delegate; + _eventHandler = eventHandler; + _includesVirtualDevices = YES; + _internalConnectedDevices = [[NSMutableSet alloc] init]; __weak typeof(self) weakSelf = self; @@ -48,7 +53,7 @@ - (instancetype)initWithName:(NSString *)name _connectionTokensByDevice = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; - NSKeyValueObservingOptions options = NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; + NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.deviceManager addObserver:self forKeyPath:@"availableDevices" options:options context:MIKMIDIConnectionManagerKVOContext]; [self.deviceManager addObserver:self forKeyPath:@"virtualSources" options:options context:MIKMIDIConnectionManagerKVOContext]; [self.deviceManager addObserver:self forKeyPath:@"virtualDestinations" options:options context:MIKMIDIConnectionManagerKVOContext]; @@ -58,6 +63,9 @@ - (instancetype)initWithName:(NSString *)name [nc addObserver:self selector:@selector(deviceWasUnplugged:) name:MIKMIDIDeviceWasRemovedNotification object:nil]; [nc addObserver:self selector:@selector(endpointWasPluggedIn:) name:MIKMIDIVirtualEndpointWasAddedNotification object:nil]; [nc addObserver:self selector:@selector(endpointWasUnplugged:) name:MIKMIDIVirtualEndpointWasRemovedNotification object:nil]; + + [self updateAvailableDevices]; + [self scanAndConnectToInitialAvailableDevices]; } return self; } @@ -122,6 +130,13 @@ - (void)saveConfiguration configuration[MIKMIDIConnectionManagerConnectedDevicesKey] = connectedDeviceNames; } + // And explicitly unconnected device names + NSMutableArray *unconnectedDeviceNames = configuration[MIKMIDIConnectionManagerUnconnectedDevicesKey]; + if (!unconnectedDeviceNames) { + unconnectedDeviceNames = [NSMutableArray array]; + configuration[MIKMIDIConnectionManagerUnconnectedDevicesKey] = unconnectedDeviceNames; + } + // For devices that were connected in saved configuration but are now unavailable, leave them // connected in the configuration so they'll reconnect automatically. for (MIKMIDIDevice *device in self.availableDevices) { @@ -129,8 +144,10 @@ - (void)saveConfiguration if (![name length]) continue; if ([self isConnectedToDevice:device]) { [connectedDeviceNames addObject:name]; + [unconnectedDeviceNames removeObject:name]; } else { [connectedDeviceNames removeObject:name]; + [unconnectedDeviceNames addObject:name]; } } @@ -152,6 +169,13 @@ - (void)loadConfiguration #pragma mark - Private +- (void)scanAndConnectToInitialAvailableDevices +{ + for (MIKMIDIDevice *device in self.availableDevices) { + [self connectToNewlyAddedDeviceIfAppropriate:device]; + } +} + - (void)updateAvailableDevices { NSArray *regularDevices = self.deviceManager.availableDevices; @@ -219,10 +243,26 @@ - (void)connectToNewlyAddedDeviceIfAppropriate:(MIKMIDIDevice *)device { if (!device) return; - BOOL shouldConnect = [self deviceIsConnectedInSavedConfiguration:device]; - + MIKMIDIAutoConnectBehavior behavior = MIKMIDIAutoConnectBehaviorConnectIfPreviouslyConnectedOrNew; + if ([self.delegate respondsToSelector:@selector(connectionManager:shouldConnectToNewlyAddedDevice:)]) { - shouldConnect = [self.delegate connectionManager:self shouldConnectToNewlyAddedDevice:device]; + behavior = [self.delegate connectionManager:self shouldConnectToNewlyAddedDevice:device]; + } + + BOOL shouldConnect = NO; + switch (behavior) { + case MIKMIDIAutoConnectBehaviorDoNotConnect: + shouldConnect = NO; + break; + case MIKMIDIAutoConnectBehaviorConnect: + shouldConnect = YES; + break; + case MIKMIDIAutoConnectBehaviorConnectOnlyIfPreviouslyConnected: + shouldConnect = [self deviceIsConnectedInSavedConfiguration:device]; + break; + case MIKMIDIAutoConnectBehaviorConnectIfPreviouslyConnectedOrNew: + shouldConnect = ![self deviceIsUnconnectedInSavedConfiguration:device]; + break; } if (shouldConnect) { @@ -259,6 +299,16 @@ - (BOOL)deviceIsConnectedInSavedConfiguration:(MIKMIDIDevice *)device return [connectedDeviceNames containsObject:deviceName]; } +- (BOOL)deviceIsUnconnectedInSavedConfiguration:(MIKMIDIDevice *)device +{ + NSString *deviceName = device.name; + if (![deviceName length]) return NO; + + NSDictionary *configuration = [self savedConfiguration]; + NSArray *unconnectedDeviceNames = configuration[MIKMIDIConnectionManagerUnconnectedDevicesKey]; + return [unconnectedDeviceNames containsObject:deviceName]; +} + #pragma mark Virtual Endpoints - (NSString *)deviceNameFromVirtualEndpoint:(MIKMIDIEndpoint *)endpoint From 5f2001e862233162fb4790326e3d0ad5cfa6dd30 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 14:39:26 -0700 Subject: [PATCH 243/284] Issue #39: Added nullability annotations to MIKMIDIDevice. --- Source/MIKMIDIDevice.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDIDevice.h b/Source/MIKMIDIDevice.h index b4df1297..235e58b6 100644 --- a/Source/MIKMIDIDevice.h +++ b/Source/MIKMIDIDevice.h @@ -12,6 +12,8 @@ @class MIKMIDIEntity; @class MIKMIDIEndpoint; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIDevice represents a MIDI device such as a DJ controller, MIDI piano keyboard, etc. * @@ -42,7 +44,7 @@ * Connecting to a Device * ---------------------- * - * To connect a device and start receiving MIDI messages from it, you must first get the source endpoints + * To connect a device and start receiving MIDI messages from it, you must first get the source endpoints * you want to connect to. Often there will be only one. You can retrieve all of a devices source endpoints * using the following: * @@ -50,7 +52,7 @@ * MIKMIDISourceEndpoint = [source firstObject]; // Or whichever source you want, but often there's only one. * * Next, connect to that source using MIKMIDIDeviceManager: - * + * * MIKMIDIDeviceManager *manager = [MIKMIDIDeviceManager sharedDeviceManager]; * NSError *error = nil; * BOOL success = [manager connectInput:source error:&error eventHandler:^(MIKMIDISourceEndpoint *source, NSArray *commands) { @@ -113,12 +115,12 @@ /** * The manufacturer of the MIDI device. */ -@property (nonatomic, strong, readonly) NSString *manufacturer; +@property (nonatomic, strong, readonly, nullable) NSString *manufacturer; /** * The model number of the MIDI device. */ -@property (nonatomic, strong, readonly) NSString *model; +@property (nonatomic, strong, readonly, nullable) NSString *model; /** * An NSArray containing instances of MIKMIDIEntity, representing the entities of the @@ -128,3 +130,5 @@ @property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDIEntity *) *entities; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file From 6f5a524818f2a59be47a262de36ae73e5c1ee07e Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 14:42:52 -0700 Subject: [PATCH 244/284] Issue #39: Added nullability annotations to MIKMIDIUtilities. --- Source/MIKMIDIUtilities.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index d249f9cb..68025bef 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -10,8 +10,11 @@ #import #import "MIKMIDIMappableResponder.h" #import "MIKMIDICommand.h" +#import "MIKMIDICompilerCompatibility.h" -NSString *MIKStringPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error); +NS_ASSUME_NONNULL_BEGIN + +NSString * _Nullable MIKStringPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error); BOOL MIKSetStringPropertyOnMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSString *string, NSError *__autoreleasing*error); SInt32 MIKIntegerPropertyFromMIDIObject(MIDIObjectRef object, CFStringRef propertyID, NSError *__autoreleasing*error); @@ -57,4 +60,4 @@ NSString *MIKMIDINoteLetterForMIDINoteNumber(UInt8 noteNumber); */ NSString *MIKMIDINoteLetterAndOctaveForMIDINote(UInt8 noteNumber); - +NS_ASSUME_NONNULL_END \ No newline at end of file From cbe10ced9698e8afebd975fec94b07e923341f28 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 14:52:14 -0700 Subject: [PATCH 245/284] Issue #106: Fixed bug where -[MIKMIDIConnectionManager availableDevices] actually returned an NSSet. --- Source/MIKMIDIConnectionManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 3ff79212..3e8520ad 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -179,7 +179,7 @@ - (void)scanAndConnectToInitialAvailableDevices - (void)updateAvailableDevices { NSArray *regularDevices = self.deviceManager.availableDevices; - NSMutableSet *result = [NSMutableSet setWithArray:regularDevices]; + NSMutableArray *result = [NSMutableArray arrayWithArray:regularDevices]; if (self.includesVirtualDevices) { NSMutableSet *endpointsInDevices = [NSMutableSet set]; From 41d5ed0f9e1e5e8703924a463f367fc33207dff6 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 15:07:09 -0700 Subject: [PATCH 246/284] Issue #106: MIKMIDIConnectionManager's connectedDevices and -isConnectedToDevice: actually work now. --- Source/MIKMIDIConnectionManager.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 3e8520ad..847cd862 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -92,6 +92,14 @@ - (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error if (!token) return NO; [self.connectionTokensByDevice setObject:token forKey:device]; + [self willChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:[NSSet setWithObject:device]]; + [self.internalConnectedDevices addObject:device]; + [self didChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:[NSSet setWithObject:device]]; + if (self.automaticallySavesConfiguration) [self saveConfiguration]; @@ -107,6 +115,15 @@ - (void)disconnectFromDevice:(MIKMIDIDevice *)device [self.deviceManager disconnectConnectionforToken:token]; + [self.connectionTokensByDevice removeObjectForKey:device]; + [self willChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:[NSSet setWithObject:device]]; + [self.internalConnectedDevices removeObject:device]; + [self didChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:[NSSet setWithObject:device]]; + if (self.automaticallySavesConfiguration) [self saveConfiguration]; } @@ -400,6 +417,7 @@ - (void)setIncludesVirtualDevices:(BOOL)includesVirtualDevices } } ++ (BOOL)automaticallyNotifiesObserversOfConnectedDevices { return NO; } - (MIKSetOf(MIKMIDIDevice *) *)connectedDevices { return [self.internalConnectedDevices copy]; From 0a2f6ce8420e4138c85da1ad25fcbb36debc9665 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 15:17:01 -0700 Subject: [PATCH 247/284] Issue #106: Fixed issues related to saving MIKMIDIConnectionManager configuration. --- Source/MIKMIDIConnectionManager.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 847cd862..066dc539 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -42,6 +42,8 @@ - (instancetype)initWithName:(NSString *)name delegate:(id Date: Fri, 6 Nov 2015 15:26:34 -0700 Subject: [PATCH 248/284] Fixed bug that caused device / endpoint disconnection to fail to have any effect. --- Source/MIKMIDIInputPort.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIInputPort.m b/Source/MIKMIDIInputPort.m index 659f5ec5..7aab44ee 100644 --- a/Source/MIKMIDIInputPort.m +++ b/Source/MIKMIDIInputPort.m @@ -171,7 +171,7 @@ - (void)removeEventHandlerForConnectionToken:(NSString *)connectionToken source: - (MIKMIDISourceEndpoint *)sourceEndpointForConnectionToken:(NSString *)token { - for (MIKMIDISourceEndpoint *source in self.handlerTokenPairsByEndpoint.objectEnumerator) { + for (MIKMIDISourceEndpoint *source in self.handlerTokenPairsByEndpoint) { NSArray *handlerPairs = [self.handlerTokenPairsByEndpoint objectForKey:source]; for (MIKMIDIConnectionTokenAndEventHandler *handlerPair in handlerPairs) { if ([handlerPair.connectionToken isEqual:token]) { From 428fc3844d49432c543209ff3fe39bd426e674b6 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 15:42:35 -0700 Subject: [PATCH 249/284] Issue #106: Fixed bug where -[MIKMIDIManager loadConfiguration] clobbered the saved configuration during loading. --- Source/MIKMIDIConnectionManager.m | 102 +++++++++++++++++------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 066dc539..c8449bf5 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -87,45 +87,14 @@ - (void)dealloc - (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error { - if ([self isConnectedToDevice:device]) return YES; - error = error ?: &(NSError *__autoreleasing){ nil }; - - id token = [self.deviceManager connectDevice:device error:error eventHandler:self.internalEventHandler]; - if (!token) return NO; - - [self.connectionTokensByDevice setObject:token forKey:device]; - [self willChangeValueForKey:@"connectedDevices" - withSetMutation:NSKeyValueUnionSetMutation - usingObjects:[NSSet setWithObject:device]]; - [self.internalConnectedDevices addObject:device]; - [self didChangeValueForKey:@"connectedDevices" - withSetMutation:NSKeyValueUnionSetMutation - usingObjects:[NSSet setWithObject:device]]; - - + BOOL result = [self internalConnectToDevice:device error:error]; if (self.automaticallySavesConfiguration) [self saveConfiguration]; - - return YES; + return result; } - (void)disconnectFromDevice:(MIKMIDIDevice *)device { - if (![self isConnectedToDevice:device]) return; - - id token = [self.connectionTokensByDevice objectForKey:device]; - if (!token) return; - - [self.deviceManager disconnectConnectionforToken:token]; - - [self.connectionTokensByDevice removeObjectForKey:device]; - [self willChangeValueForKey:@"connectedDevices" - withSetMutation:NSKeyValueMinusSetMutation - usingObjects:[NSSet setWithObject:device]]; - [self.internalConnectedDevices removeObject:device]; - [self didChangeValueForKey:@"connectedDevices" - withSetMutation:NSKeyValueMinusSetMutation - usingObjects:[NSSet setWithObject:device]]; - + [self internalDisconnectFromDevice:device]; if (self.automaticallySavesConfiguration) [self saveConfiguration]; } @@ -181,7 +150,7 @@ - (void)loadConfiguration for (MIKMIDIDevice *device in self.availableDevices) { if ([self deviceIsConnectedInSavedConfiguration:device]) { NSError *error = nil; - if (![self connectToDevice:device error:&error]) { + if (![self internalConnectToDevice:device error:&error]) { NSLog(@"Unable to connect to MIDI device %@: %@", device, error); return; } @@ -191,13 +160,6 @@ - (void)loadConfiguration #pragma mark - Private -- (void)scanAndConnectToInitialAvailableDevices -{ - for (MIKMIDIDevice *device in self.availableDevices) { - [self connectToNewlyAddedDeviceIfAppropriate:device]; - } -} - - (void)updateAvailableDevices { NSArray *regularDevices = self.deviceManager.availableDevices; @@ -261,6 +223,56 @@ - (MIKMIDIDevice *)firstAvailableDeviceWithName:(NSString *)deviceName return [[self.availableDevices filteredArrayUsingPredicate:predicate] firstObject]; } +#pragma mark - Connection / Disconnection + +- (BOOL)internalConnectToDevice:(MIKMIDIDevice *)device error:(NSError **)error +{ + if ([self isConnectedToDevice:device]) return YES; + error = error ?: &(NSError *__autoreleasing){ nil }; + + id token = [self.deviceManager connectDevice:device error:error eventHandler:self.internalEventHandler]; + if (!token) return NO; + + [self.connectionTokensByDevice setObject:token forKey:device]; + [self willChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:[NSSet setWithObject:device]]; + [self.internalConnectedDevices addObject:device]; + [self didChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueUnionSetMutation + usingObjects:[NSSet setWithObject:device]]; + + return YES; +} + +- (void)internalDisconnectFromDevice:(MIKMIDIDevice *)device +{ + if (![self isConnectedToDevice:device]) return; + + id token = [self.connectionTokensByDevice objectForKey:device]; + if (!token) return; + + [self.deviceManager disconnectConnectionforToken:token]; + + [self.connectionTokensByDevice removeObjectForKey:device]; + [self willChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:[NSSet setWithObject:device]]; + [self.internalConnectedDevices removeObject:device]; + [self didChangeValueForKey:@"connectedDevices" + withSetMutation:NSKeyValueMinusSetMutation + usingObjects:[NSSet setWithObject:device]]; + + if (self.automaticallySavesConfiguration) [self saveConfiguration]; +} + +- (void)scanAndConnectToInitialAvailableDevices +{ + for (MIKMIDIDevice *device in self.availableDevices) { + [self connectToNewlyAddedDeviceIfAppropriate:device]; + } +} + - (void)connectToNewlyAddedDeviceIfAppropriate:(MIKMIDIDevice *)device { if (!device) return; @@ -289,7 +301,7 @@ - (void)connectToNewlyAddedDeviceIfAppropriate:(MIKMIDIDevice *)device if (shouldConnect) { NSError *error = nil; - if (![self connectToDevice:device error:&error]) { + if (![self internalConnectToDevice:device error:&error]) { NSLog(@"Unable to connect to MIDI device %@: %@", device, error); return; } @@ -367,7 +379,7 @@ - (void)deviceWasPluggedIn:(NSNotification *)notification - (void)deviceWasUnplugged:(NSNotification *)notification { MIKMIDIDevice *unpluggedDevice = [notification userInfo][MIKMIDIDeviceKey]; - [self disconnectFromDevice:unpluggedDevice]; + [self internalDisconnectFromDevice:unpluggedDevice]; } - (void)endpointWasPluggedIn:(NSNotification *)notification @@ -381,7 +393,7 @@ - (void)endpointWasUnplugged:(NSNotification *)notification { MIKMIDIEndpoint *unpluggedEndpoint = [notification userInfo][MIKMIDIEndpointKey]; MIKMIDIDevice *unpluggedDevice = [self deviceContainingEndpoint:unpluggedEndpoint]; - if (unpluggedDevice) [self disconnectFromDevice:unpluggedDevice]; + if (unpluggedDevice) [self internalDisconnectFromDevice:unpluggedDevice]; } #pragma mark - KVO From 8d2002f4ed923703ceafa20a9aa790bb3f083986 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 15:47:20 -0700 Subject: [PATCH 250/284] Issue #106: Fixed exception thrown by MIKMIDIConnectionManager when unplugging virtual MIDI device. --- Source/MIKMIDIConnectionManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index c8449bf5..6f85ec12 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -358,7 +358,7 @@ - (NSString *)deviceNameFromVirtualEndpoint:(MIKMIDIEndpoint *)endpoint - (MIKMIDIDevice *)deviceContainingEndpoint:(MIKMIDIEndpoint *)endpoint { if (!endpoint) return nil; - NSMutableSet *devices = [self.availableDevices mutableCopy]; + NSMutableSet *devices = [NSMutableSet setWithArray:self.availableDevices]; [devices unionSet:self.connectedDevices]; for (MIKMIDIDevice *device in devices) { NSMutableSet *deviceEndpoints = [NSMutableSet setWithArray:[device.entities valueForKeyPath:@"@distinctUnionOfArrays.sources"]]; From 3de442124f115b2f57ec93f734fbe0af3907a5f0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 6 Nov 2015 16:27:58 -0700 Subject: [PATCH 251/284] Issue #106: Fix for potential to connect to 'half' of a virtual device. --- Source/MIKMIDIConnectionManager.m | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 6f85ec12..34e131d6 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -434,6 +434,23 @@ - (void)setIncludesVirtualDevices:(BOOL)includesVirtualDevices } } +- (void)setAvailableDevices:(NSArray *)availableDevices +{ + if (availableDevices != _availableDevices) { + + // Disconnect from newly unavailable devices. + // This will include "partial" virtual devices that are now complete + // by virtue of having been notified of other sources for them. + for (MIKMIDIDevice *device in self.connectedDevices) { + if (![availableDevices containsObject:device]) { + [self internalDisconnectFromDevice:device]; + } + } + + _availableDevices = availableDevices; + } +} + + (BOOL)automaticallyNotifiesObserversOfConnectedDevices { return NO; } - (MIKSetOf(MIKMIDIDevice *) *)connectedDevices { From d12cdf85aef1facf03b43e4e1b018313e72c60a3 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Nov 2015 11:32:09 -0700 Subject: [PATCH 252/284] Issue #39: Added nullability annotations to remaining code. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 6 +++ Source/MIKMIDIChannelEvent.h | 17 +++++-- Source/MIKMIDIChannelPressureEvent.h | 9 +++- Source/MIKMIDIChannelVoiceCommand.h | 7 ++- ...KMIDIChannelVoiceCommand_SubclassMethods.h | 5 ++ Source/MIKMIDIClientDestinationEndpoint.h | 9 +++- Source/MIKMIDIClientSourceEndpoint.h | 7 ++- Source/MIKMIDIClock.h | 4 +- Source/MIKMIDICommand.h | 12 +++-- Source/MIKMIDICommand.m | 2 +- Source/MIKMIDICommandScheduler.h | 4 ++ Source/MIKMIDICommandThrottler.h | 6 ++- Source/MIKMIDIControlChangeCommand.h | 11 +++-- Source/MIKMIDIControlChangeEvent.h | 9 +++- Source/MIKMIDIDestinationEndpoint.h | 5 ++ Source/MIKMIDIEndpoint.h | 7 ++- Source/MIKMIDIEndpointSynthesizer.h | 22 +++++---- Source/MIKMIDIEndpointSynthesizer.m | 8 ++-- Source/MIKMIDIEvent.h | 17 +++++-- Source/MIKMIDIEvent.m | 9 +++- Source/MIKMIDIEventIterator.h | 11 +++-- Source/MIKMIDIEvent_SubclassMethods.h | 13 ++++-- Source/MIKMIDIInputPort.m | 2 +- Source/MIKMIDIMappableResponder.h | 5 ++ Source/MIKMIDIMapping.h | 46 +++++++++++-------- Source/MIKMIDIMapping.m | 18 +++++--- Source/MIKMIDIMappingGenerator.h | 14 ++++-- Source/MIKMIDIMappingGenerator.m | 4 +- Source/MIKMIDIMappingItem.h | 15 ++++-- Source/MIKMIDIMappingManager.h | 11 +++-- Source/MIKMIDIMappingManager.m | 14 +++++- .../MIKMIDIMappingManager_SubclassMethods.h | 6 ++- Source/MIKMIDIMappingXMLParser.h | 5 ++ Source/MIKMIDIMappingXMLParser.m | 6 ++- Source/MIKMIDIMetaCopyrightEvent.h | 9 +++- Source/MIKMIDIMetaCuePointEvent.h | 9 +++- Source/MIKMIDIMetaEvent.h | 9 +++- Source/MIKMIDIMetaEvent.m | 2 +- Source/MIKMIDIMetaEvent_SubclassMethods.h | 16 +++++++ Source/MIKMIDIMetaInstrumentNameEvent.h | 9 +++- Source/MIKMIDIMetaKeySignatureEvent.h | 9 +++- Source/MIKMIDIMetaKeySignatureEvent.m | 2 +- Source/MIKMIDIMetaLyricEvent.h | 7 ++- Source/MIKMIDIMetaMarkerTextEvent.h | 9 +++- Source/MIKMIDIMetaSequenceEvent.h | 7 ++- Source/MIKMIDIMetaTextEvent.h | 13 ++++-- Source/MIKMIDIMetaTextEvent.m | 2 +- Source/MIKMIDIMetaTimeSignatureEvent.h | 9 +++- Source/MIKMIDIMetaTimeSignatureEvent.m | 2 +- Source/MIKMIDIMetaTrackSequenceNameEvent.h | 13 ++++-- Source/MIKMIDIMetronome.h | 6 ++- Source/MIKMIDINoteEvent.h | 25 ++++++---- Source/MIKMIDINoteOffCommand.h | 9 +++- Source/MIKMIDINoteOnCommand.h | 7 ++- Source/MIKMIDIObject_SubclassMethods.h | 5 ++ Source/MIKMIDIOutputPort.h | 5 ++ Source/MIKMIDIOutputPort.m | 2 +- Source/MIKMIDIPitchBendChangeCommand.h | 7 ++- Source/MIKMIDIPitchBendChangeEvent.h | 9 +++- Source/MIKMIDIPlayer.h | 9 +++- Source/MIKMIDIPolyphonicKeyPressureEvent.h | 9 +++- Source/MIKMIDIPort.h | 7 ++- Source/MIKMIDIPort.m | 2 +- Source/MIKMIDIPort_SubclassMethods.h | 4 ++ Source/MIKMIDIPrivateUtilities.h | 5 ++ Source/MIKMIDIProgramChangeCommand.h | 7 ++- Source/MIKMIDIProgramChangeEvent.h | 7 ++- Source/MIKMIDIResponder.h | 9 +++- Source/MIKMIDISequence+MIKMIDIPrivate.h | 6 ++- Source/MIKMIDISequence.h | 39 ++++++++-------- Source/MIKMIDISequence.m | 6 +-- Source/MIKMIDISequencer+MIKMIDIPrivate.h | 4 ++ Source/MIKMIDISequencer.h | 24 ++++++---- Source/MIKMIDISequencer.m | 6 ++- Source/MIKMIDISourceEndpoint.h | 5 ++ Source/MIKMIDISynthesizer.h | 13 ++++-- Source/MIKMIDISynthesizer.m | 2 +- Source/MIKMIDISynthesizerInstrument.h | 11 +++-- Source/MIKMIDISynthesizerInstrument.m | 6 +-- Source/MIKMIDISynthesizer_SubclassMethods.h | 9 +++- Source/MIKMIDISystemExclusiveCommand.h | 9 +++- Source/MIKMIDISystemMessageCommand.h | 9 +++- Source/MIKMIDITempoEvent.h | 11 +++-- Source/MIKMIDITrack.h | 9 +++- Source/MIKMIDITrack.m | 8 ++-- Source/MIKMIDITrack_Protected.h | 7 ++- Source/NSUIApplication+MIKMIDI.h | 8 +++- 87 files changed, 587 insertions(+), 212 deletions(-) create mode 100644 Source/MIKMIDIMetaEvent_SubclassMethods.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 134c39bc..b78977c8 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -255,6 +255,8 @@ 9DB366F71A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DB366F81A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */; }; 9DB366F91A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */; }; + 9DB8CD421BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB8CD401BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h */; }; + 9DB8CD431BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB8CD401BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h */; }; 9DBEBD581AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD591AAA21BE00E59734 /* MIKMIDICommand_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF3517A713A100BEE89F /* MIKMIDICommand_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9DBEBD5A1AAA21C400E59734 /* MIKMIDIEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 839D932D19C3A2C9007589C3 /* MIKMIDIEvent_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -446,6 +448,7 @@ 9DB366EF1A964C55001D1CF3 /* MIKMIDISynthesizer.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizer.m; sourceTree = ""; tabWidth = 4; usesTabs = 1; wrapsLines = 1; }; 9DB366F41A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizerInstrument.h; sourceTree = ""; }; 9DB366F51A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizerInstrument.m; sourceTree = ""; }; + 9DB8CD401BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaEvent_SubclassMethods.h; sourceTree = ""; }; 9DBEBD5C1AAA27D100E59734 /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = SOURCE_ROOT; }; 9DBEBD651AAA303700E59734 /* MIKMIDIChannelPressureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureEvent.h; sourceTree = ""; }; 9DBEBD661AAA303700E59734 /* MIKMIDIChannelPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureEvent.m; sourceTree = ""; }; @@ -700,6 +703,7 @@ isa = PBXGroup; children = ( 839D933B19C3A2F5007589C3 /* MIKMIDIMetaEvent.h */, + 9DB8CD401BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h */, 839D933C19C3A2F5007589C3 /* MIKMIDIMetaEvent.m */, 839D933719C3A2F5007589C3 /* MIKMIDIMetaCopyrightEvent.h */, 839D933819C3A2F5007589C3 /* MIKMIDIMetaCopyrightEvent.m */, @@ -861,6 +865,7 @@ 9D74EF7117A713A100BEE89F /* MIKMIDIEndpoint.h in Headers */, 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */, 9D74EF7317A713A100BEE89F /* MIKMIDIEntity.h in Headers */, + 9DB8CD421BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h in Headers */, 9D74EF7517A713A100BEE89F /* MIKMIDIErrors.h in Headers */, 9D74EF7717A713A100BEE89F /* MIKMIDIInputPort.h in Headers */, 9D74EF7917A713A100BEE89F /* MIKMIDIMapping.h in Headers */, @@ -948,6 +953,7 @@ 9DAF8B631A7B008A00F46528 /* MIKMIDINoteOnCommand.h in Headers */, 9D84951D1AA7678700C52475 /* MIKMIDIProgramChangeEvent.h in Headers */, 9DAF8B5E1A7B007300F46528 /* MIKMIDIDestinationEndpoint.h in Headers */, + 9DB8CD431BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h in Headers */, 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */, 9DAF8B7B1A7B00A700F46528 /* MIKMIDIPlayer.h in Headers */, 9DAF8B781A7B00A700F46528 /* MIKMIDIMetaTimeSignatureEvent.h in Headers */, diff --git a/Source/MIKMIDIChannelEvent.h b/Source/MIKMIDIChannelEvent.h index 11ed8858..78f313d9 100644 --- a/Source/MIKMIDIChannelEvent.h +++ b/Source/MIKMIDIChannelEvent.h @@ -7,6 +7,9 @@ // #import +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDIChannelEvent : MIKMIDIEvent @@ -18,7 +21,7 @@ * * @return A new instance of a subclass of MIKMIDIChannelEvent, or nil if there is an error. */ -+ (instancetype)channelEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDIChannelMessage)message; ++ (nullable instancetype)channelEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDIChannelMessage)message; // Properties @@ -45,21 +48,27 @@ @interface MIKMutableMIDIChannelEvent : MIKMIDIChannelEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 dataByte1; @property (nonatomic, readwrite) UInt8 dataByte2; @end +NS_ASSUME_NONNULL_END + #pragma mark - #import @class MIKMIDIClock; +NS_ASSUME_NONNULL_BEGIN + @interface MIKMIDICommand (MIKMIDIChannelEventToCommands) -+ (instancetype)commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:(MIKMIDIClock *)clock; ++ (nullable instancetype)commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:(MIKMIDIClock *)clock; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIChannelPressureEvent.h b/Source/MIKMIDIChannelPressureEvent.h index be1dc309..27ba7afd 100644 --- a/Source/MIKMIDIChannelPressureEvent.h +++ b/Source/MIKMIDIChannelPressureEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A channel pressure (aftertouch) event. @@ -32,9 +35,11 @@ @property (nonatomic, readwrite) UInt8 pressure; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 dataByte1; @property (nonatomic, readwrite) UInt8 dataByte2; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIChannelVoiceCommand.h b/Source/MIKMIDIChannelVoiceCommand.h index 72030f16..93e606d6 100644 --- a/Source/MIKMIDIChannelVoiceCommand.h +++ b/Source/MIKMIDIChannelVoiceCommand.h @@ -7,6 +7,9 @@ // #import "MIKMIDICommand.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDIChannelVoiceCommand is used to represent MIDI messages whose type is @@ -46,6 +49,8 @@ @property (nonatomic, readwrite) UInt8 dataByte2; @property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; -@property (nonatomic, copy, readwrite) NSData *data; +@property (nonatomic, copy, readwrite, null_resettable) NSData *data; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h b/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h index 1c680abc..7806d69f 100644 --- a/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h +++ b/Source/MIKMIDIChannelVoiceCommand_SubclassMethods.h @@ -8,9 +8,14 @@ #import "MIKMIDIChannelVoiceCommand.h" #import "MIKMIDICommand_SubclassMethods.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDIChannelVoiceCommand () @property (nonatomic, readwrite) NSUInteger value; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIClientDestinationEndpoint.h b/Source/MIKMIDIClientDestinationEndpoint.h index 7559b9a8..66f70397 100644 --- a/Source/MIKMIDIClientDestinationEndpoint.h +++ b/Source/MIKMIDIClientDestinationEndpoint.h @@ -7,9 +7,12 @@ // #import "MIKMIDIDestinationEndpoint.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIClientDestinationEndpoint; +NS_ASSUME_NONNULL_BEGIN + typedef void(^MIKMIDIClientDestinationEndpointEventHandler)(MIKMIDIClientDestinationEndpoint *destination, NSArray *commands); /** @@ -37,11 +40,13 @@ typedef void(^MIKMIDIClientDestinationEndpointEventHandler)(MIKMIDIClientDestina * * @return An instance of MIKMIDIClientDestinationEndpoint, or nil if an error occurs. */ -- (instancetype)initWithName:(NSString *)name receivedMessagesHandler:(MIKMIDIClientDestinationEndpointEventHandler)handler; +- (nullable instancetype)initWithName:(NSString *)name receivedMessagesHandler:(nullable MIKMIDIClientDestinationEndpointEventHandler)handler; /** * A block to be called when the receiver receives new incoming MIDI messages. */ -@property (nonatomic, strong) MIKMIDIClientDestinationEndpointEventHandler receivedMessagesHandler; +@property (nonatomic, strong, nullable) MIKMIDIClientDestinationEndpointEventHandler receivedMessagesHandler; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIClientSourceEndpoint.h b/Source/MIKMIDIClientSourceEndpoint.h index 887be9a3..c4411947 100644 --- a/Source/MIKMIDIClientSourceEndpoint.h +++ b/Source/MIKMIDIClientSourceEndpoint.h @@ -6,6 +6,9 @@ // #import "MIKMIDISourceEndpoint.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDIClientSourceEndpoint represents a virtual endpoint created by your application to send MIDI @@ -27,7 +30,7 @@ * * @return An instance of MIKMIDIClientSourceEndpoint, or nil if an error occurs. */ -- (instancetype)initWithName:(NSString*)name; +- (nullable instancetype)initWithName:(NSString *)name; /** * Used to send MIDI messages/commands from your application to a MIDI output endpoint. @@ -41,3 +44,5 @@ - (BOOL)sendCommands:(NSArray *)commands error:(NSError **)error; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIClock.h b/Source/MIKMIDIClock.h index 68966534..13a0e0fa 100644 --- a/Source/MIKMIDIClock.h +++ b/Source/MIKMIDIClock.h @@ -8,6 +8,7 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" /** * Returns the number of MIDITimeStamps that would occur during a specified time interval. @@ -25,6 +26,7 @@ Float64 MIKMIDIClockMIDITimeStampsPerTimeInterval(NSTimeInterval timeInterval); */ Float64 MIKMIDIClockSecondsPerMIDITimeStamp(); +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDIClock provides the number of seconds per MIDITimeStamp, as well as the @@ -213,6 +215,6 @@ Float64 MIKMIDIClockSecondsPerMIDITimeStamp(); */ + (Float64)midiTimeStampsPerTimeInterval:(NSTimeInterval)timeInterval DEPRECATED_ATTRIBUTE; - @end +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDICommand.h b/Source/MIKMIDICommand.h index 3bea78d9..9799b179 100644 --- a/Source/MIKMIDICommand.h +++ b/Source/MIKMIDICommand.h @@ -8,6 +8,7 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" /** * Types of MIDI messages. These values correspond directly to the MIDI command type values @@ -56,6 +57,8 @@ typedef NS_ENUM(NSUInteger, MIKMIDICommandType) { @class MIKMIDIMappingItem; +NS_ASSUME_NONNULL_BEGIN + /** * In MIKMIDI, MIDI messages are objects. Specifically, they are instances of MIKMIDICommand or one of its * subclasses. MIKMIDICommand's subclasses each represent a specific type of MIDI message, for example, @@ -193,14 +196,14 @@ typedef NS_ENUM(NSUInteger, MIKMIDICommandType) { /** * The raw data that makes up the receiver. */ -@property (nonatomic, copy, readonly) NSData *data; +@property (nonatomic, copy, readonly, null_resettable) NSData *data; /** * Optional mapping item used to route the command. This must be set by client code that handles * receiving MIDI commands. Allows responders to understand how a command was mapped, especially * useful to determine interaction type so that responders can interpret the command correctly. */ -@property (nonatomic, strong) MIKMIDIMappingItem *mappingItem; +@property (nonatomic, strong, nullable) MIKMIDIMappingItem *mappingItem; @end @@ -225,11 +228,12 @@ typedef NS_ENUM(NSUInteger, MIKMIDICommandType) { * transfered to the caller which becomes responsible for freeing the allocated memory. * Used by MIKMIDI when sending commands. Typically, this is not needed by clients of MIKMIDI. * - * @param outPacketList A pointer pointer to a MIDIPacketList structure which will point to the created MIDIPacketList + * @param outPacketList A pointer to a pointer to a MIDIPacketList structure which will point to the created MIDIPacketList * upon success. * @param commands An array of MIKMIDICommand instances. * * @return YES if creating the packet list was successful, NO if an error occurred. */ -BOOL MIKCreateMIDIPacketListFromCommands(MIDIPacketList **outPacketList, NSArray *commands); +BOOL MIKCreateMIDIPacketListFromCommands(MIDIPacketList * _Nonnull * _Nonnull outPacketList, NSArray *commands); +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 6aed4ada..0969b510 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -298,7 +298,7 @@ - (void)setData:(NSData *)data { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - self.internalData = [data mutableCopy]; + self.internalData = data ? [data mutableCopy] : [NSMutableData data]; } @end diff --git a/Source/MIKMIDICommandScheduler.h b/Source/MIKMIDICommandScheduler.h index abfd4e56..496ac728 100644 --- a/Source/MIKMIDICommandScheduler.h +++ b/Source/MIKMIDICommandScheduler.h @@ -7,7 +7,9 @@ // #import +#import "MIKMIDICompilerCompatibility.h" +NS_ASSUME_NONNULL_BEGIN /** * Objects that conform to this protocol can be used as a destination for MIDI commands to @@ -20,3 +22,5 @@ - (void)scheduleMIDICommands:(NSArray *)commands; @end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDICommandThrottler.h b/Source/MIKMIDICommandThrottler.h index 380976c1..bcf45673 100644 --- a/Source/MIKMIDICommandThrottler.h +++ b/Source/MIKMIDICommandThrottler.h @@ -7,14 +7,16 @@ // #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIChannelVoiceCommand; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDICommandThrottler is a simple utility class useful for throttling e.g. jog wheel/turntable controls, * which otherwise send many messages per revolution. */ - @interface MIKMIDICommandThrottler : NSObject /** @@ -35,3 +37,5 @@ - (void)resetThrottlingCountForCommand:(MIKMIDIChannelVoiceCommand *)command; @end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIControlChangeCommand.h b/Source/MIKMIDIControlChangeCommand.h index 5f094e9f..d1b0f6b9 100644 --- a/Source/MIKMIDIControlChangeCommand.h +++ b/Source/MIKMIDIControlChangeCommand.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelVoiceCommand.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI control change message. @@ -33,7 +36,7 @@ * @return A new, single MIKMIDIControlChangeCommand instance containing 14-bit value data, and whose * fourteenBitCommand property is set to YES. */ -+ (instancetype)commandByCoalescingMSBCommand:(MIKMIDIControlChangeCommand *)msbCommand andLSBCommand:(MIKMIDIControlChangeCommand *)lsbCommand; ++ (nullable instancetype)commandByCoalescingMSBCommand:(MIKMIDIControlChangeCommand *)msbCommand andLSBCommand:(MIKMIDIControlChangeCommand *)lsbCommand; /** * The MIDI control number for the command. @@ -101,6 +104,8 @@ @property (nonatomic, readwrite) UInt8 dataByte2; @property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; -@property (nonatomic, copy, readwrite) NSData *data; +@property (nonatomic, copy, readwrite, null_resettable) NSData *data; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIControlChangeEvent.h b/Source/MIKMIDIControlChangeEvent.h index 0f5b1cbe..30bd79f1 100644 --- a/Source/MIKMIDIControlChangeEvent.h +++ b/Source/MIKMIDIControlChangeEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * Control change events are typically sent when a controller value changes. @@ -40,9 +43,11 @@ @property (nonatomic, readwrite) NSUInteger controllerValue; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 dataByte1; @property (nonatomic, readwrite) UInt8 dataByte2; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIDestinationEndpoint.h b/Source/MIKMIDIDestinationEndpoint.h index 31ed3de3..77993f56 100644 --- a/Source/MIKMIDIDestinationEndpoint.h +++ b/Source/MIKMIDIDestinationEndpoint.h @@ -8,6 +8,9 @@ #import "MIKMIDIEndpoint.h" #import "MIKMIDICommandScheduler.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDIDestinationEndpoint represents a source (input) MIDI endpoint. @@ -36,3 +39,5 @@ - (void)unscheduleAllPendingEvents; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIEndpoint.h b/Source/MIKMIDIEndpoint.h index b32d98a9..4faccd49 100644 --- a/Source/MIKMIDIEndpoint.h +++ b/Source/MIKMIDIEndpoint.h @@ -7,9 +7,12 @@ // #import "MIKMIDIObject.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIEntity; +NS_ASSUME_NONNULL_BEGIN + /** * Base class for MIDI endpoint objects. Not used directly, rather, in use, instances will always be * instances of MIKMIDISourceEndpoint or MIKMIDIDestinationEndpoint. @@ -19,7 +22,7 @@ /** * The entity that contains the receiver. Will be nil for non-wrapped virtual endpoints. */ -@property (nonatomic, weak, readonly) MIKMIDIEntity *entity; +@property (nonatomic, weak, readonly, nullable) MIKMIDIEntity *entity; /** * Whether or not the endpoint is private or hidden. See kMIDIPropertyPrivate in MIDIServices.h. @@ -27,3 +30,5 @@ @property (nonatomic, readonly) BOOL isPrivate; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIEndpointSynthesizer.h b/Source/MIKMIDIEndpointSynthesizer.h index 9f84ffca..d59dd097 100644 --- a/Source/MIKMIDIEndpointSynthesizer.h +++ b/Source/MIKMIDIEndpointSynthesizer.h @@ -7,12 +7,15 @@ // #import "MIKMIDISynthesizer.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIEndpoint; @class MIKMIDISourceEndpoint; @class MIKMIDIClientDestinationEndpoint; @class MIKMIDISynthesizerInstrument; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIEndpointSynthesizer is a subclass of MIKMIDISynthesizer that * provides a very simple way to synthesize MIDI commands coming from a @@ -33,7 +36,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -+ (instancetype)playerWithMIDISource:(MIKMIDISourceEndpoint *)source; ++ (nullable instancetype)playerWithMIDISource:(MIKMIDISourceEndpoint *)source; /** * Creates and initializes an MIKMIDIEndpointSynthesizer instance. @@ -44,7 +47,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -+ (instancetype)playerWithMIDISource:(MIKMIDISourceEndpoint *)source componentDescription:(AudioComponentDescription)componentDescription; ++ (nullable instancetype)playerWithMIDISource:(MIKMIDISourceEndpoint *)source componentDescription:(AudioComponentDescription)componentDescription; /** * Initializes an MIKMIDIEndpointSynthesizer instance using Apple's DLS synth as the @@ -54,7 +57,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source; +- (nullable instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source; /** * Initializes an MIKMIDIEndpointSynthesizer instance. @@ -65,7 +68,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source componentDescription:(AudioComponentDescription)componentDescription; +- (nullable instancetype)initWithMIDISource:(MIKMIDISourceEndpoint *)source componentDescription:(AudioComponentDescription)componentDescription; /** * Creates and initializes an MIKMIDIEndpointSynthesizer instance using Apple's DLS synth as the @@ -75,7 +78,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -+ (instancetype)synthesizerWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination; ++ (nullable instancetype)synthesizerWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination; /** * Creates and initializes an MIKMIDIEndpointSynthesizer instance. @@ -86,7 +89,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -+ (instancetype)synthesizerWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination componentDescription:(AudioComponentDescription)componentDescription; ++ (nullable instancetype)synthesizerWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination componentDescription:(AudioComponentDescription)componentDescription; /** * Initializes an MIKMIDIEndpointSynthesizer instance using Apple's DLS synth as the @@ -96,7 +99,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination; +- (nullable instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination; /** * Initializes an MIKMIDIEndpointSynthesizer instance. @@ -107,7 +110,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination componentDescription:(AudioComponentDescription)componentDescription; +- (nullable instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpoint *)destination componentDescription:(AudioComponentDescription)componentDescription; // Properties @@ -117,7 +120,8 @@ * events coming from an external MIDI keyboard, or it may be an MIKMIDIClientDestinationEndpoint, * e.g. to synthesize MIDI events coming from an another application on the system. */ -@property (nonatomic, strong, readonly) MIKMIDIEndpoint *endpoint; +@property (nonatomic, strong, readonly, nullable) MIKMIDIEndpoint *endpoint; @end +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIEndpointSynthesizer.m b/Source/MIKMIDIEndpointSynthesizer.m index 098f6de1..4cbf91c7 100644 --- a/Source/MIKMIDIEndpointSynthesizer.m +++ b/Source/MIKMIDIEndpointSynthesizer.m @@ -93,12 +93,10 @@ - (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpo - (void)dealloc { - if (_endpoint) { - if ([_endpoint isKindOfClass:[MIKMIDISourceEndpoint class]]) { - [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionforToken:self.connectionToken]; - } - // Don't need to do anything for a destination endpoint. __weak reference in the messages handler will automatically nil out. + if ([_endpoint isKindOfClass:[MIKMIDISourceEndpoint class]]) { + [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionforToken:self.connectionToken]; } + // Don't need to do anything for a destination endpoint. __weak reference in the messages handler will automatically nil out. } #pragma mark - Private diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index c60ddd73..fcea9c6c 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -8,6 +8,7 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" /** * Types of MIDI events. These values are used to determine which subclass to @@ -89,6 +90,8 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) MIKMIDIMetaEventTypeSequencerSpecificEvent = 0x7F }; +NS_ASSUME_NONNULL_BEGIN + /** * In MIKMIDI, MIDI events are objects. Specifically, they are instances of MIKMIDIEvent or one of its * subclasses. MIKMIDIEvent's subclasses each represent a specific type of MIDI event, for example, @@ -155,7 +158,7 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) * * @see +mikEventTypeForMusicEventType: */ -+ (instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(NSData *)data; ++ (nullable instancetype)midiEventWithTimeStamp:(MusicTimeStamp)timeStamp eventType:(MusicEventType)eventType data:(nullable NSData *)data; /** * Initializes a new MIKMIDIEvent subclass instance. This method may return an instance of a different class than the @@ -168,7 +171,7 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) * @return For supported command types, an initialized MIKMIDIEvent subclass. Otherwise, an instance of * MIKMIDICommand itself. nil if there is an error. */ -- (instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(NSData *)data NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithTimeStamp:(MusicTimeStamp)timeStamp midiEventType:(MIKMIDIEventType)eventType data:(nullable NSData *)data NS_DESIGNATED_INITIALIZER; /** * The MIDI event type. @@ -196,18 +199,24 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) @property (nonatomic, readonly) MIKMIDIEventType eventType; @property (nonatomic) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @end +NS_ASSUME_NONNULL_END + #pragma mark - MIKMIDICommand+MIKMIDIEventToCommands #import @class MIKMIDIClock; +NS_ASSUME_NONNULL_BEGIN + @interface MIKMIDICommand (MIKMIDIEventToCommands) + (NSArray *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(MIKMIDIClock *)clock; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 37767edf..0b125734 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -253,7 +253,7 @@ - (NSData *)data { return [self.internalData copy]; } - (void)setData:(NSData *)data { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; - self.internalData = [data mutableCopy]; + self.internalData = data ? [data mutableCopy] : [NSMutableData data]; } - (void)setTimeStamp:(MusicTimeStamp)timeStamp @@ -262,6 +262,13 @@ - (void)setTimeStamp:(MusicTimeStamp)timeStamp _timeStamp = timeStamp; } +- (void)setInternalData:(NSMutableData *)internalData +{ + if (internalData != _internalData) { + _internalData = internalData ? [internalData mutableCopy] : [NSMutableData data]; + } +} + @end @implementation MIKMutableMIDIEvent diff --git a/Source/MIKMIDIEventIterator.h b/Source/MIKMIDIEventIterator.h index 61e2c958..21f09642 100644 --- a/Source/MIKMIDIEventIterator.h +++ b/Source/MIKMIDIEventIterator.h @@ -8,10 +8,13 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDITrack; @class MIKMIDIEvent; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIEventIterator is an Objective-C wrapper for CoreMIDI's MusicEventIterator. It is not intended for use by clients/users of * of MIKMIDI. Rather, it should be thought of as an MIKMIDI private class. @@ -21,10 +24,10 @@ @property (nonatomic, readonly) BOOL hasPreviousEvent; @property (nonatomic, readonly) BOOL hasCurrentEvent; @property (nonatomic, readonly) BOOL hasNextEvent; -@property (nonatomic, readonly) MIKMIDIEvent *currentEvent; +@property (nonatomic, readonly, nullable) MIKMIDIEvent *currentEvent; -- (instancetype)initWithTrack:(MIKMIDITrack *)track; -+ (instancetype)iteratorForTrack:(MIKMIDITrack *)track; +- (nullable instancetype)initWithTrack:(MIKMIDITrack *)track; ++ (nullable instancetype)iteratorForTrack:(MIKMIDITrack *)track; - (BOOL)seek:(MusicTimeStamp)timeStamp; - (BOOL)moveToNextEvent; @@ -33,3 +36,5 @@ - (BOOL)moveCurrentEventTo:(MusicTimeStamp)timestamp error:(NSError **)error; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIEvent_SubclassMethods.h b/Source/MIKMIDIEvent_SubclassMethods.h index e5a4ef47..49c45ea3 100644 --- a/Source/MIKMIDIEvent_SubclassMethods.h +++ b/Source/MIKMIDIEvent_SubclassMethods.h @@ -6,6 +6,10 @@ // Copyright (c) 2014 Mixed In Key. All rights reserved. // +#import "MIKMIDIEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDIEvent () @@ -77,15 +81,12 @@ * data property. Subclasses may set it. When mutating it, subclasses should manually * call -will/didChangeValueForKey for the internalData key path. */ -@property (nonatomic, strong, readwrite) NSMutableData *internalData; +@property (nonatomic, strong /* mutableCopy*/, readwrite) NSMutableData *internalData; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) MIKMIDIEventType eventType; -@property (nonatomic, strong, readwrite) NSData *metaData; - - /** * Additional description string to be appended to basic description provided by * -[MIKMIDIEvent description]. Subclasses of MIKMIDIEvent can override this @@ -109,4 +110,6 @@ */ + (BOOL)supportsMIKMIDIEventType:(MIKMIDIEventType)type DEPRECATED_ATTRIBUTE; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIInputPort.m b/Source/MIKMIDIInputPort.m index 7aab44ee..f0cfab66 100644 --- a/Source/MIKMIDIInputPort.m +++ b/Source/MIKMIDIInputPort.m @@ -40,7 +40,7 @@ @interface MIKMIDIInputPort () @implementation MIKMIDIInputPort -- (id)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name +- (instancetype)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name { self = [super initWithClient:clientRef name:name]; if (self) { diff --git a/Source/MIKMIDIMappableResponder.h b/Source/MIKMIDIMappableResponder.h index 478e4811..4966ba54 100644 --- a/Source/MIKMIDIMappableResponder.h +++ b/Source/MIKMIDIMappableResponder.h @@ -8,6 +8,7 @@ #import #import "MIKMIDIResponder.h" +#import "MIKMIDICompilerCompatibility.h" /** * Bit-mask constants used to specify MIDI responder types for mapping. @@ -82,6 +83,8 @@ typedef NS_OPTIONS(NSUInteger, MIKMIDIResponderType){ MIKMIDIResponderTypeAll = NSUIntegerMax, }; +NS_ASSUME_NONNULL_BEGIN + /** * This protocol defines methods that that must be implemented by MIDI responder objects to be mapped * using MIKMIDIMappingGenerator, and to whom MIDI messages will selectively be routed using a MIDI mapping @@ -142,3 +145,5 @@ typedef NS_OPTIONS(NSUInteger, MIKMIDIResponderType){ - (BOOL)illuminationStateForCommandIdentifier:(NSString *)commandID; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index 6afd4d8d..fb0613f9 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -7,6 +7,7 @@ // #import +#import "MIKMIDICompilerCompatibility.h" #import "MIKMIDICommand.h" #import "MIKMIDIResponder.h" @@ -16,6 +17,8 @@ @class MIKMIDIChannelVoiceCommand; @class MIKMIDIMappingItem; +NS_ASSUME_NONNULL_BEGIN + /** * Overview * -------- @@ -79,27 +82,12 @@ /** * Initializes and returns an MIKMIDIMapping object created from the XML file at url. * - * @note This method is currently only available on OS X. See https://github.com/mixedinkey-opensource/MIKMIDI/issues/2 - * * @param url An NSURL for the file to be read. * @param error If an error occurs, upon returns contains an NSError object that describes the problem. If you are not interested in possible errors, you may pass in NULL. * * @return An initialized MIKMIDIMapping instance, or nil if an error occurred. */ -- (instancetype)initWithFileAtURL:(NSURL *)url error:(NSError **)error; - -/** - * Initializes and returns an MIKMIDIMapping object created from the XML file at url. - * - * @note This method is currently only available on OS X. See https://github.com/mixedinkey-opensource/MIKMIDI/issues/2 - * - * @param url An NSURL for the file to be read. - * - * @return An initialized MIKMIDIMapping instance, or nil if an error occurred. - * - * @see -initWithFileAtURL:error: - */ -- (instancetype)initWithFileAtURL:(NSURL *)url; +- (nullable instancetype)initWithFileAtURL:(NSURL *)url error:(NSError **)error; /** * Creates and initializes an MIKMIDIMapping object that is the same as the passed in bundled mapping @@ -132,11 +120,11 @@ * Returns an NSString instance containing an XML representation of the receiver. * The XML document returned by this method can be written to disk. * - * @return An NSString containing an XML representation of the receiver. + * @return An NSString containing an XML representation of the receiver, or nil if an error occurred. * * @see -writeToFileAtURL:error: */ -- (NSString *)XMLStringRepresentation; +- (nullable NSString *)XMLStringRepresentation; /** * Writes the receiver as an XML file to the specified URL. @@ -220,7 +208,7 @@ /** * Optional additional key value pairs, which will be saved as attributes in this mapping's XML representation. Keys and values must be NSStrings. */ -@property (nonatomic, copy) NSDictionary *additionalAttributes; +@property (nonatomic, copy, nullable) NSDictionary *additionalAttributes; /** * All mapping items this mapping contains. @@ -256,3 +244,23 @@ - (void)removeMappingItems:(NSSet *)mappingItems; @end + +#pragma mark - + +@interface MIKMIDIMapping (Deprecated) + +/** + * @deprecated Use -initWithFileAtURL:error: instead. + * Initializes and returns an MIKMIDIMapping object created from the XML file at url. + * + * @param url An NSURL for the file to be read. + * + * @return An initialized MIKMIDIMapping instance, or nil if an error occurred. + * + * @see -initWithFileAtURL:error: + */ +- (nullable instancetype)initWithFileAtURL:(NSURL *)url DEPRECATED_ATTRIBUTE; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMapping.m b/Source/MIKMIDIMapping.m index af70b29d..a42e10a8 100644 --- a/Source/MIKMIDIMapping.m +++ b/Source/MIKMIDIMapping.m @@ -45,11 +45,6 @@ @interface MIKMIDIMapping () @implementation MIKMIDIMapping -- (instancetype)initWithFileAtURL:(NSURL *)url -{ - return [self initWithFileAtURL:url error:NULL]; -} - - (instancetype)initWithFileAtURL:(NSURL *)url error:(NSError **)error; { error = error ? error : &(NSError *__autoreleasing){ nil }; @@ -94,7 +89,7 @@ - (id)init { self = [super init]; if (self) { - self.internalMappingItems = [NSMutableSet set]; + _internalMappingItems = [NSMutableSet set]; } return self; } @@ -466,3 +461,14 @@ - (NSString *)name } @end + +#pragma mark - + +@implementation MIKMIDIMapping (Deprecated) + +- (instancetype)initWithFileAtURL:(NSURL *)url +{ + return [self initWithFileAtURL:url error:NULL]; +} + +@end \ No newline at end of file diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index b71226b9..e466781f 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -7,7 +7,7 @@ // #import -//#import "MIKMIDIPrivate.h" +#import "MIKMIDICompilerCompatibility.h" #import "MIKMIDIMapping.h" @@ -17,6 +17,8 @@ @protocol MIKMIDIMappingGeneratorDelegate; +NS_ASSUME_NONNULL_BEGIN + /** * Completion block for mapping generation method. * @@ -24,7 +26,7 @@ * @param messages The messages used to generate the mapping. May not include all messages received during mapping. * @param error If mapping failed, an NSError explaing the failure, nil if mapping succeeded. */ -typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem *mappingItem, NSArray *messages, NSError *error); +typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem *mappingItem, NSArray *messages, NSError *_Nullable error); /** * MIKMIDIMappingGenerator is used to map incoming commands from a MIDI device to MIDI responders in an application. @@ -124,9 +126,9 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem @property (nonatomic, MIKMIDIMappingGeneratorWeakProperty) id delegate; /** - * The device for which a mapping is being generated. Must not be nil. + * The device for which a mapping is being generated. Must not be nil for mapping to work. */ -@property (nonatomic, strong) MIKMIDIDevice *device; +@property (nonatomic, strong, nullable) MIKMIDIDevice *device; /** * The mapping being generated. Assign before mapping starts to modify existing mapping. @@ -233,4 +235,6 @@ shouldRemoveExistingMappingItems:(NSSet *)mappingItems - (MIKMIDIChannelVoiceCommand *)mappingGenerator:(MIKMIDIMappingGenerator *)generator commandByProcessingIncomingCommand:(MIKMIDIChannelVoiceCommand *)command; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index ab1c27c5..7763904f 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -80,11 +80,9 @@ - (instancetype)initWithDevice:(MIKMIDIDevice *)device error:(NSError **)error; return self; } -- (id)init +- (id)init NS_UNAVAILABLE { [NSException raise:NSInternalInconsistencyException format:@"-initWithDevice: is the designated initializer for %@", NSStringFromClass([self class])]; - self = [self initWithDevice:nil error:NULL]; // Keep the compiler happy - [self self]; // Keep the compiler happy part 2 return nil; } diff --git a/Source/MIKMIDIMappingItem.h b/Source/MIKMIDIMappingItem.h index f9dfaaff..fcf8b21f 100644 --- a/Source/MIKMIDIMappingItem.h +++ b/Source/MIKMIDIMappingItem.h @@ -9,9 +9,12 @@ #import #import "MIKMIDIMappableResponder.h" #import "MIKMIDICommand.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIMapping; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIMappingItem contains information about a mapping between a physical MIDI control, * and a single command supported by a particular MIDI responder object. @@ -38,11 +41,11 @@ * Returns an NSString instance containing an XML representation of the receiver. * The XML document returned by this method can be written to disk. * - * @return An NSString containing an XML representation of the receiver. + * @return An NSString containing an XML representation of the receiver, or nil if an error occurred. * * @see -writeToFileAtURL:error: */ -- (NSString *)XMLStringRepresentation; +- (nullable NSString *)XMLStringRepresentation; // Properties @@ -92,12 +95,14 @@ /** * Optional additional key value pairs, which will be saved as attributes in this item's XML representation. Keys and values must be NSStrings. */ -@property (nonatomic, copy) NSDictionary *additionalAttributes; +@property (nonatomic, copy, nullable) NSDictionary *additionalAttributes; /** * The MIDI Mapping the receiver belongs to. May be nil if the mappping item hasn't been added to a mapping yet, * or its mapping has been deallocated. */ -@property (nonatomic, weak, readonly) MIKMIDIMapping *mapping; +@property (nonatomic, weak, readonly, nullable) MIKMIDIMapping *mapping; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMappingManager.h b/Source/MIKMIDIMappingManager.h index 995feacb..293daa6a 100644 --- a/Source/MIKMIDIMappingManager.h +++ b/Source/MIKMIDIMappingManager.h @@ -7,11 +7,14 @@ // #import +#import "MIKMIDICompilerCompatibility.h" #define kMIKMIDIMappingFileExtension @"midimap" @class MIKMIDIMapping; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIMappingManager provides a centralized way to manage an application's * MIDI mappings. It handles both bundled (built in) mapping files, as well @@ -91,7 +94,7 @@ * * @return An MIKMIDIMapping instance for the imported file, or nil if there was an error. */ -- (MIKMIDIMapping *)importMappingFromFileAtURL:(NSURL *)URL overwritingExistingMapping:(BOOL)shouldOverwrite error:(NSError **)error; +- (nullable MIKMIDIMapping *)importMappingFromFileAtURL:(NSURL *)URL overwritingExistingMapping:(BOOL)shouldOverwrite error:(NSError **)error; /** * Saves user mappings to disk. These mappings are currently saved to a folder at //MIDI Mappings. @@ -155,6 +158,8 @@ * * @deprecated Deprecated. Use -mappingsWithName: instead. */ -- (MIKMIDIMapping *)mappingWithName:(NSString *)mappingName DEPRECATED_ATTRIBUTE; +- (nullable MIKMIDIMapping *)mappingWithName:(NSString *)mappingName DEPRECATED_ATTRIBUTE; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index 062cab1f..60a4e52a 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -226,7 +226,12 @@ - (void)loadAvailableUserMappings if (![[file pathExtension] isEqualToString:kMIKMIDIMappingFileExtension]) continue; // process the mapping file - MIKMIDIMapping *mapping = [[MIKMIDIMapping alloc] initWithFileAtURL:file]; + NSError *error = nil; + MIKMIDIMapping *mapping = [[MIKMIDIMapping alloc] initWithFileAtURL:file error:&error]; + if (!mapping) { + NSLog(@"Error loading MIDI mapping from %@: %@", file, error); + continue; + } if (mapping) [mappings addObject:mapping]; } } else { @@ -243,7 +248,12 @@ - (void)loadBundledMappings NSBundle *bundle = [NSBundle mainBundle]; NSArray *bundledMappingFileURLs = [bundle URLsForResourcesWithExtension:kMIKMIDIMappingFileExtension subdirectory:nil]; for (NSURL *file in bundledMappingFileURLs) { - MIKMIDIMapping *mapping = [[MIKMIDIMapping alloc] initWithFileAtURL:file]; + NSError *error = nil; + MIKMIDIMapping *mapping = [[MIKMIDIMapping alloc] initWithFileAtURL:file error:&error]; + if (!mapping) { + NSLog(@"Error loading MIDI mapping from %@: %@", file, error); + continue; + } mapping.bundledMapping = YES; if (mapping) [mappings addObject:mapping]; } diff --git a/Source/MIKMIDIMappingManager_SubclassMethods.h b/Source/MIKMIDIMappingManager_SubclassMethods.h index d6855266..0788118b 100644 --- a/Source/MIKMIDIMappingManager_SubclassMethods.h +++ b/Source/MIKMIDIMappingManager_SubclassMethods.h @@ -8,9 +8,11 @@ #import "MIKMIDIMappingManager.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIMapping; +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDIMappingManager () @@ -38,6 +40,8 @@ * * @return An array of legacy file names, or nil. */ -- (NSArray *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping; +- (nullable NSArray *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMappingXMLParser.h b/Source/MIKMIDIMappingXMLParser.h index 5bdf5a8e..f699cdc6 100644 --- a/Source/MIKMIDIMappingXMLParser.h +++ b/Source/MIKMIDIMappingXMLParser.h @@ -7,9 +7,12 @@ // #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIMapping; +NS_ASSUME_NONNULL_BEGIN + /** * A parser for XML MIDI mapping files. Only used on iOS. On OS X, NSXMLDocument is used * directly instead. Should be considered "private" for use by MIKMIDIMapping. @@ -22,3 +25,5 @@ @property (nonatomic, strong, readonly) NSArray *mappings; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMappingXMLParser.m b/Source/MIKMIDIMappingXMLParser.m index 5be67ef6..29daa56a 100644 --- a/Source/MIKMIDIMappingXMLParser.m +++ b/Source/MIKMIDIMappingXMLParser.m @@ -42,7 +42,9 @@ + (instancetype)parserWithXMLData:(NSData *)xmlData - (instancetype)initWithXMLData:(NSData *)xmlData { - if (![xmlData length]) return nil; + if (![xmlData length]) { + [NSException raise:NSInvalidArgumentException format:"Argument passed to -[%@ %@] must have a non-zero length.", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; + } self = [super init]; if (self) { @@ -160,7 +162,7 @@ - (void)parserDidEndDocument:(NSXMLParser *)parser - (NSArray *)mappings { if (!self.hasParsed) [self parse]; - return _mappings; + return _mappings ?: @[]; } @end diff --git a/Source/MIKMIDIMetaCopyrightEvent.h b/Source/MIKMIDIMetaCopyrightEvent.h index 36de74e5..b096106e 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.h +++ b/Source/MIKMIDIMetaCopyrightEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaTextEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing copyright information. @@ -22,6 +25,8 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaCuePointEvent.h b/Source/MIKMIDIMetaCuePointEvent.h index f9861002..d2de0268 100644 --- a/Source/MIKMIDIMetaCuePointEvent.h +++ b/Source/MIKMIDIMetaCuePointEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaTextEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing cue point information. @@ -22,6 +25,8 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaEvent.h b/Source/MIKMIDIMetaEvent.h index b931ad77..d7edec4c 100644 --- a/Source/MIKMIDIMetaEvent.h +++ b/Source/MIKMIDIMetaEvent.h @@ -7,9 +7,12 @@ // #import "MIKMIDIEvent.h" +#import "MIKMIDICompilerCompatibility.h" #define MIKMIDIEventMetadataStartOffset 8 +NS_ASSUME_NONNULL_BEGIN + /** * A MIDI meta event. */ @@ -40,6 +43,8 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index 0e96286d..c3e56060 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -73,7 +73,7 @@ - (void)setMetaData:(NSData *)metaData metaEvent->dataLength = (UInt32)[metaData length]; NSMutableData *newMetaData = [[self.internalData subdataWithRange:NSMakeRange(0, MIKMIDIEventMetadataStartOffset)] mutableCopy]; [newMetaData appendData:metaData]; - self.internalData = newMetaData; + self.internalData = newMetaData ?: [NSMutableData data]; } @end diff --git a/Source/MIKMIDIMetaEvent_SubclassMethods.h b/Source/MIKMIDIMetaEvent_SubclassMethods.h new file mode 100644 index 00000000..fb9998e8 --- /dev/null +++ b/Source/MIKMIDIMetaEvent_SubclassMethods.h @@ -0,0 +1,16 @@ +// +// MIKMIDIMetaEvent_SubclassMethods.h +// MIKMIDI +// +// Created by Andrew Madsen on 11/10/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import +#import "MIKMIDIEvent_SubclassMethods.h" + +@interface MIKMIDIMetaEvent () + +@property (nonatomic, strong, readwrite) NSData *metaData; + +@end diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.h b/Source/MIKMIDIMetaInstrumentNameEvent.h index 34aac8bf..276a912b 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.h +++ b/Source/MIKMIDIMetaInstrumentNameEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaTextEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing an instrument name. @@ -23,6 +26,8 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaKeySignatureEvent.h b/Source/MIKMIDIMetaKeySignatureEvent.h index 3d004e9b..56b94107 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.h +++ b/Source/MIKMIDIMetaKeySignatureEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing key signature information. @@ -33,8 +36,10 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @property (nonatomic, readwrite) UInt8 key; @property (nonatomic, readwrite) UInt8 scale; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index 00303e7d..2b5e7f08 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -7,7 +7,7 @@ // #import "MIKMIDIMetaKeySignatureEvent.h" -#import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIMetaEvent_SubclassMethods.h" #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) diff --git a/Source/MIKMIDIMetaLyricEvent.h b/Source/MIKMIDIMetaLyricEvent.h index d19bcfef..ae3fe3cb 100644 --- a/Source/MIKMIDIMetaLyricEvent.h +++ b/Source/MIKMIDIMetaLyricEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaTextEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing lyrics. @@ -22,6 +25,8 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaMarkerTextEvent.h b/Source/MIKMIDIMetaMarkerTextEvent.h index 358a9132..2a47a757 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.h +++ b/Source/MIKMIDIMetaMarkerTextEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaTextEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing marker information. @@ -22,6 +25,8 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaSequenceEvent.h b/Source/MIKMIDIMetaSequenceEvent.h index e2aa1f3f..2ea9b8e2 100644 --- a/Source/MIKMIDIMetaSequenceEvent.h +++ b/Source/MIKMIDIMetaSequenceEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing sequence information. @@ -22,6 +25,8 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaTextEvent.h b/Source/MIKMIDIMetaTextEvent.h index 271de1b0..3c1db46a 100644 --- a/Source/MIKMIDIMetaTextEvent.h +++ b/Source/MIKMIDIMetaTextEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing text. @@ -16,7 +19,7 @@ /** * The text for the event. */ -@property (nonatomic, readonly) NSString *string; +@property (nonatomic, readonly, nullable) NSString *string; @end @@ -27,7 +30,9 @@ @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; -@property (nonatomic, copy, readwrite) NSString *string; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; +@property (nonatomic, copy, readwrite, nullable) NSString *string; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaTextEvent.m b/Source/MIKMIDIMetaTextEvent.m index 2ff5d018..39c085fc 100644 --- a/Source/MIKMIDIMetaTextEvent.m +++ b/Source/MIKMIDIMetaTextEvent.m @@ -7,7 +7,7 @@ // #import "MIKMIDIMetaTextEvent.h" -#import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIMetaEvent_SubclassMethods.h" #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.h b/Source/MIKMIDIMetaTimeSignatureEvent.h index 3810d182..a4692dd7 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.h +++ b/Source/MIKMIDIMetaTimeSignatureEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIMetaEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing time signature information. @@ -41,11 +44,13 @@ @interface MIKMutableMIDIMetaTimeSignatureEvent : MIKMIDIMetaTimeSignatureEvent @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 numerator; @property (nonatomic, readwrite) UInt8 denominator; @property (nonatomic, readwrite) UInt8 metronomePulse; @property (nonatomic, readwrite) UInt8 thirtySecondsPerQuarterNote; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index c5561bae..16ff81ba 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -7,7 +7,7 @@ // #import "MIKMIDIMetaTimeSignatureEvent.h" -#import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIMetaEvent_SubclassMethods.h" #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index 42883361..a037792a 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -7,13 +7,16 @@ // #import "MIKMIDIMetaTextEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A meta event containing a track name. */ @interface MIKMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTextEvent -@property (nonatomic, readonly) NSString *name; +@property (nonatomic, readonly, nullable) NSString *name; @end @@ -22,11 +25,13 @@ */ @interface MIKMutableMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTrackSequenceNameEvent -@property (nonatomic, copy, readwrite) NSString *name; +@property (nonatomic, copy, readwrite, nullable) NSString *name; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 metadataType; -@property (nonatomic, strong, readwrite) NSData *metaData; +@property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @property (nonatomic, copy, readwrite) NSString *string; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMetronome.h b/Source/MIKMIDIMetronome.h index 37a15072..4cebe7cf 100644 --- a/Source/MIKMIDIMetronome.h +++ b/Source/MIKMIDIMetronome.h @@ -7,7 +7,9 @@ // #import "MIKMIDIEndpointSynthesizer.h" +#import "MIKMIDICompilerCompatibility.h" +NS_ASSUME_NONNULL_BEGIN /** * This class is only a subclass of MIKMIDIEndpointSynthesizer so it continues to function with MIKMIDIPlayer while @@ -29,4 +31,6 @@ */ - (BOOL)setupMetronome; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 61986c7c..30aa6701 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -7,9 +7,12 @@ // #import "MIKMIDIEvent.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIClock; +NS_ASSUME_NONNULL_BEGIN + /** * A MIDI note event. */ @@ -26,11 +29,11 @@ * * @return An initialized MIKMIDINoteEvent instance, or nil if an error occurred. */ -+ (instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp - note:(UInt8)note - velocity:(UInt8)velocity - duration:(Float32)duration - channel:(UInt8)channel; ++ (nullable instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp + note:(UInt8)note + velocity:(UInt8)velocity + duration:(Float32)duration + channel:(UInt8)channel; /** * Convenience method for creating a new MIKMIDINoteEvent from a CoreMIDI MIDINoteMessage struct. @@ -40,7 +43,7 @@ * * @return A new MIKMIDINoteEvent instance, or nil if there is an error. */ -+ (instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDINoteMessage)message; ++ (nullable instancetype)noteEventWithTimeStamp:(MusicTimeStamp)timeStamp message:(MIDINoteMessage)message; // Properties @@ -99,7 +102,7 @@ @interface MIKMutableMIDINoteEvent : MIKMIDINoteEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) UInt8 note; @property (nonatomic, readwrite) UInt8 velocity; @property (nonatomic, readwrite) UInt8 channel; @@ -108,15 +111,21 @@ @end +NS_ASSUME_NONNULL_END + #pragma mark - #import #import +NS_ASSUME_NONNULL_BEGIN + @interface MIKMIDICommand (MIKMIDINoteEventToCommands) + (NSArray *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; + (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; + (MIKMIDINoteOffCommand *)noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDINoteOffCommand.h b/Source/MIKMIDINoteOffCommand.h index 869dd567..1685a9ee 100644 --- a/Source/MIKMIDINoteOffCommand.h +++ b/Source/MIKMIDINoteOffCommand.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelVoiceCommand.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI note off message. @@ -26,7 +29,7 @@ + (instancetype)noteOffCommandWithNote:(NSUInteger)note velocity:(NSUInteger)velocity channel:(UInt8)channel - timestamp:(NSDate *)timestamp; + timestamp:(nullable NSDate *)timestamp; /** * The note number for the message. In the range 0-127. @@ -53,4 +56,6 @@ @property (nonatomic, readwrite) NSUInteger note; @property (nonatomic, readwrite) NSUInteger velocity; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDINoteOnCommand.h b/Source/MIKMIDINoteOnCommand.h index b7f023b6..2ef9aae1 100644 --- a/Source/MIKMIDINoteOnCommand.h +++ b/Source/MIKMIDINoteOnCommand.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelVoiceCommand.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI note on message. @@ -26,7 +29,7 @@ + (instancetype)noteOnCommandWithNote:(NSUInteger)note velocity:(NSUInteger)velocity channel:(UInt8)channel - timestamp:(NSDate *)timestamp; + timestamp:(nullable NSDate *)timestamp; /** * The note number for the message. In the range 0-127. @@ -54,3 +57,5 @@ @property (nonatomic, readwrite) NSUInteger velocity; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIObject_SubclassMethods.h b/Source/MIKMIDIObject_SubclassMethods.h index 1e3544d3..bbf9c60c 100644 --- a/Source/MIKMIDIObject_SubclassMethods.h +++ b/Source/MIKMIDIObject_SubclassMethods.h @@ -7,6 +7,9 @@ // #import "MIKMIDIObject.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * These methods can be called and/or overridden by subclasses of MIKMIDIObject, but are not @@ -55,3 +58,5 @@ @property (nonatomic, readwrite) BOOL isVirtual; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIOutputPort.h b/Source/MIKMIDIOutputPort.h index ee5c6d5a..0b4c740b 100644 --- a/Source/MIKMIDIOutputPort.h +++ b/Source/MIKMIDIOutputPort.h @@ -7,10 +7,13 @@ // #import "MIKMIDIPort.h" +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDICommand; @class MIKMIDIDestinationEndpoint; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIOutputPort is an Objective-C wrapper for CoreMIDI's MIDIPort class, and is only for destination ports. * It is not intended for use by clients/users of of MIKMIDI. Rather, it should be thought of as an @@ -21,3 +24,5 @@ - (BOOL)sendCommands:(NSArray *)commands toDestination:(MIKMIDIDestinationEndpoint *)destination error:(NSError **)error; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIOutputPort.m b/Source/MIKMIDIOutputPort.m index 091af2c5..70f77ced 100644 --- a/Source/MIKMIDIOutputPort.m +++ b/Source/MIKMIDIOutputPort.m @@ -17,7 +17,7 @@ @implementation MIKMIDIOutputPort -- (id)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name +- (instancetype)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name { self = [super initWithClient:clientRef name:name]; if (self) { diff --git a/Source/MIKMIDIPitchBendChangeCommand.h b/Source/MIKMIDIPitchBendChangeCommand.h index 758e62cd..65bda0a0 100644 --- a/Source/MIKMIDIPitchBendChangeCommand.h +++ b/Source/MIKMIDIPitchBendChangeCommand.h @@ -7,6 +7,9 @@ // #import +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI pitch bend change command. @@ -37,6 +40,8 @@ @property (nonatomic, readwrite) UInt8 dataByte2; @property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; -@property (nonatomic, copy, readwrite) NSData *data; +@property (nonatomic, copy, readwrite, null_resettable) NSData *data; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIPitchBendChangeEvent.h b/Source/MIKMIDIPitchBendChangeEvent.h index 36db4e01..2c84e07f 100644 --- a/Source/MIKMIDIPitchBendChangeEvent.h +++ b/Source/MIKMIDIPitchBendChangeEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A pitch bed change event. @@ -33,9 +36,11 @@ @property (nonatomic, readonly) UInt16 pitchChange; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 dataByte1; @property (nonatomic, readwrite) UInt8 dataByte2; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIPlayer.h b/Source/MIKMIDIPlayer.h index b6c5a257..79b08225 100644 --- a/Source/MIKMIDIPlayer.h +++ b/Source/MIKMIDIPlayer.h @@ -8,10 +8,13 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDISequence; @class MIKMIDIMetronome; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIPlayer can be used to play an MIKMIDISequence. */ @@ -53,7 +56,7 @@ __attribute((deprecated("use MIKMIDISequencer instead"))) /** * The music sequence to play. */ -@property (strong, nonatomic) MIKMIDISequence *sequence; +@property (strong, nonatomic, nullable) MIKMIDISequence *sequence; /** * The current position in the music sequence. @@ -80,10 +83,12 @@ __attribute((deprecated("use MIKMIDISequencer instead"))) @property (nonatomic, getter=isLooping) BOOL looping; @property (nonatomic, getter=isClickTrackEnabled) BOOL clickTrackEnabled; -@property (strong, nonatomic) MIKMIDIMetronome *metronome; +@property (strong, nonatomic, nullable) MIKMIDIMetronome *metronome; @property (nonatomic) BOOL stopPlaybackAtEndOfSequence; @property (nonatomic) MusicTimeStamp maxClickTrackTimeStamp; @end + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIPolyphonicKeyPressureEvent.h b/Source/MIKMIDIPolyphonicKeyPressureEvent.h index 58c78275..132317da 100644 --- a/Source/MIKMIDIPolyphonicKeyPressureEvent.h +++ b/Source/MIKMIDIPolyphonicKeyPressureEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A polyphonic key pressure (aftertouch) event. @@ -36,9 +39,11 @@ @property (nonatomic, readwrite) UInt8 pressure; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 dataByte1; @property (nonatomic, readwrite) UInt8 dataByte2; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIPort.h b/Source/MIKMIDIPort.h index 081af502..e1cc52bb 100644 --- a/Source/MIKMIDIPort.h +++ b/Source/MIKMIDIPort.h @@ -8,17 +8,22 @@ #import "MIKMIDIObject.h" #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIEndpoint; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDIPort is an Objective-C wrapper for CoreMIDI's MIDIPort class. It is not intended for use by clients/users of * of MIKMIDI. Rather, it should be thought of as an MIKMIDI private class. */ @interface MIKMIDIPort : NSObject -- (id)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name; +- (nullable instancetype)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name; @property (nonatomic, readonly) MIDIPortRef portRef; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIPort.m b/Source/MIKMIDIPort.m index 484ec886..b9a91bec 100644 --- a/Source/MIKMIDIPort.m +++ b/Source/MIKMIDIPort.m @@ -16,7 +16,7 @@ @implementation MIKMIDIPort -- (id)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name +- (instancetype)initWithClient:(MIDIClientRef)clientRef name:(NSString *)name { self = [super init]; if (self) { diff --git a/Source/MIKMIDIPort_SubclassMethods.h b/Source/MIKMIDIPort_SubclassMethods.h index 9ef4fd05..282bfe10 100644 --- a/Source/MIKMIDIPort_SubclassMethods.h +++ b/Source/MIKMIDIPort_SubclassMethods.h @@ -7,6 +7,9 @@ // #import "MIKMIDIPort.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDIPort () @@ -14,3 +17,4 @@ @end +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIPrivateUtilities.h b/Source/MIKMIDIPrivateUtilities.h index acf41de2..df1bf0ac 100644 --- a/Source/MIKMIDIPrivateUtilities.h +++ b/Source/MIKMIDIPrivateUtilities.h @@ -7,8 +7,13 @@ // #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDIChannelVoiceCommand; +NS_ASSUME_NONNULL_BEGIN + NSUInteger MIKMIDIControlNumberFromCommand(MIKMIDIChannelVoiceCommand *command); float MIKMIDIControlValueFromChannelVoiceCommand(MIKMIDIChannelVoiceCommand *command); + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIProgramChangeCommand.h b/Source/MIKMIDIProgramChangeCommand.h index 5a1aad29..032422cc 100644 --- a/Source/MIKMIDIProgramChangeCommand.h +++ b/Source/MIKMIDIProgramChangeCommand.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelVoiceCommand.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI program change message. @@ -34,4 +37,6 @@ @property (nonatomic, readwrite) NSUInteger programNumber; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIProgramChangeEvent.h b/Source/MIKMIDIProgramChangeEvent.h index 2133b48c..10b5c35a 100644 --- a/Source/MIKMIDIProgramChangeEvent.h +++ b/Source/MIKMIDIProgramChangeEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIChannelEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI program change event. @@ -40,9 +43,11 @@ @property (nonatomic, readwrite) NSUInteger programNumber; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) UInt8 dataByte1; @property (nonatomic, readwrite) UInt8 dataByte2; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIResponder.h b/Source/MIKMIDIResponder.h index be091319..4cba7517 100644 --- a/Source/MIKMIDIResponder.h +++ b/Source/MIKMIDIResponder.h @@ -7,9 +7,12 @@ // #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDICommand; +NS_ASSUME_NONNULL_BEGIN + /** * The MIKMIDIResponder protocol defines methods to be implemented by any object that wishes * to receive MIDI messages/commands. @@ -63,11 +66,13 @@ * application, as long as the receiver (or a parent responder) is registered. * * Should return a flat (non-recursive) array of subresponders. - * Return nil, empty array, or don't implement if you don't want subresponders to be + * Return empty array, or don't implement if you don't want subresponders to be * included in any case where the receiver would be considered for receiving MIDI * * @return An NSArray containing the receivers subresponders. Each object in the array must also conform to MIKMIDIResponder. */ -- (NSArray *)subresponders; +- (nullable NSArray *)subresponders; // Nullable for historical reasons. @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISequence+MIKMIDIPrivate.h b/Source/MIKMIDISequence+MIKMIDIPrivate.h index 1e4333da..a3da04c5 100644 --- a/Source/MIKMIDISequence+MIKMIDIPrivate.h +++ b/Source/MIKMIDISequence+MIKMIDIPrivate.h @@ -7,12 +7,16 @@ // #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDISequencer; +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDISequence (MIKMIDIPrivate) -@property (weak, nonatomic) MIKMIDISequencer *sequencer; +@property (weak, nonatomic, nullable) MIKMIDISequencer *sequencer; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 5a45258a..420edf1e 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -8,7 +8,7 @@ #import #import - +#import "MIKMIDICompilerCompatibility.h" typedef struct { UInt8 numerator; @@ -26,6 +26,8 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d @class MIKMIDISequencer; @class MIKMIDIDestinationEndpoint; +NS_ASSUME_NONNULL_BEGIN + /** * Instances of MIKMIDISequence contain a collection of MIDI tracks. MIKMIDISequences may be thought * of as MIDI "songs". They can be loaded from and saved to MIDI files. They can also be played @@ -41,7 +43,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @return A new instance of MIKMIDISequence, or nil if an error occured. */ -+ (instancetype)sequence; ++ (nullable instancetype)sequence; /** * Creates and initilazes a new instance of MIKMIDISequence from a MIDI file. @@ -52,7 +54,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @return A new instance of MIKMIDISequence containing the loaded file's MIDI sequence, or nil if an error occured. */ -+ (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; ++ (nullable instancetype)sequenceWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; /** * Creates and initilazes a new instance of MIKMIDISequence from a MIDI file. @@ -66,7 +68,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @return A new instance of MIKMIDISequence containing the loaded file's MIDI sequence, or nil if an error occured. */ -+ (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; ++ (nullable instancetype)sequenceWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; /** * Initilazes a new instance of MIKMIDISequence from a MIDI file. @@ -77,7 +79,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @return A new instance of MIKMIDISequence containing the loaded file's MIDI sequence, or nil if an error occured. */ -- (instancetype)initWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; +- (nullable instancetype)initWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; /** * Initilazes a new instance of MIKMIDISequence from a MIDI file. @@ -91,7 +93,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @return A new instance of MIKMIDISequence containing the loaded file's MIDI sequence, or nil if an error occured. */ -- (instancetype)initWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; +- (nullable instancetype)initWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; /** * Creates and initializes a new instance of MIKMIDISequence from MIDI data. @@ -102,7 +104,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. */ -+ (instancetype)sequenceWithData:(NSData *)data error:(NSError **)error; ++ (nullable instancetype)sequenceWithData:(NSData *)data error:(NSError **)error; /** * Creates and initializes a new instance of MIKMIDISequence from MIDI data. @@ -116,7 +118,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. */ -+ (instancetype)sequenceWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; ++ (nullable instancetype)sequenceWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; /** * Initializes a new instance of MIKMIDISequence from MIDI data. @@ -127,7 +129,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. */ -- (instancetype)initWithData:(NSData *)data error:(NSError **)error; +- (nullable instancetype)initWithData:(NSData *)data error:(NSError **)error; /** * Initializes a new instance of MIKMIDISequence from MIDI data. @@ -141,7 +143,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * @return If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, * you may pass in NULL. */ -- (instancetype)initWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; +- (nullable instancetype)initWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error; /** * Writes the MIDI sequence in Standard MIDI File format to a file at the specified URL. @@ -157,9 +159,9 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d #pragma mark - Track Management /** - * Creates and adds a new MIDI track to the sequence. + * Creates and adds a new MIDI track to the sequence. May return nil if an error occurs. */ -- (MIKMIDITrack *)addTrack; +- (nullable MIKMIDITrack *)addTrack; /** * Removes the specified MIDI track from the sequence. @@ -271,7 +273,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d /** * The sequencer this sequence is assigned to for playback. */ -@property (nonatomic, readonly) MIKMIDISequencer *sequencer; +@property (nonatomic, readonly, nullable) MIKMIDISequencer *sequencer; /** * The tempo track for the sequence. Even in a new, empty sequence, @@ -311,7 +313,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d /** * The MIDI data that composes the sequence. This data is equivalent to an NSData representation of a standard MIDI file. */ -@property (nonatomic, readonly) NSData *dataValue; +@property (nonatomic, readonly, nullable) NSData *dataValue; /** * A block to be called for each user event added to any music track owned by the sequence. @@ -332,7 +334,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @return A new instance of MIKMIDISequence containing the MIDI data, or nil if an error occured. */ -+ (instancetype)sequenceWithData:(NSData *)data DEPRECATED_ATTRIBUTE; ++ (nullable instancetype)sequenceWithData:(NSData *)data DEPRECATED_ATTRIBUTE; /** * @deprecated This method is deprecated. Use -initWithData:error: instead. @@ -343,7 +345,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @return A new instance of MIKMIDISequence containing the MIDI data, or nil if an error occured. */ -- (instancetype)initWithData:(NSData *)data DEPRECATED_ATTRIBUTE; +- (nullable instancetype)initWithData:(NSData *)data DEPRECATED_ATTRIBUTE; /** * @deprecated This method is deprecated. Use -[MIKMIDISequencer @@ -353,7 +355,7 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d * * @param destinationEndpoint The destination endpoint to set for each track in the sequence. */ -- (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)destinationEndpoint DEPRECATED_ATTRIBUTE; +- (void)setDestinationEndpoint:(nullable MIKMIDIDestinationEndpoint *)destinationEndpoint DEPRECATED_ATTRIBUTE; /** * @deprecated This method has been replaced by -tempoAtTimeStamp: and simply calls through to that method. @@ -393,5 +395,6 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d @end - FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequenceLongestTrackLength; + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index 09642772..6d7dfaee 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -174,7 +174,7 @@ - (void)dealloc { NSArray *tracks = self.internalTracks; self.internalTracks = nil; // Unregister for KVO - self.callBackBlock = nil; + [self setCallBackBlock:^(MIKMIDITrack *t, MusicTimeStamp ts, const MusicEventUserData *ud, MusicTimeStamp ts2, MusicTimeStamp ts3) {}]; for (MIKMIDITrack *track in tracks) { OSStatus err = MusicSequenceDisposeTrack(_musicSequence, track.musicTrack); @@ -208,7 +208,7 @@ - (MIKMIDITrack *)addTrack [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ MusicTrack musicTrack; OSStatus err = MusicSequenceNewTrack(self.musicSequence, &musicTrack); - if (err) return NSLog(@"MusicSequenceNewTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + if (err) { NSLog(@"MusicSequenceNewTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); }; track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; [self insertObject:track inInternalTracksAtIndex:[self.internalTracks count]]; @@ -434,7 +434,7 @@ - (NSArray *)tracks tracks = self.internalTracks; }]; - return tracks; + return tracks ?: @[]; } + (NSSet *)keyPathsForValuesAffectingLength diff --git a/Source/MIKMIDISequencer+MIKMIDIPrivate.h b/Source/MIKMIDISequencer+MIKMIDIPrivate.h index f5880b74..4cea3251 100644 --- a/Source/MIKMIDISequencer+MIKMIDIPrivate.h +++ b/Source/MIKMIDISequencer+MIKMIDIPrivate.h @@ -7,10 +7,14 @@ // #import +#import "MIKMIDICompilerCompatibility.h" +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDISequencer (MIKMIDIPrivate) - (void)dispatchSyncToProcessingQueueAsNeeded:(void (^)())block; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 81143e81..ff150551 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -8,6 +8,7 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDISequence; @class MIKMIDITrack; @@ -34,6 +35,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { MIKMIDISequencerClickTrackStatusAlwaysEnabled }; +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDISequencer can be used to play and record to an MIKMIDISequence. @@ -205,10 +207,10 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * @note If track is not contained by the receiver's sequence, this method does nothing. * * @param commandScheduler An object that conforms to MIKMIDICommandScheduler with which events - * in track should be scheduled during playback. + * in track should be scheduled during playback. Pass nil to remove an existing command scheduler * @param track An MIKMIDITrack instance. */ -- (void)setCommandScheduler:(id)commandScheduler forTrack:(MIKMIDITrack *)track; +- (void)setCommandScheduler:(nullable id)commandScheduler forTrack:(MIKMIDITrack *)track; /** * Returns the command scheduler for a track in the sequencer's sequence. @@ -228,7 +230,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * @see -builtinSynthesizerForTrack: * @see createSynthsIfNeeded */ -- (id)commandSchedulerForTrack:(MIKMIDITrack *)track; +- (nullable id)commandSchedulerForTrack:(MIKMIDITrack *)track; /** * Returns synthesizer the receiver will use to synthesize MIDI during playback @@ -243,14 +245,14 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * * @return An MIKMIDISynthesizer instance, or nil if a builtin synthesizer for track doesn't exist. */ -- (MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track; +- (nullable MIKMIDISynthesizer *)builtinSynthesizerForTrack:(MIKMIDITrack *)track; #pragma mark - Properties /** * The sequence to playback and record to. */ -@property (strong, nonatomic) MIKMIDISequence *sequence; +@property (nonatomic, strong) MIKMIDISequence *sequence; /** * Whether or not the sequencer is currently playing. This can be observed with KVO. @@ -371,7 +373,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { /** * The metronome to send click track events to. */ -@property (strong, nonatomic) MIKMIDIMetronome *metronome; +@property (nonatomic, strong, nullable) MIKMIDIMetronome *metronome; /** * When the click track should be heard. @@ -387,20 +389,20 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * @see recording * */ -@property (copy, nonatomic) NSSet *recordEnabledTracks; +@property (nonatomic, copy, nullable) NSSet *recordEnabledTracks; /** * An MIKMIDIClock that is synced with the sequencer's internal clock. * * @ @see -[MIKMIDIClock syncedClock] */ -@property (readonly, nonatomic) MIKMIDIClock *syncedClock; +@property (nonatomic, readonly) MIKMIDIClock *syncedClock; /** * The latest MIDITimeStamp the sequencer has looked ahead to to schedule MIDI events. */ -@property (readonly, nonatomic) MIDITimeStamp latestScheduledMIDITimeStamp; +@property (nonatomic, readonly) MIDITimeStamp latestScheduledMIDITimeStamp; #pragma mark - Deprecated @@ -438,7 +440,7 @@ typedef NS_ENUM(NSInteger, MIKMIDISequencerClickTrackStatus) { * @see -builtinSynthesizerForTrack: * @see createSynthsAndEndpointsIfNeeded */ -- (MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track __attribute((deprecated("use -setCommandScheduler:forTrack: instead"))); +- (nullable MIKMIDIDestinationEndpoint *)destinationEndpointForTrack:(MIKMIDITrack *)track __attribute((deprecated("use -setCommandScheduler:forTrack: instead"))); @end @@ -453,3 +455,5 @@ FOUNDATION_EXPORT NSString * const MIKMIDISequencerWillLoopNotification; * sequence regardless of sequence length. */ FOUNDATION_EXPORT const MusicTimeStamp MIKMIDISequencerEndOfSequenceLoopEndTimeStamp; + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index 67e9d00a..a74557dd 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -137,7 +137,7 @@ + (instancetype)sequencer - (void)dealloc { - self.sequence = nil; // remove KVO + [_sequence removeObserver:self forKeyPath:@"tracks"]; self.processingTimer = NULL; } @@ -552,6 +552,10 @@ - (MIKMIDINoteEvent *)pendingNoteEventWithNoteNumber:(NSNumber *)noteNumber chan - (void)setCommandScheduler:(id)commandScheduler forTrack:(MIKMIDITrack *)track { + if (!commandScheduler) { + [self.tracksToDestinationsMap removeObjectForKey:track]; + return; + } [self.tracksToDestinationsMap setObject:commandScheduler forKey:track]; [self.tracksToDefaultSynthsMap removeObjectForKey:track]; } diff --git a/Source/MIKMIDISourceEndpoint.h b/Source/MIKMIDISourceEndpoint.h index cdb8a804..5a09251a 100644 --- a/Source/MIKMIDISourceEndpoint.h +++ b/Source/MIKMIDISourceEndpoint.h @@ -7,6 +7,9 @@ // #import "MIKMIDIEndpoint.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDISourceEndpoint represents a source (input) MIDI endpoint. @@ -26,3 +29,5 @@ @interface MIKMIDISourceEndpoint : MIKMIDIEndpoint @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 9cdb4716..950fa537 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -10,6 +10,9 @@ #import #import "MIKMIDISynthesizerInstrument.h" #import "MIKMIDICommandScheduler.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDISynthesizer provides a simple way to synthesize MIDI messages to @@ -35,7 +38,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (instancetype)init; +- (nullable instancetype)init; /** * Initializes an MIKMIDISynthesizer instance which uses an audio unit matching @@ -46,7 +49,7 @@ * * @return An initialized MIKMIDIEndpointSynthesizer or nil if an error occurs. */ -- (instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componentDescription NS_DESIGNATED_INITIALIZER; /** * This synthesizer's available instruments. An array of @@ -128,7 +131,7 @@ * * @see -setupAUGraph */ -@property (nonatomic, readonly) AudioUnit instrumentUnit; +@property (nonatomic, readonly, nullable) AudioUnit instrumentUnit; /** * The AUGraph for the instrument. @@ -138,7 +141,7 @@ * * @see -setupAUGraph */ -@property (nonatomic) AUGraph graph; +@property (nonatomic, nullable) AUGraph graph; // Deprecated @@ -148,3 +151,5 @@ @property (nonatomic) AudioUnit instrument DEPRECATED_ATTRIBUTE; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 8a2ec338..c784fcc9 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -98,7 +98,7 @@ - (NSArray *)availableInstruments OSStatus err = AudioUnitGetProperty(audioUnit, kMusicDeviceProperty_InstrumentName, kAudioUnitScope_Global, instrumentID, &cName, &cNameSize); if (err) { NSLog(@"AudioUnitGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); - return nil; + return @[]; } NSString *name = [NSString stringWithCString:cName encoding:NSASCIIStringEncoding]; diff --git a/Source/MIKMIDISynthesizerInstrument.h b/Source/MIKMIDISynthesizerInstrument.h index dd9abb51..216f4b8e 100644 --- a/Source/MIKMIDISynthesizerInstrument.h +++ b/Source/MIKMIDISynthesizerInstrument.h @@ -8,6 +8,9 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * MIKMIDISynthesizerInstrument is used to represent @@ -22,7 +25,7 @@ * * @return A MIKMIDISynthesizerInstrument instance with the matching instrument ID, or nil if no instrument was found. */ -+ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID name:(NSString *)name; ++ (nullable instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID name:(NSString *)name; /** * The human readable name of the receiver. e.g. "Piano 1". @@ -60,6 +63,8 @@ * * @return A MIKMIDISynthesizerInstrument with the matching instrument ID, or nil if no instrument was found. */ -+ (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID DEPRECATED_ATTRIBUTE; ++ (nullable instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID DEPRECATED_ATTRIBUTE; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISynthesizerInstrument.m b/Source/MIKMIDISynthesizerInstrument.m index 0d5b2aca..b400ad39 100644 --- a/Source/MIKMIDISynthesizerInstrument.m +++ b/Source/MIKMIDISynthesizerInstrument.m @@ -19,7 +19,7 @@ - (instancetype)initWithName:(NSString *)name instrumentID:(MusicDeviceInstrumen { self = [super init]; if (self) { - _name = name; + _name = name ?: @"No instrument name"; _instrumentID = instrumentID; } return self; @@ -108,12 +108,12 @@ + (NSArray *)availableInstruments availableInstruments = [result copy]; }); - return availableInstruments; + return availableInstruments ?: @[]; } + (instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID { - return [self instrumentWithID:instrumentID name:nil]; + return [self instrumentWithID:instrumentID name:@"Unspecified Name"]; } @end \ No newline at end of file diff --git a/Source/MIKMIDISynthesizer_SubclassMethods.h b/Source/MIKMIDISynthesizer_SubclassMethods.h index 6072a875..20990cc5 100644 --- a/Source/MIKMIDISynthesizer_SubclassMethods.h +++ b/Source/MIKMIDISynthesizer_SubclassMethods.h @@ -7,11 +7,16 @@ // #import "MIKMIDISynthesizer.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDISynthesizer () - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)instrumentID error:(NSError **)error; -@property (nonatomic, readwrite) AudioUnit instrumentUnit; +@property (nonatomic, readwrite, nullable) AudioUnit instrumentUnit; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISystemExclusiveCommand.h b/Source/MIKMIDISystemExclusiveCommand.h index 09ccaa96..7e6e28d4 100644 --- a/Source/MIKMIDISystemExclusiveCommand.h +++ b/Source/MIKMIDISystemExclusiveCommand.h @@ -7,6 +7,7 @@ // #import "MIKMIDISystemMessageCommand.h" +#import "MIKMIDICompilerCompatibility.h" #define kMIKMIDISysexNonRealtimeManufacturerID 0x7E #define kMIKMIDISysexRealtimeManufacturerID 0x7F @@ -14,6 +15,8 @@ #define kMIKMIDISysexChannelDisregard 0x7F #define kMIKMIDISysexEndDelimiter 0xF7 +NS_ASSUME_NONNULL_BEGIN + /** * A MIDI System Exclusive (SysEx) message. System exclusive messages are * messages defined by individual manufacturers of MIDI devices. They @@ -84,6 +87,8 @@ @property (nonatomic, readwrite) UInt8 dataByte2; @property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; -@property (nonatomic, copy, readwrite) NSData *data; +@property (nonatomic, copy, readwrite, null_resettable) NSData *data; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDISystemMessageCommand.h b/Source/MIKMIDISystemMessageCommand.h index d3461512..7a537b84 100644 --- a/Source/MIKMIDISystemMessageCommand.h +++ b/Source/MIKMIDISystemMessageCommand.h @@ -7,6 +7,9 @@ // #import "MIKMIDICommand.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI system message command. This class is also the base class for @@ -27,6 +30,8 @@ @property (nonatomic, readwrite) UInt8 dataByte2; @property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; -@property (nonatomic, copy, readwrite) NSData *data; +@property (nonatomic, copy, readwrite, null_resettable) NSData *data; + +@end -@end \ No newline at end of file +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDITempoEvent.h b/Source/MIKMIDITempoEvent.h index d859a11d..61d23e8b 100644 --- a/Source/MIKMIDITempoEvent.h +++ b/Source/MIKMIDITempoEvent.h @@ -7,6 +7,9 @@ // #import "MIKMIDIEvent.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN /** * A MIDI tempo event. @@ -22,7 +25,7 @@ * * @return A new instance of MIKMIDITempoEvent, or nil if an error occured. */ -+ (instancetype)tempoEventWithTimeStamp:(MusicTimeStamp)timeStamp tempo:(Float64)bpm; ++ (nullable instancetype)tempoEventWithTimeStamp:(MusicTimeStamp)timeStamp tempo:(Float64)bpm; /** * The beats per minute of the tempo event. @@ -37,7 +40,9 @@ @interface MIKMutableMIDITempoEvent : MIKMIDITempoEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, strong, readwrite) NSMutableData *data; +@property (nonatomic, strong, readwrite, null_resettable) NSMutableData *data; @property (nonatomic, readwrite) Float64 bpm; -@end \ No newline at end of file +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 49310fca..6be539f2 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -8,12 +8,15 @@ #import #import +#import "MIKMIDICompilerCompatibility.h" @class MIKMIDISequence; @class MIKMIDIEvent; @class MIKMIDINoteEvent; @class MIKMIDIDestinationEndpoint; +NS_ASSUME_NONNULL_BEGIN + /** * Instances of MIKMIDITrack contain sequences of MIDI events. Commonly, * these will be MIDI notes. Multiple MIKMIDITracks can be contained in a @@ -153,7 +156,7 @@ /** * The MIDI sequence the track belongs to. */ -@property (weak, nonatomic, readonly) MIKMIDISequence *sequence; +@property (weak, nonatomic, readonly, nullable) MIKMIDISequence *sequence; /** * The underlying MusicTrack that backs the instance of MIKMIDITrack. @@ -274,7 +277,7 @@ * * The destination endpoint for the MIDI events of the track during playback. */ -@property (nonatomic, strong, readwrite) MIKMIDIDestinationEndpoint *destinationEndpoint DEPRECATED_ATTRIBUTE; +@property (nonatomic, strong, readwrite, nullable) MIKMIDIDestinationEndpoint *destinationEndpoint DEPRECATED_ATTRIBUTE; /** * @deprecated Use -addEvent: instead. @@ -319,3 +322,5 @@ - (BOOL)clearAllEvents DEPRECATED_ATTRIBUTE; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 0c0f9ad6..3c6e40cb 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -23,7 +23,7 @@ @interface MIKMIDITrack () -@property (weak, nonatomic) MIKMIDISequence *sequence; +@property (weak, nonatomic, nullable) MIKMIDISequence *sequence; @property (nonatomic, strong) NSMutableSet *internalEvents; @property (nonatomic, strong) NSArray *sortedEventsCache; @@ -321,12 +321,12 @@ - (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)start events = mutableEvents; }]; - return events; + return events ?: @[]; } - (NSArray *)eventsFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp { - return [self eventsOfClass:Nil fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + return [self eventsOfClass:[MIKMIDIEvent class] fromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; } - (NSArray *)notesFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp @@ -538,7 +538,7 @@ - (NSArray *)events events = self.sortedEventsCache; }]; - return events; + return events ?: @[]; } - (void)setEvents:(NSArray *)events diff --git a/Source/MIKMIDITrack_Protected.h b/Source/MIKMIDITrack_Protected.h index 68755f98..6bbeff4a 100644 --- a/Source/MIKMIDITrack_Protected.h +++ b/Source/MIKMIDITrack_Protected.h @@ -7,6 +7,9 @@ // #import "MIKMIDITrack.h" +#import "MIKMIDICompilerCompatibility.h" + +NS_ASSUME_NONNULL_BEGIN @interface MIKMIDITrack () @@ -19,7 +22,7 @@ * @note You should not call this method. It is for internal MIKMIDI use only. * To add a new track to a MIDI sequence use -[MIKMIDISequence addTrack]. */ -+ (instancetype)trackWithSequence:(MIKMIDISequence *)sequence musicTrack:(MusicTrack)musicTrack; ++ (nullable instancetype)trackWithSequence:(MIKMIDISequence *)sequence musicTrack:(MusicTrack)musicTrack; /** * Sets a temporary length and loopInfo for the track. @@ -39,3 +42,5 @@ - (void)restoreLengthAndLoopInfo; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/NSUIApplication+MIKMIDI.h b/Source/NSUIApplication+MIKMIDI.h index 8e3a3d1a..58bf9513 100644 --- a/Source/NSUIApplication+MIKMIDI.h +++ b/Source/NSUIApplication+MIKMIDI.h @@ -24,6 +24,8 @@ #endif +#import "MIKMIDICompilerCompatibility.h" + /** * Define MIKMIDI_SEARCH_VIEW_HIERARCHY_FOR_RESPONDERS as a non-zero value to (re)enable searching * the view hierarchy for MIDI responders. This is disabled by default because it's slow. @@ -36,6 +38,8 @@ @class MIKMIDICommand; +NS_ASSUME_NONNULL_BEGIN + /** * MIKMIDI implements a category on NSApplication (on OS X) or UIApplication (on iOS) * to facilitate the creation and use of a MIDI responder hierarchy, along with the ability @@ -108,7 +112,7 @@ * @return An object that conforms to MIKMIDIResponder, or nil if no registered responder for the passed in identifier * could be found. */ -- (id)MIDIResponderWithIdentifier:(NSString *)identifier; +- (nullable id)MIDIResponderWithIdentifier:(NSString *)identifier; /** * Returns all MIDI responders that have been registered with the application. @@ -135,3 +139,5 @@ @property (nonatomic) BOOL shouldCacheMIKMIDISubresponders; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file From 72d8dffb2e3477009807f542e41651fc2dc8ce8c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Nov 2015 12:05:48 -0700 Subject: [PATCH 253/284] Issue #108: Added generics annotations to remaining headers. --- Source/MIKMIDIClientDestinationEndpoint.h | 3 ++- Source/MIKMIDIClientSourceEndpoint.h | 4 +++- Source/MIKMIDICommand.h | 4 ++-- Source/MIKMIDICommandScheduler.h | 4 +++- Source/MIKMIDICommand_SubclassMethods.h | 2 +- Source/MIKMIDICompilerCompatibility.h | 10 ++++++++++ Source/MIKMIDIEvent.h | 2 +- Source/MIKMIDIEvent_SubclassMethods.h | 2 +- Source/MIKMIDIMappableResponder.h | 2 +- Source/MIKMIDIMapping.h | 14 ++++++------- Source/MIKMIDIMappingGenerator.h | 7 ++++--- Source/MIKMIDIMappingManager.h | 14 ++++++------- .../MIKMIDIMappingManager_SubclassMethods.h | 2 +- Source/MIKMIDIMappingXMLParser.h | 2 +- Source/MIKMIDINoteEvent.h | 2 +- Source/MIKMIDIObject_SubclassMethods.h | 2 +- Source/MIKMIDIOutputPort.h | 2 +- Source/MIKMIDIPlayer.m | 3 +++ Source/MIKMIDIResponder.h | 2 +- Source/MIKMIDISequence.h | 8 +++++--- Source/MIKMIDISequencer.h | 4 ++-- Source/MIKMIDISynthesizer.h | 4 ++-- Source/MIKMIDISynthesizerInstrument.h | 2 +- Source/MIKMIDITrack.h | 20 +++++++++---------- Source/NSUIApplication+MIKMIDI.h | 2 +- 25 files changed, 72 insertions(+), 51 deletions(-) diff --git a/Source/MIKMIDIClientDestinationEndpoint.h b/Source/MIKMIDIClientDestinationEndpoint.h index 66f70397..67d61ebb 100644 --- a/Source/MIKMIDIClientDestinationEndpoint.h +++ b/Source/MIKMIDIClientDestinationEndpoint.h @@ -10,10 +10,11 @@ #import "MIKMIDICompilerCompatibility.h" @class MIKMIDIClientDestinationEndpoint; +@class MIKMIDICommand; NS_ASSUME_NONNULL_BEGIN -typedef void(^MIKMIDIClientDestinationEndpointEventHandler)(MIKMIDIClientDestinationEndpoint *destination, NSArray *commands); +typedef void(^MIKMIDIClientDestinationEndpointEventHandler)(MIKMIDIClientDestinationEndpoint *destination, MIKArrayOf(MIKMIDICommand *) *commands); /** * MIKMIDIClientDestinationEndpoint represents a virtual endpoint created by your application to receive MIDI diff --git a/Source/MIKMIDIClientSourceEndpoint.h b/Source/MIKMIDIClientSourceEndpoint.h index c4411947..28dbdfb5 100644 --- a/Source/MIKMIDIClientSourceEndpoint.h +++ b/Source/MIKMIDIClientSourceEndpoint.h @@ -8,6 +8,8 @@ #import "MIKMIDISourceEndpoint.h" #import "MIKMIDICompilerCompatibility.h" +@class MIKMIDICommand; + NS_ASSUME_NONNULL_BEGIN /** @@ -41,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return YES if the commands were successfully sent, NO if an error occurred. */ -- (BOOL)sendCommands:(NSArray *)commands error:(NSError **)error; +- (BOOL)sendCommands:(MIKArrayOf(MIKMIDICommand *) *)commands error:(NSError **)error; @end diff --git a/Source/MIKMIDICommand.h b/Source/MIKMIDICommand.h index 9799b179..3007b96c 100644 --- a/Source/MIKMIDICommand.h +++ b/Source/MIKMIDICommand.h @@ -141,7 +141,7 @@ NS_ASSUME_NONNULL_BEGIN * * @see +commandForCommandType: */ -+ (NSArray *)commandsWithMIDIPacket:(MIDIPacket *)packet; ++ (MIKArrayOf(MIKMIDICommand *) *)commandsWithMIDIPacket:(MIDIPacket *)packet; /** @@ -234,6 +234,6 @@ NS_ASSUME_NONNULL_BEGIN * * @return YES if creating the packet list was successful, NO if an error occurred. */ -BOOL MIKCreateMIDIPacketListFromCommands(MIDIPacketList * _Nonnull * _Nonnull outPacketList, NSArray *commands); +BOOL MIKCreateMIDIPacketListFromCommands(MIDIPacketList * _Nonnull * _Nonnull outPacketList, MIKArrayOf(MIKMIDICommand *) *commands); NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDICommandScheduler.h b/Source/MIKMIDICommandScheduler.h index 496ac728..3a5d423a 100644 --- a/Source/MIKMIDICommandScheduler.h +++ b/Source/MIKMIDICommandScheduler.h @@ -9,6 +9,8 @@ #import #import "MIKMIDICompilerCompatibility.h" +@class MIKMIDICommand; + NS_ASSUME_NONNULL_BEGIN /** @@ -19,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN */ @protocol MIKMIDICommandScheduler -- (void)scheduleMIDICommands:(NSArray *)commands; +- (void)scheduleMIDICommands:(MIKArrayOf(MIKMIDICommand *) *)commands; @end diff --git a/Source/MIKMIDICommand_SubclassMethods.h b/Source/MIKMIDICommand_SubclassMethods.h index d7778c12..e5ceac6c 100644 --- a/Source/MIKMIDICommand_SubclassMethods.h +++ b/Source/MIKMIDICommand_SubclassMethods.h @@ -38,7 +38,7 @@ * * @return An NSArray of NSNumber instances containing MIKMIDICommandType values. */ -+ (NSArray *)supportedMIDICommandTypes; ++ (MIKArrayOf(NSNumber *) *)supportedMIDICommandTypes; /** * The immutable counterpart class of the receiver. diff --git a/Source/MIKMIDICompilerCompatibility.h b/Source/MIKMIDICompilerCompatibility.h index de4b6e6b..fc081e76 100644 --- a/Source/MIKMIDICompilerCompatibility.h +++ b/Source/MIKMIDICompilerCompatibility.h @@ -26,6 +26,11 @@ #define MIKArrayOf(TYPE) NSArray #define MIKArrayOfKindOf(TYPE) NSArray<__kindof TYPE> +#define MIKMutableArrayOf(TYPE) NSMutableArray + +#define MIKSetOf(TYPE) NSSet +#define MIKMutableSetOf(TYPE) NSMutableSet + #define MIKMapTableOf(KEYTYPE, OBJTYPE) NSMapTable #else @@ -33,6 +38,11 @@ #define MIKArrayOf(TYPE) NSArray #define MIKArrayOfKindOf(TYPE) NSArray +#define MIKMutableArrayOf(TYPE) NSMutableArray + +#define MIKSetOf(TYPE) NSSet +#define MIKMutableSetOf(TYPE) NSMutableSet + #define MIKMapTableOf(KEYTYPE, OBJTYPE) NSMapTable #endif diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index fcea9c6c..fdfbf59d 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -215,7 +215,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMIDICommand (MIKMIDIEventToCommands) -+ (NSArray *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(MIKMIDIClock *)clock; ++ (MIKArrayOf(MIKMIDICommand *) *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(MIKMIDIClock *)clock; @end diff --git a/Source/MIKMIDIEvent_SubclassMethods.h b/Source/MIKMIDIEvent_SubclassMethods.h index 49c45ea3..e7887657 100644 --- a/Source/MIKMIDIEvent_SubclassMethods.h +++ b/Source/MIKMIDIEvent_SubclassMethods.h @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSArray of NSNumber instances containing MIKMIDIEventType values. */ -+ (NSArray *)supportedMIDIEventTypes; ++ (MIKArrayOf(NSNumber *) *)supportedMIDIEventTypes; /** * The immutable counterpart class of the receiver. diff --git a/Source/MIKMIDIMappableResponder.h b/Source/MIKMIDIMappableResponder.h index 4966ba54..c8d4d46a 100644 --- a/Source/MIKMIDIMappableResponder.h +++ b/Source/MIKMIDIMappableResponder.h @@ -103,7 +103,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSArray containing NSString identifers for all MIDI mappable commands supported by the receiver. */ -- (NSArray *)commandIdentifiers; +- (MIKArrayOf(NSString *) *)commandIdentifiers; /** * The MIDI responder types the receiver will allow to be mapped to the command specified by commandID. diff --git a/Source/MIKMIDIMapping.h b/Source/MIKMIDIMapping.h index fb0613f9..68e30bf4 100644 --- a/Source/MIKMIDIMapping.h +++ b/Source/MIKMIDIMapping.h @@ -148,7 +148,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSSet containing MIKMIDIMappingItems for responder, or an empty set if none are found. */ -- (NSSet *)mappingItemsForMIDIResponder:(id)responder; +- (MIKSetOf(MIKMIDIMappingItem *) *)mappingItemsForMIDIResponder:(id)responder; /** * The mapping items that map controls to a specific command identifier supported by a MIDI responder. @@ -161,7 +161,7 @@ NS_ASSUME_NONNULL_BEGIN * @see -[ commandIdentifiers] * @see -mappingItemsForCommandIdentifier:responderWithIdentifier: */ -- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)commandID responder:(id)responder; +- (MIKSetOf(MIKMIDIMappingItem *) *)mappingItemsForCommandIdentifier:(NSString *)commandID responder:(id)responder; /** * The mapping items that map controls to a specific command identifier supported by a MIDI responder with a given @@ -175,7 +175,7 @@ NS_ASSUME_NONNULL_BEGIN * @see -[ commandIdentifiers] * @see -mappingItemsForCommandIdentifier:responder: */ -- (NSSet *)mappingItemsForCommandIdentifier:(NSString *)commandID responderWithIdentifier:(NSString *)responderID; +- (MIKSetOf(MIKMIDIMappingItem *) *)mappingItemsForCommandIdentifier:(NSString *)commandID responderWithIdentifier:(NSString *)responderID; /** * The mapping items for a particular MIDI command (corresponding to a physical control). @@ -186,7 +186,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSSet containing MIKMIDIMappingItems for command, or an empty set if none are found. */ -- (NSSet *)mappingItemsForMIDICommand:(MIKMIDIChannelVoiceCommand *)command; +- (MIKSetOf(MIKMIDIMappingItem *) *)mappingItemsForMIDICommand:(MIKMIDIChannelVoiceCommand *)command; /** * The name of the MIDI mapping. Currently only used to determine the (default) file name when saving a mapping to disk. @@ -213,7 +213,7 @@ NS_ASSUME_NONNULL_BEGIN /** * All mapping items this mapping contains. */ -@property (nonatomic, readonly) NSSet *mappingItems; +@property (nonatomic, readonly) MIKSetOf(MIKMIDIMappingItem *) *mappingItems; /** * Add a single mapping item to the receiver. @@ -227,7 +227,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param mappingItems An NSSet containing mappings to be added. */ -- (void)addMappingItems:(NSSet *)mappingItems; +- (void)addMappingItems:(MIKSetOf(MIKMIDIMappingItem *) *)mappingItems; /** * Remove a mapping item from the receiver. @@ -241,7 +241,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param mappingItems An NSSet containing mappings to be removed. */ -- (void)removeMappingItems:(NSSet *)mappingItems; +- (void)removeMappingItems:(MIKSetOf(MIKMIDIMappingItem *) *)mappingItems; @end diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index e466781f..ab6e561b 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -14,6 +14,7 @@ @class MIKMIDIDevice; @class MIKMIDIMapping; @class MIKMIDIMappingItem; +@class MIKMIDICommand; @protocol MIKMIDIMappingGeneratorDelegate; @@ -26,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN * @param messages The messages used to generate the mapping. May not include all messages received during mapping. * @param error If mapping failed, an NSError explaing the failure, nil if mapping succeeded. */ -typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem *mappingItem, NSArray *messages, NSError *_Nullable error); +typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem *mappingItem, MIKArrayOf(MIKMIDICommand *) *messages, NSError *_Nullable error); /** * MIKMIDIMappingGenerator is used to map incoming commands from a MIDI device to MIDI responders in an application. @@ -203,7 +204,7 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMappingGeneratorRemapBehavior) { * @return The behavior to use when mapping the newResponder. See MIKMIDIMappingGeneratorRemapBehavior for a list of possible values. */ - (MIKMIDIMappingGeneratorRemapBehavior)mappingGenerator:(MIKMIDIMappingGenerator *)generator - behaviorForRemappingControlMappedWithItems:(NSSet *)mappingItems + behaviorForRemappingControlMappedWithItems:(MIKSetOf(MIKMIDIMappingItem *) *)mappingItems toNewResponder:(id)newResponder commandIdentifier:(NSString *)commandIdentifier; @@ -220,7 +221,7 @@ typedef NS_ENUM(NSUInteger, MIKMIDIMappingGeneratorRemapBehavior) { * @return YES to remove the existing mapping items. NO to keep the existing mapping items in addition to the new mapping item being generated. */ - (BOOL)mappingGenerator:(MIKMIDIMappingGenerator *)generator -shouldRemoveExistingMappingItems:(NSSet *)mappingItems +shouldRemoveExistingMappingItems:(MIKSetOf(MIKMIDIMappingItem *) *)mappingItems forResponderBeingMapped:(id)responder; /** diff --git a/Source/MIKMIDIMappingManager.h b/Source/MIKMIDIMappingManager.h index 293daa6a..1d3877fc 100644 --- a/Source/MIKMIDIMappingManager.h +++ b/Source/MIKMIDIMappingManager.h @@ -44,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSSet containing MIKMIDIMapping instances. */ -- (NSSet *)mappingsForControllerName:(NSString *)name; +- (MIKSetOf(MIKMIDIMapping *) *)mappingsForControllerName:(NSString *)name; /** * Used to obtain the set of bundled mappings for the controller @@ -54,7 +54,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSSet containing MIKMIDIMapping instances. */ -- (NSSet *)bundledMappingsForControllerName:(NSString *)name; +- (MIKSetOf(MIKMIDIMapping *) *)bundledMappingsForControllerName:(NSString *)name; /** * Used to obtain the set of user-supplied mappings for the controller @@ -64,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSSet containing MIKMIDIMapping instances. */ -- (NSSet *)userMappingsForControllerName:(NSString *)name; +- (MIKSetOf(MIKMIDIMapping *) *)userMappingsForControllerName:(NSString *)name; /** * Used to obtaining a mapping file with a given mapping name. @@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of MIKMIDIMapping instances, or an empty array if no mapping could be found. */ -- (NSArray *)mappingsWithName:(NSString *)mappingName; +- (MIKArrayOf(MIKMIDIMapping *) *)mappingsWithName:(NSString *)mappingName; #if !TARGET_OS_IPHONE /** @@ -114,12 +114,12 @@ NS_ASSUME_NONNULL_BEGIN * MIDI mappings loaded from the application's bundle. These are built in mapping, shipped * with the application. */ -@property (nonatomic, strong, readonly) NSSet *bundledMappings; +@property (nonatomic, strong, readonly) MIKSetOf(MIKMIDIMapping *) *bundledMappings; /** * MIDI mappings loaded from the user mappings folder on disk, as well as added at runtime. */ -@property (nonatomic, strong, readonly) NSSet *userMappings; +@property (nonatomic, strong, readonly) MIKSetOf(MIKMIDIMapping *) *userMappings; /** * Add a new user mapping. The mapping will automatically be saved to a file in the @@ -143,7 +143,7 @@ NS_ASSUME_NONNULL_BEGIN * The value of this property is the same as the union of -bundledMappings and -userMappings * */ -@property (nonatomic, strong, readonly) NSSet *mappings; +@property (nonatomic, strong, readonly) MIKSetOf(MIKMIDIMapping *) *mappings; @end diff --git a/Source/MIKMIDIMappingManager_SubclassMethods.h b/Source/MIKMIDIMappingManager_SubclassMethods.h index 0788118b..618c548a 100644 --- a/Source/MIKMIDIMappingManager_SubclassMethods.h +++ b/Source/MIKMIDIMappingManager_SubclassMethods.h @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of legacy file names, or nil. */ -- (nullable NSArray *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping; +- (nullable MIKArrayOf(NSString *) *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping; @end diff --git a/Source/MIKMIDIMappingXMLParser.h b/Source/MIKMIDIMappingXMLParser.h index f699cdc6..29915dd5 100644 --- a/Source/MIKMIDIMappingXMLParser.h +++ b/Source/MIKMIDIMappingXMLParser.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)parserWithXMLData:(NSData *)xmlData; - (instancetype)initWithXMLData:(NSData *)xmlData; -@property (nonatomic, strong, readonly) NSArray *mappings; +@property (nonatomic, strong, readonly) MIKArrayOf(MIKMIDIMapping *) *mappings; @end diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 30aa6701..000c785b 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -122,7 +122,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMIDICommand (MIKMIDINoteEventToCommands) -+ (NSArray *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; ++ (MIKArrayOf(MIKMIDICommand *) *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; + (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; + (MIKMIDINoteOffCommand *)noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; diff --git a/Source/MIKMIDIObject_SubclassMethods.h b/Source/MIKMIDIObject_SubclassMethods.h index bbf9c60c..d4b1d95a 100644 --- a/Source/MIKMIDIObject_SubclassMethods.h +++ b/Source/MIKMIDIObject_SubclassMethods.h @@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSArray containing NSNumber representations of MIDIObjectType values. */ -+ (NSArray *)representedMIDIObjectTypes; ++ (MIKArrayOf(NSNumber *) *)representedMIDIObjectTypes; /** * Whether the receiver can be initialized with the passed in objectRef, diff --git a/Source/MIKMIDIOutputPort.h b/Source/MIKMIDIOutputPort.h index 0b4c740b..38c606a3 100644 --- a/Source/MIKMIDIOutputPort.h +++ b/Source/MIKMIDIOutputPort.h @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMIDIOutputPort : MIKMIDIPort -- (BOOL)sendCommands:(NSArray *)commands toDestination:(MIKMIDIDestinationEndpoint *)destination error:(NSError **)error; +- (BOOL)sendCommands:(MIKArrayOf(MIKMIDICommand *) *)commands toDestination:(MIKMIDIDestinationEndpoint *)destination error:(NSError **)error; @end diff --git a/Source/MIKMIDIPlayer.m b/Source/MIKMIDIPlayer.m index e9ef4735..76b43157 100644 --- a/Source/MIKMIDIPlayer.m +++ b/Source/MIKMIDIPlayer.m @@ -11,8 +11,11 @@ #import "MIKMIDITrack_Protected.h" #import "MIKMIDISequence.h" #import "MIKMIDIMetronome.h" +#import "MIKMIDIEvent.h" #import "MIKMIDINoteEvent.h" #import "MIKMIDIClientDestinationEndpoint.h" +#import "MIKMIDITempoEvent.h" +#import "MIKMIDIMetaTimeSignatureEvent.h" #if !__has_feature(objc_arc) #error MIKMIDIPlayer.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIPlayer.m in the Build Phases for this target diff --git a/Source/MIKMIDIResponder.h b/Source/MIKMIDIResponder.h index 4cba7517..7ae52e0b 100644 --- a/Source/MIKMIDIResponder.h +++ b/Source/MIKMIDIResponder.h @@ -71,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSArray containing the receivers subresponders. Each object in the array must also conform to MIKMIDIResponder. */ -- (nullable NSArray *)subresponders; // Nullable for historical reasons. +- (nullable MIKArrayOf(id) *)subresponders; // Nullable for historical reasons. @end diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 420edf1e..80bf03a8 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -25,6 +25,8 @@ NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 d @class MIKMIDITrack; @class MIKMIDISequencer; @class MIKMIDIDestinationEndpoint; +@class MIKMIDIMetaTimeSignatureEvent; +@class MIKMIDITempoEvent; NS_ASSUME_NONNULL_BEGIN @@ -182,7 +184,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of MIKMIDIMetaTimeSignatureEvent. */ -- (NSArray *)timeSignatureEvents; +- (MIKArrayOf(MIKMIDIMetaTimeSignatureEvent *) *)timeSignatureEvents; /** * Returns an array of MIKMIDITempoEvent from the tempo track. @@ -192,7 +194,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of MIKMIDITempoEvent. */ -- (NSArray *)tempoEvents; +- (MIKArrayOf(MIKMIDITempoEvent *) *)tempoEvents; /** * Removes any existing tempo events and inserts a tempo event with the desired bpm at the beginning of the tempo track. @@ -287,7 +289,7 @@ NS_ASSUME_NONNULL_BEGIN * * This property can be observed using Key Value Observing. */ -@property (nonatomic, readonly) NSArray *tracks; +@property (nonatomic, readonly) MIKArrayOf(MIKMIDITrack *) *tracks; /** * The underlying MusicSequence that backs the instance of MIKMIDISequence. diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index ff150551..68ad723f 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -126,7 +126,7 @@ NS_ASSUME_NONNULL_BEGIN * MIDI sequence before they get sent to their destination endpoint. * */ -- (NSArray *)modifiedMIDICommandsFromCommandsToBeScheduled:(NSArray *)commandsToBeScheduled forCommandScheduler:(id)scheduler; +- (MIKArrayOf(MIKMIDICommand *) *)modifiedMIDICommandsFromCommandsToBeScheduled:(MIKArrayOf(MIKMIDICommand *) *)commandsToBeScheduled forCommandScheduler:(id)scheduler; /** * Sets the loopStartTimeStamp and loopEndTimeStamp properties. @@ -389,7 +389,7 @@ NS_ASSUME_NONNULL_BEGIN * @see recording * */ -@property (nonatomic, copy, nullable) NSSet *recordEnabledTracks; +@property (nonatomic, copy, nullable) MIKSetOf(MIKMIDITrack *) *recordEnabledTracks; /** * An MIKMIDIClock that is synced with the sequencer's internal clock. diff --git a/Source/MIKMIDISynthesizer.h b/Source/MIKMIDISynthesizer.h index 950fa537..f510803d 100644 --- a/Source/MIKMIDISynthesizer.h +++ b/Source/MIKMIDISynthesizer.h @@ -61,7 +61,7 @@ NS_ASSUME_NONNULL_BEGIN * Instruments returned by this property can be selected using * -selectInstrument: */ -@property (nonatomic, readonly) NSArray *availableInstruments; +@property (nonatomic, readonly) MIKArrayOf(MIKMIDISynthesizerInstrument *) *availableInstruments; /** * Changes the instrument/voice used by the synthesizer. @@ -113,7 +113,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param messages An NSArray of MIKMIDICommand (subclass) instances. */ -- (void)handleMIDIMessages:(NSArray *)messages; +- (void)handleMIDIMessages:(MIKArrayOf(MIKMIDICommand *) *)messages; // Properties diff --git a/Source/MIKMIDISynthesizerInstrument.h b/Source/MIKMIDISynthesizerInstrument.h index 216f4b8e..2b19e158 100644 --- a/Source/MIKMIDISynthesizerInstrument.h +++ b/Source/MIKMIDISynthesizerInstrument.h @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSArray containing MIKMIDISynthesizerInstrument instances. */ -+ (NSArray *)availableInstruments DEPRECATED_ATTRIBUTE; ++ (MIKArrayOf(MIKMIDISynthesizerInstrument *) *)availableInstruments DEPRECATED_ATTRIBUTE; /** * @deprecated Use +instrumentWithID:inInstrumentUnit: instead. diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 6be539f2..592add22 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param events An NSArray containing the events to be added. */ -- (void)addEvents:(NSArray *)events; +- (void)addEvents:(MIKArrayOf(MIKMIDIEvent *) *)events; /** * Removes the specified MIDI event from the receiver. @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN * * @param events An NSArray containing the events to be removed. */ -- (void)removeEvents:(NSArray *)events; +- (void)removeEvents:(MIKArrayOf(MIKMIDIEvent *) *)events; /** * Removes all MIDI events from the receiver. @@ -68,7 +68,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of MIKMIDIEvent. */ -- (NSArray *)eventsFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; +- (MIKArrayOf(MIKMIDIEvent *) *)eventsFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; /** * Gets all of the MIDI events of a specific class in the track starting from startTimeStamp and ending at endTimeStamp inclusively. @@ -80,7 +80,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of specified class of MIDI events. */ -- (NSArray *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; +- (MIKArrayOf(MIKMIDIEvent *) *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; /** * Gets all of the MIDI notes in the track starting from startTimeStamp and ending at endTimeStamp inclusively. @@ -89,11 +89,11 @@ NS_ASSUME_NONNULL_BEGIN * @param endTimeStamp The ending time stamp for the range to get MIDI notes for. Use kMusicTimeStamp_EndOfTrack to get events up to the * end of the track. * - * @return An array of MIKMIDINoteEvent. + * @return An array of MIKMIDINoteEvent instances. * * @discussion Calling this method is equivalent to calling eventsOfClass:fromTimeStamp:toTimeStamp: with [MIKMIDINoteEvent class]. */ -- (NSArray *)notesFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; +- (MIKArrayOf(MIKMIDINoteEvent *) *)notesFromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; #pragma mark - Event Manipulation @@ -168,14 +168,14 @@ NS_ASSUME_NONNULL_BEGIN * * This property can be observed using Key Value Observing. */ -@property (nonatomic, copy) NSArray *events; +@property (nonatomic, copy) MIKArrayOf(MIKMIDIEvent *) *events; /** * An array of MIKMIDINoteEvent containing all of the MIDI note events for the track, sorted by timestamp. * * This property can be observed using Key Value Observing. */ -@property (nonatomic, readonly) NSArray *notes; +@property (nonatomic, readonly) MIKArrayOf(MIKMIDINoteEvent *) *notes; /** * The receiver's index in its containing sequence, or -1 if the track isn't in a sequence. @@ -299,7 +299,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return Whether or not inserting the MIDI events was succesful. */ -- (BOOL)insertMIDIEvents:(NSSet *)events DEPRECATED_ATTRIBUTE; +- (BOOL)insertMIDIEvents:(MIKSetOf(MIKMIDIEvent *) *)events DEPRECATED_ATTRIBUTE; /** * @deprecated Use -removeEvent: instead. @@ -310,7 +310,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return Whether or not removing the MIDI events was succesful. */ -- (BOOL)removeMIDIEvents:(NSSet *)events DEPRECATED_ATTRIBUTE; +- (BOOL)removeMIDIEvents:(MIKSetOf(MIKMIDIEvent *) *)events DEPRECATED_ATTRIBUTE; /** * @deprecated Use -removeAllEvents instead. diff --git a/Source/NSUIApplication+MIKMIDI.h b/Source/NSUIApplication+MIKMIDI.h index 58bf9513..77186491 100644 --- a/Source/NSUIApplication+MIKMIDI.h +++ b/Source/NSUIApplication+MIKMIDI.h @@ -119,7 +119,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An NSSet containing objects that conform to the MIKMIDIResponder protocol. */ -- (NSSet *)allMIDIResponders; +- (MIKSetOf(id) *)allMIDIResponders; // Properties From 7b25b18051529ac337c58cd404d146291ef58279 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Nov 2015 18:21:52 -0700 Subject: [PATCH 254/284] Issue #39 & #108: Fixed up some nullability and generics annotations. --- Source/MIKMIDINoteEvent.h | 6 +++--- Source/MIKMIDISynthesizerInstrument.h | 2 +- Source/MIKMIDITrack.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 000c785b..0af94bc8 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -122,9 +122,9 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMIDICommand (MIKMIDINoteEventToCommands) -+ (MIKArrayOf(MIKMIDICommand *) *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; -+ (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; -+ (MIKMIDINoteOffCommand *)noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock; ++ (MIKArrayOf(MIKMIDICommand *) *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(nullable MIKMIDIClock *)clock; ++ (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(nullable MIKMIDIClock *)clock; ++ (MIKMIDINoteOffCommand *)noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(nullable MIKMIDIClock *)clock; @end diff --git a/Source/MIKMIDISynthesizerInstrument.h b/Source/MIKMIDISynthesizerInstrument.h index 2b19e158..97fb1c53 100644 --- a/Source/MIKMIDISynthesizerInstrument.h +++ b/Source/MIKMIDISynthesizerInstrument.h @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return A MIKMIDISynthesizerInstrument instance with the matching instrument ID, or nil if no instrument was found. */ -+ (nullable instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID name:(NSString *)name; ++ (nullable instancetype)instrumentWithID:(MusicDeviceInstrumentID)instrumentID name:(nullable NSString *)name; /** * The human readable name of the receiver. e.g. "Piano 1". diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 592add22..19a8b5b1 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -80,7 +80,7 @@ NS_ASSUME_NONNULL_BEGIN * * @return An array of specified class of MIDI events. */ -- (MIKArrayOf(MIKMIDIEvent *) *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; +- (MIKArrayOfKindOf(MIKMIDIEvent *) *)eventsOfClass:(Class)eventClass fromTimeStamp:(MusicTimeStamp)startTimeStamp toTimeStamp:(MusicTimeStamp)endTimeStamp; /** * Gets all of the MIDI notes in the track starting from startTimeStamp and ending at endTimeStamp inclusively. From 396fc0a02e19f882cd3fbfeb188d9a6f2b41c727 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Nov 2015 18:23:22 -0700 Subject: [PATCH 255/284] Issue #39: Another minor nullability fix. --- Source/MIKMIDIEvent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index fdfbf59d..df1048c7 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -215,7 +215,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMIDICommand (MIKMIDIEventToCommands) -+ (MIKArrayOf(MIKMIDICommand *) *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(MIKMIDIClock *)clock; ++ (MIKArrayOf(MIKMIDICommand *) *)commandsFromMIDIEvent:(MIKMIDIEvent *)event clock:(nullable MIKMIDIClock *)clock; @end From 7031b7a389fff0da04a0bfd60694a41e4a1ab5f4 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Tue, 10 Nov 2015 18:28:04 -0700 Subject: [PATCH 256/284] Fixed build errors on iOS. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 2 ++ .../xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme | 2 +- .../MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI.xcscheme | 2 +- Source/MIKMIDIMappingXMLParser.m | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index b78977c8..bf93a246 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ 9D0895F11B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D198C171BF2D0FA00839CD7 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */; }; @@ -994,6 +995,7 @@ 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9DAF8B6E1A7B00A700F46528 /* MIKMIDIEventIterator.h in Headers */, 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, + 9D198C171BF2D0FA00839CD7 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */, 9DAF8B571A7B007300F46528 /* MIKMIDIEntity.h in Headers */, 9DAF8B621A7B008A00F46528 /* MIKMIDIControlChangeCommand.h in Headers */, 9D07CAC81BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */, diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme index dc322756..37c00af9 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 11 Nov 2015 14:11:30 -0700 Subject: [PATCH 257/284] Issue #106: Added mechanism to deal with 'stuck' note on commands upon disconnection to MIKMIDIConnectionManager. --- Source/MIKMIDIConnectionManager.h | 27 ++++++++-- Source/MIKMIDIConnectionManager.m | 86 +++++++++++++++++++++++++++---- 2 files changed, 99 insertions(+), 14 deletions(-) diff --git a/Source/MIKMIDIConnectionManager.h b/Source/MIKMIDIConnectionManager.h index af68d6f2..8775a78a 100644 --- a/Source/MIKMIDIConnectionManager.h +++ b/Source/MIKMIDIConnectionManager.h @@ -11,13 +11,14 @@ #import "MIKMIDISourceEndpoint.h" @class MIKMIDIDevice; +@class MIKMIDINoteOnCommand; @protocol MIKMIDIConnectionManagerDelegate; NS_ASSUME_NONNULL_BEGIN /** - * MIKMIDIConnectionManager can be used to manage a set of connected devices. It can be configured to automatically + * MIKMIDIConnectionManager can be used to manage a set of connected devices. It can be configured to automatically * connect to devices as they are added, and disconnect from them as they are removed. It also supports saving * the list of connected to NSUserDefaults and restoring them upon relaunch. * @@ -62,7 +63,7 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)connectToDevice:(MIKMIDIDevice *)device error:(NSError **)error; /** - * Disconnect from a connected device. No further messages from the specified device will be processed. + * Disconnect from a connected device. No further messages from the specified device will be processed. * * Note that you don't need to call this method when a previously-connected device was removed from the system. * Disconnection in that situation is handled automatically. @@ -96,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN * Load and reconnect to the devices previously saved to disk by a call to -saveConfiguration. For * this to work, the receiver's name must be the same as it was upon the previous call to -saveConfiguration. * - * @note: This method will only connect to new devices. It will not disconnect from devices not found in the + * @note: This method will only connect to new devices. It will not disconnect from devices not found in the * saved configuration. */ - (void)loadConfiguration; @@ -108,7 +109,7 @@ NS_ASSUME_NONNULL_BEGIN /** * An MIKMIDIEventHandlerBlock to be called with incoming MIDI messages from any connected device. - * + * * If you need to determine which device sent the passed in messages, call source.entity.device on the * passed in MIKMIDISourceEndpoint argument. */ @@ -157,7 +158,7 @@ NS_ASSUME_NONNULL_BEGIN /** - * Specifies behavior for connecting to a newly connected device. See + * Specifies behavior for connecting to a newly connected device. See * -connectionManager:shouldConnectToNewlyAddedDevice: */ typedef NS_ENUM(NSInteger, MIKMIDIAutoConnectBehavior) { @@ -195,6 +196,22 @@ typedef NS_ENUM(NSInteger, MIKMIDIAutoConnectBehavior) { */ - (MIKMIDIAutoConnectBehavior)connectionManager:(MIKMIDIConnectionManager *)manager shouldConnectToNewlyAddedDevice:(MIKMIDIDevice *)device; +/** + * A connection manager's delegate can implement this method to be notified when a connected device is disconnected, + * either because -disconnectFromDevice: was called, or because the device was unplugged. + * + * If a MIDI device is disconnected between sending a note on message and sending the corresponding note off command, + * this can cause a "stuck note" because the note off command will now never be delivered. e.g. a MIDI piano keyboard + * that is disconnected with a key held down. This method includes an array of these unterminated note on commands (if any) + * so that the receiver can appropriately deal with this situation. For example, corresponding note off commands could + * be generated and sent through whatever processing chain is processing incoming MIDI commands to terminate stuck notes. + * + * @param manager An instance of MIKMIDIConnectionManager. + * @param device The MIKMIDIDevice that was disconnected. + * @param commands An array of note on messages for which corresponding note off messages have not yet been received. + */ +- (void)connectionManager:(MIKMIDIConnectionManager *)manager deviceWasDisconnected:(MIKMIDIDevice *)device withUnterminatedNoteOnCommands:(MIKArrayOf(MIKMIDINoteOnCommand *) *)commands; + @end NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 34e131d6..af213385 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -9,20 +9,26 @@ #import "MIKMIDIConnectionManager.h" #import "MIKMIDIDeviceManager.h" #import "MIKMIDIDevice.h" +#import "MIKMIDIEntity.h" +#import "MIKMIDINoteOnCommand.h" +#import "MIKMIDINoteOffCommand.h" void *MIKMIDIConnectionManagerKVOContext = &MIKMIDIConnectionManagerKVOContext; NSString * const MIKMIDIConnectionManagerConnectedDevicesKey = @"MIKMIDIConnectionManagerConnectedDevicesKey"; NSString * const MIKMIDIConnectionManagerUnconnectedDevicesKey = @"MIKMIDIConnectionManagerUnconnectedDevicesKey"; +BOOL MIKMIDINoteOffCommandCorrespondsWithNoteOnCommand(MIKMIDINoteOffCommand *noteOff, MIKMIDINoteOnCommand *noteOn); + @interface MIKMIDIConnectionManager () @property (nonatomic, strong, readwrite) MIKArrayOf(MIKMIDIDevice *) *availableDevices; @property (nonatomic, strong, readonly) MIKMutableSetOf(MIKMIDIDevice *) *internalConnectedDevices; -@property (nonatomic, strong, readonly) MIKMIDIEventHandlerBlock internalEventHandler; @property (nonatomic, strong, readonly) MIKMapTableOf(MIKMIDIDevice *, id) *connectionTokensByDevice; +@property (nonatomic, strong) MIKMapTableOf(MIKMIDIDevice *, NSMutableArray *) *pendingNoteOnsByDevice; + @property (nonatomic, readonly) MIKMIDIDeviceManager *deviceManager; @end @@ -48,12 +54,8 @@ - (instancetype)initWithName:(NSString *)name delegate:(id Date: Wed, 11 Nov 2015 14:34:07 -0700 Subject: [PATCH 258/284] Issue #106: Explicitly unconnected devices in saved configuration are disconnected upon calling -[MIKMIDIConfigurationManager loadConfiguration]. --- Source/MIKMIDIConnectionManager.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index af213385..111c5220 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -150,7 +150,9 @@ - (void)saveConfiguration - (void)loadConfiguration { for (MIKMIDIDevice *device in self.availableDevices) { - if ([self deviceIsConnectedInSavedConfiguration:device]) { + if ([self deviceIsUnconnectedInSavedConfiguration:device]) { + [self internalDisconnectFromDevice:device]; + } else if ([self deviceIsConnectedInSavedConfiguration:device]) { NSError *error = nil; if (![self internalConnectToDevice:device error:&error]) { NSLog(@"Unable to connect to MIDI device %@: %@", device, error); From 65a8342ba14c050604dd81bfb438f94562b58bb4 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 11 Nov 2015 15:40:43 -0700 Subject: [PATCH 259/284] Updated README. --- README.md | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e112fe7f..2b3c905b 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ This README file is meant to give a broad overview of MIKMIDI. More complete doc MIKMIDI ------- -MIKMIDI is an easy-to-use Objective-C MIDI library created by Andrew Madsen and developed by the team at [Mixed In Key](http://www.mixedinkey.com/). It's a Cocoa-like set of Objective-C classes useful for programmers writing Objective-C or Swift OS X or iOS apps that use MIDI. It includes the ability to communicate with external MIDI devices, to read and write MIDI files, to record and play back MIDI, etc. MIKMIDI is used to provide MIDI functionality in the OS X version of our DJ app, [Flow](http://flowdjsoftware.com), as well as in our flagship app [Mixed In Key](http://www.mixedinkey.com/). +MIKMIDI is an easy-to-use Objective-C MIDI library created by Andrew Madsen and developed by him and Chris Flesner of [Mixed In Key](http://www.mixedinkey.com/). It's useful for programmers writing Objective-C or Swift OS X or iOS apps that use MIDI. It includes the ability to communicate with external MIDI devices, to read and write MIDI files, to record and play back MIDI, etc. MIKMIDI is used to provide MIDI functionality in the OS X version of our DJ app, [Flow](http://flowdjsoftware.com), as well as in our flagship app [Mixed In Key](http://www.mixedinkey.com/). -MIKMIDI can be used in projects targeting Mac OS X 10.7 and later, and iOS 6 and later. +MIKMIDI can be used in projects targeting Mac OS X 10.7 and later, and iOS 6 and later. The example code in this readme is in Objective-C. However, MIKMIDI can also easily be used from Swift code. MIKMIDI is released under an MIT license, meaning you're free to use it in both closed and open source projects. However, even in a closed source project, you must include a publicly-accessible copy of MIKMIDI's copyright notice, which you can find in the LICENSE file. @@ -20,23 +20,6 @@ MIKMIDI relies on CoreMIDI.framework, as well as on libxml2 (on iOS). Make sure On OS X, you can also use MIKMIDI.framework instead of including the MIKMIDI source in your project. To do so, open MIKMIDI.xcodeproj in the 'Framework' folder, build then copy the resultant MIKMIDI.framework into your project. In your project's settings, select your application's target, then click on the "Build Phases" tab. Expand the "Link Binary With Libraries" section, then click the "+" button in the lower left corner to add a new Framework. In the list that appears, find and select MIKMIDI.framework. -Important Note: MIKMIDI relies on Automatic Reference Counting (ARC). If you'd like to use its source in a non-ARC project, you'll need to open the "Compile Sources" build phase for the target(s) you're using it in, and add the -fobjc-arc flag to the "Compiler Flags" column for all MIKMIDI implementation (.m) files. MIKMIDI will generate a compiler error if ARC is not enabled. - -### Install as Embedded Framework on iOS 8 - -- Add MIKMIDI as a [submodule](http://git-scm.com/docs/git-submodule) by opening the Terminal, `cd`-ing into your top-level project directory, and entering the following command: - -```bash -$ git submodule add https://github.com/mixedinkey-opensource/MIKMIDI.git -``` - -- Open the `MIKMIDI/Framework` folder, and drag `MIKMIDI.xcodeproj` into the file navigator of your app project. -- In Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar. -- Ensure that the deployment target of MIKMIDI.framework matches that of the application target. -- In the tab bar at the top of that window, open the "Build Phases" panel. -- Expand the "Target Dependencies" group, and add `MIKMIDI.framework`. -- Click on the `+` button at the top left of the panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add `MIKMIDI.framework`. - MIKMIDI Overview ---------------- @@ -77,6 +60,15 @@ MIKMIDI's device support architecture is based on the underlying CoreMIDI archit `MIKMIDIDeviceManager` is used to sign up to receive messages from MIDI endpoints as well as to send them. To receive messages from a `MIKMIDISourceEndpoint`, you must connect the endpoint and supply an event handler block to be called anytime messages are received. This is done using the `-connectInput:error:eventHandler:` method. When you no longer want to receive messages, you must call the `-disconnectInput:` method. To send MIDI messages to an `MIKMIDIDestinationEndpoint`, call `-[MIKMIDIDeviceManager sendCommands:toEndpoint:error:]` passing an `NSArray` of `MIKMIDICommand` instances. For example: +```objective-c +NSDate *date = [NSDate date]; +MIKMIDINoteOnCommand *noteOn = [MIKMIDINoteOnCommand noteOnCommandWithNote:60 velocity:127 channel:0 timestamp:date]; +MIKMIDINoteOffCommand *noteOff = [MIKMIDINoteOffCommand noteOffCommandWithNote:60 velocity:0 channel:0 timestamp:[date dateByAddingTimeInterval:0.5]]; + +MIKMIDIDeviceManager *dm = [MIKMIDIDeviceManager sharedDeviceManager]; +[dm sendCommands:@[noteOn, noteOff] toEndpoint:destinationEndpoint error:&error]; +``` + If you've used CoreMIDI before, you may be familiar with `MIDIClientRef` and `MIDIPortRef`. These are used internally by MIKMIDI, but the "public" API for MIKMIDI does not expose them -- or their Objective-C counterparts -- directly. Rather, `MIKMIDIDeviceManager` itself allows sending and receiving messages to/from `MIKMIDIEndpoint`s. MIDI Messages @@ -107,9 +99,20 @@ MIKMIDI includes features to make it easy to read and write MIDI files. This sup MIDI Synthesis -------------- -MIDI synthesis is the process by which MIDI events/messages are turned into audio that you can hear. This is accomplished using `MIKMIDIEndpointSynthesizer`. +MIDI synthesis is the process by which MIDI events/messages are turned into audio that you can hear. This is accomplished using `MIKMIDISynthesizer`. Also included is a subclass of `MIKMIDISynthesizer`, `MIKMIDIEndpointSynthesizer` which can very easily be hooked up to a MIDI enpoint to synthesize incoming MIDI messages: + +```objective-c +MIKMIDISourceEndpoint *endpoint = midiDevice.entities.firstObject.sources.firstObject; +MIKMIDISynthesizer *synth = [[MIKMIDIEndpointSynthesizer alloc] initWithMIDISource:midiDevice.endpoint]; +``` MIDI Sequencing --------------- -`MIKMIDISequencer` can be used to play and record to an `MIKMIDISequence`. It includes a number of high level features useful when implementing MIDI recording and playback. +`MIKMIDISequencer` can be used to play and record to an `MIKMIDISequence`. It includes a number of high level features useful when implementing MIDI recording and playback. However, at the very simplest, MIKMIDISequencer can be used to load a MIDI file and play it like so: + +```objective-c +MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:midiFileURL error:&error]; +MIKMIDISequencer *sequencer = [MIKMIDISequencer sequencerWithSequence:sequence]; +[sequencer startPlayback]; +``` From 9632111affdb1a6f4e142282d5e8a21ea2327b43 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 11 Nov 2015 16:04:52 -0700 Subject: [PATCH 260/284] Updated MIDI Files Testbed. --- .../MIDI Files Testbed.xcodeproj/project.pbxproj | 5 ++++- .../Resources/MIDI Files Testbed-Info.plist | 2 +- Examples/MIDI Files Testbed/Source/MIKMainWindowController.m | 4 +--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj index 93a37cc1..4a509d0a 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -236,7 +236,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = MIK; - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0710; ORGANIZATIONNAME = "Mixed In Key"; }; buildConfigurationList = 9DB2A5EE192D184D0047A3EB /* Build configuration list for PBXProject "MIDI Files Testbed" */; @@ -385,6 +385,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -445,6 +446,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/MIDI Files Testbed-Prefix.pch"; INFOPLIST_FILE = "Resources/MIDI Files Testbed-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; @@ -458,6 +460,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Source/MIDI Files Testbed-Prefix.pch"; INFOPLIST_FILE = "Resources/MIDI Files Testbed-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.mixedinkey.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; diff --git a/Examples/MIDI Files Testbed/Resources/MIDI Files Testbed-Info.plist b/Examples/MIDI Files Testbed/Resources/MIDI Files Testbed-Info.plist index bdb66fa8..0a931845 100644 --- a/Examples/MIDI Files Testbed/Resources/MIDI Files Testbed-Info.plist +++ b/Examples/MIDI Files Testbed/Resources/MIDI Files Testbed-Info.plist @@ -9,7 +9,7 @@ CFBundleIconFile CFBundleIdentifier - com.mixedinkey.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m index 8f4202d6..91c35a8c 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -126,9 +126,7 @@ - (void)disconnectFromDevice { if (!self.deviceConnectionToken) return; - MIKMIDISourceEndpoint *source = [[[self.device.entities firstObject] sources] firstObject]; - if (!source) return; - [[MIKMIDIDeviceManager sharedDeviceManager] disconnectInput:source forConnectionToken:self.deviceConnectionToken]; + [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionforToken:self.deviceConnectionToken]; self.deviceConnectionToken = nil; } From 92235ea8c5c72391db6d73944aaf2d81ba1b78ae Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Wed, 11 Nov 2015 16:35:53 -0700 Subject: [PATCH 261/284] Greatly improved MIDI Files Testbed's MIKMMIDISequenceView drawing performance. --- .../Source/MIKMIDISequenceView.m | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m index 40e98410..7c87be1a 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m +++ b/Examples/MIDI Files Testbed/Source/MIKMIDISequenceView.m @@ -15,17 +15,19 @@ @interface MIKMIDISequenceView () @property (nonatomic) BOOL dragInProgress; +@property (nonatomic, strong, readonly) NSArray *noteColors; + @end @implementation MIKMIDISequenceView - (instancetype)initWithFrame:(NSRect)frame { - self = [super initWithFrame:frame]; - if (self) { - [self registerForDraggedTypes:@[NSFilenamesPboardType]]; - } - return self; + self = [super initWithFrame:frame]; + if (self) { + [self registerForDraggedTypes:@[NSFilenamesPboardType]]; + } + return self; } - (void)dealloc @@ -53,20 +55,18 @@ - (void)drawRect:(NSRect)dirtyRect CGFloat ppt = [self pixelsPerTick]; CGFloat noteHeight = [self pixelsPerNote]; NSInteger index=0; - for (MIKMIDITrack *track in self.sequence.tracks) { + NSArray *tracks = self.sequence.tracks; + for (MIKMIDITrack *track in tracks) { for (MIKMIDINoteEvent *note in [track notes]) { - NSColor *noteColor = [self.sequence.tracks count] < 2 ? [self colorForNote:note] : [self colorForTrackAtIndex:index]; - - [[NSColor blackColor] setStroke]; - [noteColor setFill]; - CGFloat yPosition = NSMinY([self bounds]) + note.note * [self pixelsPerNote]; NSRect noteRect = NSMakeRect(NSMinX([self bounds]) + note.timeStamp * ppt, yPosition, note.duration * ppt, noteHeight); - - NSBezierPath *path = [NSBezierPath bezierPathWithRect:noteRect]; - [path fill]; - [path stroke]; + + [[NSColor blackColor] set]; + NSRectFill(noteRect); + NSColor *noteColor = [tracks count] < 2 ? [self colorForNote:note] : [self colorForTrackAtIndex:index]; + [noteColor set]; + NSRectFill(NSInsetRect(noteRect, 1.0, 1.0)); } index++; } @@ -118,17 +118,14 @@ - (void)concludeDragOperation:(id)sender - (NSColor *)colorForNote:(MIKMIDINoteEvent *)note { - NSArray *colors = @[[NSColor redColor], [NSColor orangeColor], [NSColor yellowColor], [NSColor greenColor], [NSColor blueColor], [NSColor purpleColor]]; - NSGradient *gradient = [[NSGradient alloc] initWithColors:colors]; - CGFloat notePosition = (CGFloat)(note.note % 12) / 12.0; - return [gradient interpolatedColorAtLocation:notePosition]; + NSInteger notePosition = (note.note % 12); + return self.noteColors[notePosition]; } - (NSColor *)colorForTrackAtIndex:(NSInteger)index { - NSArray *colors = @[[NSColor redColor], [NSColor orangeColor], [NSColor yellowColor], [NSColor greenColor], [NSColor blueColor], [NSColor purpleColor]]; - NSGradient *gradient = [[NSGradient alloc] initWithColors:colors]; - return [gradient interpolatedColorAtLocation:index / (float)[self.sequence.tracks count]]; + NSInteger indexIntoColors = index % 12; + return self.noteColors[indexIntoColors]; } - (CGFloat)pixelsPerTick @@ -208,4 +205,24 @@ - (void)setDragInProgress:(BOOL)dragInProgress } } +@synthesize noteColors = _noteColors; +- (NSArray *)noteColors +{ + if (!_noteColors) { + _noteColors = @[[NSColor colorWithCalibratedRed:1.0 green:0.0 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:1.0 green:0.227 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:1.0 green:0.454 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:1.0 green:0.681 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:1.0 green:0.909 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:0.727 green:1.0 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:0.272 green:1.0 blue:0.0 alpha:1.0], + [NSColor colorWithCalibratedRed:0.0 green:0.818 blue:0.181 alpha:1.0], + [NSColor colorWithCalibratedRed:0.0 green:0.363 blue:0.636 alpha:1.0], + [NSColor colorWithCalibratedRed:0.045 green:0.0 blue:0.954 alpha:1.0], + [NSColor colorWithCalibratedRed:0.272 green:0.0 blue:0.727 alpha:1.0], + [NSColor colorWithCalibratedRed:0.5 green:0.0 blue:0.5 alpha:1.0]]; + } + return _noteColors; +} + @end From 4ba9b545085a410b442ddabc73186c221a950546 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 13:44:12 -0700 Subject: [PATCH 262/284] Issue #106: Minor documentation improvement to -[MIKMIDIConnectionManager initWithName:delegate:eventHandler:]. --- Source/MIKMIDIConnectionManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDIConnectionManager.h b/Source/MIKMIDIConnectionManager.h index 8775a78a..00866058 100644 --- a/Source/MIKMIDIConnectionManager.h +++ b/Source/MIKMIDIConnectionManager.h @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN * store and load the connection manager's configuration using NSUserDefaults. The passed in name * should be unique across your application, and the same from launch to launch. * - * @param name The name to give the connection manager. + * @param name The name to give the connection manager. Must not be nil or empty. * @param delegate The delegate of the connection manager * @param eventHandler An MIKMIDIEventHandlerBlock to be called with incoming MIDI messages from any connected device. * From 05914de73342fdf81321addfc2e17055de965498 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 14:28:09 -0700 Subject: [PATCH 263/284] Issue #114: MIKMIDIMappingManager mapping name customization is now done using a delegate. --- Framework/MIKMIDI.xcodeproj/project.pbxproj | 6 --- Framework/module.modulemap | 5 -- Source/MIKMIDIMappingManager.h | 44 +++++++++++++++++ Source/MIKMIDIMappingManager.m | 18 +++++-- .../MIKMIDIMappingManager_SubclassMethods.h | 47 ------------------- 5 files changed, 59 insertions(+), 61 deletions(-) delete mode 100644 Source/MIKMIDIMappingManager_SubclassMethods.h diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index bf93a246..53772c2a 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -54,7 +54,6 @@ 83BC19BC1A23CD0D004F384F /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */; }; 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83C3716819D607010017186B /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */; }; - 83C850C91B7AA47C001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 83FB360E1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */; }; 9D07CAC71BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D07CAC81BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -64,7 +63,6 @@ 9D0895F11B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9D198C171BF2D0FA00839CD7 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */; }; @@ -351,7 +349,6 @@ 83BC19BA1A23CD0D004F384F /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; 83C3716519D607010017186B /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; 83C3716619D607010017186B /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; - 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingManager_SubclassMethods.h; sourceTree = ""; }; 83FB360C1B42D58000F91DCD /* MIKMIDISequence+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequence+MIKMIDIPrivate.h"; sourceTree = ""; }; 9D07CAC61BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICompilerCompatibility.h; sourceTree = ""; }; 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; @@ -795,7 +792,6 @@ 9D74EF4817A713A100BEE89F /* MIKMIDIMappingGenerator.h */, 9D74EF4917A713A100BEE89F /* MIKMIDIMappingGenerator.m */, 9D74EF4A17A713A100BEE89F /* MIKMIDIMappingManager.h */, - 83C850C81B7AA452001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h */, 9D74EF4B17A713A100BEE89F /* MIKMIDIMappingManager.m */, ); name = Mapping; @@ -914,7 +910,6 @@ 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, 8308F6321B46C482004307AD /* MIKMIDICommandScheduler.h in Headers */, - 83C850C91B7AA47C001D71B0 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */, 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, @@ -995,7 +990,6 @@ 9DEF1CA91AA6769E00E10273 /* MIKMIDIChannelEvent.h in Headers */, 9DAF8B6E1A7B00A700F46528 /* MIKMIDIEventIterator.h in Headers */, 9DB366F11A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, - 9D198C171BF2D0FA00839CD7 /* MIKMIDIMappingManager_SubclassMethods.h in Headers */, 9DAF8B571A7B007300F46528 /* MIKMIDIEntity.h in Headers */, 9DAF8B621A7B008A00F46528 /* MIKMIDIControlChangeCommand.h in Headers */, 9D07CAC81BEA70E200C4ABB0 /* MIKMIDICompilerCompatibility.h in Headers */, diff --git a/Framework/module.modulemap b/Framework/module.modulemap index 8437bfbe..0dd65af2 100644 --- a/Framework/module.modulemap +++ b/Framework/module.modulemap @@ -21,9 +21,4 @@ framework module MIKMIDI { header "MIKMIDISynthesizer_SubclassMethods.h" export * } - - explicit module MIKMIDIMappingManager { - header "MIKMIDIMappingManager_SubclassMethods.h" - export * - } } diff --git a/Source/MIKMIDIMappingManager.h b/Source/MIKMIDIMappingManager.h index 1d3877fc..4a658fae 100644 --- a/Source/MIKMIDIMappingManager.h +++ b/Source/MIKMIDIMappingManager.h @@ -13,6 +13,8 @@ @class MIKMIDIMapping; +@protocol MIKMIDIMappingManagerDelegate; + NS_ASSUME_NONNULL_BEGIN /** @@ -110,6 +112,14 @@ NS_ASSUME_NONNULL_BEGIN // Properties +/** + * The delegate of the MIKMIDIMappingManager. Can be used to customize mapping file naming, etc. + * See the MIKMIDIMappingManagerDelegate protocol for details. + * + * @see MIKMIDIMappingManagerDelegate + */ +@property (nonatomic, weak) id delegate; + /** * MIDI mappings loaded from the application's bundle. These are built in mapping, shipped * with the application. @@ -162,4 +172,38 @@ NS_ASSUME_NONNULL_BEGIN @end +@protocol MIKMIDIMappingManagerDelegate + +/** + * Used to determine the file name for a user mapping. This file name does *not* include the + * file extension, which will be added by the caller. + * + * If this method is not implemented, or returns nil, the mapping's name itself will be used. + * + * @param manager The MIKMIDIMappingManager asking for the name. + * @param mapping The mapping a file name is needed for. + * + * @return A file name for the mapping. + */ +- (nullable NSString *)mappingManager:(MIKMIDIMappingManager *)manager fileNameForMapping:(MIKMIDIMapping *)mapping; + +/** + * When deleting user mappings, this method is called as a way to provide any additional + * file names that the mapping may have had in past versions of -fileNameForMapping: + * + * If you have changed the naming scheme that -fileNameForMapping: uses in any user-reaching + * code, you will probably want to implement this method as well, so users will be able to + * properly delete mappings with the old naming scheme. + * + * Just as with -fileNameForMapping:, the file names should *not* include the file extension. + * + * @param manager The MIKMIDIMappingManager asking for legacy names. + * @param mapping The mapping to return legacy file names for. + * + * @return An array of legacy file names, or nil. + */ +- (nullable MIKArrayOf(NSString *) *)mappingManager:(MIKMIDIMappingManager *)manager legacyFileNamesForUserMapping:(MIKMIDIMapping *)mapping; + +@end + NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDIMappingManager.m b/Source/MIKMIDIMappingManager.m index 60a4e52a..f112b20a 100644 --- a/Source/MIKMIDIMappingManager.m +++ b/Source/MIKMIDIMappingManager.m @@ -15,7 +15,6 @@ #import "MIKMIDIMappingManager.h" #import "MIKMIDIMapping.h" #import "MIKMIDIErrors.h" -#import "MIKMIDIMappingManager_SubclassMethods.h" #if !__has_feature(objc_arc) #error MIKMIDIMappingManager.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDIMappingManager.m in the Build Phases for this target @@ -289,8 +288,21 @@ - (NSURL *)fileURLWithBaseFilename:(NSString *)baseFileName return [[self userMappingsFolder] URLByAppendingPathComponent:filename]; } -- (NSString *)fileNameForMapping:(MIKMIDIMapping *)mapping { return mapping.name; } -- (NSArray *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping { return nil; } +- (NSString *)fileNameForMapping:(MIKMIDIMapping *)mapping +{ + NSString *result = nil; + if ([self.delegate respondsToSelector:@selector(mappingManager:fileNameForMapping:)]) { + result = [self.delegate mappingManager:self fileNameForMapping:mapping]; + } + return [result length] ? result : mapping.name; +} + +- (NSArray *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping +{ + if (![self.delegate respondsToSelector:@selector(mappingManager:legacyFileNamesForUserMapping:)]) return nil; + + return [self.delegate mappingManager:self legacyFileNamesForUserMapping:mapping]; +} #pragma mark - Properties diff --git a/Source/MIKMIDIMappingManager_SubclassMethods.h b/Source/MIKMIDIMappingManager_SubclassMethods.h deleted file mode 100644 index 618c548a..00000000 --- a/Source/MIKMIDIMappingManager_SubclassMethods.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// MIKMIDIMappingManager_SubclassMethods.h -// MIKMIDI -// -// Created by Chris Flesner on 8/11/15. -// Copyright (c) 2015 Mixed In Key. All rights reserved. -// - - -#import "MIKMIDIMappingManager.h" -#import "MIKMIDICompilerCompatibility.h" - -@class MIKMIDIMapping; - -NS_ASSUME_NONNULL_BEGIN - -@interface MIKMIDIMappingManager () - -/** - * Used to determine the file name for a user mapping. This file name does *not* include the - * file extension, which will be added by the caller. - * - * @param mapping The mapping a file name is needed for. - * - * @return A file name for the mapping. - */ -- (NSString *)fileNameForMapping:(MIKMIDIMapping *)mapping; - -/** - * When deleting user mappings, this method is called as a way to provide any additional - * file names that the mapping may have had in past versions of -fileNameForMapping: - * - * If you have changed the naming scheme that -fileNameForMapping: uses in any user-reaching - * code, you will probably want to implement this method as well, so users will be able to - * properly delete mappings with the old naming scheme. - * - * Just as with -fileNameForMapping:, the file names should *not* include the file extension. - * - * @param mapping The mapping to return legacy file names for. - * - * @return An array of legacy file names, or nil. - */ -- (nullable MIKArrayOf(NSString *) *)legacyFileNamesForUserMappingsObject:(MIKMIDIMapping *)mapping; - -@end - -NS_ASSUME_NONNULL_END \ No newline at end of file From 54792ccb906049cfaabab80f988e704b960ab051 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 15:22:26 -0700 Subject: [PATCH 264/284] Issue #65: Added MIKMIDIPolyphonicKeyPressureCommand and associated tests. --- Framework/MIKMIDI Tests/MIKMIDICommandTests.m | 45 +++++++++++++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 16 +++++ Source/MIKMIDI.h | 1 + Source/MIKMIDIChannelVoiceCommand.m | 3 +- Source/MIKMIDIPolyphonicKeyPressureCommand.h | 41 ++++++++++++ Source/MIKMIDIPolyphonicKeyPressureCommand.m | 67 +++++++++++++++++++ 6 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 Framework/MIKMIDI Tests/MIKMIDICommandTests.m create mode 100644 Source/MIKMIDIPolyphonicKeyPressureCommand.h create mode 100644 Source/MIKMIDIPolyphonicKeyPressureCommand.m diff --git a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m new file mode 100644 index 00000000..0a293054 --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m @@ -0,0 +1,45 @@ +// +// MIKMIDICommandTests.m +// MIKMIDI +// +// Created by Andrew Madsen on 11/12/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import +#import + +@interface MIKMIDICommandTests : XCTestCase + +@end + +@implementation MIKMIDICommandTests + +- (void)testPolyphonicKeyPressureCommand +{ + Class immutableClass = [MIKMIDIPolyphonicKeyPressureCommand class]; + Class mutableClass = [MIKMutableMIDIPolyphonicKeyPressureCommand class]; + + MIKMIDIPolyphonicKeyPressureCommand *command = [[immutableClass alloc] init]; + XCTAssert([command isMemberOfClass:[immutableClass class]], @"[[MIKMIDIPolyphonicKeyPressureCommand alloc] init] did not return an MIKMIDIPolyphonicKeyPressureCommand instance."); + XCTAssert([[MIKMIDICommand commandForCommandType:MIKMIDICommandTypePolyphonicKeyPressure] isMemberOfClass:[immutableClass class]], @"[MIKMIDICommand commandForCommandType:MIKMIDICommandTypePolyphonicKeyPressure] did not return an MIKMIDIPolyphonicKeyPressureCommand instance."); + XCTAssert([[command copy] isMemberOfClass:[immutableClass class]], @"[MIKMIDIPolyphonicKeyPressureCommand copy] did not return an MIKMIDIPolyphonicKeyPressureCommand instance."); + XCTAssertEqual(command.commandType, MIKMIDICommandTypePolyphonicKeyPressure, @"[[MIKMIDIPolyphonicKeyPressureCommand alloc] init] produced a command instance with the wrong command type."); + + MIKMutableMIDIPolyphonicKeyPressureCommand *mutableCommand = [command mutableCopy]; + XCTAssert([mutableCommand isMemberOfClass:[mutableClass class]], @"-[MIKMIDIPolyphonicKeyPressureCommand mutableCopy] did not return an mutableClass instance."); + XCTAssert([[mutableCommand copy] isMemberOfClass:[immutableClass class]], @"-[mutableClass mutableCopy] did not return an MIKMIDIPolyphonicKeyPressureCommand instance."); + + XCTAssertThrows([(MIKMutableMIDIPolyphonicKeyPressureCommand *)command setNote:64], @"-[MIKMIDIPolyphonicKeyPressureCommand setNote:] was allowed on immutable instance."); + XCTAssertThrows([(MIKMutableMIDIPolyphonicKeyPressureCommand *)command setPressure:64], @"-[MIKMIDIPolyphonicKeyPressureCommand setPressure:] was allowed on immutable instance."); + + XCTAssertNoThrow([mutableCommand setNote:64], @"-[MIKMIDIPolyphonicKeyPressureCommand setNote:] was not allowed on mutable instance."); + XCTAssertNoThrow([mutableCommand setPressure:64], @"-[MIKMIDIPolyphonicKeyPressureCommand setNote:] was not allowed on mutable instance."); + + mutableCommand.note = 42; + XCTAssertEqual(mutableCommand.note, 42, @"Setting the note on a MIKMutableMIDIPolyphonicKeyPressureCommand instance failed."); + mutableCommand.pressure = 27; + XCTAssertEqual(mutableCommand.pressure, 27, @"Setting the pressure on a MIKMutableMIDIPolyphonicKeyPressureCommand instance failed."); +} + +@end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 53772c2a..c528c071 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -63,6 +63,11 @@ 9D0895F11B0D29F200A5872E /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */; }; 9D0895F31B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D0895F41B0D2A4700A5872E /* MIKMIDIMappableResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1D9C201BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1D9C211BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1D9C221BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; + 9D1D9C231BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; + 9D1D9C251BF542BB001377F7 /* MIKMIDICommandTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */; }; @@ -354,6 +359,9 @@ 9D0895EC1B0D29F200A5872E /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; 9D0895ED1B0D29F200A5872E /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; 9D0895F21B0D2A4700A5872E /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; + 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureCommand.h; sourceTree = ""; }; + 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureCommand.m; sourceTree = ""; }; + 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandTests.m; sourceTree = ""; }; 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -517,6 +525,7 @@ isa = PBXGroup; children = ( 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */, + 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */, 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */, 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */, 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */, @@ -810,6 +819,8 @@ 9D74EF3717A713A100BEE89F /* MIKMIDIControlChangeCommand.m */, 9DED4E341AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h */, 9DED4E351AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m */, + 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */, + 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */, 9D877DF91A670261001BA997 /* MIKMIDIProgramChangeCommand.h */, 9D877DFA1A670261001BA997 /* MIKMIDIProgramChangeCommand.m */, 9D74EF4E17A713A100BEE89F /* MIKMIDINoteOnCommand.h */, @@ -917,6 +928,7 @@ 9DF99E791831841A004EE5F4 /* MIKMIDICommandThrottler.h in Headers */, 9D74EF9017A713A100BEE89F /* MIKMIDISystemMessageCommand.h in Headers */, 83BC19BB1A23CD0D004F384F /* MIKMIDIMetronome.h in Headers */, + 9D1D9C201BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */, 9D74EF9217A713A100BEE89F /* MIKMIDIUtilities.h in Headers */, 83C3716719D607010017186B /* MIKMIDIClientDestinationEndpoint.h in Headers */, 9DAE7D8E19357AAF00B25DD7 /* MIKMIDIEndpointSynthesizer.h in Headers */, @@ -984,6 +996,7 @@ 9DAF8B791A7B00A700F46528 /* MIKMIDIMetaTrackSequenceNameEvent.h in Headers */, 9DAF8B6F1A7B00A700F46528 /* MIKMIDIMetaCopyrightEvent.h in Headers */, 9DAF8B531A7B005C00F46528 /* MIKMIDIErrors.h in Headers */, + 9D1D9C211BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h in Headers */, 9D8495231AA7773500C52475 /* MIKMIDIPolyphonicKeyPressureEvent.h in Headers */, 9DAF8B7F1A7B00B100F46528 /* MIKMIDISequencer.h in Headers */, 9DBEBD681AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, @@ -1130,6 +1143,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9D1D9C251BF542BB001377F7 /* MIKMIDICommandTests.m in Sources */, 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */, 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */, 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */, @@ -1188,6 +1202,7 @@ 9D74EF8717A713A100BEE89F /* MIKMIDIOutputPort.m in Sources */, 839D935219C3A2F5007589C3 /* MIKMIDIMetaCuePointEvent.m in Sources */, 9D74EF8917A713A100BEE89F /* MIKMIDIPort.m in Sources */, + 9D1D9C221BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */, 839D933619C3A2C9007589C3 /* MIKMIDIEventIterator.m in Sources */, 839D935A19C3A2F5007589C3 /* MIKMIDIMetaLyricEvent.m in Sources */, 9DB366F81A964D4A001D1CF3 /* MIKMIDISynthesizerInstrument.m in Sources */, @@ -1256,6 +1271,7 @@ 9DAF8B441A7AFF6B00F46528 /* MIKMIDIMetaTextEvent.m in Sources */, 9DBEBD6A1AAA303700E59734 /* MIKMIDIChannelPressureEvent.m in Sources */, 9DAF8B451A7AFF6B00F46528 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */, + 9D1D9C231BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */, 9DAF8B461A7AFF6B00F46528 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, 9DAF8B471A7AFF6B00F46528 /* MIKMIDINoteEvent.m in Sources */, 9DAF8B481A7AFF6B00F46528 /* MIKMIDIPlayer.m in Sources */, diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 5a77cefc..37d7238e 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -37,6 +37,7 @@ #import "MIKMIDIPitchBendChangeCommand.h" #import "MIKMIDINoteOnCommand.h" #import "MIKMIDINoteOffCommand.h" +#import "MIKMIDIPolyphonicKeyPressureCommand.h" #import "MIKMIDISystemExclusiveCommand.h" #import "MIKMIDISystemMessageCommand.h" diff --git a/Source/MIKMIDIChannelVoiceCommand.m b/Source/MIKMIDIChannelVoiceCommand.m index b37fdcc1..055fabb2 100644 --- a/Source/MIKMIDIChannelVoiceCommand.m +++ b/Source/MIKMIDIChannelVoiceCommand.m @@ -27,8 +27,7 @@ @implementation MIKMIDIChannelVoiceCommand + (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } + (NSArray *)supportedMIDICommandTypes { - return @[@(MIKMIDICommandTypePolyphonicKeyPressure), - @(MIKMIDICommandTypeChannelPressure)]; + return @[@(MIKMIDICommandTypeChannelPressure)]; } + (Class)immutableCounterpartClass; { return [MIKMIDIChannelVoiceCommand class]; } diff --git a/Source/MIKMIDIPolyphonicKeyPressureCommand.h b/Source/MIKMIDIPolyphonicKeyPressureCommand.h new file mode 100644 index 00000000..8e27ce53 --- /dev/null +++ b/Source/MIKMIDIPolyphonicKeyPressureCommand.h @@ -0,0 +1,41 @@ +// +// MIKMIDIPolyphonicKeyPressureCommand.h +// MIKMIDI +// +// Created by Andrew Madsen on 11/12/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import + +/** + * A MIDI polyphonic key pressure message. This message is most often sent by pressing + * down on the key after it "bottoms out". + */ +@interface MIKMIDIPolyphonicKeyPressureCommand : MIKMIDIChannelVoiceCommand + +/// The note number for the message. In the range 0-127. +@property (nonatomic, readonly) NSUInteger note; + +/// Key pressure of the polyphonic key pressure message. In the range 0-127. +@property (nonatomic, readonly) NSUInteger pressure; + +@end + +/** + * The mutable counterpart to MIKMIDIPolyphonicKeyPressureCommand. + */ +@interface MIKMutableMIDIPolyphonicKeyPressureCommand : MIKMIDIPolyphonicKeyPressureCommand + +/// The note number for the message. In the range 0-127. +@property (nonatomic, readwrite) NSUInteger note; + +/// Key pressure of the polyphonic key pressure message. In the range 0-127. +@property (nonatomic, readwrite) NSUInteger pressure; + +@property (nonatomic, strong, readwrite) NSDate *timestamp; +@property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) NSUInteger value; + +@end diff --git a/Source/MIKMIDIPolyphonicKeyPressureCommand.m b/Source/MIKMIDIPolyphonicKeyPressureCommand.m new file mode 100644 index 00000000..619233bd --- /dev/null +++ b/Source/MIKMIDIPolyphonicKeyPressureCommand.m @@ -0,0 +1,67 @@ +// +// MIKMIDIPolyphonicKeyPressureCommand.m +// MIKMIDI +// +// Created by Andrew Madsen on 11/12/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIPolyphonicKeyPressureCommand.h" +#import "MIKMIDIChannelVoiceCommand_SubclassMethods.h" + +@interface MIKMIDIPolyphonicKeyPressureCommand () + +@property (nonatomic, readwrite) NSUInteger note; +@property (nonatomic, readwrite) NSUInteger pressure; + +@end + +@implementation MIKMIDIPolyphonicKeyPressureCommand + ++ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } ++ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypePolyphonicKeyPressure)]; } + ++ (Class)immutableCounterpartClass; { return [MIKMIDIPolyphonicKeyPressureCommand class]; } ++ (Class)mutableCounterpartClass; { return [MIKMutableMIDIPolyphonicKeyPressureCommand class]; } + ++ (BOOL)isMutable { return NO; } + +#pragma mark - Properties + +- (NSUInteger)note { return self.dataByte1; } +- (void)setNote:(NSUInteger)value +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.dataByte1 = (UInt8)value; +} + +- (NSUInteger)pressure { return self.value; } +- (void)setPressure:(NSUInteger)value +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.value = value; +} + +@end + +#pragma mark - + +@implementation MIKMutableMIDIPolyphonicKeyPressureCommand + ++ (BOOL)isMutable { return YES; } + +#pragma mark - Properties + +@dynamic note; +@dynamic pressure; + +// MIKMIDICommand already implements these. This keeps the compiler happy. +@dynamic channel; +@dynamic value; +@dynamic timestamp; +@dynamic dataByte1; +@dynamic dataByte2; +@dynamic midiTimestamp; +@dynamic data; + +@end From 2798e82c6da093863b69ac3630153d2faaa728d1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 15:22:36 -0700 Subject: [PATCH 265/284] Very minor updates to MIKMIDINoteOnCommand. --- Source/MIKMIDINoteOnCommand.h | 8 ++++---- Source/MIKMIDINoteOnCommand.m | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDINoteOnCommand.h b/Source/MIKMIDINoteOnCommand.h index 2ef9aae1..a5352886 100644 --- a/Source/MIKMIDINoteOnCommand.h +++ b/Source/MIKMIDINoteOnCommand.h @@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSUInteger note; /** - * Velocity of the note off message. In the range 0-127. + * Velocity of the note on message. In the range 0-127. */ @property (nonatomic, readonly) NSUInteger velocity; @@ -48,14 +48,14 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMutableMIDINoteOnCommand : MIKMIDINoteOnCommand +@property (nonatomic, readwrite) NSUInteger note; +@property (nonatomic, readwrite) NSUInteger velocity; + @property (nonatomic, strong, readwrite) NSDate *timestamp; @property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; @property (nonatomic, readwrite) UInt8 channel; @property (nonatomic, readwrite) NSUInteger value; -@property (nonatomic, readwrite) NSUInteger note; -@property (nonatomic, readwrite) NSUInteger velocity; - @end NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Source/MIKMIDINoteOnCommand.m b/Source/MIKMIDINoteOnCommand.m index 690a0a9c..288cd374 100644 --- a/Source/MIKMIDINoteOnCommand.m +++ b/Source/MIKMIDINoteOnCommand.m @@ -9,7 +9,6 @@ #import "MIKMIDINoteOnCommand.h" #import "MIKMIDIChannelVoiceCommand_SubclassMethods.h" #import "MIKMIDIUtilities.h" -#import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) #error MIKMIDINoteOnCommand.m must be compiled with ARC. Either turn on ARC for the project or set the -fobjc-arc flag for MIKMIDINoteOnCommand.m in the Build Phases for this target From c5200a47ad87de76c571969504b477adcae358a0 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 16:07:36 -0700 Subject: [PATCH 266/284] Issue #65: Added MIKMIDIChannelPressureCommand and associated tests. --- Framework/MIKMIDI Tests/MIKMIDICommandTests.m | 23 ++++++++ Framework/MIKMIDI.xcodeproj/project.pbxproj | 12 ++++ Source/MIKMIDI.h | 1 + Source/MIKMIDIChannelPressureCommand.h | 34 +++++++++++ Source/MIKMIDIChannelPressureCommand.m | 58 +++++++++++++++++++ Source/MIKMIDIChannelVoiceCommand.m | 5 +- 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 Source/MIKMIDIChannelPressureCommand.h create mode 100644 Source/MIKMIDIChannelPressureCommand.m diff --git a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m index 0a293054..ff4e3afc 100644 --- a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m @@ -42,4 +42,27 @@ - (void)testPolyphonicKeyPressureCommand XCTAssertEqual(mutableCommand.pressure, 27, @"Setting the pressure on a MIKMutableMIDIPolyphonicKeyPressureCommand instance failed."); } +- (void)testChannelPressureCommand +{ + Class immutableClass = [MIKMIDIChannelPressureCommand class]; + Class mutableClass = [MIKMutableMIDIChannelPressureCommand class]; + + MIKMIDIChannelPressureCommand *command = [[immutableClass alloc] init]; + XCTAssert([command isMemberOfClass:[immutableClass class]], @"[[MIKMIDIChannelPressureCommand alloc] init] did not return an MIKMIDIChannelPressureCommand instance."); + XCTAssert([[MIKMIDICommand commandForCommandType:MIKMIDICommandTypeChannelPressure] isMemberOfClass:[immutableClass class]], @"[MIKMIDICommand commandForCommandType:MIKMIDICommandTypePolyphonicKeyPressure] did not return an MIKMIDIChannelPressureCommand instance."); + XCTAssert([[command copy] isMemberOfClass:[immutableClass class]], @"[MIKMIDIChannelPressureCommand copy] did not return an MIKMIDIChannelPressureCommand instance."); + XCTAssertEqual(command.commandType, MIKMIDICommandTypeChannelPressure, @"[[MIKMIDIChannelPressureCommand alloc] init] produced a command instance with the wrong command type."); + + MIKMutableMIDIChannelPressureCommand *mutableCommand = [command mutableCopy]; + XCTAssert([mutableCommand isMemberOfClass:[mutableClass class]], @"-[MIKMIDIChannelPressureCommand mutableCopy] did not return an mutableClass instance."); + XCTAssert([[mutableCommand copy] isMemberOfClass:[immutableClass class]], @"-[mutableClass mutableCopy] did not return an MIKMIDIChannelPressureCommand instance."); + + XCTAssertThrows([(MIKMutableMIDIChannelPressureCommand *)command setPressure:64], @"-[MIKMIDIChannelPressureCommand setPressure:] was allowed on immutable instance."); + + XCTAssertNoThrow([mutableCommand setPressure:64], @"-[MIKMIDIChannelPressureCommand setNote:] was not allowed on mutable instance."); + + mutableCommand.pressure = 27; + XCTAssertEqual(mutableCommand.pressure, 27, @"Setting the pressure on a MIKMutableMIDIChannelPressureCommand instance failed."); +} + @end diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index c528c071..96cc9628 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -68,6 +68,10 @@ 9D1D9C221BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; 9D1D9C231BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; 9D1D9C251BF542BB001377F7 /* MIKMIDICommandTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */; }; + 9D1D9C281BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1D9C291BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9D1D9C2A1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */; }; + 9D1D9C2B1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */; }; 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */; }; 9D3781561AA407A7007A61BE /* MIKMIDIResponder.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D74EF5817A713A100BEE89F /* MIKMIDIResponder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D4DF13E1AAB57430065F004 /* MIKMIDI_Tests.m */; }; @@ -362,6 +366,8 @@ 9D1D9C1E1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureCommand.h; sourceTree = ""; }; 9D1D9C1F1BF53C88001377F7 /* MIKMIDIPolyphonicKeyPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureCommand.m; sourceTree = ""; }; 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandTests.m; sourceTree = ""; }; + 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureCommand.h; sourceTree = ""; }; + 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureCommand.m; sourceTree = ""; }; 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIResponderChainTests.m; sourceTree = ""; }; 9D4DF13A1AAB57430065F004 /* MIKMIDI Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MIKMIDI Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D4DF13D1AAB57430065F004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -815,6 +821,8 @@ 9D74EF3117A713A100BEE89F /* MIKMIDIChannelVoiceCommand.h */, 9D8BC91018071C00007E143C /* MIKMIDIChannelVoiceCommand_SubclassMethods.h */, 9D74EF3217A713A100BEE89F /* MIKMIDIChannelVoiceCommand.m */, + 9D1D9C261BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h */, + 9D1D9C271BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m */, 9D74EF3617A713A100BEE89F /* MIKMIDIControlChangeCommand.h */, 9D74EF3717A713A100BEE89F /* MIKMIDIControlChangeCommand.m */, 9DED4E341AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.h */, @@ -921,6 +929,7 @@ 9DBEBD671AAA303700E59734 /* MIKMIDIChannelPressureEvent.h in Headers */, 833B73DA1A262FE100E0CC9F /* MIKMIDISequencer.h in Headers */, 8308F6321B46C482004307AD /* MIKMIDICommandScheduler.h in Headers */, + 9D1D9C281BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h in Headers */, 9DB366F01A964C55001D1CF3 /* MIKMIDISynthesizer.h in Headers */, 833B73DE1A26346F00E0CC9F /* MIKMIDIClock.h in Headers */, 9D76DCEC1A9E52DB00A24C16 /* MIKMIDITrack_Protected.h in Headers */, @@ -987,6 +996,7 @@ 9DAF8B7C1A7B00A700F46528 /* MIKMIDITempoEvent.h in Headers */, 9DAF8B541A7B007300F46528 /* MIKMIDIDeviceManager.h in Headers */, 9DAF8B7E1A7B00B100F46528 /* MIKMIDIMetronome.h in Headers */, + 9D1D9C291BF54D38001377F7 /* MIKMIDIChannelPressureCommand.h in Headers */, 9DAF8B831A7B00BB00F46528 /* MIKMIDIPrivateUtilities.h in Headers */, 9DAF8B591A7B007300F46528 /* MIKMIDIInputPort.h in Headers */, 9D9FBCCC1B4A29A5009A7936 /* MIKMIDIPort_SubclassMethods.h in Headers */, @@ -1212,6 +1222,7 @@ 9DB366F21A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */, 9DED4E381AA90CA700DA8356 /* MIKMIDIPitchBendChangeCommand.m in Sources */, 9D74EF8F17A713A100BEE89F /* MIKMIDISystemExclusiveCommand.m in Sources */, + 9D1D9C2A1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */, 9D74EF9117A713A100BEE89F /* MIKMIDISystemMessageCommand.m in Sources */, 839D935419C3A2F5007589C3 /* MIKMIDIMetaEvent.m in Sources */, 9D7027D31ACC9D7A009AFAED /* MIKMIDIMacDebugQuickLookSupport.m in Sources */, @@ -1281,6 +1292,7 @@ 9DAF8B4B1A7AFF7500F46528 /* MIKMIDIMetronome.m in Sources */, 9DAF8B4C1A7AFF7500F46528 /* MIKMIDISequencer.m in Sources */, 9DB366F31A964C55001D1CF3 /* MIKMIDISynthesizer.m in Sources */, + 9D1D9C2B1BF54D38001377F7 /* MIKMIDIChannelPressureCommand.m in Sources */, 9DAF8B4D1A7AFF7500F46528 /* MIKMIDIUtilities.m in Sources */, 9DAF8B4E1A7AFF7500F46528 /* MIKMIDICommandThrottler.m in Sources */, 9DAF8B4F1A7AFF7500F46528 /* MIKMIDIClock.m in Sources */, diff --git a/Source/MIKMIDI.h b/Source/MIKMIDI.h index 37d7238e..e41c0602 100644 --- a/Source/MIKMIDI.h +++ b/Source/MIKMIDI.h @@ -32,6 +32,7 @@ // MIDI Commands/Messages #import "MIKMIDICommand.h" #import "MIKMIDIChannelVoiceCommand.h" +#import "MIKMIDIChannelPressureCommand.h" #import "MIKMIDIControlChangeCommand.h" #import "MIKMIDIProgramChangeCommand.h" #import "MIKMIDIPitchBendChangeCommand.h" diff --git a/Source/MIKMIDIChannelPressureCommand.h b/Source/MIKMIDIChannelPressureCommand.h new file mode 100644 index 00000000..a469ec6b --- /dev/null +++ b/Source/MIKMIDIChannelPressureCommand.h @@ -0,0 +1,34 @@ +// +// MIKMIDIChannelPressureCommand.h +// MIKMIDI +// +// Created by Andrew Madsen on 11/12/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import + +/** + * A MIDI channel pressure message. This message is most often sent by pressing + * down on the key after it "bottoms out". This differs from a MIKMIDIPolyphonicKeyPressureCommand + * in that is the single greatest pressure of all currently depressed keys, hence the lack + * of a note property. + */ +@interface MIKMIDIChannelPressureCommand : MIKMIDIChannelVoiceCommand + +/// Key pressure of the channel pressure message. In the range 0-127. +@property (nonatomic, readonly) NSUInteger pressure; + +@end + +@interface MIKMutableMIDIChannelPressureCommand : MIKMIDIChannelPressureCommand + +/// Key pressure of the channel pressure message. In the range 0-127. +@property (nonatomic, readwrite) NSUInteger pressure; + +@property (nonatomic, strong, readwrite) NSDate *timestamp; +@property (nonatomic, readwrite) MIDITimeStamp midiTimestamp; +@property (nonatomic, readwrite) UInt8 channel; +@property (nonatomic, readwrite) NSUInteger value; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIChannelPressureCommand.m b/Source/MIKMIDIChannelPressureCommand.m new file mode 100644 index 00000000..667648a2 --- /dev/null +++ b/Source/MIKMIDIChannelPressureCommand.m @@ -0,0 +1,58 @@ +// +// MIKMIDIChannelPressureCommand.m +// MIKMIDI +// +// Created by Andrew Madsen on 11/12/15. +// Copyright © 2015 Mixed In Key. All rights reserved. +// + +#import "MIKMIDIChannelPressureCommand.h" +#import "MIKMIDIChannelVoiceCommand_SubclassMethods.h" + +@interface MIKMIDIChannelPressureCommand () + +@property (nonatomic, readwrite) NSUInteger pressure; + +@end + +@implementation MIKMIDIChannelPressureCommand + ++ (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } ++ (NSArray *)supportedMIDICommandTypes { return @[@(MIKMIDICommandTypeChannelPressure)]; } + ++ (Class)immutableCounterpartClass; { return [MIKMIDIChannelPressureCommand class]; } ++ (Class)mutableCounterpartClass; { return [MIKMutableMIDIChannelPressureCommand class]; } + ++ (BOOL)isMutable { return NO; } + +#pragma mark - Properties + +- (NSUInteger)pressure { return self.dataByte1; } +- (void)setPressure:(NSUInteger)value +{ + if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; + self.dataByte1 = value; +} + +@end + +#pragma mark - + +@implementation MIKMutableMIDIChannelPressureCommand + ++ (BOOL)isMutable { return YES; } + +#pragma mark - Properties + +@dynamic pressure; + +// MIKMIDICommand already implements these. This keeps the compiler happy. +@dynamic channel; +@dynamic value; +@dynamic timestamp; +@dynamic dataByte1; +@dynamic dataByte2; +@dynamic midiTimestamp; +@dynamic data; + +@end \ No newline at end of file diff --git a/Source/MIKMIDIChannelVoiceCommand.m b/Source/MIKMIDIChannelVoiceCommand.m index 055fabb2..631415a7 100644 --- a/Source/MIKMIDIChannelVoiceCommand.m +++ b/Source/MIKMIDIChannelVoiceCommand.m @@ -25,10 +25,7 @@ @interface MIKMIDIChannelVoiceCommand () @implementation MIKMIDIChannelVoiceCommand + (void)load { [super load]; [MIKMIDICommand registerSubclass:self]; } -+ (NSArray *)supportedMIDICommandTypes -{ - return @[@(MIKMIDICommandTypeChannelPressure)]; -} ++ (NSArray *)supportedMIDICommandTypes { return @[]; } + (Class)immutableCounterpartClass; { return [MIKMIDIChannelVoiceCommand class]; } + (Class)mutableCounterpartClass; { return [MIKMutableMIDIChannelVoiceCommand class]; } From 0786abac93323bc85c0749bd84de2e31908b113a Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 16:15:55 -0700 Subject: [PATCH 267/284] Issue #116: Added .travis.yml. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..e808fc2e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: objective-c +xcode_project: "Framework/MIKMIDI.xcodeproj" +xcode_scheme: ["MIKMIDI", "MIKMIDI-iOS"] +osx_image: xcode7.1 From ba46f16fc461db2e3ec9a1e93266bc8d9214b8a3 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 16:22:06 -0700 Subject: [PATCH 268/284] Issue #116: Turned off code signing for Travis builds (hopefully). --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index e808fc2e..8a1dd490 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,6 @@ language: objective-c xcode_project: "Framework/MIKMIDI.xcodeproj" xcode_scheme: ["MIKMIDI", "MIKMIDI-iOS"] osx_image: xcode7.1 + +script: + - xcodebuild [DEFAULT_OPTIONS] CODE_SIGNING_REQUIRED=NO \ No newline at end of file From 7321a52f30976a333d48fc82abefb776b111b651 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 16:32:39 -0700 Subject: [PATCH 269/284] Issue #116: Another shot at making iOS builds on Travis work by building with the simulator SDK. --- .travis.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8a1dd490..ac14f915 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,19 @@ language: objective-c xcode_project: "Framework/MIKMIDI.xcodeproj" -xcode_scheme: ["MIKMIDI", "MIKMIDI-iOS"] + osx_image: xcode7.1 -script: - - xcodebuild [DEFAULT_OPTIONS] CODE_SIGNING_REQUIRED=NO \ No newline at end of file +xcode_scheme: + - MIKMIDI + - MIKMIDI-iOS +xcode_sdk: + macosx + iphonesimulator + +matrix: + exclude: + - xcode_scheme: MIKMIDI + xcode_sdk: iphonesimulator + - xcode_scheme: MIKMIDI-iOS + xcode_sdk: macosx + \ No newline at end of file From 2020e2811e6f9894d1dbde58c2712529db56f250 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 18:45:28 -0700 Subject: [PATCH 270/284] Added preliminary CHANGELOG (doesn't yet have changes for 1.1. --- CHANGELOG.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..28a2a6ac --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,60 @@ +# Change Log +All notable changes to MIKMIDI are documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). + +##[Unreleased] +This section is for changes commited to the MIKMIDI repository, but not yet included in an official release. + +##[1.0.1] - 2015-04-20 + +###ADDED +- Support for [Carthage](https://github.com/Carthage/Carthage) +- Better error handling for `MIKMIDIClientSource/DestinationEndpoint`, particularly on iOS. +- `MIKMIDISequence` initializer methods that include an error parameter. + +###CHANGED +- Improved documentation. + +###FIXED +- `MIKMIDIMetronome` on iOS (8). +- `MIKMIDICommand`'s channel now defaults to 0 as it should. + +###DEPRECATED +- `-[MIKMIDISequence initWithData:]`. Use `-[MIKMIDISequence initWithData:error:]`, instead. +- `+[MIKMIDISequence sequenceWithData:]`. Use `+[MIKMIDISequence sequenceWithData:error:]`, instead. +- `-[MIKMIDISequence/MIKMIDITrack setDestinationEndpoint:]`. Use API on MIKMIDISequencer instead. + +##[1.0.0] - 2015-01-29 +###ADDED +- MIDI Files Testbed OS X example app +- Added `MIKMIDISequence`, `MIKMIDITrack`, `MIKMIDIEvent`, etc. to support loading, creating, saving MIDI files +- API on `MIKMIDIManager` to allow obtaining only bundled or user mappings +- `MIKMIDIPlayer` for playing MIDI files +- Preliminary (experimental/incomplete) implementation of `MIKMIDISequencer` for both playback and recording +- `MIKMIDISynthesizer` and associated instrument selection API for MIDI synthesis +- API (`MIKMIDIClientSource/DestinationEndpoint`) for creating virtual MIDI endpoints +- iOS framework target. + +###CHANGED + +- Improved README. + +###FIXED +- Fixed bug where sending a large number of MIDI messages at a time could fail. +- `MIKMIDIMapping` save/load is now supported on iOS. +- Warnings when building for iOS. + +##[0.9.2] - 2014-06-13 +###ADDED +- Added `MIKMIDIEndpointSynthesizer` for synthesizing incoming MIDI (OS X only for now). +- Added Cocoapods podspec file to repository. + +###FIXED +- `MIKMIDIInputPort` can parse multiple MIDI messages out of a single packet. + +##[0.9.1] - 2014-05-24 +###FIXED +Minor documentation typo fixes. + +##[0.9.0] - 2014-05-16 +###ADDED +Initial release \ No newline at end of file From 3aa79af8a4654d8f51c247e8a8278a8a9a8f7cf4 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 18:58:30 -0700 Subject: [PATCH 271/284] Issue #116: More .travis.yml changes. --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index ac14f915..b21b7ac7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,13 +7,13 @@ xcode_scheme: - MIKMIDI - MIKMIDI-iOS xcode_sdk: - macosx - iphonesimulator + - macosx + - iphonesimulator matrix: exclude: - xcode_scheme: MIKMIDI - xcode_sdk: iphonesimulator + xcode_sdk: iphonesimulator - xcode_scheme: MIKMIDI-iOS - xcode_sdk: macosx + xcode_sdk: macosx \ No newline at end of file From 3790f6ac738bfe5f1dff9a46b8ff8b4ef485e072 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 19:03:22 -0700 Subject: [PATCH 272/284] Fixed minor NSLog format warnings in MIKMIDISynthesizer when building for iOS. --- Source/MIKMIDISynthesizer.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index c784fcc9..12467aed 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -421,7 +421,7 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef UInt32 sizeOfLPCMASBD = sizeof(LPCMASBD); OSStatus err = AudioUnitGetProperty(instrumentUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &LPCMASBD, &sizeOfLPCMASBD); if (err) { - NSLog(@"Unable to get stream description for instrument unit %p: %i", instrumentUnit, err); + NSLog(@"Unable to get stream description for instrument unit %p: %@", instrumentUnit, @(err)); return err; } @@ -478,7 +478,7 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef OSStatus err = MusicDeviceMIDIEvent(instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, sampleOffset); if (err) { - NSLog(@"Unable to schedule MIDI command %@ for instrument unit %p: %i", command, instrumentUnit, err); + NSLog(@"Unable to schedule MIDI command %@ for instrument unit %p: %@", command, instrumentUnit, @(err)); return err; } } @@ -512,14 +512,14 @@ - (void)setInstrumentUnit:(AudioUnit)instrumentUnit OSStatus err; if (_instrumentUnit) { err = AudioUnitRemoveRenderNotify(_instrumentUnit, MIKMIDISynthesizerInstrumentUnitRenderCallback, (__bridge void *)self); - if (err) NSLog(@"Unable to remove render notify from instrument unit %p: %i", _instrumentUnit, err); + if (err) NSLog(@"Unable to remove render notify from instrument unit %p: %@", _instrumentUnit, @(err)); } _instrumentUnit = instrumentUnit; if (_instrumentUnit) { err = AudioUnitAddRenderNotify(_instrumentUnit, MIKMIDISynthesizerInstrumentUnitRenderCallback, (__bridge void *)self); - if (err) NSLog(@"Unable to add render notify to instrument unit %p: %i", _instrumentUnit, err); + if (err) NSLog(@"Unable to add render notify to instrument unit %p: %@", _instrumentUnit, @(err)); } } } From 0b9b5cc2a90a4ab0054398b0e7689a691fd5426f Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 19:12:32 -0700 Subject: [PATCH 273/284] Fixed use of 10.8+ only API in MIKMIDISequencer. --- Source/MIKMIDISequencer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index a74557dd..695519be 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -406,7 +406,7 @@ - (void)sendAllPendingNoteOffsWithMIDITimeStamp:(MIDITimeStamp)offTimeStamp NSMutableDictionary *noteOffs = self.pendingNoteOffs; if (!noteOffs.count) return; - NSMapTable *noteOffDestinationsToCommands = [NSMapTable strongToStrongObjectsMapTable]; + NSMapTable *noteOffDestinationsToCommands = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory]; MIKMIDIClock *clock = self.clock; for (NSNumber *musicTimeStampNumber in noteOffs) { From e8b69ebbae9a504fed5c978e4ea3f1b34ac0d253 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 19:13:04 -0700 Subject: [PATCH 274/284] Fixed use of 10.8+ only API in MIKMIDIInputPort (introduced in commit f71e3999). --- Source/MIKMIDIInputPort.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIInputPort.m b/Source/MIKMIDIInputPort.m index f0cfab66..36644759 100644 --- a/Source/MIKMIDIInputPort.m +++ b/Source/MIKMIDIInputPort.m @@ -132,7 +132,9 @@ - (NSString *)createNewConnectionToken { NSString *uuidString = nil; do { // Very unlikely, but just to be safe - uuidString = [[NSUUID UUID] UUIDString]; + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + uuidString = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); + CFRelease(uuid); MIKMIDIConnectionTokenAndEventHandler *existingPair = nil; for (NSArray *handlerPairs in self.handlerTokenPairsByEndpoint.objectEnumerator) { for (MIKMIDIConnectionTokenAndEventHandler *pair in handlerPairs) { From 510bb50893fb15077836eb328bc86492384d9697 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Thu, 12 Nov 2015 19:21:04 -0700 Subject: [PATCH 275/284] Made MIKMIDIMappingManager's delegate property unsafe_unretained when deploying to 10.7. --- Source/MIKMIDICompilerCompatibility.h | 10 ++++++++++ Source/MIKMIDIMappingGenerator.h | 8 +------- Source/MIKMIDIMappingManager.h | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Source/MIKMIDICompilerCompatibility.h b/Source/MIKMIDICompilerCompatibility.h index fc081e76..7993b7af 100644 --- a/Source/MIKMIDICompilerCompatibility.h +++ b/Source/MIKMIDICompilerCompatibility.h @@ -47,3 +47,13 @@ #endif #endif // #ifndef MIKArrayOf + +// Weak support + +// On OS X 10.7, many classes (e.g. NSViewController) can't be the target of a weak +// reference, so we use unsafe_unretained there. +#if TARGET_OS_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8) +#define MIKTargetSafeWeak weak +#else +#define MIKTargetSafeWeak unsafe_unretained +#endif \ No newline at end of file diff --git a/Source/MIKMIDIMappingGenerator.h b/Source/MIKMIDIMappingGenerator.h index ab6e561b..27832d22 100644 --- a/Source/MIKMIDIMappingGenerator.h +++ b/Source/MIKMIDIMappingGenerator.h @@ -113,18 +113,12 @@ typedef void(^MIKMIDIMappingGeneratorMappingCompletionBlock)(MIKMIDIMappingItem // Properties -#if !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8) -#define MIKMIDIMappingGeneratorWeakProperty weak -#else -#define MIKMIDIMappingGeneratorWeakProperty unsafe_unretained -#endif - /** * The delegate for the mapping generator. Can be used to customize certain mapping behavior. Optional. * * The delegate must implement the MIKMIDIMappingGeneratorDelegate protocol. */ -@property (nonatomic, MIKMIDIMappingGeneratorWeakProperty) id delegate; +@property (nonatomic, MIKTargetSafeWeak) id delegate; /** * The device for which a mapping is being generated. Must not be nil for mapping to work. diff --git a/Source/MIKMIDIMappingManager.h b/Source/MIKMIDIMappingManager.h index 4a658fae..9b3dcd86 100644 --- a/Source/MIKMIDIMappingManager.h +++ b/Source/MIKMIDIMappingManager.h @@ -118,7 +118,7 @@ NS_ASSUME_NONNULL_BEGIN * * @see MIKMIDIMappingManagerDelegate */ -@property (nonatomic, weak) id delegate; +@property (nonatomic, MIKTargetSafeWeak) id delegate; /** * MIDI mappings loaded from the application's bundle. These are built in mapping, shipped From ff9d913488ed0b4c8b5275131920221308b25485 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 13 Nov 2015 08:54:06 -0700 Subject: [PATCH 276/284] Minor documentation improvement to -[MIKMIDISequencer setCommandScheduler:forTrack:] --- Source/MIKMIDISequencer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 68ad723f..5b50f085 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -207,7 +207,7 @@ NS_ASSUME_NONNULL_BEGIN * @note If track is not contained by the receiver's sequence, this method does nothing. * * @param commandScheduler An object that conforms to MIKMIDICommandScheduler with which events - * in track should be scheduled during playback. Pass nil to remove an existing command scheduler + * in track should be scheduled during playback. MIKMIDIDestinationEndpoint and MIKMIDISynthesizer both conform to MIKMIDICommandScheduler, so they can be used here. Pass nil to remove an existing command scheduler. * @param track An MIKMIDITrack instance. */ - (void)setCommandScheduler:(nullable id)commandScheduler forTrack:(MIKMIDITrack *)track; From 6e263ff0e8f51e36b315b34804b1fedd0ed19348 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 13 Nov 2015 09:41:20 -0700 Subject: [PATCH 277/284] Better documentation of some deprecated API in MIKMIDITrack. --- Source/MIKMIDITrack.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 19a8b5b1..529acbf0 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -233,6 +233,8 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Deprecated /** + * @deprecated This method only affects playback using MIKMIDIPlayer. Use `-[MIKMIDISequencer isLooping]` instead. + * * Whether the track is set to loop. * * This property can be observed using Key Value Observing. @@ -240,6 +242,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) BOOL doesLoop DEPRECATED_ATTRIBUTE; /** + * @deprecated This method only affects playback using MIKMIDIPlayer. Use `MIKMIDISequencer` looping API instead. + * * The number of times to play the designated portion of the music track. By default, a music track plays once. * * This property can be observed using Key Value Observing. @@ -247,6 +251,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) SInt32 numberOfLoops DEPRECATED_ATTRIBUTE; /** + * @deprecated This method only affects playback using MIKMIDIPlayer. + * Use `-[MIKMIDISequencer setLoopStartTimeStamp:endTimeStamp:]`, and associated properties instead. + * * The point in a MIDI track, measured in beats from the end of the MIDI track, at which to begin playback during looped playback. * That is, during looped playback, a MIDI track plays from (length – loopDuration) to length. * @@ -255,6 +262,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) MusicTimeStamp loopDuration DEPRECATED_ATTRIBUTE; /** + * @deprecated This method only affects playback using MIKMIDIPlayer. Use `MIKMIDISequencer` looping API instead. + * * The loop info for the track. * * This property can be observed using Key Value Observing. @@ -262,13 +271,13 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) MusicTrackLoopInfo loopInfo DEPRECATED_ATTRIBUTE; /** + * @deprecated This method is deprecated. Use -trackNumber instead. + * * Gets the track's track number in it's owning MIDI sequence. * * @param trackNumber On output, the track number of the track. * * @return Whether or not getting the track number was succesful. - * - * @deprecated This method is deprecated. Use -trackNumber instead. */ - (BOOL)getTrackNumber:(UInt32 *)trackNumber DEPRECATED_ATTRIBUTE; From ade49577b405a44bce093cb329a18f043cb1a889 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 13 Nov 2015 09:42:13 -0700 Subject: [PATCH 278/284] Fixed minor typo in -[MIKMIDIDeviceManager disconnectConnectionForToken:] method name. --- Source/MIKMIDIDeviceManager.h | 2 +- Source/MIKMIDIDeviceManager.m | 4 ++-- Source/MIKMIDIEndpointSynthesizer.m | 2 +- Source/MIKMIDIMappingGenerator.m | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/MIKMIDIDeviceManager.h b/Source/MIKMIDIDeviceManager.h index 2627424a..bc75aeb8 100644 --- a/Source/MIKMIDIDeviceManager.h +++ b/Source/MIKMIDIDeviceManager.h @@ -94,7 +94,7 @@ extern NSString * const MIKMIDIEndpointKey; * * @param connectionToken The connection token returned by -connectInput:error:eventHandler: when the input was connected. */ -- (void)disconnectConnectionforToken:(id)connectionToken; +- (void)disconnectConnectionForToken:(id)connectionToken; /** * Used to send MIDI messages/commands from your application to a MIDI output endpoint. diff --git a/Source/MIKMIDIDeviceManager.m b/Source/MIKMIDIDeviceManager.m index 94f26d45..49fede4e 100644 --- a/Source/MIKMIDIDeviceManager.m +++ b/Source/MIKMIDIDeviceManager.m @@ -93,7 +93,7 @@ - (id)connectInput:(MIKMIDISourceEndpoint *)endpoint error:(NSError **)error eve return [self.inputPort connectToSource:endpoint error:error eventHandler:eventHandler]; } -- (void)disconnectConnectionforToken:(id)connectionToken +- (void)disconnectConnectionForToken:(id)connectionToken { [self.inputPort disconnectConnectionForToken:connectionToken]; } @@ -428,7 +428,7 @@ @implementation MIKMIDIDeviceManager (Deprecated) - (void)disconnectInput:(MIKMIDISourceEndpoint *)endpoint forConnectionToken:(id)connectionToken { - [self disconnectConnectionforToken:connectionToken]; + [self disconnectConnectionForToken:connectionToken]; } @end \ No newline at end of file diff --git a/Source/MIKMIDIEndpointSynthesizer.m b/Source/MIKMIDIEndpointSynthesizer.m index 4cbf91c7..bf963d15 100644 --- a/Source/MIKMIDIEndpointSynthesizer.m +++ b/Source/MIKMIDIEndpointSynthesizer.m @@ -94,7 +94,7 @@ - (instancetype)initWithClientDestinationEndpoint:(MIKMIDIClientDestinationEndpo - (void)dealloc { if ([_endpoint isKindOfClass:[MIKMIDISourceEndpoint class]]) { - [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionforToken:self.connectionToken]; + [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionForToken:self.connectionToken]; } // Don't need to do anything for a destination endpoint. __weak reference in the messages handler will automatically nil out. } diff --git a/Source/MIKMIDIMappingGenerator.m b/Source/MIKMIDIMappingGenerator.m index 7763904f..03877575 100644 --- a/Source/MIKMIDIMappingGenerator.m +++ b/Source/MIKMIDIMappingGenerator.m @@ -563,7 +563,7 @@ - (BOOL)connectToDevice:(NSError **)error - (void)disconnectFromDevice { - [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionforToken:self.connectionToken]; + [[MIKMIDIDeviceManager sharedDeviceManager] disconnectConnectionForToken:self.connectionToken]; } #pragma mark - Properties From 14fff61be82867f4836bc4f3f357cd87d09ed4d1 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 13 Nov 2015 09:52:51 -0700 Subject: [PATCH 279/284] Added notes for upcoming major release (1.1) to CHANGELOG. --- CHANGELOG.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28a2a6ac..b3f21860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,55 @@ All notable changes to MIKMIDI are documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ##[Unreleased] -This section is for changes commited to the MIKMIDI repository, but not yet included in an official release. +###ADDED +- `MIKMIDISynthesizer` for general-purpose MIDI synthesis. `MIKMIDIEndpointSynthesizer` is now a subclass of `MIKMIDISynthesizer`. +- `MIKMIDISequencer` now has API for routing tracks to MIDI endpoints, synthesizers, +or other command scheduling objects (`-(setC|c)ommandScheduler:forTrack:`) +- Nullability and Objective-C generics annotations for much nicer usage from Swift. (#39 & #108) +- API for loading soundfont files using `-[MIKMIDISynthesizer loadSoundfontFromFileAtURL:error:]`. (#47) +- Added `MIKMIDIEvent` subclass `MIKMIDIChannelEvent` and related children (`MIKMIDIPitchBendChangeEvent`, `MIKMIDIControlChangeEvent`, etc.). (#63) +- Added `MIKMIDIChannelVoiceCommand` subclasses for remaining MIDI channel voice message types (pitch bend, polyphonic key pressure, channel pressure). (#65) +- API on `MIKMIDISequence` to control whether channels are split into individual tracks or not. (#66) +- Preliminary unit tests (still need a lot more coverage with tests). +- API on `MIKMIDISequencer` to set playback tempo (overrides sequence tempo). (#81) +- Ability to explicitly stop `MIKMIDIMappingGenerator`'s mapping via `-[MIKMIDIMappingGenerator endMapping]`. (#84) +- Looping API on `MIKMIDISequencer` (#85) +- API for syncing `MIKMIDIClock`s (see `-[MIKMIDIClock syncedClock]`). (#86) +- Ability to suspend MIDI mapping generation, then later resume right where it left off (`-[MIKMIDIMappingGenerator suspendMapping/resumeMapping]`). (#102) +- API for customizing mapping file naming. See `MIKMIDIMappingManagerDelegate`. (#114) +- `MIKMIDIConnectionManager` which implements a generic MIDI device connection manager including support for saving/restoring connection configuration to NSUserDefaults, etc. (#106) +- Other minor API additions and improvements. (#87, #89, #90, #93, #94) + +###CHANGED +- `MIKMIDIEndpointSynthesizerInstrument` was renamed to `MIKMIDISynthesizerInstrument`. This **does not** break existing code, due to the use of `@compatibility_alias` +- `MIKMIDISequencer` creates and uses default synthesizers for each track, allowing a minimum of configuration for simple MIDI playback. (#34) +- `MIKMIDISequence` and `MIKMIDITrack` are now KVO compliant for most of their properties. Check documentation for specifics. (#35 & #67) +- `MIKMIDISequencer` can now send MIDI to any object that conforms to the new `MIKMIDICommandScheduler` protocol. Removes the need to use virtual endpoints for internal scheduling. (#36) +- Significantly improved performance of MIDI responder hierarchy search code, including adding (optional) caching. (#82) +- Improved `MIKMIDIDeviceManager` API to simplify device disconnection, in particular. (#109) + +###FIXED +- `MIKMIDIEndpointSynthesizer` had too much reverb by default. (#38) +- `MIKMIDISequencer`'s playback would stall or drop notes when the main thread was busy/blocked. Processing is now done in the background. (#48 & #92) +- `MIKMIDIEvent` (or subclass) instances created with `alloc/init` no longer have a NULL `eventType`. (#59) +- Warnings when using MIKMIDI.framework in a Swift project. (#64) +- Bug that could cause `MIKMIDISequencer` to sometimes skip the first events in its sequence upon starting playback. (#95) +- Occasional crash (in `MIKMIDIEventIterator`) during `MIKMIDISequencer` playback. (#100) +- KVO notifications for `MIKMIDIDeviceManager`'s `availableDevices` property now includ valid `NSKeyValueChangeOld/NewKey` entries and associated values. (#112) +- Exception is no longer thrown when setting "empty" `MIKMutableMIDIMetaTimeSignatureEvent`'s numerator. (#57) +- Other minor bug fixes (#71, #83) + +###DEPRECATED +This release deprecates a number of existing MIKMIDI APIs. These APIs remain available, and functional (from Objective-C apps), but developers should switch to the use of their replacements as soon as possible. + +- `-[MIKMIDITrack getTrackNumber:]`. Use `trackNumber` @property on `MIKMIDITrack` instead. +- `-[MIKMIDISequence getTempo:atTimeStamp:]`. Use `-tempoAtTimeStamp:` instead. +- `-[MIKMIDISequence getTimeSignature:atTimeStamp:]`. Use `-timeSignatureAtTimeStamp:` instead. +- `doesLoop`, `numberOfLoops`, `loopDuration`, and `loopInfo` on `MIKMIDITrack`. These methods affect looping when using `MIKMIDIPlayer`, but not `MIKMIDISequencer`. Use `-[MIKMIDISequencer setLoopStartTimeStamp:endTimeStamp:]` instead. +- `-insertMIDIEvent:`, `-insertMIDIEvents:`, `-removeMIDIEvents:`, and `-clearAllEvents` on MIKMIDITrack. Use `-addEvent:`, `-removeEvent:`, `-removeAllEvents` instead. +- `-[MIKMIDIDeviceManager disconnectInput:forConnectionToken:]`. Use `-disconnectConnectionForToken:` instead. +- `-setMusicTimeStamp:withTempo:atMIDITimeStamp:`, `+secondsPerMIDITimeStamp`, `+midiTimeStampsPerTimeInterval:` on `MIKMIDIClock`. See documentation for replacement API. +- `+[MIKMIDICommand supportsMIDICommandType:]`. Use `+[MIKMIDICommand supportedMIDICommandTypes]` instead. This is only relevant when creating custom subclasses of `MIKMIDICommand`, which most MIKMIDI users do not need to do. (#57) ##[1.0.1] - 2015-04-20 @@ -30,7 +78,7 @@ This section is for changes commited to the MIKMIDI repository, but not yet incl - API on `MIKMIDIManager` to allow obtaining only bundled or user mappings - `MIKMIDIPlayer` for playing MIDI files - Preliminary (experimental/incomplete) implementation of `MIKMIDISequencer` for both playback and recording -- `MIKMIDISynthesizer` and associated instrument selection API for MIDI synthesis +- `MIKMIDIEndpointSynthesizerInstrument` and associated instrument selection API for MIDI synthesis - API (`MIKMIDIClientSource/DestinationEndpoint`) for creating virtual MIDI endpoints - iOS framework target. From 264e10675a2abfc455eadea1dfb85bd5ef68b08c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Fri, 13 Nov 2015 14:01:27 -0700 Subject: [PATCH 280/284] Updated with changes in 1.1. --- Source/MIKMIDIConnectionManager.m | 2 +- Source/MIKMIDIDeviceManager.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 111c5220..4c025787 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -261,7 +261,7 @@ - (void)internalDisconnectFromDevice:(MIKMIDIDevice *)device id token = [self.connectionTokensByDevice objectForKey:device]; if (!token) return; - [self.deviceManager disconnectConnectionforToken:token]; + [self.deviceManager disconnectConnectionForToken:token]; [self.connectionTokensByDevice removeObjectForKey:device]; [self willChangeValueForKey:@"connectedDevices" diff --git a/Source/MIKMIDIDeviceManager.m b/Source/MIKMIDIDeviceManager.m index 0536a3f7..4acde537 100644 --- a/Source/MIKMIDIDeviceManager.m +++ b/Source/MIKMIDIDeviceManager.m @@ -102,7 +102,7 @@ - (nullable id)connectDevice:(MIKMIDIDevice *)device error:(NSError **)error eve for (MIKMIDISourceEndpoint *source in sources) { id token = [self.inputPort connectToSource:source error:error eventHandler:eventHandler]; if (!token) { - for (id token in tokens) { [self disconnectConnectionforToken:token]; } + for (id token in tokens) { [self disconnectConnectionForToken:token]; } return nil; } [tokens addObject:token]; From e0ea4fc62051f6b9ad09a9fe4ef0338581a0c068 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 14 Nov 2015 11:20:18 -0700 Subject: [PATCH 281/284] Issue #106: Added -[MIKMIDIConnectionManager initWithName:] for convenience. --- Source/MIKMIDIConnectionManager.h | 14 +++++++++++++- Source/MIKMIDIConnectionManager.m | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Source/MIKMIDIConnectionManager.h b/Source/MIKMIDIConnectionManager.h index 00866058..de466fb6 100644 --- a/Source/MIKMIDIConnectionManager.h +++ b/Source/MIKMIDIConnectionManager.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMIDIConnectionManager : NSObject /** - * This method will throw an exception if called. Use -initWithName: instead. + * This method will throw an exception if called. Use -initWithName:delegate:eventHandler: instead. * * @return nil */ @@ -50,6 +50,18 @@ NS_ASSUME_NONNULL_BEGIN delegate:(nullable id)delegate eventHandler:(nullable MIKMIDIEventHandlerBlock)eventHandler NS_DESIGNATED_INITIALIZER; +/** + * Initializes an instance of MIKMIDIConnectionManager. The passed in name is used to independently + * store and load the connection manager's configuration using NSUserDefaults. The passed in name + * should be unique across your application, and the same from launch to launch. This is the same + * as calling -initWithName:delegate:eventHandler: with a nil delegate and eventHandler. + * + * @param name The name to give the connection manager. Must not be nil or empty. + * + * @return An initialized MIKMIDIConnectionManager instance. + */ +- (instancetype)initWithName:(NSString *)name; + /** * Connect to the specified device. When MIDI messages are received, the connection manager's event handler * block will be executed. diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 4c025787..2f13fe12 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -74,6 +74,11 @@ - (instancetype)initWithName:(NSString *)name delegate:(id Date: Sat, 14 Nov 2015 11:24:50 -0700 Subject: [PATCH 282/284] Issue #106: MIKMIDIConnectionManager (optionally) saves its configuration on application backgrounding/termination. --- Source/MIKMIDIConnectionManager.m | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/MIKMIDIConnectionManager.m b/Source/MIKMIDIConnectionManager.m index 2f13fe12..6a2dabf5 100644 --- a/Source/MIKMIDIConnectionManager.m +++ b/Source/MIKMIDIConnectionManager.m @@ -13,6 +13,12 @@ #import "MIKMIDINoteOnCommand.h" #import "MIKMIDINoteOffCommand.h" +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + void *MIKMIDIConnectionManagerKVOContext = &MIKMIDIConnectionManagerKVOContext; NSString * const MIKMIDIConnectionManagerConnectedDevicesKey = @"MIKMIDIConnectionManagerConnectedDevicesKey"; @@ -68,6 +74,13 @@ - (instancetype)initWithName:(NSString *)name delegate:(id *)change context:(void *)context From 3315245b563c57d9355bc9a517c198801e2e297c Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 14 Nov 2015 15:50:35 -0700 Subject: [PATCH 283/284] Bumped version in podspec to 1.5.0. --- MIKMIDI.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIKMIDI.podspec b/MIKMIDI.podspec index 1b7139e0..3fdab359 100644 --- a/MIKMIDI.podspec +++ b/MIKMIDI.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'MIKMIDI' - s.version = '1.1.0' + s.version = '1.5.0' s.summary = 'Library useful for programmers writing Objective-C or Swift OS X or iOS apps that use MIDI.' s.description = <<-DESC MIKMIDI is a library intended to simplify implementing Objective-C or Swift apps From 9cc3aaa2a701cfa9f63a3cd230bab2d285c43b35 Mon Sep 17 00:00:00 2001 From: Andrew Madsen Date: Sat, 14 Nov 2015 15:55:40 -0700 Subject: [PATCH 284/284] Minor CHANGELOG update. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3f21860..4740df4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ or other command scheduling objects (`-(setC|c)ommandScheduler:forTrack:`) - Other minor bug fixes (#71, #83) ###DEPRECATED -This release deprecates a number of existing MIKMIDI APIs. These APIs remain available, and functional (from Objective-C apps), but developers should switch to the use of their replacements as soon as possible. +This release deprecates a number of existing MIKMIDI APIs. These APIs remain available, and functional, but developers should switch to the use of their replacements as soon as possible. - `-[MIKMIDITrack getTrackNumber:]`. Use `trackNumber` @property on `MIKMIDITrack` instead. - `-[MIKMIDISequence getTempo:atTimeStamp:]`. Use `-tempoAtTimeStamp:` instead.