diff --git a/templates/android-make/.Makefile.swp b/templates/android-make/.Makefile.swp deleted file mode 100644 index cc5b7ce..0000000 Binary files a/templates/android-make/.Makefile.swp and /dev/null differ diff --git a/templates/android-make/Makefile b/templates/android-make/Makefile index ba193d1..4c7d736 100644 --- a/templates/android-make/Makefile +++ b/templates/android-make/Makefile @@ -1,6 +1,13 @@ include vars.mk +ifeq (${HAS_MIDI_IN}, yes) +MIN_API := 29 +else +MIN_API := 26 +endif + JAVAC = javac +CXX = ${ANDROID_NDK_DIR}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi${MIN_API}-clang++ APKSIGNER = ${BUILD_TOOLS_DIR}/apksigner ZIPALIGN = ${BUILD_TOOLS_DIR}/zipalign @@ -19,13 +26,25 @@ JARS := \ CLASSES_PATH := $(subst .,/,$(JAVA_PACKAGE_NAME)) -CLASSES := MainActivity +CLASSES := \ + MainActivity \ + MainActivity$$WebAppInterface -ifeq (${HAS_MIDI_IN}, yes) -MIN_API := 29 -else -MIN_API := 26 -endif +CXXFLAGS := \ + -fPIC \ + -DNDEBUG \ + -DBW_NO_DEBUG \ + -O3 \ + -Wall \ + -Wextra \ + -Wpedantic \ + -std=c++11 +LDFLAGS := \ + -shared \ + -static-libstdc++ \ + -ljnigraphics \ + -llog \ + -landroid all: build/${BUNDLE_NAME}.apk @@ -35,7 +54,7 @@ build/${BUNDLE_NAME}.apk: build/gen/${BUNDLE_NAME}.aligned.apk ${KEY_STORE} build/gen/${BUNDLE_NAME}.aligned.apk: build/gen/${BUNDLE_NAME}.unsigned.apk ${ZIPALIGN} -f -p 4 $^ $@ -build/gen/${BUNDLE_NAME}.unsigned.apk: build/apk/classes.dex data/AndroidManifest.xml build/assets/index.html | build/gen +build/gen/${BUNDLE_NAME}.unsigned.apk: build/apk/classes.dex build/apk/lib/armeabi-v7a/lib${BUNDLE_NAME}.so data/AndroidManifest.xml build/assets/index.html | build/gen ${AAPT} package -f -M data/AndroidManifest.xml -A build/assets $(foreach jar,$(JARS),-I $(jar)) -F $@ build/apk build/apk/classes.dex: build/apk/my_classes.jar @@ -48,10 +67,13 @@ build/apk/my_classes.jar: $(foreach class,$(CLASSES),build/obj/$(CLASSES_PATH)/$ build/obj/${CLASSES_PATH}/MainActivity.class: src/MainActivity.java | build/obj ${JAVAC} -classpath "$(subst $() $(),:,$(JARS))" -d build/obj $^ +build/apk/lib/armeabi-v7a/lib${BUNDLE_NAME}.so: src/jni.cpp | build/apk/lib/armeabi-v7a + ${CXX} $^ ${CXXFLAGS} ${LDFLAGS} -o $@ + build/assets/index.html: src/index.html | build/assets cp $^ $@ -build/gen build/apk build/obj build/assets: +build/gen build/apk build/obj build/apk/lib/armeabi-v7a build/assets: mkdir -p $@ clean: diff --git a/templates/android-make/vars.mk b/templates/android-make/vars.mk index 4ca5994..42ec826 100644 --- a/templates/android-make/vars.mk +++ b/templates/android-make/vars.mk @@ -7,6 +7,7 @@ STORE_PASS := {{=it.android_make.storePass}} KEY_PASS := {{=it.android_make.keyPass}} ANDROID_SDK_DIR := {{=it.android_make.sdkDir}} +ANDROID_NDK_DIR := ${ANDROID_SDK_DIR}/ndk/{{=it.android_make.ndkVersion}} BUILD_TOOLS_DIR := ${ANDROID_SDK_DIR}/build-tools/{{=it.android_make.buildToolsVersion}} ANDROIDX_DIR := {{=it.android_make.androidxDir}} KOTLIN_DIR := {{=it.android_make.kotlinDir}} diff --git a/templates/android/src/MainActivity.java b/templates/android/src/MainActivity.java index 819464a..f17aac8 100644 --- a/templates/android/src/MainActivity.java +++ b/templates/android/src/MainActivity.java @@ -16,20 +16,17 @@ import android.content.pm.PackageManager; import androidx.core.app.ActivityCompat; public class MainActivity extends Activity { -/* static { - System.loadLibrary("{{=it.android.javaPackageName}}"); + System.loadLibrary("{{=it.product.bundleName}}"); } public native boolean nativeAudioStart(); public native void nativeAudioStop(); public native float nativeGetParameter(int i); public native void nativeSetParameter(int i, float v); -*/ private WebView webView; -/* public class WebAppInterface { @JavascriptInterface public boolean hasAudioPermission() { @@ -60,8 +57,11 @@ public class MainActivity extends Activity { public void setParameter(int i, float v) { nativeSetParameter(i, v); } + + @JavascriptInterface + public void dummyFunc() { + } } -*/ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -73,15 +73,13 @@ public class MainActivity extends Activity { webView.setWebChromeClient(new WebChromeClient()); webView.setWebViewClient(new WebViewClient()); webSettings.setDomStorageEnabled(true); - //webView.addJavascriptInterface(new WebAppInterface(), "Android"); + webView.addJavascriptInterface(new WebAppInterface(), "Android"); webView.loadUrl("file:///android_asset/index.html"); } -/* @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (grantResults.length > 0) webView.loadUrl("javascript:gotAudioPermission()"); } -*/ } diff --git a/templates/android/src/data.h b/templates/android/src/data.h new file mode 100644 index 0000000..af5aba5 --- /dev/null +++ b/templates/android/src/data.h @@ -0,0 +1 @@ +#define JNI_FUNC(x) Java_{{=it.android.javaPackageName.replaceAll("_", "_1").replaceAll(".", "_")}}_MainActivity_##x diff --git a/templates/android/src/jni.cpp b/templates/android/src/jni.cpp new file mode 100644 index 0000000..690ad33 --- /dev/null +++ b/templates/android/src/jni.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2023, 2024 Orastron Srl unipersonale + */ + +/* +#include +#include +#include +*/ +#include +#include "data.h" +/* +#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; + 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; + } +} + +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_orastron_@JNI_NAME@_MainActivity_nativeAudioStart(JNIEnv* env, jobject thiz) { + (void)env; + (void)thiz; + +#if NUM_CHANNELS_IN == 0 + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); +#else + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); +#endif + deviceConfig.periodSizeInFrames = BLOCK_SIZE; + deviceConfig.periods = 1; + deviceConfig.performanceProfile = ma_performance_profile_low_latency; + deviceConfig.noPreSilencedOutputBuffer = 1; + deviceConfig.noClip = 0; + deviceConfig.noDisableDenormals = 0; + deviceConfig.noFixedSizedCallback = 1; + deviceConfig.dataCallback = data_callback; + deviceConfig.capture.pDeviceID = NULL; + deviceConfig.capture.format = ma_format_f32; + deviceConfig.capture.channels = NUM_CHANNELS_IN; + deviceConfig.capture.shareMode = ma_share_mode_shared; + deviceConfig.playback.pDeviceID = NULL; + deviceConfig.playback.format = ma_format_f32; + deviceConfig.playback.channels = NUM_CHANNELS_OUT; + deviceConfig.playback.shareMode = ma_share_mode_shared; + + if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) + return false; + + P_INIT(&instance); + P_SET_SAMPLE_RATE(&instance, (float)device.sampleRate); +#ifdef P_MEM_REQ + size_t req = P_MEM_REQ(&instance); + if (req) { + mem = malloc(req); + if (mem == NULL) { + ma_device_uninit(&device); + return false; + } + P_MEM_SET(&instance, mem); + } else + mem = NULL; +#endif + + for (int i = 0; i < NUM_PARAMETERS; i++) { + paramValues[i] = config_parameters[i].defaultValueUnmapped; + if (!config_parameters[i].out) + P_SET_PARAMETER(&instance, i, paramValues[i]); + } + + P_RESET(&instance); + +#if NUM_CHANNELS_IN != 0 + for (int i = 0; i < NUM_CHANNELS_IN; i++) + inBufs[i] = bufs[i]; +#endif + for (int i = 0; i < NUM_CHANNELS_OUT; i++) + outBufs[i] = bufs[i]; + + if (ma_device_start(&device) != MA_SUCCESS) { +#ifdef P_MEM_REQ + free(mem); +#endif + ma_device_uninit(&device); + return false; + } + + return true; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_orastron_@JNI_NAME@_MainActivity_nativeAudioStop(JNIEnv* env, jobject thiz) { + (void)env; + (void)thiz; + +#ifdef P_MEM_REQ + free(mem); +#endif +#ifdef P_FINI + P_FINI(&instance); +#endif + ma_device_stop(&device); + ma_device_uninit(&device); +} + +extern "C" +JNIEXPORT jfloat JNICALL +Java_com_orastron_@JNI_NAME@_MainActivity_nativeGetParameter(JNIEnv* env, jobject thiz, jint i) { + (void)env; + (void)thiz; + + mutex.lock(); + float v = paramValues[i]; + mutex.unlock(); + return v; +} + + +extern "C" +JNIEXPORT void JNICALL +Java_com_orastron_@JNI_NAME@_MainActivity_nativeSetParameter(JNIEnv* env, jobject thiz, jint i, jfloat v) { + (void)env; + (void)thiz; + + mutex.lock(); + paramValues[i] = v; + mutex.unlock(); +} + +#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 +*/ + +extern "C" +JNIEXPORT jboolean JNICALL +JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { + return true; +} + +extern "C" +JNIEXPORT void JNICALL +JNI_FUNC(nativeAudioStop)(JNIEnv* env, jobject thiz) { +} + +extern "C" +JNIEXPORT jfloat JNICALL +JNI_FUNC(nativeGetParameter)(JNIEnv* env, jobject thiz, jint i) { + return 0.f; +} + +extern "C" +JNIEXPORT void JNICALL +JNI_FUNC(nativeSetParameter)(JNIEnv* env, jobject thiz, jint i, jfloat v) { +} + +extern "C" +JNIEXPORT void JNICALL +JNI_FUNC(addMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) { +} + +extern "C" +JNIEXPORT void JNICALL +JNI_FUNC(removeMidiPort)(JNIEnv* env, jobject thiz, jobject d, jint p) { +} diff --git a/templates/android/tibia-index.js b/templates/android/tibia-index.js index e4922c7..90f5ade 100644 --- a/templates/android/tibia-index.js +++ b/templates/android/tibia-index.js @@ -3,6 +3,8 @@ var sep = path.sep; module.exports = function (data, api) { api.generateFileFromTemplateFile(`data${sep}AndroidManifest.xml`, `data${sep}AndroidManifest.xml`, data); + api.generateFileFromTemplateFile(`src${sep}data.h`, `src${sep}data.h`, data); + api.copyFile(`src${sep}jni.cpp`, `src${sep}jni.cpp`); api.generateFileFromTemplateFile(`src${sep}MainActivity.java`, `src${sep}MainActivity.java`, data); api.generateFileFromTemplateFile(`src${sep}index.html`, `src${sep}index.html`, data); }; diff --git a/templates/lv2-make/Makefile b/templates/lv2-make/Makefile index 0927071..d9d1013 100644 --- a/templates/lv2-make/Makefile +++ b/templates/lv2-make/Makefile @@ -1,7 +1,9 @@ include vars.mk CC = gcc + CFLAGS = -fPIC -Wall -Wpedantic -Wextra -Wno-unused-parameter +LDFLAGS = -shared BUNDLE_DIR = ${BUNDLE_NAME}.lv2 @@ -13,7 +15,7 @@ build/${BUNDLE_DIR}/manifest.ttl: data/manifest.ttl | build/${BUNDLE_DIR} cp $^ $@ build/${BUNDLE_DIR}/${SO_FILE}: src/lv2.c | build/${BUNDLE_DIR} - ${CC} $^ -o $@ ${CFLAGS} ${CFLAGS_EXTRA} ${LIBS_EXTRA} -shared + ${CC} $^ -o $@ ${CFLAGS} ${CFLAGS_EXTRA} ${LDFLAGS} ${LDFLAGS_EXTRA} build/${BUNDLE_DIR}: mkdir -p $@ diff --git a/templates/lv2-make/vars.mk b/templates/lv2-make/vars.mk index 8d966e7..657892f 100644 --- a/templates/lv2-make/vars.mk +++ b/templates/lv2-make/vars.mk @@ -1,3 +1,3 @@ BUNDLE_NAME := {{=it.product.bundleName}} CFLAGS_EXTRA := {{=it.make && it.make.cflags ? it.make.cflags : ""}} {{=it.lv2_make && it.lv2_make.cflags ? it.lv2_make.cflags : ""}} -LIBS_EXTRA := {{=it.make && it.make.libs ? it.make.libs : ""}} {{=it.lv2_make && it.lv2_make.libs ? it.lv2_make.libs : ""}} +LDFLAGS_EXTRA := {{=it.make && it.make.ldflags ? it.make.ldflags : ""}} {{=it.lv2_make && it.lv2_make.ldflags ? it.lv2_make.ldflags : ""}} diff --git a/templates/vst3-make/Makefile b/templates/vst3-make/Makefile index 8bba71d..a90eaa7 100644 --- a/templates/vst3-make/Makefile +++ b/templates/vst3-make/Makefile @@ -1,8 +1,9 @@ include vars.mk CC = gcc + CFLAGS = -fPIC -Wall -Wpedantic -Wextra -Wno-unused-parameter -LIBS = -lm +LDFLAGS = -shared -lm BUNDLE_DIR = ${BUNDLE_NAME}.vst3 @@ -15,7 +16,7 @@ build/${BUNDLE_DIR}/Contents/Info.plist: data/Info.plist | build/${BUNDLE_DIR}/C cp $^ $@ build/${BUNDLE_DIR}/Contents/${SO_FILE}: src/vst3.c | build/${BUNDLE_DIR}/Contents/${SO_DIR} - ${CC} $^ -o $@ ${CFLAGS} ${CFLAGS_EXTRA} ${LIBS} ${LIBS_EXTRA} -shared + ${CC} $^ -o $@ ${CFLAGS} ${CFLAGS_EXTRA} ${LDFLAGS} ${LDFLAGS_EXTRA} build/${BUNDLE_DIR}/Contents build/${BUNDLE_DIR}/Contents/${SO_DIR}: mkdir -p $@ diff --git a/templates/vst3-make/vars.mk b/templates/vst3-make/vars.mk index 103ae50..e793acf 100644 --- a/templates/vst3-make/vars.mk +++ b/templates/vst3-make/vars.mk @@ -1,3 +1,3 @@ BUNDLE_NAME := {{=it.product.bundleName}} CFLAGS_EXTRA := {{=it.make && it.make.cflags ? it.make.cflags : ""}} {{=it.vst3_make && it.vst3_make.cflags ? it.vst3_make.cflags : ""}} -LIBS_EXTRA := {{=it.make && it.make.libs ? it.make.libs : ""}} {{=it.vst3_make && it.vst3_make.libs ? it.vst3_make.libs : ""}} +LDFLAGS_EXTRA := {{=it.make && it.make.ldflags ? it.make.ldflags : ""}} {{=it.vst3_make && it.vst3_make.ldflags ? it.vst3_make.ldflags : ""}} diff --git a/templates/web-make/Makefile b/templates/web-make/Makefile index e64156c..7aab781 100644 --- a/templates/web-make/Makefile +++ b/templates/web-make/Makefile @@ -31,7 +31,7 @@ default: all all: ${ALL} build/${BUNDLE_NAME}.wasm: src/data.h src/memset.h src/plugin.h src/walloc.h src/processor.c | build - ${CC} src/processor.c -o $@ ${CFLAGS} ${LDFLAGS} ${CFLAGS_EXTRA} ${LIBS_EXTRA} + ${CC} src/processor.c -o $@ ${CFLAGS} ${CFLAGS_EXTRA} ${LDFLAGS} ${LDFLAGS_EXTRA} build/${BUNDLE_NAME}_processor.js: src/processor.js | build cp $^ $@ diff --git a/templates/web-make/vars.mk b/templates/web-make/vars.mk index 661f831..ae7faf6 100644 --- a/templates/web-make/vars.mk +++ b/templates/web-make/vars.mk @@ -1,4 +1,4 @@ BUNDLE_NAME := {{=it.product.bundleName}} CFLAGS_EXTRA := {{=it.make && it.make.cflags ? it.make.cflags : ""}} {{=it.web_make && it.web_make.cflags ? it.web_make.cflags : ""}} -LIBS_EXTRA := {{=it.make && it.make.libs ? it.make.libs : ""}} {{=it.web_make && it.web_make.libs ? it.web_make.libs : ""}} +LDFLAGS_EXTRA := {{=it.make && it.make.ldflags ? it.make.ldflags : ""}} {{=it.web_make && it.web_make.ldflags ? it.web_make.ldflags : ""}} HAS_MIDI_IN := {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0 ? "yes" : "no"}} diff --git a/test/android-make.json b/test/android-make.json index 8cd47ba..d061bcc 100644 --- a/test/android-make.json +++ b/test/android-make.json @@ -5,6 +5,7 @@ "storePass": "android", "keyPass": "android", "sdkDir": "${HOME}/Android/Sdk", + "ndkVersion": "25.2.9519653", "buildToolsVersion": "34.0.0", "androidxDir": "${HOME}/Android/androidx", "kotlinDir": "${HOME}/Android/kotlin",