java Android 6.0 (Marshmallow):如何演奏 MIDI 音符?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36193250/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 01:02:52  来源:igfitidea点击:

Android 6.0 (Marshmallow): How to play midi notes?

javaandroidaudiomidiandroid-6.0-marshmallow

提问by Cody

I'm creating an app that generates live instrument sounds and I'm planning on using the new Midi API featured in Android Marshmallow (version 6.0). I've read the package overview document here http://developer.android.com/reference/android/media/midi/package-summary.htmland I know how to generate Midi notes but i'm still unsure: how do I actually play these notes after I've generated their Midi data?

我正在创建一个生成现场乐器声音的应用程序,我计划使用 Android Marshmallow(6.0 版)中的新 Midi API。我在这里阅读了包概述文档http://developer.android.com/reference/android/media/midi/package-summary.html,我知道如何生成 Midi 笔记,但我仍然不确定:我该怎么做在我生成了它们的 Midi 数据之后,真的演奏这些音符吗?

Do I need a synthesizer program to play Midi notes? If so, do I have to make my own or is one provided by Android or a 3rd party?

我需要合成器程序来演奏 Midi 音符吗?如果是这样,我必须自己制作还是由 Android 或第三方提供?

I am a novice with Midi so please be as descriptive as possible with your answer.

我是 Midi 的新手,所以请尽可能描述您的答案。

What i've tried so far:I've created a Midi manager object and opened an input port

到目前为止尝试过的:我创建了一个 Midi 管理器对象并打开了一个输入端口

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

Then, i've sent a test noteOn midi message to the port

然后,我向端口发送了一条测试 noteOn 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);

I've also set up a class to receive the midi note messages

我还设置了一个类来接收 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());

Now, here's where i'm most confused. The use case of my app is to be an all-in-one composition & playback tool for making music. In other words, my app needs to contain or use a virtual midi device (like an intent of another app's midi synthesizer). Unless someone already made such a synthesizer, I must create one myself within my app's lifecycle. How do I actually actually convert a received midi noteOn() into sound coming out of my speakers? I'm especially confused because there also has to be a way to programmatically decide what type of instrument the note sounds like it's coming from: is this also done in a synthesizer?

现在,这是我最困惑的地方。我的应用程序的用例是成为用于制作音乐的多合一作曲和播放工具。换句话说,我的应用程序需要包含或使用一个虚拟的 MIDI 设备(就像另一个应用程序的 MIDI 合成器的意图)。除非有人已经制作了这样的合成器,否则我必须在我的应用程序生命周期内自己创建一个。我实际上如何将接收到的 midi noteOn() 转换为扬声器发出的声音?我特别困惑,因为还必须有一种方法来以编程方式确定音符听起来像是来自哪种乐器:这也是在合成器中完成的吗?

Midi support in Android Marshmallow is fairly new so I haven't been able to find any tutorials or sample synthesizer apps online. Any insight is appreciated.

Android Marshmallow 中的 Midi 支持是相当新的,所以我无法在线找到任何教程或示例合成器应用程序。任何见解表示赞赏。

回答by Markus Kauppinen

I haven't found any "official" way to control the internal synthesizer from Java code.

我还没有找到任何从 Java 代码控制内部合成器的“官方”方法。

Probably the easiest option is to use the Android midi driver for the Sonivox synthesizer.

可能最简单的选择是将Android MIDI 驱动程序用于 Sonivox 合成器

Get it as an AAR package(unzip the *.zip) and store the *.aar file somewhere in your workspace. The path doesn't really matter and it doesn't need to be inside your own app's folder structure but the "libs" folder inside your project could be a logical place.

将其作为 AAR 包获取(解压缩 *.zip)并将 *.aar 文件存储在您的工作区中的某个位置。路径并不重要,它不需要位于您自己的应用程序的文件夹结构中,但您项目中的“libs”文件夹可能是一个合乎逻辑的位置。

With your Android project open in Android Studio:

在 Android Studio 中打开您的 Android 项目:

File -> New -> New Module -> Import .JAR/.AAR Package -> Next -> Find and select the "MidiDriver-all-release.aar" and change the subproject name if you want. -> Finish

文件 -> 新建 -> 新模块 -> 导入 .JAR/.AAR 包 -> 下一步 -> 查找并选择“MidiDriver-all-release.aar”并根据需要更改子项目名称。-> 完成

Wait for Gradle to do it's magic and then go to your "app" module's settings (your own app project's settings) to the "Dependencies" tab and add (with the green "+" sign) the MIDI Driver as a module dependency. Now you have access to the MIDI Driver:

等待 Gradle 完成它的魔法,然后转到“应用程序”模块的设置(您自己的应用程序项目的设置)到“依赖项”选项卡并添加(带有绿色“+”号)MIDI 驱动程序作为模块依赖项。现在您可以访问 MIDI 驱动程序:

import org.billthefarmer.mididriver.MidiDriver;
   ...
MidiDriver midiDriver = new MidiDriver();

Without having to worry anything about NDK and C++ you have these Java methods available:

无需担心 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);

A very basic "proof of concept" for playing and stopping a note could be something like:

演奏和停止音符的一个非常基本的“概念证明”可能是这样的:

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;
    }
}

The layout file just has one button that plays the predefined note when held down and stops it when released:

布局文件只有一个按钮,按住时播放预定义的音符,松开时停止播放:

<?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>

It is actually this simple. The code above could well be a starting point for a touch piano app with 128 selectable instruments, very decent latency and a proper "note off" functionality which many apps lack.

其实就是这么简单。上面的代码很可能是一个具有 128 种可选乐器、非常不错的延迟和许多应用程序缺乏的适当“音符关闭”功能的触摸钢琴应用程序的起点。

As for choosing the instrument: You'll just need to send a MIDI "program change" message to the channel on which you intend to play to choose one of the 128 sounds in the General MIDI soundset. But that's related to the details of MIDI and not to the usage of the library.

至于选择乐器:您只需将 MIDI“程序更改”消息发送到您打算演奏的通道,以选择通用 MIDI 声音集中的 128 种声音之一。但这与 MIDI 的细节有关,与库的使用无关。

Likewise you'll probably want to abstract away the low level details of MIDI so that you can easily play a specific note on a specific channel with a specific instrument at a specific velocity for a specific time and for that you might find some clues from all the open source Java and MIDI related applications and libraries made so far.

同样,您可能希望抽象出 MIDI 的低级细节,以便您可以在特定时间以特定速度使用特定乐器轻松演奏特定通道上的特定音符,为此您可能会发现一些线索迄今为止制作的开源 Java 和 MIDI 相关应用程序和库。

This approach doesn't require Android 6.0 by the way. And at the moment only 4.6 % of devices visiting the Play Store run Android 6.xso there wouldn't be much audience for your app.

顺便说一下,这种方法不需要 Android 6.0。目前,访问 Play 商店的设备中只有 4.6% 运行 Android 6.x,因此您的应用不会有太多受众。

Of course if you want to use the android.media.midipackage you could then use the library to implement a android.media.midi.MidiReceiverto receive the MIDI events and play them on the internal synthesizer. Google already has some demo code that plays notes with square and saw waves. Just replace that with the internal synthesizer.

当然,如果您想使用该android.media.midi包,则可以使用该库来实现一个android.media.midi.MidiReceiver以接收 MIDI 事件并在内部合成器上播放它们。谷歌已经有一些演示代码,可以播放方波和锯齿波的音符。只需将其替换为内部合成器即可。

Some other options could be to check out what's the status with porting FluidSynthto Android. I guess there might be something available.

其他一些选项可能是检查将FluidSynth移植到 Android的状态。我想可能有一些可用的东西。

Edit:Other possibly interesting libraries:

编辑:其他可能有趣的库:

回答by CaptJak

Do I need a synthesizer program to play Midi notes? If so, do I have to make my own or is one provided by Android or a 3rd party?

我需要合成器程序来演奏 Midi 音符吗?如果是这样,我必须自己制作还是由 Android 或第三方提供?

No, fortunately you don't need to make your own synthesizer. Android already has one built in: the SONiVOX Embedded Audio Syntehesizer. Android states in the docs on SONiVOX JETCreator:

不,幸运的是您不需要制作自己的合成器。Android 已经内置了一个:SONiVOX 嵌入式音频合成器。Android在 SONiVOX JETCreator文档中指出

JET works in conjunction with SONiVOX's Embedded Audio Synthesizer (EAS) which is the MIDI playback device for Android.

JET 与 SONiVOX 的嵌入式音频合成器 (EAS) 协同工作,后者是 Android 的 MIDI 播放设备。

It wasn't clear whether or not you want real-time playback, or if you want to create a composition first and play it later within the same app. You also state that you want to play midi notes, not files. But, just so you know, Midi playback is supported on android devices. So playing a .mid file should be done the same way you would play a .wav file using MediaPlayer.

目前尚不清楚您是否想要实时播放,或者您是否想要先创建一个作品然后在同一个应用程序中播放它。您还声明要播放 MIDI 音符,而不是文件。但是,正如您所知,Android 设备支持Midi 播放。因此,播放 .mid 文件的方式应该与使用 .wav 文件播放的方式相同MediaPlayer

To be honest, I haven't use the midi package, or done midi playback, but if you can create a .mid file and save it to disk, then you should be able to play it back using straight MediaPlayer.

老实说,我没有使用midi包,也没有做过midi播放,但是如果你可以创建一个.mid文件并将其保存到磁盘,那么你应该可以使用直接播放它MediaPlayer

Now, if you want to play straight midi notes, notfiles, then you can use this mididriver package. Using this package you should be able to write midi data to the Embedded Synthesizer:

现在,如果你想播放直接的 MIDI音符而不是文件,那么你可以使用这个 mididriver 包。使用此包,您应该能够将 MIDI 数据写入嵌入式合成器:

/**
* 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[])  

If you want to step even lower than that, you could even play straight PCM using AudioTrack.

如果你想比这更低,你甚至可以使用AudioTrack直接播放 PCM 。

For additional info, here is a blog postI found from someone who seemed to have similar troubles to yours. He states:

有关其他信息,请参阅我从与您遇到类似问题的人那里找到的博客文章。他说:

Personally I solved the dynamic midi generation issue as follows: programmatically generate a midi file, write it to the device storage, initiate a mediaplayer with the file and let it play. This is fast enough if you just need to play a dynamic midi sound. I doubt it's useful for creating user controlled midi stuff like sequencers, but for other cases it's great.

我个人解决了动态midi生成问题如下:以编程方式生成midi文件,将其写入设备存储,使用该文件启动媒体播放器并让它播放。如果您只需要播放动态的 MIDI 声音,这已经足够快了。我怀疑它对于创建用户控制的 MIDI 东西(如音序器)是否有用,但对于其他情况,它很棒。

Hope I covered everything.

希望我涵盖了一切。

回答by AndroidHobby

To generate sound using Android MIDI API, you need a synthesizer app which accepts MIDI input. Unfortunately, this is the only such app I have found on Google Play: https://play.google.com/store/apps/details?id=com.mobileer.midisynthexample

要使用 Android MIDI API 生成声音,您需要一个接受 MIDI 输入的合成器应用程序。不幸的是,这是我在 Google Play 上找到的唯一这样的应用程序:https: //play.google.com/store/apps/details?id=com.mobileer.midisynthexample

I was able to play music by sending note on and note off messages to this app. But program change worked poorly. Unless I did something wrong in my code, it seems the app has only two instruments.

我可以通过向这个应用程序发送音符开和音符关消息来播放音乐。但程序更改效果不佳。除非我在代码中做错了什么,否则该应用程序似乎只有两个工具。

However, there are some guys working on other synthesizer apps, so I expect more apps will be available soon. This app looks promising, though I haven't tested it myself yet: https://github.com/pedrolcl/android/tree/master/NativeGMSynth

但是,有些人正在开发其他合成器应用程序,所以我预计很快就会有更多应用程序可用。这个应用程序看起来很有希望,虽然我还没有自己测试过:https: //github.com/pedrolcl/android/tree/master/NativeGMSynth