diff --git a/templates/android-make/.Makefile.swp b/templates/android-make/.Makefile.swp new file mode 100644 index 0000000..0c3efed Binary files /dev/null and b/templates/android-make/.Makefile.swp differ diff --git a/templates/android-make/Makefile b/templates/android-make/Makefile index 4ef4f51..bd09a6d 100644 --- a/templates/android-make/Makefile +++ b/templates/android-make/Makefile @@ -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} diff --git a/templates/android/src/MainActivity.java b/templates/android/src/MainActivity.java index bed1431..afc1ce8 100644 --- a/templates/android/src/MainActivity.java +++ b/templates/android/src/MainActivity.java @@ -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 midiDevices = new ArrayList(); + + 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 diff --git a/templates/android/src/data.h b/templates/android/src/data.h index a74f0ec..14941c1 100644 --- a/templates/android/src/data.h +++ b/templates/android/src/data.h @@ -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; diff --git a/templates/android/src/jni.cpp b/templates/android/src/jni.cpp index b926dfd..7fb4c44 100644 --- a/templates/android/src/jni.cpp +++ b/templates/android/src/jni.cpp @@ -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 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(pInput); -#endif - float *y = reinterpret_cast(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::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(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::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 #include @@ -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 + +# include +#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 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::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::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 diff --git a/templates/cmd/src/data.h b/templates/cmd/src/data.h index a98ffb4..901beff 100644 --- a/templates/cmd/src/data.h +++ b/templates/cmd/src/data.h @@ -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")}}