Monday, 30 May 2011

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.


Note that connections can also be made from the other end. By opening Audio MIDI Setup on a Mac on the same LAN, we can browse to the iPhone and initiate a connection from the computer. In this instance, the MIDIController instance will inform the UI that a connection has been made via an NSNotification. If multiple connections are made to or from the same device, they are bridged in that:
  • 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 settings view also allows the user to set the MIDI channel on which messages will be sent in response to actions from the main UI views. This is similarly the channel on which the app will listen for incoming MIDI commands.

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;

Internally, these use the writeMIDIPacket methods to construct a MIDIPacketList and send it via the output port created above to the MIDINetworkSession's destinationEndpoint. Care should be taken to construct the packet lists correctly, as sending garbage data (for instance where the packet count is set too high) will typically result in disconnection by the remote service.

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.

No comments:

Post a Comment