Android has a built-in MIDI synthesizer. You can access it through the Android NDK. I described this in an article containing several downloads . This answer is a brief description of this article. What you see here is proof of concept. It will show how to play MIDI notes on Android, but needs to be improved. Suggestions for improvement are welcome :-)
Eclipse Java. , Delphi XE5 Mobile pack, , : Android SDK NDK. , Android SDK Google. Eclipse Android Development Tools (ADT) . Android SDK/NDK, Delphi XE5 ( Delphi, Options | Tools | SDK Manager). , Delphi Eclipse SDK NDK.
MIDI-, Willam Farmer. SoniVox, . (Java). org.drivers.midioutput, Java_org_drivers_midioutput_MidiDriver_ (. ).
midi.c jus, ndk-build . . mips x86 .
, : ndk . Delphi Delphi, : Rad Studio , Delphi SDK NDK. , C:, C:\ndk. MKLINK, ndk. , , , . , , , . ndk-build.
midi.c - NDK SoniVox
#include
#include "eas.h"
#include "eas_reverb.h"
#define NUM_BUFFERS 4
static EAS_DATA_HANDLE pEASData;
const S_EAS_LIB_CONFIG *pLibConfig;
static EAS_PCM *buffer;
static EAS_I32 bufferSize;
static EAS_HANDLE midiHandle;
jint
Java_org_drivers_midioutput_MidiDriver_version (JNIEnv *env, jobject clazz)
{
return 3;
}
jint
Java_org_drivers_midioutput_MidiDriver_init(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
pLibConfig = EAS_Config();
if (pLibConfig == NULL || pLibConfig->libVersion != LIB_VERSION)
return 0;
bufferSize = pLibConfig->mixBufferSize * pLibConfig->numChannels *
NUM_BUFFERS;
if ((result = EAS_Init(&pEASData)) != EAS_SUCCESS)
return 0;
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET,
EAS_PARAM_REVERB_CHAMBER);
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS,
EAS_FALSE);
if (result = EAS_OpenMIDIStream(pEASData, &midiHandle, NULL) !=
EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return 0;
}
return bufferSize;
}
jintArray
Java_org_drivers_midioutput_MidiDriver_config(JNIEnv *env,
jobject clazz)
{
jboolean isCopy;
if (pLibConfig == NULL)
return NULL;
jintArray configArray = (*env)->NewIntArray(env, 4);
jint *config = (*env)->GetIntArrayElements(env, configArray, &isCopy);
config[0] = pLibConfig->maxVoices;
config[1] = pLibConfig->numChannels;
config[2] = pLibConfig->sampleRate;
config[3] = pLibConfig->mixBufferSize;
(*env)->ReleaseIntArrayElements(env, configArray, config, 0);
return configArray;
}
jint
Java_org_drivers_midioutput_MidiDriver_render(JNIEnv *env,
jobject clazz,
jshortArray shortArray)
{
jboolean isCopy;
EAS_RESULT result;
EAS_I32 numGenerated;
EAS_I32 count;
jsize size;
if (pEASData == NULL)
return 0;
buffer =
(EAS_PCM *)(*env)->GetShortArrayElements(env, shortArray, &isCopy);
size = (*env)->GetArrayLength(env, shortArray);
count = 0;
while (count < size) { result = EAS_Render(pEASData, buffer + count, pLibConfig->mixBufferSize, &numGenerated);
if (result != EAS_SUCCESS)
break;
count += numGenerated * pLibConfig->numChannels;
}
(*env)->ReleaseShortArrayElements(env, shortArray, buffer, 0);
return count;
}
jboolean
Java_org_drivers_midioutput_MidiDriver_write(JNIEnv *env,
jobject clazz,
jbyteArray byteArray)
{
jboolean isCopy;
EAS_RESULT result;
jint length;
EAS_U8 *buf;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
buf = (EAS_U8 *)(*env)->GetByteArrayElements(env, byteArray, &isCopy);
length = (*env)->GetArrayLength(env, byteArray);
result = EAS_WriteMIDIStream(pEASData, midiHandle, buf, length);
(*env)->ReleaseByteArrayElements(env, byteArray, buf, 0);
if (result != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
jboolean
Java_org_drivers_midioutput_MidiDriver_shutdown(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
if ((result = EAS_CloseMIDIStream(pEASData, midiHandle)) != EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return JNI_FALSE;
}
if ((result = EAS_Shutdown(pEASData)) != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
ndk-build, lib .so. , midi.c libmidi.so. , midi.c.
MidiDriver.Java , audioTrack . , . , , Delphi Java MidiDriver: class MIDI_Output. Delphi.
MidiDriver - Java C-, SoniVox. MIDI_Output - Java Delphi. MIDI_Output MidiDriver.
MidiDriver - NDK
package org.drivers.midioutput;
import java.io.File;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
public class MidiDriver implements Runnable
{
private static final int SAMPLE_RATE = 22050;
private static final int BUFFER_SIZE = 4096;
private Thread thread;
private AudioTrack audioTrack;
private OnMidiStartListener listener;
private short buffer[];
public MidiDriver ()
{
Log.d ("midi", " *** MidiDriver started");
}
public void start ()
{
thread = new Thread (this, "MidiDriver");
thread.start ();
}
@Override
public void run ()
{
processMidi ();
}
public void stop ()
{
Thread t = thread;
thread = null;
while (t != null && t.isAlive ())
Thread.yield ();
}
private void processMidi ()
{
int status = 0;
int size = 0;
Log.d ("midi", " *** processMIDI");
if ((size = init()) == 0)
return;
buffer = new short [size];
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE, AudioTrack.MODE_STREAM);
if (audioTrack == null)
{
shutdown ();
return;
}
if (listener != null)
listener.onMidiStart();
audioTrack.play();
while (thread != null)
{
if (render (buffer) == 0)
break;
status = audioTrack.write (buffer, 0, buffer.length);
if (status < 0) break; }
if (render(buffer) > 0)
audioTrack.write(buffer, 0, buffer.length);
shutdown();
audioTrack.release();
}
public void setOnMidiStartListener (OnMidiStartListener l)
{
listener = l;
}
public static void load_lib (String libName)
{
File file = new File (libName);
if (file.exists ())
{
System.load (libName);
} else
{
System.loadLibrary (libName);
}
}
public interface OnMidiStartListener
{
public abstract void onMidiStart ();
}
public native int version ();
private native int init ();
public native int [] config ();
private native int render (short a []);
public native boolean write (byte a []);
private native boolean shutdown ();
static
{
System.loadLibrary ("midi");
}
}
MIDI_Output - MidiDriver
package org.drivers.midioutput;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import org.drivers.midioutput.MidiDriver.OnMidiStartListener;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Log;
public class MIDI_Output implements OnMidiStartListener
{
protected MidiDriver midi_driver;
protected MediaPlayer media_player;
public MIDI_Output ()
{
midi_driver = new MidiDriver();
Log.d ("midi", " *** midi_driver opened with version " +
String.valueOf (midi_driver.version ()));
if (midi_driver != null)
midi_driver.setOnMidiStartListener (this);
}
public int test_int (int n)
{
int sq = n * n;
return n * n;
}
public void start ()
{
if (midi_driver != null)
{
midi_driver.start ();
Log.d ("midi", " *** midi_driver.start ()");
}
}
public void stop ()
{
if (midi_driver != null)
{
midi_driver.stop ();
Log.d ("midi", " *** midi_driver.stop ()");
}
stopSong ();
}
@Override
public void onMidiStart()
{
Log.d ("midi", " *** onSMidiStart");
}
protected void putShort (int m, int n, int v)
{
if (midi_driver != null)
{
byte msg [] = new byte [3];
msg [0] = (byte) m;
msg [1] = (byte) n;
msg [2] = (byte) v;
Log.d ("midi", " *** putShort (" + String.valueOf (m) + ", " + String.valueOf (n) + ", " + String.valueOf (v) + ")");
midi_driver.write (msg);
}
}
public boolean isPlayingSong ()
{
return media_player != null;
}
public void playSong (String audioFilename)
{
String audioPath;
try
{
FileDescriptor fd = null;
audioFilename = "/Data/d/song.mid";
File baseDir = Environment.getExternalStorageDirectory ();
audioPath = baseDir.getAbsolutePath () + audioFilename;
Log.d ("midi", " *** Look for file: " + audioPath);
FileInputStream fis = new FileInputStream (audioPath);
fd = fis.getFD ();
if (fd != null)
{
Log.d ("midi", " *** Found file, trying to play: " + audioPath);
MediaPlayer mediaPlayer = new MediaPlayer ();
mediaPlayer.setDataSource (fd);
mediaPlayer.prepare ();
mediaPlayer.start ();
}
} catch (Exception e)
{
Log.d ("midi", " *** Exception while trying to play file: " + e.getMessage ());
}
}
public void stopSong ()
{
if (media_player != null)
{
media_player.stop ();
media_player.release ();
media_player = null;
}
}
}
MidiDriver MIDI_Output Eclipse Android, MainActivity. . (adb). adb -d logcat. log.d( "midi", "*** message" ) , , . , , Android ( ), , . Delphi, . Delphi.
, MIDI_Output.apk \bin. Delphi Java.
Java Delphi JNI. RedTitan. TMIDI_Output_Device.
, test_apk_fn MIDI_Output.apk Android. JNI , Java. javaClassName , Java. Delphi JNI .
TMIDI_Output_Device - Delphi Java MIDI_Output
unit MIDI_Output_Device;
interface
uses
System.SysUtils,
FMX.Types,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes,
Androidapi.Jni,
Androidapi.JNI.Dalvik,
Androidapi.JNI.GraphicsContentViewText;
const
test_apk_fn = '/storage/sdcard0/Data/d/MIDI_Output.apk';
type
TMIDI_Output_Device = class (TObject)
private
JavaEnv: PJNIEnv;
context: JContext;
CL: JDexClassLoader;
JavaObject: JObject;
JavaObjectID: JNIObject;
jTempClass: Jlang_Class;
jTemp: JObject;
oTemp: TObject;
jLocalInterface: ILocalObject;
optimizedpath_jfile: JFile;
dexpath_jstring, optimizedpath_jstring: JString;
fun_version: JNIMethodID;
fun_start: JNIMethodID;
fun_put_short: JNIMethodID;
fun_play_song: JNIMethodID;
public
constructor Create;
procedure setup_midi_output (class_name: string);
procedure put_short (status, data_1, data_2: integer);
procedure play_song (file_name: string);
end; // Class: MIDI_Output_Device //
implementation
uses
FMX.Helpers.Android;
constructor TMIDI_Output_Device.Create;
begin
setup_midi_output ('MIDI_Output');
end; // Create //
procedure TMIDI_Output_Device.setup_midi_output (class_name: string);
var
javaClassName: string;
ji: JNIInt;
jiStatus, jiData_1, jiData_2: JNIValue;
begin
javaClassName := Format ('org.drivers.midioutput/%s', [class_name]);
context := SharedActivityContext;
JavaEnv := TJNIResolver.GetJNIEnv;
Log.d ('Loading external library from "' + test_apk_fn + '"');
dexpath_jstring := StringToJString (test_apk_fn);
// locate/create a directory where our dex files can be put
optimizedpath_jfile := context.getDir (StringToJString ('outdex'), TJContext.javaclass.mode_private);
optimizedpath_jstring := optimizedpath_jfile.getAbsolutePath;
Log.d ('Path for DEX files = ' + JStringToString (optimizedpath_jstring));
Log.d ('APK containing target class = ' + JStringToString (dexpath_jstring));
CL := TJDexClassLoader.JavaClass.init (dexpath_jstring, optimizedpath_jstring, nil, TJDexClassLoader.JavaClass.getSystemClassLoader);
// Test whether the Dex class is loaded, if not, exit
if not assigned (CL) then
begin
Log.d ('?Failed to get DEXClassLoader');
exit;
end; // if
// Load the Java class
jTempClass := CL.loadClass (StringToJString (javaClassName));
if assigned (jTempClass) then
begin
jTemp := jTempClass; // N.B You could now import the entire class
if jTemp.QueryInterface (ILocalObject,jLocalInterface) = S_OK then
begin
// supports ilocalobject
JavaObject := jTempClass.newInstance;
oTemp := JavaObject as TObject;
JavaObjectID := tjavaimport (otemp).GetObjectID;
Log.d (oTemp.ClassName);
// try to access the version function from the midi_output class
fun_version := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'version', '()I');
if not assigned (fun_version) then
begin
Log.d ('?fun_version not supported');
end else
begin
ji := JavaEnv^.CallIntMethodA (JavaEnv, JavaObjectID, fun_version, nil);
Log.d ('version returns ' + inttostr (ji));
end; // if
// try to access the start function from the midi_output class
fun_start := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'start', '()V');
if not assigned (fun_start) then
begin
Log.d ('?fun_start not supported');
end else
begin
JavaEnv^.CallVoidMethodA (JavaEnv, JavaObjectID, fun_start, nil);
Log.d ('fun_start found');
end; // if
// try to access the putShort function from the midi_output class
fun_put_short := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'putShort','(III)V');
if not assigned (fun_put_short) then
begin
Log.d ('?putShort not supported');
end else
begin
Log.d (Format (' @@@ putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
put_short ($90, 60, 127);
end; // if
// try to access the playSong function from the midi_output class
fun_play_song := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'playSong', '(Ljava/lang/String)V');
if not assigned (fun_play_song) then
begin
Log.d ('?playSong not supported');
end else
begin
Log.d (' @@@ playSong found');
end; // if
end else
begin
Log.d ('?Could not derive ILOCALOBJECT');
end;
end else Log.d ('?'+javaClassname+' not found')
end; // setup_midi_output //
procedure TMIDI_Output_Device.put_short (status, data_1, data_2: integer);
var
jiStatus, jiData_1, jiData_2: JNIValue;
x: array of JNIOBJECT;
begin
jiStatus.i := status;
jiData_1.i := data_1;
jiData_2.i := data_2;
setLength (x, 3);
x [0] := jiStatus.l;
x [1] := jiData_1.l;
x [2] := jiData_2.l;
Log.d (Format ('putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_put_short, x);
end; // put_short //
procedure TMIDI_Output_Device.play_song (file_name: string);
var
x: array of JNIObject;
begin
SetLength (x, 1);
x [0] := StringToJNIString (JavaEnv, file_name);
Log.d ('playSong (' + file_name + ')');
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_play_song, x);
end; // playSong //
end. // Unit: MIDI_Output_Device //
Delphi , Java. libmidi.so, Android - .zip , Java. MIDI_Output.apk WinZip WinRar, . lib, libmidi.so ARM 5 7. adb -d logcat adb , MIDI_Output.apk. , , libmidi.so .
Libmidi.so \usr\lib - \platform Android SDK. : C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\android-ndk-r8e\platform\android-14\arch-arm\usr\lib. , .
, , MIDI Delphi Android-. :
NDK ?
NDK Delphi
DLL. MidiDriver
.
C Pascal, NDK.
MediaPlayer MIDI
. , MediaPlayer Activity
, Delphi MainActivity JNI Java
. , .
.apk,
JavaVM . libmidi.so
\usr\lib.
, .apk.
/data/app -lib
, JNI
Play Store .