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. 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, our flagship app Mixed In Key, and our composition software, Odesi.
MIKMIDI can be used in projects targeting Mac OS X 10.7 and later, and iOS 6 and later. The example code in this readme is in Objective-C. However, MIKMIDI can also easily be used from Swift code.
MIKMIDI is released under an MIT license, meaning you're free to use it in both closed and open source projects. However, even in a closed source project, you must include a publicly-accessible copy of MIKMIDI's copyright notice, which you can find in the LICENSE file.
If you have any questions about, or suggestions for MIKMIDI, please contact the maintainer. Contributions are always welcome. Please see our contribution guidelines for more information. We'd also always love to hear about any cool projects you're using it in.
How To Use MIKMIDI
MIKMIDI is provided as a source library for both OS X and iOS. Additionally, for OS X, a project to build a Framework is included. To use MIKMIDI in your project, add its source to your project by dragging the contents of the 'Source' folder into your Xcode project.
MIKMIDI relies on CoreMIDI.framework, as well as on libxml2 (on iOS). Make sure Objective-C module support is turned on in your target/project's build settings (see here).
On OS X, you can also use MIKMIDI.framework instead of including the MIKMIDI source in your project. To do so, open MIKMIDI.xcodeproj in the 'Framework' folder, build then copy the resultant MIKMIDI.framework into your project. In your project's settings, select your application's target, then click on the "Build Phases" tab. Expand the "Link Binary With Libraries" section, then click the "+" button in the lower left corner to add a new Framework. In the list that appears, find and select MIKMIDI.framework.
MIKMIDI has an Objective-C interface -- as opposed to CoreMIDI's pure C API -- in order to make adding MIDI support to a Cocoa/Cocoa Touch app easier. At it’s core, MIKMIDI consists of relatively thin Objective-C wrappers around underlying CoreMIDI APIs. Much of MIKMIDI's design is informed and driven by CoreMIDI's design. For this reason, familiarity with the high level pieces of CoreMIDI can be helpful in understanding and using MIKMIDI.
MIKMIDI is not limited to Objective-C interfaces for existing CoreMIDI functionality. MIKMIDI provides a number of higher level features. This includes an easy way to receive and route MIDI messages to appropriate parts of your application. Also included is functionality intended to facilitate implementing a MIDI learning UI so that users may create custom MIDI mapping files. These MIDI mapping files associate physical controls on a particular piece of MIDI hardware with corresponding receivers (e.g. on-screen buttons, knobs, musical instruments, etc.) in your application.
To understand MIKMIDI, it's helpful to break it down into its major subsystems:
- Device support -- includes support for device discovery, connection/disconnection, and sending/receiving MIDI messages.
- Commands -- includes a number of Objective-C classes that various represent MIDI message types as received from and sent to MIDI devices and endpoints.
- Mapping -- support for generating, saving, loading, and using files that associate physical MIDI controls with corresponding application features.
- Files -- support for reading and writing MIDI files.
- Synthesis -- support for turning MIDI into audio, e.g. playback of MIDI files and incoming MIDI keyboard input.
- Sequencing -- Recording and playback of MIDI.
Of course, these subsystems are used together to enable sophisticated features.
MIKMIDI's device support architecture is based on the underlying CoreMIDI architecture. There are several major classes used to represent portions of a device. All of these classes are subclasses of
MIKMIDIObject. These classes are listed below. In parentheses is the corresponding CoreMIDI class.
- MIKMIDIObject (MIDIObjectRef) -- Abstract base class for all the classes listed below. Includes properties common to all MIDI objects.
- MIKMIDIDevice (MIDIDeviceRef) -- Represents a single physical device, e.g. a DJ controller, MIDI keyboard, MIDI drum set, etc.
- MIKMIDIEntity (MIDIEntityRef) -- Groups related endpoints. Owned by MIKMIDIDevice, contains MIKMIDIEndpoints.
- MIKMIDIEndpoint (MIDIEndpointRef) -- Abstract base class representing a MIDI endpoint. Not used directly, only via its subclasses MIKMIDISourceEndpoint and MIKMIDIDestinationEndpoint.
- MIKMIDISourceEndpoint -- Represents a MIDI source. MIDI sources receive messages that your application can hear and process.
- MIKMIDIDestinationEndpoint -- Represents a MIDI destination. Your application passes MIDI messages to a destination endpoint in order to send them to a device.
MIKMIDIDeviceManager is a singleton class used for device discovery, and to send and receive MIDI messages to and from endpoints. To get a list of MIDI devices available on the system, call
-availableDevices on the shared device manager:
NSArray *availableMIDIDevices = [[MIKMIDIDeviceManager sharedDeviceManager] availableDevices];
MIKMIDIDeviceManager also includes the ability to retrieve 'virtual' endpoints, to enable communicating with other MIDI apps, or with devices (e.g. Native Instruments controllers) which present as virtual endpoints rather than physical devices.
virtualDestinations properties are Key Value Observing (KVO) compliant. This means that for example,
availableDevices can be bound to an
NSPopupMenu in an OS X app to provide an automatically updated list of connected MIDI devices. They can also be directly observed using key value observing to be notified when devices are connected or disconnected, etc. Additionally,
MIKMIDIDeviceManager posts these notifications:
MIKMIDIDeviceManager is used to sign up to receive messages from MIDI endpoints as well as to send them. To receive messages from a
MIKMIDISourceEndpoint, you must connect the endpoint and supply an event handler block to be called anytime messages are received. This is done using the
-connectInput:error:eventHandler: method. When you no longer want to receive messages, you must call the
-disconnectInput: method. To send MIDI messages to an
-[MIKMIDIDeviceManager sendCommands:toEndpoint:error:] passing an
MIKMIDICommand instances. For example:
NSDate *date = [NSDate date]; MIKMIDINoteOnCommand *noteOn = [MIKMIDINoteOnCommand noteOnCommandWithNote:60 velocity:127 channel:0 timestamp:date]; MIKMIDINoteOffCommand *noteOff = [MIKMIDINoteOffCommand noteOffCommandWithNote:60 velocity:0 channel:0 timestamp:[date dateByAddingTimeInterval:0.5]]; MIKMIDIDeviceManager *dm = [MIKMIDIDeviceManager sharedDeviceManager]; [dm sendCommands:@[noteOn, noteOff] toEndpoint:destinationEndpoint error:&error];
If you've used CoreMIDI before, you may be familiar with
MIDIPortRef. These are used internally by MIKMIDI, but the "public" API for MIKMIDI does not expose them -- or their Objective-C counterparts -- directly. Rather,
MIKMIDIDeviceManager itself allows sending and receiving messages to/from
In MIKMIDI, MIDI messages are Objective-C objects. These objects are instances of concrete subclasses of
MIKMIDICommand. Each MIDI message type (e.g. Control Change, Note On, System Exclusive, etc.) has a corresponding class (e.g. MIKMIDIControlChangeCommand). Each command class has properties specific to that message type. By default, MIKMIDICommands are immutable. Mutable variants of each command type are also available.
MIKMIDI includes features to help with adding MIDI mapping support to an application. MIDI mapping refers to the ability to map physical controls on a particular hardware controller to specific functions in the application. MIKMIDI's mapping support includes the ability to generate, save, load, and use mapping files that associate physical controls with an application's specific functionality. It also includes help with implementing a system that allows end users to easily generate their own mappings using a "MIDI learn" style interface.
The major components of MIKMIDI's MIDI mapping functionality are:
- MIKMIDIMapping - Model class containing information to map incoming messages to to the appropriate application functionality.
- MIKMIDIMappingManager - Singleton manager used to load, save, and retrieve both application-bundled, and user customized mapping files.
- MIKMIDIMappingGenerator - Class that can listen to incoming MIDI messages, and associate them with application functionality, creating a custom MIDI mapping file.
MIKMIDI includes features to make it easy to read and write MIDI files. This support is primarily provided by:
- MIKMIDISequence - This class is used to represent a MIDI sequence, and can be read from or written to a MIDI file.
- MIKMIDITrack - An MIKMIDISequence contains one or more MIKMIDITracks.
- MIKMIDIEvent - MIKMIDIEvent and its specific subclasses are used to represent MIDI events contained by MIKMIDITracks.
MIDI synthesis is the process by which MIDI events/messages are turned into audio that you can hear. This is accomplished using
MIKMIDISynthesizer. Also included is a subclass of
MIKMIDIEndpointSynthesizer which can very easily be hooked up to a MIDI enpoint to synthesize incoming MIDI messages:
MIKMIDISourceEndpoint *endpoint = midiDevice.entities.firstObject.sources.firstObject; MIKMIDISynthesizer *synth = [[MIKMIDIEndpointSynthesizer alloc] initWithMIDISource:endpoint];
MIKMIDISequencer can be used to play and record to an
MIKMIDISequence. It includes a number of high level features useful when implementing MIDI recording and playback. However, at the very simplest, MIKMIDISequencer can be used to load a MIDI file and play it like so:
MIKMIDISequence *sequence = [MIKMIDISequence sequenceWithFileAtURL:midiFileURL error:&error]; MIKMIDISequencer *sequencer = [MIKMIDISequencer sequencerWithSequence:sequence]; [sequencer startPlayback];