Network MIDI on iOS - Part 1
- Composing outgoing MIDI data in response to user input
- Processing incoming MIDI data in real time (with semaphores and lock free buffers)
- Finding network services with Bonjour
Updated project for iOS 7 (should work back to iOS 5.1):
Please note you need to run this on a device rather than the simulator.
(Now seems to work OK on the iOS 7 simulator at least).
Network MIDI on iOS - Part 2
In this part, I will discuss finding and publishing network MIDI services using Bonjour, and creating the MIDI client using the CoreMIDI API. The source code for this project is available for download in
Part 1 of this article.
The network MIDI services in OS X and iOS can be discovered in the same way as any other Bonjour service. In the initialiser of the MIDIController class (see MIDIController.m) an instance of
NSNetServiceBrowser is created, and instructed to search for services of the type MIDINetworkBonjourServiceType i.e. @"_apple-midi._udp". By implementing the delegate protocol for this browser, a MIDIController instance can monitor available services of
this type on the network.
These services are exposed to the remainder of the application as a dictionary. In addition, various operations are provided to allow connection, disconnection etc. (see the section marked "Connection Management" in the implementation for details). The sample application uses these methods to provide a simple UI to connect to any detected services. The settings page is displayed by flipping the main view. From here, we can select one or more of the remote MIDI services available on the current LAN.
- All incoming data from the network is merged as if it was coming from a single device
- Outgoing data from the device is sent to all network services
The MIDIController initialiser also sets up the shared
MIDINetworkSession instance, which acts as a bridge between the network services and the CoreMIDI API. A MIDI client, input port and output port are then created. The input port is connected to the source endpoint of the MIDINetworkSession instance. Note
that these endpoints are named from the perspective of the iOS application; the "source" endpoint is where data will be received from the network, and the "destination" endpoint is where the application will send data to the network.
To send data to the output port once a connection has been made, the app invokes the methods in the section marked Sending in the implementation file, namely:
-(void) allNotesOffOnChannel:(NSUInteger)channel;
-(void) sendChangeForController:(NSUInteger)controller onChannel:(NSUInteger)channel withValue:(NSUInteger)value;
-(void) sendNote:(NSUInteger)note on:(BOOL)on onChannel:(NSUInteger)channel withVelocity:(NSUInteger)velocity;
-(void) sendMMCCommand:(NSUInteger)command toDevice:(NSUInteger)device;
The sendChangeForController:onChannel:withValue: method is invoked in response to user input from the controller tab:
The sendNote:on:onChannel:withVelocity: method is invoked when the user presses the "keys" on the Notes tab:
The sendMMCCommand:toDevice: method is invoked in response to button presses on the MMC tab:
Please note that the MMC command output hasn't been tested as I don't have any devices that respond to this part of the MIDI protocol. Let me know via the comments if you have any problems.
The operation of the input port is described in Part 3 of this article.
Network MIDI on iOS - Part 3
In this part I will discuss how incoming MIDI is received into the application and subsequently processed. The source code for this project is available for download in
Part 1 of this article.
The MIDI controller class (see MIDIController.h) provides a formal protocol, MIDIReceivedDelegate, and a corresponding delegate property. This defines the following methods:
- (void) midiControllerUpdated:(Byte)controller onChannel:(Byte)channel toValue:(Byte)value;
- (void) midiNoteOnOff:(Byte)note onChannel:(Byte)channel withVelocity:(Byte)velocity on:(BOOL)on;
These methods encapsulate the complexity of receiving MIDI data - but how is this accomplished behind the scenes?
When we created the MIDI client in part 2 of this article, we also created a MIDI input port. This was passed a pointer to a callback function, MIDIInputReadProc. This callback function is called by the operating system when MIDI data is received at the port.
As a real time callback function which may be re-entrant when the system is under load, certain restrictions should be adhered to. In particular, this function should avoid:
- Allocating and deallocating memory
- Acquiring locks
- Performing lengthy operations
The function walks the list of MIDI packets received as follows:
For each MIDI packet received:
- The packet's length and data are written into a structured circular buffer (see MIDIPacketBuffer.h)
- A Mach semaphore is incremented to signal the rest of the application that another packet is available for processing.
Note that the structured buffer in this example disregards the timestamp of the MIDI packet. This will be covered in a later example.
Another thread (see midiInputThreadProc) waits on this semaphore. If the semaphore is signalled, the length of the next MIDI packet is retrieved from the circular buffer, and then that amount of data is copied into a regular buffer.
This data is then parsed from this buffer. The following
MIDI commands are identified in this example:
- Control changes
- Note on/off
By these means, the application decouples the processing of MIDI data from its receipt, and conforms with the requirements placed on it by the CoreMIDI port model