Ответ 1
Я не нашел никакого "официального" способа управления внутренним синтезатором из кода Java.
Вероятно, самый простой вариант - использовать MIDI-драйвер Android для синтезатора Sonivox.
Получите его в виде пакета AAR (разархивируйте *.zip) и сохраните файл *.aar где-нибудь в вашей рабочей области. Путь на самом деле не имеет значения, и он не обязательно должен находиться внутри структуры папок вашего приложения, но папка "libs" внутри вашего проекта может быть логичным местом.
С вашим проектом Android, открытым в Android Studio:
Файл → Новый → Новый модуль → Импорт пакета .JAR/.AAR → Далее → Найти и выберите "MidiDriver-all-release.aar" и измените подпроект имя, если хотите. → Готово
Подождите, пока Gradle сделает это по-волшебному, а затем перейдите в настройки модуля "app" (настройки вашего собственного приложения) на вкладку "Dependencies" и добавьте (с зеленым знаком "+") драйвер 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 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-сообщение "изменение программы" на канал, на котором вы собираетесь играть, чтобы выбрать один из 128 звуков в общем наборе MIDI-звуков. Но это относится к деталям MIDI, а не к использованию библиотеки.
Точно так же вы, вероятно, захотите абстрагироваться от низкоуровневых деталей MIDI, чтобы вы могли легко воспроизводить конкретную ноту на определенном канале с конкретным инструментом с определенной скоростью в течение определенного времени и для этого вы можете найти некоторые подсказки из всех приложения и библиотеки с открытым исходным кодом, связанные с Java и MIDI.
Этот подход не требует Android 6.0, кстати. А на данный момент только 4,6% устройств, посещающих Play Store, используют Android 6.x, поэтому у вашего приложения не будет большой аудитории.
Конечно, если вы хотите использовать пакет android.media.midi
, вы можете использовать библиотеку для реализации android.media.midi.MidiReceiver
для получения событий MIDI и воспроизведения их на внутреннем синтезаторе. У Google уже есть некоторый демонстрационный код , который воспроизводит ноты с прямоangularьными и пилообразными волнами. Просто замените его внутренним синтезатором.
Некоторые другие варианты могут быть, чтобы проверить, что состояние с портированием FluidSynth на Android. Я думаю, что-то может быть доступно.
Редактировать: Другие, возможно, интересные библиотеки:
- порт Java пакет javax.sound.midi для абстрагирования технических подробностей MIDI низкого уровня
- Драйвер USB MIDI для подключения к цифровому пианино/клавиатуре с разъемом USB MIDI
- Драйвер MIDI через Bluetooth LE для беспроводного подключения к цифровому пианино/клавиатуре, поддерживающей MIDI через Bluetooth LE (как, например, некоторые недавние цифровые фортепиано Roland и Dexibell)
- Порт библиотеки JFugue Music для Android для дальнейшего абстрагирования деталей MIDI и мышления с точки зрения теории музыки