android midi seems to work ok

This commit is contained in:
Stefano D'Angelo 2024-01-28 11:54:30 +01:00
parent 958704bcd0
commit 7c4913543e
6 changed files with 151 additions and 130 deletions

Binary file not shown.

View File

@ -30,6 +30,10 @@ CLASSES := \
MainActivity \
MainActivity$$WebAppInterface
ifeq (${HAS_MIDI_IN}, yes)
CLASSES += MainActivity$$WebAppInterface$$MidiDeviceCallback MainActivity$$WebAppInterface$$1
endif
CXXFLAGS := \
-fPIC \
-DNDEBUG \
@ -46,6 +50,10 @@ LDFLAGS := \
-llog \
-landroid
ifeq (${HAS_MIDI_IN}, yes)
LDFLAGS += -lamidi
endif
all: build/${BUNDLE_NAME}.apk
build/${BUNDLE_NAME}.apk: build/gen/${BUNDLE_NAME}.aligned.apk ${KEY_STORE}

View File

@ -14,6 +14,14 @@ import android.webkit.JavascriptInterface;
import android.content.Context;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
{{?it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0}}
import android.media.midi.MidiManager;
import android.media.midi.MidiManager.DeviceCallback;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceInfo.PortInfo;
import android.media.midi.MidiDevice;
import java.util.ArrayList;
{{?}}
public class MainActivity extends Activity {
static {
@ -24,10 +32,79 @@ public class MainActivity extends Activity {
public native void nativeAudioStop();
public native float nativeGetParameter(int i);
public native void nativeSetParameter(int i, float v);
{{?it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0}}
public native void addMidiPort(MidiDevice d, int p);
public native void removeMidiPort(MidiDevice d, int p);
{{?}}
private WebView webView;
public class WebAppInterface {
{{?it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0}}
private MidiManager midiManager;
private MidiDeviceCallback midiDeviceCallback;
public ArrayList<MidiDevice> midiDevices = new ArrayList<MidiDevice>();
public void addMidiDevices(MidiDeviceInfo[] devices) {
for (int i = 0; i < devices.length; i++) {
if (devices[i].getOutputPortCount() == 0)
continue;
midiManager.openDevice(devices[i],
new MidiManager.OnDeviceOpenedListener() {
@Override
public void onDeviceOpened(MidiDevice device) {
PortInfo[] ports = device.getInfo().getPorts();
for (int i = 0; i < ports.length; i++)
if (ports[i].getType() == PortInfo.TYPE_OUTPUT)
addMidiPort(device, ports[i].getPortNumber());
WebAppInterface.this.midiDevices.add(device);
}
}, null);
}
}
public void removeMidiDevices(MidiDeviceInfo[] devices) {
for (int i = 0; i < midiDevices.size(); i++) {
MidiDevice device = midiDevices.get(i);
int id = device.getInfo().getId();
int j = 0;
for (; j < devices.length; j++)
if (id == devices[j].getId())
break;
if (j == devices.length)
continue;
PortInfo[] ports = device.getInfo().getPorts();
for (j = 0; j < ports.length; j++)
if (ports[j].getType() == PortInfo.TYPE_OUTPUT)
removeMidiPort(device, ports[j].getPortNumber());
midiDevices.remove(i);
}
}
public void removeAllMidiDevices() {
for (int i = 0; i < midiDevices.size(); i++) {
MidiDevice device = midiDevices.get(i);
PortInfo[] ports = device.getInfo().getPorts();
for (int j = 0; j < ports.length; j++)
if (ports[j].getType() == PortInfo.TYPE_OUTPUT)
removeMidiPort(device, ports[j].getPortNumber());
}
midiDevices.clear();
}
public class MidiDeviceCallback extends MidiManager.DeviceCallback {
@Override
public void onDeviceAdded(MidiDeviceInfo device) {
WebAppInterface.this.addMidiDevices(new MidiDeviceInfo[]{device});
}
@Override
public void onDeviceRemoved(MidiDeviceInfo device) {
WebAppInterface.this.removeMidiDevices(new MidiDeviceInfo[]{device});
}
}
{{?}}
@JavascriptInterface
public boolean hasAudioPermission() {
return MainActivity.this.checkCallingOrSelfPermission(android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
@ -40,12 +117,27 @@ public class MainActivity extends Activity {
@JavascriptInterface
public boolean audioStart() {
{{?it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0}}
midiManager = (MidiManager)getSystemService(Context.MIDI_SERVICE);
addMidiDevices(midiManager.getDevices());
midiDeviceCallback = new MidiDeviceCallback();
midiManager.registerDeviceCallback(midiDeviceCallback, null);
{{?}}
return nativeAudioStart();
}
@JavascriptInterface
public void audioStop() {
nativeAudioStop();
{{?it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0}}
midiManager.unregisterDeviceCallback(midiDeviceCallback);
removeAllMidiDevices();
{{?}}
}
@JavascriptInterface

View File

@ -13,6 +13,8 @@
#define NUM_MIDI_INPUTS {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}}
#define MIDI_BUS_IN {{=it.product.buses.findIndex(x => x.type == "midi" && x.direction == "input")}}
#if (AUDIO_BUS_IN >= 0) || (AUDIO_BUS_OUT >= 0)
static struct {
size_t index;

View File

@ -2,129 +2,6 @@
* Copyright (C) 2023, 2024 Orastron Srl unipersonale
*/
/*
#ifdef P_NOTE_ON
struct PortData {
AMidiDevice *device;
int portNumber;
AMidiOutputPort *port;
};
std::vector<PortData> midiPorts;
#define MIDI_BUFFER_SIZE 1024
uint8_t midiBuffer[MIDI_BUFFER_SIZE];
#endif
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) {
(void)pDevice;
#if NUM_CHANNELS_IN == 0
(void)pInput;
#else
const float *x = reinterpret_cast<const float *>(pInput);
#endif
float *y = reinterpret_cast<float *>(pOutput);
if (mutex.try_lock()) {
for (int i = 0; i < NUM_PARAMETERS; i++)
if (config_parameters[i].out)
paramValues[i] = P_GET_PARAMETER(&instance, i);
else
P_SET_PARAMETER(&instance, i, paramValues[i]);
#ifdef P_NOTE_ON
for (std::vector<PortData>::iterator it = midiPorts.begin(); it != midiPorts.end(); it++) {
int32_t opcode;
size_t numBytes;
while (AMidiOutputPort_receive(it->port, &opcode, midiBuffer, MIDI_BUFFER_SIZE, &numBytes, NULL) > 0) {
if (opcode != AMIDI_OPCODE_DATA)
continue;
switch (midiBuffer[0] & 0xf0) {
case 0x90:
P_NOTE_ON(&instance, midiBuffer[1], midiBuffer[2]);
break;
case 0x80:
P_NOTE_OFF(&instance, midiBuffer[1]);
break;
#ifdef P_PITCH_BEND
case 0xe0:
P_PITCH_BEND(&instance, midiBuffer[2] << 7 | midiBuffer[1]);
break;
#endif
#ifdef P_MOD_WHEEL
case 0xb0:
if (midiBuffer[1] == 1)
P_MOD_WHEEL(&instance, midiBuffer[2]);
break;
#endif
}
}
}
#endif
mutex.unlock();
}
ma_uint32 i = 0;
while (i < frameCount) {
ma_uint32 n = std::min(frameCount - i, static_cast<ma_uint32>(BLOCK_SIZE));
int l;
#if NUM_CHANNELS_IN != 0
l = NUM_CHANNELS_IN * i;
for (ma_uint32 j = 0; j < n; j++)
for (int k = 0; k < NUM_CHANNELS_IN; k++, l++)
bufs[k][j] = x[l];
#endif
#if NUM_CHANNELS_IN != 0
P_PROCESS(&instance, inBufs, outBufs, n);
#else
P_PROCESS(&instance, NULL, outBufs, n);
#endif
l = NUM_CHANNELS_OUT * i;
for (ma_uint32 j = 0; j < n; j++)
for (int k = 0; k < NUM_CHANNELS_OUT; k++, l++)
y[l] = bufs[k][j];
i += n;
}
}
#ifdef P_NOTE_ON
extern "C"
JNIEXPORT void JNICALL
Java_com_orastron_@JNI_NAME@_MainActivity_addMidiPort(JNIEnv* env, jobject thiz, jobject d, jint p) {
(void)thiz;
PortData data;
AMidiDevice_fromJava(env, d, &data.device);
data.portNumber = p;
mutex.lock();
if (AMidiOutputPort_open(data.device, p, &data.port) == AMEDIA_OK)
midiPorts.push_back(data);
mutex.unlock();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_orastron_@JNI_NAME@_MainActivity_removeMidiPort(JNIEnv* env, jobject thiz, jobject d, jint p) {
(void)thiz;
AMidiDevice *device;
AMidiDevice_fromJava(env, d, &device);
mutex.lock();
for (std::vector<PortData>::iterator it = midiPorts.begin(); it != midiPorts.end(); ) {
PortData data = *it;
if (data.device != device || data.portNumber != p) {
it++;
continue;
}
AMidiOutputPort_close(data.port);
it = midiPorts.erase(it);
}
mutex.unlock();
}
#endif
*/
#include <stdlib.h>
#include <stdint.h>
@ -147,6 +24,11 @@ Java_com_orastron_@JNI_NAME@_MainActivity_removeMidiPort(JNIEnv* env, jobject th
# define BLOCK_SIZE 32
#endif
#if NUM_MIDI_INPUTS > 0
# include <vector>
# include <amidi/AMidi.h>
#endif
#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0
static ma_device device;
@ -177,6 +59,16 @@ std::mutex mutex;
float param_values[PARAMETERS_N];
float param_values_prev[PARAMETERS_N];
#endif
#if NUM_MIDI_INPUTS > 0
struct PortData {
AMidiDevice *device;
int portNumber;
AMidiOutputPort *port;
};
std::vector<PortData> midiPorts;
# define MIDI_BUFFER_SIZE 1024
uint8_t midiBuffer[MIDI_BUFFER_SIZE];
#endif
static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) {
(void)pDevice;
@ -191,7 +83,18 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput,
plugin_set_parameter(&instance, i, param_values[i]);
param_values_prev[i] = param_values[i];
}
// TODO: midi
}
# endif
# if NUM_MIDI_INPUTS > 0
for (std::vector<PortData>::iterator it = midiPorts.begin(); it != midiPorts.end(); it++) {
int32_t opcode;
size_t numBytes;
while (AMidiOutputPort_receive(it->port, &opcode, midiBuffer, MIDI_BUFFER_SIZE, &numBytes, NULL) > 0) {
if (opcode != AMIDI_OPCODE_DATA || (midiBuffer[0] & 0xf0) == 0xf0)
continue;
plugin_midi_msg_in(&instance, MIDI_BUS_IN, midiBuffer);
}
}
# endif
mutex.unlock();
@ -404,20 +307,38 @@ JNI_FUNC(nativeSetParameter)(JNIEnv* env, jobject thiz, jint i, jfloat v) {
#endif
}
#if NUM_MIDI_INPUTS > 0
extern "C"
JNIEXPORT void JNICALL
JNI_FUNC(addMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) {
(void)env;
(void)thiz;
//TBD
PortData data;
AMidiDevice_fromJava(env, d, &data.device);
data.portNumber = p;
mutex.lock();
if (AMidiOutputPort_open(data.device, p, &data.port) == AMEDIA_OK)
midiPorts.push_back(data);
mutex.unlock();
}
extern "C"
JNIEXPORT void JNICALL
JNI_FUNC(removeMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) {
(void)env;
(void)thiz;
//TBD
AMidiDevice *device;
AMidiDevice_fromJava(env, d, &device);
mutex.lock();
for (std::vector<PortData>::iterator it = midiPorts.begin(); it != midiPorts.end(); ) {
PortData data = *it;
if (data.device != device || data.portNumber != p) {
it++;
continue;
}
AMidiOutputPort_close(data.port);
it = midiPorts.erase(it);
}
mutex.unlock();
}
#endif

View File

@ -11,8 +11,6 @@
#define NUM_ALL_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input").reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}}
#define NUM_ALL_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output").reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}}
#define MIDI_BUS_IN {{=it.product.buses.findIndex(x => x.type == "midi" && x.direction == "input")}}
#define NUM_MIDI_INPUTS {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}}
#define MIDI_BUS_IN {{=it.product.buses.findIndex(x => x.type == "midi" && x.direction == "input")}}