From 7c4913543edab6a7096d212e3a1a5b141b174d00 Mon Sep 17 00:00:00 2001 From: Stefano D'Angelo Date: Sun, 28 Jan 2024 11:54:30 +0100 Subject: [PATCH] android midi seems to work ok --- templates/android-make/.Makefile.swp | Bin 0 -> 12288 bytes templates/android-make/Makefile | 8 ++ templates/android/src/MainActivity.java | 92 ++++++++++++ templates/android/src/data.h | 2 + templates/android/src/jni.cpp | 177 +++++++----------------- templates/cmd/src/data.h | 2 - 6 files changed, 151 insertions(+), 130 deletions(-) create mode 100644 templates/android-make/.Makefile.swp diff --git a/templates/android-make/.Makefile.swp b/templates/android-make/.Makefile.swp new file mode 100644 index 0000000000000000000000000000000000000000..0c3efed7e262d7107c5fe4403a2986e58226b298 GIT binary patch literal 12288 zcmeI2OKclO7{{j++J?7yNj)H@!J@H}^(Jj8A*j%e?Y7(c5!rdQK)2)FvAywnz3i@| zgkU&w;&I@>OXbuXaH%L)H~<%r5aNKiAc5e>?7GGCOmGH*6Obw&wTUEH~*P$ zW~**va#1bQ$8%EzpSuY;GuyVm|j|W*ctLG^8%;aJh`Xnx@5vxJj#01yTk6s{(z$DNWx& zjvkqs6kQ)4Jw)%lXW_qkN$pbwQUy{4QUy{4QUy{4QUy{4QUz{c1$<`@c?t2~6wTpc z^u1@>cl0y;N)<>INEJvGNEJvGNEJvGNEJvGNEJvGNEJvGxB(Sl1|f@k3Hjw_1dsp! zkN^Gu`(1?m2EGNKgHOR#@Bw%qTn3Xs2K&GdcM|dmxB}L}8nD3}^@G{r{D?kSqKouz9Q7{G`1P8#Mw-NFy_zC<7 zz5rLj2jG41DtHAb;H_H;c@vbt6qp1rj}Y#Ni*-mpsRF42|4V_~+?i_S{0wE5 zMRkiao0>LWOd47X8$w1h8%kkz5SWQhnTurPX)4)t^itH~wK?jYF11Y0rw-FQti?V0 z0&U?vCVDZdn8|FMRTdR(PRXBBP8YR`QZ5cIjnZf7BahI?Q{NWB1fvWweuDBf)2EXp zGMb@{zG)e9W@C1tQYb|(bIj==YPUNwoj9BrGp-HB%*vUuz&aa_cQ{KWscYI2b4;jQRx6q^r(&*BukSm3Oz`6Eb3>$kGUCKU zw%q)ytoZv`MB!{t@XbX*c!4w*)H$W3o~~diAd5qfU3WkXnFOhN_5Be1v)u_@R9L7RgG!6JGdj_05c@}nL$ zqF1t2c#YeT6wLvq?sxqmhoEppwP9 zreP9n#pmc)X5)-f*UD-^)zr#hg06Ef3ngI`?paZ^dM)~lDBxUdxy)&sx))U7V)9L0 z!a?f!hCV(XR<@bT4HV-sXHG=M#tGO=O~KQW!wqJmZ_u*D*L;_SMRC)Kv8qRdufpt- zR;g-*;_Sldu%fb6ZqBKBRL1uM4F-y5936RL#>rDNlUd>o#c@>I=WdhfJd-IyjRIda zb)LuQ4P4g$)MILQ@pc`Lj3k>y_~FSPMW4I}L{)sBXgHzZgVt)oUaoCkOY!R*A##QghmbKcI9hZyq)W?}@_t&Jg>Eqh* zV{*f^#lse!vyf<_KFUn-^tE*{i)RqKH9k&)XA+Djo(}Xl|D)?$=y>Ef;g-iqvg+|& T_p@p1R^Q-smAPK7+aZ4ejFH$g literal 0 HcmV?d00001 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")}}