From 906ecf403305fb728a4d7b225bbed8ff672c252b Mon Sep 17 00:00:00 2001 From: Stefano D'Angelo Date: Thu, 25 Jan 2024 15:01:42 +0100 Subject: [PATCH] more android --- templates/android/src/data.h | 39 +++++++++++-- templates/android/src/index.html | 83 ++++++++++++++++++++++++++- templates/android/src/jni.cpp | 98 +++++++++++++++++++++----------- 3 files changed, 179 insertions(+), 41 deletions(-) diff --git a/templates/android/src/data.h b/templates/android/src/data.h index 1705a7f..a74f0ec 100644 --- a/templates/android/src/data.h +++ b/templates/android/src/data.h @@ -1,12 +1,39 @@ -#define AUDIO_BUS_IN {{=it.product.buses.findIndex(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)}} -#define AUDIO_BUS_OUT {{=it.product.buses.findIndex(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)}} +#define NUM_AUDIO_BUSES_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input").length}} +#define NUM_AUDIO_BUSES_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output").length}} -#define NUM_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}} -#define NUM_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}} +#define AUDIO_BUS_IN {{=it.product.buses.findIndex(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)}} +#define AUDIO_BUS_OUT {{=it.product.buses.findIndex(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)}} -#define NUM_MIDI_INPUTS {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}} +#define NUM_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}} +#define NUM_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}} +#define NUM_NON_OPT_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.optional).reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}} +#define NUM_NON_OPT_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.optional).reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}} +#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 PARAMETERS_N {{=it.product.parameters.length}} +#define NUM_MIDI_INPUTS {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}} + +#if (AUDIO_BUS_IN >= 0) || (AUDIO_BUS_OUT >= 0) +static struct { + size_t index; + char out; + char optional; + char channels; +} audio_bus_data[NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT] = { +{{~it.product.buses :b:i}} +{{?b.type == "audio"}} + { + /* .index = */ {{=i}}, + /* .out = */ {{=b.direction == "output" ? 1 : 0}}, + /* .optional = */ {{=b.optional ? 1 : 0}}, + /* .channels = */ {{=b.channels == "mono" ? 1 : 2}} + }, +{{?}} +{{~}} +}; +#endif + +#define PARAMETERS_N {{=it.product.parameters.length}} #if PARAMETERS_N > 0 diff --git a/templates/android/src/index.html b/templates/android/src/index.html index dc0f1dd..db8e9b5 100644 --- a/templates/android/src/index.html +++ b/templates/android/src/index.html @@ -9,8 +9,18 @@ var data = { parameters: {{=JSON.stringify(it.product.parameters, null, 2)}} }; +function map(index, value) { + var p = data.parameters[index]; + return p.map == "logarithmic" ? p.minimum * Math.exp((2.0 * Math.log(Math.sqrt(p.maximum * p.minimum) / Math.abs(p.minimum))) * value) : p.minimum + (p.maximum - p.minimum) * value; +} + +function unmap(index, value) { + var p = data.parameters[index]; + return p.map == "logarithmic" ? Math.log(value / p.minimum) / (2.0 * Math.log(Math.sqrt(p.maximum * p.minimum) / Math.abs(p.minimum))) : (value - p.minimum) / (p.maximum - p.minimum); +} + var hasAudioPermission = true; -for (var i = 0; i < buses.length; i++) +for (var i = 0; i < data.buses.length; i++) if (!data.buses[i].output) { hasAudioPermission = Android.hasAudioPermission(); break; @@ -24,6 +34,77 @@ window.onload = function () { var paramsElem = document.getElementById("params"); topButtonElem.value = hasAudioPermission ? "START" : "INIT"; + topButtonElem.addEventListener("click", function () { + if (!hasAudioPermission) { + Android.requestAudioPermission(); + return; + } + + if (audioStarted) { + clearInterval(outParamInterval); + Android.audioStop(); + + paramsElem.innerHTML = ""; + + topButtonElem.value = "START"; + audioStarted = false; + + return; + } + + if (!Android.audioStart()) { + alert("Could not start audio"); + return; + } + + for (var i = 0; i < data.parameters.length; i++) { + var div = document.createElement("div"); + + var label = document.createElement("label"); + label.setAttribute("for", "p" + i); + label.innerText = data.parameters[i].name; + + var range = document.createElement("input"); + range.classList.add("range"); + range.setAttribute("type", "range"); + range.setAttribute("id", "p" + i); + range.setAttribute("name", "p" + i); + if (data.parameters[i].isBypass || data.parameters[i].toggled) { + range.setAttribute("min", 0); + range.setAttribute("max", 1); + range.setAttribute("step", 1); + } else { + range.setAttribute("min", 0); + range.setAttribute("max", 1); + range.setAttribute("step", data.parameters[i].integer ? 1 / (data.parameters[i].maximum - data.parameters[i].minimum) : "any"); + } + range.value = unmap(i, data.parameters[i].defaultValue); + if (data.parameters[i].direction == "output") + range.setAttribute("readonly", "true"); + else { + let index = i; + range.addEventListener("input", + function (ev) { + Android.setParameter(index, map(index, parseFloat(ev.target.value))); + }); + } + + div.appendChild(label); + div.appendChild(document.createElement("br")); + div.appendChild(range); + paramsElem.appendChild(div); + } + + outParamInterval = setInterval( + function () { + for (var i = 0; i < data.parameters.length; i++) + if (data.parameters[i].direction == "output") + document.getElementById("p" + i).value = unmap(i, Android.getParameter(i)); + }, 50); + + topButtonElem.value = "STOP"; + audioStarted = true; + }); }; function gotAudioPermission() { diff --git a/templates/android/src/jni.cpp b/templates/android/src/jni.cpp index b55e812..96a8573 100644 --- a/templates/android/src/jni.cpp +++ b/templates/android/src/jni.cpp @@ -3,32 +3,6 @@ */ /* -#include -#include -#include -#include -#define MINIAUDIO_IMPLEMENTATION -#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS -#define MA_ENABLE_AAUDIO -#include -#include -#include "config.h" - -#define BLOCK_SIZE 32 -#define NUM_BUFS (NUM_CHANNELS_IN > NUM_CHANNELS_OUT ? NUM_CHANNELS_IN : NUM_CHANNELS_OUT) - -ma_device device; -P_TYPE instance; -float paramValues[NUM_PARAMETERS]; -float bufs[NUM_BUFS][BLOCK_SIZE]; -#if NUM_CHANNELS_IN != 0 -const float *inBufs[NUM_CHANNELS_IN]; -#endif -float *outBufs[NUM_CHANNELS_OUT]; -std::mutex mutex; -#ifdef P_MEM_REQ -void *mem; -#endif #ifdef P_NOTE_ON struct PortData { AMidiDevice *device; @@ -157,6 +131,7 @@ Java_com_orastron_@JNI_NAME@_MainActivity_removeMidiPort(JNIEnv* env, jobject th #include "data.h" #include "plugin.h" +#include #include #if PARAMETERS_N + NUM_MIDI_INPUTS > 0 # include @@ -178,14 +153,25 @@ static ma_device device; #endif static plugin instance; static void * mem; +#if (NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN) || (NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT) +float zero[BLOCK_SIZE]; +#endif #if NUM_CHANNELS_IN > 0 float x_buf[NUM_CHANNELS_IN * BLOCK_SIZE]; #endif +#if NUM_ALL_CHANNELS_IN > 0 +const float * x[NUM_ALL_CHANNELS_IN]; +#else +const float ** x; +#endif #if NUM_CHANNELS_OUT > 0 float y_buf[NUM_CHANNELS_OUT * BLOCK_SIZE]; #endif -const float ** x; +#if NUM_ALL_CHANNELS_OUT > 0 +float * y[NUM_ALL_CHANNELS_IN]; +#else float ** y; +#endif #if PARAMETERS_N > 0 std::mutex mutex; float param_values[PARAMETERS_N]; @@ -225,13 +211,17 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, x_buf[BLOCK_SIZE * k + j] = in_buf[ix]; #endif +#if (NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN) || (NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT) + memset(zero, 0, BLOCK_SIZE * sizeof(float)); +#endif + plugin_process(&instance, x, y, n); #if NUM_CHANNELS_OUT > 0 size_t iy = NUM_CHANNELS_OUT * i; for (ma_uint32 j = 0; j < n; j++) for (size_t k = 0; k < NUM_CHANNELS_OUT; k++, iy++) - y_buf[BLOCK_SIZE * k + j] = out_buf[ix]; + out_buf[iy] = y_buf[BLOCK_SIZE * k + j]; #endif i += n; @@ -300,15 +290,55 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { plugin_reset(&instance); -#if NUM_CHANNELS_IN > 0 - for (size_t i = 0; i < NUM_CHANNELS_IN; i++) - x[i] = x_buf + BLOCK_SIZE * i; +#if NUM_ALL_CHANNELS_IN > 0 +# if AUDIO_BUS_IN >= 0 + size_t ix = 0; + size_t ixb = 0; + for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { + if (audio_bus_data[j].out) + continue; + if (audio_bus_data[j].index == AUDIO_BUS_IN) + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++, ixb++) + x[ix] = x_buf + BLOCK_SIZE * ixb; +# if NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN + else if (!audio_bus_data[j].optional) + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) + x[ix] = zero; +# endif + else + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) + x[ix] = NULL; + } +# else + for (size_t i = 0; i < NUM_ALL_CHANNELS_IN; i++) + x[i] = NULL; +# endif #else x = NULL; #endif -#if NUM_CHANNELS_OUT > 0 - for (size_t i = 0; i < NUM_CHANNELS_OUT; i++) - y[i] = y_buf + BLOCK_SIZE * i; +#if NUM_ALL_CHANNELS_OUT > 0 +# if AUDIO_BUS_OUT >= 0 + size_t iy = 0; + size_t iyb = 0; + for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { + if (!audio_bus_data[j].out) + continue; + if (audio_bus_data[j].index == AUDIO_BUS_OUT) + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++, iyb++) + y[iy] = y_buf + BLOCK_SIZE * iyb; +# if NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT + else if (!audio_bus_data[j].optional) + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) + y[iy] = zero; +# endif + else + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) + y[iy] = NULL; + } +# else + for (size_t i = 0; i < NUM_ALL_CHANNELS_OUT; i++) + y[i] = NULL; +# endif #else y = NULL; #endif