1<html> 2<body> 3 4<p> 5Provides classes for sending and receiving messages using the standard MIDI 6event protocol over USB, Bluetooth LE, and virtual (inter-app) transports. 7</p> 8 9<h1 id=overview>Overview</h1> 10 11<p>The Android MIDI package allows users to:</p> 12 13<ul> 14 <li> Connect a MIDI keyboard to Android to play a synthesizer or drive music apps.</li> 15 <li> Connect alternative MIDI controllers to Android.</li> 16 <li> Drive external MIDI synths from Android.</li> 17 <li> Drive external peripherals, lights, show control, etc from Android.</li> 18 <li> Generate music dynamically from games or music creation apps.</li> 19 <li> Generate MIDI messages in one app and send them to a second app.</li> 20 <li> Use an Android device running in <em>peripheral mode</em> as a multitouch controller 21 connected to a laptop.</li> 22</ul> 23 24<h2 id=the_api_features_include>The API features include:</h2> 25 26<ul> 27 <li> Enumeration of currently available devices. Information includes name, vendor, 28capabilities, etc.</li> 29 <li> Provide notification when MIDI devices are plugged in or unplugged.</li> 30 <li> Support efficient transmission of single or multiple short 1-3 byte MIDI messages.</li> 31 <li> Support transmission of arbitrary length data for SysEx, etc.</li> 32 <li> Timestamps to avoid jitter.</li> 33 <li> Support creation of <em>virtual MIDI devices</em> that can be connected to other devices. 34 An example might be a synthesizer app that can be controlled by a composing app.</li> 35 <li> Support direct connection or “patching” of devices for lower latency.</li> 36</ul> 37 38<h2 id=transports_supported>Transports Supported</h2> 39 40 41<p>The API is “transport agnostic”. But there are several transports currently 42supported:</p> 43 44<ul> 45 <li> USB 46 <li> software routing 47 <li> BTLE 48</ul> 49 50<h1 id=android_midi_terminology>Android MIDI Terminology</h1> 51 52 53<h2 id=terminology>Terminology</h2> 54 55 56<p>A <strong>Device</strong> is a MIDI capable object that has zero or more InputPorts and OutputPorts.</p> 57 58<p>An <strong>InputPort</strong> has 16 channels and can <strong>receive</strong> MIDI messages from an OutputPort or an app.</p> 59 60<p>An <strong>OutputPort</strong> has 16 channels and can <strong>send</strong> MIDI messages to an InputPort or an app.</p> 61 62<p><strong>MidiService</strong> is a centralized process that keeps track of all devices and brokers 63communication between them.</p> 64 65<p><strong>MidiManager</strong> is a class that the application or a device manager calls to communicate with 66the MidiService.</p> 67 68<h1 id=writing_a_midi_application>Writing a MIDI Application</h1> 69 70<h2 id=manifest_feature>Declare Feature in Manifest</h2> 71 72<p>An app that requires the MIDI API should declare that in the AndroidManifest.xml file. 73Then the app will not appear in the Play Store for old devices that do not support the MIDI API.</p> 74 75<pre class=prettyprint> 76<uses-feature android:name="android.software.midi" android:required="true"/> 77</pre> 78 79<h2 id=check_feature>Check for Feature Support</h2> 80 81<p>An app can also check at run-time whether the MIDI feature is supported on a platform. 82This is particularly useful during development when you install apps directly on a device. 83</p> 84 85<pre class=prettyprint> 86if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) { 87 // do MIDI stuff 88} 89</pre> 90 91<h2 id=the_midimanager>The MidiManager</h2> 92 93 94<p>The primary class for accessing the MIDI package is through the MidiManager.</p> 95 96<pre class=prettyprint> 97MidiManager m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 98</pre> 99 100 101<h2 id=get_list_of_already_plugged_in_entities>Get List of Already Plugged In Entities</h2> 102 103 104<p>When an app starts, it can get a list of all the available MIDI devices. This 105information can be presented to a user, allowing them to choose a device.</p> 106 107<pre class=prettyprint> 108MidiDeviceInfo[] infos = m.getDevices(); 109</pre> 110 111 112<h2 id=notification_of_midi_devices_hotplug_events>Notification of MIDI Devices HotPlug Events</h2> 113 114 115<p>The application can request notification when, for example, keyboards are 116plugged in or unplugged.</p> 117 118<pre class=prettyprint> 119m.registerDeviceCallback(new MidiManager.DeviceCallback() { 120 public void onDeviceAdded( MidiDeviceInfo info ) { 121 ... 122 } 123 public void onDeviceRemoved( MidiDeviceInfo info ) { 124 ... 125 } 126 }); 127</pre> 128 129 130<h2 id=device_and_port_information>Device and Port Information</h2> 131 132 133<p>You can query the number of input and output ports.</p> 134 135<pre class=prettyprint> 136int numInputs = info.getInputPortCount(); 137int numOutputs = info.getOutputPortCount(); 138</pre> 139 140 141<p>Note that “input” and “output” directions reflect the point of view 142of the MIDI device itself, not your app. 143For example, to send MIDI notes to a synthesizer, open the synth's INPUT port. 144To receive notes from a keyboard, open the keyboard's OUTPUT port.</p> 145 146<p>The MidiDeviceInfo has a bundle of properties.</p> 147 148<pre class=prettyprint> 149Bundle properties = info.getProperties(); 150String manufacturer = properties 151 .getString(MidiDeviceInfo.PROPERTY_MANUFACTURER); 152</pre> 153 154 155<p>Other properties include PROPERTY_PRODUCT, PROPERTY_NAME, 156PROPERTY_SERIAL_NUMBER</p> 157 158<p>You can get the names and types of the ports from a PortInfo object. 159The type will be either TYPE_INPUT or TYPE_OUTPUT.</p> 160 161<pre class=prettyprint> 162MidiDeviceInfo.PortInfo[] portInfos = info.getPorts(); 163String portName = portInfos[0].getName(); 164if (portInfos[0].getType() == MidiDeviceInfo.PortInfo.TYPE_INPUT) { 165 ... 166} 167</pre> 168 169 170<h2 id=open_a_midi_device>Open a MIDI Device</h2> 171 172 173<p>To access a MIDI device you need to open it first. The open is asynchronous so 174you need to provide a callback for completion. You can specify an optional 175Handler if you want the callback to occur on a specific Thread.</p> 176 177<pre class=prettyprint> 178m.openDevice(info, new MidiManager.OnDeviceOpenedListener() { 179 @Override 180 public void onDeviceOpened(MidiDevice device) { 181 if (device == null) { 182 Log.e(TAG, "could not open device " + info); 183 } else { 184 ... 185 } 186 }, new Handler(Looper.getMainLooper()) 187 ); 188</pre> 189 190 191<h2 id=open_a_midi_input_port>Open a MIDI Input Port</h2> 192 193 194<p>If you want to send a message to a MIDI Device then you need to open an “input” 195port with exclusive access.</p> 196 197<pre class=prettyprint> 198MidiInputPort inputPort = device.openInputPort(index); 199</pre> 200 201 202<h2 id=send_a_noteon>Send a NoteOn</h2> 203 204 205<p>MIDI messages are sent as byte arrays. Here we encode a NoteOn message.</p> 206 207<pre class=prettyprint> 208byte[] buffer = new byte[32]; 209int numBytes = 0; 210int channel = 3; // MIDI channels 1-16 are encoded as 0-15. 211buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on 212buffer[numBytes++] = (byte)60; // pitch is middle C 213buffer[numBytes++] = (byte)127; // max velocity 214int offset = 0; 215// post is non-blocking 216inputPort.send(buffer, offset, numBytes); 217</pre> 218 219 220<p>Sometimes it is convenient to send MIDI messages with a timestamp. By 221scheduling events in the future we can mask scheduling jitter. Android MIDI 222timestamps are based on the monotonic nanosecond system timer. This is 223consistent with the other audio and input timers.</p> 224 225<p>Here we send a message with a timestamp 2 seconds in the future.</p> 226 227<pre class=prettyprint> 228final long NANOS_PER_SECOND = 1000000000L; 229long now = System.nanoTime(); 230long future = now + (2 * NANOS_PER_SECOND); 231inputPort.send(buffer, offset, numBytes, future); 232</pre> 233 234 235<p>If you want to cancel events that you have scheduled in the future then call 236flush().</p> 237 238<pre class=prettyprint> 239inputPort.flush(); // discard events 240</pre> 241 242 243<p>If there were any MIDI NoteOff message left in the buffer then they will be 244discarded and you may get stuck notes. So we recommend sending “all notes off” 245after doing a flush.</p> 246 247<h2 id=receive_a_note>Receive a Note</h2> 248 249 250<p>To receive MIDI data from a device you need to extend MidiReceiver. Then 251connect your receiver to an output port of the device.</p> 252 253<pre class=prettyprint> 254class MyReceiver extends MidiReceiver { 255 public void onSend(byte[] data, int offset, 256 int count, long timestamp) throws IOException { 257 // parse MIDI or whatever 258 } 259} 260MidiOutputPort outputPort = device.openOutputPort(index); 261outputPort.connect(new MyReceiver()); 262</pre> 263 264 265<p>The data that arrives is not validated or aligned in any particular way. It is 266raw MIDI data and can contain multiple messages or partial messages. It might 267contain System Real-Time messages, which can be interleaved inside other 268messages.</p> 269 270<h1 id=using_midi_btle>Using MIDI Over Bluetooth LE</h1> 271 272<p>MIDI devices can be connected to Android using Bluetooth LE.</p> 273 274<p>Before using the device, the app must scan for available BTLE devices and then allow 275the user to connect. 276See the Android developer website for an 277<a href="https://source.android.com/devices/audio/midi_test#apps" target="_blank">example 278program</a>.</p> 279 280<h2 id=btle_location_permissions>Request Location Permission for BTLE</h2> 281 282<p>Applications that scan for Bluetooth devices must request permission in the 283manifest file. This LOCATION permission is required because it may be possible to 284guess the location of an Android device by seeing which BTLE devices are nearby.</p> 285 286<pre class=prettyprint> 287<uses-permission android:name="android.permission.BLUETOOTH"/> 288<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 289<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 290</pre> 291 292<p>Apps must also request location permission from the user at run-time. 293See the documentation for <code>Activity.requestPermissions()</code> for details and an example. 294</p> 295 296<h2 id=btle_scan_devices>Scan for MIDI Devices</h2> 297 298<p>The app will only want to see MIDI devices and not mice or other non-MIDI devices. 299So construct a ScanFilter using the UUID for standard MIDI over BTLE.</p> 300 301<pre class=prettyprint> 302MIDI over BTLE UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700" 303</pre> 304 305<h2 id=btle_open_device>Open a MIDI Bluetooth Device</h2> 306 307<p>See the documentation for <code>android.bluetooth.le.BluetoothLeScanner.startScan()</code> 308method for details. When the user selects a MIDI/BTLE device then you can open it 309using the MidiManager.</p> 310 311<pre class=prettyprint> 312m.openBluetoothDevice(bluetoothDevice, callback, handler); 313</pre> 314 315<p>Once the MIDI/BTLE device has been opened by one app then it will also become available to other 316apps using the 317<a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>. 318</p> 319 320<h1 id=creating_a_midi_virtual_device_service>Creating a MIDI Virtual Device Service</h1> 321 322 323<p>An app can provide a MIDI Service that can be used by other apps. For example, 324an app can provide a custom synthesizer that other apps can send messages to. 325The service must be guarded with permission "android.permission.BIND_MIDI_DEVICE_SERVICE".</p> 326 327<h2 id=manifest_files>Manifest Files</h2> 328 329 330<p>An app declares that it will function as a MIDI server in the 331AndroidManifest.xml file.</p> 332 333<pre class=prettyprint> 334<service android:name="<strong>MySynthDeviceService</strong>" 335 android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE" 336 android:exported="true"> 337 <intent-filter> 338 <action android:name="android.media.midi.MidiDeviceService" /> 339 </intent-filter> 340 <meta-data android:name="android.media.midi.MidiDeviceService" 341 android:resource="@xml/<strong>synth_device_info</strong>" /> 342</service> 343</pre> 344 345 346<p>The details of the resource in this example is stored in 347“res/xml/synth_device_info.xml”. The port names that you 348declare in this file will be available from PortInfo.getName().</p> 349 350<pre class=prettyprint> 351<devices> 352 <device manufacturer="MyCompany" product="MidiSynthBasic"> 353 <input-port name="input" /> 354 </device> 355</devices> 356</pre> 357 358 359<h2 id=extend_midideviceservice>Extend MidiDeviceService</h2> 360 361 362<p>You then define your server by extending android.media.midi.MidiDeviceService. 363Let‘s assume you have a MySynthEngine class that extends MidiReceiver.</p> 364 365<pre class=prettyprint> 366import android.media.midi.MidiDeviceService; 367import android.media.midi.MidiDeviceStatus; 368import android.media.midi.MidiReceiver; 369 370public class MidiSynthDeviceService extends MidiDeviceService { 371 private static final String TAG = "MidiSynthDeviceService"; 372 private MySynthEngine mSynthEngine = new MySynthEngine(); 373 private boolean synthStarted = false; 374 375 @Override 376 public void onCreate() { 377 super.onCreate(); 378 } 379 380 @Override 381 public void onDestroy() { 382 mSynthEngine.stop(); 383 super.onDestroy(); 384 } 385 386 @Override 387 // Declare the receivers associated with your input ports. 388 public MidiReceiver[] onGetInputPortReceivers() { 389 return new MidiReceiver[] { mSynthEngine }; 390 } 391 392 /** 393 * This will get called when clients connect or disconnect. 394 * You can use it to turn on your synth only when needed. 395 */ 396 @Override 397 public void onDeviceStatusChanged(MidiDeviceStatus status) { 398 if (status.isInputPortOpen(0) && !synthStarted) { 399 mSynthEngine.start(); 400 synthStarted = true; 401 } else if (!status.isInputPortOpen(0) && synthStarted){ 402 mSynthEngine.stop(); 403 synthStarted = false; 404 } 405 } 406} 407</pre> 408 409 410<h1 id=using_midi_2_0>Using MIDI 2.0</h1> 411 412<p>An app can use <a href= 413"https://www.midi.org/midi-articles/details-about-midi-2-0-midi-ci-profiles-and-property-exchange" 414class="external">MIDI 2.0</a> over USB starting in Android T. MIDI 2.0 packets are embedded in 415Universal MIDI Packets, or UMP for short. A MIDI 2.0 USB device should create two interfaces, 416one endpoint that accepts only MIDI 1.0 packets and one that accepts only UMP packets. 417For more info about MIDI 2.0 and UMP, please read the MIDI 2.0 and UMP spec. Starting from Android 418V, apps can also open <a href="#creating_a_midi_2_0_virtual_device_service"> MIDI 2.0 virtual</a> 419devices.</p> 420 421<p>MidiManager.getDevices() would simply return the 1.0 interface. This interface should work 422exactly the same as before. In order to use the new UMP interface, retrieve the device with the 423following code snippet.</p> 424 425<pre class=prettyprint> 426Collection<MidiDeviceInfo> universalDeviceInfos = midiManager.getDevicesForTransport( 427 MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS); 428</pre> 429 430<p>UMP packet sizes are always a multiple of 4 bytes. For each set of 4 bytes, they are sent in 431network order. Compare the following NoteOn code snippet with the NoteOn code snippet above. </p> 432 433<pre class=prettyprint> 434byte[] buffer = new byte[32]; 435int numBytes = 0; 436int channel = 3; // MIDI channels 1-16 are encoded as 0-15. 437int group = 0; 438buffer[numBytes++] = (byte)(0x20 + group); // MIDI 1.0 Channel Voice Message 439buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on 440buffer[numBytes++] = (byte)60; // pitch is middle C 441buffer[numBytes++] = (byte)127; // max velocity 442int offset = 0; 443// post is non-blocking 444inputPort.send(buffer, offset, numBytes); 445</pre> 446 447<p>MIDI 2.0 messages can be sent through UMP after negotiating with the device. This is called 448MIDI-CI and is documented in the MIDI 2.0 spec. Some USB devices support pre-negotiated MIDI 2.0. 449For a MidiDeviceInfo, you can query the defaultProtocol.</p> 450 451<pre class=prettyprint> 452int defaultProtocol = info.getDefaultProtocol(); 453</pre> 454 455<p>To register for callbacks when MIDI 2.0 devices are added or removed, use 456MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS as the transport.</p> 457 458<pre class=prettyprint> 459midiManager.registerDeviceCallback( 460 MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS, executor, callback); 461</pre> 462 463<h1 id=creating_a_midi_2_0_virtual_device_service>Creating a MIDI 2.0 Virtual Device Service</h1> 464 465 466<p>Starting in Android V, an app can provide a MIDI 2.0 Service that can be used by other apps. 467MIDI 2.0 packets are embedded in Universal MIDI Packets, or UMP for short. The service must be 468guarded with permission "android.permission.BIND_MIDI_DEVICE_SERVICE".</p> 469 470<h2 id=manifest_files>Manifest Files</h2> 471 472 473<p>An app declares that it will function as a MIDI server in the AndroidManifest.xml file. Unlike 474MIDI 1.0 virtual devices, android.media.midi.MidiUmpDeviceService is used</p> 475 476<pre class=prettyprint> 477<service android:name="<strong>MidiEchoDeviceService</strong>" 478 android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE" 479 android:exported="true"> 480 <intent-filter> 481 <action android:name="android.media.midi.MidiUmpDeviceService" /> 482 </intent-filter> 483 <property android:name="android.media.midi.MidiUmpDeviceService" 484 android:resource="@xml/<strong>echo_device_info</strong>" /> 485</service> 486</pre> 487 488 489<p>The details of the resource in this example is stored in “res/xml/echo_device_info.xml 490”. The port names that you declare in this file will be available from PortInfo.getName(). 491Unlike MIDI 1.0, MIDI 2.0 ports are bidirectional. If you declare a port in this service, then it 492automatically creates an input port and an output port with the same name. Clients can use those 493two ports like the MIDI 1.0 ports.</p> 494 495<pre class=prettyprint> 496<devices> 497 <device manufacturer="MyCompany" product="MidiEcho"> 498 <port name="port1" /> 499 </device> 500</devices> 501</pre> 502 503 504<h2 id=extend_midiumpdeviceservice>Extend MidiUmpDeviceService</h2> 505 506 507<p>You then define your server by extending android.media.midi.MidiUmpDeviceService.</p> 508 509<pre class=prettyprint> 510import android.media.midi.MidiDeviceStatus; 511import android.media.midi.MidiReceiver; 512import android.media.midi.MidiUmpDeviceService; 513 514import java.io.IOException; 515import java.util.ArrayList; 516import java.util.Collections; 517import java.util.List; 518 519public class MidiEchoDeviceService extends MidiUmpDeviceService { 520 private static final String TAG = "MidiEchoDeviceService"; 521 // Other apps will write to this port. 522 private MidiReceiver mInputReceiver = new MyReceiver(); 523 // This app will copy the data to this port. 524 private MidiReceiver mOutputReceiver; 525 526 @Override 527 public void onCreate() { 528 super.onCreate(); 529 } 530 531 @Override 532 public void onDestroy() { 533 super.onDestroy(); 534 } 535 536 @Override 537 // Declare the receivers associated with your input ports. 538 public List<MidiReceiver> onGetInputPortReceivers() { 539 return new ArrayList<MidiReceiver>(Collections.singletonList(mInputReceiver)); 540 } 541 542 /** 543 * Sample receiver to echo from the input port to the output port. 544 * In this example, we are just echoing the data and not parsing it. 545 * You will probably want to convert the bytes to a packet and then interpret the packet. 546 * See the MIDI 2.0 spec at the MMA site. Packets are either 4, 8, 12 or 16 bytes. 547 */ 548 class MyReceiver extends MidiReceiver { 549 @Override 550 public void onSend(byte[] data, int offset, int count, long timestamp) 551 throws IOException { 552 if (mOutputReceiver == null) { 553 mOutputReceiver = getOutputPortReceivers().get(0); 554 } 555 // Copy input to output. 556 mOutputReceiver.send(data, offset, count, timestamp); 557 } 558 } 559 560 /** 561 * This will get called when clients connect or disconnect. 562 * You can use it to figure out how many devices are connected. 563 */ 564 @Override 565 public void onDeviceStatusChanged(MidiDeviceStatus status) { 566 // inputOpened = status.isInputPortOpen(0); 567 // outputOpenCount = status.getOutputPortOpenCount(0); 568 } 569} 570</pre> 571 572</body> 573</html> 574