通过USB,蓝牙LE和虚拟(应用程序内)传输提供使用标准MIDI事件协议发送和接收消息的类。
Android MIDI软件包允许用户:
API是“运输不可知论”。 但目前有几种运输支持:
设备是一个具有零个或多个输入端口和输出端口的支持MIDI的对象。
InputPort有16个通道,可以从OutputPort或应用程序 接收 MIDI消息。
一个 OutputPort有16个通道,可以 将 MIDI消息发送到一个InputPort或一个应用程序。
MidiService是一个集中化的过程,可以跟踪所有设备和代理之间的通信。
MidiManager是应用程序或设备管理器调用的与MidiService进行通信的类。
需要MIDI API的应用程序应在AndroidManifest.xml文件中声明该应用程序。 然后,该应用将不会出现在Play商店中,用于不支持MIDI API的旧设备。
<uses-feature android:name="android.software.midi" android:required="true"/>
应用程序还可以在运行时检查平台上是否支持MIDI功能。 当您直接在设备上安装应用程序时,这在开发过程中特别有用。
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) { // do MIDI stuff }
访问MIDI包的主要类是通过MidiManager。
MidiManager m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
当应用程序启动时,它可以获得所有可用MIDI设备的列表。 这些信息可以呈现给用户,让他们选择一个设备。
MidiDeviceInfo[] infos = m.getDevices();
例如,当键盘插入或拔出时,应用程序可以请求通知。
m.registerDeviceCallback(new MidiManager.DeviceCallback() { public void onDeviceAdded( MidiDeviceInfo info ) { ... } public void onDeviceRemoved( MidiDeviceInfo info ) { ... } });
您可以查询输入和输出端口的数量。
int numInputs = info.getInputPortCount(); int numOutputs = info.getOutputPortCount();
请注意,“输入”和“输出”是从设备的角度出发的。 所以合成器将有一个接收消息的“输入”端口。 键盘将有一个发送消息的“输出”端口。
MidiDeviceInfo有一组属性。
Bundle properties = info.getProperties(); String manufacturer = properties .getString(MidiDeviceInfo.PROPERTY_MANUFACTURER);
其他属性包括PROPERTY_PRODUCT,PROPERTY_NAME,PROPERTY_SERIAL_NUMBER
您可以从PortInfo对象中获取端口的名称和类型。 该类型将是TYPE_INPUT或TYPE_OUTPUT。
MidiDeviceInfo.PortInfo[] portInfos = info.getPorts(); String portName = portInfos[0].getName(); if (portInfos[0].getType() == MidiDeviceInfo.PortInfo.TYPE_INPUT) { ... }
要访问MIDI设备,您需要先打开它。 打开是异步的,所以你需要提供一个回调来完成。 如果您希望回调在特定的线程上发生,您可以指定一个可选的处理程序。
m.openDevice(info, new MidiManager.OnDeviceOpenedListener() { @Override public void onDeviceOpened(MidiDevice device) { if (device == null) { Log.e(TAG, "could not open device " + info); } else { ... } }, new Handler(Looper.getMainLooper()) );
如果你想发送消息到MIDI设备,那么你需要打开一个独占访问的“输入”端口。
MidiInputPort inputPort = device.openInputPort(index);
MIDI消息以字节数组的形式发送。 这里我们编码一个NoteOn消息。
byte[] buffer = new byte[32]; int numBytes = 0; int channel = 3; // MIDI channels 1-16 are encoded as 0-15. buffer[numBytes++] = (byte)(0x90 + (channel - 1)); // note on buffer[numBytes++] = (byte)60; // pitch is middle C buffer[numBytes++] = (byte)127; // max velocity int offset = 0; // post is non-blocking inputPort.send(buffer, offset, numBytes);
有时发送带有时间戳的MIDI信息很方便。 通过在未来安排事件,我们可以屏蔽调度抖动。 Android MIDI时间戳基于单调纳秒系统计时器。 这与其他音频和输入定时器一致。
在这里,我们发送一条消息,其中包含将来2秒的时间戳。
final long NANOS_PER_SECOND = 1000000000L; long now = System.nanoTime(); long future = now + (2 * NANOS_PER_SECOND); inputPort.send(buffer, offset, numBytes, future);
如果您想取消将来计划的事件,请调用flush()。
inputPort.flush(); // discard events
如果缓冲区中有任何MIDI NoteOff消息,则它们将被丢弃,并且可能会卡住音符。 所以我们建议在冲洗完成后发送“全部注释”。
要从设备接收MIDI数据,您需要扩展MidiReceiver。 然后将您的接收器连接到设备的输出端口。
class MyReceiver extends MidiReceiver { public void onSend(byte[] data, int offset, int count, long timestamp) throws IOException { // parse MIDI or whatever } } MidiOutputPort outputPort = device.openOutputPort(index); outputPort.connect(new MyReceiver());
到达的数据未经任何特定方式验证或对齐。 这是原始的MIDI数据,可以包含多条消息或部分消息。 它可能包含系统实时消息,这些消息可以交织在其他消息中。
应用程序可以提供可供其他应用程序使用的MIDI服务。 例如,应用可以提供其他应用可以发送消息的自定义合成器。 该服务必须经过“android.permission.BIND_MIDI_DEVICE_SERVICE”权限的保护。
一个应用程序声明它将在AndroidManifest.xml文件中用作MIDI服务器。
<service android:name="MySynthDeviceService" android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"> <intent-filter> <action android:name="android.media.midi.MidiDeviceService" /> </intent-filter> <meta-data android:name="android.media.midi.MidiDeviceService" android:resource="@xml/synth_device_info" /> </service>
本例中资源的详细信息存储在“res / xml / synth_device_info.xml”中。 您在此文件中声明的端口名称将从PortInfo.getName()中可用。
<devices> <device manufacturer="MyCompany" product="MidiSynthBasic"> <input-port name="input" /> </device> </devices>
然后通过扩展android.media.midi.MidiDeviceService来定义您的服务器。 让我们假设你有一个扩展MidiReceiver的MySynthEngine类。
import android.media.midi.MidiDeviceService; import android.media.midi.MidiDeviceStatus; import android.media.midi.MidiReceiver; public class MidiSynthDeviceService extends MidiDeviceService { private static final String TAG = "MidiSynthDeviceService"; private MySynthEngine mSynthEngine = new MySynthEngine(); private boolean synthStarted = false; @Override public void onCreate() { super.onCreate(); } @Override public void onDestroy() { mSynthEngine.stop(); super.onDestroy(); } @Override // Declare the receivers associated with your input ports. public MidiReceiver[] onGetInputPortReceivers() { return new MidiReceiver[] { mSynthEngine }; } /** * This will get called when clients connect or disconnect. * You can use it to turn on your synth only when needed. */ @Override public void onDeviceStatusChanged(MidiDeviceStatus status) { if (status.isInputPortOpen(0) && !synthStarted) { mSynthEngine.start(); synthStarted = true; } else if (!status.isInputPortOpen(0) && synthStarted){ mSynthEngine.stop(); synthStarted = false; } } }
MIDI设备可以使用蓝牙LE连接到Android。
在使用设备之前,应用程序必须扫描可用的BTLE设备,然后允许用户连接。 将提供一个示例程序,以便在Android开发人员网站上查找它。
扫描蓝牙设备的应用程序必须在清单文件中请求许可。 此LOCATION权限是必需的,因为可以通过查看哪些BTLE设备位于附近来猜测Android设备的位置。
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
应用程序还必须在运行时向用户请求位置许可。 有关详细信息和示例,请参阅Activity.requestPermissions()
的文档。
该应用程序将只想看到MIDI设备,而不是鼠标或其他非MIDI设备。 因此,在BTLE上使用标准MIDI的UUID构建一个ScanFilter。
MIDI over BTLE UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"
有关详细信息,请参阅android.bluetooth.le.BluetoothLeScanner.startScan()
方法的文档。 当用户选择MIDI / BTLE设备时,您可以使用MidiManager打开它。
m.openBluetoothDevice(bluetoothDevice, callback, handler);
一旦MIDI / BTLE设备被一个应用程序打开后,它也可以被其他应用程序使用 MIDI device discovery calls described above 。
MidiDevice | 此类用于向MIDI设备发送和接收数据。此类的实例由 openDevice(MidiDeviceInfo, MidiManager.OnDeviceOpenedListener, Handler) 创建。 |
MidiDevice.MidiConnection | 该类表示一个设备的输出端口与另一个设备的输入端口之间的连接。 |
MidiDeviceInfo | 该类包含描述MIDI设备的信息。 |
MidiDeviceInfo.PortInfo | 包含有关输入或输出端口的信息。 |
MidiDeviceService | 一种实现虚拟MIDI设备的服务。 |
MidiDeviceStatus | 这是一个描述MIDI设备端口当前状态的不可变类。 |
MidiInputPort | 该类用于将数据发送到MIDI设备上的端口 |
MidiManager | 这个类是MIDI服务的公共应用程序接口。 |
MidiManager.DeviceCallback | 回叫类用于客户端接收MIDI设备添加和删除通知 |
MidiOutputPort | 该类用于从MIDI设备上的端口接收数据 |
MidiReceiver | 用于向MIDI设备发送数据和从MIDI设备接收数据的接口。 |
MidiSender | 由设备提供的接口允许将MidiReceivers连接到MIDI设备。 |