Android 6.0 (Marshmallow): How to Play Midi Notes

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

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

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

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.

With your Android project open in Android Studio:

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

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:

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

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

// 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.

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.

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.

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.x so there wouldn't be much audience for your app.

Of course if you want to use the android.media.midi package you could then use the library to implement a android.media.midi.MidiReceiver to 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.

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

Edit: Other possibly interesting libraries:

  • port of Java's javax.sound.midi package for abstracting the low level MIDI technical details
  • USB MIDI Driver for connecting to a digital piano/keyboard with a USB MIDI connector
  • MIDI over Bluetooth LE driver for connecting wirelessly to a digital piano/keyboard that supports MIDI over Bluetooth LE (like e.g. some recent Roland and Dexibell digital pianos)
  • JFugue Music library port for Android for further abstracting the MIDI details and instead thinking in terms of music theory

Android. MIDI. Playing first N notes

Of course it's possible. It'd be a lot easier to find a library to do it though, try something like this:

https://code.google.com/p/android-midi-lib/

If that doesn't do it, google midi file manipulation library java or something like that, you'll find something.

Edit: Take a look at this, it's newer and more relevant
Android 6.0 (Marshmallow): How to play midi notes?

Android - Play SoundFont with MIDI file

There are two libraries that will be used to play a midi file using SoundFont.

Midi Driver

Just a synthesizer for playing MIDI note on Android. You can use it with USB/Bluetooth-MIDI library together to create your MIDI application.

SoundFont2 file is supported.

Android MIDI Library

This library provides an interface to read, manipulate, and write MIDI files. "Playback" is supported as a real-time event dispatch system. This library does NOT include actual audio playback or device interfacing.

To initialize SF2-SoundBank

SF2Soundbank sf = new SF2Soundbank(getAssets().open("test.sf2"));
synth = new SoftSynthesizer();
synth.open();
synth.loadAllInstruments(sf);
synth.getChannels()[0].programChange(0);
synth.getChannels()[1].programChange(1);
recv = synth.getReceiver();

To Play the Midi notes from midi file

MidiFile midiFile = new MidiFile(getAssets().open("test.mid"));

// Create a new MidiProcessor:
MidiProcessor processor = new MidiProcessor(midiFile);

// listen for all midi events:
processor.registerEventListener(new MidiEventListener() {
@Override
public void onStart(boolean fromBeginning) {

}

@Override
public void onEvent(MidiEvent event, long ms) {

if (event.getClass() == NoteOn.class) {

NoteOn noteOn = ((NoteOn) event);

try {
ShortMessage msg = new ShortMessage();
msg.setMessage(ShortMessage.NOTE_ON, channel, noteOn.getNoteValue(), noteOn.getVelocity());
recv.send(msg, ms);
} catch (InvalidMidiDataException e) {
e.printStackTrace();
}

} else if (event.getClass() == NoteOff.class) {

NoteOff noteOff = ((NoteOff) event);

try {
ShortMessage msg = new ShortMessage();
msg.setMessage(ShortMessage.NOTE_ON, channel, noteOff.getNoteValue(), noteOff.getVelocity());
recv.send(msg, ms);
} catch (InvalidMidiDataException e) {
e.printStackTrace();
}

}
}

@Override
public void onStop(boolean finished) {

}
}, MidiEvent.class);

// Start the processor:
processor.start();

Variable to define SF channel

private int channel = 0;

android - play a sound(note) for a certain duration

You can use Timer Task for this work. It executes your code for specific limit of time.

How do I play non-sine notes on Android? MIDI?

All I can say is to check out pitch-shifting (which you seem to have heard of) and soundpool (which would require some recording of your own) and these 2 links:

Audio Playback Rate in Android

Programmatically increase the pitch of an array of audio samples

the second link seems to have more info.



Related Topics



Leave a reply



Submit