我还没有找到任何“官方”方法来控制Java代码中的内部合成器。
可能最简单的选择是将Android midi驱动程序用于Sonivox合成器。
将其作为 AAR 包获取(解压缩 *.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。目前,只有4.6%的访问Play商店的设备运行Android 6.x,因此您的应用程序不会有太多的受众。
当然,如果你想使用这个包,你可以使用库来实现一个来接收MIDI事件,并在内部合成器上播放它们。谷歌已经有一些演示代码,可以用正方形和锯波播放音符。只需将其替换为内部合成器即可。android.media.midi
android.media.midi.MidiReceiver
其他一些选项可能是查看将FluidSynth移植到Android的状态。我想可能会有一些东西可用。
编辑:其他可能有趣的库: