当前位置: 首页 > 面试题库 >

Android 6.0(棉花糖):如何演奏Midi音符?

范修伟
2023-03-14
问题内容

我正在创建一个可产生现场乐器声音的应用程序,并且计划使用Android棉花糖(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);

然后,我已经向端口发送了一个测试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音符消息

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支持是相当新的,因此我无法在线找到任何教程或示例合成器应用程序。任何见解均表示赞赏。


问题答案:

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

可能最简单的选择是为Sonivox合成器使用Android
midi驱动程序

以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。目前,访问Play商店的设备中只有4.6%运行Android
6.x,因此您的应用不会有太多的受众。

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

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

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

  • Java的javax.sound.midi包的端口,用于抽象底层MIDI技术细节
  • USB MIDI驱动程序,用于通过USB MIDI接口连接到数码钢琴/键盘
  • MIDI over Bluetooth LE驱动程序,用于无线连接到通过Bluetooth LE支持MIDI的数码钢琴/键盘(例如,一些最新的Roland和Dexibell数码钢琴)
  • 适用于Android的JFugue音乐库端口,用于进一步抽象MIDI详细信息,并根据音乐理论进行思考


 类似资料:
  • 问题内容: 我在Android 6.0(棉花糖)上遇到了日期格式问题。引发以下异常的代码是我的应用程序用于API请求(“客户端”)的纯Java库(单独构建)。如果相关的话,该库是用Java 1.6构建的……无论如何,这是代码。 … 具有价值; …“修剪”之后是; 该代码自Froyo成立以来一直有效,并且已经过单元测试。除了棉花糖,所有东西都会抛出异常。 偏移量“ 21”是10:59中“ 9”之后的

  • 问题内容: 我有一个图像URI,我想将此URI转换为真实路径。我看了很多答案,但没有一个对我有用。我正在使用棉花糖6.0.1。图片URI为。 码: 问题答案: A 不是。A 不必代表您可以访问的文件系统上的文件。将可能指向的内容是: 存储在无法访问的可移动存储中 存储在另一个应用程序的内部存储中,您无法访问 以加密形式存储,需要解密时 存储在SQLite数据库的BLOB列中,需要将其加载并提供服务

  • 当我运行app时,它在logcat中显示这个 java.lang.SecurityException:权限拒绝:从ProcessRecord{2dd511f 24656:com.marg.pharmanxt/u0a158}(PID=24656,UID=10158)中启动意图{act=android.Intent.action.call dat=tel:xxxxxxxxxcapt=2dd511f 2

  • IntelliJ Android Studio上次更新(2016年1月30日起)支持Java8吗? 我用了一个lambda表达式 注意:我使用的是最后一个API级别

  • 我现在找了一段时间,想做什么都找不到答案。 我想播放一个midi文件,并在播放时在屏幕上显示音符。当一个音符停止播放时,它应该从屏幕上消失。 我可以用音序器播放midi,但不知道如何让音符播放,或者何时停止播放音符。 我研究过ControllerEventListeners和MetaEventListeners,但仍然不知道如何做到这一点。 任何帮助都将不胜感激。

  • 问题内容: 我正在尝试通过Android拨打电话,并且还设置了运行时权限。并且询问是否允许拨打电话。但是,当我按允许时,应用程序崩溃: 这是我的实现方式: 这是我在logcat中获得的: 是什么原因造成的? 问题答案: 堆栈跟踪似乎表明您的权限流工作正常,但是对from 的调用崩溃了。是你传递到设置是否正确?我看不到在代码的那部分中设置了它。 另请注意,它可以代表您处理SDK版本检查,因此您应该可