Using the MIDI driver from C/C++ codes
kickmyassp opened this issue · 17 comments
Thanks for the java interface for SONIVOX midi synthesizer (That is what this is, I think) Before I found it, porting fluidsyth seemed the only way to go, but with a lot of works.
My MIDI notes come from C codes. I tried to play the notes using the driver through JNI but it failed due to a thread issue. An alternative would be to bring the MIDI notes over via JNI and play them in java, but it means no real-time playback.
Is it possible to use driver directly in native C/C++ instead? I noticed "Native methods" were mentioned in doc but without enough details.
In case if the problem still exists for you here what can you do:
Change all
to a specific ABI for example armeabi-v7a
in this file
I guess it's somehow related to the latest releases of Android Studio, if you need to have more than one ABI try another way of declaration multiply ABIs described here:
The reference to native interface means a native interface visible and callable from Java. If you just want to play notes you can use the two functions
jboolean Java_org_billthefarmer_mididriver_MidiDriver_init(JNIEnv *env, jobject obj)
jboolean Java_org_billthefarmer_mididriver_MidiDriver_shutdown(JNIEnv *env, jobject obj)
directly from C/C++ as the JNI args are ignored and the result is boolean, or you can copy the code, remove the JNI stuff and just call it midi_init() and midi_shutdown(); The other function you need is
jboolean Java_org_billthefarmer_mididriver_MidiDriver_write(JNIEnv *env, jobject obj, jbyteArray byteArray)
but that's not callable from C/C++ because of the JNI argument, so you will need to copy it and remove the JNI code and call it midi_write(). There is also
jbooleanJava_org_billthefarmer_mididriver_MidiDriver_setVolume(JNIEnv *env, jobject obj, jint volume)
You can call that direct by passing two nulls or zeros for the first two arguments, which are ignored, or copy the code as above.
Thanks to both you gentlemen,
I appreciate billthefarmer's suggestion on using C/C++ functions directly. It seems to me that I still need to make some minor changes to the source and I want to be able to build the package before I make any changes and that's where I am stuck now.
Apisov's suggestion on APP_ABI in Application.mk did not help. I still get
Android NDK: INTERNAL ERROR: The armeabi ABI should have exactly one architecture definitions. Found: ''
I tried a few other things that seem to avoid the above problem but create new problems.
I am somewhat puzzled as to why you think you need to build the library yourself. You are obviously using Android Studio, so you can add the library as a dependency as per
dependencies {
compile 'com.github.billthefarmer:mididriver:v1.14'
}
And not use the Java part. Google are constantly moving the goalposts with their SDKs, once I've got something working nicely I tend to ignore the updates because, as you have found, it breaks things. Rebuilding this library with a later version of the tools won't make it any better, you will probably end up with an identical result.
It's probably just my ignorance. I wasn't sure what functions in the library are actually accessible to my C/C++ codes, other than those few java methods mentioned in the doc. For instance, are all the functions defined in eas_public.c are accessible? If that's the case, I agree I don't need to build the library.
Instead I just copy the header file over and build my version of the interface.
Because the Sonivox library is written in C, all the functions are accessible. That doesn't mean it's a good idea to use them without setting up all the infrastructure first. I suggest you read the docs here: https://github.com/aosp-mirror/platform_external_sonivox/tree/master/docs. Particularly https://github.com/aosp-mirror/platform_external_sonivox/blob/master/docs/EASLibrary3_5.pdf. And the Android docs for OpenSLES https://developer.android.com/ndk/guides/audio/opensl/opensl-for-android.
I have added a C/C++ native interface in version 1.15...
#include "midi.h"
jboolean midi_init() // Return true on success, or false on failure.
jboolean midi_write(EAS_U8 *bytes, jint length)
// Writes midi data to the Sonivox
// synthesizer. The length of the array
// should be the exact length of the
// message or messages. Returns true
// on success, false on
// failure.
jboolean midi_setVolume(jint volume)
// Set master volume for EAS
// synthesizer (between 0 and 100).
// Returns true on success, false on
// failure.
jboolean midi_shutdown() // Shut down the synthesizer. Returns true on
// success, false on failure.
I hope you would soon post the aar file for this version as well.
I tried to make the native interface myself with 1.14 library but failed since the compiler says all those functions defined in eas_public.c (EAS_Config, EAS_Init, EAS_WriteMIDIStream,...) are undefined references. As of now, my app can use MIDIDriver java class but not C functions behind it.
So I am very happy to see you decided to do it with 1.15.
If you still want to build the project I forgot to mention that you also probably needed to update Android Gradle plugin to 3.1.4
Line 7 in 61cd32e
and Gradle version to 4.4
change it here to gradle-4.4-all.zip
I had the same error as you are.
After changing to these versions I was able to build the project and run it.
There is another important thing to check, the NDK version. I use the latest - 17.1.4828580
PS: I did change the compile SDK too but I doubt that it afects somehow this question.
On 1.15.aar:
I request you to create an app in which those new native C/C++ functions are actually used. My attempt failed. These functions remain undefined as clang++ command line does not link/load the AAR file. I guess that AAR is mainly for sharing java codes and clang doesn't know how to use it.
On building the project:
I followed Apisov's suggestions:
- APP_ABI := armeabi-v7a in Application.mk
- set Android Gradle plugin to 3.1.4 in mididiriver/build.gradle
- set distributionUrl=https://services.gradle.org/distributions/gradle-4.4-all.zip in gradle-wrapper.properties
- check NDK version (17.1.4828580)
I get this error message:
Build command failed.
Error while executing process /Users/macuser/Library/Android/sdk/ndk-bundle/ndk-build with arguments {NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=/Users/macuser/tmp/mididriver/library/src/main/jni/Android.mk NDK_APPLICATION_MK=/Users/macuser/tmp/mididriver/library/src/main/jni/Application.mk APP_ABI=armeabi-v7a NDK_ALL_ABIS=armeabi-v7a NDK_DEBUG=1 APP_PLATFORM=android-14 NDK_OUT=/Users/macuser/tmp/mididriver/library/build/intermediates/ndkBuild/debug/obj NDK_LIBS_OUT=/Users/macuser/tmp/mididriver/library/build/intermediates/ndkBuild/debug/lib /Users/macuser/tmp/mididriver/library/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/libmidi.so}
Android NDK: WARNING:/Users/macuser/tmp/mididriver/library/src/main/jni/Android.mk:sonivox: LOCAL_LDLIBS is always ignored for static libraries
[armeabi-v7a] Compile++ thumb: midi <= midi.cpp
[armeabi-v7a] Compile arm : sonivox <= eas_chorus.c
clang: error: unsupported argument '--defsym' to option 'Wa,'
clang: error: unsupported argument 'SAMPLE_RATE_22050=1' to option 'Wa,'
clang: error: unsupported argument '--defsym' to option 'Wa,'
clang: error: unsupported argument 'STEREO_OUTPUT=1' to option 'Wa,'
clang: error: unsupported argument '--defsym' to option 'Wa,'
clang: error: unsupported argument 'FILTER_ENABLED=1' to option 'Wa,'
clang: error: unsupported argument '--defsym' to option 'Wa,'
clang: error: unsupported argument 'SAMPLES_8_BIT=1' to option 'Wa,'
make: *** [/Users/macuser/tmp/mididriver/library/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/objs-debug/sonivox/lib_src/eas_chorus.o] Error 1
This seems to complain about Android.mk, in particular, these lines:
asm_flags := \
-I $(LOCAL_PATH)/lib_src \
--defsym SAMPLE_RATE_22050=1 \
--defsym STEREO_OUTPUT=1 \
--defsym FILTER_ENABLED=1 \
--defsym SAMPLES_8_BIT=1
LOCAL_CFLAGS += \
$(foreach f,$(asm_flags),-Wa,"$(f)")
This is the same place I got stuck once before and I am very lost at this point.
I don't know the answer, I expect it's in the android docs somewhere, or try stackoverflow. In an app I build using the driver, the native midi library is in build/intermediates/exploded-aar/com.github.billthefarmer/mididriver/v1.14/jni/<arch>/libmidi.so
. That could change with a different version of the gradle build tools. You will need the include files midi.h
and eas.h
.
You appear to attempting to rebuild the driver with different tools. It builds perfectly with the tools I am using, which are:
Gradle 2.14.1
Android gradle build tools 2.2.2
Android build tools 25.0.2
NDK android-ndk-r14b
I appreciate that these are now old, but I wrote it five years ago. If it ain't broke, don't fix it.
Ok, here is a test project that does nothing, but includes a C++ file that references all the exported C/C++ functions in the midi library. https://github.com/billthefarmer/test. I have used the .aar file because the Jitpack build failed for the same reason as yours.
dependencies {
compile 'org.billthefarmer.mididriver:MidiDriver-1.15@aar'
}
repositories{
flatDir{
dirs 'libs'
}
}
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := midi
LOCAL_SRC_FILES := $(LOCAL_PATH)/../../../build/intermediates/exploded-aar/org.billthefarmer.mididriver/MidiDriver-1.15/jni/$(TARGET_ARCH_ABI)/libmidi.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c++
LOCAL_SHARED_LIBRARIES := midi
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
#include "midi.h"
void test()
{
jboolean result;
EAS_U8 midi[3];
result = midi_init();
result = midi_write(midi, sizeof(midi));
result = midi_setVolume(99);
result = midi_shutdown();
}
Compilation started at Thu Aug 16 17:53:48
gradle assembleRelease
Incremental java compilation is an incubating feature.
:app:preBuild UP-TO-DATE
:app:preReleaseBuild UP-TO-DATE
:app:checkReleaseManifest
:app:preDebugBuild UP-TO-DATE
:app:prepareOrgBillthefarmerMididriverMidiDriver115Library UP-TO-DATE
:app:prepareReleaseDependencies
:app:compileReleaseAidl UP-TO-DATE
:app:compileReleaseRenderscript UP-TO-DATE
:app:generateReleaseBuildConfig
:app:generateReleaseResValues UP-TO-DATE
:app:generateReleaseResources UP-TO-DATE
:app:mergeReleaseResources UP-TO-DATE
:app:processReleaseManifest UP-TO-DATE
:app:processReleaseResources UP-TO-DATE
:app:generateReleaseSources
:app:incrementalReleaseJavaCompilationSafeguard
:app:compileReleaseJavaWithJavac
:app:compileReleaseJavaWithJavac - is not incremental (e.g. outputs have changed, no previous execution, etc.).
:app:generateJsonModelRelease UP-TO-DATE
:app:externalNativeBuildRelease
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\mips64\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\mips\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\x86_64\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\x86\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\arm64-v8a\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\armeabi-v7a\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\armeabi\libtest.so
:app:compileReleaseSources
:app:lintVitalRelease
:app:mergeReleaseShaders UP-TO-DATE
:app:compileReleaseShaders UP-TO-DATE
:app:generateReleaseAssets UP-TO-DATE
:app:mergeReleaseAssets UP-TO-DATE
:app:transformClassesWithDexForRelease
:app:mergeReleaseJniLibFolders UP-TO-DATE
:app:transformNative_libsWithMergeJniLibsForRelease UP-TO-DATE
:app:transformNative_libsWithStripDebugSymbolForRelease UP-TO-DATE
:app:processReleaseJavaRes UP-TO-DATE
:app:transformResourcesWithMergeJavaResForRelease UP-TO-DATE
:app:packageRelease
:app:assembleRelease
BUILD SUCCESSFUL
Total time: 5.547 secs
Compilation finished at Thu Aug 16 17:53:55
The reason for the NDK errors is that NDK r17b has withdrawn support for armeabi, mips and mips64. You need a ndk section in defaultConfig so it doesn't attempt to build them.
defaultConfig {
buildConfigField "long", "BUILT", System.currentTimeMillis() + "L"
ndk {
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
Compilation started at Fri Aug 17 09:03:01
gradle assembleRelease
Incremental java compilation is an incubating feature.
:app:preBuild UP-TO-DATE
:app:preReleaseBuild UP-TO-DATE
:app:checkReleaseManifest
:app:preDebugBuild UP-TO-DATE
:app:prepareOrgBillthefarmerMididriverMidiDriver115Library UP-TO-DATE
:app:prepareReleaseDependencies
:app:compileReleaseAidl UP-TO-DATE
:app:compileReleaseRenderscript UP-TO-DATE
:app:generateReleaseBuildConfig
:app:generateReleaseResValues UP-TO-DATE
:app:generateReleaseResources UP-TO-DATE
:app:mergeReleaseResources UP-TO-DATE
:app:processReleaseManifest UP-TO-DATE
:app:processReleaseResources UP-TO-DATE
:app:generateReleaseSources
:app:incrementalReleaseJavaCompilationSafeguard
:app:compileReleaseJavaWithJavac
:app:compileReleaseJavaWithJavac - is not incremental (e.g. outputs have changed, no previous execution, etc.).
:app:generateJsonModelRelease UP-TO-DATE
:app:externalNativeBuildRelease
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\arm64-v8a\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\x86\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\x86_64\libtest.so
building D:\android\Test\app\build\intermediates\ndkBuild\release\obj\local\armeabi-v7a\libtest.so
:app:compileReleaseSources
:app:lintVitalRelease
:app:mergeReleaseShaders UP-TO-DATE
:app:compileReleaseShaders UP-TO-DATE
:app:generateReleaseAssets UP-TO-DATE
:app:mergeReleaseAssets UP-TO-DATE
:app:transformClassesWithDexForRelease
:app:mergeReleaseJniLibFolders UP-TO-DATE
:app:transformNative_libsWithMergeJniLibsForRelease UP-TO-DATE
:app:transformNative_libsWithStripDebugSymbolForRelease UP-TO-DATE
:app:processReleaseJavaRes UP-TO-DATE
:app:transformResourcesWithMergeJavaResForRelease UP-TO-DATE
:app:packageRelease
:app:assembleRelease
BUILD SUCCESSFUL
Total time: 4.0 secs
Compilation finished at Fri Aug 17 09:03:06
The reason the mididriver won't build is the above, plus NDK r17b has withdrawn support for the GNU compiler, which must include the assembler. I checked the platform_external_sonivox buil;d file - https://github.com/aosp-mirror/platform_external_sonivox/blob/master/arm-wt-22k/Android.bp and they haven't ported the assembler files, so I'm embuggered unless I remove the arm assembler option. That's only the latest embuggerment, they've done it several times.
NDK r16b works fine.
billthefarmer,
I finally have your 'test' project working on my side. Different versions of tools seem to have created some nasty problems. For benefit for others who might try this, here is a summary of the problems I encountered in trying to make 'test' project work.
-
Gradle and build plugin versions had to be updated to avoid a nonsensical error message on ABI.
-
When I build, I don't get 'exploded-aar' directory. I have the impression that, in your case, the build process generated 'exploded-aar' automatically. I had felt desperate until I found out that AAR was just a zip file. I only had to unzip the aar to get 'libmidi.so' files and place them myself.
Thank you for helping me out on this.
(I am also a farmer and musician playing clarinet)
Thanks for the native interface. Makes it usable through xamarin much easier. Good stuff!
Note: while playing around I found that it also compiles nicely with clang 5 on ARM and x86 (the ones I tried).