【问题标题】:Android 6.0 (Marshmallow): How to play midi notes?Android 6.0 (Marshmallow):如何播放 midi 音符?
【发布时间】:2016-07-11 15:13:03
【问题描述】:

我正在创建一个生成现场乐器声音的应用,并且我计划使用 Android Marshmallow(6.0 版)中的新 Midi API。我已经阅读了 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);

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

我还设置了一个类来接收midi note消息

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 合成器的意图)。除非有人已经制作了这样的合成器,否则我必须在我的应用程序的生命周期内自己创建一个。我实际上如何将接收到的 midi noteOn() 转换为从扬声器发出的声音?我特别困惑,因为还必须有一种方法来以编程方式确定音符听起来像是来自什么类型的乐器:这也是在合成器中完成的吗?

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

【问题讨论】:

  • 您连接到哪个设备?它的类型和名称是什么?
  • 我还没有真正连接到设备,这是我的问题的一部分。我要连接的“设备”将是一个虚拟设备:一个接受 MIDI 音符作为输入和输出声音的程序。是否存在这样的程序,还是我必须自己制作?
  • 代码中的变量device 是什么?无论如何,请阅读documentation
  • 我已经阅读了该文档,您的回答根本没有帮助。 “设备”不是一个真实的对象,我只是在我的问题中发布了该文档中的相关示例代码以提供上下文。在实际程序中,设备是一个接受 midi 指令(如 noteOn)并将它们合成为真实声音的对象。如果您阅读过文档,您会意识到没有提及用于实际播放 noteOn() 消息的预打包合成器,这就是我要问的问题:这种预构建的 Android 合成器是否存在?
  • 评论不是答案。无论如何,似乎还没有人知道答案。请在您的 Marshmallow 设备(我没有)上运行枚举代码,并在此处发布答案。

标签: java android audio midi android-6.0-marshmallow


【解决方案1】:

我还没有找到任何“官方”方法来控制 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 的状态。我想可能有一些可用的东西。

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

【讨论】:

  • 我尝试创建一个更完整的快速和肮脏的“触摸钢琴”,带有一个完整的八度音阶,您还可以通过 MIDI 程序更改消息选择乐器。 (参考selectInstrument()方法)Android Studio项目为here。从我的角度来看,一切都是公共领域,图书馆的文档说:“在 GPL 或 Apache 许可下获得许可 - 您的选择。”
  • 内部合成器兼容“通用 MIDI 级别 1”。 GM1 规范定义了 128 种乐器声音(其中一部分是声音 F/X 而不是实际乐器)。您可以在 https://midi.org/specifications 找到各种 MIDI 详细信息。仪器组是here。除非内部合成器支持 downloadable sounds (DLS),否则您无法添加自定义乐器。我自己对 DLS 并不熟悉。
  • @MarkusKauppinen 这很好用。谢谢。但是,你能告诉我如何将播放的文件存储在文件中,然后再播放吗?
  • @SwapnilD 这本身就是一个单独且相当广泛的主题。而且我自己也没有做过。但是您必须了解 MIDI 文件和 MIDI 事件,并将用户产生的 MIDI 事件(程序更改、音符打开、音符关闭等)存储到 MIDI 文件中。我相信 javax.sound.midi 包在这方面会很有用。另一方面,Android 已经支持 MIDI 文件播放,所以这很容易。
  • @MarkusKauppinen 我对您的代码有疑问。我如何管理节奏?我的意思是我有 4 个我想演奏的全音符我知道除了速度壮举之外的一切。但是我如何设置节奏或如何管理它?
【解决方案2】:

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

不,幸运的是,您不需要制作自己的合成器。 Android 已经内置了一个:SONiVOX 嵌入式音频合成器。 Android 状态在docs on SONiVOX JETCreator:

JET 与 SONiVOX 的嵌入式音频合成器 (EAS) 结合使用,后者是 Android 的 MIDI 播放设备。

不清楚您是否想要实时播放,或者您是否想要先创建乐曲,然后在同一个应用程序中播放。您还声明要播放 MIDI 音符,而不是文件。但是,正如您所知,Midi 播放是supported on android devices。因此,播放 .mid 文件的方式应该与使用 MediaPlayer 播放 .wav 文件的方式相同。

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

现在,如果你想直接播放notes不是文件,那么你可以使用this mididriver package。使用这个包,您应该能够将 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[])  

如果您想走得更低,您甚至可以使用AudioTrack 直接播放 PCM。

有关其他信息,这是我从与您遇到类似问题的人那里找到的blog post。他说:

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

希望我涵盖了所有内容。

【讨论】:

  • “现场乐器声音”意味着实时播放。
  • 问题实际上是 Marshmallow 是否(以及如何)允许通过本机 MIDI 包访问合成器,而无需使用 NDK。
  • @CL。我没有从问题中得到这个,谢谢你的澄清。不,看起来唯一的方法是使用 NDK。看起来我链接的包(在 GitHub 上)为你做了很多事情。来自博客文章(上面也有链接):“但它并没有暴露给我们这些可怜的开发人员。我也不知道是否可以使用 NDK 来只使用这些 C(++) API。我不知道”没有这方面的 C 知识,并且 NDK 文档没有提到它们是受支持/稳定的。” feature request
  • 这描述了Marshmallow之前的情况。有什么改变吗?
  • @CL。由于无法找到任何其他说明,midi 流未在android.media.midi 概述中列出的事实,我能找到的关于 midi 流的唯一文档来自SONiVOX EAS API,并且功能请求我上面列出的已于 2015 年 3 月(发布前 7 个月)关闭,我会说它没有改变任何东西。
【解决方案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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-16
    相关资源
    最近更新 更多