Android 6.0 (Marshmallow): Как играть в миди?

Я создаю приложение, которое генерирует звуки живых инструментов, и я планирую использовать новый Midi API, представленный в Android Marshmallow (версия 6.0). Я прочитал документ обзора пакета здесь http://developer.android.com/reference/android/media/midi/package-summary.html, и я знаю, как создавать заметки Midi, но я все еще не уверен: как я действительно играю эти заметки после того, как я 'Сгенерировали свои данные Midi?

Мне нужна программа синтезатора для воспроизведения примечаний Midi? Если да, должен ли я сделать свой собственный или он предоставляется Android или сторонним лицом?

Я новичок в Midi, поэтому, пожалуйста, будьте как можно более наглядными с вашим ответом.

Что я пробовал до сих пор: Я создал объект менеджера Midi и открыл входной порт

MidiManager m = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 
MidiInputPort inputPort = device.openInputPort(index);

Затем я отправил тестовую заметку на сообщение midi на порт

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

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-синтезатора приложения). Если кто-то уже не сделал такой синтезатор, я должен создать его самостоятельно в течение жизненного цикла приложения. Как я фактически конвертирую полученную миди noteOn() в звук, выходящий из моих динамиков? Я особенно смущен, потому что также должен быть способ программно решить, на каком типе инструмента звучит примечание: как это делается в синтезаторе?

Поддержка Midi в Android Marshmallow довольно нова, поэтому я не смог найти какие-либо учебники или примеры приложений для синтезаторов в Интернете. Любое понимание оценено.

Ответ 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 и мышления с точки зрения теории музыки

Ответ 2

  Нужна ли мне программа синтезатора, чтобы играть ноты Midi? Если да, должен ли я сделать свой собственный или он предоставлен Android или третьей стороной?

Нет, к счастью, вам не нужно создавать собственный синтезатор. В Android уже есть один встроенный: SONiVOX Embedded Audio Syntehesizer. Android указывается в документах на SONiVOX JETCreator:

JET работает совместно с SONiVOX Embedded Audio Synthesizer (EAS), который является устройством воспроизведения MIDI для Android.

Не было ясно, хотите ли вы воспроизведение в реальном времени или хотите сначала создать композицию, а затем воспроизвести ее в том же приложении. Вы также заявляете, что хотите проигрывать миди-ноты, а не файлы. Но, как вы знаете, воспроизведение Midi поддерживается на устройствах Android. Таким образом, воспроизведение файла .mid должно быть выполнено так же, как воспроизведение файла .wav с использованием MediaPlayer.

Честно говоря, я не использовал пакет midi и не воспроизводил midi, но если вы можете создать файл .mid и сохранить его на диск, то вы сможете воспроизводить его, используя прямой MediaPlayer.

Теперь, если вы хотите воспроизводить прямые миди ноты, а не файлы, вы можете использовать этот пакет мидриверов. Используя этот пакет, вы сможете записывать промежуточные данные во встроенный синтезатор:

/**
* Writes midi data to the Sonivox synthesizer. 
* The length of the array should be the exact length 
* of the message or messages. Returns true on success, 
* false on failure.
*/

boolean write(byte buffer[])  

Если вы хотите сделать шаг еще ниже, вы можете даже играть в прямой PCM, используя AudioTrack.

Дополнительную информацию можно найти в сообщении в блоге, которое я нашел у человека, у которого, похоже, были проблемы, подобные вашим. Он заявляет:

Лично я решил проблему динамического генерирования миди следующим образом: программно сгенерировал миди файл, записал его в хранилище устройства, запустил медиаплеер с файлом и позволил ему воспроизводиться. Это достаточно быстро, если вам просто нужно воспроизвести динамический миди-звук. Я сомневаюсь, что это полезно для создания контролируемых пользователем миди, таких как секвенсоры, но в других случаях это здорово.

Надеюсь, я покрыл все.

Ответ 3

Чтобы генерировать звук с помощью Android MIDI API, вам нужно приложение синтезатора, которое принимает вход MIDI. К сожалению, это единственное такое приложение, которое я нашел в Google Play: https://play.google.com/store/apps/details?id=com.mobileer.midisynthexample

Я был в состоянии воспроизводить музыку, отправляя заметки и записывать сообщения в это приложение. Но смена программы работала плохо. Если в моем коде что-то не так, похоже, в приложении всего два инструмента.

Тем не менее, есть некоторые ребята, работающие над другими приложениями синтезатора, так что я ожидаю, что скоро появятся новые приложения. Это приложение выглядит многообещающе, хотя я сам еще не тестировал его:https://github.com/pedrolcl/android/tree/master/NativeGMSynth