xref: /aosp_15_r20/frameworks/base/media/java/android/media/midi/package.html (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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 &ldquo;patching&rdquo; of devices for lower latency.</li>
36</ul>
37
38<h2 id=transports_supported>Transports Supported</h2>
39
40
41<p>The API is &ldquo;transport agnostic&rdquo;. 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&lt;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 &ldquo;input&rdquo; and &ldquo;output&rdquo; 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    &#64;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 &ldquo;input&rdquo;
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 &ldquo;all notes off&rdquo;
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&lt;uses-permission android:name="android.permission.BLUETOOTH"/>
288&lt;uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
289&lt;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 &quot;android.permission.BIND_MIDI_DEVICE_SERVICE&quot;.</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&lt;service android:name="<strong>MySynthDeviceService</strong>"
335  android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
336  android:exported="true">
337  &lt;intent-filter>
338    &lt;action android:name="android.media.midi.MidiDeviceService" />
339  &lt;/intent-filter>
340  &lt;meta-data android:name="android.media.midi.MidiDeviceService"
341      android:resource="@xml/<strong>synth_device_info</strong>" />
342&lt;/service>
343</pre>
344
345
346<p>The details of the resource in this example is stored in
347&ldquo;res/xml/synth_device_info.xml&rdquo;. The port names that you
348declare in this file will be available from PortInfo.getName().</p>
349
350<pre class=prettyprint>
351&lt;devices>
352    &lt;device manufacturer="MyCompany" product="MidiSynthBasic">
353        &lt;input-port name="input" />
354    &lt;/device>
355&lt;/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&lsquo;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    &#64;Override
376    public void onCreate() {
377        super.onCreate();
378    }
379
380    &#64;Override
381    public void onDestroy() {
382        mSynthEngine.stop();
383        super.onDestroy();
384    }
385
386    &#64;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    &#64;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&#60;MidiDeviceInfo&#62; 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 &quot;android.permission.BIND_MIDI_DEVICE_SERVICE&quot;.</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&lt;service android:name="<strong>MidiEchoDeviceService</strong>"
478  android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
479  android:exported="true">
480  &lt;intent-filter>
481    &lt;action android:name="android.media.midi.MidiUmpDeviceService" />
482  &lt;/intent-filter>
483  &lt;property android:name="android.media.midi.MidiUmpDeviceService"
484      android:resource="@xml/<strong>echo_device_info</strong>" />
485&lt;/service>
486</pre>
487
488
489<p>The details of the resource in this example is stored in &ldquo;res/xml/echo_device_info.xml
490&rdquo;. 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&lt;devices>
497    &lt;device manufacturer="MyCompany" product="MidiEcho">
498        &lt;port name="port1" />
499    &lt;/device>
500&lt;/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    &#64;Override
527    public void onCreate() {
528        super.onCreate();
529    }
530
531    &#64;Override
532    public void onDestroy() {
533        super.onDestroy();
534    }
535
536    &#64;Override
537    // Declare the receivers associated with your input ports.
538    public List&lt;MidiReceiver> onGetInputPortReceivers() {
539        return new ArrayList&lt;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        &#64;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    &#64;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