我还没有找到任何“官方”方法来控制 Java 代码的内部合成器。
可能最简单的选择是使用Android midi driver for the Sonivox synthesizer。
获取它as an AAR package(解压缩 *.zip)并将 *.aar 文件存储在工作区的某个位置。路径并不重要,它不需要在您自己的应用程序的文件夹结构中,但您项目中的“libs”文件夹可能是一个合乎逻辑的位置。
在 Android Studio 中打开您的 Android 项目:
文件->新建->新建模块->导入.JAR/.AAR包->下一步->查找
并选择“MidiDriver-all-release.aar”并更改子项目
如果你愿意的话。 -> 完成
等待 Gradle 完成它的魔法,然后转到“应用程序”模块的设置(您自己的应用程序项目的设置)到“依赖项”选项卡并添加(带有绿色“+”号)MIDI 驱动程序作为模块依赖。现在您可以访问 MIDI 驱动程序了:
import org.billthefarmer.mididriver.MidiDriver;
...
MidiDriver midiDriver = new MidiDriver();
无需担心 NDK 和 C++,您可以使用这些 Java 方法:
// Not really necessary. Receives a callback when/if start() has succeeded.
midiDriver.setOnMidiStartListener(listener);
// Starts the driver.
midiDriver.start();
// Receives the driver's config info.
midiDriver.config();
// Stops the driver.
midiDriver.stop();
// Just calls write().
midiDriver.queueEvent(event);
// Sends a MIDI event to the synthesizer.
midiDriver.write(event);
弹奏和停止音符的一个非常基本的“概念证明”可能是这样的:
package com.example.miditest;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import org.billthefarmer.mididriver.MidiDriver;
public class MainActivity extends AppCompatActivity implements MidiDriver.OnMidiStartListener,
View.OnTouchListener {
private MidiDriver midiDriver;
private byte[] event;
private int[] config;
private Button buttonPlayNote;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonPlayNote = (Button)findViewById(R.id.buttonPlayNote);
buttonPlayNote.setOnTouchListener(this);
// Instantiate the driver.
midiDriver = new MidiDriver();
// Set the listener.
midiDriver.setOnMidiStartListener(this);
}
@Override
protected void onResume() {
super.onResume();
midiDriver.start();
// Get the configuration.
config = midiDriver.config();
// Print out the details.
Log.d(this.getClass().getName(), "maxVoices: " + config[0]);
Log.d(this.getClass().getName(), "numChannels: " + config[1]);
Log.d(this.getClass().getName(), "sampleRate: " + config[2]);
Log.d(this.getClass().getName(), "mixBufferSize: " + config[3]);
}
@Override
protected void onPause() {
super.onPause();
midiDriver.stop();
}
@Override
public void onMidiStart() {
Log.d(this.getClass().getName(), "onMidiStart()");
}
private void playNote() {
// Construct a note ON message for the middle C at maximum velocity on channel 1:
event = new byte[3];
event[0] = (byte) (0x90 | 0x00); // 0x90 = note On, 0x00 = channel 1
event[1] = (byte) 0x3C; // 0x3C = middle C
event[2] = (byte) 0x7F; // 0x7F = the maximum velocity (127)
// Internally this just calls write() and can be considered obsoleted:
//midiDriver.queueEvent(event);
// Send the MIDI event to the synthesizer.
midiDriver.write(event);
}
private void stopNote() {
// Construct a note OFF message for the middle C at minimum velocity on channel 1:
event = new byte[3];
event[0] = (byte) (0x80 | 0x00); // 0x80 = note Off, 0x00 = channel 1
event[1] = (byte) 0x3C; // 0x3C = middle C
event[2] = (byte) 0x00; // 0x00 = the minimum velocity (0)
// Send the MIDI event to the synthesizer.
midiDriver.write(event);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(this.getClass().getName(), "Motion event: " + event);
if (v.getId() == R.id.buttonPlayNote) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.d(this.getClass().getName(), "MotionEvent.ACTION_DOWN");
playNote();
}
if (event.getAction() == MotionEvent.ACTION_UP) {
Log.d(this.getClass().getName(), "MotionEvent.ACTION_UP");
stopNote();
}
}
return false;
}
}
布局文件只有一个按钮,按住时播放预定义的音符,松开时停止:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.miditest.MainActivity"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play a note"
android:id="@+id/buttonPlayNote" />
</LinearLayout>
其实就是这么简单。上面的代码很可能是一个触控钢琴应用程序的起点,它具有 128 种可选乐器、非常好的延迟和许多应用程序所缺乏的适当的“音符关闭”功能。
至于选择乐器:您只需将 MIDI“程序更改”消息发送到您打算在其上播放的通道,以选择通用 MIDI 声音集中的 128 种声音之一。但这与 MIDI 的细节有关,与库的使用无关。
同样,您可能想要抽象出 MIDI 的低级细节,以便您可以轻松地在特定通道上使用特定乐器以特定速度在特定时间播放特定音符,并且您可能会发现一些迄今为止制作的所有开源 Java 和 MIDI 相关应用程序和库的线索。
顺便说一下,这种方法不需要 Android 6.0。目前only 4.6 % of devices visiting the Play Store run Android 6.x 所以你的应用不会有太多的受众。
当然,如果您想使用 android.media.midi 包,您可以使用库来实现 android.media.midi.MidiReceiver 来接收 MIDI 事件并在内部合成器上播放它们。谷歌已经有一些demo code that plays notes with square and saw waves。只需将其替换为内部合成器即可。
其他一些选项可能是检查将 FluidSynth 移植到 Android 的状态。我想可能有一些可用的东西。
编辑:其他可能有趣的库: