[Scummvm-git-logs] scummvm master -> d6df7db11430100d70eaa0628a36a54c2e3a103d
lephilousophe
noreply at scummvm.org
Sat Sep 5 22:03:17 UTC 2026
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
d6df7db114 ANDROID: Add MIDI driver support
Commit: d6df7db11430100d70eaa0628a36a54c2e3a103d
https://github.com/scummvm/scummvm/commit/d6df7db11430100d70eaa0628a36a54c2e3a103d
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-06T00:03:13+02:00
Commit Message:
ANDROID: Add MIDI driver support
BLE devices need an helper application to make them known.
Changed paths:
A backends/midi/android.cpp
A backends/platform/android/org/scummvm/scummvm/MidiPort.java
backends/module.mk
backends/platform/android/jni-android.cpp
backends/platform/android/jni-android.h
backends/platform/android/org/scummvm/scummvm/ScummVM.java
backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
base/plugins.cpp
po/POTFILES
diff --git a/backends/midi/android.cpp b/backends/midi/android.cpp
new file mode 100644
index 00000000000..fc28471a719
--- /dev/null
+++ b/backends/midi/android.cpp
@@ -0,0 +1,299 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+// Allow use of stuff in <time.h> and abort()
+#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
+#define FORBIDDEN_SYMBOL_EXCEPTION_abort
+
+// Disable printf override in common/forbidden.h to avoid
+// clashes with log.h from the Android SDK.
+// That header file uses
+// __attribute__ ((format(printf, 3, 4)))
+// which gets messed up by our override mechanism; this could
+// be avoided by either changing the Android SDK to use the equally
+// legal and valid
+// __attribute__ ((format(__printf__, 3, 4)))
+// or by refining our printf override to use a varadic macro
+// (which then wouldn't be portable, though).
+// Anyway, for now we just disable the printf override globally
+// for the Android port
+#define FORBIDDEN_SYMBOL_EXCEPTION_printf
+
+#include "common/scummsys.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "backends/platform/android/android.h"
+#include "backends/platform/android/jni-android.h"
+
+#include "audio/musicplugin.h"
+#include "audio/mpu401.h"
+
+#include "common/translation.h"
+
+#include <dlfcn.h>
+#include <amidi/AMidi.h>
+
+class MidiDriver_Android : public MidiDriver_MPU401 {
+public:
+ MidiDriver_Android(jobject device, int32_t portId);
+ ~MidiDriver_Android();
+ int open() override;
+ bool isOpen() const override { return _inPort; }
+ void close() override;
+ void send(uint32 b) override;
+ void sysEx(const byte *msg, uint16 length) override;
+
+private:
+ AMidiDevice *_device;
+ int32_t _portId;
+ AMidiInputPort *_inPort;
+
+
+ // We can't use NDK weak symbols because AMidi depends on libamidi.so to be loaded when our own library is
+ // but there is no weak library concept so if we reference it, it must be present.
+ static int amidi_loaded;
+ static media_status_t (*AMidiDevice_fromJava)(
+ JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr);
+ static media_status_t (*AMidiDevice_release)(const AMidiDevice *midiDevice);
+ static ssize_t (*AMidiDevice_getNumInputPorts)(const AMidiDevice *device);
+ static media_status_t (*AMidiInputPort_open)(const AMidiDevice *device, int32_t portNumber,
+ AMidiInputPort **outInputPortPtr);
+ static ssize_t (*AMidiInputPort_send)(const AMidiInputPort *inputPort, const uint8_t *buffer,
+ size_t numBytes);
+ static void (*AMidiInputPort_close)(const AMidiInputPort *inputPort);
+};
+
+int MidiDriver_Android::amidi_loaded = 0;
+media_status_t (*MidiDriver_Android::AMidiDevice_fromJava)(
+ JNIEnv *env, jobject midiDeviceObj, AMidiDevice **outDevicePtrPtr) = nullptr;
+media_status_t (*MidiDriver_Android::AMidiDevice_release)(const AMidiDevice *midiDevice) = nullptr;
+ssize_t (*MidiDriver_Android::AMidiDevice_getNumInputPorts)(const AMidiDevice *device) = nullptr;
+media_status_t (*MidiDriver_Android::AMidiInputPort_open)(const AMidiDevice *device, int32_t portNumber,
+ AMidiInputPort **outInputPortPtr) = nullptr;
+ssize_t (*MidiDriver_Android::AMidiInputPort_send)(const AMidiInputPort *inputPort, const uint8_t *buffer,
+ size_t numBytes) = nullptr;
+void (*MidiDriver_Android::AMidiInputPort_close)(const AMidiInputPort *inputPort) = nullptr;
+
+MidiDriver_Android::MidiDriver_Android(jobject device, int32_t portId)
+ : _device(nullptr), _portId(portId), _inPort(nullptr) {
+ if (!device) {
+ // Device couldn't be open in Android side: create a bogus device
+ return;
+ }
+
+ if (!amidi_loaded) {
+ // Lazy load the AMidi functions
+ void *amidi = dlopen("libamidi.so", RTLD_NOW | RTLD_LOCAL);
+ if (!amidi) {
+ warning("MidiDriver_Android can't load AMidi");
+ amidi_loaded = -1;
+ }
+
+#define LOAD_FUNC(n, T) \
+ if (!amidi_loaded && !(MidiDriver_Android::n = (T)dlsym(amidi, #n))) { \
+ amidi_loaded = -1; \
+ warning("MidiDriver_Android can't find AMidi function %s", #n); \
+ }
+
+ LOAD_FUNC(AMidiDevice_fromJava, media_status_t (*)(JNIEnv *, jobject, AMidiDevice **));
+ LOAD_FUNC(AMidiDevice_release, media_status_t (*)(const AMidiDevice *));
+ LOAD_FUNC(AMidiDevice_getNumInputPorts, ssize_t (*)(const AMidiDevice *));
+ LOAD_FUNC(AMidiInputPort_open, media_status_t (*)(const AMidiDevice *, int32_t, AMidiInputPort **));
+ LOAD_FUNC(AMidiInputPort_send, ssize_t (*)(const AMidiInputPort *, const uint8_t *, size_t));
+ LOAD_FUNC(AMidiInputPort_close, void (*)(const AMidiInputPort *));
+
+ if (!amidi_loaded) {
+ amidi_loaded = 1;
+ }
+
+ }
+ if (amidi_loaded != 1) {
+ // AMidi loading failed: create a bogus device
+ return;
+ }
+
+ JNIEnv *env = JNI::getEnv();
+
+ media_status_t status = MidiDriver_Android::AMidiDevice_fromJava(env, device, &_device);
+ env->DeleteLocalRef(device);
+
+ if (status != AMEDIA_OK) {
+ warning("Can't open MIDI device: %d", status);
+ _device = nullptr;
+ }
+}
+
+MidiDriver_Android::~MidiDriver_Android() {
+ if (_inPort) {
+ MidiDriver_Android::AMidiInputPort_close(_inPort);
+ _inPort = nullptr;
+ }
+ if (_device) {
+ MidiDriver_Android::AMidiDevice_release(_device);
+ _device = nullptr;
+ }
+}
+
+int MidiDriver_Android::open() {
+ if (!_device) {
+ return MERR_DEVICE_NOT_AVAILABLE;
+ }
+
+ if (isOpen()) {
+ return MERR_ALREADY_OPEN;
+ }
+
+ ssize_t inPorts = MidiDriver_Android::AMidiDevice_getNumInputPorts(_device);
+ if (inPorts < 0) {
+ warning("Can't get MIDI device input ports: %d", (int)inPorts);
+ return MERR_DEVICE_NOT_AVAILABLE;
+ }
+
+ if (_portId >= inPorts) {
+ return MERR_DEVICE_NOT_AVAILABLE;
+ }
+
+ media_status_t status = MidiDriver_Android::AMidiInputPort_open(_device, _portId, &_inPort);
+ if (status != AMEDIA_OK) {
+ g_system->displayMessageOnOSD(_("Can't connect MIDI port: no sound will be produced"));
+ _inPort = nullptr;
+ return MERR_CANNOT_CONNECT;
+ }
+
+ return 0;
+}
+
+void MidiDriver_Android::close() {
+ MidiDriver_MPU401::close();
+
+ if (_inPort) {
+ MidiDriver_Android::AMidiInputPort_close(_inPort);
+ _inPort = nullptr;
+ }
+}
+
+void MidiDriver_Android::send(uint32 b) {
+ assert(isOpen());
+
+ midiDriverCommonSend(b);
+
+ // Extract the MIDI data
+ byte data[3] = {
+ static_cast<byte>(b & 0x000000FF), // status byte
+ static_cast<byte>((b & 0x0000FF00) >> 8), // first byte
+ static_cast<byte>((b & 0x00FF0000) >> 16), // second byte
+ };
+
+ size_t data_length;
+
+ // Compute the correct length of the MIDI command. This is important,
+ // else things may screw up badly...
+ switch (data[0] & 0xF0) {
+ case 0x80: // Note Off
+ case 0x90: // Note On
+ case 0xA0: // Polyphonic Aftertouch
+ case 0xB0: // Controller Change
+ case 0xE0: // Pitch Bending
+ data_length = 3;
+ break;
+ case 0xC0: // Programm Change
+ case 0xD0: // Monophonic Aftertouch
+ data_length = 2;
+ break;
+ default:
+ warning("Android driver encountered unsupported status byte: 0x%02x", data[0]);
+ data_length = 3;
+ break;
+ }
+
+ MidiDriver_Android::AMidiInputPort_send(_inPort, data, data_length);
+}
+
+void MidiDriver_Android::sysEx(const byte *msg, uint16 length) {
+ assert(isOpen());
+
+ unsigned char buf[270];
+
+ assert(length + 2 <= ARRAYSIZE(buf));
+
+ midiDriverCommonSysEx(msg, length);
+
+ // Add SysEx frame
+ buf[0] = 0xF0;
+ memcpy(buf + 1, msg, length);
+ buf[length + 1] = 0xF7;
+
+ MidiDriver_Android::AMidiInputPort_send(_inPort, buf, length + 2);
+}
+
+
+// Plugin interface
+
+class AndroidMusicPlugin : public MusicPluginObject {
+public:
+ const char *getName() const {
+ return "Android";
+ }
+
+ const char *getId() const {
+ return "android";
+ }
+
+ MusicDevices getDevices() const;
+ Common::Error createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle = 0) const;
+};
+
+MusicDevices AndroidMusicPlugin::getDevices() const {
+ MusicDevices devices;
+ Common::Array<Common::String> names = JNI::getMIDIDevices();
+ for (uint i = 0 ; i < names.size(); i++) {
+ devices.push_back(MusicDevice(this, names[i], MT_GM)); //Assume GM here
+ }
+ return devices;
+}
+
+Common::Error AndroidMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle device) const {
+ Common::Array<Common::String> names = JNI::getMIDIDevices();
+
+ jobject midiDevice = nullptr;
+ int32_t port = 0;
+
+ for (uint i = 0 ; i < names.size(); i++) {
+ MusicDevice md(this, names[i], MT_GM);
+ if (md.getHandle() == device) {
+ midiDevice = JNI::openMIDIDevice(i, &port);
+ break;
+ }
+ }
+ // Always return a driver even if the device doesn't exist: engines don't handle the error
+ // We will fail at opening
+ *mididriver = new MidiDriver_Android(midiDevice, port);
+ return Common::kNoError;
+}
+
+//#if PLUGIN_ENABLED_DYNAMIC(ANDROID)
+ //REGISTER_PLUGIN_DYNAMIC(ANDROID, PLUGIN_TYPE_MUSIC, AndroidMusicPlugin);
+//#else
+ REGISTER_PLUGIN_STATIC(ANDROID, PLUGIN_TYPE_MUSIC, AndroidMusicPlugin);
+//#endif
diff --git a/backends/module.mk b/backends/module.mk
index b3b8a5a0028..02a21d2f855 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -355,6 +355,7 @@ MODULE_OBJS += \
fs/android/android-posix-fs.o \
fs/android/android-saf-fs.o \
graphics/android/android-graphics.o \
+ midi/android.o \
mixer/android/android-mixer.o \
mutex/pthread/pthread-mutex.o \
networking/basic/android/jni.o \
diff --git a/backends/platform/android/jni-android.cpp b/backends/platform/android/jni-android.cpp
index a954f2c7363..6378c82d0b1 100644
--- a/backends/platform/android/jni-android.cpp
+++ b/backends/platform/android/jni-android.cpp
@@ -103,6 +103,8 @@ jmethodID JNI::_MID_getScummVMLogPath;
jmethodID JNI::_MID_setCurrentGame = 0;
jmethodID JNI::_MID_notifyHTTPService = 0;
jmethodID JNI::_MID_getTTSManager = 0;
+jmethodID JNI::_MID_getMIDIDevices = 0;
+jmethodID JNI::_MID_openMIDIDevice = 0;
jmethodID JNI::_MID_getSysArchives = 0;
jmethodID JNI::_MID_getAllStorageLocations = 0;
jmethodID JNI::_MID_initSurface = 0;
@@ -612,6 +614,64 @@ jobject JNI::getTTSManager() {
return ttsManager;
}
+Common::Array<Common::String> JNI::getMIDIDevices() {
+ Common::Array<Common::String> res;
+
+ JNIEnv *env = JNI::getEnv();
+
+ jobjectArray array =
+ (jobjectArray)env->CallObjectMethod(_jobj, _MID_getMIDIDevices);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Error getting MIDI devices");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return res;
+ }
+
+ jsize size = env->GetArrayLength(array);
+ for (jsize i = 0; i < size; ++i) {
+ jstring midi_obj = (jstring)env->GetObjectArrayElement(array, i);
+ const char *midi = env->GetStringUTFChars(midi_obj, 0);
+
+ if (midi) {
+ res.push_back(midi);
+ env->ReleaseStringUTFChars(midi_obj, midi);
+ }
+
+ env->DeleteLocalRef(midi_obj);
+ }
+
+ env->DeleteLocalRef(array);
+ return res;
+}
+
+jobject JNI::openMIDIDevice(int device, int32_t *portId) {
+ Common::Array<Common::String> res;
+
+ JNIEnv *env = JNI::getEnv();
+
+ jintArray portIdArray = env->NewIntArray(1);
+
+ jobject deviceObj = env->CallObjectMethod(_jobj, _MID_openMIDIDevice, device, portIdArray);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Error opening MIDI device");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ return nullptr;
+ }
+
+ env->GetIntArrayRegion(portIdArray, 0, 1, portId);
+ env->DeleteLocalRef(portIdArray);
+
+ return deviceObj;
+}
+
// The following adds assets folder to search set.
// However searching and retrieving from "assets" on Android this is slow
// so we also make sure to add the base directory, with a higher priority
@@ -756,6 +816,8 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
FIND_METHOD(, setCurrentGame, "(Ljava/lang/String;)V");
FIND_METHOD(, notifyHTTPService, "(IZ)V");
FIND_METHOD(, getTTSManager, "()Lorg/scummvm/scummvm/TextToSpeechManager;");
+ FIND_METHOD(, getMIDIDevices, "()[Ljava/lang/String;");
+ FIND_METHOD(, openMIDIDevice, "(I[I)Landroid/media/midi/MidiDevice;");
FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;");
FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
diff --git a/backends/platform/android/jni-android.h b/backends/platform/android/jni-android.h
index dc850d754ab..5763d610753 100644
--- a/backends/platform/android/jni-android.h
+++ b/backends/platform/android/jni-android.h
@@ -102,6 +102,8 @@ public:
static void setCurrentGame(const Common::String &target);
static void notifyHTTPService(int localPort, bool minimal);
static jobject getTTSManager();
+ static Common::Array<Common::String> getMIDIDevices();
+ static jobject openMIDIDevice(int deviceId, int *port);
static inline bool haveSurface();
static inline bool swapBuffers();
@@ -161,6 +163,8 @@ private:
static jmethodID _MID_setCurrentGame;
static jmethodID _MID_notifyHTTPService;
static jmethodID _MID_getTTSManager;
+ static jmethodID _MID_getMIDIDevices;
+ static jmethodID _MID_openMIDIDevice;
static jmethodID _MID_getSysArchives;
static jmethodID _MID_getAllStorageLocations;
static jmethodID _MID_initSurface;
diff --git a/backends/platform/android/org/scummvm/scummvm/MidiPort.java b/backends/platform/android/org/scummvm/scummvm/MidiPort.java
new file mode 100644
index 00000000000..e503cceb95d
--- /dev/null
+++ b/backends/platform/android/org/scummvm/scummvm/MidiPort.java
@@ -0,0 +1,152 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.scummvm.scummvm;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+// MIDI is available from Marshmallow but not exposed by NDK before Quince Tart
+ at RequiresApi(Build.VERSION_CODES.Q)
+class MidiPort {
+ private static class MidiDeviceOpenedListener implements MidiManager.OnDeviceOpenedListener {
+ final Object synchronizer = new Object();
+ MidiDevice midiDevice = null;
+
+ @Override
+ public void onDeviceOpened(MidiDevice midiDevice) {
+ synchronized (synchronizer) {
+ this.midiDevice = midiDevice;
+ synchronizer.notify();
+ }
+ }
+ }
+
+ private final MidiDeviceInfo device;
+ private final MidiDeviceInfo.PortInfo port;
+ private String name;
+
+ private MidiPort(MidiDeviceInfo device, MidiDeviceInfo.PortInfo port) {
+ this.device = device;
+ this.port = port;
+ }
+
+ @NonNull
+ public static ArrayList<MidiPort> getMidiPorts(@NonNull Context context) {
+ MidiManager manager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+ MidiDeviceInfo[] devices;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Set<MidiDeviceInfo> infosSet = manager.getDevicesForTransport(MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+ devices = infosSet.toArray(new MidiDeviceInfo[]{});
+ } else {
+ // This useless intermediate variable is here to allow attaching the annotation
+ @SuppressWarnings({"deprecation", "RedundantSuppression"})
+ MidiDeviceInfo[] devices_ = manager.getDevices();
+ devices = devices_;
+ }
+ ArrayList<MidiPort> ports = new ArrayList<>();
+ for (MidiDeviceInfo device : devices) {
+ for (MidiDeviceInfo.PortInfo port : device.getPorts()) {
+ if (port.getType() != MidiDeviceInfo.PortInfo.TYPE_INPUT) {
+ continue;
+ }
+ ports.add(new MidiPort(device, port));
+ }
+ }
+ ports.sort((MidiPort l, MidiPort r) -> l.getName().compareTo(r.getName()));
+ return ports;
+ }
+
+ @NonNull
+ public String getName() {
+ if (name != null) {
+ return name;
+ }
+
+ Bundle props = device.getProperties();
+ String name = props.getString(MidiDeviceInfo.PROPERTY_NAME);
+ final String serial = props.getString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER);
+ if (serial != null && !serial.isEmpty()) {
+ name += " (" + serial + ")";
+ }
+ name += " - ";
+ String portName = port.getName();
+ if (portName != null && !portName.isEmpty()) {
+ name += portName;
+ } else {
+ int portNum = port.getPortNumber();
+ name += portNum;
+ }
+ this.name = name;
+ return name;
+ }
+
+ public MidiDevice open(@NonNull Context context, @NonNull int[] portId) {
+ MidiManager manager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+
+ MidiDeviceOpenedListener listener = new MidiDeviceOpenedListener();
+ HandlerThread thread = new HandlerThread("MIDI Device Handler");
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+
+ BluetoothDevice ble;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ ble = device.getProperties().getParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, BluetoothDevice.class);
+ } else {
+ @SuppressWarnings({"deprecation", "RedundantSuppression"})
+ Object ble_ = device.getProperties().getParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE);
+ ble = (BluetoothDevice) ble_;
+ }
+ synchronized (listener.synchronizer) {
+ if (ble != null) {
+ manager.openBluetoothDevice(ble, listener, handler);
+ } else {
+ manager.openDevice(device, listener, handler);
+ }
+
+ try {
+ listener.synchronizer.wait();
+ } catch (InterruptedException e) {
+ Log.d(ScummVM.LOG_TAG, "Warning: interrupted while waiting for MIDI device");
+ thread.quit();
+ return null;
+ }
+ }
+
+ thread.quit();
+ portId[0] = port.getPortNumber();
+ return listener.midiDevice;
+ }
+}
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
index 02bff3af178..37c223f04d7 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
@@ -23,6 +23,7 @@ package org.scummvm.scummvm;
import android.content.res.AssetManager;
import android.graphics.PixelFormat;
+import android.media.midi.MidiDevice;
import android.util.Log;
import android.view.SurfaceHolder;
@@ -131,6 +132,10 @@ public abstract class ScummVM implements SurfaceHolder.Callback,
/** @noinspection unused */ @Keep
abstract protected TextToSpeechManager getTTSManager();
/** @noinspection unused */ @Keep
+ abstract protected String[] getMIDIDevices();
+ /** @noinspection unused */ @Keep
+ abstract protected MidiDevice openMIDIDevice(int device, int[] portId);
+ /** @noinspection unused */ @Keep
abstract protected String[] getSysArchives();
/** @noinspection unused */ @Keep
abstract protected String[] getAllStorageLocations();
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 740cb73a090..9ba500428f1 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -43,6 +43,7 @@ import android.hardware.usb.UsbManager;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
+import android.media.midi.MidiDevice;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.net.nsd.NsdManager;
@@ -87,6 +88,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
@@ -973,6 +975,37 @@ public class ScummVMActivity extends Activity {
return _tts;
}
+ @Override
+ protected String[] getMIDIDevices() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ // No MIDI support before Marshmallow and no NDK support before Quince Tart
+ return new String[]{};
+ }
+
+ ArrayList<MidiPort> ports = MidiPort.getMidiPorts(ScummVMActivity.this);
+ String[] devices = new String[ports.size()];
+ for (int i = 0; i < devices.length; i++) {
+ devices[i] = ports.get(i).getName();
+ }
+ return devices;
+ }
+
+ @Override
+ protected MidiDevice openMIDIDevice(int device, int[] portId) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ // No MIDI support before Marshmallow and no NDK support before Quince Tart
+ return null;
+ }
+
+ ArrayList<MidiPort> ports = MidiPort.getMidiPorts(ScummVMActivity.this);
+ if (device >= ports.size()) {
+ return null;
+ }
+ MidiPort portDevice = ports.get(device);
+
+ return portDevice.open(ScummVMActivity.this, portId);
+ }
+
@Override
protected String[] getSysArchives() {
File assetsDir = new File(_actualScummVMDataDir, "assets");
diff --git a/base/plugins.cpp b/base/plugins.cpp
index 93aaa7bcbfd..d6e11d086a6 100644
--- a/base/plugins.cpp
+++ b/base/plugins.cpp
@@ -128,6 +128,9 @@ public:
#if defined(RISCOS)
LINK_PLUGIN(RISCOS)
#endif
+ #if defined(ANDROID_BACKEND)
+ LINK_PLUGIN(ANDROID)
+ #endif
#if defined(MACOSX)
LINK_PLUGIN(COREAUDIO)
#endif
diff --git a/po/POTFILES b/po/POTFILES
index f43ba5ec61c..34056a85dde 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -96,6 +96,7 @@ backends/keymapper/hardware-input.cpp
backends/keymapper/remap-widget.cpp
backends/keymapper/virtual-mouse.cpp
backends/midi/windows.cpp
+backends/midi/android.cpp
backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
backends/networking/sdl_net/handlers/downloadfilehandler.cpp
backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp
More information about the Scummvm-git-logs
mailing list