diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ed1b9e..2c9bd20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,44 @@ All notable changes to MIKMIDI are documented in this file. This project adheres ##[Unreleased] This section is for recent changes not yet included in an official release. +##[1.6.0] - 2016-06-01 + +### ADDED + +- `MIKMIDISequencer` now respects the offset, muted, and solo properties of `MIKMIDITrack`. (#99) +- Error-returning variant of `-[MIKMIDISequence addTrack:]`, `-addTrackWithError:`. `-addTrack:` is now deprecated. (#134) +- Added `maximumLookAheadInterval` property to `MIKMIDISequencer` (ac8142b) +- Fixed issue where click track events would be added to the beginning of a loop when the status was EnabledOnlyInPreRoll. (2535c5b) +- Convenience initializers for `MIKMIDINoteOn/OffCommand` that take `MIDITimeStamp`s instead of `NSDate`s. (0bf4a00) +- Custom initializers for several `MIKMIDIMetaEvent` subclasses, greatly simplifing their creation. (#150) + +### CHANGED + +- Improved support for subclassing `MIKMIDISynthesizer` and customizing MIDI event scheduling (87b38ea) + +### FIXED + +- Issue where `MIKMIDISequencer` would almost immediately stop recording if its sequence was empty. (#45) +- `MIKMIDIMetronome` allows loading soundfonts. (a50ccdf) +- Issue with stuck notes in `MIKMIDISequencer`'s pre-roll. (07a9304) +- `MIKMIDISequencer` now ignores incoming MIDI in recording mode during the pre-roll. (2312e4d) +- Potentional crash in `MIKMIDISequencer`. (4c0ce02) +- `MIKMIDIGetCurrentTimeStamp()` is now available in Swift (658cb63) +- Scheduling issues in `MIKMIDISynthesizer` (6698fad) +- Updated MIDI Files Testbed to fix deprecation warnings. +- Improved MIDI Soundboard example for iOS (#119, d0ada0a, a35ee94) +- Incorrect length for some `MIKMIDICommand` subclass instances when created with alloc/init. (#125) +- Incorrect MSB calculation for pitch bend commands. (#147, thanks to akmidd) +- Bug in logic for detecting and exposing available virtual sources and destinations (#144, #145, thanks to jrmaxdev) + + +### DEPRECATED +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. + +- `-[MIKMIDISequence addTrack:]`. Use `-addTrackWithError:` instead. (#134) + ##[1.5.0] - 2015-11-14 -###ADDED +### 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:`) @@ -24,7 +60,7 @@ or other command scheduling objects (`-(setC|c)ommandScheduler:forTrack:`) - `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 +### 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) @@ -32,7 +68,7 @@ or other command scheduling objects (`-(setC|c)ommandScheduler:forTrack:`) - 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 +### 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) @@ -43,7 +79,7 @@ or other command scheduling objects (`-(setC|c)ommandScheduler:forTrack:`) - Exception is no longer thrown when setting "empty" `MIKMutableMIDIMetaTimeSignatureEvent`'s numerator. (#57) - Other minor bug fixes (#71, #83) -###DEPRECATED +### DEPRECATED 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. @@ -57,25 +93,25 @@ This release deprecates a number of existing MIKMIDI APIs. These APIs remain ava ##[1.0.1] - 2015-04-20 -###ADDED +### 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 +### CHANGED - Improved documentation. -###FIXED +### FIXED - `MIKMIDIMetronome` on iOS (8). - `MIKMIDICommand`'s channel now defaults to 0 as it should. -###DEPRECATED +### 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 +### 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 @@ -85,27 +121,27 @@ This release deprecates a number of existing MIKMIDI APIs. These APIs remain ava - API (`MIKMIDIClientSource/DestinationEndpoint`) for creating virtual MIDI endpoints - iOS framework target. -###CHANGED +### CHANGED - Improved README. -###FIXED +### 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 - Added `MIKMIDIEndpointSynthesizer` for synthesizing incoming MIDI (OS X only for now). - Added Cocoapods podspec file to repository. -###FIXED +### FIXED - `MIKMIDIInputPort` can parse multiple MIDI messages out of a single packet. ##[0.9.1] - 2014-05-24 -###FIXED +### FIXED Minor documentation typo fixes. ##[0.9.0] - 2014-05-16 -###ADDED +### ADDED Initial release \ No newline at end of file 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 4a509d0a..456445f3 100644 --- a/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj +++ b/Examples/MIDI Files Testbed/MIDI Files Testbed.xcodeproj/project.pbxproj @@ -372,6 +372,7 @@ 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,6 +411,7 @@ 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/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m index 00a0e23b..d9dd6e91 100644 --- a/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m +++ b/Examples/MIDI Files Testbed/Source/MIKMainWindowController.m @@ -67,7 +67,13 @@ - (IBAction)toggleRecording:(id)sender return; } else { if (!self.sequence) self.sequence = [MIKMIDISequence sequence]; - self.sequencer.recordEnabledTracks = [NSSet setWithObject:[self.sequence addTrack]]; + NSError *error = nil; + MIKMIDITrack *newTrack = [self.sequence addTrackWithError:&error]; + if (!newTrack) { + [self presentError:error]; + return; + } + self.sequencer.recordEnabledTracks = [NSSet setWithObject:newTrack]; [self.sequencer startRecording]; } } diff --git a/Examples/MIDI Testbed/README.md b/Examples/MIDI Testbed/README.md new file mode 100644 index 00000000..3c5e8698 --- /dev/null +++ b/Examples/MIDI Testbed/README.md @@ -0,0 +1 @@ +MIDI Testbed is an Objective-C app for OS X that demonstrates the use of MIKMIDI to connect to MIDI devices, as well as to send/receive data to/from them. The application is quite simple. All relevant code is in MIKAppDelegate.m. diff --git a/Examples/MIDI Testbed/Sources/MIKAppDelegate.m b/Examples/MIDI Testbed/Sources/MIKAppDelegate.m index 8dc14eb5..e59044cd 100644 --- a/Examples/MIDI Testbed/Sources/MIKAppDelegate.m +++ b/Examples/MIDI Testbed/Sources/MIKAppDelegate.m @@ -124,4 +124,12 @@ - (IBAction)commandTextFieldDidSelect:(id)sender [self sendSysex:sender]; } +#pragma mark - MIKMIDIConnectionManagerDelegate + +// We only want to connect to the device that the user selects +- (MIKMIDIAutoConnectBehavior)connectionManager:(MIKMIDIConnectionManager *)manager shouldConnectToNewlyAddedDevice:(MIKMIDIDevice *)device +{ + return MIKMIDIAutoConnectBehaviorDoNotConnect; +} + @end diff --git a/Examples/iOS/MIDI Soundboard/MIDI Soundboard.xcodeproj/project.pbxproj b/Examples/iOS/MIDI Soundboard/MIDI Soundboard.xcodeproj/project.pbxproj index 119eefb3..c7516c53 100644 --- a/Examples/iOS/MIDI Soundboard/MIDI Soundboard.xcodeproj/project.pbxproj +++ b/Examples/iOS/MIDI Soundboard/MIDI Soundboard.xcodeproj/project.pbxproj @@ -12,19 +12,10 @@ 9D6BF5E2175EA60E00DF6B15 /* Black.png in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5E1175EA60E00DF6B15 /* Black.png */; }; 9D6BF5E5175EA67B00DF6B15 /* ORSPianoButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D6BF5E4175EA67B00DF6B15 /* ORSPianoButton.m */; }; 9D6BF5E7175EA6F300DF6B15 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D6BF5E6175EA6F300DF6B15 /* AudioToolbox.framework */; }; - 9D6BF5F6175EA9A600DF6B15 /* 0.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5EA175EA9A600DF6B15 /* 0.aiff */; }; - 9D6BF5F7175EA9A600DF6B15 /* 1.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5EB175EA9A600DF6B15 /* 1.aiff */; }; - 9D6BF5F8175EA9A600DF6B15 /* 2.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5EC175EA9A600DF6B15 /* 2.aiff */; }; - 9D6BF5F9175EA9A600DF6B15 /* 3.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5ED175EA9A600DF6B15 /* 3.aiff */; }; - 9D6BF5FA175EA9A600DF6B15 /* 4.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5EE175EA9A600DF6B15 /* 4.aiff */; }; - 9D6BF5FB175EA9A600DF6B15 /* 5.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5EF175EA9A600DF6B15 /* 5.aiff */; }; - 9D6BF5FC175EA9A600DF6B15 /* 6.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5F0175EA9A600DF6B15 /* 6.aiff */; }; - 9D6BF5FD175EA9A600DF6B15 /* 7.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5F1175EA9A600DF6B15 /* 7.aiff */; }; - 9D6BF5FE175EA9A600DF6B15 /* 8.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5F2175EA9A600DF6B15 /* 8.aiff */; }; - 9D6BF5FF175EA9A600DF6B15 /* 9.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5F3175EA9A600DF6B15 /* 9.aiff */; }; - 9D6BF600175EA9A600DF6B15 /* 10.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5F4175EA9A600DF6B15 /* 10.aiff */; }; - 9D6BF601175EA9A600DF6B15 /* 11.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 9D6BF5F5175EA9A600DF6B15 /* 11.aiff */; }; 9D6BF603175EAFE500DF6B15 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D6BF602175EAFE500DF6B15 /* AVFoundation.framework */; }; + 9D7B3BC81CFF7B5000822CE3 /* MIKMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D7B3BC51CFF7B4600822CE3 /* MIKMIDI.framework */; }; + 9D7B3BC91CFF7B5000822CE3 /* MIKMIDI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9D7B3BC51CFF7B4600822CE3 /* MIKMIDI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9D7B3BCE1CFF7E8D00822CE3 /* Grand Piano.sf2 in Resources */ = {isa = PBXBuildFile; fileRef = 9D7B3BCD1CFF7E8D00822CE3 /* Grand Piano.sf2 */; }; 9D8E68B3175C1A6A006546F6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D8E68B2175C1A6A006546F6 /* UIKit.framework */; }; 9D8E68B5175C1A6A006546F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D8E68B4175C1A6A006546F6 /* Foundation.framework */; }; 9D8E68B7175C1A6A006546F6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D8E68B6175C1A6A006546F6 /* CoreGraphics.framework */; }; @@ -39,74 +30,54 @@ 9D8E6939175C1BD6006546F6 /* ORSAvailableDevicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8E6938175C1BD6006546F6 /* ORSAvailableDevicesTableViewController.m */; }; 9D8E693F175C1D2F006546F6 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D8E693E175C1D2F006546F6 /* CoreMIDI.framework */; }; 9D8E6948175EBBC4006546F6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D8E6947175EBBC4006546F6 /* QuartzCore.framework */; }; - 9D9751531C4EAF4100A2F318 /* MIKMIDIChannelEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750C41C4EAF4100A2F318 /* MIKMIDIChannelEvent.m */; }; - 9D9751541C4EAF4100A2F318 /* MIKMIDIChannelPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750C61C4EAF4100A2F318 /* MIKMIDIChannelPressureCommand.m */; }; - 9D9751551C4EAF4100A2F318 /* MIKMIDIChannelPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750C81C4EAF4100A2F318 /* MIKMIDIChannelPressureEvent.m */; }; - 9D9751561C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750CA1C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand.m */; }; - 9D9751571C4EAF4100A2F318 /* MIKMIDIClientDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750CD1C4EAF4100A2F318 /* MIKMIDIClientDestinationEndpoint.m */; }; - 9D9751581C4EAF4100A2F318 /* MIKMIDIClientSourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750CF1C4EAF4100A2F318 /* MIKMIDIClientSourceEndpoint.m */; }; - 9D9751591C4EAF4100A2F318 /* MIKMIDIClock.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750D11C4EAF4100A2F318 /* MIKMIDIClock.m */; }; - 9D97515A1C4EAF4100A2F318 /* MIKMIDICommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750D31C4EAF4100A2F318 /* MIKMIDICommand.m */; }; - 9D97515B1C4EAF4100A2F318 /* MIKMIDICommandThrottler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750D71C4EAF4100A2F318 /* MIKMIDICommandThrottler.m */; }; - 9D97515C1C4EAF4100A2F318 /* MIKMIDIConnectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750DA1C4EAF4100A2F318 /* MIKMIDIConnectionManager.m */; }; - 9D97515D1C4EAF4100A2F318 /* MIKMIDIControlChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750DC1C4EAF4100A2F318 /* MIKMIDIControlChangeCommand.m */; }; - 9D97515E1C4EAF4100A2F318 /* MIKMIDIControlChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750DE1C4EAF4100A2F318 /* MIKMIDIControlChangeEvent.m */; }; - 9D97515F1C4EAF4100A2F318 /* MIKMIDIDestinationEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750E01C4EAF4100A2F318 /* MIKMIDIDestinationEndpoint.m */; }; - 9D9751601C4EAF4100A2F318 /* MIKMIDIDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750E21C4EAF4100A2F318 /* MIKMIDIDevice.m */; }; - 9D9751611C4EAF4100A2F318 /* MIKMIDIDeviceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750E41C4EAF4100A2F318 /* MIKMIDIDeviceManager.m */; }; - 9D9751621C4EAF4100A2F318 /* MIKMIDIEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750E61C4EAF4100A2F318 /* MIKMIDIEndpoint.m */; }; - 9D9751631C4EAF4100A2F318 /* MIKMIDIEndpointSynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750E81C4EAF4100A2F318 /* MIKMIDIEndpointSynthesizer.m */; }; - 9D9751641C4EAF4100A2F318 /* MIKMIDIEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750EA1C4EAF4100A2F318 /* MIKMIDIEntity.m */; }; - 9D9751651C4EAF4100A2F318 /* MIKMIDIErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750EC1C4EAF4100A2F318 /* MIKMIDIErrors.m */; }; - 9D9751661C4EAF4100A2F318 /* MIKMIDIEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750EE1C4EAF4100A2F318 /* MIKMIDIEvent.m */; }; - 9D9751671C4EAF4100A2F318 /* MIKMIDIEventIterator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750F11C4EAF4100A2F318 /* MIKMIDIEventIterator.m */; }; - 9D9751681C4EAF4100A2F318 /* MIKMIDIInputPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750F31C4EAF4100A2F318 /* MIKMIDIInputPort.m */; }; - 9D9751691C4EAF4100A2F318 /* MIKMIDIMacDebugQuickLookSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750F41C4EAF4100A2F318 /* MIKMIDIMacDebugQuickLookSupport.m */; }; - 9D97516A1C4EAF4100A2F318 /* MIKMIDIMapping.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750F71C4EAF4100A2F318 /* MIKMIDIMapping.m */; }; - 9D97516B1C4EAF4100A2F318 /* MIKMIDIMappingGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750F91C4EAF4100A2F318 /* MIKMIDIMappingGenerator.m */; }; - 9D97516C1C4EAF4100A2F318 /* MIKMIDIMappingItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750FB1C4EAF4100A2F318 /* MIKMIDIMappingItem.m */; }; - 9D97516D1C4EAF4100A2F318 /* MIKMIDIMappingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750FD1C4EAF4100A2F318 /* MIKMIDIMappingManager.m */; }; - 9D97516E1C4EAF4100A2F318 /* MIKMIDIMappingXMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9750FF1C4EAF4100A2F318 /* MIKMIDIMappingXMLParser.m */; }; - 9D97516F1C4EAF4100A2F318 /* MIKMIDIMetaCopyrightEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751011C4EAF4100A2F318 /* MIKMIDIMetaCopyrightEvent.m */; }; - 9D9751701C4EAF4100A2F318 /* MIKMIDIMetaCuePointEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751031C4EAF4100A2F318 /* MIKMIDIMetaCuePointEvent.m */; }; - 9D9751711C4EAF4100A2F318 /* MIKMIDIMetaEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751051C4EAF4100A2F318 /* MIKMIDIMetaEvent.m */; }; - 9D9751721C4EAF4100A2F318 /* MIKMIDIMetaInstrumentNameEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751081C4EAF4100A2F318 /* MIKMIDIMetaInstrumentNameEvent.m */; }; - 9D9751731C4EAF4100A2F318 /* MIKMIDIMetaKeySignatureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97510A1C4EAF4100A2F318 /* MIKMIDIMetaKeySignatureEvent.m */; }; - 9D9751741C4EAF4100A2F318 /* MIKMIDIMetaLyricEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97510C1C4EAF4100A2F318 /* MIKMIDIMetaLyricEvent.m */; }; - 9D9751751C4EAF4100A2F318 /* MIKMIDIMetaMarkerTextEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97510E1C4EAF4100A2F318 /* MIKMIDIMetaMarkerTextEvent.m */; }; - 9D9751761C4EAF4100A2F318 /* MIKMIDIMetaSequenceEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751101C4EAF4100A2F318 /* MIKMIDIMetaSequenceEvent.m */; }; - 9D9751771C4EAF4100A2F318 /* MIKMIDIMetaTextEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751121C4EAF4100A2F318 /* MIKMIDIMetaTextEvent.m */; }; - 9D9751781C4EAF4100A2F318 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751141C4EAF4100A2F318 /* MIKMIDIMetaTimeSignatureEvent.m */; }; - 9D9751791C4EAF4100A2F318 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751161C4EAF4100A2F318 /* MIKMIDIMetaTrackSequenceNameEvent.m */; }; - 9D97517A1C4EAF4100A2F318 /* MIKMIDIMetronome.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751181C4EAF4100A2F318 /* MIKMIDIMetronome.m */; }; - 9D97517B1C4EAF4100A2F318 /* MIKMIDINoteEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97511A1C4EAF4100A2F318 /* MIKMIDINoteEvent.m */; }; - 9D97517C1C4EAF4100A2F318 /* MIKMIDINoteOffCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97511C1C4EAF4100A2F318 /* MIKMIDINoteOffCommand.m */; }; - 9D97517D1C4EAF4100A2F318 /* MIKMIDINoteOnCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97511E1C4EAF4100A2F318 /* MIKMIDINoteOnCommand.m */; }; - 9D97517E1C4EAF4100A2F318 /* MIKMIDIObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751201C4EAF4100A2F318 /* MIKMIDIObject.m */; }; - 9D97517F1C4EAF4100A2F318 /* MIKMIDIOutputPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751231C4EAF4100A2F318 /* MIKMIDIOutputPort.m */; }; - 9D9751801C4EAF4100A2F318 /* MIKMIDIPitchBendChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751251C4EAF4100A2F318 /* MIKMIDIPitchBendChangeCommand.m */; }; - 9D9751811C4EAF4100A2F318 /* MIKMIDIPitchBendChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751271C4EAF4100A2F318 /* MIKMIDIPitchBendChangeEvent.m */; }; - 9D9751821C4EAF4100A2F318 /* MIKMIDIPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751291C4EAF4100A2F318 /* MIKMIDIPlayer.m */; }; - 9D9751831C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97512B1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureCommand.m */; }; - 9D9751841C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97512D1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureEvent.m */; }; - 9D9751851C4EAF4100A2F318 /* MIKMIDIPort.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97512F1C4EAF4100A2F318 /* MIKMIDIPort.m */; }; - 9D9751861C4EAF4100A2F318 /* MIKMIDIPrivateUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751331C4EAF4100A2F318 /* MIKMIDIPrivateUtilities.m */; }; - 9D9751871C4EAF4100A2F318 /* MIKMIDIProgramChangeCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751351C4EAF4100A2F318 /* MIKMIDIProgramChangeCommand.m */; }; - 9D9751881C4EAF4100A2F318 /* MIKMIDIProgramChangeEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751371C4EAF4100A2F318 /* MIKMIDIProgramChangeEvent.m */; }; - 9D9751891C4EAF4100A2F318 /* MIKMIDISequence.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97513B1C4EAF4100A2F318 /* MIKMIDISequence.m */; }; - 9D97518A1C4EAF4100A2F318 /* MIKMIDISequencer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97513E1C4EAF4100A2F318 /* MIKMIDISequencer.m */; }; - 9D97518B1C4EAF4100A2F318 /* MIKMIDISourceEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751401C4EAF4100A2F318 /* MIKMIDISourceEndpoint.m */; }; - 9D97518C1C4EAF4100A2F318 /* MIKMIDISynthesizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751421C4EAF4100A2F318 /* MIKMIDISynthesizer.m */; }; - 9D97518D1C4EAF4100A2F318 /* MIKMIDISynthesizerInstrument.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751451C4EAF4100A2F318 /* MIKMIDISynthesizerInstrument.m */; }; - 9D97518E1C4EAF4100A2F318 /* MIKMIDISystemExclusiveCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751471C4EAF4100A2F318 /* MIKMIDISystemExclusiveCommand.m */; }; - 9D97518F1C4EAF4100A2F318 /* MIKMIDISystemMessageCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751491C4EAF4100A2F318 /* MIKMIDISystemMessageCommand.m */; }; - 9D9751901C4EAF4100A2F318 /* MIKMIDITempoEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97514B1C4EAF4100A2F318 /* MIKMIDITempoEvent.m */; }; - 9D9751911C4EAF4100A2F318 /* MIKMIDITrack.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D97514D1C4EAF4100A2F318 /* MIKMIDITrack.m */; }; - 9D9751921C4EAF4100A2F318 /* MIKMIDIUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751501C4EAF4100A2F318 /* MIKMIDIUtilities.m */; }; - 9D9751931C4EAF4100A2F318 /* NSUIApplication+MIKMIDI.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D9751521C4EAF4100A2F318 /* NSUIApplication+MIKMIDI.m */; }; 9DB1D24519256855004FD5AF /* libxml2.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DB1D24319256840004FD5AF /* libxml2.2.dylib */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 9D7B3BC21CFF7B4600822CE3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D7B3BBC1CFF7B4600822CE3 /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9D74EEA517A7129300BEE89F; + remoteInfo = MIKMIDI; + }; + 9D7B3BC41CFF7B4600822CE3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D7B3BBC1CFF7B4600822CE3 /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9DAF8B061A7AFF1100F46528; + remoteInfo = "MIKMIDI-iOS"; + }; + 9D7B3BC61CFF7B4600822CE3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D7B3BBC1CFF7B4600822CE3 /* MIKMIDI.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9D4DF13A1AAB57430065F004; + remoteInfo = "MIKMIDI Tests"; + }; + 9D7B3BCA1CFF7B5000822CE3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9D7B3BBC1CFF7B4600822CE3 /* MIKMIDI.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 9DAF8B051A7AFF1100F46528; + remoteInfo = "MIKMIDI-iOS"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9D7B3BCC1CFF7B5000822CE3 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9D7B3BC91CFF7B5000822CE3 /* MIKMIDI.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 9D6BF5AA175C3A0D00DF6B15 /* ORSSplitViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORSSplitViewManager.h; sourceTree = ""; }; 9D6BF5AB175C3A0D00DF6B15 /* ORSSplitViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORSSplitViewManager.m; sourceTree = ""; }; @@ -116,19 +87,9 @@ 9D6BF5E3175EA67B00DF6B15 /* ORSPianoButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORSPianoButton.h; sourceTree = ""; }; 9D6BF5E4175EA67B00DF6B15 /* ORSPianoButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORSPianoButton.m; sourceTree = ""; }; 9D6BF5E6175EA6F300DF6B15 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; - 9D6BF5EA175EA9A600DF6B15 /* 0.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 0.aiff; sourceTree = ""; }; - 9D6BF5EB175EA9A600DF6B15 /* 1.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 1.aiff; sourceTree = ""; }; - 9D6BF5EC175EA9A600DF6B15 /* 2.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 2.aiff; sourceTree = ""; }; - 9D6BF5ED175EA9A600DF6B15 /* 3.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 3.aiff; sourceTree = ""; }; - 9D6BF5EE175EA9A600DF6B15 /* 4.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 4.aiff; sourceTree = ""; }; - 9D6BF5EF175EA9A600DF6B15 /* 5.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 5.aiff; sourceTree = ""; }; - 9D6BF5F0175EA9A600DF6B15 /* 6.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 6.aiff; sourceTree = ""; }; - 9D6BF5F1175EA9A600DF6B15 /* 7.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 7.aiff; sourceTree = ""; }; - 9D6BF5F2175EA9A600DF6B15 /* 8.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 8.aiff; sourceTree = ""; }; - 9D6BF5F3175EA9A600DF6B15 /* 9.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 9.aiff; sourceTree = ""; }; - 9D6BF5F4175EA9A600DF6B15 /* 10.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 10.aiff; sourceTree = ""; }; - 9D6BF5F5175EA9A600DF6B15 /* 11.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = 11.aiff; sourceTree = ""; }; 9D6BF602175EAFE500DF6B15 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 9D7B3BBC1CFF7B4600822CE3 /* MIKMIDI.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MIKMIDI.xcodeproj; path = ../../../Framework/MIKMIDI.xcodeproj; sourceTree = ""; }; + 9D7B3BCD1CFF7E8D00822CE3 /* Grand Piano.sf2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Grand Piano.sf2"; sourceTree = ""; }; 9D8E68AF175C1A6A006546F6 /* MIDI Soundboard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MIDI Soundboard.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 9D8E68B2175C1A6A006546F6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 9D8E68B4175C1A6A006546F6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; @@ -148,151 +109,6 @@ 9D8E6938175C1BD6006546F6 /* ORSAvailableDevicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORSAvailableDevicesTableViewController.m; sourceTree = ""; }; 9D8E693E175C1D2F006546F6 /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; }; 9D8E6947175EBBC4006546F6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - 9D9750C21C4EAF4100A2F318 /* MIKMIDI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDI.h; sourceTree = ""; }; - 9D9750C31C4EAF4100A2F318 /* MIKMIDIChannelEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelEvent.h; sourceTree = ""; }; - 9D9750C41C4EAF4100A2F318 /* MIKMIDIChannelEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelEvent.m; sourceTree = ""; }; - 9D9750C51C4EAF4100A2F318 /* MIKMIDIChannelPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureCommand.h; sourceTree = ""; }; - 9D9750C61C4EAF4100A2F318 /* MIKMIDIChannelPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureCommand.m; sourceTree = ""; }; - 9D9750C71C4EAF4100A2F318 /* MIKMIDIChannelPressureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelPressureEvent.h; sourceTree = ""; }; - 9D9750C81C4EAF4100A2F318 /* MIKMIDIChannelPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelPressureEvent.m; sourceTree = ""; }; - 9D9750C91C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelVoiceCommand.h; sourceTree = ""; }; - 9D9750CA1C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIChannelVoiceCommand.m; sourceTree = ""; }; - 9D9750CB1C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIChannelVoiceCommand_SubclassMethods.h; sourceTree = ""; }; - 9D9750CC1C4EAF4100A2F318 /* MIKMIDIClientDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientDestinationEndpoint.h; sourceTree = ""; }; - 9D9750CD1C4EAF4100A2F318 /* MIKMIDIClientDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientDestinationEndpoint.m; sourceTree = ""; }; - 9D9750CE1C4EAF4100A2F318 /* MIKMIDIClientSourceEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClientSourceEndpoint.h; sourceTree = ""; }; - 9D9750CF1C4EAF4100A2F318 /* MIKMIDIClientSourceEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClientSourceEndpoint.m; sourceTree = ""; }; - 9D9750D01C4EAF4100A2F318 /* MIKMIDIClock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIClock.h; sourceTree = ""; }; - 9D9750D11C4EAF4100A2F318 /* MIKMIDIClock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIClock.m; sourceTree = ""; }; - 9D9750D21C4EAF4100A2F318 /* MIKMIDICommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommand.h; sourceTree = ""; }; - 9D9750D31C4EAF4100A2F318 /* MIKMIDICommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommand.m; sourceTree = ""; }; - 9D9750D41C4EAF4100A2F318 /* MIKMIDICommand_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommand_SubclassMethods.h; sourceTree = ""; }; - 9D9750D51C4EAF4100A2F318 /* MIKMIDICommandScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommandScheduler.h; sourceTree = ""; }; - 9D9750D61C4EAF4100A2F318 /* MIKMIDICommandThrottler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICommandThrottler.h; sourceTree = ""; }; - 9D9750D71C4EAF4100A2F318 /* MIKMIDICommandThrottler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDICommandThrottler.m; sourceTree = ""; }; - 9D9750D81C4EAF4100A2F318 /* MIKMIDICompilerCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDICompilerCompatibility.h; sourceTree = ""; }; - 9D9750D91C4EAF4100A2F318 /* MIKMIDIConnectionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIConnectionManager.h; sourceTree = ""; }; - 9D9750DA1C4EAF4100A2F318 /* MIKMIDIConnectionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIConnectionManager.m; sourceTree = ""; }; - 9D9750DB1C4EAF4100A2F318 /* MIKMIDIControlChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIControlChangeCommand.h; sourceTree = ""; }; - 9D9750DC1C4EAF4100A2F318 /* MIKMIDIControlChangeCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIControlChangeCommand.m; sourceTree = ""; }; - 9D9750DD1C4EAF4100A2F318 /* MIKMIDIControlChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIControlChangeEvent.h; sourceTree = ""; }; - 9D9750DE1C4EAF4100A2F318 /* MIKMIDIControlChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIControlChangeEvent.m; sourceTree = ""; }; - 9D9750DF1C4EAF4100A2F318 /* MIKMIDIDestinationEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIDestinationEndpoint.h; sourceTree = ""; }; - 9D9750E01C4EAF4100A2F318 /* MIKMIDIDestinationEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIDestinationEndpoint.m; sourceTree = ""; }; - 9D9750E11C4EAF4100A2F318 /* MIKMIDIDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIDevice.h; sourceTree = ""; }; - 9D9750E21C4EAF4100A2F318 /* MIKMIDIDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIDevice.m; sourceTree = ""; }; - 9D9750E31C4EAF4100A2F318 /* MIKMIDIDeviceManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIDeviceManager.h; sourceTree = ""; }; - 9D9750E41C4EAF4100A2F318 /* MIKMIDIDeviceManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIDeviceManager.m; sourceTree = ""; }; - 9D9750E51C4EAF4100A2F318 /* MIKMIDIEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEndpoint.h; sourceTree = ""; }; - 9D9750E61C4EAF4100A2F318 /* MIKMIDIEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEndpoint.m; sourceTree = ""; }; - 9D9750E71C4EAF4100A2F318 /* MIKMIDIEndpointSynthesizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEndpointSynthesizer.h; sourceTree = ""; }; - 9D9750E81C4EAF4100A2F318 /* MIKMIDIEndpointSynthesizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEndpointSynthesizer.m; sourceTree = ""; }; - 9D9750E91C4EAF4100A2F318 /* MIKMIDIEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEntity.h; sourceTree = ""; }; - 9D9750EA1C4EAF4100A2F318 /* MIKMIDIEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEntity.m; sourceTree = ""; }; - 9D9750EB1C4EAF4100A2F318 /* MIKMIDIErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIErrors.h; sourceTree = ""; }; - 9D9750EC1C4EAF4100A2F318 /* MIKMIDIErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIErrors.m; sourceTree = ""; }; - 9D9750ED1C4EAF4100A2F318 /* MIKMIDIEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEvent.h; sourceTree = ""; }; - 9D9750EE1C4EAF4100A2F318 /* MIKMIDIEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEvent.m; sourceTree = ""; }; - 9D9750EF1C4EAF4100A2F318 /* MIKMIDIEvent_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEvent_SubclassMethods.h; sourceTree = ""; }; - 9D9750F01C4EAF4100A2F318 /* MIKMIDIEventIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIEventIterator.h; sourceTree = ""; }; - 9D9750F11C4EAF4100A2F318 /* MIKMIDIEventIterator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIEventIterator.m; sourceTree = ""; }; - 9D9750F21C4EAF4100A2F318 /* MIKMIDIInputPort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIInputPort.h; sourceTree = ""; }; - 9D9750F31C4EAF4100A2F318 /* MIKMIDIInputPort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIInputPort.m; sourceTree = ""; }; - 9D9750F41C4EAF4100A2F318 /* MIKMIDIMacDebugQuickLookSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMacDebugQuickLookSupport.m; sourceTree = ""; }; - 9D9750F51C4EAF4100A2F318 /* MIKMIDIMappableResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappableResponder.h; sourceTree = ""; }; - 9D9750F61C4EAF4100A2F318 /* MIKMIDIMapping.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMapping.h; sourceTree = ""; }; - 9D9750F71C4EAF4100A2F318 /* MIKMIDIMapping.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMapping.m; sourceTree = ""; }; - 9D9750F81C4EAF4100A2F318 /* MIKMIDIMappingGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingGenerator.h; sourceTree = ""; }; - 9D9750F91C4EAF4100A2F318 /* MIKMIDIMappingGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingGenerator.m; sourceTree = ""; }; - 9D9750FA1C4EAF4100A2F318 /* MIKMIDIMappingItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingItem.h; sourceTree = ""; }; - 9D9750FB1C4EAF4100A2F318 /* MIKMIDIMappingItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingItem.m; sourceTree = ""; }; - 9D9750FC1C4EAF4100A2F318 /* MIKMIDIMappingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingManager.h; sourceTree = ""; }; - 9D9750FD1C4EAF4100A2F318 /* MIKMIDIMappingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingManager.m; sourceTree = ""; }; - 9D9750FE1C4EAF4100A2F318 /* MIKMIDIMappingXMLParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMappingXMLParser.h; sourceTree = ""; }; - 9D9750FF1C4EAF4100A2F318 /* MIKMIDIMappingXMLParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMappingXMLParser.m; sourceTree = ""; }; - 9D9751001C4EAF4100A2F318 /* MIKMIDIMetaCopyrightEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaCopyrightEvent.h; sourceTree = ""; }; - 9D9751011C4EAF4100A2F318 /* MIKMIDIMetaCopyrightEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaCopyrightEvent.m; sourceTree = ""; }; - 9D9751021C4EAF4100A2F318 /* MIKMIDIMetaCuePointEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaCuePointEvent.h; sourceTree = ""; }; - 9D9751031C4EAF4100A2F318 /* MIKMIDIMetaCuePointEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaCuePointEvent.m; sourceTree = ""; }; - 9D9751041C4EAF4100A2F318 /* MIKMIDIMetaEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaEvent.h; sourceTree = ""; }; - 9D9751051C4EAF4100A2F318 /* MIKMIDIMetaEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaEvent.m; sourceTree = ""; }; - 9D9751061C4EAF4100A2F318 /* MIKMIDIMetaEvent_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaEvent_SubclassMethods.h; sourceTree = ""; }; - 9D9751071C4EAF4100A2F318 /* MIKMIDIMetaInstrumentNameEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaInstrumentNameEvent.h; sourceTree = ""; }; - 9D9751081C4EAF4100A2F318 /* MIKMIDIMetaInstrumentNameEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaInstrumentNameEvent.m; sourceTree = ""; }; - 9D9751091C4EAF4100A2F318 /* MIKMIDIMetaKeySignatureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaKeySignatureEvent.h; sourceTree = ""; }; - 9D97510A1C4EAF4100A2F318 /* MIKMIDIMetaKeySignatureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaKeySignatureEvent.m; sourceTree = ""; }; - 9D97510B1C4EAF4100A2F318 /* MIKMIDIMetaLyricEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaLyricEvent.h; sourceTree = ""; }; - 9D97510C1C4EAF4100A2F318 /* MIKMIDIMetaLyricEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaLyricEvent.m; sourceTree = ""; }; - 9D97510D1C4EAF4100A2F318 /* MIKMIDIMetaMarkerTextEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaMarkerTextEvent.h; sourceTree = ""; }; - 9D97510E1C4EAF4100A2F318 /* MIKMIDIMetaMarkerTextEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaMarkerTextEvent.m; sourceTree = ""; }; - 9D97510F1C4EAF4100A2F318 /* MIKMIDIMetaSequenceEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaSequenceEvent.h; sourceTree = ""; }; - 9D9751101C4EAF4100A2F318 /* MIKMIDIMetaSequenceEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaSequenceEvent.m; sourceTree = ""; }; - 9D9751111C4EAF4100A2F318 /* MIKMIDIMetaTextEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaTextEvent.h; sourceTree = ""; }; - 9D9751121C4EAF4100A2F318 /* MIKMIDIMetaTextEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaTextEvent.m; sourceTree = ""; }; - 9D9751131C4EAF4100A2F318 /* MIKMIDIMetaTimeSignatureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaTimeSignatureEvent.h; sourceTree = ""; }; - 9D9751141C4EAF4100A2F318 /* MIKMIDIMetaTimeSignatureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaTimeSignatureEvent.m; sourceTree = ""; }; - 9D9751151C4EAF4100A2F318 /* MIKMIDIMetaTrackSequenceNameEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetaTrackSequenceNameEvent.h; sourceTree = ""; }; - 9D9751161C4EAF4100A2F318 /* MIKMIDIMetaTrackSequenceNameEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaTrackSequenceNameEvent.m; sourceTree = ""; }; - 9D9751171C4EAF4100A2F318 /* MIKMIDIMetronome.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIMetronome.h; sourceTree = ""; }; - 9D9751181C4EAF4100A2F318 /* MIKMIDIMetronome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetronome.m; sourceTree = ""; }; - 9D9751191C4EAF4100A2F318 /* MIKMIDINoteEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDINoteEvent.h; sourceTree = ""; }; - 9D97511A1C4EAF4100A2F318 /* MIKMIDINoteEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDINoteEvent.m; sourceTree = ""; }; - 9D97511B1C4EAF4100A2F318 /* MIKMIDINoteOffCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDINoteOffCommand.h; sourceTree = ""; }; - 9D97511C1C4EAF4100A2F318 /* MIKMIDINoteOffCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDINoteOffCommand.m; sourceTree = ""; }; - 9D97511D1C4EAF4100A2F318 /* MIKMIDINoteOnCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDINoteOnCommand.h; sourceTree = ""; }; - 9D97511E1C4EAF4100A2F318 /* MIKMIDINoteOnCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDINoteOnCommand.m; sourceTree = ""; }; - 9D97511F1C4EAF4100A2F318 /* MIKMIDIObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIObject.h; sourceTree = ""; }; - 9D9751201C4EAF4100A2F318 /* MIKMIDIObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIObject.m; sourceTree = ""; }; - 9D9751211C4EAF4100A2F318 /* MIKMIDIObject_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIObject_SubclassMethods.h; sourceTree = ""; }; - 9D9751221C4EAF4100A2F318 /* MIKMIDIOutputPort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIOutputPort.h; sourceTree = ""; }; - 9D9751231C4EAF4100A2F318 /* MIKMIDIOutputPort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIOutputPort.m; sourceTree = ""; }; - 9D9751241C4EAF4100A2F318 /* MIKMIDIPitchBendChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeCommand.h; sourceTree = ""; }; - 9D9751251C4EAF4100A2F318 /* MIKMIDIPitchBendChangeCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPitchBendChangeCommand.m; sourceTree = ""; }; - 9D9751261C4EAF4100A2F318 /* MIKMIDIPitchBendChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPitchBendChangeEvent.h; sourceTree = ""; }; - 9D9751271C4EAF4100A2F318 /* MIKMIDIPitchBendChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPitchBendChangeEvent.m; sourceTree = ""; }; - 9D9751281C4EAF4100A2F318 /* MIKMIDIPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPlayer.h; sourceTree = ""; }; - 9D9751291C4EAF4100A2F318 /* MIKMIDIPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPlayer.m; sourceTree = ""; }; - 9D97512A1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureCommand.h; sourceTree = ""; }; - 9D97512B1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureCommand.m; sourceTree = ""; }; - 9D97512C1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPolyphonicKeyPressureEvent.h; sourceTree = ""; }; - 9D97512D1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPolyphonicKeyPressureEvent.m; sourceTree = ""; }; - 9D97512E1C4EAF4100A2F318 /* MIKMIDIPort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPort.h; sourceTree = ""; }; - 9D97512F1C4EAF4100A2F318 /* MIKMIDIPort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPort.m; sourceTree = ""; }; - 9D9751301C4EAF4100A2F318 /* MIKMIDIPort_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPort_SubclassMethods.h; sourceTree = ""; }; - 9D9751311C4EAF4100A2F318 /* MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPrivate.h; sourceTree = ""; }; - 9D9751321C4EAF4100A2F318 /* MIKMIDIPrivateUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIPrivateUtilities.h; sourceTree = ""; }; - 9D9751331C4EAF4100A2F318 /* MIKMIDIPrivateUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIPrivateUtilities.m; sourceTree = ""; }; - 9D9751341C4EAF4100A2F318 /* MIKMIDIProgramChangeCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIProgramChangeCommand.h; sourceTree = ""; }; - 9D9751351C4EAF4100A2F318 /* MIKMIDIProgramChangeCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIProgramChangeCommand.m; sourceTree = ""; }; - 9D9751361C4EAF4100A2F318 /* MIKMIDIProgramChangeEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIProgramChangeEvent.h; sourceTree = ""; }; - 9D9751371C4EAF4100A2F318 /* MIKMIDIProgramChangeEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIProgramChangeEvent.m; sourceTree = ""; }; - 9D9751381C4EAF4100A2F318 /* MIKMIDIResponder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIResponder.h; sourceTree = ""; }; - 9D9751391C4EAF4100A2F318 /* MIKMIDISequence+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequence+MIKMIDIPrivate.h"; sourceTree = ""; }; - 9D97513A1C4EAF4100A2F318 /* MIKMIDISequence.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequence.h; sourceTree = ""; }; - 9D97513B1C4EAF4100A2F318 /* MIKMIDISequence.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequence.m; sourceTree = ""; }; - 9D97513C1C4EAF4100A2F318 /* MIKMIDISequencer+MIKMIDIPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MIKMIDISequencer+MIKMIDIPrivate.h"; sourceTree = ""; }; - 9D97513D1C4EAF4100A2F318 /* MIKMIDISequencer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISequencer.h; sourceTree = ""; }; - 9D97513E1C4EAF4100A2F318 /* MIKMIDISequencer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISequencer.m; sourceTree = ""; }; - 9D97513F1C4EAF4100A2F318 /* MIKMIDISourceEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISourceEndpoint.h; sourceTree = ""; }; - 9D9751401C4EAF4100A2F318 /* MIKMIDISourceEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISourceEndpoint.m; sourceTree = ""; }; - 9D9751411C4EAF4100A2F318 /* MIKMIDISynthesizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizer.h; sourceTree = ""; }; - 9D9751421C4EAF4100A2F318 /* MIKMIDISynthesizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizer.m; sourceTree = ""; }; - 9D9751431C4EAF4100A2F318 /* MIKMIDISynthesizer_SubclassMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizer_SubclassMethods.h; sourceTree = ""; }; - 9D9751441C4EAF4100A2F318 /* MIKMIDISynthesizerInstrument.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISynthesizerInstrument.h; sourceTree = ""; }; - 9D9751451C4EAF4100A2F318 /* MIKMIDISynthesizerInstrument.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISynthesizerInstrument.m; sourceTree = ""; }; - 9D9751461C4EAF4100A2F318 /* MIKMIDISystemExclusiveCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISystemExclusiveCommand.h; sourceTree = ""; }; - 9D9751471C4EAF4100A2F318 /* MIKMIDISystemExclusiveCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISystemExclusiveCommand.m; sourceTree = ""; }; - 9D9751481C4EAF4100A2F318 /* MIKMIDISystemMessageCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDISystemMessageCommand.h; sourceTree = ""; }; - 9D9751491C4EAF4100A2F318 /* MIKMIDISystemMessageCommand.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDISystemMessageCommand.m; sourceTree = ""; }; - 9D97514A1C4EAF4100A2F318 /* MIKMIDITempoEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITempoEvent.h; sourceTree = ""; }; - 9D97514B1C4EAF4100A2F318 /* MIKMIDITempoEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDITempoEvent.m; sourceTree = ""; }; - 9D97514C1C4EAF4100A2F318 /* MIKMIDITrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITrack.h; sourceTree = ""; }; - 9D97514D1C4EAF4100A2F318 /* MIKMIDITrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDITrack.m; sourceTree = ""; }; - 9D97514E1C4EAF4100A2F318 /* MIKMIDITrack_Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDITrack_Protected.h; sourceTree = ""; }; - 9D97514F1C4EAF4100A2F318 /* MIKMIDIUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIKMIDIUtilities.h; sourceTree = ""; }; - 9D9751501C4EAF4100A2F318 /* MIKMIDIUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIUtilities.m; sourceTree = ""; }; - 9D9751511C4EAF4100A2F318 /* NSUIApplication+MIKMIDI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUIApplication+MIKMIDI.h"; sourceTree = ""; }; - 9D9751521C4EAF4100A2F318 /* NSUIApplication+MIKMIDI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUIApplication+MIKMIDI.m"; sourceTree = ""; }; 9DB1D24319256840004FD5AF /* libxml2.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.2.dylib; path = usr/lib/libxml2.2.dylib; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -307,6 +123,7 @@ 9D6BF5E7175EA6F300DF6B15 /* AudioToolbox.framework in Frameworks */, 9D8E693F175C1D2F006546F6 /* CoreMIDI.framework in Frameworks */, 9D8E68B3175C1A6A006546F6 /* UIKit.framework in Frameworks */, + 9D7B3BC81CFF7B5000822CE3 /* MIKMIDI.framework in Frameworks */, 9D8E68B5175C1A6A006546F6 /* Foundation.framework in Frameworks */, 9D8E68B7175C1A6A006546F6 /* CoreGraphics.framework in Frameworks */, ); @@ -315,23 +132,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 9D6BF5E9175EA9A600DF6B15 /* PianoNotes */ = { + 9D7B3BBD1CFF7B4600822CE3 /* Products */ = { isa = PBXGroup; children = ( - 9D6BF5EA175EA9A600DF6B15 /* 0.aiff */, - 9D6BF5EB175EA9A600DF6B15 /* 1.aiff */, - 9D6BF5EC175EA9A600DF6B15 /* 2.aiff */, - 9D6BF5ED175EA9A600DF6B15 /* 3.aiff */, - 9D6BF5EE175EA9A600DF6B15 /* 4.aiff */, - 9D6BF5EF175EA9A600DF6B15 /* 5.aiff */, - 9D6BF5F0175EA9A600DF6B15 /* 6.aiff */, - 9D6BF5F1175EA9A600DF6B15 /* 7.aiff */, - 9D6BF5F2175EA9A600DF6B15 /* 8.aiff */, - 9D6BF5F3175EA9A600DF6B15 /* 9.aiff */, - 9D6BF5F4175EA9A600DF6B15 /* 10.aiff */, - 9D6BF5F5175EA9A600DF6B15 /* 11.aiff */, + 9D7B3BC31CFF7B4600822CE3 /* MIKMIDI.framework */, + 9D7B3BC51CFF7B4600822CE3 /* MIKMIDI.framework */, + 9D7B3BC71CFF7B4600822CE3 /* MIKMIDI Tests.xctest */, ); - path = PianoNotes; + name = Products; sourceTree = ""; }; 9D8E68A6175C1A6A006546F6 = { @@ -355,6 +163,7 @@ 9D8E68B1175C1A6A006546F6 /* Frameworks */ = { isa = PBXGroup; children = ( + 9D7B3BBC1CFF7B4600822CE3 /* MIKMIDI.xcodeproj */, 9DB1D24319256840004FD5AF /* libxml2.2.dylib */, 9D8E6947175EBBC4006546F6 /* QuartzCore.framework */, 9D6BF602175EAFE500DF6B15 /* AVFoundation.framework */, @@ -373,7 +182,6 @@ 9D8E68DB175C1AAE006546F6 /* ORSAppDelegate.h */, 9D8E68DC175C1AAE006546F6 /* ORSAppDelegate.m */, 9D8E693A175C1CA7006546F6 /* Views */, - 9D9750C11C4EAF4100A2F318 /* MIKMIDI */, 9D8E68F6175C1AD1006546F6 /* UI Controllers */, 9D8E68F7175C1AD6006546F6 /* Other Sources */, ); @@ -383,9 +191,9 @@ 9D8E68E2175C1AB3006546F6 /* Resources */ = { isa = PBXGroup; children = ( - 9D6BF5E9175EA9A600DF6B15 /* PianoNotes */, 9D8E68F5175C1ABA006546F6 /* Storyboards and XIBs */, 9D8E68E9175C1AB3006546F6 /* Images */, + 9D7B3BCD1CFF7E8D00822CE3 /* Grand Piano.sf2 */, 9D8E68ED175C1AB3006546F6 /* MIDI Soundboard-Info.plist */, 9D8E68E3175C1AB3006546F6 /* InfoPlist.strings */, ); @@ -443,159 +251,6 @@ name = Views; sourceTree = ""; }; - 9D9750C11C4EAF4100A2F318 /* MIKMIDI */ = { - isa = PBXGroup; - children = ( - 9D9750C21C4EAF4100A2F318 /* MIKMIDI.h */, - 9D9750C31C4EAF4100A2F318 /* MIKMIDIChannelEvent.h */, - 9D9750C41C4EAF4100A2F318 /* MIKMIDIChannelEvent.m */, - 9D9750C51C4EAF4100A2F318 /* MIKMIDIChannelPressureCommand.h */, - 9D9750C61C4EAF4100A2F318 /* MIKMIDIChannelPressureCommand.m */, - 9D9750C71C4EAF4100A2F318 /* MIKMIDIChannelPressureEvent.h */, - 9D9750C81C4EAF4100A2F318 /* MIKMIDIChannelPressureEvent.m */, - 9D9750C91C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand.h */, - 9D9750CA1C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand.m */, - 9D9750CB1C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand_SubclassMethods.h */, - 9D9750CC1C4EAF4100A2F318 /* MIKMIDIClientDestinationEndpoint.h */, - 9D9750CD1C4EAF4100A2F318 /* MIKMIDIClientDestinationEndpoint.m */, - 9D9750CE1C4EAF4100A2F318 /* MIKMIDIClientSourceEndpoint.h */, - 9D9750CF1C4EAF4100A2F318 /* MIKMIDIClientSourceEndpoint.m */, - 9D9750D01C4EAF4100A2F318 /* MIKMIDIClock.h */, - 9D9750D11C4EAF4100A2F318 /* MIKMIDIClock.m */, - 9D9750D21C4EAF4100A2F318 /* MIKMIDICommand.h */, - 9D9750D31C4EAF4100A2F318 /* MIKMIDICommand.m */, - 9D9750D41C4EAF4100A2F318 /* MIKMIDICommand_SubclassMethods.h */, - 9D9750D51C4EAF4100A2F318 /* MIKMIDICommandScheduler.h */, - 9D9750D61C4EAF4100A2F318 /* MIKMIDICommandThrottler.h */, - 9D9750D71C4EAF4100A2F318 /* MIKMIDICommandThrottler.m */, - 9D9750D81C4EAF4100A2F318 /* MIKMIDICompilerCompatibility.h */, - 9D9750D91C4EAF4100A2F318 /* MIKMIDIConnectionManager.h */, - 9D9750DA1C4EAF4100A2F318 /* MIKMIDIConnectionManager.m */, - 9D9750DB1C4EAF4100A2F318 /* MIKMIDIControlChangeCommand.h */, - 9D9750DC1C4EAF4100A2F318 /* MIKMIDIControlChangeCommand.m */, - 9D9750DD1C4EAF4100A2F318 /* MIKMIDIControlChangeEvent.h */, - 9D9750DE1C4EAF4100A2F318 /* MIKMIDIControlChangeEvent.m */, - 9D9750DF1C4EAF4100A2F318 /* MIKMIDIDestinationEndpoint.h */, - 9D9750E01C4EAF4100A2F318 /* MIKMIDIDestinationEndpoint.m */, - 9D9750E11C4EAF4100A2F318 /* MIKMIDIDevice.h */, - 9D9750E21C4EAF4100A2F318 /* MIKMIDIDevice.m */, - 9D9750E31C4EAF4100A2F318 /* MIKMIDIDeviceManager.h */, - 9D9750E41C4EAF4100A2F318 /* MIKMIDIDeviceManager.m */, - 9D9750E51C4EAF4100A2F318 /* MIKMIDIEndpoint.h */, - 9D9750E61C4EAF4100A2F318 /* MIKMIDIEndpoint.m */, - 9D9750E71C4EAF4100A2F318 /* MIKMIDIEndpointSynthesizer.h */, - 9D9750E81C4EAF4100A2F318 /* MIKMIDIEndpointSynthesizer.m */, - 9D9750E91C4EAF4100A2F318 /* MIKMIDIEntity.h */, - 9D9750EA1C4EAF4100A2F318 /* MIKMIDIEntity.m */, - 9D9750EB1C4EAF4100A2F318 /* MIKMIDIErrors.h */, - 9D9750EC1C4EAF4100A2F318 /* MIKMIDIErrors.m */, - 9D9750ED1C4EAF4100A2F318 /* MIKMIDIEvent.h */, - 9D9750EE1C4EAF4100A2F318 /* MIKMIDIEvent.m */, - 9D9750EF1C4EAF4100A2F318 /* MIKMIDIEvent_SubclassMethods.h */, - 9D9750F01C4EAF4100A2F318 /* MIKMIDIEventIterator.h */, - 9D9750F11C4EAF4100A2F318 /* MIKMIDIEventIterator.m */, - 9D9750F21C4EAF4100A2F318 /* MIKMIDIInputPort.h */, - 9D9750F31C4EAF4100A2F318 /* MIKMIDIInputPort.m */, - 9D9750F41C4EAF4100A2F318 /* MIKMIDIMacDebugQuickLookSupport.m */, - 9D9750F51C4EAF4100A2F318 /* MIKMIDIMappableResponder.h */, - 9D9750F61C4EAF4100A2F318 /* MIKMIDIMapping.h */, - 9D9750F71C4EAF4100A2F318 /* MIKMIDIMapping.m */, - 9D9750F81C4EAF4100A2F318 /* MIKMIDIMappingGenerator.h */, - 9D9750F91C4EAF4100A2F318 /* MIKMIDIMappingGenerator.m */, - 9D9750FA1C4EAF4100A2F318 /* MIKMIDIMappingItem.h */, - 9D9750FB1C4EAF4100A2F318 /* MIKMIDIMappingItem.m */, - 9D9750FC1C4EAF4100A2F318 /* MIKMIDIMappingManager.h */, - 9D9750FD1C4EAF4100A2F318 /* MIKMIDIMappingManager.m */, - 9D9750FE1C4EAF4100A2F318 /* MIKMIDIMappingXMLParser.h */, - 9D9750FF1C4EAF4100A2F318 /* MIKMIDIMappingXMLParser.m */, - 9D9751001C4EAF4100A2F318 /* MIKMIDIMetaCopyrightEvent.h */, - 9D9751011C4EAF4100A2F318 /* MIKMIDIMetaCopyrightEvent.m */, - 9D9751021C4EAF4100A2F318 /* MIKMIDIMetaCuePointEvent.h */, - 9D9751031C4EAF4100A2F318 /* MIKMIDIMetaCuePointEvent.m */, - 9D9751041C4EAF4100A2F318 /* MIKMIDIMetaEvent.h */, - 9D9751051C4EAF4100A2F318 /* MIKMIDIMetaEvent.m */, - 9D9751061C4EAF4100A2F318 /* MIKMIDIMetaEvent_SubclassMethods.h */, - 9D9751071C4EAF4100A2F318 /* MIKMIDIMetaInstrumentNameEvent.h */, - 9D9751081C4EAF4100A2F318 /* MIKMIDIMetaInstrumentNameEvent.m */, - 9D9751091C4EAF4100A2F318 /* MIKMIDIMetaKeySignatureEvent.h */, - 9D97510A1C4EAF4100A2F318 /* MIKMIDIMetaKeySignatureEvent.m */, - 9D97510B1C4EAF4100A2F318 /* MIKMIDIMetaLyricEvent.h */, - 9D97510C1C4EAF4100A2F318 /* MIKMIDIMetaLyricEvent.m */, - 9D97510D1C4EAF4100A2F318 /* MIKMIDIMetaMarkerTextEvent.h */, - 9D97510E1C4EAF4100A2F318 /* MIKMIDIMetaMarkerTextEvent.m */, - 9D97510F1C4EAF4100A2F318 /* MIKMIDIMetaSequenceEvent.h */, - 9D9751101C4EAF4100A2F318 /* MIKMIDIMetaSequenceEvent.m */, - 9D9751111C4EAF4100A2F318 /* MIKMIDIMetaTextEvent.h */, - 9D9751121C4EAF4100A2F318 /* MIKMIDIMetaTextEvent.m */, - 9D9751131C4EAF4100A2F318 /* MIKMIDIMetaTimeSignatureEvent.h */, - 9D9751141C4EAF4100A2F318 /* MIKMIDIMetaTimeSignatureEvent.m */, - 9D9751151C4EAF4100A2F318 /* MIKMIDIMetaTrackSequenceNameEvent.h */, - 9D9751161C4EAF4100A2F318 /* MIKMIDIMetaTrackSequenceNameEvent.m */, - 9D9751171C4EAF4100A2F318 /* MIKMIDIMetronome.h */, - 9D9751181C4EAF4100A2F318 /* MIKMIDIMetronome.m */, - 9D9751191C4EAF4100A2F318 /* MIKMIDINoteEvent.h */, - 9D97511A1C4EAF4100A2F318 /* MIKMIDINoteEvent.m */, - 9D97511B1C4EAF4100A2F318 /* MIKMIDINoteOffCommand.h */, - 9D97511C1C4EAF4100A2F318 /* MIKMIDINoteOffCommand.m */, - 9D97511D1C4EAF4100A2F318 /* MIKMIDINoteOnCommand.h */, - 9D97511E1C4EAF4100A2F318 /* MIKMIDINoteOnCommand.m */, - 9D97511F1C4EAF4100A2F318 /* MIKMIDIObject.h */, - 9D9751201C4EAF4100A2F318 /* MIKMIDIObject.m */, - 9D9751211C4EAF4100A2F318 /* MIKMIDIObject_SubclassMethods.h */, - 9D9751221C4EAF4100A2F318 /* MIKMIDIOutputPort.h */, - 9D9751231C4EAF4100A2F318 /* MIKMIDIOutputPort.m */, - 9D9751241C4EAF4100A2F318 /* MIKMIDIPitchBendChangeCommand.h */, - 9D9751251C4EAF4100A2F318 /* MIKMIDIPitchBendChangeCommand.m */, - 9D9751261C4EAF4100A2F318 /* MIKMIDIPitchBendChangeEvent.h */, - 9D9751271C4EAF4100A2F318 /* MIKMIDIPitchBendChangeEvent.m */, - 9D9751281C4EAF4100A2F318 /* MIKMIDIPlayer.h */, - 9D9751291C4EAF4100A2F318 /* MIKMIDIPlayer.m */, - 9D97512A1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureCommand.h */, - 9D97512B1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureCommand.m */, - 9D97512C1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureEvent.h */, - 9D97512D1C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureEvent.m */, - 9D97512E1C4EAF4100A2F318 /* MIKMIDIPort.h */, - 9D97512F1C4EAF4100A2F318 /* MIKMIDIPort.m */, - 9D9751301C4EAF4100A2F318 /* MIKMIDIPort_SubclassMethods.h */, - 9D9751311C4EAF4100A2F318 /* MIKMIDIPrivate.h */, - 9D9751321C4EAF4100A2F318 /* MIKMIDIPrivateUtilities.h */, - 9D9751331C4EAF4100A2F318 /* MIKMIDIPrivateUtilities.m */, - 9D9751341C4EAF4100A2F318 /* MIKMIDIProgramChangeCommand.h */, - 9D9751351C4EAF4100A2F318 /* MIKMIDIProgramChangeCommand.m */, - 9D9751361C4EAF4100A2F318 /* MIKMIDIProgramChangeEvent.h */, - 9D9751371C4EAF4100A2F318 /* MIKMIDIProgramChangeEvent.m */, - 9D9751381C4EAF4100A2F318 /* MIKMIDIResponder.h */, - 9D9751391C4EAF4100A2F318 /* MIKMIDISequence+MIKMIDIPrivate.h */, - 9D97513A1C4EAF4100A2F318 /* MIKMIDISequence.h */, - 9D97513B1C4EAF4100A2F318 /* MIKMIDISequence.m */, - 9D97513C1C4EAF4100A2F318 /* MIKMIDISequencer+MIKMIDIPrivate.h */, - 9D97513D1C4EAF4100A2F318 /* MIKMIDISequencer.h */, - 9D97513E1C4EAF4100A2F318 /* MIKMIDISequencer.m */, - 9D97513F1C4EAF4100A2F318 /* MIKMIDISourceEndpoint.h */, - 9D9751401C4EAF4100A2F318 /* MIKMIDISourceEndpoint.m */, - 9D9751411C4EAF4100A2F318 /* MIKMIDISynthesizer.h */, - 9D9751421C4EAF4100A2F318 /* MIKMIDISynthesizer.m */, - 9D9751431C4EAF4100A2F318 /* MIKMIDISynthesizer_SubclassMethods.h */, - 9D9751441C4EAF4100A2F318 /* MIKMIDISynthesizerInstrument.h */, - 9D9751451C4EAF4100A2F318 /* MIKMIDISynthesizerInstrument.m */, - 9D9751461C4EAF4100A2F318 /* MIKMIDISystemExclusiveCommand.h */, - 9D9751471C4EAF4100A2F318 /* MIKMIDISystemExclusiveCommand.m */, - 9D9751481C4EAF4100A2F318 /* MIKMIDISystemMessageCommand.h */, - 9D9751491C4EAF4100A2F318 /* MIKMIDISystemMessageCommand.m */, - 9D97514A1C4EAF4100A2F318 /* MIKMIDITempoEvent.h */, - 9D97514B1C4EAF4100A2F318 /* MIKMIDITempoEvent.m */, - 9D97514C1C4EAF4100A2F318 /* MIKMIDITrack.h */, - 9D97514D1C4EAF4100A2F318 /* MIKMIDITrack.m */, - 9D97514E1C4EAF4100A2F318 /* MIKMIDITrack_Protected.h */, - 9D97514F1C4EAF4100A2F318 /* MIKMIDIUtilities.h */, - 9D9751501C4EAF4100A2F318 /* MIKMIDIUtilities.m */, - 9D9751511C4EAF4100A2F318 /* NSUIApplication+MIKMIDI.h */, - 9D9751521C4EAF4100A2F318 /* NSUIApplication+MIKMIDI.m */, - ); - name = MIKMIDI; - path = ../../../../Source; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -606,10 +261,12 @@ 9D8E68AB175C1A6A006546F6 /* Sources */, 9D8E68AC175C1A6A006546F6 /* Frameworks */, 9D8E68AD175C1A6A006546F6 /* Resources */, + 9D7B3BCC1CFF7B5000822CE3 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + 9D7B3BCB1CFF7B5000822CE3 /* PBXTargetDependency */, ); name = "MIDI Soundboard"; productName = "MIDI Soundboard"; @@ -636,6 +293,12 @@ mainGroup = 9D8E68A6175C1A6A006546F6; productRefGroup = 9D8E68B0175C1A6A006546F6 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 9D7B3BBD1CFF7B4600822CE3 /* Products */; + ProjectRef = 9D7B3BBC1CFF7B4600822CE3 /* MIKMIDI.xcodeproj */; + }, + ); projectRoot = ""; targets = ( 9D8E68AE175C1A6A006546F6 /* MIDI Soundboard */, @@ -643,6 +306,30 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + 9D7B3BC31CFF7B4600822CE3 /* MIKMIDI.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MIKMIDI.framework; + remoteRef = 9D7B3BC21CFF7B4600822CE3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 9D7B3BC51CFF7B4600822CE3 /* MIKMIDI.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MIKMIDI.framework; + remoteRef = 9D7B3BC41CFF7B4600822CE3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 9D7B3BC71CFF7B4600822CE3 /* MIKMIDI Tests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = "MIKMIDI Tests.xctest"; + remoteRef = 9D7B3BC61CFF7B4600822CE3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ 9D8E68AD175C1A6A006546F6 /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -651,22 +338,11 @@ 9D8E68EE175C1AB3006546F6 /* InfoPlist.strings in Resources */, 9D8E68EF175C1AB3006546F6 /* MainStoryboard_iPad.storyboard in Resources */, 9D8E68F0175C1AB3006546F6 /* MainStoryboard_iPhone.storyboard in Resources */, + 9D7B3BCE1CFF7E8D00822CE3 /* Grand Piano.sf2 in Resources */, 9D8E68F1175C1AB3006546F6 /* Default-568h@2x.png in Resources */, 9D8E68F2175C1AB3006546F6 /* Default.png in Resources */, 9D8E68F3175C1AB3006546F6 /* Default@2x.png in Resources */, 9D6BF5E2175EA60E00DF6B15 /* Black.png in Resources */, - 9D6BF5F6175EA9A600DF6B15 /* 0.aiff in Resources */, - 9D6BF5F7175EA9A600DF6B15 /* 1.aiff in Resources */, - 9D6BF5F8175EA9A600DF6B15 /* 2.aiff in Resources */, - 9D6BF5F9175EA9A600DF6B15 /* 3.aiff in Resources */, - 9D6BF5FA175EA9A600DF6B15 /* 4.aiff in Resources */, - 9D6BF5FB175EA9A600DF6B15 /* 5.aiff in Resources */, - 9D6BF5FC175EA9A600DF6B15 /* 6.aiff in Resources */, - 9D6BF5FD175EA9A600DF6B15 /* 7.aiff in Resources */, - 9D6BF5FE175EA9A600DF6B15 /* 8.aiff in Resources */, - 9D6BF5FF175EA9A600DF6B15 /* 9.aiff in Resources */, - 9D6BF600175EA9A600DF6B15 /* 10.aiff in Resources */, - 9D6BF601175EA9A600DF6B15 /* 11.aiff in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -677,82 +353,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9D9751781C4EAF4100A2F318 /* MIKMIDIMetaTimeSignatureEvent.m in Sources */, - 9D97516F1C4EAF4100A2F318 /* MIKMIDIMetaCopyrightEvent.m in Sources */, - 9D9751881C4EAF4100A2F318 /* MIKMIDIProgramChangeEvent.m in Sources */, - 9D9751851C4EAF4100A2F318 /* MIKMIDIPort.m in Sources */, - 9D97518A1C4EAF4100A2F318 /* MIKMIDISequencer.m in Sources */, - 9D9751611C4EAF4100A2F318 /* MIKMIDIDeviceManager.m in Sources */, - 9D9751741C4EAF4100A2F318 /* MIKMIDIMetaLyricEvent.m in Sources */, - 9D9751871C4EAF4100A2F318 /* MIKMIDIProgramChangeCommand.m in Sources */, - 9D9751801C4EAF4100A2F318 /* MIKMIDIPitchBendChangeCommand.m in Sources */, - 9D9751921C4EAF4100A2F318 /* MIKMIDIUtilities.m in Sources */, - 9D97518B1C4EAF4100A2F318 /* MIKMIDISourceEndpoint.m in Sources */, - 9D9751671C4EAF4100A2F318 /* MIKMIDIEventIterator.m in Sources */, - 9D97516B1C4EAF4100A2F318 /* MIKMIDIMappingGenerator.m in Sources */, - 9D9751691C4EAF4100A2F318 /* MIKMIDIMacDebugQuickLookSupport.m in Sources */, - 9D9751751C4EAF4100A2F318 /* MIKMIDIMetaMarkerTextEvent.m in Sources */, - 9D97515A1C4EAF4100A2F318 /* MIKMIDICommand.m in Sources */, - 9D9751861C4EAF4100A2F318 /* MIKMIDIPrivateUtilities.m in Sources */, 9D8E68DF175C1AAE006546F6 /* main.m in Sources */, - 9D9751901C4EAF4100A2F318 /* MIKMIDITempoEvent.m in Sources */, - 9D9751601C4EAF4100A2F318 /* MIKMIDIDevice.m in Sources */, - 9D9751721C4EAF4100A2F318 /* MIKMIDIMetaInstrumentNameEvent.m in Sources */, - 9D9751581C4EAF4100A2F318 /* MIKMIDIClientSourceEndpoint.m in Sources */, - 9D9751701C4EAF4100A2F318 /* MIKMIDIMetaCuePointEvent.m in Sources */, - 9D97516E1C4EAF4100A2F318 /* MIKMIDIMappingXMLParser.m in Sources */, - 9D97516A1C4EAF4100A2F318 /* MIKMIDIMapping.m in Sources */, - 9D9751681C4EAF4100A2F318 /* MIKMIDIInputPort.m in Sources */, - 9D97516D1C4EAF4100A2F318 /* MIKMIDIMappingManager.m in Sources */, 9D8E68E0175C1AAE006546F6 /* ORSAppDelegate.m in Sources */, - 9D97515C1C4EAF4100A2F318 /* MIKMIDIConnectionManager.m in Sources */, - 9D97518C1C4EAF4100A2F318 /* MIKMIDISynthesizer.m in Sources */, - 9D9751651C4EAF4100A2F318 /* MIKMIDIErrors.m in Sources */, - 9D97518D1C4EAF4100A2F318 /* MIKMIDISynthesizerInstrument.m in Sources */, - 9D9751731C4EAF4100A2F318 /* MIKMIDIMetaKeySignatureEvent.m in Sources */, - 9D9751821C4EAF4100A2F318 /* MIKMIDIPlayer.m in Sources */, - 9D97517C1C4EAF4100A2F318 /* MIKMIDINoteOffCommand.m in Sources */, - 9D9751791C4EAF4100A2F318 /* MIKMIDIMetaTrackSequenceNameEvent.m in Sources */, - 9D9751591C4EAF4100A2F318 /* MIKMIDIClock.m in Sources */, - 9D9751841C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureEvent.m in Sources */, - 9D97516C1C4EAF4100A2F318 /* MIKMIDIMappingItem.m in Sources */, 9D8E6939175C1BD6006546F6 /* ORSAvailableDevicesTableViewController.m in Sources */, - 9D9751711C4EAF4100A2F318 /* MIKMIDIMetaEvent.m in Sources */, - 9D97517A1C4EAF4100A2F318 /* MIKMIDIMetronome.m in Sources */, - 9D97515D1C4EAF4100A2F318 /* MIKMIDIControlChangeCommand.m in Sources */, - 9D9751891C4EAF4100A2F318 /* MIKMIDISequence.m in Sources */, - 9D9751931C4EAF4100A2F318 /* NSUIApplication+MIKMIDI.m in Sources */, - 9D97518E1C4EAF4100A2F318 /* MIKMIDISystemExclusiveCommand.m in Sources */, - 9D97515B1C4EAF4100A2F318 /* MIKMIDICommandThrottler.m in Sources */, - 9D97517D1C4EAF4100A2F318 /* MIKMIDINoteOnCommand.m in Sources */, - 9D9751771C4EAF4100A2F318 /* MIKMIDIMetaTextEvent.m in Sources */, - 9D9751641C4EAF4100A2F318 /* MIKMIDIEntity.m in Sources */, 9D6BF5AC175C3A0D00DF6B15 /* ORSSplitViewManager.m in Sources */, - 9D97518F1C4EAF4100A2F318 /* MIKMIDISystemMessageCommand.m in Sources */, - 9D9751811C4EAF4100A2F318 /* MIKMIDIPitchBendChangeEvent.m in Sources */, - 9D9751571C4EAF4100A2F318 /* MIKMIDIClientDestinationEndpoint.m in Sources */, - 9D9751531C4EAF4100A2F318 /* MIKMIDIChannelEvent.m in Sources */, - 9D9751631C4EAF4100A2F318 /* MIKMIDIEndpointSynthesizer.m in Sources */, - 9D9751551C4EAF4100A2F318 /* MIKMIDIChannelPressureEvent.m in Sources */, - 9D97515E1C4EAF4100A2F318 /* MIKMIDIControlChangeEvent.m in Sources */, - 9D9751661C4EAF4100A2F318 /* MIKMIDIEvent.m in Sources */, - 9D97517E1C4EAF4100A2F318 /* MIKMIDIObject.m in Sources */, - 9D9751911C4EAF4100A2F318 /* MIKMIDITrack.m in Sources */, - 9D9751761C4EAF4100A2F318 /* MIKMIDIMetaSequenceEvent.m in Sources */, - 9D97517B1C4EAF4100A2F318 /* MIKMIDINoteEvent.m in Sources */, - 9D97517F1C4EAF4100A2F318 /* MIKMIDIOutputPort.m in Sources */, 9D6BF5AF175C3C9900DF6B15 /* ORSSoundboardViewController.m in Sources */, - 9D9751831C4EAF4100A2F318 /* MIKMIDIPolyphonicKeyPressureCommand.m in Sources */, - 9D97515F1C4EAF4100A2F318 /* MIKMIDIDestinationEndpoint.m in Sources */, 9D6BF5E5175EA67B00DF6B15 /* ORSPianoButton.m in Sources */, - 9D9751621C4EAF4100A2F318 /* MIKMIDIEndpoint.m in Sources */, - 9D9751541C4EAF4100A2F318 /* MIKMIDIChannelPressureCommand.m in Sources */, - 9D9751561C4EAF4100A2F318 /* MIKMIDIChannelVoiceCommand.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 9D7B3BCB1CFF7B5000822CE3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "MIKMIDI-iOS"; + targetProxy = 9D7B3BCA1CFF7B5000822CE3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 9D8E68E3175C1AB3006546F6 /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -851,6 +470,8 @@ "\"$(SDK_DIR)\"/usr/include/libxml2", ); INFOPLIST_FILE = "Resources/MIDI Soundboard-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.openreelsoftware.com.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; @@ -868,6 +489,8 @@ "\"$(SDK_DIR)\"/usr/include/libxml2", ); INFOPLIST_FILE = "Resources/MIDI Soundboard-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 8.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.openreelsoftware.com.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; diff --git a/Examples/iOS/MIDI Soundboard/README.md b/Examples/iOS/MIDI Soundboard/README.md new file mode 100644 index 00000000..4fb781d3 --- /dev/null +++ b/Examples/iOS/MIDI Soundboard/README.md @@ -0,0 +1,9 @@ +## MIDI Soundboard + +This is a simple example of an Objective-C iOS app that uses MIKMIDI to create an onscreen piano keyboard. It can also connect to and receive MIDI from a connected MIDI keyboard. + +It demonstrates the use of MIKMIDICommand, MIKMIDISynthesizer, MIKMIDIDevice, MIKMIDIResponder, and other MIKMIDI APIs. + +**Note**: This example was originally written when MIKMIDI was first being developed. Some aspects of it could be done better with newer MIKMIDI API. The roadmap for MIKMIDI 1.7 includes updating or replacing this example with better iOS example code. + +As always, please direct questions to [Andrew Madsen](mailto:andrew@mixedinkey.com). \ No newline at end of file diff --git a/Examples/iOS/MIDI Soundboard/Resources/Grand Piano.sf2 b/Examples/iOS/MIDI Soundboard/Resources/Grand Piano.sf2 new file mode 100644 index 00000000..04ee7b92 Binary files /dev/null and b/Examples/iOS/MIDI Soundboard/Resources/Grand Piano.sf2 differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/0.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/0.aiff deleted file mode 100644 index b408bb13..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/0.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/1.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/1.aiff deleted file mode 100644 index 6750d91b..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/1.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/10.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/10.aiff deleted file mode 100644 index 152ed9e4..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/10.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/11.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/11.aiff deleted file mode 100644 index 76cbc281..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/11.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/2.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/2.aiff deleted file mode 100644 index 25fd0470..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/2.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/3.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/3.aiff deleted file mode 100644 index 07ef9bca..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/3.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/4.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/4.aiff deleted file mode 100644 index 4a6c6bcf..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/4.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/5.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/5.aiff deleted file mode 100644 index b3b6f9d3..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/5.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/6.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/6.aiff deleted file mode 100644 index 2243285e..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/6.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/7.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/7.aiff deleted file mode 100644 index 0a0a9c30..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/7.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/8.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/8.aiff deleted file mode 100644 index b94fc3b3..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/8.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/9.aiff b/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/9.aiff deleted file mode 100644 index a62f1d22..00000000 Binary files a/Examples/iOS/MIDI Soundboard/Resources/PianoNotes/9.aiff and /dev/null differ diff --git a/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPad.storyboard b/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPad.storyboard index adf73011..31037c12 100644 --- a/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPad.storyboard +++ b/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPad.storyboard @@ -1,11 +1,11 @@ - + - - + + - + @@ -13,7 +13,6 @@ - @@ -111,9 +110,6 @@ - - - @@ -343,11 +306,22 @@ - + + + + + + + + + + + + @@ -355,9 +329,4 @@ - - - - - diff --git a/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPhone.storyboard b/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPhone.storyboard index 531fb1c3..80db7b16 100644 --- a/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPhone.storyboard +++ b/Examples/iOS/MIDI Soundboard/Resources/en.lproj/MainStoryboard_iPhone.storyboard @@ -1,8 +1,8 @@ - + - - + + @@ -23,9 +23,4 @@ - - - - - diff --git a/Examples/iOS/MIDI Soundboard/Sources/ORSAppDelegate.m b/Examples/iOS/MIDI Soundboard/Sources/ORSAppDelegate.m index cb7c67b3..35b5e0bb 100644 --- a/Examples/iOS/MIDI Soundboard/Sources/ORSAppDelegate.m +++ b/Examples/iOS/MIDI Soundboard/Sources/ORSAppDelegate.m @@ -7,46 +7,13 @@ // #import "ORSAppDelegate.h" -#import "MIKMIDI.h" +#import @implementation ORSAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. - NSSet *mappings = [[MIKMIDIMappingManager sharedManager] mappings]; - MIKMIDIMapping *mapping = [mappings anyObject]; - NSLog(@"mapping: %@", [mapping XMLStringRepresentation]); - // NSLog(@"Mappings: %@", mappings); - return YES; } - -- (void)applicationWillResignActive:(UIApplication *)application -{ - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} @end diff --git a/Examples/iOS/MIDI Soundboard/Sources/ORSAvailableDevicesTableViewController.m b/Examples/iOS/MIDI Soundboard/Sources/ORSAvailableDevicesTableViewController.m index 798e95c0..133b1156 100644 --- a/Examples/iOS/MIDI Soundboard/Sources/ORSAvailableDevicesTableViewController.m +++ b/Examples/iOS/MIDI Soundboard/Sources/ORSAvailableDevicesTableViewController.m @@ -7,7 +7,7 @@ // #import "ORSAvailableDevicesTableViewController.h" -#import "MIKMIDI.h" +#import @interface ORSAvailableDevicesTableViewController () diff --git a/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.h b/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.h index 43f3df01..72aace25 100644 --- a/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.h +++ b/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.h @@ -7,7 +7,7 @@ // #import -#import "MIKMIDI.h" +#import @interface ORSPianoButton : UIButton diff --git a/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.m b/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.m index e8e9058d..09bf9300 100644 --- a/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.m +++ b/Examples/iOS/MIDI Soundboard/Sources/ORSPianoButton.m @@ -8,6 +8,7 @@ #import "ORSPianoButton.h" #import +#import @implementation ORSPianoButton diff --git a/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.h b/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.h index 7c034d76..8f6982c1 100644 --- a/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.h +++ b/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.h @@ -10,7 +10,7 @@ #import "ORSAvailableDevicesTableViewController.h" #import -@interface ORSSoundboardViewController : UIViewController +@interface ORSSoundboardViewController : UIViewController - (IBAction)pianoKeyDown:(id)sender; diff --git a/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.m b/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.m index 80c58a4a..d91caf91 100644 --- a/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.m +++ b/Examples/iOS/MIDI Soundboard/Sources/ORSSoundboardViewController.m @@ -7,7 +7,7 @@ // #import "ORSSoundboardViewController.h" -#import "MIKMIDI.h" +#import @interface ORSSoundboardViewController () @@ -15,29 +15,40 @@ @interface ORSSoundboardViewController () @property (nonatomic, strong) MIKMIDIDevice *device; @property (nonatomic, strong) id connectionToken; -@property (nonatomic, strong) NSMutableSet *audioPlayers; +@property (nonatomic, strong, readonly) MIKMIDISynthesizer *synthesizer; + +@property (nonatomic, strong) IBOutletCollection(UIButton) NSArray *pianoButtons; @end @implementation ORSSoundboardViewController -- (IBAction)pianoKeyDown:(id)sender +- (void)viewDidLoad { - NSString *fileName = [NSString stringWithFormat:@"%li", (long)[sender tag]]; - NSURL *fileURL = [[NSBundle mainBundle] URLForResource:fileName withExtension:@"aiff"]; - if (!fileURL) return; + [super viewDidLoad]; - NSError *error = nil; - AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error]; - if (!audioPlayer) { - NSLog(@"Unable to load %@ into audio player: %@", fileURL, error); - return; + for (UIButton *button in self.pianoButtons) { + [button addTarget:self action:@selector(pianoKeyDown:) forControlEvents:UIControlEventTouchDown]; + [button addTarget:self action:@selector(pianoKeyUp:) forControlEvents:UIControlEventTouchUpInside]; + [button addTarget:self action:@selector(pianoKeyUp:) forControlEvents:UIControlEventTouchUpOutside]; + [button addTarget:self action:@selector(pianoKeyUp:) forControlEvents:UIControlEventTouchCancel]; } - - audioPlayer.delegate = self; - audioPlayer.volume = 1.0; - [audioPlayer play]; - [self.audioPlayers addObject:audioPlayer]; +} + +#pragma mark - Actions + +- (IBAction)pianoKeyDown:(id)sender +{ + UInt8 note = 60 + [sender tag]; + MIKMIDINoteOnCommand *noteOn = [MIKMIDINoteOnCommand noteOnCommandWithNote:note velocity:127 channel:0 timestamp:[NSDate date]]; + [self.synthesizer handleMIDIMessages:@[noteOn]]; +} + +- (IBAction)pianoKeyUp:(id)sender +{ + UInt8 note = 60 + [sender tag]; + MIKMIDINoteOffCommand *noteOff = [MIKMIDINoteOffCommand noteOffCommandWithNote:note velocity:127 channel:0 timestamp:[NSDate date]]; + [self.synthesizer handleMIDIMessages:@[noteOff]]; } #pragma mark - Private @@ -75,13 +86,6 @@ - (void)connectToDevice:(MIKMIDIDevice *)device self.connectionToken = connectionToken; } -#pragma mark - AVAudioPlayerDelegate - -- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag -{ - [self.audioPlayers removeObject:player]; -} - #pragma mark ORSAvailableDevicesTableViewControllerDelegate - (void)availableDevicesTableViewController:(ORSAvailableDevicesTableViewController *)controller midiDeviceWasSelected:(MIKMIDIDevice *)device @@ -130,12 +134,18 @@ - (void)setDevice:(MIKMIDIDevice *)device } } -- (NSMutableSet *)audioPlayers +@synthesize synthesizer = _synthesizer; +- (MIKMIDISynthesizer *)synthesizer { - if (!_audioPlayers) { - _audioPlayers = [NSMutableSet set]; + if (!_synthesizer) { + _synthesizer = [[MIKMIDISynthesizer alloc] init]; + NSURL *soundfont = [[NSBundle mainBundle] URLForResource:@"Grand Piano" withExtension:@"sf2"]; + NSError *error = nil; + if (![_synthesizer loadSoundfontFromFileAtURL:soundfont error:&error]) { + NSLog(@"Error loading soundfont for synthesizer. Sound will be degraded. %@", error); + } } - return _audioPlayers; + return _synthesizer; } @end diff --git a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m index ff4e3afc..e27acf93 100644 --- a/Framework/MIKMIDI Tests/MIKMIDICommandTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDICommandTests.m @@ -52,6 +52,7 @@ - (void)testChannelPressureCommand 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."); + XCTAssertEqual(command.data.length, 2, "MIKMIDIChannelPressureCommand had an incorrect data length %@ (should be 2)", @(command.data.length)); MIKMutableMIDIChannelPressureCommand *mutableCommand = [command mutableCopy]; XCTAssert([mutableCommand isMemberOfClass:[mutableClass class]], @"-[MIKMIDIChannelPressureCommand mutableCopy] did not return an mutableClass instance."); diff --git a/Framework/MIKMIDI Tests/MIKMIDIMetaEventTests.m b/Framework/MIKMIDI Tests/MIKMIDIMetaEventTests.m new file mode 100644 index 00000000..57fa781d --- /dev/null +++ b/Framework/MIKMIDI Tests/MIKMIDIMetaEventTests.m @@ -0,0 +1,141 @@ +// +// MIKMIDIMetaEventTests.m +// MIKMIDI +// +// Created by Andrew Madsen on 4/21/16. +// Copyright © 2016 Mixed In Key. All rights reserved. +// + +#import +#import +#import +#import + +@interface MIKMIDIMetaEventTests : XCTestCase + +@end + +@implementation MIKMIDIMetaEventTests + +- (void)testMetaEventInit +{ + NSString *text = @"Mixed In Key"; + MIKMIDIMetaTextEvent *textEvent = + (MIKMIDIMetaTextEvent *)[[MIKMIDIMetaEvent alloc] initWithMetaData:[text dataUsingEncoding:NSUTF8StringEncoding] + metadataType:MIKMIDIMetaEventTypeTextEvent + timeStamp:10]; + XCTAssertTrue([textEvent isMemberOfClass:[MIKMIDIMetaTextEvent class]], @"MIKMIDIMetaEvent initializer didn't produce expected subclass (MIKMIDIMetaTextEvent)"); + XCTAssertEqualObjects(textEvent.string, text, @"MIKMIDIMetaTextEvent didn't have expected string value."); + XCTAssertEqual(textEvent.metadataType, MIKMIDIMetaEventTypeTextEvent, "MIKMIDIMetaTextEvent didn't have expected metadataType."); + XCTAssertEqual(textEvent.timeStamp, 10, "MIKMIDIMetaTextEvent didn't have expected timeStamp."); + + MIKMutableMIDIMetaTimeSignatureEvent *timeSignatureEvent = (MIKMutableMIDIMetaTimeSignatureEvent *)[[MIKMutableMIDIMetaEvent alloc] initWithTimeStamp:26 midiEventType:MIKMIDIEventTypeMetaTimeSignature data:nil]; + timeSignatureEvent.numerator = 6; + timeSignatureEvent.denominator= 8; + + XCTAssertTrue([timeSignatureEvent isMemberOfClass:[MIKMutableMIDIMetaTimeSignatureEvent class]], @"MIKMutableMIDIMetaEvent initializer didn't produce expected subclass (MIKMutableMIDIMetaTimeSignatureEvent)"); + XCTAssertEqual(timeSignatureEvent.numerator, 6, "MIKMIDIMetaTimeSignatureEvent didn't have expected numerator."); + XCTAssertEqual(timeSignatureEvent.denominator, 8, "MIKMIDIMetaTimeSignatureEvent didn't have expected denominator."); + XCTAssertEqual(timeSignatureEvent.metronomePulse, 24, "MIKMIDIMetaTimeSignatureEvent didn't have expected metronomePulse."); + XCTAssertEqual(timeSignatureEvent.thirtySecondsPerQuarterNote, 8, "MIKMIDIMetaTimeSignatureEvent didn't have expected thirtySecondsPerQuarterNote."); + XCTAssertEqual(timeSignatureEvent.metadataType, MIKMIDIMetaEventTypeTimeSignature, "MIKMIDIMetaTimeSignatureEvent didn't have expected metadataType."); + XCTAssertEqual(timeSignatureEvent.timeStamp, 26, "MIKMIDIMetaTimeSignatureEvent didn't have expected timeStamp."); +} + +- (void)testBareInits +{ + NSArray *classNames = @[ + @"MIKMIDIMetaCuePointEvent", + @"MIKMIDIMetaLyricEvent", + @"MIKMIDIMetaTrackSequenceNameEvent", + @"MIKMIDIMetaTextEvent", + @"MIKMIDIMetaTimeSignatureEvent", + @"MIKMIDIMetaEvent", + @"MIKMIDIMetaMarkerTextEvent", + @"MIKMIDIMetaInstrumentNameEvent", + @"MIKMIDIMetaCopyrightEvent", + @"MIKMIDIMetaKeySignatureEvent", + ]; + for (NSString *className in classNames) { + Class subclass = NSClassFromString(className); + Class mutableSubclass = [subclass mutableCounterpartClass]; + XCTAssertFalse([subclass isMutable], "%@ should not be mutable.", NSStringFromClass(subclass)); + XCTAssertTrue([mutableSubclass isMutable], "%@ should be mutable.", NSStringFromClass(mutableSubclass)); + + MIKMIDIMetaEvent *event = [[subclass alloc] init]; + MIKMutableMIDIMetaEvent *mutableEvent = [[mutableSubclass alloc] init]; + XCTAssertTrue([event isMemberOfClass:subclass], "-[[%@ alloc] init] did not produce instance of expected class (%@)", NSStringFromClass(subclass), NSStringFromClass([event class])); + XCTAssertTrue([mutableEvent isMemberOfClass:mutableSubclass], "-[[%@ alloc] init] did not produce instance of expected class (%@)", NSStringFromClass(mutableSubclass), NSStringFromClass([mutableEvent class])); + + MIKMIDIEventType eventType = [[[subclass supportedMIDIEventTypes] firstObject] unsignedIntegerValue]; + XCTAssertEqual(event.eventType, eventType, "-[[%@ alloc] init] did not produce instance with expected event type (%@)", NSStringFromClass(subclass), @(eventType)); + XCTAssertEqual(mutableEvent.eventType, eventType, "-[[%@ alloc] init] did not produce instance with expected event type (%@)", NSStringFromClass(mutableSubclass), @(eventType)); + + // FIXME: See Issue #151 +// MIKMIDIMetaEventType metadataType = [MIKMIDIMetaEvent metaSubtypeForEventType:eventType]; +// XCTAssertEqual(event.metadataType, metadataType, "-[[%@ alloc] init] did not produce instance with expected metadata type (%@)", NSStringFromClass(subclass), @(metadataType)); +// XCTAssertEqual(mutableEvent.metadataType, metadataType, "-[[%@ alloc] init] did not produce instance with expected metadata type (%@)", NSStringFromClass(mutableSubclass), @(metadataType)); + } + +} + +- (void)testMetaTextEventInit +{ + NSString *text = @"Mixed In Key"; + MIKMIDIMetaTextEvent *event = [[MIKMIDIMetaTextEvent alloc] initWithString:text timeStamp:27]; + XCTAssertTrue([event isMemberOfClass:[MIKMIDIMetaTextEvent class]], @"MIKMIDIMetaTextEvent initializer didn't produce expected subclass (MIKMIDIMetaTextEvent)"); + XCTAssertEqualObjects(event.string, text, @"MIKMIDIMetaTextEvent didn't have expected string value."); + XCTAssertEqual(event.metadataType, MIKMIDIMetaEventTypeTextEvent, "MIKMIDIMetaTextEvent didn't have expected metadataType."); + XCTAssertEqual(event.timeStamp, 27, "MIKMIDIMetaTextEvent didn't have expected timeStamp."); +} + +- (void)testMetaTrackSequenceNameEventInit +{ + NSString *name = @"Have You Heard?"; + MIKMIDIMetaTrackSequenceNameEvent *event = + [[MIKMIDIMetaTrackSequenceNameEvent alloc] initWithName:name timeStamp:42]; + + XCTAssertTrue([event isMemberOfClass:[MIKMIDIMetaTrackSequenceNameEvent class]], @"MIKMIDIMetaTrackSequenceNameEvent initializer didn't produce expected subclass (MIKMIDIMetaTrackSequenceNameEvent)"); + XCTAssertEqualObjects(event.name, name, @"MIKMIDIMetaTrackSequenceNameEvent didn't have expected string value."); + XCTAssertEqual(event.metadataType, MIKMIDIMetaEventTypeTrackSequenceName, "MIKMIDIMetaTrackSequenceNameEvent didn't have expected metadataType."); + XCTAssertEqual(event.timeStamp, 42, "MIKMIDIMetaTrackSequenceNameEvent didn't have expected timeStamp."); +} + +- (void)testMetaTimeSignatureEventInit +{ + MIKMIDIMetaTimeSignatureEvent *event = + [[MIKMIDIMetaTimeSignatureEvent alloc] initWithNumerator:9 denominator:8 timeStamp:63]; + + XCTAssertTrue([event isMemberOfClass:[MIKMIDIMetaTimeSignatureEvent class]], @"MIKMIDIMetaTimeSignatureEvent initializer didn't produce expected subclass (MIKMIDIMetaTimeSignatureEvent)"); + XCTAssertEqual(event.numerator, 9, "MIKMIDIMetaTimeSignatureEvent didn't have expected numerator."); + XCTAssertEqual(event.denominator, 8, "MIKMIDIMetaTimeSignatureEvent didn't have expected denominator."); + XCTAssertEqual(event.metronomePulse, 24, "MIKMIDIMetaTimeSignatureEvent didn't have expected metronomePulse."); + XCTAssertEqual(event.thirtySecondsPerQuarterNote, 8, "MIKMIDIMetaTimeSignatureEvent didn't have expected thirtySecondsPerQuarterNote."); + XCTAssertEqual(event.metadataType, MIKMIDIMetaEventTypeTimeSignature, "MIKMIDIMetaTimeSignatureEvent didn't have expected metadataType."); + XCTAssertEqual(event.timeStamp, 63, "MIKMIDIMetaTimeSignatureEvent didn't have expected timeStamp."); + + MIKMIDIMetaTimeSignatureEvent *event2 = + [[MIKMIDIMetaTimeSignatureEvent alloc] initWithTimeSignature:MIKMIDITimeSignatureMake(3, 4) timeStamp:127]; + + XCTAssertTrue([event2 isMemberOfClass:[MIKMIDIMetaTimeSignatureEvent class]], @"MIKMIDIMetaTimeSignatureEvent initializer didn't produce expected subclass (MIKMIDIMetaTimeSignatureEvent)"); + XCTAssertEqual(event2.numerator, 3, "MIKMIDIMetaTimeSignatureEvent didn't have expected numerator."); + XCTAssertEqual(event2.denominator, 4, "MIKMIDIMetaTimeSignatureEvent didn't have expected denominator."); + XCTAssertEqual(event2.metronomePulse, 24, "MIKMIDIMetaTimeSignatureEvent didn't have expected metronomePulse."); + XCTAssertEqual(event2.thirtySecondsPerQuarterNote, 8, "MIKMIDIMetaTimeSignatureEvent didn't have expected thirtySecondsPerQuarterNote."); + XCTAssertEqual(event2.metadataType, MIKMIDIMetaEventTypeTimeSignature, "MIKMIDIMetaTimeSignatureEvent didn't have expected metadataType."); + XCTAssertEqual(event2.timeStamp, 127, "MIKMIDIMetaTimeSignatureEvent didn't have expected timeStamp."); +} + +- (void)testMetaKeySignatureEventInit +{ + MIKMIDIMetaKeySignatureEvent *event = + [[MIKMIDIMetaKeySignatureEvent alloc] initWithMusicalKey:MIKMIDIMusicalKeyFMinor timeStamp:127]; + + XCTAssertTrue([event isMemberOfClass:[MIKMIDIMetaKeySignatureEvent class]], @"MIKMIDIMetaKeySignatureEvent initializer didn't produce expected subclass (MIKMIDIMetaKeySignatureEvent)"); + XCTAssertEqual(event.numberOfFlatsAndSharps, -4, "MIKMIDIMetaKeySignatureEvent didn't have expected numerator."); + XCTAssertEqual(event.scale, MIKMIDIMusicalScaleMinor, "MIKMIDIMetaKeySignatureEvent didn't have expected denominator."); + XCTAssertEqual(event.metadataType, MIKMIDIMetaEventTypeKeySignature, "MIKMIDIMetaKeySignatureEvent didn't have expected metadataType."); + XCTAssertEqual(event.timeStamp, 127, "MIKMIDIMetaKeySignatureEvent didn't have expected timeStamp."); +} + +@end diff --git a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m index 9e6a8613..49c3fc11 100644 --- a/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m +++ b/Framework/MIKMIDI Tests/MIKMIDISequenceTests.m @@ -79,7 +79,7 @@ - (void)testKVOForAddingATrack if ([change[NSKeyValueChangeNewKey] count] != 1) return NO; return YES; }]; - MIKMIDITrack *firstTrack = [self.sequence addTrack]; + MIKMIDITrack *firstTrack = [self.sequence addTrackWithError:NULL]; XCTAssertNotNil(firstTrack, @"Creating an MIKMIDITrack failed."); XCTAssertEqual(self.sequence.tracks.count, 1, @"MIKMIDISequence's tracks count was incorrect after adding a track."); [self waitForExpectationsWithTimeout:0.1 handler:^(NSError *error) { @@ -89,9 +89,9 @@ - (void)testKVOForAddingATrack - (void)testKVOForRemovingATrack { - MIKMIDITrack *firstTrack = [self.sequence addTrack]; + MIKMIDITrack *firstTrack = [self.sequence addTrackWithError:NULL]; XCTAssertNotNil(firstTrack, @"Creating an MIKMIDITrack failed."); - MIKMIDITrack *secondTrack = [self.sequence addTrack]; + MIKMIDITrack *secondTrack = [self.sequence addTrackWithError:NULL]; XCTAssertNotNil(secondTrack, @"Creating an MIKMIDITrack failed."); [self keyValueObservingExpectationForObject:self.sequence keyPath:@"tracks" handler:^BOOL(MIKMIDISequence *sequence, NSDictionary *change) { @@ -113,11 +113,11 @@ - (void)testKVOForRemovingATrack - (void)testLength { - MIKMIDITrack *firstTrack = [self.sequence addTrack]; + MIKMIDITrack *firstTrack = [self.sequence addTrackWithError:NULL]; XCTAssertNotNil(firstTrack, @"Creating an MIKMIDITrack failed."); - MIKMIDITrack *secondTrack = [self.sequence addTrack]; + MIKMIDITrack *secondTrack = [self.sequence addTrackWithError:NULL]; XCTAssertNotNil(secondTrack, @"Creating an MIKMIDITrack failed."); - MIKMIDITrack *thirdTrack = [self.sequence addTrack]; + MIKMIDITrack *thirdTrack = [self.sequence addTrackWithError:NULL]; XCTAssertNotNil(thirdTrack, @"Creating an MIKMIDITrack failed."); self.sequence.length = MIKMIDISequenceLongestTrackLength; diff --git a/Framework/MIKMIDI.xcodeproj/project.pbxproj b/Framework/MIKMIDI.xcodeproj/project.pbxproj index 82d4a2f0..6a478ab4 100644 --- a/Framework/MIKMIDI.xcodeproj/project.pbxproj +++ b/Framework/MIKMIDI.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 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 */; }; + 9D0225311CC92ECF0090EAB4 /* MIKMIDIMetaEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D0225301CC92ECF0090EAB4 /* MIKMIDIMetaEventTests.m */; }; 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, ); }; }; @@ -266,8 +267,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 */; }; + 9DB8CD421BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB8CD401BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9DB8CD431BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = 9DB8CD401BF266FE00F6388D /* MIKMIDIMetaEvent_SubclassMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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, ); }; }; @@ -362,6 +363,7 @@ 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 = ""; }; + 9D0225301CC92ECF0090EAB4 /* MIKMIDIMetaEventTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIKMIDIMetaEventTests.m; 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 = ""; }; @@ -538,6 +540,7 @@ 9D1D9C241BF542BB001377F7 /* MIKMIDICommandTests.m */, 9D4DF14C1AAB57800065F004 /* MIKMIDISequenceTests.m */, 9D4DF1531AAB60490065F004 /* MIKMIDITrackTests.m */, + 9D0225301CC92ECF0090EAB4 /* MIKMIDIMetaEventTests.m */, 9DCDDB591AB3514100F8347E /* MIKMIDISequencerTests.m */, 9D2ED25E1AFBD062000325CC /* MIKMIDIResponderChainTests.m */, 9D4DF13C1AAB57430065F004 /* Supporting Files */, @@ -1161,6 +1164,7 @@ files = ( 9D1D9C251BF542BB001377F7 /* MIKMIDICommandTests.m in Sources */, 9DCDDB5A1AB3514100F8347E /* MIKMIDISequencerTests.m in Sources */, + 9D0225311CC92ECF0090EAB4 /* MIKMIDIMetaEventTests.m in Sources */, 9D4DF13F1AAB57430065F004 /* MIKMIDI_Tests.m in Sources */, 9D2ED25F1AFBD062000325CC /* MIKMIDIResponderChainTests.m in Sources */, 9D4DF14D1AAB57800065F004 /* MIKMIDISequenceTests.m in Sources */, diff --git a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme index 92f0132b..8cfc6f80 100644 --- a/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme +++ b/Framework/MIKMIDI.xcodeproj/xcshareddata/xcschemes/MIKMIDI-iOS.xcscheme @@ -28,7 +28,26 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + diff --git a/Framework/module.modulemap b/Framework/module.modulemap index 0dd65af2..d4382349 100644 --- a/Framework/module.modulemap +++ b/Framework/module.modulemap @@ -17,6 +17,11 @@ framework module MIKMIDI { export * } + explicit module MIKMIDIMetaEventSubclass { + header "MIKMIDIMetaEvent_SubclassMethods.h" + export * + } + explicit module MIKMIDISynthesizerSubclass { header "MIKMIDISynthesizer_SubclassMethods.h" export * diff --git a/MIKMIDI.podspec b/MIKMIDI.podspec index 3fdab359..95fa08d7 100644 --- a/MIKMIDI.podspec +++ b/MIKMIDI.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'MIKMIDI' - s.version = '1.5.0' + s.version = '1.6.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 diff --git a/README.md b/README.md index 209573ae..ad0f40e2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 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 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 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 versions of our DJ app, [Flow](http://flowdjsoftware.com), our flagship app [Mixed In Key](http://www.mixedinkey.com/), and our composition software, [Odesi](http://odesi.mixedinkey.com). 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. diff --git a/Source/MIKMIDIClientDestinationEndpoint.m b/Source/MIKMIDIClientDestinationEndpoint.m index 0d09adb8..60d48696 100644 --- a/Source/MIKMIDIClientDestinationEndpoint.m +++ b/Source/MIKMIDIClientDestinationEndpoint.m @@ -88,9 +88,10 @@ void MIKMIDIDestinationReadProc(const MIDIPacketList *pktList, void *readProcRef NSMutableArray *receivedCommands = [NSMutableArray array]; MIDIPacket *packet = (MIDIPacket *)pktList->packet; for (int i=0; inumPackets; i++) { - if (packet->length == 0) continue; - NSArray *commands = [MIKMIDICommand commandsWithMIDIPacket:packet]; - if (commands) [receivedCommands addObjectsFromArray:commands]; + if (packet->length > 0) { + NSArray *commands = [MIKMIDICommand commandsWithMIDIPacket:packet]; + if (commands) [receivedCommands addObjectsFromArray:commands]; + } packet = MIDIPacketNext(packet); } diff --git a/Source/MIKMIDICommand.m b/Source/MIKMIDICommand.m index 0969b510..b1e32b59 100644 --- a/Source/MIKMIDICommand.m +++ b/Source/MIKMIDICommand.m @@ -113,8 +113,10 @@ - (id)initWithMIDIPacket:(MIDIPacket *)packet 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]; + NSInteger length = MIKMIDIStandardLengthOfMessageForCommandType(commandType); + if (length <= 0) { length = 3; }; + self.internalData = [NSMutableData dataWithLength:length]; ((UInt8 *)[self.internalData mutableBytes])[0] = commandType; } } diff --git a/Source/MIKMIDIDeviceManager.m b/Source/MIKMIDIDeviceManager.m index 4acde537..41ac3298 100644 --- a/Source/MIKMIDIDeviceManager.m +++ b/Source/MIKMIDIDeviceManager.m @@ -381,11 +381,10 @@ - (NSArray *)virtualSources { return [self.internalVirtualSources copy]; } - (void)addInternalVirtualSourcesObject:(MIKMIDISourceEndpoint *)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"]; + NSUInteger index = [self.internalVirtualSources count]; + [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualSources"]; + [self.internalVirtualSources insertObject:source atIndex:index]; + [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualSources"]; } - (void)removeInternalVirtualSourcesObject:(MIKMIDISourceEndpoint *)source @@ -403,11 +402,10 @@ - (NSArray *)virtualDestinations { return [self.internalVirtualDestinations copy - (void)addInternalVirtualDestinationsObject:(MIKMIDIDestinationEndpoint *)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"]; + NSUInteger index = [self.internalVirtualDestinations count]; + [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualDestinations"]; + [self.internalVirtualDestinations insertObject:destination atIndex:index]; + [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:[NSIndexSet indexSetWithIndex:index] forKey:@"virtualDestinations"]; } - (void)removeInternalVirtualDestinationsObject:(MIKMIDIDestinationEndpoint *)destination diff --git a/Source/MIKMIDIErrors.h b/Source/MIKMIDIErrors.h index 9b32ac64..a6b035d0 100644 --- a/Source/MIKMIDIErrors.h +++ b/Source/MIKMIDIErrors.h @@ -45,6 +45,13 @@ typedef NS_ENUM(NSInteger, MIKMIDIErrorCode) { */ MIKMIDIMappingIncorrectFileExtensionErrorCode, + /** + * An error ocurred while creating a new track in an MIKMIDISequence. + * The error's info dictionary may contain an underlying error with + * more informationin for its NSUnderlyingErrorKey. + */ + MIKMIDISequenceAddTrackFailedErrorCode, + /** * An error ocurred during an operation on event(s) in an MIKMIDITrack * because the event(s) could not be found in the track. diff --git a/Source/MIKMIDIEvent.h b/Source/MIKMIDIEvent.h index 81c5118f..105737d1 100644 --- a/Source/MIKMIDIEvent.h +++ b/Source/MIKMIDIEvent.h @@ -16,7 +16,6 @@ * * @note These are similar, but do not directly correspond to the values of MusicEventType */ - typedef NS_ENUM(NSUInteger, MIKMIDIEventType) { MIKMIDIEventTypeNULL = kMusicEventType_NULL, @@ -71,25 +70,6 @@ typedef NS_ENUM(NSUInteger, MIKMIDIChannelEventType) MIKMIDIChannelEventTypePitchBendChange = 0xE0, }; -typedef NS_ENUM(NSUInteger, MIKMIDIMetaEventTypeType) -{ - MIKMIDIMetaEventTypeSequenceNumber = 0x00, - MIKMIDIMetaEventTypeTextEvent = 0x01, - MIKMIDIMetaEventTypeCopyrightNotice = 0x02, - MIKMIDIMetaEventTypeTrackSequenceName = 0x03, - MIKMIDIMetaEventTypeInstrumentName = 0x04, - MIKMIDIMetaEventTypeLyricText = 0x05, - MIKMIDIMetaEventTypeMarkerText = 0x06, - MIKMIDIMetaEventTypeCuePoint = 0x07, - MIKMIDIMetaEventTypeMIDIChannelPrefix = 0x20, - MIKMIDIMetaEventTypeEndOfTrack = 0x2F, - MIKMIDIMetaEventTypeTempoSetting = 0x51, - MIKMIDIMetaEventTypeSMPTEOffset = 0x54, - MIKMIDIMetaEventTypeTimeSignature = 0x58, - MIKMIDIMetaEventTypeKeySignature = 0x59, - MIKMIDIMetaEventTypeSequencerSpecificEvent = 0x7F -}; - NS_ASSUME_NONNULL_BEGIN /** diff --git a/Source/MIKMIDIEvent.m b/Source/MIKMIDIEvent.m index 0b125734..650732c2 100644 --- a/Source/MIKMIDIEvent.m +++ b/Source/MIKMIDIEvent.m @@ -8,6 +8,7 @@ #import "MIKMIDIEvent.h" #import "MIKMIDIEvent_SubclassMethods.h" +#import "MIKMIDIMetaEvent.h" #import "MIKMIDIUtilities.h" #if !__has_feature(objc_arc) @@ -144,7 +145,6 @@ + (void)cacheSubclassesByEvent + (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, ^{ @@ -154,22 +154,6 @@ + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType @(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), @@ -184,8 +168,8 @@ + (MIKMIDIEventType)mikEventTypeForMusicEventType:(MusicEventType)musicEventType }); if (musicEventType == kMusicEventType_Meta) { - UInt8 metaEventType = *(UInt8 *)[data bytes]; - return [metaTypeToMIDITypeMap[@(metaEventType)] unsignedIntegerValue]; + MIKMIDIMetaEventType metaEventType = *(MIKMIDIMetaEventType *)[data bytes]; + return [MIKMIDIMetaEvent eventTypeForMetaSubtype:metaEventType]; } else if (musicEventType == kMusicEventType_MIDIChannelMessage) { UInt8 channelEventType = *(UInt8 *)[data bytes] & 0xF0; return [channelEventTypeToMIDITypeMap[@(channelEventType)] unsignedIntegerValue]; diff --git a/Source/MIKMIDIInputPort.m b/Source/MIKMIDIInputPort.m index 36644759..46d2be41 100644 --- a/Source/MIKMIDIInputPort.m +++ b/Source/MIKMIDIInputPort.m @@ -238,9 +238,10 @@ void MIKMIDIPortReadCallback(const MIDIPacketList *pktList, void *readProcRefCon NSMutableArray *receivedCommands = [NSMutableArray array]; MIDIPacket *packet = (MIDIPacket *)pktList->packet; for (int i=0; inumPackets; i++) { - if (packet->length == 0) continue; - NSArray *commands = [MIKMIDICommand commandsWithMIDIPacket:packet]; - if (commands) [receivedCommands addObjectsFromArray:commands]; + if (packet->length > 0) { + NSArray *commands = [MIKMIDICommand commandsWithMIDIPacket:packet]; + if (commands) [receivedCommands addObjectsFromArray:commands]; + } packet = MIDIPacketNext(packet); } diff --git a/Source/MIKMIDIMetaCopyrightEvent.h b/Source/MIKMIDIMetaCopyrightEvent.h index b096106e..66c34096 100644 --- a/Source/MIKMIDIMetaCopyrightEvent.h +++ b/Source/MIKMIDIMetaCopyrightEvent.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaCopyrightEvent : MIKMIDIMetaCopyrightEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaCuePointEvent.h b/Source/MIKMIDIMetaCuePointEvent.h index d2de0268..a9afaecf 100644 --- a/Source/MIKMIDIMetaCuePointEvent.h +++ b/Source/MIKMIDIMetaCuePointEvent.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaCuePointEvent : MIKMIDIMetaCuePointEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaEvent.h b/Source/MIKMIDIMetaEvent.h index d7edec4c..6213e681 100644 --- a/Source/MIKMIDIMetaEvent.h +++ b/Source/MIKMIDIMetaEvent.h @@ -9,7 +9,39 @@ #import "MIKMIDIEvent.h" #import "MIKMIDICompilerCompatibility.h" -#define MIKMIDIEventMetadataStartOffset 8 +static const NSUInteger MIKMIDIEventMetadataStartOffset = 8; + +/** + * Subtypes of MIKMIDIMetaEvent. You should use the corresponding meta subtypes in MIKMIDIEventType when + * initializing an event with -initWithTimeStamp:midiEventType:data: or similar methods. + * + * The reason for a separate enum here, even though there is a 1 to 1 correspondence with values in + * MIKMIDIEventType is that these values are dictated by the MIDI standard, and overlap values defined + * for MusicEventType. Having these separately defined allows us to effectively "flatten" MIKMIDIEventType + * to treat meta event subtypes as first class event types. + */ +typedef NS_ENUM(UInt8, MIKMIDIMetaEventType) +{ + MIKMIDIMetaEventTypeSequenceNumber = 0x00, + MIKMIDIMetaEventTypeTextEvent = 0x01, + MIKMIDIMetaEventTypeCopyrightNotice = 0x02, + MIKMIDIMetaEventTypeTrackSequenceName = 0x03, + MIKMIDIMetaEventTypeInstrumentName = 0x04, + MIKMIDIMetaEventTypeLyricText = 0x05, + MIKMIDIMetaEventTypeMarkerText = 0x06, + MIKMIDIMetaEventTypeCuePoint = 0x07, + MIKMIDIMetaEventTypeMIDIChannelPrefix = 0x20, + MIKMIDIMetaEventTypeEndOfTrack = 0x2F, + MIKMIDIMetaEventTypeTempoSetting = 0x51, + MIKMIDIMetaEventTypeSMPTEOffset = 0x54, + MIKMIDIMetaEventTypeTimeSignature = 0x58, + MIKMIDIMetaEventTypeKeySignature = 0x59, + MIKMIDIMetaEventTypeSequencerSpecificEvent = 0x7F, + MIKMIDIMetaEventTypeInvalid = 0x66, +}; + +// For legacy compatibility. Should use MIKMIDIMetaEventType in new code. +typedef MIKMIDIMetaEventType MIKMIDIMetaEventTypeType; NS_ASSUME_NONNULL_BEGIN @@ -18,10 +50,42 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMIDIMetaEvent : MIKMIDIEvent +/** + * Can be used to get the high-level MIKMIDIEventType for an MIKMIDIMetaEventType. + * Most users of MIKMIDI should not need to use this. + * + * @param subtype An MIKMIDIMetaEventType value. + * + * @return The corresponding MIKMIDIEventType value. MIKMIDIEventTypeNULL if subtype is invalid or unknown. + */ ++ (MIKMIDIEventType)eventTypeForMetaSubtype:(MIKMIDIMetaEventType)subtype; + +/** + * Can be used to get the meta event subtype MIKMIDIMetaEventType for an MIKMIDIEventType. + * Most users of MIKMIDI should not need to use this. + * + * @param eventType An MIKMIDIEventType value. + * + * @return The corresponding MIKMIDIMetaEventType value. MIKMIDIMetaEventTypeInvalid if eventType is invalid or unknown. + */ ++ (MIKMIDIMetaEventType)metaSubtypeForEventType:(MIKMIDIEventType)eventType; + +/** + * Initializes a new MIKMIDIMetaEvent subclass with the specified data and metadataType. + * + * @param metaData An NSData containing the metadata for the event. + * @param type The type of metadata. The appropriate subclass of MIKMIDIMetaEvent will be returned depending + * on this value. If this value is invalid or unknown, a plain MIKMIDIMetaEvent instance will be returned. + * @param timeStamp The MusicTimeStamp timestamp for the event. + * + * @return An initialized instance of MIKMIDIMetaEvent or one of its subclasses. + */ +- (instancetype)initWithMetaData:(NSData *)metaData metadataType:(MIKMIDIMetaEventType)type timeStamp:(MusicTimeStamp)timeStamp; + /** * The type of metadata. See MIDIMetaEvent for more information. */ -@property (nonatomic, readonly) UInt8 metadataType; +@property (nonatomic, readonly) MIKMIDIMetaEventType metadataType; /** * The length of the metadata. See MIDIMetaEvent for more information. @@ -35,14 +99,13 @@ NS_ASSUME_NONNULL_BEGIN @end - /** * The mutable counterpart of MIKMIDIMetaEvent. */ @interface MIKMutableMIDIMetaEvent : MIKMIDIMetaEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaEvent.m b/Source/MIKMIDIMetaEvent.m index c3e56060..016ef20a 100644 --- a/Source/MIKMIDIMetaEvent.m +++ b/Source/MIKMIDIMetaEvent.m @@ -23,11 +23,77 @@ + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaEvent class]; } + (BOOL)isMutable { return NO; } + (NSData *)initialData { return [NSData dataWithBytes:&(MIDIMetaEvent){0} length:sizeof(MIDIMetaEvent)]; } +- (instancetype)initWithMetaData:(NSData *)metaData metadataType:(MIKMIDIMetaEventType)type timeStamp:(MusicTimeStamp)timeStamp +{ + MIKMIDIEventType eventType = [MIKMIDIMetaEvent eventTypeForMetaSubtype:type]; + if (eventType == MIKMIDIEventTypeNULL) { + type = 0; + eventType = MIKMIDIEventTypeMeta; + } + NSMutableData *data = [[[[self class] initialData] subdataWithRange:NSMakeRange(0, MIKMIDIEventMetadataStartOffset)] mutableCopy]; + [data appendData:metaData]; + MIDIMetaEvent *metaEvent = (MIDIMetaEvent *)[data mutableBytes]; + metaEvent->metaEventType = type; + metaEvent->dataLength = (UInt32)[metaData length]; + return [self initWithTimeStamp:timeStamp midiEventType:eventType data:data]; +} + +- (instancetype)initWithMetaData:(NSData *)metaData timeStamp:(MusicTimeStamp)timeStamp +{ + MIKMIDIEventType eventType = [[[[self class] supportedMIDIEventTypes] firstObject] unsignedIntegerValue]; + MIKMIDIMetaEventType metaType = [MIKMIDIMetaEvent metaSubtypeForEventType:eventType]; + return [self initWithMetaData:metaData metadataType:metaType timeStamp:timeStamp]; +} + - (NSString *)additionalEventDescription { return [NSString stringWithFormat:@"Metadata Type: 0x%02x, Length: %u, Data: %@", self.metadataType, (unsigned int)self.metadataLength, self.metaData]; } +#pragma mark - Public + ++ (MIKMIDIEventType)eventTypeForMetaSubtype:(MIKMIDIMetaEventType)subtype +{ + return [[self metaTypeToMIDITypeMap][@(subtype)] unsignedIntegerValue]; +} + ++ (MIKMIDIMetaEventType)metaSubtypeForEventType:(MIKMIDIEventType)eventType +{ + NSDictionary *map = [self metaTypeToMIDITypeMap]; + for (NSNumber *key in map) { + if ([map[key] isEqualToNumber:@(eventType)]) { + return [key unsignedIntegerValue]; + } + } + return MIKMIDIMetaEventTypeInvalid; +} + +#pragma mark - Private + ++ (NSDictionary *)metaTypeToMIDITypeMap +{ + static NSDictionary *metaTypeToMIDITypeMap = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + 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),}; + }); + return metaTypeToMIDITypeMap; +} + #pragma mark - Properties + (NSSet *)keyPathsForValuesAffectingInternalData @@ -35,7 +101,7 @@ + (NSSet *)keyPathsForValuesAffectingInternalData return [NSSet setWithObjects:@"metadataType", @"metadata", nil]; } -- (UInt8)metadataType +- (MIKMIDIMetaEventType)metadataType { MIDIMetaEvent *metaEvent = (MIDIMetaEvent*)[self.internalData bytes]; return metaEvent->metaEventType; @@ -85,6 +151,6 @@ @implementation MIKMutableMIDIMetaEvent @dynamic metadataType; @dynamic metaData; -+ (BOOL)isMutable { return NO; } ++ (BOOL)isMutable { return YES; } @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaEvent_SubclassMethods.h b/Source/MIKMIDIMetaEvent_SubclassMethods.h index 84d460a7..cfe2dd78 100644 --- a/Source/MIKMIDIMetaEvent_SubclassMethods.h +++ b/Source/MIKMIDIMetaEvent_SubclassMethods.h @@ -11,6 +11,19 @@ @interface MIKMIDIMetaEvent () +/** + * Initializes a new MIKMIDIMetaEvent subclass with the specified data, inferring + * the meta data type using +supportedMIDIEventTypes. Only meant to be used internally + * to more easily implement custom initializers. + * + * @param metaData An NSData containing the metadata for the event. + * on this value. If this value is invalid or unknown, a plain MIKMIDIMetaEvent instance will be returned. + * @param timeStamp The MusicTimeStamp timestamp for the event. + * + * @return An initialized instance of MIKMIDIMetaEvent or one of its subclasses. + */ +- (instancetype)initWithMetaData:(NSData *)metaData timeStamp:(MusicTimeStamp)timeStamp; + @property (nonatomic, strong, readwrite) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaInstrumentNameEvent.h b/Source/MIKMIDIMetaInstrumentNameEvent.h index 276a912b..0d4db2ec 100644 --- a/Source/MIKMIDIMetaInstrumentNameEvent.h +++ b/Source/MIKMIDIMetaInstrumentNameEvent.h @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaInstrumentNameEvent : MIKMIDIMetaInstrumentNameEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaKeySignatureEvent.h b/Source/MIKMIDIMetaKeySignatureEvent.h index 56b94107..4950c3f6 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.h +++ b/Source/MIKMIDIMetaKeySignatureEvent.h @@ -9,6 +9,45 @@ #import "MIKMIDIMetaEvent.h" #import "MIKMIDICompilerCompatibility.h" +typedef NS_ENUM(int8_t, MIKMIDIMusicalKey) { + MIKMIDIMusicalKeyCFlatMajor = -7, + MIKMIDIMusicalKeyGFlatMajor, + MIKMIDIMusicalKeyDFlatMajor, + MIKMIDIMusicalKeyAFlatMajor, + MIKMIDIMusicalKeyEFlatMajor, + MIKMIDIMusicalKeyBFlatMajor, + MIKMIDIMusicalKeyFMajor, + MIKMIDIMusicalKeyCMajor, + MIKMIDIMusicalKeyGMajor, + MIKMIDIMusicalKeyDMajor, + MIKMIDIMusicalKeyAMajor, + MIKMIDIMusicalKeyEMajor, + MIKMIDIMusicalKeyBMajor, + MIKMIDIMusicalKeyFSharpMajor, + MIKMIDIMusicalKeyCSharpMajor, + + MIKMIDIMusicalKeyAFlatMinor = MIKMIDIMusicalKeyCFlatMajor+100, + MIKMIDIMusicalKeyEFlatMinor, + MIKMIDIMusicalKeyBFlatMinor, + MIKMIDIMusicalKeyFMinor, + MIKMIDIMusicalKeyCMinor, + MIKMIDIMusicalKeyGMinor, + MIKMIDIMusicalKeyDMinor, + MIKMIDIMusicalKeyAMinor, + MIKMIDIMusicalKeyEMinor, + MIKMIDIMusicalKeyBMinor, + MIKMIDIMusicalKeyFSharpMinor, + MIKMIDIMusicalKeyCSharpMinor, + MIKMIDIMusicalKeyGSharpMinor, + MIKMIDIMusicalKeyDSharpMinor, + MIKMIDIMusicalKeyASharpMinor, +}; + +typedef NS_ENUM(UInt8, MIKMIDIMusicalScale) { + MIKMIDIMusicalScaleMajor = 0, + MIKMIDIMusicalScaleMinor = 1 +}; + NS_ASSUME_NONNULL_BEGIN /** @@ -16,16 +55,31 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMIDIMetaKeySignatureEvent : MIKMIDIMetaEvent +/** + * Initializes an instane of MIKMIDIMetaKeySignatureEvent with the specified musical key and timeStamp. + * + * @param musicalKey The musical key for the event. See MIKMIDIMusicalKey for a list of possible values. + * @param timeStamp The time stamp for the event. + * + * @return An initialized MIKMIDIMetaKeySignatureEvent instance. + */ +- (instancetype)initWithMusicalKey:(MIKMIDIMusicalKey)musicalKey timeStamp:(MusicTimeStamp)timeStamp; + +/** + * The musical key for the event. See MIKMIDIMusicalKey for a list of possible values. + */ +@property (nonatomic, readonly) MIKMIDIMusicalKey musicalKey; + /** * The key for the event. Values can be between -7 and 7 and specify * the key signature in terms of number of flats (if negative) or sharps (if positive). */ -@property (nonatomic, readonly) UInt8 key; +@property (nonatomic, readonly) int8_t numberOfFlatsAndSharps; /** * The scale for the event. A value of 0 indicates a major scale, a value of 1 indicates a minor scale. */ -@property (nonatomic, readonly) UInt8 scale; +@property (nonatomic, readonly) MIKMIDIMusicalScale scale; @end @@ -35,10 +89,33 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaKeySignatureEvent : MIKMIDIMetaKeySignatureEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; -@property (nonatomic, readwrite) UInt8 key; -@property (nonatomic, readwrite) UInt8 scale; +@property (nonatomic, readwrite) MIKMIDIMusicalKey musicalKey; +@property (nonatomic, readwrite) int8_t numberOfFlatsAndSharps; +@property (nonatomic, readwrite) MIKMIDIMusicalScale scale; + +@end + +#pragma mark - Deprecated + +@interface MIKMIDIMetaKeySignatureEvent (Deprecated) + +/** + * @deprecated: This property is deprecated, and didn't work properly in previous versions + * due to the use of a signed type. Use numberOfFlatsAndSharps, which is directly equivalent, or + * musicalKey, instead. + * + * The key for the event. Values can be between -7 and 7 and specify + * the key signature in terms of number of flats (if negative) or sharps (if positive). + */ +@property (nonatomic, readonly) UInt8 key DEPRECATED_ATTRIBUTE; + +@end + +@interface MIKMutableMIDIMetaKeySignatureEvent (Deprecated) + +@property (nonatomic, readwrite) UInt8 key DEPRECATED_ATTRIBUTE; @end diff --git a/Source/MIKMIDIMetaKeySignatureEvent.m b/Source/MIKMIDIMetaKeySignatureEvent.m index 2b5e7f08..3ca39598 100644 --- a/Source/MIKMIDIMetaKeySignatureEvent.m +++ b/Source/MIKMIDIMetaKeySignatureEvent.m @@ -23,40 +23,89 @@ + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaKeySignatureEvent c + (BOOL)isMutable { return NO; } + (NSData *)initialData { - NSMutableData *superData = [[super initialData] mutableCopy]; - [superData increaseLengthBy:2]; // Account for key and scale bytes - return [superData copy]; + NSMutableData *result = [NSMutableData dataWithBytes:&(MIDIMetaEvent){0} length:sizeof(MIDIMetaEvent)]; + MIDIMetaEvent *metaEvent = (MIDIMetaEvent *)[result mutableBytes]; + metaEvent->dataLength = 2; + metaEvent->metaEventType = MIKMIDIMetaEventTypeKeySignature; + NSInteger metaDataLength = result.length - MIKMIDIEventMetadataStartOffset; + if (metaDataLength < 2) { [result increaseLengthBy:2-metaDataLength]; } + UInt8 *metaDataBytes = (UInt8 *)[result mutableBytes] + MIKMIDIEventMetadataStartOffset; + metaDataBytes[0] = 0; // C + metaDataBytes[1] = 0; // Major + return [result copy]; } +- (instancetype)initWithMusicalKey:(MIKMIDIMusicalKey)musicalKey timeStamp:(MusicTimeStamp)timeStamp +{ + NSMutableData *metaData = [[NSMutableData alloc] init]; + int8_t scale = [self scaleForMusicalKey:musicalKey]; + UInt8 numberOfFlatsAndSharps = [self numberOfFlatsAndSharpsInMusicalKey:musicalKey]; + [metaData appendBytes:&(UInt8){numberOfFlatsAndSharps} length:1]; + [metaData appendBytes:&(int8_t){scale} length:1]; + return [self initWithMetaData:metaData timeStamp:timeStamp]; +} + +#pragma mark - Private + +- (MIKMIDIMusicalKey)musicalKeyForNumberOfFlatsAndSharps:(int8_t)flatsSharps scale:(MIKMIDIMusicalScale)scale +{ + return flatsSharps + scale * 100; +} + +- (MIKMIDIMusicalScale)scaleForMusicalKey:(MIKMIDIMusicalKey)musicalKey +{ + return musicalKey > 7 ? MIKMIDIMusicalScaleMinor : MIKMIDIMusicalScaleMajor; +} + +- (int8_t)numberOfFlatsAndSharpsInMusicalKey:(MIKMIDIMusicalKey)musicalKey +{ + MIKMIDIMusicalScale scale = [self scaleForMusicalKey:musicalKey]; + return musicalKey - scale * 100; +} + +#pragma mark - Properties + + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; - if ([key isEqualToString:@"key"] || [key isEqualToString:@"scale"]) { + if ([key isEqualToString:@"key"] || [key isEqualToString:@"scale"] || + [key isEqualToString:@"musicalKey"] || [key isEqualToString:@"numberOfFlatsAndSharps"]) { keyPaths = [keyPaths setByAddingObject:@"metaData"]; } return keyPaths; } -- (UInt8)key +- (MIKMIDIMusicalKey)musicalKey +{ + return [self musicalKeyForNumberOfFlatsAndSharps:self.numberOfFlatsAndSharps scale:self.scale]; +} + +- (void)setMusicalKey:(MIKMIDIMusicalKey)musicalKey +{ + self.scale = [self scaleForMusicalKey:musicalKey]; + self.numberOfFlatsAndSharps = [self numberOfFlatsAndSharpsInMusicalKey:musicalKey]; +} + +- (int8_t)numberOfFlatsAndSharps { - return *(UInt8*)[self.metaData bytes]; + return *(int8_t*)[self.metaData bytes]; } -- (void)setKey:(NSString *)key +- (void)setNumberOfFlatsAndSharps:(int8_t)numberOfFlatsAndSharps { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; NSMutableData *mutableMetaData = [self.metaData mutableCopy]; - [mutableMetaData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&key length:1]; + [mutableMetaData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&numberOfFlatsAndSharps length:1]; [self setMetaData:[mutableMetaData copy]]; } -- (UInt8)scale +- (MIKMIDIMusicalScale)scale { - return *((UInt8*)[self.metaData bytes] + 1); + return *((MIKMIDIMusicalScale*)[self.metaData bytes] + 1); } -- (void)setScale:(UInt8)scale +- (void)setScale:(MIKMIDIMusicalScale)scale { if (![[self class] isMutable]) return MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION; @@ -67,7 +116,7 @@ - (void)setScale:(UInt8)scale - (NSString *)additionalEventDescription { - return [NSString stringWithFormat:@"Metadata Type: 0x%02x, Key: %d, Scale %d", self.metadataType, self.key, self.scale]; + return [NSString stringWithFormat:@"Metadata Type: 0x%02x, Key: %d, Scale %d", self.metadataType, self.numberOfFlatsAndSharps, self.scale]; } @end @@ -77,9 +126,19 @@ @implementation MIKMutableMIDIMetaKeySignatureEvent @dynamic timeStamp; @dynamic metadataType; @dynamic metaData; -@dynamic key; +@dynamic musicalKey; +@dynamic numberOfFlatsAndSharps; @dynamic scale; + (BOOL)isMutable { return YES; } +@end + +#pragma mark - + +@implementation MIKMIDIMetaKeySignatureEvent (Deprecated) + +- (UInt8)key { return self.numberOfFlatsAndSharps; } +- (void)setKey:(UInt8)key { self.numberOfFlatsAndSharps = key; } + @end \ No newline at end of file diff --git a/Source/MIKMIDIMetaLyricEvent.h b/Source/MIKMIDIMetaLyricEvent.h index ae3fe3cb..29f557be 100644 --- a/Source/MIKMIDIMetaLyricEvent.h +++ b/Source/MIKMIDIMetaLyricEvent.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaLyricEvent : MIKMIDIMetaLyricEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaMarkerTextEvent.h b/Source/MIKMIDIMetaMarkerTextEvent.h index 2a47a757..bc1e49ad 100644 --- a/Source/MIKMIDIMetaMarkerTextEvent.h +++ b/Source/MIKMIDIMetaMarkerTextEvent.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaMarkerTextEvent : MIKMIDIMetaMarkerTextEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaSequenceEvent.h b/Source/MIKMIDIMetaSequenceEvent.h index 2ea9b8e2..ee446d20 100644 --- a/Source/MIKMIDIMetaSequenceEvent.h +++ b/Source/MIKMIDIMetaSequenceEvent.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaSequenceEvent : MIKMIDIMetaSequenceEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @end diff --git a/Source/MIKMIDIMetaTextEvent.h b/Source/MIKMIDIMetaTextEvent.h index 3c1db46a..e603504e 100644 --- a/Source/MIKMIDIMetaTextEvent.h +++ b/Source/MIKMIDIMetaTextEvent.h @@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMIDIMetaTextEvent : MIKMIDIMetaEvent +- (instancetype)initWithString:(NSString *)string timeStamp:(MusicTimeStamp)timeStamp; + /** * The text for the event. */ @@ -29,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @interface MIKMutableMIDIMetaTextEvent : MIKMIDIMetaTextEvent @property (nonatomic, readwrite) MusicTimeStamp timeStamp; -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @property (nonatomic, copy, readwrite, nullable) NSString *string; diff --git a/Source/MIKMIDIMetaTextEvent.m b/Source/MIKMIDIMetaTextEvent.m index 39c085fc..f2e60383 100644 --- a/Source/MIKMIDIMetaTextEvent.m +++ b/Source/MIKMIDIMetaTextEvent.m @@ -22,6 +22,12 @@ + (Class)immutableCounterpartClass { return [MIKMIDIMetaTextEvent class]; } + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTextEvent class]; } + (BOOL)isMutable { return NO; } +- (instancetype)initWithString:(NSString *)string timeStamp:(MusicTimeStamp)timeStamp +{ + NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; + return [self initWithMetaData:data timeStamp:timeStamp]; +} + + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; @@ -50,7 +56,6 @@ - (NSString *)additionalEventDescription @end - @implementation MIKMutableMIDIMetaTextEvent @dynamic timeStamp; diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.h b/Source/MIKMIDIMetaTimeSignatureEvent.h index a4692dd7..f3f9682e 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.h +++ b/Source/MIKMIDIMetaTimeSignatureEvent.h @@ -9,6 +9,34 @@ #import "MIKMIDIMetaEvent.h" #import "MIKMIDICompilerCompatibility.h" +/** + * Represents a time signature. Note that in contrast to time signature events in raw MIDI, + * the denominator here is the "natural" denominator for the time signature. e.g. 4/4 time + * is represented with a numerator of 4 and denominator of 4. + */ +typedef struct { + UInt8 numerator; /// The number of beats per measure. + UInt8 denominator; // The fraction of a whole note per beat (e.g. 4 here means a quarter note per beat) +} MIKMIDITimeSignature; + +/** + * Convenience function to create a MIDITimeSignature struct. + * + * For example, to create a time signature struct for 4/4 time: + * MIKMIDITimeSignatureMake(4, 4) + * + * @param numerator The numerator for the time signature, or number of beats per measure. + * @param denominator The denominator for the time signature, or fraction of a note per beat. + * + * @return An MIKMIDITimeSignature struct. + */ +NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 denominator) { + MIKMIDITimeSignature ts; + ts.numerator = numerator; + ts.denominator = denominator; + return ts; +} + NS_ASSUME_NONNULL_BEGIN /** @@ -16,6 +44,30 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMIDIMetaTimeSignatureEvent : MIKMIDIMetaEvent +/** + * Initializes an MIKMIDIMetaTimeSignatureEvent with the specified time signature. + * + * @param signature An MIKMIDITimeSignature value. + * @param timeStamp The time stamp for the event. + * + * @return An initialized MIKMIDIMetaTimeSignatureEvent instance. + */ +- (instancetype)initWithTimeSignature:(MIKMIDITimeSignature)signature timeStamp:(MusicTimeStamp)timeStamp; + +/** + * Initializes an MIKMIDIMetaTimeSignatureEvent with the specified time signature numerator and denominator. + * + * Instances initialized with this initializer use the default values for metronomePulse (24) + * and thirtySecondsPerQuarterNote (8). + * + * @param numerator The numerator for the time signature, or number of beats per measure. + * @param denominator The denominator for the time signature, or fraction of a note per beat. + * @param timeStamp The time stamp for the event. + * + * @return An initialized MIKMIDIMetaTimeSignatureEvent instance. + */ +- (instancetype)initWithNumerator:(UInt8)numerator denominator:(UInt8)denominator timeStamp:(MusicTimeStamp)timeStamp; + /** * The numerator of the time signature. */ @@ -43,7 +95,7 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMutableMIDIMetaTimeSignatureEvent : MIKMIDIMetaTimeSignatureEvent -@property (nonatomic, readwrite) UInt8 metadataType; +@property (nonatomic, readwrite) MIKMIDIMetaEventType metadataType; @property (nonatomic, strong, readwrite, null_resettable) NSData *metaData; @property (nonatomic, readwrite) MusicTimeStamp timeStamp; @property (nonatomic, readwrite) UInt8 numerator; diff --git a/Source/MIKMIDIMetaTimeSignatureEvent.m b/Source/MIKMIDIMetaTimeSignatureEvent.m index 16ff81ba..2cc6dcf0 100644 --- a/Source/MIKMIDIMetaTimeSignatureEvent.m +++ b/Source/MIKMIDIMetaTimeSignatureEvent.m @@ -23,9 +23,35 @@ + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTimeSignatureEvent + (BOOL)isMutable { return NO; } + (NSData *)initialData { - NSMutableData *superData = [[super initialData] mutableCopy]; - [superData increaseLengthBy:2]; // Account for numerator, denominator, metronome, and 32nd note bytes - return [superData copy]; + NSMutableData *result = [NSMutableData dataWithBytes:&(MIDIMetaEvent){0} length:sizeof(MIDIMetaEvent)]; + MIDIMetaEvent *metaEvent = (MIDIMetaEvent *)[result mutableBytes]; + metaEvent->dataLength = 4; + metaEvent->metaEventType = MIKMIDIMetaEventTypeTimeSignature; + NSInteger metaDataLength = result.length - MIKMIDIEventMetadataStartOffset; + if (metaDataLength < 4) { [result increaseLengthBy:4-metaDataLength]; } + UInt8 *metaDataBytes = (UInt8 *)[result mutableBytes] + MIKMIDIEventMetadataStartOffset; + metaDataBytes[0] = 4; // numerator + metaDataBytes[1] = 2; // denominator + metaDataBytes[2] = 24; // metronomePulse + metaDataBytes[3] = 8;// thirtySecondsPerQuarterNote + return [result copy]; +} + +- (instancetype)initWithTimeSignature:(MIKMIDITimeSignature)signature timeStamp:(MusicTimeStamp)timeStamp +{ + return [self initWithNumerator:signature.numerator denominator:signature.denominator timeStamp:timeStamp]; +} + +- (instancetype)initWithNumerator:(UInt8)numerator denominator:(UInt8)denominator timeStamp:(MusicTimeStamp)timeStamp +{ + NSMutableData *metaData = [NSMutableData data]; + [metaData appendBytes:&numerator length:1]; + UInt8 denominatorPower = log2(denominator); + [metaData appendBytes:&denominatorPower length:1]; + [metaData appendBytes:&(UInt8){24} length:1]; // metronomePulse + [metaData appendBytes:&(UInt8){8} length:1]; // thirtySecondsPerQuarterNote + + return [self initWithMetaData:metaData timeStamp:timeStamp]; } + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.h b/Source/MIKMIDIMetaTrackSequenceNameEvent.h index a037792a..5437b28d 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.h +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.h @@ -16,6 +16,8 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MIKMIDIMetaTrackSequenceNameEvent : MIKMIDIMetaTextEvent +- (instancetype)initWithName:(NSString *)name timeStamp:(MusicTimeStamp)timeStamp; + @property (nonatomic, readonly, nullable) NSString *name; @end diff --git a/Source/MIKMIDIMetaTrackSequenceNameEvent.m b/Source/MIKMIDIMetaTrackSequenceNameEvent.m index e483a225..52203b21 100644 --- a/Source/MIKMIDIMetaTrackSequenceNameEvent.m +++ b/Source/MIKMIDIMetaTrackSequenceNameEvent.m @@ -22,11 +22,12 @@ + (Class)immutableCounterpartClass { return [MIKMIDIMetaTrackSequenceNameEvent c + (Class)mutableCounterpartClass { return [MIKMutableMIDIMetaTrackSequenceNameEvent class]; } + (BOOL)isMutable { return NO; } -+ (NSSet *)keyPathsForValuesAffectingName +- (instancetype)initWithName:(NSString *)name timeStamp:(MusicTimeStamp)timeStamp { - return [NSSet setWithObjects:@"string", nil]; + return [self initWithString:name timeStamp:timeStamp]; } ++ (NSSet *)keyPathsForValuesAffectingName { return [NSSet setWithObject:@"string"]; } - (NSString *)name { return self.string; } @end diff --git a/Source/MIKMIDIMetronome.m b/Source/MIKMIDIMetronome.m index 7f0c1362..946935db 100644 --- a/Source/MIKMIDIMetronome.m +++ b/Source/MIKMIDIMetronome.m @@ -22,7 +22,7 @@ + (AudioComponentDescription)appleSynthComponentDescription AudioComponentDescription instrumentcd = (AudioComponentDescription){0}; instrumentcd.componentManufacturer = kAudioUnitManufacturer_Apple; instrumentcd.componentType = kAudioUnitType_MusicDevice; - instrumentcd.componentSubType = kAudioUnitSubType_MIDISynth; + instrumentcd.componentSubType = kAudioUnitSubType_Sampler; return instrumentcd; } #endif diff --git a/Source/MIKMIDINoteEvent.h b/Source/MIKMIDINoteEvent.h index 19e28d04..d43fd0a3 100644 --- a/Source/MIKMIDINoteEvent.h +++ b/Source/MIKMIDINoteEvent.h @@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) Float32 duration; /** - * The time stamp at the end of the notes duration. + * The time stamp at the end of the notes duration. This is simply the event's timeStamp + duration. */ @property (nonatomic, readonly) MusicTimeStamp endTimeStamp; diff --git a/Source/MIKMIDINoteEvent.m b/Source/MIKMIDINoteEvent.m index 1ecdce21..1678a212 100644 --- a/Source/MIKMIDINoteEvent.m +++ b/Source/MIKMIDINoteEvent.m @@ -185,7 +185,7 @@ + (NSArray *)commandsFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIC + (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock { - MIKMutableMIDINoteOnCommand *noteOn = [MIKMutableMIDINoteOnCommand commandForCommandType:MIKMIDICommandTypeNoteOn]; + MIKMutableMIDINoteOnCommand *noteOn = [[MIKMutableMIDINoteOnCommand alloc] init]; noteOn.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:noteEvent.timeStamp]; noteOn.channel = noteEvent.channel; noteOn.note = noteEvent.note; @@ -195,7 +195,7 @@ + (MIKMIDINoteOnCommand *)noteOnCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEve +(MIKMIDINoteOffCommand *)noteOffCommandFromNoteEvent:(MIKMIDINoteEvent *)noteEvent clock:(MIKMIDIClock *)clock { - MIKMutableMIDINoteOffCommand *noteOff = [MIKMutableMIDINoteOffCommand commandForCommandType:MIKMIDICommandTypeNoteOff]; + MIKMutableMIDINoteOffCommand *noteOff = [[MIKMutableMIDINoteOffCommand alloc] init]; noteOff.midiTimestamp = [clock midiTimeStampForMusicTimeStamp:noteEvent.endTimeStamp]; noteOff.channel = noteEvent.channel; noteOff.note = noteEvent.note; diff --git a/Source/MIKMIDINoteOffCommand.h b/Source/MIKMIDINoteOffCommand.h index 1685a9ee..11bfd662 100644 --- a/Source/MIKMIDINoteOffCommand.h +++ b/Source/MIKMIDINoteOffCommand.h @@ -31,6 +31,21 @@ NS_ASSUME_NONNULL_BEGIN channel:(UInt8)channel timestamp:(nullable NSDate *)timestamp; +/** + * 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 MIDITimeStamp for the command. + * + * @return An initialized MIKMIDINoteOffCommand instance. + */ ++ (instancetype)noteOffCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + midiTimeStamp:(MIDITimeStamp)timestamp; + /** * The note number for the message. In the range 0-127. */ diff --git a/Source/MIKMIDINoteOffCommand.m b/Source/MIKMIDINoteOffCommand.m index a74eb0b3..0d123d02 100644 --- a/Source/MIKMIDINoteOffCommand.m +++ b/Source/MIKMIDINoteOffCommand.m @@ -42,6 +42,21 @@ + (instancetype)noteOffCommandWithNote:(NSUInteger)note return [self isMutable] ? result : [result copy]; } ++ (instancetype)noteOffCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + midiTimeStamp:(MIDITimeStamp)timestamp +{ + MIKMutableMIDINoteOffCommand *result = [[MIKMutableMIDINoteOffCommand alloc] init]; + result.note = note; + result.velocity = velocity; + result.channel = channel; + result.midiTimestamp = timestamp; + + 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 a5352886..0b57700d 100644 --- a/Source/MIKMIDINoteOnCommand.h +++ b/Source/MIKMIDINoteOnCommand.h @@ -31,6 +31,23 @@ NS_ASSUME_NONNULL_BEGIN channel:(UInt8)channel timestamp:(nullable NSDate *)timestamp; + +/** + * 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 MIDITimestamp for the command. + * + * @return An initialized MIKMIDINoteOnCommand instance. + */ ++ (instancetype)noteOnCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + midiTimeStamp:(MIDITimeStamp)timestamp; + + /** * The note number for the message. In the range 0-127. */ diff --git a/Source/MIKMIDINoteOnCommand.m b/Source/MIKMIDINoteOnCommand.m index 288cd374..99cfb452 100644 --- a/Source/MIKMIDINoteOnCommand.m +++ b/Source/MIKMIDINoteOnCommand.m @@ -42,6 +42,20 @@ + (instancetype)noteOnCommandWithNote:(NSUInteger)note return [self isMutable] ? result : [result copy]; } ++ (instancetype)noteOnCommandWithNote:(NSUInteger)note + velocity:(NSUInteger)velocity + channel:(UInt8)channel + midiTimeStamp:(MIDITimeStamp)timestamp +{ + MIKMutableMIDINoteOnCommand *result = [[MIKMutableMIDINoteOnCommand alloc] init]; + result.note = note; + result.velocity = velocity; + result.channel = channel; + result.midiTimestamp = timestamp; + + return [self isMutable] ? result : [result copy]; +} + #pragma mark - Properties - (NSUInteger)note { return self.dataByte1; } diff --git a/Source/MIKMIDIPitchBendChangeCommand.m b/Source/MIKMIDIPitchBendChangeCommand.m index b1516765..00763ccc 100644 --- a/Source/MIKMIDIPitchBendChangeCommand.m +++ b/Source/MIKMIDIPitchBendChangeCommand.m @@ -46,7 +46,7 @@ - (void)setPitchChange:(UInt16)pitchChange pitchChange = MIN(pitchChange, 0x3FFF); self.dataByte1 = pitchChange & 0x007F; - self.dataByte2 = pitchChange & 0x3F80; + self.dataByte2 = (pitchChange & 0x3F80) >> 7; } @end diff --git a/Source/MIKMIDIPlayer.m b/Source/MIKMIDIPlayer.m index 76b43157..87a2c830 100644 --- a/Source/MIKMIDIPlayer.m +++ b/Source/MIKMIDIPlayer.m @@ -191,7 +191,7 @@ - (void)addClickTrackWhenNeededFromTimeStamp:(MusicTimeStamp)fromTimeStamp [clickSequence.tempoTrack addEvents:self.sequence.tempoEvents]; [clickSequence.tempoTrack addEvents:self.sequence.timeSignatureEvents]; self.clickPlayer.sequence = clickSequence; - MIKMIDITrack *clickTrack = [clickSequence addTrack]; + MIKMIDITrack *clickTrack = [clickSequence addTrackWithError:NULL]; OSStatus err = MusicTrackSetDestMIDIEndpoint(clickTrack.musicTrack, (MIDIEndpointRef)self.metronomeEndpoint.objectRef); if (err) { diff --git a/Source/MIKMIDISequence.h b/Source/MIKMIDISequence.h index 80bf03a8..0f942042 100644 --- a/Source/MIKMIDISequence.h +++ b/Source/MIKMIDISequence.h @@ -9,18 +9,7 @@ #import #import #import "MIKMIDICompilerCompatibility.h" - -typedef struct { - UInt8 numerator; - UInt8 denominator; -} MIKMIDITimeSignature; // Deprecated - -NS_INLINE MIKMIDITimeSignature MIKMIDITimeSignatureMake(UInt8 numerator, UInt8 denominator) { - MIKMIDITimeSignature ts; - ts.numerator = numerator; - ts.denominator = denominator; - return ts; -} +#import "MIKMIDIMetaTimeSignatureEvent.h" @class MIKMIDITrack; @class MIKMIDISequencer; @@ -160,10 +149,22 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Track Management +/** + * Creates and adds a new MIDI track to the sequence. + * + * @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 The newly created track, or nil if an error occurred. + */ +- (nullable MIKMIDITrack *)addTrackWithError:(NSError **)error; + /** * Creates and adds a new MIDI track to the sequence. May return nil if an error occurs. + * + * @deprecated: This method is deprecated. You should use -addTrackWithError: instead. */ -- (nullable MIKMIDITrack *)addTrack; +- (nullable MIKMIDITrack *)addTrack DEPRECATED_ATTRIBUTE NS_SWIFT_UNAVAILABLE("Use the error throwing variant instead."); /** * Removes the specified MIDI track from the sequence. diff --git a/Source/MIKMIDISequence.m b/Source/MIKMIDISequence.m index fbff1192..054438bb 100644 --- a/Source/MIKMIDISequence.m +++ b/Source/MIKMIDISequence.m @@ -15,6 +15,7 @@ #import "MIKMIDIDestinationEndpoint.h" #import "MIKMIDISequence+MIKMIDIPrivate.h" #import "MIKMIDISequencer+MIKMIDIPrivate.h" +#import "MIKMIDIErrors.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 MIKMIDISequence.m in the Build Phases for this target @@ -43,19 +44,19 @@ @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 %@ 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 %@ in %s.", @(err), __PRETTY_FUNCTION__); + return nil; + } + + return [self initWithMusicSequence:sequence error:NULL]; } + (instancetype)sequenceWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; @@ -75,7 +76,8 @@ - (instancetype)initWithFileAtURL:(NSURL *)fileURL error:(NSError **)error; - (instancetype)initWithFileAtURL:(NSURL *)fileURL convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error { - NSData *data = [NSData dataWithContentsOfURL:fileURL options:0 error:error]; + NSData *data = [NSData dataWithContentsOfURL:fileURL options:0 error:error]; + if (!data) return nil; return [self initWithData:data convertMIDIChannelsToTracks:convertMIDIChannelsToTracks error:error]; } @@ -96,24 +98,24 @@ - (instancetype)initWithData:(NSData *)data error:(NSError **)error - (instancetype)initWithData:(NSData *)data convertMIDIChannelsToTracks:(BOOL)convertMIDIChannelsToTracks error:(NSError **)error { - error = error ? error : &(NSError *__autoreleasing){ nil }; + error = error ?: &(NSError *__autoreleasing){ nil }; - MusicSequence sequence; - OSStatus err = NewMusicSequence(&sequence); - if (err) { - NSLog(@"NewMusicSequence() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + MusicSequence sequence; + OSStatus err = NewMusicSequence(&sequence); + if (err) { + NSLog(@"NewMusicSequence() failed with error %@ 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 %@ in %s.", @(err), __PRETTY_FUNCTION__); + if (err) { + NSLog(@"MusicSequenceFileLoadData() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; - return nil; - } - + return nil; + } + return [self initWithMusicSequence:sequence error:error]; } @@ -175,14 +177,14 @@ - (void)dealloc NSArray *tracks = self.internalTracks; self.internalTracks = nil; // Unregister for KVO [self setCallBackBlock:^(MIKMIDITrack *t, MusicTimeStamp ts, const MusicEventUserData *ud, MusicTimeStamp ts2, MusicTimeStamp ts3) {}]; - + 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__); + + OSStatus err = DisposeMusicSequence(_musicSequence); + if (err) NSLog(@"DisposeMusicSequence() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); } #pragma mark - Sequencer Synchronization @@ -190,7 +192,7 @@ - (void)dealloc - (void)dispatchSyncToSequencerProcessingQueueAsNeeded:(void (^)())block { if (!block) return; - + MIKMIDISequencer *sequencer = self.sequencer; if (sequencer) { [sequencer dispatchSyncToProcessingQueueAsNeeded:block]; @@ -201,86 +203,98 @@ - (void)dispatchSyncToSequencerProcessingQueueAsNeeded:(void (^)())block #pragma mark - Adding and Removing Tracks -- (MIKMIDITrack *)addTrack +- (MIKMIDITrack *)addTrackWithError:(NSError **)error { - __block MIKMIDITrack *track; - + __block MIKMIDITrack *track = nil; + error = error ?: &(NSError *__autoreleasing){ nil }; + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ MusicTrack musicTrack; OSStatus err = MusicSequenceNewTrack(self.musicSequence, &musicTrack); - if (err) { NSLog(@"MusicSequenceNewTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); }; - + if (err) { + NSLog(@"MusicSequenceNewTrack() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + NSError *underlyingError = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; + NSDictionary *userInfo = @{NSUnderlyingErrorKey : underlyingError}; + *error = [NSError MIKMIDIErrorWithCode:MIKMIDISequenceAddTrackFailedErrorCode userInfo:userInfo]; + return; + }; + track = [MIKMIDITrack trackWithSequence:self musicTrack:musicTrack]; [self insertObject:track inInternalTracksAtIndex:[self.internalTracks count]]; }]; - + return track; } +- (MIKMIDITrack *)addTrack +{ + return [self addTrackWithError:NULL]; +} + - (BOOL)removeTrack:(MIKMIDITrack *)track { if (!track) return NO; - + __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 success; + + return success; } #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 %@ 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 %@ 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 = (NSInteger)(loopedTimeStamp / length); - loopedTimeStamp -= (length * numTimesLooped); - } - - return loopedTimeStamp; + MusicTimeStamp length = self.length; + + if (loopedTimeStamp > length) { + NSInteger numTimesLooped = (NSInteger)(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 @@ -289,7 +303,7 @@ - (BOOL)setOverallTempo:(Float64)bpm [self.tempoTrack removeAllEvents]; 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 @@ -300,13 +314,13 @@ - (BOOL)setTempo:(Float64)bpm atTimeStamp:(MusicTimeStamp)timeStamp - (Float64)tempoAtTimeStamp:(MusicTimeStamp)timeStamp { - __block Float64 tempo = 0; - + __block Float64 tempo = 0; + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ NSArray *events = [self.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:0 toTimeStamp:timeStamp]; tempo = [[events lastObject] bpm]; }]; - + return tempo; } @@ -346,7 +360,7 @@ - (MIKMIDITimeSignature)timeSignatureAtTimeStamp:(MusicTimeStamp)timeStamp if (event) { result.numerator = event.numerator; result.denominator = event.denominator; -} + } return result; } @@ -354,7 +368,7 @@ - (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 @@ -365,7 +379,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } - + if ([self.internalTracks containsObject:object] && ([keyPath isEqualToString:@"length"] || [keyPath isEqualToString:@"offset"])) { [self updateLengthDefinedByTracks]; @@ -374,12 +388,12 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N - (void)updateLengthDefinedByTracks { - MusicTimeStamp length = 0; - for (MIKMIDITrack *track in self.tracks) { - MusicTimeStamp trackLength = track.length + track.offset; - if (trackLength > length) length = trackLength; - } - + MusicTimeStamp length = 0; + for (MIKMIDITrack *track in self.tracks) { + MusicTimeStamp trackLength = track.length + track.offset; + if (trackLength > length) length = trackLength; + } + self.lengthDefinedByTracks = length; } @@ -430,11 +444,11 @@ + (BOOL)automaticallyNotifiesObserversOfTracks { return NO; } - (NSArray *)tracks { __block NSArray *tracks; - + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ tracks = self.internalTracks; }]; - + return tracks ?: @[]; } @@ -447,11 +461,11 @@ + (NSSet *)keyPathsForValuesAffectingLength - (MusicTimeStamp)length { __block MusicTimeStamp length = 0; - + [self dispatchSyncToSequencerProcessingQueueAsNeeded:^{ length = (_length == MIKMIDISequenceLongestTrackLength) ? self.lengthDefinedByTracks : _length; }]; - + return length; } @@ -469,22 +483,22 @@ + (NSSet *)keyPathsForValuesAffectingDurationInSeconds - (Float64)durationInSeconds { - Float64 duration = 0; - OSStatus err = MusicSequenceGetSecondsForBeats(self.musicSequence, self.length, &duration); - if (err) NSLog(@"MusicSequenceGetSecondsForBeats() failed with error %@ 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 %@ 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 %@ 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 %@ in %s.", @(err), __PRETTY_FUNCTION__); + return nil; + } + + return (__bridge_transfer NSData *)data; } - (MIKMIDISequencer *)sequencer { return _sequencer; } @@ -510,9 +524,9 @@ - (instancetype)initWithData:(NSData *)data - (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 @@ -548,8 +562,6 @@ - (BOOL)getTimeSignature:(MIKMIDITimeSignature *)signature atTimeStamp:(MusicTim @end - - #pragma mark - @implementation MIKMIDISequence (MIKMIDIPrivate) diff --git a/Source/MIKMIDISequencer.h b/Source/MIKMIDISequencer.h index 5b50f085..790212d3 100644 --- a/Source/MIKMIDISequencer.h +++ b/Source/MIKMIDISequencer.h @@ -111,6 +111,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)stop; +/** + * Sends any pending note offs for the command scheduler immeidately. + * This can be useful if you are changing the notes in the MIDI track and + * you want the old notes to immediately stop rather than play until their + * original end time stamp. + */ +- (void)stopAllPlayingNotesForCommandScheduler:(id)scheduler; + /** * Allows subclasses to modify the MIDI commands that are about to be * scheduled with a command scheduler. @@ -290,6 +298,8 @@ NS_ASSUME_NONNULL_BEGIN /** * The current playback position in the sequence. + * + * @note This property is *not* observable using Key Value Observing. */ @property (nonatomic) MusicTimeStamp currentTimeStamp; @@ -404,6 +414,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) MIDITimeStamp latestScheduledMIDITimeStamp; + +/** + * The maximum amount the sequencer will look ahead to schedule MIDI events. (0.05 to 1s). + * + * The default of 0.1s should suffice for most uses. You may however, need a longer time + * if your sequencer needs to playback on iOS while the device is locked. + */ +@property (nonatomic) NSTimeInterval maximumLookAheadInterval; + #pragma mark - Deprecated /** diff --git a/Source/MIKMIDISequencer.m b/Source/MIKMIDISequencer.m index de3f3d8e..fea99926 100644 --- a/Source/MIKMIDISequencer.m +++ b/Source/MIKMIDISequencer.m @@ -82,8 +82,8 @@ @interface MIKMIDISequencer () @property (nonatomic, strong) NSMutableDictionary *pendingRecordedNoteEvents; -@property (nonatomic) MusicTimeStamp playbackOffset; @property (nonatomic) MusicTimeStamp startingTimeStamp; +@property (nonatomic) MusicTimeStamp initialStartingTimeStamp; @property (nonatomic, strong) NSMapTable *tracksToDestinationsMap; @property (nonatomic, strong) NSMapTable *tracksToDefaultSynthsMap; @@ -116,6 +116,7 @@ - (instancetype)initWithSequence:(MIKMIDISequence *)sequence _createSynthsIfNeeded = YES; _processingQueueKey = &_processingQueueKey; _processingQueueContext = &_processingQueueContext; + _maximumLookAheadInterval = 0.1; } return self; } @@ -149,14 +150,25 @@ - (void)startPlayback } - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp +{ + [self startPlaybackAtTimeStamp:timeStamp adjustForPreRollWhenRecording:YES]; +} + +- (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp adjustForPreRollWhenRecording:(BOOL)adjustForPreRoll { MIDITimeStamp midiTimeStamp = MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(0.001); [self startPlaybackAtTimeStamp:timeStamp MIDITimeStamp:midiTimeStamp]; } - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITimeStamp)midiTimeStamp +{ + [self startPlaybackAtTimeStamp:timeStamp MIDITimeStamp:midiTimeStamp adjustForPreRollWhenRecording:YES]; +} + +- (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITimeStamp)midiTimeStamp adjustForPreRollWhenRecording:(BOOL)adjustForPreRoll { if (self.isPlaying) [self stop]; + if (adjustForPreRoll && self.isRecording) timeStamp -= self.preRoll; NSString *queueLabel = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingFormat:@".%@.%p", [self class], self]; dispatch_queue_attr_t attr = DISPATCH_QUEUE_SERIAL; @@ -172,10 +184,10 @@ - (void)startPlaybackAtTimeStamp:(MusicTimeStamp)timeStamp MIDITimeStamp:(MIDITi self.processingQueue = queue; dispatch_sync(queue, ^{ - MusicTimeStamp startingTimeStamp = timeStamp + self.playbackOffset; - self.startingTimeStamp = startingTimeStamp; + self.startingTimeStamp = timeStamp; + self.initialStartingTimeStamp = timeStamp; - Float64 startingTempo = [self.sequence tempoAtTimeStamp:startingTimeStamp]; + Float64 startingTempo = [self.sequence tempoAtTimeStamp:timeStamp]; if (!startingTempo) startingTempo = kDefaultTempo; [self updateClockWithMusicTimeStamp:timeStamp tempo:startingTempo atMIDITimeStamp:midiTimeStamp]; }); @@ -208,6 +220,34 @@ - (void)stop [self stopWithDispatchToProcessingQueue:YES]; } +- (void)stopAllPlayingNotesForCommandScheduler:(id)scheduler +{ + [self dispatchSyncToProcessingQueueAsNeeded:^{ + NSMutableArray *commandsToSendNow = [NSMutableArray array]; + MIDITimeStamp offTimeStamp = MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(self.maximumLookAheadInterval); + + for (MIKMIDIPendingNoteOffsForTimeStamp *pendingNoteOffsForTimeStamp in self.pendingNoteOffs.allValues) { + NSMutableArray *noteEvents = pendingNoteOffsForTimeStamp.noteEventsWithEndTimeStamp; + NSUInteger count = noteEvents.count; + NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet]; + for (NSUInteger i = 0; i < count; i++) { + MIKMIDIEventWithDestination *event = noteEvents[i]; + if (event.destination == scheduler) { + [indexesToRemove addIndex:i]; + + MIKMIDINoteEvent *noteEvent = (MIKMIDINoteEvent *)event.event; + MIKMIDINoteOffCommand *command = [MIKMIDINoteOffCommand noteOffCommandWithNote:noteEvent.note velocity:0 channel:noteEvent.channel midiTimeStamp:offTimeStamp]; + [commandsToSendNow addObject:command]; + } + } + + [noteEvents removeObjectsAtIndexes:indexesToRemove]; + } + + if (commandsToSendNow.count) [self scheduleCommands:commandsToSendNow withCommandScheduler:scheduler]; + }]; +} + - (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue { MIDITimeStamp stopTimeStamp = MIKMIDIGetCurrentTimeStamp(); @@ -224,7 +264,7 @@ - (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue self.looping = NO; MusicTimeStamp stopMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:stopTimeStamp]; - _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength + self.playbackOffset) ? stopMusicTimeStamp - self.playbackOffset : self.sequenceLength; + _currentTimeStamp = (stopMusicTimeStamp <= self.sequenceLength) ? stopMusicTimeStamp : self.sequenceLength; [clock unsyncMusicTimeStampsAndTemposFromMIDITimeStamps]; }; @@ -232,26 +272,26 @@ - (void)stopWithDispatchToProcessingQueue:(BOOL)dispatchToProcessingQueue dispatchToProcessingQueue ? dispatch_sync(self.processingQueue, stopPlayback) : stopPlayback(); self.processingQueue = NULL; - self.playbackOffset = 0; self.playing = NO; self.recording = NO; } - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStamp { - MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(0.1); + MIDITimeStamp toMIDITimeStamp = MIKMIDIGetCurrentTimeStamp() + MIKMIDIClockMIDITimeStampsPerTimeInterval(self.maximumLookAheadInterval); if (toMIDITimeStamp < fromMIDITimeStamp) return; MIKMIDIClock *clock = self.clock; MIKMIDISequence *sequence = self.sequence; - MusicTimeStamp playbackOffset = self.playbackOffset; - MusicTimeStamp loopStartTimeStamp = self.loopStartTimeStamp + playbackOffset; - MusicTimeStamp loopEndTimeStamp = self.effectiveLoopEndTimeStamp + playbackOffset; + MusicTimeStamp loopStartTimeStamp = self.loopStartTimeStamp; + MusicTimeStamp loopEndTimeStamp = self.effectiveLoopEndTimeStamp; MusicTimeStamp fromMusicTimeStamp = [clock musicTimeStampForMIDITimeStamp:fromMIDITimeStamp]; 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); + MusicTimeStamp maxToMusicTimeStamp = self.isRecording ? DBL_MAX : self.sequenceLength; // If recording, don't limit max timestamp (Issue #45) + maxToMusicTimeStamp = isLooping ? loopEndTimeStamp : maxToMusicTimeStamp; + MusicTimeStamp toMusicTimeStamp = MIN(calculatedToMusicTimeStamp, maxToMusicTimeStamp); MIDITimeStamp actualToMIDITimeStamp = [clock midiTimeStampForMusicTimeStamp:toMusicTimeStamp]; // Get relevant tempo events @@ -260,9 +300,9 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam Float64 overrideTempo = self.tempo; if (!overrideTempo) { - NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + NSArray *sequenceTempoEvents = [sequence.tempoTrack eventsOfClass:[MIKMIDITempoEvent class] fromTimeStamp:MAX(fromMusicTimeStamp, 0) toTimeStamp:toMusicTimeStamp]; for (MIKMIDITempoEvent *tempoEvent in sequenceTempoEvents) { - NSNumber *timeStampKey = @(tempoEvent.timeStamp + playbackOffset); + NSNumber *timeStampKey = @(tempoEvent.timeStamp); allEventsByTimeStamp[timeStampKey] = [NSMutableArray arrayWithObject:tempoEvent]; tempoEventsByTimeStamp[timeStampKey] = tempoEvent; } @@ -296,11 +336,37 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam } // Get other events + NSMutableArray *nonMutedTracks = [[NSMutableArray alloc] init]; + NSMutableArray *soloTracks = [[NSMutableArray alloc] init]; for (MIKMIDITrack *track in sequence.tracks) { - NSArray *events = [track eventsFromTimeStamp:MAX(fromMusicTimeStamp - playbackOffset, 0) toTimeStamp:toMusicTimeStamp - playbackOffset]; + if (track.isMuted) continue; + + [nonMutedTracks addObject:track]; + if (track.solo) { [soloTracks addObject:track]; } + } + + // Never play muted tracks. If any non-muted tracks are soloed, only play those. Matches MusicPlayer behavior + NSArray *tracksToPlay = soloTracks.count != 0 ? soloTracks : nonMutedTracks; + + for (MIKMIDITrack *track in tracksToPlay) { + MusicTimeStamp startTimeStamp = MAX(fromMusicTimeStamp - track.offset, 0); + MusicTimeStamp endTimeStamp = toMusicTimeStamp - track.offset; + NSArray *events = [track eventsFromTimeStamp:startTimeStamp toTimeStamp:endTimeStamp]; + if (track.offset != 0) { + // Shift events by offset + NSMutableArray *shiftedEvents = [NSMutableArray array]; + for (MIKMIDIEvent *event in events) { + MIKMutableMIDIEvent *shiftedEvent = [event mutableCopy]; + shiftedEvent.timeStamp += track.offset; + [shiftedEvents addObject:shiftedEvent]; + } + events = shiftedEvents; + } + 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); + if ([event isKindOfClass:[MIKMIDINoteEvent class]] && [(MIKMIDINoteEvent *)event duration] <= 0) continue; + NSNumber *timeStampKey = @(event.timeStamp); NSMutableArray *eventsAtTimeStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimeStamp addObject:[MIKMIDIEventWithDestination eventWithDestination:destination event:event]]; allEventsByTimeStamp[timeStampKey] = eventsAtTimeStamp; @@ -309,7 +375,7 @@ - (void)processSequenceStartingFromMIDITimeStamp:(MIDITimeStamp)fromMIDITimeStam // Get click track events for (MIKMIDIEventWithDestination *destinationEvent in [self clickTrackEventsFromTimeStamp:fromMusicTimeStamp toTimeStamp:toMusicTimeStamp]) { - NSNumber *timeStampKey = @(destinationEvent.event.timeStamp + playbackOffset); + NSNumber *timeStampKey = @(destinationEvent.event.timeStamp); NSMutableArray *eventsAtTimesStamp = allEventsByTimeStamp[timeStampKey] ? allEventsByTimeStamp[timeStampKey] : [NSMutableArray array]; [eventsAtTimesStamp addObject:destinationEvent]; allEventsByTimeStamp[timeStampKey] = eventsAtTimesStamp; @@ -357,7 +423,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)) { + if ((systemTimeStamp > actualToMIDITimeStamp) && ([clock musicTimeStampForMIDITimeStamp:systemTimeStamp] >= self.sequenceLength)) { [self stopWithDispatchToProcessingQueue:NO]; } } @@ -390,13 +456,8 @@ - (void)scheduleEventWithDestination:(MIKMIDIEventWithDestination *)destinationE } else if ([event isKindOfClass:[MIKMIDIChannelEvent class]]) { command = [MIKMIDICommand commandFromChannelEvent:(MIKMIDIChannelEvent *)event clock:clock]; } - - // Adjust command time stamp to account for our playback offset. - if (command) { - MIKMutableMIDICommand *adjustedCommand = [command mutableCopy]; - adjustedCommand.midiTimestamp += [clock midiTimeStampsPerMusicTimeStamp:self.playbackOffset]; - [self scheduleCommands:@[adjustedCommand] withCommandScheduler:destination]; - } + + if (command) [self scheduleCommands:@[command] withCommandScheduler:destination]; } - (void)sendAllPendingNoteOffsWithMIDITimeStamp:(MIDITimeStamp)offTimeStamp @@ -472,7 +533,6 @@ - (void)resumeRecording - (void)prepareForRecordingWithPreRoll:(BOOL)includePreRoll { self.pendingRecordedNoteEvents = [NSMutableDictionary dictionary]; - if (includePreRoll) self.playbackOffset = self.preRoll; self.recording = YES; } @@ -481,9 +541,9 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command if (!self.isRecording) return; MIDITimeStamp midiTimeStamp = command.midiTimestamp; - - MusicTimeStamp playbackOffset = self.playbackOffset; - MusicTimeStamp musicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:midiTimeStamp] - playbackOffset; + MusicTimeStamp musicTimeStamp = [self.clock musicTimeStampForMIDITimeStamp:midiTimeStamp]; + + if (musicTimeStamp < 0) { return; } // Command is in pre-roll MIKMIDIEvent *event; if ([command isKindOfClass:[MIKMIDINoteOnCommand class]]) { // note On @@ -512,7 +572,7 @@ - (void)recordMIDICommand:(MIKMIDICommand *)command - (void)recordAllPendingNoteEventsWithOffTimeStamp:(MusicTimeStamp)offTimeStamp { NSMutableSet *events = [NSMutableSet set]; - + NSMutableDictionary *pendingRecordedNoteEvents = self.pendingRecordedNoteEvents; for (NSNumber *noteNumber in pendingRecordedNoteEvents) { for (MIKMutableMIDINoteEvent *event in pendingRecordedNoteEvents[noteNumber]) { @@ -522,8 +582,12 @@ - (void)recordAllPendingNoteEventsWithOffTimeStamp:(MusicTimeStamp)offTimeStamp } } self.pendingRecordedNoteEvents = [NSMutableDictionary dictionary]; - - if ([events count]) [self.recordEnabledTracks makeObjectsPerformSelector:@selector(addEvents:) withObject:events]; + + if ([events count]) { + for (MIKMIDITrack *track in self.recordEnabledTracks) { + [track addEvents:[events allObjects]]; + } + } } - (MIKMIDINoteEvent *)pendingNoteEventWithNoteNumber:(NSNumber *)noteNumber channel:(UInt8)channel releaseVelocity:(UInt8)releaseVelocity offTimeStamp:(MusicTimeStamp)offTimeStamp @@ -592,18 +656,17 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp MIDINoteMessage tockMessage = metronome.tockMessage; MIKMIDISequence *sequence = self.sequence; - MusicTimeStamp playbackOffset = self.playbackOffset; - MIKMIDITimeSignature timeSignature = [sequence timeSignatureAtTimeStamp:fromTimeStamp - playbackOffset]; + MIKMIDITimeSignature timeSignature = [sequence timeSignatureAtTimeStamp:MAX(fromTimeStamp, 0)]; NSMutableArray *timeSignatureEvents = [[sequence.tempoTrack eventsOfClass:[MIKMIDIMetaTimeSignatureEvent class] - fromTimeStamp:MAX(fromTimeStamp - playbackOffset, 0) - toTimeStamp:toTimeStamp] mutableCopy]; + fromTimeStamp:MAX(fromTimeStamp, 0) + toTimeStamp:MAX(toTimeStamp, 0)] mutableCopy]; MusicTimeStamp clickTimeStamp = floor(fromTimeStamp); while (clickTimeStamp <= toTimeStamp) { - if (clickTrackStatus == MIKMIDISequencerClickTrackStatusEnabledOnlyInPreRoll && clickTimeStamp >= self.startingTimeStamp) break; + if (clickTrackStatus == MIKMIDISequencerClickTrackStatusEnabledOnlyInPreRoll && clickTimeStamp >= self.initialStartingTimeStamp + self.preRoll) break; MIKMIDIMetaTimeSignatureEvent *event = [timeSignatureEvents firstObject]; - if (event && event.timeStamp - playbackOffset <= clickTimeStamp) { + if (event && event.timeStamp <= clickTimeStamp) { timeSignature = (MIKMIDITimeSignature) { .numerator = event.numerator, .denominator = event.denominator }; [timeSignatureEvents removeObjectAtIndex:0]; } @@ -612,7 +675,7 @@ - (NSMutableArray *)clickTrackEventsFromTimeStamp:(MusicTimeStamp)fromTimeStamp NSInteger adjustedTimeStamp = clickTimeStamp * timeSignature.denominator / 4.0; BOOL isTick = !((adjustedTimeStamp + timeSignature.numerator) % (timeSignature.numerator)); MIDINoteMessage clickMessage = isTick ? tickMessage : tockMessage; - MIKMIDINoteEvent *noteEvent = [MIKMIDINoteEvent noteEventWithTimeStamp:clickTimeStamp - playbackOffset message:clickMessage]; + MIKMIDINoteEvent *noteEvent = [MIKMIDINoteEvent noteEventWithTimeStamp:clickTimeStamp message:clickMessage]; [clickEvents addObject:[MIKMIDIEventWithDestination eventWithDestination:metronome event:noteEvent]]; } @@ -681,8 +744,7 @@ - (MusicTimeStamp)currentTimeStamp MIKMIDIClock *clock = self.clock; if (clock.isReady) { MusicTimeStamp timeStamp = [clock musicTimeStampForMIDITimeStamp:MIKMIDIGetCurrentTimeStamp()]; - MusicTimeStamp playbackOffset = self.playbackOffset; - _currentTimeStamp = MAX(((timeStamp <= self.sequenceLength + playbackOffset) ? timeStamp - playbackOffset : self.sequenceLength), self.startingTimeStamp); + _currentTimeStamp = MAX(((timeStamp <= self.sequenceLength) ? timeStamp : self.sequenceLength), self.startingTimeStamp); } return _currentTimeStamp; } @@ -693,7 +755,7 @@ - (void)setCurrentTimeStamp:(MusicTimeStamp)currentTimeStamp BOOL isRecording = self.isRecording; [self stop]; if (isRecording) [self prepareForRecordingWithPreRoll:NO]; - [self startPlaybackAtTimeStamp:currentTimeStamp]; + [self startPlaybackAtTimeStamp:currentTimeStamp adjustForPreRollWhenRecording:NO]; } else { _currentTimeStamp = currentTimeStamp; } @@ -762,6 +824,11 @@ - (void)setSequence:(MIKMIDISequence *)sequence } } +- (void)setMaximumLookAheadInterval:(NSTimeInterval)maximumLookAheadInterval +{ + _maximumLookAheadInterval = MIN(MAX(maximumLookAheadInterval, 0.05), 1.0); +} + #pragma mark - Deprecated - (void)setDestinationEndpoint:(MIKMIDIDestinationEndpoint *)endpoint forTrack:(MIKMIDITrack *)track diff --git a/Source/MIKMIDISynthesizer.m b/Source/MIKMIDISynthesizer.m index 12467aed..041ab19a 100644 --- a/Source/MIKMIDISynthesizer.m +++ b/Source/MIKMIDISynthesizer.m @@ -21,6 +21,7 @@ @interface MIKMIDISynthesizer () dispatch_queue_t _scheduledCommandQueue; } + @end @@ -37,6 +38,10 @@ - (instancetype)initWithAudioUnitDescription:(AudioComponentDescription)componen if (self) { _componentDescription = componentDescription; if (![self setupAUGraph]) return nil; + + self.sendMIDICommand = ^(MIKMIDISynthesizer *synth, MusicDeviceComponent inUnit, UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) { + return MusicDeviceMIDIEvent(inUnit, inStatus, inData1, inData2, inOffsetSampleFrame); + }; } return self; } @@ -208,7 +213,7 @@ - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)i UInt32 bankSelectStatus = 0xB0 | channel; UInt8 bankSelectMSB = (instrumentID >> 16) & 0x7F; - err = MusicDeviceMIDIEvent(self.instrumentUnit, bankSelectStatus, 0x00, bankSelectMSB, 0); + err = _sendMIDICommand(self, self.instrumentUnit, bankSelectStatus, 0x00, bankSelectMSB, 0); if (err) { NSLog(@"MusicDeviceMIDIEvent() (MSB Bank Select) failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; @@ -216,7 +221,7 @@ - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)i } UInt8 bankSelectLSB = (instrumentID >> 8) & 0x7F; - err = MusicDeviceMIDIEvent(self.instrumentUnit, bankSelectStatus, 0x20, bankSelectLSB, 0); + err = _sendMIDICommand(self, self.instrumentUnit, bankSelectStatus, 0x20, bankSelectLSB, 0); if (err) { NSLog(@"MusicDeviceMIDIEvent() (LSB Bank Select) failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; @@ -226,7 +231,7 @@ - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)i UInt32 programChangeStatus = 0xC0 | channel; UInt8 programChange = instrumentID & 0x7F; - err = MusicDeviceMIDIEvent(self.instrumentUnit, programChangeStatus, programChange, 0, 0); + err = _sendMIDICommand(self, self.instrumentUnit, programChangeStatus, programChange, 0, 0); if (err) { NSLog(@"MusicDeviceMIDIEvent() (Program Change) failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]; @@ -401,6 +406,73 @@ - (void)scheduleMIDICommands:(NSArray *)commands #pragma mark - Callbacks +OSStatus MIKMIDISynthesizerScheduleUpcomingMIDICommands(MIKMIDISynthesizer *synth, AudioUnit instrumentUnit, UInt32 inNumberFrames, Float64 sampleRate, const AudioTimeStamp *inTimeStamp) +{ + dispatch_queue_t queue = synth->_scheduledCommandQueue; + if (!queue) return noErr; // no commands have been scheduled with this synth + + static NSTimeInterval lastTimeUntilNextCallback = 0; + static MIDITimeStamp lastMIDITimeStampsUntilNextCallback = 0; + NSTimeInterval timeUntilNextCallback = inNumberFrames / sampleRate; + MIDITimeStamp midiTimeStampsUntilNextCallback = lastMIDITimeStampsUntilNextCallback; + + if (lastTimeUntilNextCallback != timeUntilNextCallback) { + midiTimeStampsUntilNextCallback = MIKMIDIClockMIDITimeStampsPerTimeInterval(timeUntilNextCallback); + lastTimeUntilNextCallback = timeUntilNextCallback; + lastMIDITimeStampsUntilNextCallback = midiTimeStampsUntilNextCallback; + } + + MIDITimeStamp toTimeStamp = inTimeStamp->mHostTime + midiTimeStampsUntilNextCallback; + CFMutableArrayRef commandsToSend = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);; + + dispatch_sync(queue, ^{ + CFMutableDictionaryRef commandsByTimeStamp = synth->_scheduledCommandsByTimeStamp; + if (!commandsByTimeStamp || !CFDictionaryGetCount(commandsByTimeStamp)) return; + + + CFMutableSetRef commandTimeStampsSet = synth->_scheduledCommandTimeStampsSet; + CFMutableArrayRef commandTimeStampsArray = synth->_scheduledCommandTimeStampsArray; + if (!commandTimeStampsSet || !commandTimeStampsArray) return; + + 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.unsignedLongLongValue; + 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); + }); + + NSTimeInterval secondsPerMIDITimeStamp = MIKMIDIClockSecondsPerMIDITimeStamp(); + + CFIndex commandCount = CFArrayGetCount(commandsToSend); + for (CFIndex i = 0; i < commandCount; i++) { + MIKMIDICommand *command = (__bridge MIKMIDICommand *)CFArrayGetValueAtIndex(commandsToSend, i); + + MIDITimeStamp sendTimeStamp = command.midiTimestamp; + if (sendTimeStamp < inTimeStamp->mHostTime) sendTimeStamp = inTimeStamp->mHostTime; + MIDITimeStamp timeStampOffset = sendTimeStamp - inTimeStamp->mHostTime; + Float64 sampleOffset = secondsPerMIDITimeStamp * timeStampOffset * sampleRate; + + OSStatus err = synth->_sendMIDICommand(synth, instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, sampleOffset); + if (err) { + NSLog(@"Unable to schedule MIDI command %@ for instrument unit %p: %@", command, instrumentUnit, @(err)); + return err; + } + } + + CFRelease(commandsToSend); + return noErr; +} + static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRefCon, AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, @@ -413,9 +485,6 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef 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); @@ -425,65 +494,7 @@ static OSStatus MIKMIDISynthesizerInstrumentUnitRenderCallback(void * inRef return err; } - static NSTimeInterval lastTimeUntilNextCallback = 0; - static MIDITimeStamp lastMIDITimeStampsUntilNextCallback = 0; - NSTimeInterval timeUntilNextCallback = inNumberFrames / LPCMASBD.mSampleRate; - MIDITimeStamp midiTimeStampsUntilNextCallback = lastMIDITimeStampsUntilNextCallback; - - if (lastTimeUntilNextCallback != timeUntilNextCallback) { - midiTimeStampsUntilNextCallback = MIKMIDIClockMIDITimeStampsPerTimeInterval(timeUntilNextCallback); - lastTimeUntilNextCallback = timeUntilNextCallback; - lastMIDITimeStampsUntilNextCallback = midiTimeStampsUntilNextCallback; - } - - MIDITimeStamp toTimeStamp = inTimeStamp->mHostTime + midiTimeStampsUntilNextCallback; - CFMutableArrayRef commandsToSend = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);; - - dispatch_sync(queue, ^{ - CFMutableDictionaryRef commandsByTimeStamp = synth->_scheduledCommandsByTimeStamp; - if (!commandsByTimeStamp || !CFDictionaryGetCount(commandsByTimeStamp)) return; - - - CFMutableSetRef commandTimeStampsSet = synth->_scheduledCommandTimeStampsSet; - CFMutableArrayRef commandTimeStampsArray = synth->_scheduledCommandTimeStampsArray; - if (!commandTimeStampsSet || !commandTimeStampsArray) return; - - 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); - }); - - NSTimeInterval secondsPerMIDITimeStamp = MIKMIDIClockSecondsPerMIDITimeStamp(); - - CFIndex commandCount = CFArrayGetCount(commandsToSend); - for (CFIndex i = 0; i < commandCount; i++) { - MIKMIDICommand *command = (__bridge MIKMIDICommand *)CFArrayGetValueAtIndex(commandsToSend, i); - - MIDITimeStamp sendTimeStamp = command.midiTimestamp; - if (sendTimeStamp < inTimeStamp->mHostTime) sendTimeStamp = inTimeStamp->mHostTime; - MIDITimeStamp timeStampOffset = sendTimeStamp - inTimeStamp->mHostTime; - Float64 sampleOffset = 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: %@", command, instrumentUnit, @(err)); - return err; - } - } - - CFRelease(commandsToSend); + return MIKMIDISynthesizerScheduleUpcomingMIDICommands(synth, instrumentUnit, inNumberFrames, LPCMASBD.mSampleRate, inTimeStamp); } return noErr; } @@ -501,7 +512,7 @@ - (void)setGraph:(AUGraph)graph - (void)handleMIDIMessages:(NSArray *)commands { for (MIKMIDICommand *command in commands) { - OSStatus err = MusicDeviceMIDIEvent(self.instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, 0); + OSStatus err = _sendMIDICommand(self, self.instrumentUnit, command.statusByte, command.dataByte1, command.dataByte2, 0); if (err) NSLog(@"Unable to send MIDI command to synthesizer %@: %@", command, @(err)); } } diff --git a/Source/MIKMIDISynthesizerInstrument.m b/Source/MIKMIDISynthesizerInstrument.m index b400ad39..4244380b 100644 --- a/Source/MIKMIDISynthesizerInstrument.m +++ b/Source/MIKMIDISynthesizerInstrument.m @@ -34,7 +34,7 @@ - (BOOL)isEqual:(id)object { if (object == self) return YES; if (![object isMemberOfClass:[self class]]) return NO; - if (!self.instrumentID == [object instrumentID]) return NO; + if (self.instrumentID != [object instrumentID]) return NO; return [self.name isEqualToString:[object name]]; } diff --git a/Source/MIKMIDISynthesizer_SubclassMethods.h b/Source/MIKMIDISynthesizer_SubclassMethods.h index 20990cc5..763e2dd7 100644 --- a/Source/MIKMIDISynthesizer_SubclassMethods.h +++ b/Source/MIKMIDISynthesizer_SubclassMethods.h @@ -16,7 +16,14 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)sendBankSelectAndProgramChangeForInstrumentID:(MusicDeviceInstrumentID)instrumentID error:(NSError **)error; @property (nonatomic, readwrite, nullable) AudioUnit instrumentUnit; +@property (nonatomic, copy) OSStatus (^sendMIDICommand)(MIKMIDISynthesizer *synth, MusicDeviceComponent inUnit, UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame); @end -NS_ASSUME_NONNULL_END \ No newline at end of file +FOUNDATION_EXPORT OSStatus MIKMIDISynthesizerScheduleUpcomingMIDICommands(MIKMIDISynthesizer *synth, + AudioUnit _Nullable instrumentUnit, + UInt32 inNumberFrames, + Float64 sampleRate, + const AudioTimeStamp *inTimeStamp); + +NS_ASSUME_NONNULL_END diff --git a/Source/MIKMIDITempoEvent.h b/Source/MIKMIDITempoEvent.h index 61d23e8b..dd8fbb31 100644 --- a/Source/MIKMIDITempoEvent.h +++ b/Source/MIKMIDITempoEvent.h @@ -23,9 +23,9 @@ NS_ASSUME_NONNULL_BEGIN * * @param bpm The beats per minute of the tempo event. * - * @return A new instance of MIKMIDITempoEvent, or nil if an error occured. + * @return A new instance of MIKMIDITempoEvent */ -+ (nullable instancetype)tempoEventWithTimeStamp:(MusicTimeStamp)timeStamp tempo:(Float64)bpm; ++ (instancetype)tempoEventWithTimeStamp:(MusicTimeStamp)timeStamp tempo:(Float64)bpm; /** * The beats per minute of the tempo event. diff --git a/Source/MIKMIDITrack.h b/Source/MIKMIDITrack.h index 529acbf0..2d59c719 100644 --- a/Source/MIKMIDITrack.h +++ b/Source/MIKMIDITrack.h @@ -185,30 +185,28 @@ NS_ASSUME_NONNULL_BEGIN /** * 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. + * This can be used to offset playback by MIKMIDISequencer of individual tracks in a sequence. * - * @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. + * This property can be observed using Key Value Observing. */ @property (nonatomic) MusicTimeStamp offset; /** - * Whether or not the MIDI track is muted. + * Whether or not the MIDI track is muted. Muted tracks are not played by MIKMIDISequencer. * * 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, getter = isMuted) BOOL muted; /** * Whether or not the MIDI track is soloed. * - * This property can be observed using Key Value Observing. + * If a track is muted, it is not played by MIKMIDISequencer, and its solo status is ignored. + * If any non-muted tracks in a sequence have this property set to YES, MIKMIDISequencer will only play those, + * and will not play the non-solo tracks. If this is set to NO for all non-muted tracks, then + * all non-muted tracks will be played. * - * @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. + * This property can be observed using Key Value Observing. */ @property (nonatomic, getter = isSolo) BOOL solo; diff --git a/Source/MIKMIDITrack.m b/Source/MIKMIDITrack.m index 3fd0e2aa..e3c3ec56 100644 --- a/Source/MIKMIDITrack.m +++ b/Source/MIKMIDITrack.m @@ -616,51 +616,87 @@ - (NSInteger)trackNumber return (NSInteger)trackNumber; } +@synthesize offset = _offset; + - (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 %@ in %s.", @(err), __PRETTY_FUNCTION__); - return offset; + if (_offset != 0) return _offset; + + if (self.musicTrack) { + MusicTimeStamp offset = 0; + UInt32 offsetLength = sizeof(offset); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, &offsetLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + return offset; + } else { + return 0; + } } - (void)setOffset:(MusicTimeStamp)offset { - OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, sizeof(offset)); - if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + _offset = offset; + + if (self.musicTrack) { + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_OffsetTime, &offset, sizeof(offset)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + } } +@synthesize muted = _muted; + - (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 %@ in %s.", @(err), __PRETTY_FUNCTION__); - return isMuted ? YES : NO; + if (_muted) return YES; + + if (self.musicTrack) { + Boolean isMuted = FALSE; + UInt32 isMutedLength = sizeof(isMuted); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &isMuted, &isMutedLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + return isMuted ? YES : NO; + } else { + return 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 %@ in %s.", @(err), __PRETTY_FUNCTION__); + _muted = muted; + + if (self.musicTrack) { + Boolean mutedBoolean = muted ? TRUE : FALSE; + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + } } +@synthesize solo = _solo; + - (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 %@ in %s.", @(err), __PRETTY_FUNCTION__); - return isSolo ? YES : NO; + if (_solo) return YES; + + if (self.musicTrack) { + Boolean isSolo = FALSE; + UInt32 isSoloLength = sizeof(isSolo); + OSStatus err = MusicTrackGetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &isSolo, &isSoloLength); + if (err) NSLog(@"MusicTrackGetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + return isSolo ? YES : NO; + } else { + return 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 %@ in %s.", @(err), __PRETTY_FUNCTION__); + _solo = solo; + + if (self.musicTrack) { + Boolean soloBoolean = solo ? TRUE : FALSE; + OSStatus err = MusicTrackSetProperty(self.musicTrack, kSequenceTrackProperty_SoloStatus, &soloBoolean, sizeof(soloBoolean)); + if (err) NSLog(@"MusicTrackSetProperty() failed with error %@ in %s.", @(err), __PRETTY_FUNCTION__); + } } + (NSSet *)keyPathsForValuesAffectingLength diff --git a/Source/MIKMIDIUtilities.h b/Source/MIKMIDIUtilities.h index 9f0c817c..706e3636 100644 --- a/Source/MIKMIDIUtilities.h +++ b/Source/MIKMIDIUtilities.h @@ -35,7 +35,7 @@ MIDIPacket MIKMIDIPacketCreate(MIDITimeStamp timeStamp, UInt16 length, MIKArrayO #define MIKMIDI_RAISE_MUTATION_ATTEMPT_EXCEPTION ([NSException raise:NSInternalInconsistencyException format:@"Attempt to mutate immutable %@", NSStringFromClass([self class])]) // A prettier way to get the mac_absolute_time() when working with MIDITimeStamps. -#define MIKMIDIGetCurrentTimeStamp() (mach_absolute_time()) +MIDITimeStamp MIKMIDIGetCurrentTimeStamp(); /** * Returns whether a given MIDI note number corresponds to a "black key" on a piano. diff --git a/Source/MIKMIDIUtilities.m b/Source/MIKMIDIUtilities.m index 3ad7eb7f..24fc4c20 100644 --- a/Source/MIKMIDIUtilities.m +++ b/Source/MIKMIDIUtilities.m @@ -150,6 +150,11 @@ NSInteger MIKMIDIStandardLengthOfMessageForCommandType(MIKMIDICommandType comman return result; } +MIDITimeStamp MIKMIDIGetCurrentTimeStamp() +{ + return mach_absolute_time(); +} + MIDIPacket MIKMIDIPacketCreate(MIDITimeStamp timeStamp, UInt16 length, MIKArrayOf(NSNumber *) *data /*max length 256*/) { MIDIPacket result = {0};