Most visited

Recently visited

Added in API level 23

android.media.midi

通过USB,蓝牙LE和虚拟(应用程序内)传输提供使用标准MIDI事件协议发送和接收消息的类。

概述

Android MIDI软件包允许用户:

The API features include:

Transports Supported

API是“运输不可知论”。 但目前有几种运输支持:

Android MIDI Terminology

Terminology

设备是一个具有零个或多个输入端口和输出端口的支持MIDI的对象。

InputPort有16个通道,可以从OutputPort或应用程序 接收 MIDI消息。

一个 OutputPort有16个通道,可以 MIDI消息发送到一个InputPort或一个应用程序。

MidiService是一个集中化的过程,可以跟踪所有设备和代理之间的通信。

MidiManager是应用程序或设备管理器调用的与MidiService进行通信的类。

Writing a MIDI Application

Declare Feature in Manifest

需要MIDI API的应用程序应在AndroidManifest.xml文件中声明该应用程序。 然后,该应用将不会出现在Play商店中,用于不支持MIDI API的旧设备。

<uses-feature android:name="android.software.midi" android:required="true"/>

Check for Feature Support

应用程序还可以在运行时检查平台上是否支持MIDI功能。 当您直接在设备上安装应用程序时,这在开发过程中特别有用。

if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
    // do MIDI stuff
}

The MidiManager

访问MIDI包的主要类是通过MidiManager。

MidiManager m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);

Get List of Already Plugged In Entities

当应用程序启动时,它可以获得所有可用MIDI设备的列表。 这些信息可以呈现给用户,让他们选择一个设备。

MidiDeviceInfo[] infos = m.getDevices();

Notification of MIDI Devices HotPlug Events

例如,当键盘插入或拔出时,应用程序可以请求通知。

m.registerDeviceCallback(new MidiManager.DeviceCallback() {
    public void onDeviceAdded( MidiDeviceInfo info ) {
      ...
    }
    public void onDeviceRemoved( MidiDeviceInfo info ) {
      ...
    }
  });

Device and Port Information

您可以查询输入和输出端口的数量。

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) {
    ...
}

Open a MIDI Device

要访问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())
    );

Open a MIDI Input Port

如果你想发送消息到MIDI设备,那么你需要打开一个独占访问的“输入”端口。

MidiInputPort inputPort = device.openInputPort(index);

Send a NoteOn

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消息,则它们将被丢弃,并且可能会卡住音符。 所以我们建议在冲洗完成后发送“全部注释”。

Receive a Note

要从设备接收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数据,可以包含多条消息或部分消息。 它可能包含系统实时消息,这些消息可以交织在其他消息中。

Creating a MIDI Virtual Device Service

应用程序可以提供可供其他应用程序使用的MIDI服务。 例如,应用可以提供其他应用可以发送消息的自定义合成器。 该服务必须经过“android.permission.BIND_MIDI_DEVICE_SERVICE”权限的保护。

Manifest Files

一个应用程序声明它将在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>

Extend MidiDeviceService

然后通过扩展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;
        }
    }
}

Using MIDI Over Bluetooth LE

MIDI设备可以使用蓝牙LE连接到Android。

在使用设备之前,应用程序必须扫描可用的BTLE设备,然后允许用户连接。 将提供一个示例程序,以便在Android开发人员网站上查找它。

Request Location Permission for BTLE

扫描蓝牙设备的应用程序必须在清单文件中请求许可。 此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()的文档。

Scan for MIDI Devices

该应用程序将只想看到MIDI设备,而不是鼠标或其他非MIDI设备。 因此,在BTLE上使用标准MIDI的UUID构建一个ScanFilter。

MIDI over BTLE UUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"

Open a MIDI Bluetooth Device

有关详细信息,请参阅android.bluetooth.le.BluetoothLeScanner.startScan()方法的文档。 当用户选择MIDI / BTLE设备时,您可以使用MidiManager打开它。

m.openBluetoothDevice(bluetoothDevice, callback, handler);

一旦MIDI / BTLE设备被一个应用程序打开后,它也可以被其他应用程序使用 MIDI device discovery calls described above

Interfaces

MidiManager.OnDeviceOpenedListener Listener类用于接收 openDevice(MidiDeviceInfo, MidiManager.OnDeviceOpenedListener, Handler)openBluetoothDevice(BluetoothDevice, MidiManager.OnDeviceOpenedListener, Handler)的结果

Classes

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设备。

Hooray!