1 <html> 2 <body> 3 <p>Android MIDI User Guide</p> 4 5 <h1 id=overview>Overview</h1> 6 7 8 <p>This document describes how to use the Android MIDI API in Java.</p> 9 10 <p>The Android MIDI package allows users to:</p> 11 12 <ul> 13 <li> Connect a MIDI keyboard to Android to play a synthesizer or drive music apps.</li> 14 <li> Connect alternative MIDI controllers to Android.</li> 15 <li> Drive external MIDI synths from Android.</li> 16 <li> Drive external peripherals, lights, show control, etc from Android.</li> 17 <li> Generate music dynamically from games or music creation apps.</li> 18 <li> Generate MIDI messages in one app and send them to a second app.</li> 19 <li> Use an Android device running in <em>peripheral mode</em> as a multitouch controller 20 connected to a laptop.</li> 21 </ul> 22 23 <h2 id=the_api_features_include>The API features include:</h2> 24 25 <ul> 26 <li> Enumeration of currently available devices. Information includes name, vendor, 27 capabilities, etc.</li> 28 <li> Provide notification when MIDI devices are plugged in or unplugged.</li> 29 <li> Support efficient transmission of single or multiple short 1-3 byte MIDI messages.</li> 30 <li> Support transmission of arbitrary length data for SysEx, etc.</li> 31 <li> Timestamps to avoid jitter.</li> 32 <li> Support creation of <em>virtual MIDI devices</em> that can be connected to other devices. 33 An example might be a synthesizer app that can be controlled by a composing app.</li> 34 <li> Support direct connection or “patching” of devices for lower latency.</li> 35 </ul> 36 37 <h2 id=transports_supported>Transports Supported</h2> 38 39 40 <p>The API is “transport agnostic”. But there are several transports currently 41 supported:</p> 42 43 <ul> 44 <li> USB 45 <li> software routing 46 <li> BTLE 47 </ul> 48 49 <h1 id=android_midi_terminology>Android MIDI Terminology</h1> 50 51 52 <h2 id=terminology>Terminology</h2> 53 54 55 <p>A <strong>Device</strong> is a MIDI capable object that has zero or more InputPorts and OutputPorts.</p> 56 57 <p>An <strong>InputPort</strong> has 16 channels and can <strong>receive</strong> MIDI messages from an OutputPort or an app.</p> 58 59 <p>An <strong>OutputPort</strong> has 16 channels and can <strong>send</strong> MIDI messages to an InputPort or an app.</p> 60 61 <p><strong>MidiService</strong> is a centralized process that keeps track of all devices and brokers 62 communication between them.</p> 63 64 <p><strong>MidiManager</strong> is a class that the application or a device manager calls to communicate with 65 the MidiService.</p> 66 67 <h1 id=writing_a_midi_application>Writing a MIDI Application</h1> 68 69 <h2 id=manifest_feature>Declare Feature in Manifest</h2> 70 71 <p>An app that requires the MIDI API should declare that in the AndroidManifest.xml file. 72 Then the app will not appear in the Play Store for old devices that do not support the MIDI API.</p> 73 74 <pre class=prettyprint> 75 <uses-feature android:name="android.software.midi" android:required="true"/> 76 </pre> 77 78 <h2 id=check_feature>Check for Feature Support</h2> 79 80 <p>An app can also check at run-time whether the MIDI feature is supported on a platform. 81 This is particularly useful during development when you install apps directly on a device. 82 </p> 83 84 <pre class=prettyprint> 85 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) { 86 // do MIDI stuff 87 } 88 </pre> 89 90 <h2 id=the_midimanager>The MidiManager</h2> 91 92 93 <p>The primary class for accessing the MIDI package is through the MidiManager.</p> 94 95 <pre class=prettyprint> 96 MidiManager m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 97 </pre> 98 99 100 <h2 id=get_list_of_already_plugged_in_entities>Get List of Already Plugged In Entities</h2> 101 102 103 <p>When an app starts, it can get a list of all the available MIDI devices. This 104 information can be presented to a user, allowing them to choose a device.</p> 105 106 <pre class=prettyprint> 107 MidiDeviceInfo[] infos = m.getDevices(); 108 </pre> 109 110 111 <h2 id=notification_of_midi_devices_hotplug_events>Notification of MIDI Devices HotPlug Events</h2> 112 113 114 <p>The application can request notification when, for example, keyboards are 115 plugged in or unplugged.</p> 116 117 <pre class=prettyprint> 118 m.registerDeviceCallback(new MidiManager.DeviceCallback() { 119 public void onDeviceAdded( MidiDeviceInfo info ) { 120 ... 121 } 122 public void onDeviceRemoved( MidiDeviceInfo info ) { 123 ... 124 } 125 }); 126 </pre> 127 128 129 <h2 id=device_and_port_information>Device and Port Information</h2> 130 131 132 <p>You can query the number of input and output ports.</p> 133 134 <pre class=prettyprint> 135 int numInputs = info.getInputPortCount(); 136 int numOutputs = info.getOutputPortCount(); 137 </pre> 138 139 140 <p>Note that “input” and “output” are from the standpoint of the device. So a 141 synthesizer will have an “input” port that receives messages. A keyboard will 142 have an “output” port that sends messages.</p> 143 144 <p>The MidiDeviceInfo has a bundle of properties.</p> 145 146 <pre class=prettyprint> 147 Bundle properties = info.getProperties(); 148 String manufacturer = properties 149 .getString(MidiDeviceInfo.PROPERTY_MANUFACTURER); 150 </pre> 151 152 153 <p>Other properties include PROPERTY_PRODUCT, PROPERTY_NAME, 154 PROPERTY_SERIAL_NUMBER</p> 155 156 <p>You can get the names and types of the ports from a PortInfo object. 157 The type will be either TYPE_INPUT or TYPE_OUTPUT.</p> 158 159 <pre class=prettyprint> 160 MidiDeviceInfo.PortInfo[] portInfos = info.getPorts(); 161 String portName = portInfos[0].getName(); 162 if (portInfos[0].getType() == MidiDeviceInfo.PortInfo.TYPE_INPUT) { 163 ... 164 } 165 </pre> 166 167 168 <h2 id=open_a_midi_device>Open a MIDI Device</h2> 169 170 171 <p>To access a MIDI device you need to open it first. The open is asynchronous so 172 you need to provide a callback for completion. You can specify an optional 173 Handler if you want the callback to occur on a specific Thread.</p> 174 175 <pre class=prettyprint> 176 m.openDevice(info, new MidiManager.OnDeviceOpenedListener() { 177 @Override 178 public void onDeviceOpened(MidiDevice device) { 179 if (device == null) { 180 Log.e(TAG, "could not open device " + info); 181 } else { 182 ... 183 }, new Handler(Looper.getMainLooper()) 184 ); 185 </pre> 186 187 188 <h2 id=open_a_midi_input_port>Open a MIDI Input Port</h2> 189 190 191 <p>If you want to send a message to a MIDI Device then you need to open an “input” 192 port with exclusive access.</p> 193 194 <pre class=prettyprint> 195 MidiInputPort inputPort = device.openInputPort(index); 196 </pre> 197 198 199 <h2 id=send_a_noteon>Send a NoteOn</h2> 200 201 202 <p>MIDI messages are sent as byte arrays. Here we encode a NoteOn message.</p> 203 204 <pre class=prettyprint> 205 byte[] buffer = new byte[32]; 206 int numBytes = 0; 207 int channel = 3; // MIDI channels 1-16 are encoded as 0-15. 208 buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on 209 buffer[numBytes++] = (byte)60; // pitch is middle C 210 buffer[numBytes++] = (byte)127; // max velocity 211 int offset = 0; 212 // post is non-blocking 213 inputPort.send(buffer, offset, numBytes); 214 </pre> 215 216 217 <p>Sometimes it is convenient to send MIDI messages with a timestamp. By 218 scheduling events in the future we can mask scheduling jitter. Android MIDI 219 timestamps are based on the monotonic nanosecond system timer. This is 220 consistent with the other audio and input timers.</p> 221 222 <p>Here we send a message with a timestamp 2 seconds in the future.</p> 223 224 <pre class=prettyprint> 225 final long NANOS_PER_SECOND = 1000000000L; 226 long now = System.nanoTime(); 227 long future = now + (2 * NANOS_PER_SECOND); 228 inputPort.send(buffer, offset, numBytes, future); 229 </pre> 230 231 232 <p>If you want to cancel events that you have scheduled in the future then call 233 flush().</p> 234 235 <pre class=prettyprint> 236 inputPort.flush(); // discard events 237 </pre> 238 239 240 <p>If there were any MIDI NoteOff message left in the buffer then they will be 241 discarded and you may get stuck notes. So we recommend sending “all notes off” 242 after doing a flush.</p> 243 244 <h2 id=receive_a_note>Receive a Note</h2> 245 246 247 <p>To receive MIDI data from a device you need to extend MidiReceiver. Then 248 connect your receiver to an output port of the device.</p> 249 250 <pre class=prettyprint> 251 class MyReceiver extends MidiReceiver { 252 public void onSend(byte[] data, int offset, 253 int count, long timestamp) throws IOException { 254 // parse MIDI or whatever 255 } 256 } 257 MidiOutputPort outputPort = device.openOutputPort(index); 258 outputPort.connect(new MyReceiver()); 259 </pre> 260 261 262 <p>The data that arrives is not validated or aligned in any particular way. It is 263 raw MIDI data and can contain multiple messages or partial messages. It might 264 contain System Real-Time messages, which can be interleaved inside other 265 messages.</p> 266 267 <h1 id=creating_a_midi_virtual_device_service>Creating a MIDI Virtual Device Service</h1> 268 269 270 <p>An app can provide a MIDI Service that can be used by other apps. For example, 271 an app can provide a custom synthesizer that other apps can send messages to. 272 The service must be guarded with permission "android.permission.BIND_MIDI_DEVICE_SERVICE".</p> 273 274 <h2 id=manifest_files>Manifest Files</h2> 275 276 277 <p>An app declares that it will function as a MIDI server in the 278 AndroidManifest.xml file.</p> 279 280 <pre class=prettyprint> 281 <service android:name="<strong>MySynthDeviceService</strong>" 282 android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> 283 <intent-filter> 284 <action android:name="android.media.midi.MidiDeviceService" /> 285 </intent-filter> 286 <meta-data android:name="android.media.midi.MidiDeviceService" 287 android:resource="@xml/<strong>synth_device_info</strong>" /> 288 </service> 289 </pre> 290 291 292 <p>The details of the resource in this example is stored in 293 “res/xml/synth_device_info.xml”. The port names that you 294 declare in this file will be available from PortInfo.getName().</p> 295 296 <pre class=prettyprint> 297 <devices> 298 <device manufacturer="MyCompany" product="MidiSynthBasic"> 299 <input-port name="input" /> 300 </device> 301 </devices> 302 </pre> 303 304 305 <h2 id=extend_midideviceservice>Extend MidiDeviceService</h2> 306 307 308 <p>You then define your server by extending android.media.midi.MidiDeviceService. 309 Let‘s assume you have a MySynthEngine class that extends MidiReceiver.</p> 310 311 <pre class=prettyprint> 312 import android.media.midi.MidiDeviceService; 313 import android.media.midi.MidiDeviceStatus; 314 import android.media.midi.MidiReceiver; 315 316 public class MidiSynthDeviceService extends MidiDeviceService { 317 private static final String TAG = "MidiSynthDeviceService"; 318 private MySynthEngine mSynthEngine = new MySynthEngine(); 319 private boolean synthStarted = false; 320 321 @Override 322 public void onCreate() { 323 super.onCreate(); 324 } 325 326 @Override 327 public void onDestroy() { 328 mSynthEngine.stop(); 329 super.onDestroy(); 330 } 331 332 @Override 333 // Declare the receivers associated with your input ports. 334 public MidiReceiver[] onGetInputPortReceivers() { 335 return new MidiReceiver[] { mSynthEngine }; 336 } 337 338 /** 339 * This will get called when clients connect or disconnect. 340 * You can use it to turn on your synth only when needed. 341 */ 342 @Override 343 public void onDeviceStatusChanged(MidiDeviceStatus status) { 344 if (status.isInputPortOpen(0) && !synthStarted) { 345 mSynthEngine.start(); 346 synthStarted = true; 347 } else if (!status.isInputPortOpen(0) && synthStarted){ 348 mSynthEngine.stop(); 349 synthStarted = false; 350 } 351 } 352 } 353 </pre> 354 355 <h1 id=using_midi_btle>Using MIDI Over Bluetooth LE</h1> 356 357 <p>MIDI devices can be connected to Android using Bluetooth LE.</p> 358 359 <p>Before using the device, the app must scan for available BTLE devices and then allow 360 the user to connect. An example program 361 will be provided so look for it on the Android developer website.</p> 362 363 <h2 id=btle_location_permissions>Request Location Permission for BTLE</h2> 364 365 <p>Applications that scan for Bluetooth devices must request permission in the 366 manifest file. This LOCATION permission is required because it may be possible to 367 guess the location of an Android device by seeing which BTLE devices are nearby.</p> 368 369 <pre class=prettyprint> 370 <uses-permission android:name="android.permission.BLUETOOTH"/> 371 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> 372 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 373 </pre> 374 375 <p>Apps must also request location permission from the user at run-time. 376 See the documentation for <code>Activity.requestPermissions()</code> for details and an example. 377 </p> 378 379 <h2 id=btle_scan_devices>Scan for MIDI Devices</h2> 380 381 <p>The app will only want to see MIDI devices and not mice or other non-MIDI devices. 382 So construct a ScanFilter using the UUID for standard MIDI over BTLE.</p> 383 384 <pre class=prettyprint> 385 MIDI over BTLE UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700" 386 </pre> 387 388 <h2 id=btle_open_device>Open a MIDI Bluetooth Device</h2> 389 390 <p>See the documentation for <code>android.bluetooth.le.BluetoothLeScanner.startScan()</code> 391 method for details. When the user selects a MIDI/BTLE device then you can open it 392 using the MidiManager.</p> 393 394 <pre class=prettyprint> 395 m.openBluetoothDevice(bluetoothDevice, callback, handler); 396 </pre> 397 398 <p>Once the MIDI/BTLE device has been opened by one app then it will also become available to other 399 apps using the 400 <a href="#get_list_of_already_plugged_in_entities">MIDI device discovery calls described above</a>. 401 </p> 402 403 </body> 404 </html> 405