# Android USB MIDI Driver
Android USB MIDI Driver is a library that enables Android applications to communicate with USB MIDI devices without requiring root privileges. It leverages the Android USB Host API (available on Android 3.1+) to connect to standard USB MIDI devices like synthesizers, keyboards, sequencers, and instruments. The library also supports non-standard USB MIDI devices from manufacturers like YAMAHA, Roland, and MOTU through custom device filters.
The driver provides multiple integration patterns including Activity-based, Service-based, Fragment-based, and standalone driver implementations. It supports both single and multiple device connections simultaneously, offers `javax.sound.midi` compatible classes for Java developers familiar with desktop MIDI programming, and handles all the low-level USB protocol details including virtual MIDI cables (0-15) that extend MIDI channel capabilities.
## Installation and Dependencies
Add the library to your Android project using JitPack.
```gradle
// In your root build.gradle
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
// In your app's build.gradle
dependencies {
implementation 'com.github.kshoji:USB-MIDI-Driver:0.1.14'
}
```
## AndroidManifest Configuration
Configure the manifest to enable USB host mode and set the proper activity launch mode.
```xml
```
## AbstractSingleMidiActivity
Base Activity class for applications that connect to a single USB MIDI device. Automatically handles device attachment/detachment and provides callback methods for all MIDI events.
```java
public class MySingleMidiActivity extends AbstractSingleMidiActivity {
// Handler for UI updates from MIDI callback threads
private final Handler midiEventHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
TextView textView = findViewById(R.id.midiEventText);
textView.setText((String) msg.obj);
return true;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_midi);
}
// Called when a MIDI output device is attached
@Override
public void onMidiOutputDeviceAttached(@NonNull MidiOutputDevice midiOutputDevice) {
Toast.makeText(this, "MIDI Device connected: " +
midiOutputDevice.getProductName(), Toast.LENGTH_SHORT).show();
}
// Called when a MIDI output device is detached
@Override
public void onMidiOutputDeviceDetached(@NonNull MidiOutputDevice midiOutputDevice) {
Toast.makeText(this, "MIDI Device disconnected", Toast.LENGTH_SHORT).show();
}
@Override
public void onMidiInputDeviceAttached(@NonNull MidiInputDevice midiInputDevice) {
// Input device ready to receive MIDI events
}
@Override
public void onMidiInputDeviceDetached(@NonNull MidiInputDevice midiInputDevice) {
// Input device disconnected
}
// Note On event callback (called from background thread)
@Override
public void onMidiNoteOn(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
String message = String.format("Note On - Channel: %d, Note: %d, Velocity: %d",
channel, note, velocity);
midiEventHandler.sendMessage(Message.obtain(midiEventHandler, 0, message));
// Play sound or trigger action based on note
playNote(note, velocity);
}
// Note Off event callback
@Override
public void onMidiNoteOff(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
String message = String.format("Note Off - Channel: %d, Note: %d", channel, note);
midiEventHandler.sendMessage(Message.obtain(midiEventHandler, 0, message));
stopNote(note);
}
// Control Change callback (knobs, sliders, pedals)
@Override
public void onMidiControlChange(@NonNull MidiInputDevice sender, int cable,
int channel, int function, int value) {
// function: CC number (0-127), value: 0-127
handleControlChange(function, value);
}
// Program Change callback (instrument selection)
@Override
public void onMidiProgramChange(@NonNull MidiInputDevice sender, int cable,
int channel, int program) {
// program: 0-127
selectInstrument(program);
}
// Pitch Bend callback
@Override
public void onMidiPitchWheel(@NonNull MidiInputDevice sender, int cable,
int channel, int amount) {
// amount: 0 (lowest) - 8192 (center) - 16383 (highest)
applyPitchBend(amount);
}
// System Exclusive message callback
@Override
public void onMidiSystemExclusive(@NonNull MidiInputDevice sender, int cable,
byte[] systemExclusive) {
// Process SysEx data (starts with 0xF0, ends with 0xF7)
processSysEx(systemExclusive);
}
// Required callback implementations
@Override
public void onDeviceAttached(@NonNull UsbDevice usbDevice) { }
@Override
public void onDeviceDetached(@NonNull UsbDevice usbDevice) { }
@Override
public void onMidiMiscellaneousFunctionCodes(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override
public void onMidiCableEvents(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override
public void onMidiSystemCommonMessage(@NonNull MidiInputDevice sender,
int cable, byte[] bytes) { }
@Override
public void onMidiSingleByte(@NonNull MidiInputDevice sender, int cable, int byte1) { }
@Override
public void onMidiPolyphonicAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int note, int pressure) { }
@Override
public void onMidiChannelAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int pressure) { }
@Override
public void onMidiTimeCodeQuarterFrame(@NonNull MidiInputDevice sender,
int cable, int timing) { }
@Override
public void onMidiSongSelect(@NonNull MidiInputDevice sender, int cable, int song) { }
@Override
public void onMidiSongPositionPointer(@NonNull MidiInputDevice sender,
int cable, int position) { }
@Override
public void onMidiTuneRequest(@NonNull MidiInputDevice sender, int cable) { }
@Override
public void onMidiTimingClock(@NonNull MidiInputDevice sender, int cable) { }
@Override
public void onMidiStart(@NonNull MidiInputDevice sender, int cable) { }
@Override
public void onMidiContinue(@NonNull MidiInputDevice sender, int cable) { }
@Override
public void onMidiStop(@NonNull MidiInputDevice sender, int cable) { }
@Override
public void onMidiActiveSensing(@NonNull MidiInputDevice sender, int cable) { }
@Override
public void onMidiReset(@NonNull MidiInputDevice sender, int cable) { }
}
```
## AbstractMultipleMidiActivity
Base Activity class for applications that need to connect to multiple USB MIDI devices simultaneously. Provides Set collections for managing multiple input and output devices.
```java
public class MyMultipleMidiActivity extends AbstractMultipleMidiActivity {
private ArrayAdapter deviceAdapter;
private Map outputDeviceMap = new HashMap<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi_midi);
// Setup device list
Spinner deviceSpinner = findViewById(R.id.deviceSpinner);
deviceAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
deviceSpinner.setAdapter(deviceAdapter);
}
@Override
public void onMidiOutputDeviceAttached(@NonNull MidiOutputDevice midiOutputDevice) {
String deviceName = midiOutputDevice.getProductName();
if (deviceName == null) {
deviceName = midiOutputDevice.getDeviceAddress();
}
outputDeviceMap.put(deviceName, midiOutputDevice);
deviceAdapter.add(deviceName);
deviceAdapter.notifyDataSetChanged();
}
@Override
public void onMidiOutputDeviceDetached(@NonNull MidiOutputDevice midiOutputDevice) {
String deviceName = midiOutputDevice.getProductName();
if (deviceName == null) {
deviceName = midiOutputDevice.getDeviceAddress();
}
outputDeviceMap.remove(deviceName);
deviceAdapter.remove(deviceName);
deviceAdapter.notifyDataSetChanged();
}
@Override
public void onMidiInputDeviceAttached(@NonNull MidiInputDevice midiInputDevice) {
// Each input device will trigger callbacks when receiving MIDI
Log.d("MIDI", "Input device attached: " + midiInputDevice.getProductName());
}
@Override
public void onMidiInputDeviceDetached(@NonNull MidiInputDevice midiInputDevice) {
Log.d("MIDI", "Input device detached: " + midiInputDevice.getProductName());
}
// Note events include the sender device for identification
@Override
public void onMidiNoteOn(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
String deviceName = sender.getProductName();
Log.d("MIDI", "Note On from " + deviceName + ": " + note);
// Forward to all output devices (MIDI thru)
for (MidiOutputDevice outputDevice : getMidiOutputDevices()) {
outputDevice.sendMidiNoteOn(cable, channel, note, velocity);
}
}
@Override
public void onMidiNoteOff(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
for (MidiOutputDevice outputDevice : getMidiOutputDevices()) {
outputDevice.sendMidiNoteOff(cable, channel, note, velocity);
}
}
// Get all connected input devices
public void listConnectedDevices() {
Set inputDevices = getMidiInputDevices();
Set outputDevices = getMidiOutputDevices();
for (MidiInputDevice device : inputDevices) {
Log.d("MIDI", "Input: " + device.getProductName() +
" (" + device.getManufacturerName() + ")");
}
for (MidiOutputDevice device : outputDevices) {
Log.d("MIDI", "Output: " + device.getProductName() +
" (" + device.getManufacturerName() + ")");
}
}
// Remaining callbacks...
@Override public void onDeviceAttached(@NonNull UsbDevice usbDevice) { }
@Override public void onDeviceDetached(@NonNull UsbDevice usbDevice) { }
@Override public void onMidiMiscellaneousFunctionCodes(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiCableEvents(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiSystemCommonMessage(@NonNull MidiInputDevice sender,
int cable, byte[] bytes) { }
@Override public void onMidiSingleByte(@NonNull MidiInputDevice sender, int cable, int byte1) { }
@Override public void onMidiPolyphonicAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int note, int pressure) { }
@Override public void onMidiChannelAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int pressure) { }
@Override public void onMidiControlChange(@NonNull MidiInputDevice sender,
int cable, int channel, int function, int value) { }
@Override public void onMidiProgramChange(@NonNull MidiInputDevice sender,
int cable, int channel, int program) { }
@Override public void onMidiPitchWheel(@NonNull MidiInputDevice sender,
int cable, int channel, int amount) { }
@Override public void onMidiSystemExclusive(@NonNull MidiInputDevice sender,
int cable, byte[] systemExclusive) { }
@Override public void onMidiTimeCodeQuarterFrame(@NonNull MidiInputDevice sender,
int cable, int timing) { }
@Override public void onMidiSongSelect(@NonNull MidiInputDevice sender, int cable, int song) { }
@Override public void onMidiSongPositionPointer(@NonNull MidiInputDevice sender,
int cable, int position) { }
@Override public void onMidiTuneRequest(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiTimingClock(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStart(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiContinue(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStop(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiActiveSensing(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiReset(@NonNull MidiInputDevice sender, int cable) { }
}
```
## MidiOutputDevice - Sending MIDI Messages
The MidiOutputDevice class provides methods for sending all types of MIDI messages to connected devices. All parameters use standard MIDI ranges: cable (0-15), channel (0-15), note/velocity/values (0-127).
```java
public class MidiSender {
private MidiOutputDevice midiOutputDevice;
public void setOutputDevice(MidiOutputDevice device) {
this.midiOutputDevice = device;
}
// Send Note On message
public void sendNoteOn(int channel, int note, int velocity) {
if (midiOutputDevice != null) {
int cable = 0; // Virtual cable ID (0-15)
midiOutputDevice.sendMidiNoteOn(cable, channel, note, velocity);
}
}
// Send Note Off message
public void sendNoteOff(int channel, int note, int velocity) {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiNoteOff(0, channel, note, velocity);
}
}
// Send Control Change (CC) message
public void sendControlChange(int channel, int controller, int value) {
if (midiOutputDevice != null) {
// Common controllers: 1=Mod Wheel, 7=Volume, 10=Pan, 64=Sustain
midiOutputDevice.sendMidiControlChange(0, channel, controller, value);
}
}
// Send Program Change (instrument select)
public void sendProgramChange(int channel, int program) {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiProgramChange(0, channel, program);
}
}
// Send Pitch Bend
public void sendPitchBend(int channel, int amount) {
if (midiOutputDevice != null) {
// amount: 0 (lowest) - 8192 (center) - 16383 (highest)
midiOutputDevice.sendMidiPitchWheel(0, channel, amount);
}
}
// Send Polyphonic Aftertouch (per-note pressure)
public void sendPolyAftertouch(int channel, int note, int pressure) {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiPolyphonicAftertouch(0, channel, note, pressure);
}
}
// Send Channel Aftertouch (channel-wide pressure)
public void sendChannelAftertouch(int channel, int pressure) {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiChannelAftertouch(0, channel, pressure);
}
}
// Send System Exclusive message
public void sendSysEx(byte[] sysexData) {
if (midiOutputDevice != null) {
// SysEx must start with 0xF0 and end with 0xF7
midiOutputDevice.sendMidiSystemExclusive(0, sysexData);
}
}
// Send RPN (Registered Parameter Number) message
public void sendRPN(int channel, int parameterNumber, int value) {
if (midiOutputDevice != null) {
// Common RPNs: 0=Pitch Bend Sensitivity, 1=Fine Tuning, 2=Coarse Tuning
midiOutputDevice.sendRPNMessage(0, channel, parameterNumber, value);
}
}
// Send NRPN (Non-Registered Parameter Number) message
public void sendNRPN(int channel, int parameterNumber, int value) {
if (midiOutputDevice != null) {
midiOutputDevice.sendNRPNMessage(0, channel, parameterNumber, value);
}
}
// Transport control messages
public void sendStart() {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiStart(0);
}
}
public void sendStop() {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiStop(0);
}
}
public void sendContinue() {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiContinue(0);
}
}
public void sendTimingClock() {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiTimingClock(0);
}
}
// Send Song Position Pointer
public void sendSongPosition(int position) {
if (midiOutputDevice != null) {
// position: 0-16383 (in MIDI beats, 6 per quarter note)
midiOutputDevice.sendMidiSongPositionPointer(0, position);
}
}
// Send Song Select
public void sendSongSelect(int song) {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiSongSelect(0, song);
}
}
// Send raw MIDI message (3 bytes)
public void sendRawMidi(int byte1, int byte2, int byte3) {
if (midiOutputDevice != null) {
midiOutputDevice.sendMidiMessage(0, byte1, byte2, byte3);
}
}
}
```
## UsbMidiDriver - Standalone Driver
A standalone driver class that can be used without extending Activity. Useful for Service-based architectures or custom implementations.
```java
public class MidiManager {
private UsbMidiDriver midiDriver;
private OnMidiEventCallback callback;
public interface OnMidiEventCallback {
void onNoteOn(int channel, int note, int velocity);
void onNoteOff(int channel, int note, int velocity);
void onControlChange(int channel, int controller, int value);
}
public void initialize(Context context, OnMidiEventCallback callback) {
this.callback = callback;
midiDriver = new UsbMidiDriver(context) {
@Override
public void onDeviceAttached(@NonNull UsbDevice usbDevice) {
Log.d("MIDI", "Device attached: " + usbDevice.getDeviceName());
}
@Override
public void onDeviceDetached(@NonNull UsbDevice usbDevice) {
Log.d("MIDI", "Device detached: " + usbDevice.getDeviceName());
}
@Override
public void onMidiInputDeviceAttached(@NonNull MidiInputDevice midiInputDevice) {
Log.d("MIDI", "MIDI Input ready: " + midiInputDevice.getProductName());
}
@Override
public void onMidiInputDeviceDetached(@NonNull MidiInputDevice midiInputDevice) {
Log.d("MIDI", "MIDI Input disconnected");
}
@Override
public void onMidiOutputDeviceAttached(@NonNull MidiOutputDevice midiOutputDevice) {
Log.d("MIDI", "MIDI Output ready: " + midiOutputDevice.getProductName());
}
@Override
public void onMidiOutputDeviceDetached(@NonNull MidiOutputDevice midiOutputDevice) {
Log.d("MIDI", "MIDI Output disconnected");
}
@Override
public void onMidiNoteOn(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
if (callback != null) {
callback.onNoteOn(channel, note, velocity);
}
}
@Override
public void onMidiNoteOff(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
if (callback != null) {
callback.onNoteOff(channel, note, velocity);
}
}
@Override
public void onMidiControlChange(@NonNull MidiInputDevice sender, int cable,
int channel, int function, int value) {
if (callback != null) {
callback.onControlChange(channel, function, value);
}
}
// Implement remaining callbacks...
@Override public void onMidiPolyphonicAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int note, int pressure) { }
@Override public void onMidiChannelAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int pressure) { }
@Override public void onMidiProgramChange(@NonNull MidiInputDevice sender,
int cable, int channel, int program) { }
@Override public void onMidiPitchWheel(@NonNull MidiInputDevice sender,
int cable, int channel, int amount) { }
@Override public void onMidiSystemExclusive(@NonNull MidiInputDevice sender,
int cable, byte[] systemExclusive) { }
@Override public void onMidiSystemCommonMessage(@NonNull MidiInputDevice sender,
int cable, byte[] bytes) { }
@Override public void onMidiSingleByte(@NonNull MidiInputDevice sender,
int cable, int byte1) { }
@Override public void onMidiMiscellaneousFunctionCodes(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiCableEvents(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiTimeCodeQuarterFrame(@NonNull MidiInputDevice sender,
int cable, int timing) { }
@Override public void onMidiSongSelect(@NonNull MidiInputDevice sender,
int cable, int song) { }
@Override public void onMidiSongPositionPointer(@NonNull MidiInputDevice sender,
int cable, int position) { }
@Override public void onMidiTuneRequest(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiTimingClock(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStart(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiContinue(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStop(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiActiveSensing(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiReset(@NonNull MidiInputDevice sender, int cable) { }
};
// Start the driver
midiDriver.open();
}
public Set getOutputDevices() {
return midiDriver.getMidiOutputDevices();
}
public Set getConnectedDevices() {
return midiDriver.getConnectedUsbDevices();
}
public void suspend() {
if (midiDriver != null) {
// Suspend MIDI communication (e.g., when app goes to background)
// Events will be discarded until resume() is called
}
}
public void shutdown() {
if (midiDriver != null) {
midiDriver.close();
midiDriver = null;
}
}
}
```
## MultipleMidiService - Background MIDI Service
Android Service for handling MIDI connections in the background, allowing MIDI communication to continue even when the Activity is not in the foreground.
```java
// In your Activity
public class MidiServiceActivity extends AppCompatActivity {
private MultipleMidiService midiService;
private boolean bound = false;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MultipleMidiService.LocalBinder binder = (MultipleMidiService.LocalBinder) service;
midiService = binder.getService();
bound = true;
// Set up listeners
midiService.setOnMidiDeviceAttachedListener(new OnMidiDeviceAttachedListener() {
@Override
public void onDeviceAttached(@NonNull UsbDevice usbDevice) {
Log.d("MIDI", "USB device attached");
}
@Override
public void onMidiInputDeviceAttached(@NonNull MidiInputDevice midiInputDevice) {
// Set up event listener for this input device
midiInputDevice.setMidiEventListener(midiEventListener);
Log.d("MIDI", "Input device ready: " + midiInputDevice.getProductName());
}
@Override
public void onMidiOutputDeviceAttached(@NonNull MidiOutputDevice midiOutputDevice) {
Log.d("MIDI", "Output device ready: " + midiOutputDevice.getProductName());
}
});
midiService.setOnMidiDeviceDetachedListener(new OnMidiDeviceDetachedListener() {
@Override
public void onDeviceDetached(@NonNull UsbDevice usbDevice) { }
@Override
public void onMidiInputDeviceDetached(@NonNull MidiInputDevice midiInputDevice) {
Log.d("MIDI", "Input device detached");
}
@Override
public void onMidiOutputDeviceDetached(@NonNull MidiOutputDevice midiOutputDevice) {
Log.d("MIDI", "Output device detached");
}
});
}
@Override
public void onServiceDisconnected(ComponentName name) {
bound = false;
}
};
private final OnMidiInputEventListener midiEventListener = new OnMidiInputEventListener() {
@Override
public void onMidiNoteOn(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
runOnUiThread(() -> handleNoteOn(channel, note, velocity));
}
@Override
public void onMidiNoteOff(@NonNull MidiInputDevice sender, int cable,
int channel, int note, int velocity) {
runOnUiThread(() -> handleNoteOff(channel, note, velocity));
}
// Implement other callbacks...
@Override public void onMidiPolyphonicAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int note, int pressure) { }
@Override public void onMidiControlChange(@NonNull MidiInputDevice sender,
int cable, int channel, int function, int value) { }
@Override public void onMidiProgramChange(@NonNull MidiInputDevice sender,
int cable, int channel, int program) { }
@Override public void onMidiChannelAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int pressure) { }
@Override public void onMidiPitchWheel(@NonNull MidiInputDevice sender,
int cable, int channel, int amount) { }
@Override public void onMidiSystemExclusive(@NonNull MidiInputDevice sender,
int cable, byte[] systemExclusive) { }
@Override public void onMidiSystemCommonMessage(@NonNull MidiInputDevice sender,
int cable, byte[] bytes) { }
@Override public void onMidiSingleByte(@NonNull MidiInputDevice sender,
int cable, int byte1) { }
@Override public void onMidiMiscellaneousFunctionCodes(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiCableEvents(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiTimeCodeQuarterFrame(@NonNull MidiInputDevice sender,
int cable, int timing) { }
@Override public void onMidiSongSelect(@NonNull MidiInputDevice sender,
int cable, int song) { }
@Override public void onMidiSongPositionPointer(@NonNull MidiInputDevice sender,
int cable, int position) { }
@Override public void onMidiTuneRequest(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiTimingClock(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStart(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiContinue(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStop(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiActiveSensing(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiReset(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiRPNReceived(@NonNull MidiInputDevice sender,
int cable, int channel, int function, int valueMSB, int valueLSB) { }
@Override public void onMidiNRPNReceived(@NonNull MidiInputDevice sender,
int cable, int channel, int function, int valueMSB, int valueLSB) { }
@Override public void onMidiRPNReceived(@NonNull MidiInputDevice sender,
int cable, int channel, int function, int value) { }
@Override public void onMidiNRPNReceived(@NonNull MidiInputDevice sender,
int cable, int channel, int function, int value) { }
};
@Override
protected void onStart() {
super.onStart();
// Start and bind to the service
Intent intent = new Intent(this, MultipleMidiService.class);
startService(intent);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (bound) {
unbindService(serviceConnection);
bound = false;
}
}
public void sendNoteToAllDevices(int note, int velocity) {
if (bound && midiService != null) {
for (MidiOutputDevice device : midiService.getMidiOutputDevices()) {
device.sendMidiNoteOn(0, 0, note, velocity);
}
}
}
}
```
## UsbMidiSystem - javax.sound.midi Compatibility
Provides `javax.sound.midi` compatible API for developers familiar with Java SE MIDI programming. Enables use of standard MIDI classes like MidiSystem, MidiDevice, Receiver, and Transmitter.
```java
public class JavaxSoundMidiExample extends AppCompatActivity {
private UsbMidiSystem usbMidiSystem;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize the USB MIDI system
usbMidiSystem = new UsbMidiSystem(this);
usbMidiSystem.initialize();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (usbMidiSystem != null) {
usbMidiSystem.terminate();
}
}
// Using javax.sound.midi API
public void useJavaxSoundMidi() {
try {
// Get all MIDI devices
MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
for (MidiDevice.Info info : infos) {
MidiDevice device = MidiSystem.getMidiDevice(info);
device.open();
// Get Receiver for sending MIDI
if (device.getMaxReceivers() != 0) {
Receiver receiver = device.getReceiver();
// Send Note On
ShortMessage noteOn = new ShortMessage();
noteOn.setMessage(ShortMessage.NOTE_ON, 0, 60, 100);
receiver.send(noteOn, -1);
// Send Note Off after delay
new Handler().postDelayed(() -> {
try {
ShortMessage noteOff = new ShortMessage();
noteOff.setMessage(ShortMessage.NOTE_OFF, 0, 60, 0);
receiver.send(noteOff, -1);
} catch (InvalidMidiDataException e) {
e.printStackTrace();
}
}, 500);
}
// Get Transmitter for receiving MIDI
if (device.getMaxTransmitters() != 0) {
Transmitter transmitter = device.getTransmitter();
transmitter.setReceiver(new Receiver() {
@Override
public void send(MidiMessage message, long timeStamp) {
if (message instanceof ShortMessage) {
ShortMessage sm = (ShortMessage) message;
int command = sm.getCommand();
int channel = sm.getChannel();
int data1 = sm.getData1();
int data2 = sm.getData2();
Log.d("MIDI", "Received: cmd=" + command +
", ch=" + channel + ", d1=" + data1 + ", d2=" + data2);
}
}
@Override
public void close() { }
});
}
}
} catch (MidiUnavailableException | InvalidMidiDataException e) {
e.printStackTrace();
}
}
// Get available synthesizers
public void listSynthesizers() {
Synthesizer[] synthesizers = MidiSystem.getAvailableSynthesizers();
for (Synthesizer synth : synthesizers) {
Log.d("MIDI", "Synthesizer: " + synth.getDeviceInfo().getName());
}
}
}
```
## Suspend and Resume MIDI Communication
Temporarily suspend MIDI event processing to conserve resources when the app is in the background.
```java
public class MidiLifecycleActivity extends AbstractMultipleMidiActivity {
@Override
protected void onPause() {
super.onPause();
// Suspend MIDI when app goes to background
// All events will be discarded until resume
suspendMidiDevices();
}
@Override
protected void onResume() {
super.onResume();
// Resume MIDI when app comes to foreground
resumeMidiDevices();
}
// For AbstractSingleMidiActivity, use the same methods:
// suspendMidiDevices() and resumeMidiDevices()
// For UsbMidiDriver:
// midiDriver.suspend() and midiDriver.resume()
// For MultipleMidiService:
// midiService.suspend() and midiService.resume()
// Callback implementations...
@Override public void onDeviceAttached(@NonNull UsbDevice usbDevice) { }
@Override public void onDeviceDetached(@NonNull UsbDevice usbDevice) { }
@Override public void onMidiInputDeviceAttached(@NonNull MidiInputDevice device) { }
@Override public void onMidiInputDeviceDetached(@NonNull MidiInputDevice device) { }
@Override public void onMidiOutputDeviceAttached(@NonNull MidiOutputDevice device) { }
@Override public void onMidiOutputDeviceDetached(@NonNull MidiOutputDevice device) { }
@Override public void onMidiNoteOn(@NonNull MidiInputDevice sender,
int cable, int channel, int note, int velocity) { }
@Override public void onMidiNoteOff(@NonNull MidiInputDevice sender,
int cable, int channel, int note, int velocity) { }
@Override public void onMidiPolyphonicAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int note, int pressure) { }
@Override public void onMidiControlChange(@NonNull MidiInputDevice sender,
int cable, int channel, int function, int value) { }
@Override public void onMidiProgramChange(@NonNull MidiInputDevice sender,
int cable, int channel, int program) { }
@Override public void onMidiChannelAftertouch(@NonNull MidiInputDevice sender,
int cable, int channel, int pressure) { }
@Override public void onMidiPitchWheel(@NonNull MidiInputDevice sender,
int cable, int channel, int amount) { }
@Override public void onMidiSystemExclusive(@NonNull MidiInputDevice sender,
int cable, byte[] systemExclusive) { }
@Override public void onMidiSystemCommonMessage(@NonNull MidiInputDevice sender,
int cable, byte[] bytes) { }
@Override public void onMidiSingleByte(@NonNull MidiInputDevice sender, int cable, int byte1) { }
@Override public void onMidiMiscellaneousFunctionCodes(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiCableEvents(@NonNull MidiInputDevice sender,
int cable, int byte1, int byte2, int byte3) { }
@Override public void onMidiTimeCodeQuarterFrame(@NonNull MidiInputDevice sender,
int cable, int timing) { }
@Override public void onMidiSongSelect(@NonNull MidiInputDevice sender, int cable, int song) { }
@Override public void onMidiSongPositionPointer(@NonNull MidiInputDevice sender,
int cable, int position) { }
@Override public void onMidiTuneRequest(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiTimingClock(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStart(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiContinue(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiStop(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiActiveSensing(@NonNull MidiInputDevice sender, int cable) { }
@Override public void onMidiReset(@NonNull MidiInputDevice sender, int cable) { }
}
```
## Summary
Android USB MIDI Driver is ideal for building music applications such as synthesizers, MIDI controllers, sequencers, DAW controllers, MIDI monitors/loggers, and instruments that need to communicate with external USB MIDI hardware. The library supports multiple integration patterns: Activity-based for simple single-screen apps, Service-based for background MIDI processing, and standalone UsbMidiDriver for custom architectures. Key use cases include receiving MIDI input from keyboards and controllers to trigger sounds or control parameters, sending MIDI output to synthesizers and sound modules, building MIDI thru/routing applications, and creating `javax.sound.midi` compatible applications for cross-platform code sharing.
Integration typically involves extending one of the abstract Activity classes (AbstractSingleMidiActivity or AbstractMultipleMidiActivity), implementing the required callback methods for device attachment/detachment and MIDI events, and using MidiOutputDevice methods to send MIDI data. The library handles USB permission requests, device enumeration, and low-level USB MIDI protocol details automatically. For applications requiring background operation, MultipleMidiService provides a bound service pattern, while UsbMidiDriver offers maximum flexibility for custom implementations. The `javax.sound.midi` compatibility layer through UsbMidiSystem enables developers to use familiar Java SE MIDI APIs on Android.