├── external └── .gitignore ├── gh-pages ├── kotlin │ └── .keep └── index.html ├── samples ├── .gitignore ├── aaphostsample │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── values │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ └── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── AndroidManifest.xml │ │ └── androidTest │ │ │ └── java │ │ │ └── org │ │ │ └── androidaudioplugin │ │ │ └── aaphostsample │ │ │ └── AudioPluginInterfaceTest.kt │ ├── proguard-rules.pro │ ├── CMakeLists.txt │ └── build.gradle.kts └── aappluginsample │ ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── xml │ │ │ │ ├── ump_device_info.xml │ │ │ │ └── device_info.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── cpp │ │ │ ├── aapxs │ │ │ │ ├── include │ │ │ │ │ └── aap │ │ │ │ │ │ └── examples │ │ │ │ │ │ └── aapxs │ │ │ │ │ │ └── test-extension.h │ │ │ │ └── aap-xs-sample.cpp │ │ │ ├── CMakeLists.txt │ │ │ └── instrument │ │ │ │ └── ayumi.h │ │ └── AndroidManifest.xml │ └── androidTest │ │ └── java │ │ └── org │ │ └── androidaudioplugin │ │ └── aapinstrumentsample │ │ ├── MidiDeviceServiceTest.kt │ │ └── PluginTest.kt │ └── proguard-rules.pro ├── androidaudioplugin-testing ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── org │ │ └── androidaudioplugin │ │ └── androidaudioplugin │ │ └── testing │ │ ├── AudioPluginMidiDeviceServiceTesting.kt │ │ └── AudioPluginServiceTesting.kt ├── proguard-rules.pro └── build.gradle.kts ├── androidaudioplugin ├── .gitignore ├── src │ ├── main │ │ ├── cpp │ │ │ ├── android │ │ │ │ ├── ALooperMessage.cpp │ │ │ │ ├── gen │ │ │ │ │ └── include │ │ │ │ │ │ └── aidl │ │ │ │ │ │ └── org │ │ │ │ │ │ └── androidaudioplugin │ │ │ │ │ │ ├── BpAudioPluginInterfaceCallback.h │ │ │ │ │ │ ├── BnAudioPluginInterfaceCallback.h │ │ │ │ │ │ ├── BpAudioPluginInterface.h │ │ │ │ │ │ └── AudioPluginInterfaceCallback.h │ │ │ │ ├── android-application-context.cpp │ │ │ │ └── audio-plugin-host-android-internal.cpp │ │ │ ├── core │ │ │ │ ├── include_cmidi2.h │ │ │ │ ├── hosting │ │ │ │ │ ├── plugin-client-system.cpp │ │ │ │ │ ├── audio-plugin-host-internals.h │ │ │ │ │ ├── PluginHost.Service.cpp │ │ │ │ │ ├── plugin-service-list.h │ │ │ │ │ ├── plugin-connections.cpp │ │ │ │ │ └── PluginInformation.cpp │ │ │ │ ├── aapxs │ │ │ │ │ ├── standard-extensions.cpp │ │ │ │ │ └── gui-aapxs.cpp │ │ │ │ └── AAPJniFacade.h │ │ │ └── desktop │ │ │ │ └── audio-plugin-host-desktop-internal.cpp │ │ ├── java │ │ │ └── org │ │ │ │ └── androidaudioplugin │ │ │ │ ├── ExtensionInformation.kt │ │ │ │ ├── AudioPluginViewFactory.kt │ │ │ │ ├── AudioPluginException.kt │ │ │ │ ├── hosting │ │ │ │ ├── AAPMetadataException.kt │ │ │ │ ├── PluginServiceConnection.kt │ │ │ │ ├── UmpHelper.kt │ │ │ │ ├── NativePluginClient.kt │ │ │ │ ├── AudioPluginClientInitializer.kt │ │ │ │ ├── AudioPluginMidiSettings.kt │ │ │ │ └── AudioPluginClientBase.kt │ │ │ │ ├── NativePluginService.kt │ │ │ │ ├── PluginServiceInformation.kt │ │ │ │ ├── ParameterInformation.kt │ │ │ │ ├── PortInformation.kt │ │ │ │ ├── AudioPluginNatives.kt │ │ │ │ ├── AudioPluginServiceHelper.kt │ │ │ │ ├── NativeLocalPluginInstance.kt │ │ │ │ └── PluginInformation.kt │ │ ├── aidl │ │ │ └── org │ │ │ │ └── androidaudioplugin │ │ │ │ ├── AudioPluginInterfaceCallback.aidl │ │ │ │ └── AudioPluginInterface.aidl │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── update-binder.sh │ └── androidTest │ │ └── java │ │ └── org │ │ └── androidaudioplugin │ │ └── NativeTest.kt ├── consumer-rules.pro └── proguard-rules.pro ├── include └── aap │ ├── core │ ├── aapxs │ │ └── extension-service.h │ ├── host │ │ ├── audio-plugin-host.h │ │ ├── desktop │ │ │ └── audio-plugin-host-desktop.h │ │ ├── android │ │ │ └── audio-plugin-host-android.h │ │ ├── plugin-client-system.h │ │ └── plugin-connections.h │ ├── AAPXSMidi2RecipientSession.h │ ├── android │ │ └── android-application-context.h │ └── AAPXSMidi2InitiatorSession.h │ ├── unstable │ ├── utility.h │ └── logging.h │ └── ext │ ├── state.h │ ├── plugin-info.h │ ├── urid.h │ └── gui.h ├── .github ├── FUNDING.yml └── workflows │ ├── deploy.yml │ └── build.yml ├── docs ├── .gitignore ├── images │ ├── systrace.png │ ├── aaphostsample.png │ ├── aap-process-model.png │ └── aap-instruments-on-kmmk.png ├── schemas │ ├── midi.rnc │ ├── gui.rnc │ └── parameters.rnc └── design │ └── CLIENT_SERVICE.md ├── androidaudioplugin-midi-device-service ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── org │ │ │ └── androidaudioplugin │ │ │ └── midideviceservice │ │ │ ├── MidiDeviceServiceStartup.kt │ │ │ ├── StandaloneAudioPluginMidiDeviceService.kt │ │ │ ├── AudioPluginMidiReceiver.kt │ │ │ └── AudioPluginMidiDeviceInstance.kt │ │ └── cpp │ │ ├── AAPMidiProcessor_android.cpp │ │ └── CMakeLists.txt ├── proguard-rules.pro └── NOTES.md ├── androidaudioplugin-manager └── src │ └── main │ ├── cpp │ ├── AudioDevice.cpp │ ├── VirtualAudioDeviceManager.cpp │ ├── include_cmidi2.h │ ├── PluginPlayerConfiguration.cpp │ ├── LocalDefinitions.h │ ├── AudioDeviceManager.cpp │ ├── androidaudioplugin_manager.h │ ├── AudioDeviceManager.h │ ├── OboeAudioDeviceManager.h │ ├── PluginPlayer.h │ ├── AudioBuffer.h │ ├── PluginPlayerConfiguration.h │ ├── PluginPlayer.cpp │ ├── AudioGraphNode.AudioDevice.cpp │ ├── AAPMidiEventTranslator.h │ ├── CMakeLists.txt │ ├── AudioDevice.h │ ├── VirtualAudioDeviceManager.h │ ├── AudioBuffer.cpp │ └── AudioGraph.h │ ├── assets │ └── androidaudioplugin_manager_sample_audio.ogg │ └── AndroidManifest.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── androidaudioplugin-ui-compose ├── consumer-rules.pro ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ └── strings.xml │ │ └── drawable │ │ │ └── bright_life.png │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── org │ │ └── androidaudioplugin │ │ └── ui │ │ └── compose │ │ └── PluginParameterEnumSelector.kt └── proguard-rules.pro ├── androidaudioplugin-ui-compose-app ├── consumer-rules.pro ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ └── strings.xml │ │ └── drawable │ │ │ └── bright_life.png │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── org │ │ └── androidaudioplugin │ │ └── ui │ │ └── compose │ │ └── app │ │ ├── GenericPluginHostActivity.kt │ │ ├── PluginManagerActivity.kt │ │ └── PluginManagerTheme.kt └── proguard-rules.pro ├── .gitignore ├── androidaudioplugin-ui-web ├── src │ └── main │ │ ├── assets │ │ └── web │ │ │ ├── bright_life.png │ │ │ └── index.html │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── org │ │ └── androidaudioplugin │ │ └── ui │ │ └── web │ │ ├── AAPServiceScriptInterface.kt │ │ ├── AAPClientScriptInterface.kt │ │ └── AudioPluginWebViewFactory.kt ├── consumer-rules.pro ├── proguard-rules.pro └── build.gradle.kts ├── publish-pom.gradle ├── .gitmodules ├── gradle.properties ├── settings.gradle ├── LICENSE ├── asan-wrap-bugfixed.sh ├── common.gradle ├── clean-asan-for-debugging.sh ├── publish-root.gradle └── setup-asan-for-debugging.sh /external/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | -------------------------------------------------------------------------------- /gh-pages/kotlin/.keep: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /samples/.gitignore: -------------------------------------------------------------------------------- 1 | jniLibs 2 | -------------------------------------------------------------------------------- /androidaudioplugin-testing/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /androidaudioplugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /include/aap/core/aapxs/extension-service.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/aaphostsample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [atsushieno] 2 | -------------------------------------------------------------------------------- /androidaudioplugin-testing/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | html 2 | latex 3 | javadoc 4 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioDevice.h" 2 | -------------------------------------------------------------------------------- /docs/images/systrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/docs/images/systrace.png -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/VirtualAudioDeviceManager.cpp: -------------------------------------------------------------------------------- 1 | #include "VirtualAudioDeviceManager.h" 2 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/android/ALooperMessage.cpp: -------------------------------------------------------------------------------- 1 | #include "ALooperMessage.h" 2 | #include 3 | -------------------------------------------------------------------------------- /docs/images/aaphostsample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/docs/images/aaphostsample.png -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/include_cmidi2.h: -------------------------------------------------------------------------------- 1 | ../../../../androidaudioplugin/src/main/cpp/core/include_cmidi2.h -------------------------------------------------------------------------------- /docs/images/aap-process-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/docs/images/aap-process-model.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class org.androidaudioplugin.ui.compose.ComposeAudioPluginViewFactory 2 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class org.androidaudioplugin.ui.compose.ComposeAudioPluginViewFactory 2 | -------------------------------------------------------------------------------- /docs/images/aap-instruments-on-kmmk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/docs/images/aap-instruments-on-kmmk.png -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/PluginPlayerConfiguration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "PluginPlayerConfiguration.h" 3 | 4 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AAP Sample Plugins 3 | 4 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TestActivity 3 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TestActivity 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | /.idea 3 | *.iml 4 | .gradle 5 | /local.properties 6 | .DS_Store 7 | build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | .kotlin 12 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/src/main/assets/web/bright_life.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/androidaudioplugin-ui-web/src/main/assets/web/bright_life.png -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class org.androidaudioplugin.ui.web.AudioPluginWebViewFactory 2 | -keep class org.androidaudioplugin.ui.web.WebUIHostHelper 3 | 4 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidaudioplugin-testing/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose/src/main/res/drawable/bright_life.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/androidaudioplugin-ui-compose/src/main/res/drawable/bright_life.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/src/main/res/drawable/bright_life.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/androidaudioplugin-ui-compose-app/src/main/res/drawable/bright_life.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aaphostsample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/samples/aappluginsample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /publish-pom.gradle: -------------------------------------------------------------------------------- 1 | android { 2 | publishing { 3 | multipleVariants { 4 | allVariants() 5 | withJavadocJar() 6 | withSourcesJar() 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/assets/androidaudioplugin_manager_sample_audio.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atsushieno/aap-core/HEAD/androidaudioplugin-manager/src/main/assets/androidaudioplugin_manager_sample_audio.ogg -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/ExtensionInformation.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | // We are sloppy here, instead of causing crash at loading invalid plugin info... 4 | class ExtensionInformation(var required: Boolean, var uri: String?) 5 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/xml/ump_device_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /include/aap/core/host/audio-plugin-host.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _ANDROID_AUDIO_PLUGIN_HOST_HPP_ 4 | #define _ANDROID_AUDIO_PLUGIN_HOST_HPP_ 5 | 6 | #include "plugin-host.h" 7 | #include "plugin-instance.h" 8 | 9 | #endif // _ANDROID_AUDIO_PLUGIN_HOST_HPP_ 10 | 11 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.aidl: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin; 2 | 3 | oneway interface AudioPluginInterfaceCallback { 4 | void hostExtension(int instanceId, String uri, int opcode); 5 | void requestProcess(int instanceId); 6 | } 7 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Shows when audio plugins are running 3 | Audio plugin service is running 4 | 5 | -------------------------------------------------------------------------------- /docs/schemas/midi.rnc: -------------------------------------------------------------------------------- 1 | namespace midi = "urn://androidaudioplugin.org/extensions/midi" 2 | datatypes xs = "http://www.w3.org/2001/XMLSchema-datatypes" 3 | 4 | // attributes on plugin element 5 | 6 | // MIDNAM URI document, should be available via document provider. 7 | attribute midi:midnam { xs:string } 8 | 9 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/xml/device_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/include_cmidi2.h: -------------------------------------------------------------------------------- 1 | #pragma clang diagnostic push 2 | #pragma clang diagnostic ignored "-Wunused-variable" 3 | #pragma clang diagnostic ignored "-Wunused-function" 4 | #pragma clang diagnostic ignored "-Wunused-but-set-variable" 5 | #include 6 | #pragma clang diagnostic pop 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /docs/schemas/gui.rnc: -------------------------------------------------------------------------------- 1 | namespace gui = "urn://androidaudioplugin.org/extensions/gui" 2 | datatypes xs = "http://www.w3.org/2001/XMLSchema-datatypes" 3 | 4 | // attributes on plugin element 5 | attribute gui:ui-view-factory { xs:string } 6 | attribute gui:ui-activity { xs:string } 7 | attribute gui:ui-web { xs:string } 8 | 9 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginViewFactory.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | import android.content.Context 4 | import android.view.View 5 | 6 | abstract class AudioPluginViewFactory { 7 | abstract fun createView(context: Context, pluginId: String, instanceId: Int) : View 8 | } 9 | -------------------------------------------------------------------------------- /gh-pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | AndroidAudioPlugin API References 4 | 5 | 6 |

AndroidAudioPlugin API References

7 |

Native API

8 |

Kotlin API

9 |

androidaudioplugin.aar

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginException.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | class AudioPluginException : Exception { 4 | constructor() : this ("AudioPlugin error") 5 | constructor(message: String) : super(message) 6 | constructor(message: String, innerException: Exception) : super(message, innerException) 7 | } -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /androidaudioplugin/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class androidx.core.app.CoreComponentFactory { (); } 2 | -keep class org.androidaudioplugin.AudioPluginService 3 | -keep class org.androidaudioplugin.hosting.AudioPluginHostHelper 4 | -keep class org.androidaudioplugin.hosting.AudioPluginServiceConnector 5 | -keep class org.androidaudioplugin.AudioPluginServiceHelper 6 | -------------------------------------------------------------------------------- /androidaudioplugin/src/androidTest/java/org/androidaudioplugin/NativeTest.kt: -------------------------------------------------------------------------------- 1 | 2 | package org.androidaudioplugin 3 | 4 | import org.junit.Assert.assertTrue 5 | import org.junit.Test 6 | 7 | class NativeTest { 8 | @Test 9 | fun test1() { 10 | // FIXME: implement actual tests here. 11 | assertTrue("test", true) 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/cerbero"] 2 | path = dependencies/cerbero-artifacts/cerbero 3 | url = https://github.com/atsushieno/cerbero.git 4 | [submodule "external/cmidi2"] 5 | path = external/cmidi2 6 | url = https://github.com/atsushieno/cmidi2.git 7 | [submodule "external/choc"] 8 | path = external/choc 9 | url = https://github.com/Tracktion/choc.git 10 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/AAPMetadataException.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.hosting 2 | 3 | class AAPMetadataException : Exception { 4 | constructor() : super("Error in AAP metadata") 5 | constructor(message: String) : super(message) 6 | constructor(message: String, innerException: Exception) : super(message, innerException) 7 | } -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/LocalDefinitions.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_LOCALDEFINITIONS_H 2 | #define AAP_CORE_LOCALDEFINITIONS_H 3 | 4 | #define AAP_OPEN_CLASS 5 | 6 | #define AAP_MANAGER_MIDI_BUFFER_SIZE 65536 7 | #define AAP_PLUGIN_PLAYER_DEFAULT_MIDI_RING_BUFFER_SIZE 8192 8 | #define AAP_MANAGER_LOG_TAG "AAPManager" 9 | 10 | #endif //AAP_CORE_LOCALDEFINITIONS_H 11 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AAPHostSample 3 | remote audio plugins on this device 4 | audio plugins in this application 5 | Audio Plugin parameters 6 | 7 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioDeviceManager.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioDeviceManager.h" 2 | 3 | 4 | #if ANDROID 5 | #include "OboeAudioDeviceManager.h" 6 | 7 | aap::OboeAudioDeviceManager audioDeviceManager{}; 8 | 9 | aap::AudioDeviceManager* aap::AudioDeviceManager::getInstance() { 10 | return &audioDeviceManager; 11 | } 12 | 13 | #else 14 | 15 | // implement desktop version? 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/PluginServiceConnection.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.hosting 2 | 3 | import android.content.ServiceConnection 4 | import android.os.IBinder 5 | import org.androidaudioplugin.PluginServiceInformation 6 | 7 | class PluginServiceConnection(val platformServiceConnection: ServiceConnection, val serviceInfo: PluginServiceInformation, val binder: IBinder) 8 | -------------------------------------------------------------------------------- /include/aap/core/host/desktop/audio-plugin-host-desktop.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_AUDIO_PLUGIN_HOST_DESKTOP_H 2 | #define ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_AUDIO_PLUGIN_HOST_DESKTOP_H 3 | #if !ANDROID 4 | 5 | #include "../audio-plugin-host.h" 6 | 7 | namespace aap { 8 | 9 | } // namespace aap 10 | 11 | #endif // ANDROID 12 | #endif // ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_AUDIO_PLUGIN_HOST_DESKTOP_H 13 | 14 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/androidaudioplugin_manager.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef AAP_CORE_ANDROIDAUDIOPLUGIN_MANAGER_H 3 | #define AAP_CORE_ANDROIDAUDIOPLUGIN_MANAGER_H 4 | 5 | #include "LocalDefinitions.h" 6 | 7 | #include "AudioDevice.h" 8 | #include "AudioDeviceManager.h" 9 | #include "AudioGraph.h" 10 | #include "AudioGraphNode.h" 11 | 12 | #if ANDROID 13 | 14 | #include "OboeAudioDeviceManager.h" 15 | 16 | #endif 17 | 18 | #endif //AAP_CORE_ANDROIDAUDIOPLUGIN_MANAGER_H 19 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | android.prefabVersion=2.1.0 3 | 4 | kotlin.code.style=official 5 | 6 | org.gradle.jvmargs=-Xmx2560M -XX:+UseParallelGC 7 | 8 | org.gradle.caching=true 9 | 10 | org.gradle.configureondemand=true 11 | #org.gradle.configuration-cache=true 12 | 13 | android.experimental.androidTest.useUnifiedTestPlatform=true 14 | android.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" 15 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 16 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/hosting/plugin-client-system.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "aap/core/host/plugin-client-system.h" 3 | 4 | namespace aap { 5 | 6 | std::vector PluginClientSystem::getInstalledPlugins(bool returnCacheIfExists, std::vector* searchPaths) { 7 | std::vector aapPaths{}; 8 | for (auto path : getPluginPaths()) 9 | getAAPMetadataPaths(path, aapPaths); 10 | auto ret = getPluginsFromMetadataPaths(aapPaths); 11 | return ret; 12 | } 13 | 14 | } // namespace aap 15 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/NativePluginService.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | /* 4 | This class wraps native aap::PluginService and provides the corresponding features to the native API, to some extent. 5 | */ 6 | class NativePluginService(pluginId: String) { 7 | internal val native: Long 8 | 9 | fun getInstance(instanceId: Int) = NativeLocalPluginInstance(this, instanceId) 10 | 11 | init { 12 | native = AudioPluginServiceHelper.getServiceInstance(pluginId) 13 | assert(native != 0L) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/schemas/parameters.rnc: -------------------------------------------------------------------------------- 1 | default namespace "urn://androidaudioplugin.org/extensions/parameters" 2 | datatypes xs = "http://www.w3.org/2001/XMLSchema-datatypes" 3 | 4 | // content of plugin element 5 | element parameters { 6 | element parameter { 7 | attribute id { xs:int } 8 | attribute name { xs:string } 9 | attribute default { xs:float } 10 | attribute minimum { xs:float } 11 | attribute maximum { xs:float } 12 | element enumeration { 13 | attribute value { xs:float } 14 | attribute name { xs:string } 15 | } 16 | }* 17 | } 18 | 19 | -------------------------------------------------------------------------------- /include/aap/core/host/android/audio-plugin-host-android.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_AUDIO_PLUGIN_HOST_ANDROID_H 2 | #define ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_AUDIO_PLUGIN_HOST_ANDROID_H 3 | #ifdef ANDROID 4 | 5 | #include 6 | #include "../audio-plugin-host.h" 7 | 8 | namespace aap { 9 | aap::PluginClientConnectionList* getPluginConnectionListByConnectorInstanceId(int32_t connectorInstanceId, bool createIfNotExist); 10 | 11 | } // namespace aap 12 | 13 | #endif // ANDROID 14 | #endif // ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_AUDIO_PLUGIN_HOST_ANDROID_H 15 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/hosting/audio-plugin-host-internals.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AUDIO_PLUGIN_HOST_INTERNALS_H 2 | #define AAP_CORE_AUDIO_PLUGIN_HOST_INTERNALS_H 3 | 4 | #include "aap/unstable/logging.h" 5 | 6 | #if ANDROID 7 | AndroidAudioPluginFactory *GetAndroidAudioPluginFactoryClientBridge(aap::PluginClient *client); 8 | #else 9 | AndroidAudioPluginFactory *GetDesktopAudioPluginFactoryClientBridge(aap::PluginClient *client); 10 | #endif 11 | 12 | namespace aap { 13 | int32_t getMidiSettingsFromLocalConfig(std::string pluginId); 14 | } 15 | #endif // AAP_CORE_AUDIO_PLUGIN_HOST_INTERNALS_H 16 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/src/main/java/org/androidaudioplugin/ui/compose/app/GenericPluginHostActivity.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.ui.compose.app 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | 7 | open class GenericPluginHostActivity : ComponentActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContent { 11 | PluginManagerTheme { 12 | SystemPluginManagerMain() 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/PluginServiceInformation.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | import android.graphics.drawable.Drawable 4 | 5 | /** 6 | * Plugin Service information structure. The members mostly correspond to `` element 7 | * and `` for an AudioPluginService in `AndroidManifest.xml` 8 | */ 9 | class PluginServiceInformation(var label: String, var packageName: String, var className: String, 10 | var icon: Drawable? = null) { 11 | var extensions = mutableListOf() 12 | var plugins = mutableListOf() 13 | } 14 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/update-binder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "`uname`" == 'Darwin' ] ; then 4 | ANDROID_HOME=~/Library/Android/sdk 5 | else 6 | ANDROID_HOME=~/Android/Sdk 7 | fi 8 | BUILD_TOOLS_VERSION=35.0.1 9 | THIS_DIR=`dirname $(readlink -f $0)` 10 | AIDL_DIR=$THIS_DIR 11 | AIDL_TOOL=$ANDROID_HOME/build-tools/$BUILD_TOOLS_VERSION/aidl 12 | 13 | $AIDL_TOOL --lang=ndk \ 14 | -o $THIS_DIR/cpp/android/gen \ 15 | -h $THIS_DIR/cpp/android/gen/include \ 16 | -I $THIS_DIR/aidl/ \ 17 | $THIS_DIR/aidl/org/androidaudioplugin/AudioPluginInterface.aidl \ 18 | $THIS_DIR/aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.aidl 19 | 20 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/hosting/PluginHost.Service.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #define LOG_TAG "AAP.PluginHost.Service" 6 | 7 | int32_t aap::PluginService::createInstance(std::string identifier, int sampleRate) { 8 | auto info = plugin_list->getPluginInformation(identifier); 9 | if (!info) { 10 | aap::a_log_f(AAP_LOG_LEVEL_ERROR, LOG_TAG, "aap::PluginService: Plugin information was not found for: %s ", identifier.c_str()); 11 | return -1; 12 | } 13 | auto instance = instantiateLocalPlugin(info, sampleRate); 14 | return instance->getInstanceId(); 15 | } 16 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/src/main/java/org/androidaudioplugin/ui/compose/app/PluginManagerActivity.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.ui.compose.app 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.material3.Surface 7 | 8 | class PluginManagerActivity : ComponentActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContent { 12 | PluginManagerTheme { 13 | Surface { 14 | LocalPluginManagerMain() 15 | } 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositories { 10 | mavenLocal() 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = 'aap-core' 17 | include ':androidaudioplugin' 18 | include ':androidaudioplugin-midi-device-service' 19 | include ':androidaudioplugin-ui-compose' 20 | include ':androidaudioplugin-ui-compose-app' 21 | include ':androidaudioplugin-manager' 22 | include ':androidaudioplugin-ui-web' 23 | include ':samples:aappluginsample' 24 | include ':samples:aaphostsample' 25 | include ':androidaudioplugin-testing' 26 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/ParameterInformation.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | class ParameterInformation(var id: Int, var name: String, var minimumValue: Double = 0.0, var maximumValue: Double = 1.0, var defaultValue: Double = 0.0) { 4 | // They are used by JNI 5 | private fun addEnum(index: Int, value: Double, name: String) { 6 | enumerations.add(EnumerationInformation(index, value, name)) 7 | } 8 | private fun getEnumCount() = enumerations.size 9 | private fun getEnum(index: Int) = enumerations[index] 10 | 11 | class EnumerationInformation(var index: Int, var value: Double, var name: String) 12 | 13 | val enumerations: MutableList = mutableListOf() 14 | } 15 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioDeviceManager.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AUDIODEVICEMANAGER_H 2 | #define AAP_CORE_AUDIODEVICEMANAGER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "AudioDevice.h" 8 | 9 | namespace aap { 10 | class AudioDeviceManager { 11 | public: 12 | // This needs to be implemented for each platform 13 | static AudioDeviceManager* getInstance(); 14 | 15 | virtual AudioDeviceIn* ensureDefaultInputOpened(int32_t sampleRate, int32_t framesPerCallback, int32_t numChannels) = 0; 16 | virtual AudioDeviceOut* ensureDefaultOutputOpened(int32_t sampleRate, int32_t framesPerCallback, int32_t numChannels) = 0; 17 | }; 18 | } 19 | 20 | 21 | #endif //AAP_CORE_AUDIODEVICEMANAGER_H 22 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/src/main/java/org/androidaudioplugin/midideviceservice/MidiDeviceServiceStartup.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.midideviceservice 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import org.androidaudioplugin.hosting.AudioPluginHostHelper 6 | import org.androidaudioplugin.hosting.AudioPluginMidiSettings 7 | 8 | class MidiDeviceServiceStartup : Initializer { 9 | override fun create(context: Context) { 10 | System.loadLibrary("aapmidideviceservice") 11 | initializeApplicationContext(context.applicationContext) 12 | } 13 | 14 | private external fun initializeApplicationContext(context: Context) 15 | 16 | override fun dependencies(): MutableList>> = mutableListOf() 17 | } 18 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/UmpHelper.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.hosting 2 | 3 | object UmpHelper { 4 | private val i8x4to32 = { b1: Byte, b2: Byte, b3: Byte, b4: Byte -> 5 | (b1.toInt() shl 24) + (b2.toInt() shl 16) + (b3.toInt() shl 8) + b4 } 6 | 7 | fun aapUmpSysex8Parameter(parameterId: UInt, parameterValue: Float, group: Byte = 0, channel: Byte = 0, key: Byte = 0, noteId: UByte = 0u) : Array { 8 | val streamId: Byte = 0 9 | val subId: Byte = 0 10 | val subId2: Byte = 0 11 | return arrayOf(i8x4to32((0x50 + group).toByte(), 0, streamId, 0x7E), 12 | i8x4to32(0x7F, subId, subId2, channel), 13 | i8x4to32(key, noteId.toByte(), 0, 0) + (parameterId and 0x3FFFu).toInt(), 14 | parameterValue.toRawBits()) 15 | } 16 | } -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/PortInformation.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | /** 4 | * Port information structure. The members mostly correspond to attributes in a `` element in 5 | * `aap_metadata.xml`. 6 | */ 7 | class PortInformation(var index: Int, var name: String, var direction: Int, var content: Int) 8 | { 9 | companion object { 10 | const val PORT_DIRECTION_INPUT = 0 11 | const val PORT_DIRECTION_OUTPUT = 1 12 | 13 | // Open-end list of content characteristics i.e. it can be extended in the future (reserved) 14 | const val PORT_CONTENT_TYPE_GENERAL = 0 15 | const val PORT_CONTENT_TYPE_AUDIO = 1 16 | const val PORT_CONTENT_TYPE_MIDI = 2 17 | const val PORT_CONTENT_TYPE_MIDI2 = 3 18 | } 19 | 20 | var minimumSizeInBytes: Int = 0 21 | } 22 | -------------------------------------------------------------------------------- /androidaudioplugin-testing/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /include/aap/unstable/utility.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_UNSTABLE_UTILITY_H 2 | #define AAP_CORE_UNSTABLE_UTILITY_H 3 | 4 | #include 5 | #include 6 | 7 | #define AAP_ASSERT_FALSE assert(false) 8 | 9 | namespace aap { 10 | 11 | // I don't think any simple and stupid SpinLock works well on mobiles, as we do not want to dry up battery. 12 | class NanoSleepLock { 13 | std::atomic_flag state = ATOMIC_FLAG_INIT; 14 | public: 15 | void lock() noexcept { 16 | const auto delay = timespec{0, 1000}; // 1 microsecond 17 | while(state.test_and_set()) 18 | clock_nanosleep(CLOCK_REALTIME, 0, &delay, nullptr); 19 | } 20 | void unlock() noexcept { state.clear(); } 21 | bool try_lock() noexcept { return !state.test_and_set(); } 22 | }; 23 | 24 | } 25 | 26 | 27 | #endif//AAP_CORE_UNSTABLE_UTILITY_H -------------------------------------------------------------------------------- /samples/aaphostsample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class androidx.core.app.CoreComponentFactory { (); } 24 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/aappluginsample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class androidx.core.app.CoreComponentFactory { (); } 24 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/OboeAudioDeviceManager.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_ANDROIDAUDIODEVICEMANAGER_H 2 | #define AAP_CORE_ANDROIDAUDIODEVICEMANAGER_H 3 | 4 | #include "AudioDeviceManager.h" 5 | #include 6 | 7 | namespace aap { 8 | 9 | class OboeAudioDeviceIn; 10 | class OboeAudioDeviceOut; 11 | 12 | class OboeAudioDeviceManager : public AudioDeviceManager { 13 | uint32_t frames_per_callback{}; 14 | std::shared_ptr input{nullptr}; 15 | std::shared_ptr output{nullptr}; 16 | 17 | public: 18 | OboeAudioDeviceManager() = default; 19 | AudioDeviceIn * ensureDefaultInputOpened(int32_t sampleRate, int32_t framesPerCallback, int32_t numChannels) override; 20 | 21 | AudioDeviceOut * ensureDefaultOutputOpened(int32_t sampleRate, int32_t framesPerCallback, int32_t numChannels) override; 22 | }; 23 | } 24 | 25 | 26 | #endif //AAP_CORE_ANDROIDAUDIODEVICEMANAGER_H 27 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/src/main/java/org/androidaudioplugin/midideviceservice/StandaloneAudioPluginMidiDeviceService.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.midideviceservice 2 | 3 | import androidx.annotation.RequiresApi 4 | import org.androidaudioplugin.AudioPluginServiceHelper 5 | import org.androidaudioplugin.PluginInformation 6 | 7 | class StandaloneAudioPluginMidiDeviceService : AudioPluginMidiDeviceService() { 8 | 9 | override val plugins: List 10 | get() = AudioPluginServiceHelper.getLocalAudioPluginService(applicationContext) 11 | .plugins.filter { p -> p.packageName == this.packageName } 12 | } 13 | 14 | @RequiresApi(35) 15 | class StandaloneAudioPluginMidiUmpDeviceService : AudioPluginMidiUmpDeviceService() { 16 | 17 | override val plugins: List 18 | get() = AudioPluginServiceHelper.getLocalAudioPluginService(applicationContext) 19 | .plugins.filter { p -> p.packageName == this.packageName } 20 | } 21 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/PluginPlayer.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_PLUGINPLAYER_H 2 | #define AAP_CORE_PLUGINPLAYER_H 3 | 4 | #include "PluginPlayerConfiguration.h" 5 | #include "AudioGraph.h" 6 | 7 | namespace aap { 8 | class PluginPlayer { 9 | PluginPlayerConfiguration configuration; 10 | 11 | SimpleLinearAudioGraph graph; 12 | 13 | public: 14 | explicit PluginPlayer(PluginPlayerConfiguration &configuration); 15 | 16 | virtual ~PluginPlayer(); 17 | 18 | SimpleLinearAudioGraph& getGraph() { return graph; } 19 | 20 | void setAudioSource(uint8_t *data, int32_t dataLength, const char *filename); 21 | 22 | void addMidiEvents(uint8_t* data, int32_t dataLength, int64_t timestampInNanoseconds); 23 | 24 | void startProcessing(); 25 | 26 | void pauseProcessing(); 27 | 28 | void enableAudioRecorder(); 29 | 30 | void setPresetIndex(int index); 31 | }; 32 | } 33 | 34 | 35 | #endif //AAP_CORE_PLUGINPLAYER_H 36 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class org.androidaudioplugin.ui.web.AudioPluginWebViewFactory 24 | -keep class org.androidaudioplugin.ui.web.WebUIHostHelper 25 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/NativePluginClient.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.hosting 2 | 3 | /* 4 | This class wraps native aap::PluginClient and provides the corresponding features to the native API, to some extent. 5 | */ 6 | class NativePluginClient(val native: Long) { 7 | 8 | // depending on how the native instance is created, it may or may not be invoked from Kotlin code. 9 | fun dispose() = destroyInstance(native) 10 | 11 | fun createInstanceFromExistingConnection(sampleRate: Int, pluginId: String) : NativeRemotePluginInstance { 12 | return NativeRemotePluginInstance.create(pluginId, sampleRate, native) 13 | } 14 | 15 | companion object { 16 | fun createFromConnection(serviceConnectionId: Int) = NativePluginClient(newInstance(serviceConnectionId)) 17 | 18 | @JvmStatic 19 | private external fun newInstance(serviceConnectionId: Int) : Long 20 | 21 | @JvmStatic 22 | private external fun destroyInstance(native: Long) 23 | } 24 | } -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/hosting/plugin-service-list.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef AAP_CORE_PLUGIN_SERVICE_LIST_H 3 | #define AAP_CORE_PLUGIN_SERVICE_LIST_H 4 | 5 | #include "aap/core/host/audio-plugin-host.h" 6 | 7 | namespace aap { 8 | 9 | class PluginServiceList { 10 | std::vector bound_plugin_service_list{}; 11 | 12 | public: 13 | static PluginServiceList* getInstance(); 14 | 15 | void addBoundServiceInProcess(PluginService* service) { 16 | bound_plugin_service_list.emplace_back(service); 17 | } 18 | 19 | void removeBoundServiceInProcess(PluginService* service) { 20 | auto i = std::find(bound_plugin_service_list.begin(), bound_plugin_service_list.end(), service); 21 | bound_plugin_service_list.erase(i); 22 | } 23 | 24 | PluginService* findBoundServiceInProcess(const char* pluginId) { 25 | for (auto s : bound_plugin_service_list) 26 | if (true) // FIXME: find exact Service instance 27 | return s; 28 | return nullptr; 29 | } 30 | }; 31 | 32 | } 33 | 34 | #endif //AAP_CORE_PLUGIN_SERVICE_LIST_H 35 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/AudioPluginClientInitializer.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.hosting 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import org.androidaudioplugin.AudioPluginNatives 6 | 7 | /** 8 | * Implements an Initializer in Jetpack App Startup manner. Every host should set it up. 9 | * 10 | * libandroidaudioplugin.so may be dynamically loaded when any library that depends on it is loaded, 11 | * but in that case the initialization function set up here is NOT called and thus JNI calls 12 | * (including the basic AAP Binder calls) will fail. 13 | * To avoid that, set up this initializer to ensure the JNI setup. 14 | */ 15 | class AudioPluginClientInitializer : Initializer { 16 | override fun create(context: Context) : Unit { 17 | System.loadLibrary("androidaudioplugin") 18 | AudioPluginNatives.initializeAAPJni(context) 19 | } 20 | 21 | override fun dependencies(): MutableList>> { 22 | return mutableListOf>>() 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-* Audio Plugins For Android developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioBuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AUDIOBUFFER_H 2 | #define AAP_CORE_AUDIOBUFFER_H 3 | 4 | #include "LocalDefinitions.h" 5 | #define AAP_MANAGER_AUDIO_QUEUE_NX_FRAMES 4 6 | #include 7 | #include 8 | #include 9 | 10 | namespace aap { 11 | 12 | class AudioBuffer { 13 | static int32_t aapBufferGetNumFrames(aap_buffer_t &); 14 | static void *aapBufferGetBuffer(aap_buffer_t &, int32_t); 15 | static int32_t aapBufferGetBufferSize(aap_buffer_t &, int32_t); 16 | static int32_t aapBufferGetNumPorts(aap_buffer_t &); 17 | 18 | public: 19 | choc::buffer::ChannelArrayBuffer audio; 20 | void *midi_in; 21 | void *midi_out; 22 | int32_t midi_capacity; 23 | 24 | AudioBuffer(int32_t numChannels, int32_t framesPerCallback, 25 | int32_t midiBufferSize = AAP_MANAGER_MIDI_BUFFER_SIZE); 26 | ~AudioBuffer(); 27 | 28 | aap_buffer_t asAAPBuffer(); 29 | }; 30 | 31 | } 32 | 33 | #endif //AAP_CORE_AUDIOBUFFER_H 34 | -------------------------------------------------------------------------------- /include/aap/core/host/plugin-client-system.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_PLUGIN_CLIENT_SYSTEM_H 2 | #define AAP_CORE_PLUGIN_CLIENT_SYSTEM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../plugin-information.h" 8 | #include "plugin-connections.h" 9 | 10 | namespace aap { 11 | 12 | class PluginClientSystem 13 | { 14 | public: 15 | static PluginClientSystem* getInstance(); 16 | 17 | virtual int32_t createSharedMemory(size_t size) = 0; 18 | 19 | virtual void ensurePluginServiceConnected(aap::PluginClientConnectionList* connections, std::string serviceName, std::function callback) = 0; 20 | 21 | virtual std::vector getPluginPaths() = 0; 22 | virtual void getAAPMetadataPaths(std::string path, std::vector& results) = 0; 23 | virtual std::vector getPluginsFromMetadataPaths(std::vector& aapMetadataPaths) = 0; 24 | 25 | std::vector getInstalledPlugins(bool returnCacheIfExists = true, std::vector* searchPaths = nullptr); 26 | }; 27 | 28 | } // namespace aap 29 | 30 | #endif //AAP_CORE_PLUGIN_CLIENT_SYSTEM_H 31 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/androidTest/java/org/androidaudioplugin/aapinstrumentsample/MidiDeviceServiceTest.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.aapinstrumentsample 2 | 3 | import android.content.Context 4 | import androidx.test.core.app.ApplicationProvider 5 | import androidx.test.rule.ServiceTestRule 6 | import org.androidaudioplugin.androidaudioplugin.testing.AudioPluginMidiDeviceServiceTesting 7 | import org.androidaudioplugin.androidaudioplugin.testing.AudioPluginServiceTesting 8 | import org.junit.Rule 9 | import org.junit.Test 10 | 11 | class MidiDeviceServiceTest { 12 | 13 | private val applicationContext = ApplicationProvider.getApplicationContext() 14 | private val testing = AudioPluginMidiDeviceServiceTesting(applicationContext) 15 | @get:Rule 16 | val serviceRule = ServiceTestRule() 17 | 18 | @Test 19 | fun basicServiceOperations() { 20 | // Replace plugin name after copy-pasting this code. 21 | testing.basicServiceOperations("InstrumentAPISample") 22 | } 23 | 24 | @Test 25 | fun repeatDirectServieOperations() { 26 | for (i in 0 until 5) 27 | basicServiceOperations() 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/PluginPlayerConfiguration.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_PLUGINPLAYERCONFIGURATION_H 2 | #define AAP_CORE_PLUGINPLAYERCONFIGURATION_H 3 | 4 | #include "AudioDevice.h" 5 | 6 | #include 7 | 8 | namespace aap { 9 | class PluginPlayerConfiguration { 10 | int32_t sample_rate; 11 | int32_t frames_per_callback; 12 | int32_t channel_count; 13 | 14 | public: 15 | PluginPlayerConfiguration( 16 | /** should default to device optimal settings */ 17 | int32_t sampleRate, 18 | /** should default to device optimal settings */ 19 | int32_t framesPerCallback, 20 | int32_t channelCount) : 21 | sample_rate(sampleRate), 22 | frames_per_callback(framesPerCallback), 23 | channel_count(channelCount) { 24 | } 25 | 26 | int32_t getSampleRate() { return sample_rate; } 27 | 28 | int32_t getFramesPerCallback() { return frames_per_callback; } 29 | 30 | int32_t getChannelCount() { return channel_count; } 31 | }; 32 | 33 | } 34 | 35 | 36 | #endif //AAP_CORE_PLUGINPLAYERCONFIGURATION_H 37 | -------------------------------------------------------------------------------- /asan-wrap-bugfixed.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | HERE=$(cd "$(dirname "$0")" && pwd) 3 | 4 | cmd=$1 5 | shift 6 | 7 | # This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- 8 | # bit app running on a 64-bit device, the 64-bit getprop will fail to load 9 | # because it will preload a 32-bit ASan runtime. 10 | # https://github.com/android/ndk/issues/1744 11 | os_version=$(getprop ro.build.version.sdk) 12 | 13 | if [ "$os_version" -eq "27" ]; then 14 | cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" 15 | elif [ "$os_version" -eq "28" ]; then 16 | cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" 17 | else 18 | cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" 19 | fi 20 | 21 | export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 22 | ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) 23 | if [ -f "$HERE/libc++_shared.so" ]; then 24 | # Workaround for https://github.com/android-ndk/ndk/issues/988. 25 | export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" 26 | else 27 | export LD_PRELOAD="$ASAN_LIB" 28 | fi 29 | 30 | exec $cmd 31 | -------------------------------------------------------------------------------- /androidaudioplugin/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class androidx.core.app.CoreComponentFactory { (); } 24 | -keep class org.androidaudioplugin.AudioPluginService 25 | -keep class org.androidaudioplugin.hosting.AudioPluginHostHelper 26 | -keep class org.androidaudioplugin.hosting.AudioPluginServiceConnector 27 | -keep class org.androidaudioplugin.AudioPluginServiceHelper 28 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/aapxs/standard-extensions.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "aap/core/aapxs/standard-extensions.h" 3 | 4 | aap::xs::AAPXSDefinition_Midi midi; 5 | aap::xs::AAPXSDefinition_Parameters parameters; 6 | aap::xs::AAPXSDefinition_Presets presets; 7 | aap::xs::AAPXSDefinition_State state; 8 | aap::xs::AAPXSDefinition_Gui gui; 9 | aap::xs::AAPXSDefinition_Urid urid; 10 | 11 | aap::xs::AAPXSDefinitionRegistry::AAPXSDefinitionRegistry( 12 | std::unique_ptr mapping, 13 | std::vector items) 14 | : AAPXSUridMapping(mapping.get()), mapping(std::move(mapping)) { 15 | std::function add = [&](AAPXSDefinition d) { this->add(d, d.uri); }; 16 | 17 | for (auto item : items) 18 | add(item); 19 | } 20 | 21 | aap::xs::AAPXSDefinitionRegistry standard_extensions{std::make_unique(), std::vector({ 22 | urid.asPublic(), 23 | midi.asPublic(), 24 | parameters.asPublic(), 25 | presets.asPublic(), 26 | state.asPublic(), 27 | gui.asPublic() 28 | })}; 29 | 30 | aap::xs::AAPXSDefinitionRegistry *aap::xs::AAPXSDefinitionRegistry::getStandardExtensions() { 31 | return &standard_extensions; 32 | } 33 | -------------------------------------------------------------------------------- /include/aap/ext/state.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_STATE_H_INCLUDED 2 | #define AAP_STATE_H_INCLUDED 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "../android-audio-plugin.h" 9 | #include "stdint.h" 10 | 11 | #define AAP_STATE_EXTENSION_URI "urn://androidaudioplugin.org/extensions/state/v3" 12 | 13 | typedef struct { 14 | void* data; 15 | size_t data_size; 16 | } aap_state_t; 17 | 18 | typedef struct aap_state_extension_t { 19 | /* 20 | * `aapxs_context` is an opaque pointer assigned and used by AAPXS hosting implementation (libandroidaudioplugin). 21 | * Neither of plugin developer (extension user) or extension developers is supposed to touch it. 22 | * this struct is instantiated per extension in a plugin instance. 23 | */ 24 | void *aapxs_context; 25 | RT_UNSAFE size_t (*get_state_size) (aap_state_extension_t* ext, AndroidAudioPlugin* plugin); 26 | RT_UNSAFE void (*get_state) (aap_state_extension_t* ext, AndroidAudioPlugin* plugin, aap_state_t* destination); 27 | RT_UNSAFE void (*set_state) (aap_state_extension_t* ext, AndroidAudioPlugin* plugin, aap_state_t* source); 28 | } aap_state_extension_t; 29 | 30 | #ifdef __cplusplus 31 | } // extern "C" 32 | #endif 33 | 34 | #endif /* AAP_STATE_H_INCLUDED */ 35 | -------------------------------------------------------------------------------- /include/aap/ext/plugin-info.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDAUDIOPLUGIN_PLUGIN_INFO_EXTENSION_H_INCLUDED 2 | #define ANDROIDAUDIOPLUGIN_PLUGIN_INFO_EXTENSION_H_INCLUDED 3 | 4 | // plugin-info extension can be used tentatively by a plugin to retrieve its own metadata, ports, and 5 | // parameters from the host that had parsed `aap_metadata.xml` and configured ports etc. 6 | // Those information items are not obviously known to plugin wrappers, including our own local host 7 | // unless it implements `get_plugin_info()` (newly introduced in post-0.7.8 API) 8 | // 9 | // IPC is not involved for `get()`, as it is implemented at LocalPluginInstance. A local plugin host should be 10 | // able to resolve all the plugin information items. 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #include "../android-audio-plugin.h" 17 | #include "stdint.h" 18 | 19 | #define AAP_PLUGIN_INFO_EXTENSION_URI "urn://androidaudioplugin.org/extensions/plugin-info/v3" 20 | 21 | typedef struct aap_host_plugin_info_extension_t { 22 | aap_plugin_info_t (*get) (aap_host_plugin_info_extension_t* ext, AndroidAudioPluginHost* host, const char *pluginId); 23 | } aap_host_plugin_info_extension_t; 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif // ANDROIDAUDIOPLUGIN_PLUGIN_INFO_EXTENSION_H_INCLUDED 30 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/PluginPlayer.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginPlayer.h" 2 | #include 3 | 4 | aap::PluginPlayer::PluginPlayer(aap::PluginPlayerConfiguration &pluginPlayerConfiguration) : 5 | configuration(pluginPlayerConfiguration), 6 | graph(configuration.getSampleRate(), configuration.getFramesPerCallback(), configuration.getChannelCount()) { 7 | } 8 | 9 | aap::PluginPlayer::~PluginPlayer() { 10 | graph.pauseProcessing(); 11 | } 12 | 13 | void aap::PluginPlayer::setAudioSource(uint8_t *data, int32_t dataLength, const char *filename) { 14 | graph.setAudioSource(data, dataLength, filename); 15 | } 16 | 17 | void aap::PluginPlayer::startProcessing() { 18 | graph.startProcessing(); 19 | } 20 | 21 | void aap::PluginPlayer::pauseProcessing() { 22 | graph.pauseProcessing(); 23 | } 24 | 25 | void aap::PluginPlayer::enableAudioRecorder() { 26 | graph.enableAudioRecorder(); 27 | } 28 | 29 | void 30 | aap::PluginPlayer::addMidiEvents(uint8_t *data, int32_t dataLength, int64_t timestampInNanoseconds) { 31 | graph.addMidiEvent(data, dataLength, timestampInNanoseconds); 32 | } 33 | 34 | void aap::PluginPlayer::setPresetIndex(int index) { 35 | graph.setPresetIndex(index); 36 | } 37 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginNatives.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | import android.content.Context 4 | import android.os.IBinder 5 | import android.os.SharedMemory 6 | 7 | internal class AudioPluginNatives 8 | { 9 | companion object { 10 | init { 11 | System.loadLibrary("androidaudioplugin") 12 | } 13 | 14 | @JvmStatic 15 | external fun initializeAAPJni(applicationContext: Context) 16 | 17 | @JvmStatic 18 | external fun createBinderForService() : IBinder 19 | 20 | @JvmStatic 21 | external fun destroyBinderForService(binder: IBinder) 22 | 23 | @JvmStatic 24 | external fun prepareNativeLooper() 25 | 26 | @JvmStatic 27 | external fun startNativeLooper() 28 | 29 | @JvmStatic 30 | external fun stopNativeLooper() 31 | 32 | @JvmStatic 33 | external fun addBinderForClient(connectorInstanceId: Int, packageName: String, className: String, binder: IBinder) 34 | 35 | @JvmStatic 36 | external fun removeBinderForClient(connectorInstanceId: Int, packageName: String, className: String) 37 | 38 | @JvmStatic 39 | external fun getSharedMemoryFD(shm: SharedMemory) : Int 40 | 41 | @JvmStatic 42 | external fun closeSharedMemoryFD(fd: Int) 43 | } 44 | } -------------------------------------------------------------------------------- /common.gradle: -------------------------------------------------------------------------------- 1 | 2 | android { 3 | compileSdk = libs.versions.android.compileSdk.get().toInteger() 4 | buildToolsVersion = libs.versions.build.tools.get() 5 | 6 | defaultConfig { 7 | minSdk = libs.versions.android.minSdk.get().toInteger() 8 | targetSdk = libs.versions.android.targetSdk.get().toInteger() 9 | versionCode = 1 10 | versionName = libs.versions.aap.core.get() 11 | 12 | consumerProguardFiles ("consumer-rules.pro") 13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | 16 | // This should not be necessary in normal projects, but we specify c++_shared and thus 17 | // ODR hits us. We specify ANDROID_STL=c++_shared in favor of prefab (which is not enabled 18 | // yet but will be at some stage). This means, every AAP related project has to fix the 19 | // NDK version in use. It is not realistic when it becomes for public uses, but so far 20 | // we have to take this pill... 21 | //ndkVersion = "21.4.7075529" 22 | ndkVersion = libs.versions.ndk.get() 23 | buildFeatures { 24 | prefab = true 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_11 29 | targetCompatibility = JavaVersion.VERSION_11 30 | } 31 | kotlinOptions { 32 | jvmTarget = '11' 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/hosting/plugin-connections.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "aap/core/host/plugin-connections.h" 3 | #include "aap/core/host/plugin-client-system.h" 4 | 5 | namespace aap { 6 | 7 | PluginListSnapshot PluginListSnapshot::queryServices() { 8 | PluginListSnapshot ret{}; 9 | for (auto p : PluginClientSystem::getInstance()->getInstalledPlugins()) 10 | ret.plugins.emplace_back(p); 11 | return ret; 12 | } 13 | 14 | void* PluginClientConnectionList::getServiceHandleForConnectedPlugin(std::string packageName, std::string className) 15 | { 16 | for (int i = 0; i < serviceConnections.size(); i++) { 17 | auto s = serviceConnections[i]; 18 | if (s->getPackageName() == packageName && s->getClassName() == className) 19 | return serviceConnections[i]->getConnectionData(); 20 | } 21 | return nullptr; 22 | } 23 | 24 | void* PluginClientConnectionList::getServiceHandleForConnectedPlugin(std::string pluginId) 25 | { 26 | auto pl = PluginClientSystem::getInstance()->getInstalledPlugins(); 27 | for (auto &plugin : pl) 28 | if (plugin->getPluginID() == pluginId) 29 | return getServiceHandleForConnectedPlugin(plugin->getPluginPackageName(), 30 | plugin->getPluginLocalName()); 31 | return nullptr; 32 | } 33 | 34 | } // namespace aap 35 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/cpp/aapxs/include/aap/examples/aapxs/test-extension.h: -------------------------------------------------------------------------------- 1 | // Extension API -------------------------------------- 2 | 3 | #ifndef AAPXS_SAMPLE_TEST_EXTENSION_H_INCLUDED 4 | #define AAPXS_SAMPLE_TEST_EXTENSION_H_INCLUDED 5 | 6 | #include 7 | 8 | #define AAPXS_EXAMPLE_TEST_EXTENSION_URI "urn://androidaudioplugin.org/examples/aapxs/test1/v1" 9 | 10 | // we have to resort to this forward declaration in the function typedefs below. 11 | struct example_test_extension_t; 12 | 13 | // Extension function #1: takes an integer input and do something, return an integer value. 14 | // Context may be required in the applied plugins (depends on each plugin developer). 15 | typedef int32_t (*fooFunc) (struct example_test_extension_t* context, AndroidAudioPlugin* plugin, int32_t input); 16 | 17 | // Extension function #2: takes a string input and do something. Nothing to return. 18 | // Context may be required in the applied plugins (depends on each plugin developer). 19 | typedef void (*barFunc) (struct example_test_extension_t* context, AndroidAudioPlugin* plugin, char *msg); 20 | 21 | // The extension structure 22 | typedef struct example_test_extension_t { 23 | void *context; // it might be AndroidAudioPlugin, or AAPXSClientInstance 24 | fooFunc foo; 25 | barFunc bar; 26 | } example_test_extension_t; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /clean-asan-for-debugging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This shell script is to set up ASAN for debugging native libs. 4 | # Specify your own path in those variables below. 5 | 6 | # They have to be adjusted to your environment and NDK version. 7 | # Although note that whenever we use AAP the NDK version has to be fixed to 8 | # this one otherwise libc++_shared.so version mismatch may bite you. 9 | # 10 | # Also note that you need below in build.gradle: 11 | # > android.packagingOptions.jniLibs.useLegacyPackaging = true 12 | # 13 | ANDROID_NDK_PATH=~/Android/Sdk/ndk/21.4.7075529 14 | #ANDROID_NDK_PATH=~/Android/Sdk/ndk/23.1.7779620 15 | CLANG_VER=9.0.9 16 | #CLANG_VER=12.0.8 17 | HOST_ARCH_LIB=linux-x86_64/lib64 18 | CLANG_LIB=$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$HOST_ARCH_LIB/clang/$CLANG_VER/lib 19 | 20 | ALL_ABIS=("x86" "x86_64" "armeabi-v7a" "arm64-v8a") 21 | 22 | ALL_APPS=("samples/aaphostsample" "samples/aapbarebonepluginsample" "samples/aapinstrumentsample" "samples/aapxssample") 23 | 24 | for sample in "${ALL_APPS[@]}"; do 25 | 26 | echo "APP: $sample" 27 | SAMPLE=$sample/src/main 28 | SAMPLE_RES=$SAMPLE/resources/lib 29 | 30 | for a in "${ALL_ABIS[@]}"; do 31 | echo " ABI: $a" 32 | rm $SAMPLE_RES/$a/wrap.sh 33 | done 34 | 35 | rm $SAMPLE/jniLibs/*/libclang_rt.asan-*.so 36 | 37 | done 38 | 39 | echo "done." 40 | echo "NOTE: do not forget to reset "enable_asan" in the root build.gradle.kts." 41 | -------------------------------------------------------------------------------- /include/aap/core/AAPXSMidi2RecipientSession.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AAPXSMIDI2RECIPIENTSESSION_H 2 | #define AAP_CORE_AAPXSMIDI2RECIPIENTSESSION_H 3 | 4 | #include 5 | #include "../android-audio-plugin.h" 6 | #include "aap_midi2_helper.h" 7 | 8 | namespace aap { 9 | class AAPXSMidi2RecipientSession { 10 | aap_midi2_aapxs_parse_context aapxs_parse_context{}; 11 | 12 | std::function call_extension; 13 | public: 14 | AAPXSMidi2RecipientSession(); 15 | virtual ~AAPXSMidi2RecipientSession(); 16 | 17 | uint8_t* midi2_aapxs_data_buffer{nullptr}; 18 | uint8_t* midi2_aapxs_conversion_helper_buffer{nullptr}; 19 | 20 | void setExtensionCallback(std::function caller) { 21 | call_extension = caller; 22 | } 23 | 24 | void process(void* buffer); 25 | 26 | void 27 | addReply(void (*addMidi2Event)(AAPXSMidi2RecipientSession *, void *, int32_t), 28 | void* addMidi2EventUserData, 29 | uint8_t extensionUrid, 30 | const char* extensionUri, 31 | int32_t group, 32 | int32_t requestId, 33 | void* data, 34 | int32_t dataSize, 35 | int32_t opcode); 36 | }; 37 | } 38 | 39 | #endif //AAP_CORE_AAPXSMIDI2RECIPIENTSESSION_H 40 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/desktop/audio-plugin-host-desktop-internal.cpp: -------------------------------------------------------------------------------- 1 | #include "aap/android-audio-plugin.h" 2 | #include "aap/core/host/audio-plugin-host.h" 3 | #include "aap/core/host/plugin-client-system.h" 4 | #include "aap/core/host/desktop/audio-plugin-host-desktop.h" 5 | #include "aap/core/aapxs/extension-service.h" 6 | 7 | #if !ANDROID 8 | 9 | extern "C" 10 | int32_t createGui(std::string pluginId, int32_t instanceId, void* audioPluginView) { 11 | assert(false); // It is not implemented (not even suposed to be). 12 | } 13 | 14 | namespace aap { 15 | 16 | int32_t getMidiSettingsFromLocalConfig(std::string pluginId) { 17 | assert(false); // FIXME: implement 18 | } 19 | 20 | PluginClientSystem* PluginClientSystem::getInstance() { 21 | assert(false); // FIXME: implement 22 | } 23 | 24 | } // namespace aap 25 | 26 | AndroidAudioPlugin* aap_desktop_plugin_new( 27 | AndroidAudioPluginFactory *pluginFactory, 28 | const char* pluginUniqueId, 29 | int aapSampleRate, 30 | AndroidAudioPluginHost* host 31 | ) 32 | { 33 | assert(false); // FIXME: implement 34 | } 35 | 36 | void aap_desktop_plugin_delete( 37 | AndroidAudioPluginFactory *pluginFactory, 38 | AndroidAudioPlugin *instance) 39 | { 40 | assert(false); // FIXME: implement 41 | } 42 | 43 | AndroidAudioPluginFactory *GetDesktopAudioPluginFactoryClientBridge(aap::PluginClient* client) { 44 | return new AndroidAudioPluginFactory{aap_desktop_plugin_new, aap_desktop_plugin_delete, client}; 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/android/gen/include/aidl/org/androidaudioplugin/BpAudioPluginInterfaceCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is auto-generated. DO NOT MODIFY. 3 | * Using: /Users/atsushi/Library/Android/sdk/build-tools/35.0.1/aidl --lang=ndk -o /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen -h /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen/include -I /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/ /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterface.aidl /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.aidl 4 | */ 5 | #pragma once 6 | 7 | #include "aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.h" 8 | 9 | #include 10 | 11 | namespace aidl { 12 | namespace org { 13 | namespace androidaudioplugin { 14 | class BpAudioPluginInterfaceCallback : public ::ndk::BpCInterface { 15 | public: 16 | explicit BpAudioPluginInterfaceCallback(const ::ndk::SpAIBinder& binder); 17 | virtual ~BpAudioPluginInterfaceCallback(); 18 | 19 | ::ndk::ScopedAStatus hostExtension(int32_t in_instanceId, const std::string& in_uri, int32_t in_opcode) override; 20 | ::ndk::ScopedAStatus requestProcess(int32_t in_instanceId) override; 21 | }; 22 | } // namespace androidaudioplugin 23 | } // namespace org 24 | } // namespace aidl 25 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy to Maven Central 2 | 3 | on: workflow_dispatch 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: checkout 13 | uses: actions/checkout@v4 14 | with: 15 | submodules: recursive 16 | - name: set up JDK 17 17 | uses: actions/setup-java@v4 18 | with: 19 | java-version: 17 20 | distribution: temurin 21 | - name: publish 22 | run: ./gradlew --warning-mode=all publishToMavenCentral dokkaGenerate 23 | env: 24 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 25 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 26 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }} 27 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY_CONTENTS }} 28 | 29 | - name: Create Release 30 | uses: ncipollo/release-action@v1 31 | if: success() && startsWith(github.ref, 'refs/tags/') 32 | with: 33 | artifacts: ./*/build/outputs/aar/*.aar,samples/*/build/outputs/apk/debug/*.apk 34 | - name: deploy to GitHub Pages 35 | if: success() && startsWith(github.ref, 'refs/tags/') 36 | uses: JamesIves/github-pages-deploy-action@v4 37 | with: 38 | branch: gh-pages 39 | folder: androidaudioplugin/build/dokka/html 40 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/AudioPluginMidiSettings.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.hosting 2 | 3 | import android.content.Context 4 | import androidx.preference.PreferenceManager 5 | 6 | class AudioPluginMidiSettings { 7 | 8 | companion object { 9 | private const val settingsKeyBase = "midi-settings-flags" 10 | 11 | // keep these in sync with aap_parameters_mapping_policy in parameters extension. 12 | const val AAP_PARAMETERS_MAPPING_POLICY_NONE = 0 13 | const val AAP_PARAMETERS_MAPPING_POLICY_ACC = 1 14 | const val AAP_PARAMETERS_MAPPING_POLICY_CC = 2 15 | const val AAP_PARAMETERS_MAPPING_POLICY_SYSEX8 = 4 16 | const val AAP_PARAMETERS_MAPPING_POLICY_PROGRAM = 8 17 | const val AAP_PARAMETERS_MAPPING_POLICY_DEFAULT = AAP_PARAMETERS_MAPPING_POLICY_NONE 18 | 19 | @JvmStatic 20 | fun putMidiSettingsToSharedPreference(context: Context, pluginId: String, flags: Int) { 21 | val manager = PreferenceManager.getDefaultSharedPreferences(context) 22 | manager.edit() 23 | .putInt("$pluginId-$settingsKeyBase", flags) 24 | .apply() 25 | } 26 | 27 | // It can be invoked via JNI 28 | @JvmStatic 29 | fun getMidiSettingsFromSharedPreference(context: Context, pluginId: String) = 30 | PreferenceManager.getDefaultSharedPreferences(context).getInt("$pluginId-$settingsKeyBase", AAP_PARAMETERS_MAPPING_POLICY_DEFAULT) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/androidTest/java/org/androidaudioplugin/aapinstrumentsample/PluginTest.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.aapinstrumentsample 2 | 3 | import android.content.Context 4 | import androidx.test.core.app.ApplicationProvider 5 | import androidx.test.rule.ServiceTestRule 6 | import org.androidaudioplugin.AudioPluginServiceHelper 7 | import org.androidaudioplugin.androidaudioplugin.testing.AudioPluginServiceTesting 8 | import org.androidaudioplugin.hosting.AudioPluginHostHelper 9 | import org.junit.Assert 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | class PluginTest { 14 | private val applicationContext = ApplicationProvider.getApplicationContext() 15 | private val testing = AudioPluginServiceTesting(applicationContext) 16 | @get:Rule 17 | val serviceRule = ServiceTestRule() 18 | 19 | @Test 20 | fun testPluginInfo() { 21 | testing.testSinglePluginInformation { 22 | Assert.assertEquals("urn:org.androidaudioplugin/samples/aapinstrumentsample/InstrumentSample", it.pluginId) 23 | Assert.assertEquals("AAP Developers", it.developer) 24 | Assert.assertEquals(5, it.parameters.size) 25 | Assert.assertEquals(0, it.ports.size) 26 | } 27 | } 28 | 29 | @Test 30 | fun basicServiceOperationsForAllPlugins() { 31 | testing.basicServiceOperationsForAllPlugins() 32 | } 33 | 34 | @Test 35 | fun repeatDirectServiceOperations() { 36 | val pluginInfo = AudioPluginServiceHelper.getLocalAudioPluginService(applicationContext).plugins.first() 37 | 38 | for (i in 0 until 5) 39 | testing.testInstancingAndProcessing(pluginInfo) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/src/main/java/org/androidaudioplugin/ui/web/AAPServiceScriptInterface.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.ui.web 2 | 3 | import android.webkit.JavascriptInterface 4 | import org.androidaudioplugin.NativeLocalPluginInstance 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | * `AAPServiceScriptInterface` is an `AAPScriptInterface` which provides JavaScript API to interoperate 9 | * with the AudioPluginService via AudioPluginWevViewFactory. 10 | * 11 | * It should store the GUI events into the plugin's event input buffer, which should be - 12 | * 13 | * - "unified" with the audio plugin's process() event inputs, when the plugin is "activated" 14 | * - immediately processed when it is not at "activated" state. 15 | * 16 | * The NativeLocalPluginInstance is responsible to store those event inputs and deal with above. 17 | */ 18 | class AAPServiceScriptInterface(private val instance: NativeLocalPluginInstance) : AAPScriptInterface() { 19 | private val DEFAULT_BUFFER_SIZE = 8192 20 | 21 | override fun addEventUmpInput(data: ByteBuffer, size: Int) = 22 | instance.addEventUmpInput(data, size) 23 | 24 | override fun getPortBuffer(port: Int, data: ByteArray, length: Int) { 25 | TODO("Not yet implemented") 26 | } 27 | 28 | @get:JavascriptInterface 29 | override val portCount = instance.getPortCount() 30 | 31 | @JavascriptInterface 32 | override fun getPort(index: Int) = JsPortInformation(instance.getPort(index)) 33 | 34 | @get:JavascriptInterface 35 | override val parameterCount = instance.getParameterCount() 36 | 37 | @JavascriptInterface 38 | override fun getParameter(index: Int) = JsParameterInformation(instance.getParameter(index)) 39 | } -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/src/main/cpp/AAPMidiProcessor_android.cpp: -------------------------------------------------------------------------------- 1 | #include "AAPMidiProcessor_android.h" 2 | #include "aap/unstable/logging.h" 3 | 4 | namespace aap::midi { 5 | oboe::DataCallbackResult AAPMidiProcessorOboePAL::onAudioReady( 6 | oboe::AudioStream *audioStream, void *audioData, int32_t oboeNumFrames) { 7 | return (oboe::DataCallbackResult) owner->processAudioIO(audioData, oboeNumFrames); 8 | } 9 | 10 | int32_t AAPMidiProcessorOboePAL::setupStream() { 11 | 12 | builder.setDirection(oboe::Direction::Output) 13 | ->setPerformanceMode(oboe::PerformanceMode::LowLatency) 14 | ->setSharingMode(oboe::SharingMode::Shared) 15 | ->setFormat(oboe::AudioFormat::Float) 16 | ->setChannelCount(owner->getChannelCount()) 17 | // channels should be processed by plugin framework itself, but in case its output is mono 18 | // and the audio output is stereo in the end, there is no reason to refuse it. 19 | ->setChannelConversionAllowed(true) 20 | ->setFramesPerDataCallback(owner->getAAPFrameSize()) 21 | ->setDataCallback(callback.get()); 22 | 23 | return 0; 24 | } 25 | 26 | int32_t AAPMidiProcessorOboePAL::startStreaming() { 27 | oboe::Result result = builder.openStream(stream); 28 | if (result != oboe::Result::OK) { 29 | aap::aprintf("Failed to create Oboe stream: %s", oboe::convertToText(result)); 30 | return 1; 31 | } 32 | 33 | return (int32_t) stream->requestStart(); 34 | } 35 | 36 | int32_t AAPMidiProcessorOboePAL::stopStreaming() { 37 | 38 | // close Oboe stream. 39 | stream->stop(); 40 | stream->close(); 41 | stream.reset(); 42 | 43 | return 0; 44 | } 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioGraphNode.AudioDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioGraphNode.h" 2 | #include "AudioGraph.h" 3 | 4 | aap::AudioDeviceInputNode::~AudioDeviceInputNode() { 5 | getDevice()->stopCallback(); 6 | } 7 | 8 | void aap::AudioDeviceInputNode::processAudio(AudioBuffer *audioData, int32_t numFrames) { 9 | // copy current audio input data into `audioData` 10 | getDevice()->read(audioData, consumer_position, numFrames); 11 | // TODO: adjust ring buffer offset, not just simple adder. 12 | consumer_position += numFrames; 13 | } 14 | 15 | void aap::AudioDeviceInputNode::start() { 16 | if (!shouldSkip()) 17 | getDevice()->startCallback(); 18 | } 19 | 20 | void aap::AudioDeviceInputNode::pause() { 21 | if (!shouldSkip()) 22 | getDevice()->stopCallback(); 23 | } 24 | 25 | void aap::AudioDeviceInputNode::setPermissionGranted() { 26 | permission_granted = true; 27 | } 28 | 29 | bool aap::AudioDeviceInputNode::shouldSkip() { 30 | return getDevice()->isPermissionRequired() && !permission_granted; 31 | } 32 | 33 | //-------- 34 | 35 | aap::AudioDeviceOutputNode::~AudioDeviceOutputNode() { 36 | getDevice()->stopCallback(); 37 | } 38 | 39 | void aap::AudioDeviceOutputNode::processAudio(AudioBuffer *audioData, int32_t numFrames) { 40 | // copy `audioData` into current audio output buffer 41 | getDevice()->write(audioData, consumer_position, numFrames); 42 | // TODO: adjust ring buffer offset, not just simple adder. 43 | consumer_position += numFrames; 44 | } 45 | 46 | void aap::AudioDeviceOutputNode::start() { 47 | getDevice()->startCallback(); 48 | } 49 | 50 | void aap::AudioDeviceOutputNode::pause() { 51 | getDevice()->stopCallback(); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /publish-root.gradle: -------------------------------------------------------------------------------- 1 | // It is modified version of https://github.com/GetStream/stream-chat-android/blob/develop/scripts/ 2 | // Create variables with empty default values 3 | ext["ossrhUsername"] = '' 4 | ext["ossrhPassword"] = '' 5 | ext["sonatypeStagingProfileId"] = '' 6 | ext["signing.keyId"] = '' 7 | ext["signing.password"] = '' 8 | ext["signing.secretKeyRingFile"] = '' 9 | 10 | File secretPropsFile = project.rootProject.file('local.properties') 11 | if (secretPropsFile.exists()) { 12 | // Read local.properties file first if it exists 13 | Properties p = new Properties() 14 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } 15 | p.each { name, value -> ext[name] = value } 16 | } 17 | // Use system environment variables 18 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') ?: ext["ossrhUsername"] 19 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') ?: ext["ossrhPassword"] 20 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') ?: ext["sonatypeStagingProfileId"] 21 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') ?: ext["signing.keyId"] 22 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD') ?: ext["signing.password"] 23 | ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE') ?: ext["signing.secretKeyRingFile"] 24 | 25 | // Set up Sonatype repository 26 | /* 27 | nexusPublishing { 28 | repositories { 29 | sonatype { 30 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 31 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 32 | stagingProfileId = sonatypeStagingProfileId 33 | username = ossrhUsername 34 | password = ossrhPassword 35 | } 36 | } 37 | } 38 | */ 39 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6.0) 2 | 3 | project(aappluginsamples LANGUAGES C CXX) 4 | 5 | find_package (androidaudioplugin REQUIRED CONFIG) 6 | 7 | add_library(aap-instrument-sample SHARED 8 | "instrument/aap-instrument-sample.cpp" 9 | "instrument/ayumi.c" 10 | ) 11 | add_library(aap-effect-sample SHARED 12 | "effect/aap-effect-sample.cpp" 13 | ) 14 | add_library(aap-xs-sample SHARED 15 | "aapxs/test-extension-service.cpp" 16 | "aapxs/aap-xs-sample.cpp" 17 | ) 18 | target_include_directories(aap-xs-sample PRIVATE 19 | "aapxs/include" 20 | ) 21 | list(APPEND target-plugins aap-instrument-sample aap-effect-sample aap-xs-sample) 22 | 23 | foreach(TheTarget ${target-plugins}) 24 | message("TheTarget: ${TheTarget}") 25 | 26 | target_include_directories(${TheTarget} PRIVATE 27 | "${CMAKE_CURRENT_LIST_DIR}/../../../../../include" 28 | "${CMAKE_CURRENT_LIST_DIR}/../../../../../external/cmidi2" 29 | ) 30 | 31 | target_compile_options(${TheTarget} PRIVATE 32 | -Wall 33 | -Wextra-semi 34 | -Wshadow 35 | -Wshadow-field 36 | ) 37 | 38 | target_link_libraries(${TheTarget} PRIVATE 39 | android 40 | log 41 | androidaudioplugin::androidaudioplugin 42 | ) 43 | 44 | # You can set it via build.gradle 45 | if (${AAP_ENABLE_ASAN}) 46 | target_compile_options (${TheTarget} PRIVATE 47 | -fsanitize=address -fno-omit-frame-pointer 48 | ) 49 | target_link_options(${TheTarget} PRIVATE 50 | -fsanitize=address 51 | ) 52 | target_set_properties(${TheTarget} PRIVATE 53 | LINK_FLAGS -fsanitize=address 54 | ) 55 | endif() 56 | endforeach () 57 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/src/main/java/org/androidaudioplugin/ui/web/AAPClientScriptInterface.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.ui.web 2 | 3 | import android.webkit.JavascriptInterface 4 | import org.androidaudioplugin.hosting.NativeRemotePluginInstance 5 | import java.nio.ByteBuffer 6 | 7 | /** 8 | * `AAPClientScriptInterface` is a reference `AAPScriptInterface` implementation which provides JavaScript API 9 | * to interoperate with the AudioPluginInstance. It is intended to be used for in-host-process UI. 10 | * 11 | * It should store the GUI events into the plugin's event input buffer, which should be - 12 | * 13 | * - "unified" with the audio plugin's process() event inputs, when the plugin is "activated" 14 | * - immediately processed when it is not at "activated" state. 15 | * 16 | * The AudioPluginInstance is responsible to store those event inputs and deal with above. 17 | */ 18 | @Suppress("unused") 19 | class AAPClientScriptInterface(private val instance: NativeRemotePluginInstance) : AAPScriptInterface() { 20 | override fun addEventUmpInput(data: ByteBuffer, size: Int) { 21 | instance.addEventUmpInput(data, size) 22 | } 23 | 24 | override fun getPortBuffer(port: Int, data: ByteArray, length: Int) = 25 | instance.getPortBuffer(port, ByteBuffer.wrap(data, 0, length), length) 26 | 27 | // It seems that those JavaScriptInterface attributes are also needed for overriden members... 28 | 29 | @get:JavascriptInterface 30 | override val portCount = instance.getPortCount() 31 | 32 | @JavascriptInterface 33 | override fun getPort(index: Int) = JsPortInformation(instance.getPort(index)) 34 | 35 | @get:JavascriptInterface 36 | override val parameterCount = instance.getParameterCount() 37 | 38 | @JavascriptInterface 39 | override fun getParameter(id: Int) = JsParameterInformation(instance.getParameter(id)) 40 | } -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/cpp/instrument/ayumi.h: -------------------------------------------------------------------------------- 1 | /* Author: Peter Sovietov */ 2 | 3 | #ifndef AYUMI_H 4 | #define AYUMI_H 5 | 6 | enum { 7 | TONE_CHANNELS = 3, 8 | DECIMATE_FACTOR = 8, 9 | FIR_SIZE = 192, 10 | DC_FILTER_SIZE = 1024 11 | }; 12 | 13 | struct tone_channel { 14 | int tone_period; 15 | int tone_counter; 16 | int tone; 17 | int t_off; 18 | int n_off; 19 | int e_on; 20 | int volume; 21 | double pan_left; 22 | double pan_right; 23 | }; 24 | 25 | struct interpolator { 26 | double c[4]; 27 | double y[4]; 28 | }; 29 | 30 | struct dc_filter { 31 | double sum; 32 | double delay[DC_FILTER_SIZE]; 33 | }; 34 | 35 | struct ayumi { 36 | struct tone_channel channels[TONE_CHANNELS]; 37 | int noise_period; 38 | int noise_counter; 39 | int noise; 40 | int envelope_counter; 41 | int envelope_period; 42 | int envelope_shape; 43 | int envelope_segment; 44 | int envelope; 45 | const double* dac_table; 46 | double step; 47 | double x; 48 | struct interpolator interpolator_left; 49 | struct interpolator interpolator_right; 50 | double fir_left[FIR_SIZE * 2]; 51 | double fir_right[FIR_SIZE * 2]; 52 | int fir_index; 53 | struct dc_filter dc_left; 54 | struct dc_filter dc_right; 55 | int dc_index; 56 | double left; 57 | double right; 58 | }; 59 | 60 | int ayumi_configure(struct ayumi* ay, int is_ym, double clock_rate, int sr); 61 | void ayumi_set_pan(struct ayumi* ay, int index, double pan, int is_eqp); 62 | void ayumi_set_tone(struct ayumi* ay, int index, int period); 63 | void ayumi_set_noise(struct ayumi* ay, int period); 64 | void ayumi_set_mixer(struct ayumi* ay, int index, int t_off, int n_off, int e_on); 65 | void ayumi_set_volume(struct ayumi* ay, int index, int volume); 66 | void ayumi_set_envelope(struct ayumi* ay, int period); 67 | void ayumi_set_envelope_shape(struct ayumi* ay, int shape); 68 | void ayumi_process(struct ayumi* ay); 69 | void ayumi_remove_dc(struct ayumi* ay); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /include/aap/core/android/android-application-context.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_APPLICATION_CONTEXT_H 2 | #define ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_APPLICATION_CONTEXT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace aap { 11 | 12 | /** 13 | * It is a utility function that is supposed to be called at androidx.startup:startup-runtime 14 | * (or plain old ContentProvider#onCreate()) to ensure that app context can be accessible 15 | * by further native code. 16 | * 17 | * @param env JNIEnv that is passed by androidx.startup.Initializer#create() overrides. 18 | * @param applicationContext the app context to store. We will add GlobalRef. 19 | */ 20 | void set_application_context(JNIEnv *env, jobject applicationContext); 21 | /** 22 | * (Currently not in use as there is no "always graceful" app quit on Android platform.) 23 | * @param env 24 | */ 25 | void unset_application_context(JNIEnv *env); 26 | 27 | class NonRealtimeLoopRunner; 28 | NonRealtimeLoopRunner* get_non_rt_loop_runner(); 29 | 30 | void prepare_non_rt_event_looper(); 31 | 32 | [[noreturn]] void start_non_rt_event_looper(); 33 | 34 | void stop_non_rt_event_looper(); 35 | 36 | JavaVM *get_android_jvm(); 37 | 38 | /** 39 | * Returns the android application context that was set by set_application_context(). 40 | * @return the application context which was set in prior, or nullptr if it was nto set. 41 | */ 42 | jobject get_android_application_context(); 43 | 44 | /** 45 | * Returns the asset manager that is bound to the application context. It is instantiated via 46 | * getAssetManager() through JNI, created a global ref and then returned. 47 | * @param env 48 | * @return the asset manager, of AAssetManager type. 49 | */ 50 | AAssetManager *get_android_asset_manager(JNIEnv* env); 51 | 52 | } 53 | 54 | #endif /* ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_APPLICATION_CONTEXT_H */ 55 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose-app/src/main/java/org/androidaudioplugin/ui/compose/app/PluginManagerTheme.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.ui.compose.app 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Typography 8 | import androidx.compose.material3.darkColorScheme 9 | import androidx.compose.material3.dynamicDarkColorScheme 10 | import androidx.compose.material3.dynamicLightColorScheme 11 | import androidx.compose.material3.lightColorScheme 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.SideEffect 14 | import androidx.compose.ui.graphics.toArgb 15 | import androidx.compose.ui.platform.LocalContext 16 | import androidx.compose.ui.platform.LocalView 17 | import androidx.core.view.WindowCompat 18 | 19 | @Composable 20 | fun PluginManagerTheme( 21 | darkTheme: Boolean = isSystemInDarkTheme(), 22 | // Dynamic color is available on Android 12+ 23 | dynamicColor: Boolean = true, 24 | content: @Composable () -> Unit 25 | ) { 26 | val colorScheme = when { 27 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 28 | val context = LocalContext.current 29 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 30 | } 31 | 32 | darkTheme -> darkColorScheme() 33 | else -> lightColorScheme() 34 | } 35 | val view = LocalView.current 36 | if (!view.isInEditMode) { 37 | SideEffect { 38 | val window = (view.context as Activity).window 39 | window.statusBarColor = colorScheme.primary.toArgb() 40 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 41 | } 42 | } 43 | 44 | MaterialTheme( 45 | colorScheme = colorScheme, 46 | typography = Typography(), 47 | content = content 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.1) 2 | 3 | project(aapmidideviceservice LANGUAGES CXX) 4 | 5 | set (TOPDIR "../../../..") 6 | 7 | # List of sources. Android build has some additional sources. 8 | set (aapmidideviceservice_SOURCES 9 | "aapmidideviceservice-jni.cpp" 10 | "AAPMidiProcessor.cpp" 11 | "AAPMidiProcessor_android.cpp" 12 | "zix/ring.cpp" 13 | ) 14 | 15 | add_library ( # Specifies the name of the library. 16 | aapmidideviceservice 17 | 18 | # Sets the library as a shared library. 19 | SHARED 20 | 21 | # Provides a relative path to your source file(s). 22 | ${aapmidideviceservice_SOURCES} 23 | ) 24 | 25 | find_package(oboe REQUIRED CONFIG) 26 | find_package(androidaudioplugin REQUIRED CONFIG) 27 | 28 | set (aapmidideviceservice_INCLUDES 29 | . 30 | ${TOPDIR}/external/cmidi2 31 | ${TOPDIR}/include 32 | ) 33 | 34 | target_include_directories (aapmidideviceservice 35 | PRIVATE 36 | ${aapmidideviceservice_INCLUDES} 37 | ) 38 | 39 | target_compile_options (aapmidideviceservice 40 | PRIVATE 41 | -std=c++17 42 | -Wall 43 | -Wshadow 44 | # uncomment this if you want to enable AddressSanitizer 45 | #-fsanitize=address -fno-omit-frame-pointer 46 | -DHAVE_MLOCK=1 47 | ) 48 | 49 | target_link_libraries (aapmidideviceservice 50 | PRIVATE 51 | android 52 | log 53 | binder_ndk 54 | oboe::oboe 55 | androidaudioplugin::androidaudioplugin 56 | ) 57 | 58 | target_compile_definitions (aapmidideviceservice 59 | PUBLIC 60 | ANDROID=${ANDROID} 61 | ) 62 | 63 | 64 | # You can set it via build.gradle 65 | if (${AAP_ENABLE_ASAN}) 66 | target_compile_options (aapmidideviceservice 67 | PUBLIC 68 | -fsanitize=address -fno-omit-frame-pointer 69 | ) 70 | 71 | target_link_options(aapmidideviceservice 72 | PUBLIC 73 | -fsanitize=address 74 | ) 75 | 76 | set_target_properties(aapmidideviceservice 77 | PROPERTIES LINK_FLAGS -fsanitize=address 78 | ) 79 | endif() 80 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginServiceHelper.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | import android.content.ComponentName 4 | import android.content.Context 5 | import android.view.View 6 | import org.androidaudioplugin.hosting.AudioPluginHostHelper 7 | 8 | // It is used only by AudioPluginService and some plugin extensions (such as AudioPluginLv2ServiceExtension) to process client (plugin host) requests. 9 | object AudioPluginServiceHelper { 10 | fun getLocalAudioPluginService(context: Context) = 11 | AudioPluginHostHelper.queryAudioPluginServices(context) 12 | .first { svc -> svc.packageName == context.packageName } 13 | 14 | @JvmStatic 15 | fun getForegroundServiceType(context: Context, packageName: String, serviceClassName: String) = 16 | context.packageManager.getServiceInfo(ComponentName(packageName, serviceClassName), 0).foregroundServiceType 17 | 18 | @JvmStatic 19 | external fun getServiceInstance(pluginId: String): Long 20 | 21 | // It is used by AudioPluginViewService (which makes use of SurfaceControlViewHost). 22 | @JvmStatic 23 | fun createNativeView(context: Context, pluginId: String, instanceId: Int): View { 24 | val pluginInfo = getLocalAudioPluginService(context).plugins.firstOrNull { it.pluginId == pluginId } 25 | ?: throw AudioPluginException("Specified plugin '$pluginId' was not found") 26 | val factoryClassName = pluginInfo.uiViewFactory 27 | ?: throw AudioPluginException("'ui-view-factory' attribute is not specified in aap_metadata.xml") 28 | val cls = Class.forName(factoryClassName) 29 | if (!AudioPluginViewFactory::class.java.isAssignableFrom(cls)) 30 | throw AudioPluginException("The class '$factoryClassName' specified by 'ui-view-factory' attribute in aap_metadata.xml must be derived from AudioPluginViewFactory.") 31 | val factory = cls.getConstructor().newInstance() as AudioPluginViewFactory 32 | return factory.createView(context, pluginId, instanceId) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /include/aap/core/AAPXSMidi2InitiatorSession.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AAPXSMIDI2INITIATORSESSION_H 2 | #define AAP_CORE_AAPXSMIDI2INITIATORSESSION_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "aap_midi2_helper.h" 9 | #include "aap/aapxs.h" 10 | 11 | namespace aap { 12 | class AAPXSMidi2InitiatorSession; 13 | const size_t MAX_PENDING_CALLBACKS = UINT8_MAX; 14 | 15 | typedef void (*add_midi2_event_func) (AAPXSMidi2InitiatorSession* session, void* userData, int32_t messageSize); 16 | 17 | class AAPXSMidi2InitiatorSession { 18 | 19 | struct CallbackUnit { 20 | uint32_t request_id; 21 | aapxs_completion_callback func; 22 | void* data; 23 | }; 24 | 25 | int32_t midi_buffer_size; 26 | aap_midi2_aapxs_parse_context aapxs_parse_context{}; 27 | std::function handle_reply; 28 | CallbackUnit pending_callbacks[MAX_PENDING_CALLBACKS]; 29 | 30 | public: 31 | AAPXSMidi2InitiatorSession(int32_t midiBufferSize); 32 | ~AAPXSMidi2InitiatorSession(); 33 | 34 | uint8_t *aapxs_rt_midi_buffer{nullptr}; 35 | uint8_t *aapxs_rt_conversion_helper_buffer{nullptr}; 36 | 37 | void setReplyHandler(std::function handleReply) { 38 | handle_reply = handleReply; 39 | } 40 | 41 | void addSession(add_midi2_event_func addMidi2Event, 42 | void* addMidi2EventUserData, 43 | int32_t group, 44 | int32_t requestId, 45 | uint8_t urid, 46 | const char* uri, 47 | void* data, 48 | int32_t dataSize, 49 | int32_t opcode); 50 | 51 | void addSession(add_midi2_event_func addMidi2Event, void* addMidi2EventUserData, AAPXSRequestContext* request); 52 | 53 | void completeSession(void* buffer, void* pluginOrHost); 54 | }; 55 | } 56 | 57 | #endif //AAP_CORE_AAPXSMIDI2INITIATORSESSION_H 58 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterface.aidl: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin; 2 | import org.androidaudioplugin.AudioPluginInterfaceCallback; 3 | 4 | interface AudioPluginInterface { 5 | const int AAP_BINDER_ERROR_UNEXPECTED_INSTANCE_ID = 1; 6 | const int AAP_BINDER_ERROR_CREATE_INSTANCE_FAILED = 2; 7 | const int AAP_BINDER_ERROR_UNEXPECTED_FEATURE_URI = 3; 8 | const int AAP_BINDER_ERROR_SHARED_MEMORY_EXTENSION = 10; 9 | const int AAP_BINDER_ERROR_MMAP_FAILED = 11; 10 | const int AAP_BINDER_ERROR_MMAP_NULL_RETURN = 12; 11 | const int AAP_BINDER_ERROR_INVALID_SHARED_MEMORY_FD = 20; 12 | const int AAP_BINDER_ERROR_CALLBACK_ALREADY_SET = 30; 13 | const int AAP_BINDER_ERROR_NULL_CALLBACK = 31; 14 | 15 | // ServiceConnection operations 16 | void setCallback(in AudioPluginInterfaceCallback callback); 17 | 18 | // Instance operations 19 | 20 | int beginCreate(String pluginId, int sampleRate); 21 | // add an AAP extension, with a URI, with an optional shared memory FD and its size dedicated to it. 22 | // For consistency in the future, the order of calls to `addExtension()` should be considered as significant 23 | // (i.e. the extension list is ordered). Any extension that could affect other extensions should be added earlier. 24 | void addExtension(int instanceID, String uri, in ParcelFileDescriptor sharedMemoryFD, int size); 25 | void endCreate(int instanceID); 26 | 27 | boolean isPluginAlive(int instanceID); 28 | 29 | void extension(int instanceID, String uri, int opcode); 30 | 31 | // Indicates thaat it begins "prepare" step, to plugin. 32 | // When received, plugin finishes port configuration. 33 | // Port configuration by extensions should be done before this call. 34 | void beginPrepare(int instanceID); 35 | void prepareMemory(int instanceID, int shmFDIndex, in ParcelFileDescriptor sharedMemoryFD); 36 | void endPrepare(int instanceID, int frameCount); 37 | void activate(int instanceID); 38 | void process(int instanceID, int frameCount, int timeoutInNanoseconds); 39 | void deactivate(int instanceID); 40 | 41 | void destroy(int instanceID); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /include/aap/ext/urid.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_URID_H_INCLUDED 2 | #define AAP_URID_H_INCLUDED 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "../android-audio-plugin.h" 9 | #include "stdint.h" 10 | 11 | /* 12 | * URID extension provides host a way to tell the target plugin that an integer is mapped to 13 | * a URI so that the integer could be used as an alternate identifier. Such an integer can be 14 | * used in realtime context because there will be no need to perform O(n) comparisons. 15 | * 16 | * LV2 has the same concept. The API in AAP is similar but less flexible: we do not support unmapping. 17 | * 18 | * URID does not assure that the integer is the only value that is mapped to the URI: 19 | * Multiple URIDs could be mapped to the same URI. 20 | * 21 | * Due to the design limitation in AAPXS, the value range for a URID is 1 to 255: 22 | * 0 is regarded as unmapped, in that case implementations will perform O(n) URI mapping. 23 | * 24 | * It should be noted that a host application does not have full control on the mappings. 25 | * The reference implementation could give arbitrary number of mappings. 26 | * Also note that they are also consumed by host implementation (service loader), not just the plugin. 27 | * 28 | */ 29 | 30 | #define AAP_URID_EXTENSION_URI "urn://androidaudioplugin.org/extensions/urid/v3" 31 | 32 | typedef struct aap_urid_extension_t { 33 | /* 34 | * `aapxs_context` is an opaque pointer assigned and used by AAPXS hosting implementation (libandroidaudioplugin). 35 | * Neither of plugin developer (extension user) or extension developers is supposed to touch it. 36 | * this struct is instantiated per extension in a plugin instance. 37 | */ 38 | void *aapxs_context; 39 | 40 | /* 41 | * Add a mapping from URI to URID. 42 | * It is an error if this function is called once it started "beginPrepare()" call. 43 | */ 44 | void (*map) (aap_urid_extension_t* ext, AndroidAudioPlugin* plugin, uint8_t urid, const char* uri); 45 | } aap_urid_extension_t; 46 | 47 | #ifdef __cplusplus 48 | } // extern "C" 49 | #endif 50 | 51 | #endif // AAP_URID_H_INCLUDED 52 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/android/gen/include/aidl/org/androidaudioplugin/BnAudioPluginInterfaceCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is auto-generated. DO NOT MODIFY. 3 | * Using: /Users/atsushi/Library/Android/sdk/build-tools/35.0.1/aidl --lang=ndk -o /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen -h /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen/include -I /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/ /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterface.aidl /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.aidl 4 | */ 5 | #pragma once 6 | 7 | #include "aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.h" 8 | 9 | #include 10 | #include 11 | 12 | #ifndef __BIONIC__ 13 | #ifndef __assert2 14 | #define __assert2(a,b,c,d) ((void)0) 15 | #endif 16 | #endif 17 | 18 | namespace aidl { 19 | namespace org { 20 | namespace androidaudioplugin { 21 | class BnAudioPluginInterfaceCallback : public ::ndk::BnCInterface { 22 | public: 23 | BnAudioPluginInterfaceCallback(); 24 | virtual ~BnAudioPluginInterfaceCallback(); 25 | protected: 26 | ::ndk::SpAIBinder createBinder() override; 27 | private: 28 | }; 29 | class IAudioPluginInterfaceCallbackDelegator : public BnAudioPluginInterfaceCallback { 30 | public: 31 | explicit IAudioPluginInterfaceCallbackDelegator(const std::shared_ptr &impl) : _impl(impl) { 32 | } 33 | 34 | ::ndk::ScopedAStatus hostExtension(int32_t in_instanceId, const std::string& in_uri, int32_t in_opcode) override { 35 | return _impl->hostExtension(in_instanceId, in_uri, in_opcode); 36 | } 37 | ::ndk::ScopedAStatus requestProcess(int32_t in_instanceId) override { 38 | return _impl->requestProcess(in_instanceId); 39 | } 40 | protected: 41 | private: 42 | std::shared_ptr _impl; 43 | }; 44 | 45 | } // namespace androidaudioplugin 46 | } // namespace org 47 | } // namespace aidl 48 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-compose/src/main/java/org/androidaudioplugin/ui/compose/PluginParameterEnumSelector.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.ui.compose 2 | 3 | import androidx.compose.foundation.layout.width 4 | import androidx.compose.material3.DropdownMenuItem 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.ExperimentalMaterial3Api 7 | import androidx.compose.material3.ExposedDropdownMenuBox 8 | import androidx.compose.material3.ExposedDropdownMenuDefaults 9 | import androidx.compose.material3.TextField 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.getValue 12 | import androidx.compose.runtime.mutableStateOf 13 | import androidx.compose.runtime.remember 14 | import androidx.compose.runtime.setValue 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.unit.dp 17 | import org.androidaudioplugin.ParameterInformation 18 | 19 | @OptIn(ExperimentalMaterial3Api::class) 20 | @Composable 21 | fun PluginParameterEnumSelector(value: Float, 22 | enumerations: List, 23 | onValueChange: (value: Double) -> Unit) { 24 | val v = value.toDouble() 25 | var enumExpanded by remember { mutableStateOf(false) } 26 | ExposedDropdownMenuBox( 27 | expanded = enumExpanded, onExpandedChange = { enumExpanded = it }) { 28 | val name = (enumerations.firstOrNull { it.value == v } ?: enumerations.first()).name 29 | TextField( 30 | name, onValueChange = {}, readOnly = true, 31 | trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = enumExpanded) }, 32 | modifier = Modifier.menuAnchor() 33 | ) 34 | ExposedDropdownMenu( 35 | expanded = enumExpanded, 36 | onDismissRequest = { enumExpanded = false }) { 37 | enumerations.forEach { 38 | DropdownMenuItem(text = { Text(it.name) }, 39 | onClick = { 40 | if (enumExpanded) 41 | onValueChange(it.value) 42 | enumExpanded = !enumExpanded 43 | }) 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/hosting/PluginInformation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | aap::PluginInformation::PluginInformation(bool isOutProcess, const char* pluginPackageName, 5 | const char* pluginLocalName, const char* displayName, 6 | const char* developerName, const char* versionString, 7 | const char* pluginID, const char* sharedLibraryFilename, 8 | const char* libraryEntrypoint, const char* metadataFullPath, 9 | const char* primaryCategory, const char* uiViewFactory, 10 | const char* uiActivity, const char* uiWeb) 11 | : is_out_process(isOutProcess), 12 | plugin_package_name(pluginPackageName), 13 | plugin_local_name(pluginLocalName), 14 | display_name(displayName), 15 | developer_name(developerName), 16 | version(versionString), 17 | shared_library_filename(sharedLibraryFilename), 18 | library_entrypoint(libraryEntrypoint), 19 | plugin_id(pluginID), 20 | metadata_full_path(metadataFullPath), 21 | primary_category(primaryCategory), 22 | ui_view_factory(uiViewFactory), 23 | ui_activity(uiActivity), 24 | ui_web(uiWeb) 25 | { 26 | struct tm epoch{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 27 | last_info_updated_unixtime_milliseconds = (int64_t) (1000.0 * difftime(time(nullptr), mktime(&epoch))); 28 | 29 | char *cp; 30 | size_t len = (size_t) snprintf(nullptr, 0, "%s+%s+%s", display_name.c_str(), plugin_id.c_str(), version.c_str()); 31 | cp = (char*) calloc(len, 1); 32 | snprintf(cp, len, "%s+%s+%s", display_name.c_str(), plugin_id.c_str(), version.c_str()); 33 | identifier_string = cp; 34 | free(cp); 35 | } 36 | 37 | aap::PluginExtensionInformation aap::PluginInformation::getExtension(const char *uri) const { 38 | for (int i = 0, n = getNumExtensions(); i < n; i++) { 39 | auto ext = getExtension(i); 40 | if (strcmp(ext.uri.c_str(), uri) == 0) 41 | return ext; 42 | } 43 | return {}; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /androidaudioplugin-testing/src/main/java/org/androidaudioplugin/androidaudioplugin/testing/AudioPluginMidiDeviceServiceTesting.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.androidaudioplugin.testing 2 | 3 | import android.content.Context 4 | import android.media.midi.MidiDeviceInfo 5 | import android.media.midi.MidiManager 6 | import kotlinx.coroutines.CompletableDeferred 7 | import kotlinx.coroutines.delay 8 | import kotlinx.coroutines.runBlocking 9 | import kotlinx.coroutines.sync.Mutex 10 | 11 | class AudioPluginMidiDeviceServiceTesting(private val applicationContext: Context) { 12 | 13 | val simpleNoteOn = arrayOf(0x90, 0x40, 10).map { it.toByte() }.toByteArray() 14 | val simpleNoteOff = arrayOf(0x80, 0x40, 0).map { it.toByte() }.toByteArray() 15 | 16 | fun basicServiceOperations(deviceName: String) { 17 | val midiManager = applicationContext.getSystemService(Context.MIDI_SERVICE) as MidiManager 18 | val deviceInfo = midiManager.devices.first { d -> d.properties.getString(MidiDeviceInfo.PROPERTY_NAME) == deviceName } 19 | 20 | runBlocking { 21 | val deferred = CompletableDeferred() 22 | 23 | midiManager.openDevice(deviceInfo, { device -> 24 | var error: Exception? = null 25 | runBlocking { 26 | try { 27 | val input = device.openInputPort(deviceInfo.ports.first { p -> 28 | p.type == MidiDeviceInfo.PortInfo.TYPE_INPUT 29 | }.portNumber) 30 | assert(input != null) 31 | input.send(simpleNoteOn, 0, simpleNoteOn.size) 32 | delay(50) 33 | input.send(simpleNoteOff, 0, simpleNoteOn.size) 34 | 35 | input.close() 36 | } catch (ex: Exception) { 37 | error = ex 38 | } finally { 39 | deferred.complete(Unit) 40 | } 41 | if (error != null) 42 | throw AssertionError("MIDI output test failure", error) 43 | 44 | device.close() 45 | } 46 | }, null) 47 | 48 | deferred.await() 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AAPMidiEventTranslator.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AAPMIDIEVENTTRANSLATOR_H 2 | #define AAP_CORE_AAPMIDIEVENTTRANSLATOR_H 3 | 4 | #include 5 | #include "LocalDefinitions.h" 6 | 7 | namespace aap { 8 | 9 | class AAPMidiEventTranslator { 10 | RemotePluginInstance* instance; 11 | uint8_t preset_urid{aap::xs::UridMapping::UNMAPPED_URID}; 12 | // used when we need MIDI1<->UMP translation. 13 | uint8_t* translation_buffer{nullptr}; 14 | int32_t midi_buffer_size; 15 | 16 | uint8_t* conversion_helper_buffer{nullptr}; 17 | int32_t conversion_helper_buffer_size; 18 | 19 | // MIDI protocol type of the messages it receives via JNI 20 | int32_t receiver_midi_transport_protocol; 21 | int32_t current_mapping_policy{AAP_PARAMETERS_MAPPING_POLICY_NONE}; 22 | 23 | int32_t detectEndpointConfigurationMessage(uint8_t* bytes, size_t offset, size_t length); 24 | 25 | public: 26 | explicit AAPMidiEventTranslator(RemotePluginInstance* instance, int32_t midiBufferSize = AAP_MANAGER_MIDI_BUFFER_SIZE, int32_t initialMidiTransportProtocol = 2/*CMIDI2_PROTOCOL_TYPE_MIDI2*/); 27 | ~AAPMidiEventTranslator(); 28 | 29 | void setPlugin(RemotePluginInstance* pluginInstance); 30 | 31 | int32_t translateMidiEvent(uint8_t *data, int32_t length); 32 | 33 | // If needed, translate MIDI1 bytestream to MIDI2 UMP. 34 | // returns 0 if translation did not happen. Otherwise return the size of translated buffer in translation_buffer. 35 | // 36 | // Note that it will update `bytes` contents and implicitly requires enough space for conversion. 37 | size_t translateMidiBufferIfNeeded(uint8_t* bytes, size_t offset, size_t length); 38 | 39 | uint8_t* getTranslationBuffer() { return translation_buffer; } 40 | 41 | // If needed, process MIDI mapping for parameters (CC/ACC/PNACC to sysex8) and presets (program). 42 | // returns 0 if translation did not happen. Otherwise return the size of translated buffer in translation_buffer. 43 | size_t runThroughMidi2UmpForMidiMapping(uint8_t* bytes, size_t offset, size_t length); 44 | }; 45 | } 46 | 47 | #endif //AAP_CORE_AAPMIDIEVENTTRANSLATOR_H 48 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5.1) 2 | 3 | project(androidaudioplugin-manager LANGUAGES CXX) 4 | 5 | # List of sources. Android build has some additional sources. 6 | set (androidaudioplugin-manager_SOURCES 7 | #zix/ring.cpp 8 | AudioBuffer.cpp 9 | AudioDevice.cpp 10 | AudioDeviceManager.cpp 11 | OboeAudioDeviceManager.cpp 12 | VirtualAudioDeviceManager.cpp 13 | AudioGraph.cpp 14 | AudioGraphNode.AudioDevice.cpp 15 | AudioGraphNode.DataSource.cpp 16 | AudioGraphNode.Plugin.cpp 17 | AudioGraphNode.Midi.cpp 18 | AAPMidiEventTranslator.cpp 19 | PluginPlayer.cpp 20 | PluginPlayerConfiguration.cpp 21 | JNI.cpp 22 | ) 23 | 24 | if (ANDROID) 25 | set (androidaudioplugin-manager_SOURCES 26 | ${androidaudioplugin-manager_SOURCES} 27 | ) 28 | else (ANDROID) 29 | set (androidaudioplugin-manager_SOURCES 30 | ${androidaudioplugin-manager_SOURCES} 31 | ) 32 | endif (ANDROID) 33 | 34 | find_package(oboe REQUIRED CONFIG) 35 | find_package(androidaudioplugin REQUIRED CONFIG) 36 | 37 | add_library (androidaudioplugin-manager 38 | SHARED 39 | ${androidaudioplugin-manager_SOURCES} 40 | ) 41 | 42 | target_compile_options (androidaudioplugin-manager 43 | PRIVATE 44 | -std=c++17 -Wall -Wshadow 45 | -Werror=unguarded-availability 46 | ) 47 | 48 | target_compile_definitions (androidaudioplugin-manager 49 | PUBLIC 50 | __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__=1 51 | ) 52 | 53 | target_include_directories (androidaudioplugin-manager 54 | PRIVATE 55 | "../../../../include/" 56 | "../../../../external/cmidi2" 57 | "../../../../external/choc" 58 | ) 59 | 60 | if (ANDROID) 61 | target_include_directories (androidaudioplugin-manager 62 | PRIVATE 63 | ) 64 | 65 | target_link_libraries (androidaudioplugin-manager 66 | androidaudioplugin::androidaudioplugin 67 | oboe::oboe 68 | log 69 | android 70 | ) 71 | endif (ANDROID) 72 | 73 | # You can set it via build.gradle. 74 | if (${AAP_ENABLE_ASAN}) 75 | target_compile_options (androidaudioplugin-manager 76 | PUBLIC 77 | -fsanitize=address -fno-omit-frame-pointer 78 | ) 79 | 80 | target_link_options(androidaudioplugin-manager 81 | PUBLIC 82 | -fsanitize=address 83 | ) 84 | 85 | set_target_properties(androidaudioplugin-manager 86 | PROPERTIES LINK_FLAGS -fsanitize=address 87 | ) 88 | endif() 89 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.dokka) 5 | alias(libs.plugins.vanniktech.maven.publish) 6 | signing 7 | } 8 | 9 | apply { from ("../common.gradle") } 10 | version = libs.versions.aap.core.get() 11 | 12 | android { 13 | namespace = "org.androidaudioplugin.ui.web" 14 | ext["description"] = "AndroidAudioPlugin - UI (Web)" 15 | 16 | buildTypes { 17 | release { 18 | isMinifyEnabled = false 19 | proguardFiles (getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation (project(":androidaudioplugin")) 26 | implementation (libs.androidx.core.ktx) 27 | implementation (libs.kotlin.stdlib.jdk8) 28 | implementation (libs.androidx.appcompat) 29 | implementation (libs.webkit) 30 | implementation (libs.ktmidi) 31 | 32 | androidTestImplementation (libs.junit) 33 | androidTestImplementation (libs.test.ext.junit) 34 | androidTestImplementation (libs.test.espresso.core) 35 | } 36 | 37 | 38 | val gitProjectName = "aap-core" 39 | val packageName = project.name 40 | val packageDescription = android.ext["description"].toString() 41 | // my common settings 42 | val packageUrl = "https://github.com/atsushieno/$gitProjectName" 43 | val licenseName = "MIT" 44 | val licenseUrl = "https://github.com/atsushieno/$gitProjectName/blob/main/LICENSE" 45 | val devId = "atsushieno" 46 | val devName = "Atsushi Eno" 47 | val devEmail = "atsushieno@gmail.com" 48 | 49 | // Common copy-pasted 50 | mavenPublishing { 51 | publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL) 52 | if (project.hasProperty("mavenCentralUsername") || System.getenv("ORG_GRADLE_PROJECT_mavenCentralUsername") != null) 53 | signAllPublications() 54 | coordinates(group.toString(), project.name, version.toString()) 55 | pom { 56 | name.set(packageName) 57 | description.set(packageDescription) 58 | url.set(packageUrl) 59 | scm { url.set(packageUrl) } 60 | licenses { license { name.set(licenseName); url.set(licenseUrl) } } 61 | developers { developer { id.set(devId); name.set(devName); email.set(devEmail) } } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /samples/aaphostsample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Sets the minimum version of CMake required to build your native library. 2 | # This ensures that a certain set of CMake features is available to 3 | # your build. 4 | 5 | cmake_minimum_required(VERSION 3.6.0) 6 | 7 | # it is not usable until cmake 3.14... 8 | #target_link_directories(aapnativebridge 9 | link_directories( 10 | "../../androidaudioplugin/build/intermediates/merged_native_libs/debug/out/lib/${CMAKE_ANDROID_ARCH_ABI}" 11 | ) 12 | 13 | # Specifies a library name, specifies whether the library is STATIC or 14 | # SHARED, and provides relative paths to the source code. You can 15 | # define multiple libraries by adding multiple add_library() commands, 16 | # and CMake builds them for you. When you build your app, Gradle 17 | # automatically packages shared libraries with your APK. 18 | 19 | add_library( # Specifies the name of the library. 20 | aaphostsample 21 | 22 | # Sets the library as a shared library. 23 | SHARED 24 | 25 | # Provides a relative path to your source file(s). 26 | # FIXME: this is not a valid sample anymore. It accesses AIDL which is NOT public. 27 | # replace with aap::PluginHost and co. 28 | "src/main/jni/AAPSampleRemoteInterop_native.cpp" 29 | ) 30 | 31 | target_include_directories(aaphostsample 32 | PRIVATE 33 | "${Project_SOURCE_DIR}/../../native/plugin-api/include" 34 | "${Project_SOURCE_DIR}/../../native/androidaudioplugin/core/include" 35 | # FIXME: it should have no access to AIDL generated code. 36 | "${Project_SOURCE_DIR}/../../native/androidaudioplugin/android/src/gen/include" 37 | ) 38 | 39 | target_compile_options(aaphostsample 40 | PRIVATE 41 | -std=c++17 42 | -Wall 43 | -Wextra-semi 44 | -Wshadow 45 | -Wshadow-field 46 | ) 47 | 48 | if (BUILD_WITH_PREFAB) 49 | if (ANDROID) 50 | find_package (androidaudioplugin REQUIRED CONFIG) 51 | set (aaphostsample_LIBS 52 | android 53 | binder_ndk 54 | log 55 | androidaudioplugin::androidaudioplugin) 56 | else (ANDROID) 57 | set (aaphostsample_LIBS 58 | androidaudioplugin) 59 | endif (ANDROID) 60 | target_link_libraries(aaphostsample ${aaphostsample_LIBS}) 61 | else () 62 | target_link_libraries(aaphostsample android binder_ndk log androidaudioplugin) 63 | endif () 64 | -------------------------------------------------------------------------------- /androidaudioplugin-testing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.dokka) 5 | alias(libs.plugins.vanniktech.maven.publish) 6 | signing 7 | } 8 | 9 | apply { from ("../common.gradle") } 10 | version = libs.versions.aap.core.get() 11 | 12 | android { 13 | namespace = "org.androidaudioplugin.androidaudioplugin.testing" 14 | ext["description"] = "AndroidAudioPlugin - testing" 15 | 16 | buildTypes { 17 | release { 18 | isMinifyEnabled = false 19 | proguardFiles (getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation (project(":androidaudioplugin")) 26 | 27 | implementation (libs.androidx.core.ktx) 28 | implementation (libs.kotlin.stdlib.jdk8) 29 | implementation (libs.androidx.appcompat) 30 | implementation (libs.coroutines.core) 31 | implementation (libs.coroutines.android) 32 | implementation (libs.junit) 33 | 34 | testImplementation (libs.junit) 35 | androidTestImplementation (libs.test.ext.junit) 36 | androidTestImplementation (libs.test.espresso.core) 37 | } 38 | 39 | val gitProjectName = "aap-core" 40 | val packageName = project.name 41 | val packageDescription = android.ext["description"].toString() 42 | // my common settings 43 | val packageUrl = "https://github.com/atsushieno/$gitProjectName" 44 | val licenseName = "MIT" 45 | val licenseUrl = "https://github.com/atsushieno/$gitProjectName/blob/main/LICENSE" 46 | val devId = "atsushieno" 47 | val devName = "Atsushi Eno" 48 | val devEmail = "atsushieno@gmail.com" 49 | 50 | // Common copy-pasted 51 | mavenPublishing { 52 | publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL) 53 | if (project.hasProperty("mavenCentralUsername") || System.getenv("ORG_GRADLE_PROJECT_mavenCentralUsername") != null) 54 | signAllPublications() 55 | coordinates(group.toString(), project.name, version.toString()) 56 | pom { 57 | name.set(packageName) 58 | description.set(packageDescription) 59 | url.set(packageUrl) 60 | scm { url.set(packageUrl) } 61 | licenses { license { name.set(licenseName); url.set(licenseUrl) } } 62 | developers { developer { id.set(devId); name.set(devName); email.set(devEmail) } } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /setup-asan-for-debugging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This shell script is to set up ASAN for debugging native libs. 4 | # Specify your own path in those variables below. 5 | # Note that ASAN is *deprecated in Android 14 or later (use HWAsan instead). 6 | 7 | # They have to be adjusted to your environment and NDK version. 8 | # Although note that whenever we use AAP the NDK version has to be fixed to 9 | # this one otherwise libc++_shared.so version mismatch may bite you. 10 | # 11 | # Also note that you need below in build.gradle: 12 | # > android.packagingOptions.jniLibs.useLegacyPackaging = true 13 | # 14 | if [ "$ANDROID_NDK_PATH" == "" ]; then 15 | if [ `uname` == "Darwin" ]; then 16 | ANDROID_NDK_PATH=~/Library/Android/Sdk/ndk/26.1.10909125 17 | else 18 | ANDROID_NDK_PATH=~/Android/Sdk/ndk/26.1.10909125 19 | fi 20 | fi 21 | UNAMEXX=`uname | tr '[:upper:]' '[:lower:]'` 22 | # Note that these paths may not work depending on the NDK versions. Always make sure that the resulting paths exist 23 | HOST_ARCH_LIB=$UNAMEXX-x86_64/lib 24 | CLANG_VER=17 25 | CLANG_LIB=$ANDROID_NDK_PATH/toolchains/llvm/prebuilt/$HOST_ARCH_LIB/clang/$CLANG_VER/lib 26 | echo "UNAME: $UNAMEXX" 27 | echo "CLANG_LIB: $CLANG_LIB" 28 | 29 | ALL_ABIS=("x86" "x86_64" "armeabi-v7a" "arm64-v8a") 30 | 31 | ALL_APPS=("samples/aaphostsample" "samples/aapbarebonepluginsample" "samples/aapinstrumentsample" "samples/aapxssample") 32 | 33 | for sample in "${ALL_APPS[@]}"; do 34 | echo "APP: $sample" 35 | 36 | SAMPLE=$sample/src/main 37 | SAMPLE_RES=$SAMPLE/resources/lib 38 | 39 | for a in "${ALL_ABIS[@]}"; do 40 | echo " ABI: $a" 41 | mkdir -p $SAMPLE/jniLibs/$a ; 42 | # This is causing unresponsive debugger. Do not use it. Load asan so at using System.loadLibrary() instead. 43 | mkdir -p $SAMPLE_RES/$a 44 | cp -R asan-wrap-bugfixed.sh $SAMPLE_RES/$a/wrap.sh 45 | dos2unix $SAMPLE_RES/$a/wrap.sh 46 | chmod +x $SAMPLE_RES/$a/wrap.sh 47 | done 48 | 49 | chmod +x $CLANG_LIB/linux/libclang_rt.asan-*.so 50 | cp $CLANG_LIB/linux/libclang_rt.asan-i686*.so $SAMPLE/jniLibs/x86 51 | cp $CLANG_LIB/linux/libclang_rt.asan-x86_64*.so $SAMPLE/jniLibs/x86_64 52 | cp $CLANG_LIB/linux/libclang_rt.asan-arm*.so $SAMPLE/jniLibs/armeabi-v7a 53 | cp $CLANG_LIB/linux/libclang_rt.asan-aarch64*.so $SAMPLE/jniLibs/arm64-v8a 54 | 55 | done 56 | 57 | echo "done." 58 | echo "NOTE: do not forget to set "enable_asan" in the root build.gradle.kts." 59 | 60 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/NativeLocalPluginInstance.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | import java.nio.ByteBuffer 4 | 5 | class NativeLocalPluginInstance(private val service: NativePluginService, private val instanceId: Int) { 6 | 7 | fun getPluginId() = getPluginId(service.native, instanceId) 8 | fun getPortCount() = getPortCount(service.native, instanceId) 9 | fun getPort(index: Int) = getPort(service.native, instanceId, index) 10 | fun getParameterCount() = getParameterCount(service.native, instanceId) 11 | fun getParameter(index: Int) = getParameter(service.native, instanceId, index) 12 | 13 | fun addEventUmpInput(data: ByteBuffer, size: Int) = addEventUmpInput(service.native, instanceId, data, size) 14 | 15 | // FIXME: we should probably generalize extension features, instead of defining functions and properties for all. 16 | fun getPresetCount() = getPresetCount(service.native, instanceId) 17 | 18 | fun getPresetName(index: Int) = getPresetName(service.native, instanceId, index) 19 | fun setPresetIndex(index: Int) = setPresetIndex(service.native, instanceId, index) 20 | 21 | companion object { 22 | @JvmStatic 23 | private external fun getPluginId(nativeService: Long, instanceId: Int): String 24 | 25 | @JvmStatic 26 | private external fun getPortCount(nativeService: Long, instanceId: Int): Int 27 | @JvmStatic 28 | private external fun getPort(nativeService: Long, instanceId: Int, index: Int): PortInformation 29 | @JvmStatic 30 | private external fun getParameterCount(nativeService: Long, instanceId: Int): Int 31 | @JvmStatic 32 | private external fun getParameter(nativeService: Long, instanceId: Int, index: Int): ParameterInformation 33 | 34 | @JvmStatic 35 | private external fun addEventUmpInput(nativeService: Long, instanceId: Int, data: ByteBuffer, size: Int) 36 | 37 | // FIXME: we should probably generalize extension features, instead of defining functions and properties for all. 38 | @JvmStatic 39 | private external fun getPresetCount(nativeService: Long, instanceId: Int) : Int 40 | @JvmStatic 41 | private external fun getPresetName(nativeService: Long, instanceId: Int, index: Int) : String 42 | @JvmStatic 43 | private external fun setPresetIndex(nativeService: Long, instanceId: Int, index: Int) 44 | } 45 | } -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioDevice.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AUDIODEVICE_H 2 | #define AAP_CORE_AUDIODEVICE_H 3 | 4 | #include 5 | #include "LocalDefinitions.h" 6 | #include "AudioBuffer.h" 7 | 8 | #include 9 | 10 | namespace aap { 11 | 12 | typedef void(AudioDeviceCallback(void* callbackContext, AudioBuffer* audioData, int32_t numFrames)); 13 | 14 | class AAP_PUBLIC_API AudioDeviceIn { 15 | 16 | public: 17 | AudioDeviceIn() {} 18 | 19 | AAP_PUBLIC_API virtual void startCallback() = 0; 20 | AAP_PUBLIC_API virtual void stopCallback() = 0; 21 | 22 | virtual void setAudioCallback(AudioDeviceCallback audioDeviceCallback, void* callbackContext) = 0; 23 | 24 | /// reads the audio data from the backend into `dstAudioData`. 25 | virtual void read(AudioBuffer *dstAudioData, int32_t bufferPosition, int32_t numFrames) = 0; 26 | 27 | /// The platform backend may require audio recording permission (e.g. Android). 28 | /// The backend implementation should return true if that is the case. 29 | virtual bool isPermissionRequired() { return false; } 30 | }; 31 | 32 | class AAP_PUBLIC_API AudioDeviceOut { 33 | public: 34 | AudioDeviceOut() = default; 35 | 36 | AAP_PUBLIC_API virtual void startCallback() = 0; 37 | AAP_PUBLIC_API virtual void stopCallback() = 0; 38 | 39 | /// Sets an abstracted `AudioDeviceCallback` to be called within the actual device callback. 40 | /// 41 | /// The platform backend (e.g. OboeAudioDeviceOut) will invoke `audioDeviceCallback` 42 | /// before it attempts to write its cached output buffer. 43 | /// 44 | /// A user app is supposed to call `write()` within the actual callback. `write()` will then 45 | /// store the audio buffer within the instance of this class so that the platform backend 46 | /// can write to its audio buffer (e.g. `audioData` in Oboe `onAudioReady()`). 47 | virtual void setAudioCallback(AudioDeviceCallback audioDeviceCallback, void* callbackContext) = 0; 48 | 49 | /// writes `audioDataToWrite` into the backend audio output. 50 | /// Note that the actual outputting is done through the backend audio callback. 51 | virtual void write(AudioBuffer *audioDataToWrite, int32_t bufferPosition, int32_t numFrames) = 0; 52 | }; 53 | } 54 | 55 | #endif //AAP_CORE_AUDIODEVICE_H 56 | -------------------------------------------------------------------------------- /include/aap/unstable/logging.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_LOGGING_H_DEFINED 3 | #define ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_LOGGING_H_DEFINED 1 4 | 5 | #include 6 | #include 7 | 8 | #if ANDROID 9 | #include 10 | #endif 11 | 12 | #define logged_android_app_name "AAPHostNative" 13 | 14 | enum AAP_LOG_LEVEL { 15 | #if ANDROID 16 | AAP_LOG_LEVEL_UNKNOWN = ANDROID_LOG_UNKNOWN, // should not be used 17 | AAP_LOG_LEVEL_DEFAULT = ANDROID_LOG_DEFAULT, // should not be used 18 | AAP_LOG_LEVEL_VERBOSE = ANDROID_LOG_VERBOSE, 19 | AAP_LOG_LEVEL_DEBUG = ANDROID_LOG_DEBUG, 20 | AAP_LOG_LEVEL_INFO = ANDROID_LOG_INFO, 21 | AAP_LOG_LEVEL_WARN = ANDROID_LOG_WARN, 22 | AAP_LOG_LEVEL_ERROR = ANDROID_LOG_ERROR, 23 | AAP_LOG_LEVEL_FATAL = ANDROID_LOG_FATAL 24 | #else 25 | AAP_LOG_LEVEL_UNKNOWN = 0, // should not be used 26 | AAP_LOG_LEVEL_DEFAULT, 27 | AAP_LOG_LEVEL_VERBOSE, 28 | AAP_LOG_LEVEL_DEBUG, 29 | AAP_LOG_LEVEL_INFO, 30 | AAP_LOG_LEVEL_WARN, 31 | AAP_LOG_LEVEL_ERROR, 32 | AAP_LOG_LEVEL_FATAL 33 | #endif 34 | }; 35 | 36 | namespace aap 37 | { 38 | 39 | static inline int a_log_vprintf(AAP_LOG_LEVEL logLevel, const char *tag, const char *fmt, va_list ap) { 40 | #if ANDROID 41 | return __android_log_vprint(logLevel, tag, fmt, ap); 42 | #else 43 | return vprintf(fmt, ap); 44 | #endif 45 | } 46 | 47 | static inline int avprintf(const char *fmt, va_list ap) { 48 | return a_log_vprintf(AAP_LOG_LEVEL_INFO, logged_android_app_name, fmt, ap); 49 | } 50 | 51 | static inline int a_log_f(AAP_LOG_LEVEL logLevel, const char *tag, const char *fmt,...) { 52 | va_list ap; 53 | va_start (ap, fmt); 54 | auto ret = a_log_vprintf(logLevel, tag, fmt, ap); 55 | va_end(ap); 56 | return ret; 57 | } 58 | 59 | static inline int aprintf(const char *fmt,...) { 60 | va_list ap; 61 | va_start (ap, fmt); 62 | auto ret = a_log_vprintf(AAP_LOG_LEVEL_INFO, logged_android_app_name, fmt, ap); 63 | va_end(ap); 64 | return ret; 65 | } 66 | 67 | static inline void a_log(AAP_LOG_LEVEL logLevel, const char *tag, const char* s) { 68 | #if ANDROID 69 | __android_log_print(logLevel, tag, "%s", s); 70 | #else 71 | puts(s); 72 | #endif 73 | } 74 | 75 | static inline void aputs(const char* s) { 76 | a_log(AAP_LOG_LEVEL_INFO, logged_android_app_name, s); 77 | } 78 | 79 | } 80 | 81 | #endif // ANDROIDAUDIOPLUGINFRAMEWORK_ANDROID_LOGGING_H_DEFINED 82 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/VirtualAudioDeviceManager.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_VIRTUALAUDIODEVICEMANAGER_H 2 | #define AAP_CORE_VIRTUALAUDIODEVICEMANAGER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "AudioDeviceManager.h" 8 | 9 | namespace aap { 10 | 11 | class VirtualAudioDeviceIn : public AudioDeviceIn { 12 | bool running{false}; 13 | public: 14 | explicit VirtualAudioDeviceIn() = default; 15 | virtual ~VirtualAudioDeviceIn() = default; 16 | 17 | void startCallback() override { running = true; } 18 | void stopCallback() override { running = false; } 19 | void setAudioCallback(AudioDeviceCallback audioDeviceCallback, void* callbackContext) override { 20 | // should we implement something here? 21 | } 22 | void read(AudioBuffer *dstAudioData, int32_t bufferPosition, int32_t numFrames) override { 23 | // should we implement something here? 24 | } 25 | }; 26 | 27 | class VirtualAudioDeviceOut : public AudioDeviceOut { 28 | bool running{false}; 29 | public: 30 | explicit VirtualAudioDeviceOut() = default; 31 | virtual ~VirtualAudioDeviceOut() = default; 32 | 33 | void startCallback() override { running = true; } 34 | void stopCallback() override { running = false; } 35 | void setAudioCallback(AudioDeviceCallback audioDeviceCallback, void* callbackContext) override { 36 | // should we implement something here? 37 | } 38 | void write(AudioBuffer *audioDataToWrite, int32_t bufferPosition, int32_t numFrames) override { 39 | // should we implement something here? 40 | } 41 | }; 42 | 43 | class VirtualAudioDeviceManager : public AudioDeviceManager { 44 | std::shared_ptr input{}; 45 | std::shared_ptr output{}; 46 | 47 | public: 48 | VirtualAudioDeviceManager() 49 | : input(std::make_shared()), 50 | output(std::make_shared()) { 51 | } 52 | 53 | AudioDeviceIn * ensureDefaultInputOpened(int32_t sampleRate, int32_t framesPerCallback, int32_t numChannels) override { return input.get(); } 54 | AudioDeviceOut * ensureDefaultOutputOpened(int32_t sampleRate, int32_t framesPerCallback, int32_t numChannels) override { return output.get(); } 55 | }; 56 | 57 | } 58 | 59 | 60 | #endif //AAP_CORE_VIRTUALAUDIODEVICEMANAGER_H 61 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/src/main/java/org/androidaudioplugin/midideviceservice/AudioPluginMidiReceiver.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.midideviceservice 2 | 3 | import android.media.midi.MidiReceiver 4 | import kotlinx.coroutines.runBlocking 5 | 6 | // LAMESPEC: This is a failure point in Android MIDI API. 7 | // onGetInputPortReceivers() should be called no earlier than onCreate() because onCreate() is 8 | // the method that is supposed to set up the entire Service itself, using fully supplied 9 | // Application context. Without Application context, you have no access to assets, package 10 | // manager including Application meta-data (via ServiceInfo). 11 | // 12 | // However, Android MidiDeviceService invokes that method **at its constructor**! This means, 13 | // you cannot use any information above to determine how many ports this service has. 14 | // This includes access to `midi_device_info.xml` ! Of course we cannot do that either. 15 | // 16 | // Solution? We have to ask you to give explicit number of input and output ports. 17 | // This violates the basic design guideline that an abstract member should not be referenced 18 | // at constructor, but it is inevitable due to this Android MIDI API design flaw. 19 | // 20 | // Since it is instantiated at MidiDeviceService initializer, we cannot run any code that 21 | // depends on Application context until MidiDeviceService.onCreate() is invoked, so 22 | // (1) we have a dedicated `initialize()` method to do that job, so (2) do not anything 23 | // before that happens! 24 | open class AudioPluginMidiReceiver( 25 | private val owner: AudioPluginMidiDevice, 26 | private val portIndex: Int, 27 | private val midiTransport: Int) : MidiReceiver() { 28 | companion object { 29 | init { 30 | System.loadLibrary("aapmidideviceservice") 31 | } 32 | } 33 | 34 | private var instance: AudioPluginMidiDeviceInstance? = null 35 | private val port = owner.deviceInfo.ports[portIndex] 36 | 37 | fun onDeviceOpened() { 38 | assert(instance == null) 39 | runBlocking { 40 | instance = AudioPluginMidiDeviceInstance.create(owner.getPluginId(portIndex), owner, midiTransport) 41 | } 42 | } 43 | 44 | fun onDeviceClosed() { 45 | instance?.onDeviceClosed() 46 | instance = null 47 | } 48 | 49 | override fun onSend(msg: ByteArray?, offset: Int, count: Int, timestamp: Long) { 50 | instance?.onSend(msg, offset, count, timestamp) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "AudioBuffer.h" 2 | #include "aap/unstable/utility.h" 3 | 4 | aap::AudioBuffer::AudioBuffer(int32_t numChannels, int32_t framesPerCallback, int32_t midiBufferSize) { 5 | audio = choc::buffer::createChannelArrayBuffer(numChannels, 6 | framesPerCallback, 7 | []() { return (float) 0; }); 8 | if (midiBufferSize <= 0) 9 | AAP_ASSERT_FALSE; // note: should not reach here 10 | audio.clear(); 11 | midi_capacity = midiBufferSize; 12 | midi_in = midiBufferSize > 0 ? calloc(1, midiBufferSize) : nullptr; 13 | midi_out = midiBufferSize > 0 ? calloc(1, midiBufferSize) : nullptr; 14 | } 15 | 16 | aap::AudioBuffer::~AudioBuffer() { 17 | if (midi_in) 18 | free(midi_in); 19 | if (midi_out) 20 | free(midi_out); 21 | } 22 | 23 | aap_buffer_t aap::AudioBuffer::asAAPBuffer() { 24 | aap_buffer_t ret{}; 25 | ret.impl = this; 26 | ret.num_frames = aapBufferGetNumFrames; 27 | ret.get_buffer = aapBufferGetBuffer; 28 | ret.get_buffer_size = aapBufferGetBufferSize; 29 | ret.num_ports = aapBufferGetNumPorts; 30 | return ret; 31 | } 32 | 33 | int32_t aap::AudioBuffer::aapBufferGetNumFrames(aap_buffer_t &b) { 34 | return ((AudioBuffer*) b.impl)->audio.getNumFrames(); 35 | } 36 | 37 | void *aap::AudioBuffer::aapBufferGetBuffer(aap_buffer_t &b, int32_t portIndex) { 38 | auto data = (AudioBuffer*) b.impl; 39 | if (portIndex < data->audio.getChannelRange().end) 40 | return data->audio.getChannel(portIndex).data.data; 41 | switch (portIndex - data->audio.getChannelRange().end) { 42 | case 0: return data->midi_in; 43 | case 1: return data->midi_out; 44 | } 45 | AAP_ASSERT_FALSE; // should not reach here 46 | return nullptr; 47 | } 48 | 49 | int32_t aap::AudioBuffer::aapBufferGetBufferSize(aap_buffer_t &b, int32_t portIndex) { 50 | auto data = (AudioBuffer*) b.impl; 51 | if (portIndex < data->audio.getChannelRange().end) 52 | return data->audio.getNumFrames() * sizeof(float); 53 | switch (portIndex - data->audio.getChannelRange().end) { 54 | case 0: 55 | case 1: 56 | return data->midi_capacity; 57 | } 58 | AAP_ASSERT_FALSE; // should not reach here 59 | return 0; 60 | } 61 | 62 | int32_t aap::AudioBuffer::aapBufferGetNumPorts(aap_buffer_t &b) { 63 | auto data = (AudioBuffer*) b.impl; 64 | return data->audio.getNumChannels() + 2; 65 | } 66 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/hosting/AudioPluginClientBase.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.hosting 2 | 3 | import android.content.Context 4 | import android.media.AudioManager 5 | import org.androidaudioplugin.AudioPluginNatives 6 | import org.androidaudioplugin.PluginInformation 7 | 8 | open class AudioPluginClientBase(private val context: Context) { 9 | // Service connection 10 | protected val serviceConnector by lazy { AudioPluginServiceConnector(context) } 11 | protected val native by lazy { NativePluginClient.createFromConnection(serviceConnector.serviceConnectionId) } 12 | 13 | val serviceConnectionId by lazy { serviceConnector.serviceConnectionId } 14 | 15 | val onConnectedListeners by lazy { serviceConnector.onConnectedListeners } 16 | val onDisconnectingListeners by lazy { serviceConnector.onDisconnectingListeners } 17 | 18 | fun dispose() { 19 | onDispose() 20 | serviceConnector.connectedServices.forEach { disconnectPluginService(it.serviceInfo.packageName) } 21 | native.dispose() 22 | } 23 | 24 | open fun onDispose() {} 25 | 26 | suspend fun connectToPluginService(packageName: String) : PluginServiceConnection { 27 | val conn = serviceConnector.findExistingServiceConnection(packageName) 28 | if (conn == null) { 29 | val service = AudioPluginHostHelper.queryAudioPluginServices(context.applicationContext) 30 | .first { c -> c.packageName == packageName } 31 | return serviceConnector.bindAudioPluginService(service) 32 | } 33 | return conn 34 | } 35 | 36 | fun disconnectPluginService(packageName: String) { 37 | val conn = serviceConnector.findExistingServiceConnection(packageName) 38 | if (conn != null) 39 | serviceConnector.unbindAudioPluginService(conn.serviceInfo.packageName) 40 | } 41 | 42 | fun instantiateNativePlugin(pluginInfo: PluginInformation) : NativeRemotePluginInstance { 43 | val conn = serviceConnector.findExistingServiceConnection(pluginInfo.packageName) 44 | assert(conn != null) 45 | return native.createInstanceFromExistingConnection(sampleRate, pluginInfo.pluginId!!) 46 | } 47 | 48 | var sampleRate : Int 49 | 50 | init { 51 | AudioPluginNatives.initializeAAPJni(context.applicationContext) 52 | 53 | val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager 54 | sampleRate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)?.toInt() ?: 44100 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/src/main/java/org/androidaudioplugin/ui/web/AudioPluginWebViewFactory.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.ui.web 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.webkit.WebResourceRequest 6 | import android.webkit.WebResourceResponse 7 | import android.webkit.WebView 8 | import android.webkit.WebViewClient 9 | import androidx.webkit.WebViewAssetLoader 10 | import org.androidaudioplugin.AudioPluginException 11 | import org.androidaudioplugin.AudioPluginViewFactory 12 | import org.androidaudioplugin.AudioPluginServiceHelper 13 | import org.androidaudioplugin.NativeLocalPluginInstance 14 | import org.androidaudioplugin.NativePluginService 15 | 16 | class AudioPluginWebViewFactory : AudioPluginViewFactory() { 17 | override fun createView(context: Context, pluginId: String, instanceId: Int) = 18 | getWebView(context, pluginId, instanceId) 19 | 20 | @SuppressLint("SetJavaScriptEnabled") 21 | fun getWebView(ctx: Context, pluginId: String, instanceId: Int): WebView { 22 | val pluginInfo = AudioPluginServiceHelper.getLocalAudioPluginService(ctx).plugins.firstOrNull { it.pluginId == pluginId } 23 | ?: throw AudioPluginException("Plugin '$pluginId' not found.") 24 | return WebView(ctx).also { webView -> 25 | val builder = WebViewAssetLoader.Builder() 26 | // it is used for local assets, meaning that it is not for remote plugin process. 27 | builder.addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(ctx)) 28 | val assetLoader = 29 | // it is used for the remote zip archive. It is then asynchronously loaded. 30 | builder.addPathHandler("/zip/", LazyZipArchivePathHandler( 31 | ctx, pluginId, pluginInfo.packageName)) 32 | .build() 33 | webView.webViewClient = object : WebViewClient() { 34 | override fun shouldInterceptRequest( 35 | view: WebView, 36 | request: WebResourceRequest 37 | ): WebResourceResponse? { 38 | return assetLoader.shouldInterceptRequest(request.url) 39 | } 40 | } 41 | webView.settings.javaScriptEnabled = true 42 | val nativeInstance = NativePluginService(pluginId).getInstance(instanceId) 43 | webView.addJavascriptInterface(AAPServiceScriptInterface(nativeInstance), "AAPInterop") 44 | 45 | webView.loadUrl("https://appassets.androidplatform.net/zip/index.html") 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/android/gen/include/aidl/org/androidaudioplugin/BpAudioPluginInterface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is auto-generated. DO NOT MODIFY. 3 | * Using: /Users/atsushi/Library/Android/sdk/build-tools/35.0.1/aidl --lang=ndk -o /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen -h /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen/include -I /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/ /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterface.aidl /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.aidl 4 | */ 5 | #pragma once 6 | 7 | #include "aidl/org/androidaudioplugin/AudioPluginInterface.h" 8 | 9 | #include 10 | 11 | namespace aidl { 12 | namespace org { 13 | namespace androidaudioplugin { 14 | class BpAudioPluginInterface : public ::ndk::BpCInterface { 15 | public: 16 | explicit BpAudioPluginInterface(const ::ndk::SpAIBinder& binder); 17 | virtual ~BpAudioPluginInterface(); 18 | 19 | ::ndk::ScopedAStatus setCallback(const std::shared_ptr<::aidl::org::androidaudioplugin::IAudioPluginInterfaceCallback>& in_callback) override; 20 | ::ndk::ScopedAStatus beginCreate(const std::string& in_pluginId, int32_t in_sampleRate, int32_t* _aidl_return) override; 21 | ::ndk::ScopedAStatus addExtension(int32_t in_instanceID, const std::string& in_uri, const ::ndk::ScopedFileDescriptor& in_sharedMemoryFD, int32_t in_size) override; 22 | ::ndk::ScopedAStatus endCreate(int32_t in_instanceID) override; 23 | ::ndk::ScopedAStatus isPluginAlive(int32_t in_instanceID, bool* _aidl_return) override; 24 | ::ndk::ScopedAStatus extension(int32_t in_instanceID, const std::string& in_uri, int32_t in_opcode) override; 25 | ::ndk::ScopedAStatus beginPrepare(int32_t in_instanceID) override; 26 | ::ndk::ScopedAStatus prepareMemory(int32_t in_instanceID, int32_t in_shmFDIndex, const ::ndk::ScopedFileDescriptor& in_sharedMemoryFD) override; 27 | ::ndk::ScopedAStatus endPrepare(int32_t in_instanceID, int32_t in_frameCount) override; 28 | ::ndk::ScopedAStatus activate(int32_t in_instanceID) override; 29 | ::ndk::ScopedAStatus process(int32_t in_instanceID, int32_t in_frameCount, int32_t in_timeoutInNanoseconds) override; 30 | ::ndk::ScopedAStatus deactivate(int32_t in_instanceID) override; 31 | ::ndk::ScopedAStatus destroy(int32_t in_instanceID) override; 32 | }; 33 | } // namespace androidaudioplugin 34 | } // namespace org 35 | } // namespace aidl 36 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/java/org/androidaudioplugin/PluginInformation.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin 2 | 3 | /** 4 | * Plugin information structure. The members mostly correspond to attributes and content elements 5 | * in a `` element in `aap_metadata.xml`. 6 | */ 7 | class PluginInformation( 8 | /** Android package name */ 9 | var packageName: String, 10 | /** Android class name */ 11 | var localName: String, 12 | /** human readable name of the plugin */ 13 | var displayName: String, 14 | /** human readable version code */ 15 | var version: String?, 16 | /** category label. Right now `Instrument` or `Effect` are the expected values. */ 17 | var category: String?, 18 | /** developer name (such as personal name, company name, team name) */ 19 | var developer: String?, 20 | /** plugin identifier, typically a URN */ 21 | var pluginId: String?, 22 | /** the shared library name that needs to be loaded when AudioPluginService is initialized. */ 23 | var sharedLibraryName: String?, 24 | /** AAP factory entrypoint function name */ 25 | var libraryEntryPoint: String?, 26 | /** in-plugin-process UI View factory class name */ 27 | var uiViewFactory: String? = null, 28 | /** in-plugin-process UI Activity class name */ 29 | var uiActivity : String? = null, 30 | /** in-host-process Web UI archive name */ 31 | var uiWeb : String? = null, 32 | /** indicates if it is in-process plugin or not */ 33 | var isOutProcess: Boolean = true) 34 | { 35 | companion object { 36 | const val PRIMARY_CATEGORY_EFFECT = "Effect" 37 | const val PRIMARY_CATEGORY_INSTRUMENT = "Instrument" 38 | } 39 | 40 | var extensions = mutableListOf() 41 | 42 | var parameters = mutableListOf() 43 | 44 | var ports = mutableListOf() 45 | 46 | // These obvious members are for use in C interop and not expected to be consumed by user developers. 47 | fun getExtensionCount() : Int 48 | { 49 | return extensions.size 50 | } 51 | fun getExtension(index : Int) : ExtensionInformation 52 | { 53 | return extensions[index] 54 | } 55 | fun getDeclaredParameterCount() : Int 56 | { 57 | return parameters.size 58 | } 59 | fun getDeclaredParameter(index : Int) : ParameterInformation { 60 | return parameters[index] 61 | } 62 | fun getDeclaredPortCount() : Int 63 | { 64 | return ports.size 65 | } 66 | fun getDeclaredPort(index : Int) : PortInformation { 67 | return ports[index] 68 | } 69 | } -------------------------------------------------------------------------------- /include/aap/core/host/plugin-connections.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef AAP_CORE_PLUGIN_CONNECTIONS_H 3 | #define AAP_CORE_PLUGIN_CONNECTIONS_H 4 | 5 | #include "../plugin-information.h" 6 | 7 | namespace aap { 8 | 9 | class PluginListSnapshot { 10 | std::vector services{}; 11 | std::vector plugins{}; 12 | 13 | public: 14 | static PluginListSnapshot queryServices(); 15 | 16 | size_t getNumPluginInformation() 17 | { 18 | return plugins.size(); 19 | } 20 | 21 | const PluginInformation* getPluginInformation(int32_t index) 22 | { 23 | return plugins[(size_t) index]; 24 | } 25 | 26 | const PluginInformation* getPluginInformation(std::string identifier) 27 | { 28 | for (auto plugin : plugins) { 29 | if (plugin->getPluginID().compare(identifier) == 0) 30 | return plugin; 31 | } 32 | return nullptr; 33 | } 34 | }; 35 | 36 | 37 | class PluginClientConnection { 38 | std::string package_name; 39 | std::string class_name; 40 | void * connection_data; 41 | 42 | public: 43 | PluginClientConnection(std::string packageName, std::string className, void * connectionData) 44 | : package_name(packageName), class_name(className), connection_data(connectionData) {} 45 | 46 | inline std::string & getPackageName() { return package_name; } 47 | inline std::string & getClassName() { return class_name; } 48 | inline void * getConnectionData() { return connection_data; } 49 | }; 50 | 51 | class PluginClientConnectionList { 52 | std::vector serviceConnections{}; 53 | 54 | public: 55 | inline void add(std::unique_ptr entry) { 56 | serviceConnections.emplace_back(entry.release()); 57 | } 58 | 59 | inline void remove(std::string packageName, std::string className) { 60 | for (size_t i = 0; i < serviceConnections.size(); i++) { 61 | auto &c = serviceConnections[i]; 62 | if (c->getPackageName() == packageName && c->getClassName() == className) { 63 | delete serviceConnections[i]; 64 | serviceConnections.erase(serviceConnections.begin() + i); 65 | break; 66 | } 67 | } 68 | } 69 | 70 | void* getServiceHandleForConnectedPlugin(std::string packageName, std::string className); 71 | 72 | void* getServiceHandleForConnectedPlugin(std::string pluginId); 73 | 74 | [[maybe_unused]] void* getServiceHandleForConnectedService(std::string pluginId); 75 | }; 76 | 77 | } 78 | 79 | #endif //AAP_CORE_PLUGIN_CONNECTIONS_H 80 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 23 | 24 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 38 | 39 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/NOTES.md: -------------------------------------------------------------------------------- 1 | # androidaudioplugin-midi-device-service implementation notes 2 | 3 | `AudioPluginMidiDeviceService` is a MidiDeviceService implementation that turns AAP instrument into a virtual MIDI output device (`MidiReceiver` in `android.media.midi` API). 4 | 5 | As of Android 13, there is no API that makes it possible for a `MidiDeviceService` to receive MIDI inputs in native manner (Android Native MIDI API only expects `MidiDevice` instance as the entrypoint), every MIDI input is sent to `AudioPluginMidiDeviceService` via Android SDK API (Kotlin/JVM). 6 | 7 | Here is the basic workflow: 8 | 9 | Instantiation, mostly at Kotlin side: 10 | 11 | - `AudioPluginMidiDeviceService` manages `AudioPluginMidiReceiver` instances for each port, in accordance with Android MIDI API. 12 | - `AudioPluginMidiReceiver` internally holds an `AudioPluginMidiDeviceInstance` which internally holds an `AudioPluginClientBase` (which is kind of problematic; it should be probably managed at `AudioPluginMidiDeviceService` per service, but anyways) and a native instance (instantiated internally and held as instanceID). 13 | - whenever `AudioPluginMidiReceiver` receives MIDI inputs, it is dispatched to native code via its `AudioPluginMidiDeviceInstance`. 14 | 15 | MIDI message dispatching, at native side: 16 | 17 | - `AudioPluginMidiDeviceInstance` simply passes the MIDI inputs it received to native `AAPMidiProcessor` using JNI. 18 | - `AAPMidiProcessor::processMidiInput()` receives the MIDI 1.0 bytes input - 19 | - it internally translates it to UMP (`translateMidiBufferIfNeeded()`), then 20 | - resolves MIDI mapping to replace some messages (such as CCs) to parameter change sysex (`runThroughMidi2UmpForMidiMapping()`). 21 | - the resulting UMP packets are stored in a temporary queue (buffer) that will be then copied to the actual MIDI port buffer when the plugin's `process()` is being kicked from within the Oboe audio callback. 22 | - The Oboe callback is actually `oboe::StabilizedCallback` which forwards to platform-agnostic `AAPMidiProcessor::processAudioIO`. We also have "stub" implementation which sometimes helps debugging without depending on realtime audio. 23 | - The audio callback (oboe `onAudioReady()` -> `AAPMidiProcessor::processAudioIO`) invokes the plugin instance's `process()` function and get audio outputs in non-interleaved form. 24 | - Oboe audio callback needs to receive audio data as interleaved format, so interleave the `process()` output. 25 | - Currently we put the audio processing results onto a `zix_ring_buffer` and let `processAudioIO()` consume it. (It should be unnecessarily though, but my attempt to remove it failed hard to trigger audio glitches. It is mostly harmless so I leave it so far.) 26 | 27 | 28 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/android/android-application-context.cpp: -------------------------------------------------------------------------------- 1 | #include "aap/core/android/android-application-context.h" 2 | #include "ALooperMessage.h" 3 | #include 4 | 5 | namespace aap { 6 | 7 | // Android-specific API. Not sure if we would like to keep it in the host API - it is for plugins. 8 | JavaVM *android_vm{nullptr}; 9 | jobject application_context{nullptr}; 10 | // It is globally allocated, assuming that there is only one AudioPluginService object within an app. 11 | // and thus it is the only loop runner within an app. 12 | // We will have to change this assumption if there could be more than one service in an app... 13 | std::unique_ptr non_rt_loop_runner{nullptr}; 14 | 15 | extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { 16 | android_vm = vm; 17 | return JNI_VERSION_1_6; 18 | } 19 | 20 | void unset_application_context(JNIEnv *env) { 21 | if (application_context) 22 | env->DeleteGlobalRef(application_context); 23 | } 24 | 25 | void set_application_context(JNIEnv *env, jobject jobjectApplicationContext) { 26 | if (application_context) 27 | unset_application_context(env); 28 | if (!android_vm) 29 | env->GetJavaVM(&android_vm); 30 | application_context = env->NewGlobalRef((jobject) jobjectApplicationContext); 31 | } 32 | 33 | JavaVM *get_android_jvm() { return android_vm; } 34 | 35 | jobject get_android_application_context() { return application_context; } 36 | 37 | NonRealtimeLoopRunner* get_non_rt_loop_runner() { return non_rt_loop_runner.get(); } 38 | 39 | #include 40 | void prepare_non_rt_event_looper() { 41 | assert(!non_rt_loop_runner); 42 | 43 | auto looper = ALooper_prepare(0); 44 | ALooper_acquire(looper); 45 | // probably we need this constant adjustable... 46 | non_rt_loop_runner = std::make_unique(looper, 65536); 47 | } 48 | 49 | void start_non_rt_event_looper() { 50 | while(true) { 51 | int fd, events; 52 | void* data; 53 | ALooper_pollOnce(-1, &fd, &events, &data); 54 | } 55 | } 56 | 57 | void stop_non_rt_event_looper() { 58 | if (!non_rt_loop_runner) 59 | return; // already stopped 60 | auto looper = (ALooper*) non_rt_loop_runner->getLooper(); 61 | ALooper_release(looper); 62 | non_rt_loop_runner.reset(nullptr); 63 | } 64 | 65 | AAssetManager *get_android_asset_manager(JNIEnv* env) { 66 | if (!application_context) 67 | return nullptr; 68 | auto appClass = env->GetObjectClass(application_context); 69 | auto getAssetsID = env->GetMethodID(appClass, "getAssets", "()Landroid/content/res/AssetManager;"); 70 | auto assetManagerJ = env->CallObjectMethod(application_context, getAssetsID); 71 | return AAssetManager_fromJava(env, assetManagerJ); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /samples/aappluginsample/src/main/cpp/aapxs/aap-xs-sample.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "aap/android-audio-plugin.h" 5 | #include "aap/examples/aapxs/test-extension.h" 6 | #include "aap/ext/parameters.h" 7 | 8 | struct AAPXSSampleContext { 9 | AndroidAudioPluginHost* host; 10 | aap_parameters_host_extension_t* params_ext; 11 | 12 | AAPXSSampleContext(AndroidAudioPluginHost* host) : 13 | host(host) { 14 | params_ext = (aap_parameters_host_extension_t*) host->get_extension(host, AAP_PARAMETERS_EXTENSION_URI); 15 | assert(params_ext); 16 | } 17 | }; 18 | 19 | void test_plugin_prepare(AndroidAudioPlugin *plugin, aap_buffer_t *buffer) {} 20 | 21 | void test_plugin_activate(AndroidAudioPlugin *plugin) {} 22 | 23 | void test_plugin_process(AndroidAudioPlugin *plugin, aap_buffer_t *buffer, int32_t frameCount, int64_t timeoutInNanoiseconds) { 24 | auto context = (AAPXSSampleContext*) plugin->plugin_specific; 25 | auto params = context->params_ext; 26 | params->notify_parameters_changed(context->params_ext, context->host); 27 | if (frameCount == INT32_MAX) throw std::runtime_error("whoa"); // NOP in practice, just to make sure we successfully pass host extension calls. 28 | } 29 | 30 | void test_plugin_deactivate(AndroidAudioPlugin *plugin) {} 31 | 32 | int32_t test_foo (struct example_test_extension_t* context, AndroidAudioPlugin* plugin, int32_t input) { return 0; } 33 | 34 | void test_bar (struct example_test_extension_t* context, AndroidAudioPlugin* plugin, char* msg) {} 35 | 36 | example_test_extension_t test_ext{nullptr, test_foo, test_bar}; 37 | 38 | void* test_plugin_get_extension(AndroidAudioPlugin *plugin, const char* uri) { 39 | if (!strcmp(uri, AAPXS_EXAMPLE_TEST_EXTENSION_URI)) 40 | return &test_ext; 41 | return nullptr; 42 | } 43 | 44 | AndroidAudioPlugin *test_plugin_new( 45 | AndroidAudioPluginFactory *pluginFactory, 46 | const char *pluginUniqueId, 47 | int sampleRate, 48 | AndroidAudioPluginHost *host) { 49 | return new AndroidAudioPlugin{ 50 | new AAPXSSampleContext(host), 51 | test_plugin_prepare, 52 | test_plugin_activate, 53 | test_plugin_process, 54 | test_plugin_deactivate, 55 | test_plugin_get_extension 56 | }; 57 | } 58 | 59 | void test_plugin_delete( 60 | AndroidAudioPluginFactory *pluginFactory, 61 | AndroidAudioPlugin *instance) { 62 | delete (AAPXSSampleContext*) instance->plugin_specific; 63 | delete instance; 64 | } 65 | 66 | AndroidAudioPluginFactory factory{test_plugin_new, test_plugin_delete}; 67 | 68 | AndroidAudioPluginFactory *GetAndroidAudioPluginFactory() { 69 | return &factory; 70 | } 71 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/android/audio-plugin-host-android-internal.cpp: -------------------------------------------------------------------------------- 1 | #include "audio-plugin-host-android-internal.h" 2 | #include "../core/AAPJniFacade.h" 3 | 4 | namespace aap { 5 | 6 | // ---------------------------------- 7 | 8 | std::vector convertPluginList(jobjectArray jPluginInfos) 9 | { 10 | std::vector ret{}; 11 | if (!jPluginInfos) { 12 | AAP_ASSERT_FALSE; 13 | return ret; 14 | } 15 | JNIEnv *env; 16 | aap::get_android_jvm()->AttachCurrentThread(&env, nullptr); 17 | jsize infoSize = env->GetArrayLength(jPluginInfos); 18 | for (int i = 0; i < infoSize; i++) { 19 | auto jPluginInfo = (jobject) env->GetObjectArrayElement(jPluginInfos, i); 20 | ret.emplace_back(AAPJniFacade::getInstance()->pluginInformation_fromJava(env, jPluginInfo)); 21 | } 22 | return ret; 23 | } 24 | 25 | std::vector queryInstalledPlugins() { 26 | return convertPluginList(AAPJniFacade::getInstance()->queryInstalledPluginsJNI()); 27 | } 28 | 29 | std::vector AndroidPluginClientSystem::getPluginPaths() { 30 | std::vector ret{}; 31 | for (auto p : queryInstalledPlugins()) { 32 | if (!p) 33 | AAP_ASSERT_FALSE; 34 | else { 35 | auto packageName = p->getPluginPackageName(); 36 | if (std::find(ret.begin(), ret.end(), packageName) == ret.end()) 37 | ret.emplace_back(packageName); 38 | } 39 | } 40 | return ret; 41 | } 42 | 43 | std::vector AndroidPluginClientSystem::getPluginsFromMetadataPaths(std::vector& aapMetadataPaths) { 44 | std::vector results{}; 45 | for (auto p : queryInstalledPlugins()) 46 | if (std::find(aapMetadataPaths.begin(), aapMetadataPaths.end(), 47 | p->getPluginPackageName()) != aapMetadataPaths.end()) 48 | results.emplace_back(p); 49 | return results; 50 | } 51 | 52 | void AndroidPluginClientSystem::ensurePluginServiceConnected(aap::PluginClientConnectionList* connections, std::string serviceName, std::function callback) { 53 | auto connId = AAPJniFacade::getInstance()->getConnectorInstanceId(connections); 54 | AAPJniFacade::getInstance()->ensureServiceConnectedFromJni(connId, serviceName, callback); 55 | } 56 | 57 | // ------------------------------------------------------- 58 | 59 | std::unique_ptr android_pal_instance{}; 60 | 61 | #if ANDROID 62 | PluginClientSystem* PluginClientSystem::getInstance() { 63 | if (android_pal_instance == nullptr) 64 | android_pal_instance = std::make_unique(); 65 | return android_pal_instance.get(); 66 | } 67 | #endif 68 | 69 | } // namespace aap 70 | -------------------------------------------------------------------------------- /include/aap/ext/gui.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ANDROIDAUDIOPLUGIN_GUI_EXTENSION_H_INCLUDED 3 | #define ANDROIDAUDIOPLUGIN_GUI_EXTENSION_H_INCLUDED 4 | 5 | #include "../android-audio-plugin.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define AAP_GUI_EXTENSION_URI "urn://androidaudioplugin.org/extensions/gui/v3" 12 | 13 | typedef int32_t aap_gui_instance_id; 14 | 15 | // some pre-defined error codes 16 | #define AAP_GUI_RESULT_OK 0 17 | #define AAP_GUI_ERROR_NO_DETAILS -1 18 | #define AAP_GUI_ERROR_NO_GUI_DEFINED -2 19 | #define AAP_GUI_ALREADY_INSTANTIATED -3 20 | #define AAP_GUI_ERROR_NO_CREATE_DEFINED -10 21 | #define AAP_GUI_ERROR_NO_SHOW_DEFINED -11 22 | #define AAP_GUI_ERROR_NO_HIDE_DEFINED -12 23 | #define AAP_GUI_ERROR_NO_RESIZE_DEFINED -13 24 | #define AAP_GUI_ERROR_NO_DESTROY_DEFINED -14 25 | 26 | // In-process GUI extension using Android View and WindowManager. 27 | // FIXME: guiInstanceId may not be required as there should not be more than one AudioPluginView. 28 | 29 | typedef struct aap_gui_extension_t { 30 | void* aapxs_context; 31 | // creates a new GUI View internally. It will be shown as an overlay window later (by `show()`). 32 | // returns > 0 for a new GUI instance ID or <0 for error code e.g. already instantiated or no GUI found. 33 | // Note that audioPluginView parameter is used only between AAPXS service and the plugin. 34 | // The plugin client process has no access to the View in the AudioPluginService process. 35 | // The actual instantiation could be asynchronously done. 36 | RT_UNSAFE aap_gui_instance_id (*create) (aap_gui_extension_t* ext, AndroidAudioPlugin* plugin, const char* pluginId, int32_t instance, void* audioPluginView); 37 | 38 | // shows the view (using `WindowManager.addView()`). 39 | // returns AAP_GUI_RESULT_OK for success, or non-zero error code. e.g. AAP_GUI_ERROR_NO_DETAILS. 40 | RT_UNSAFE int32_t (*show) (aap_gui_extension_t* ext, AndroidAudioPlugin* plugin, aap_gui_instance_id guiInstanceId); 41 | 42 | // hides the view (using `WindowManager.removeView()`). 43 | RT_UNSAFE void (*hide) (aap_gui_extension_t* ext, AndroidAudioPlugin* plugin, aap_gui_instance_id guiInstanceId); 44 | 45 | // resizes the View (by using `WindowManager.updateViewLayout()`. 46 | // returns AAP_GUI_RESULT_OK for success, or non-zero error code. e.g. AAP_GUI_ERROR_NO_DETAILS. 47 | RT_UNSAFE int32_t (*resize) (aap_gui_extension_t* ext, AndroidAudioPlugin* plugin, aap_gui_instance_id guiInstanceId, int32_t width, int32_t height); 48 | 49 | // frees the view. 50 | RT_UNSAFE void (*destroy) (aap_gui_extension_t* ext, AndroidAudioPlugin* plugin, aap_gui_instance_id guiInstanceId); 51 | } aap_gui_extension_t; 52 | 53 | #ifdef __cplusplus 54 | } // extern "C" 55 | #endif 56 | 57 | #endif // ANDROIDAUDIOPLUGIN_GUI_EXTENSION_H_INCLUDED 58 | -------------------------------------------------------------------------------- /samples/aaphostsample/src/androidTest/java/org/androidaudioplugin/aaphostsample/AudioPluginInterfaceTest.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.aaphostsample 2 | 3 | import android.content.ComponentName 4 | import android.content.Context 5 | import android.content.Intent 6 | import androidx.test.core.app.ApplicationProvider 7 | import androidx.test.rule.ServiceTestRule 8 | import org.androidaudioplugin.hosting.AudioPluginHostHelper 9 | import org.androidaudioplugin.AudioPluginInterface 10 | import org.junit.Rule 11 | import org.junit.Test 12 | 13 | class AudioPluginInterfaceTest { 14 | private val applicationContext = ApplicationProvider.getApplicationContext() 15 | 16 | @get:Rule 17 | val serviceRule = ServiceTestRule() 18 | 19 | // avoid No tests found 20 | @Test 21 | fun dummy() { 22 | } 23 | 24 | /* We don't really expect binder based AudioPluginInterface proxy do the right job anymore. 25 | Test NativeRemotePluginInstance instead. 26 | 27 | // It likely requires shared memory extension, but well, only if we really would like to test it... 28 | //@Test 29 | fun basicDirectServiceOperations() { 30 | val pluginId = "urn:org.androidaudioplugin/samples/aapbarebonepluginsample/FlatFilter" 31 | val services = AudioPluginHostHelper.queryAudioPluginServices(applicationContext) 32 | val pluginInfo = services.flatMap { s -> s.plugins.filter { p -> p.pluginId == pluginId} }.first() 33 | 34 | val portInfo = pluginInfo.getPort(0) 35 | assert(portInfo.name == "Left In") 36 | 37 | val intent = Intent(AudioPluginHostHelper.AAP_ACTION_NAME) 38 | intent.component = ComponentName(pluginInfo.packageName, pluginInfo.localName) 39 | val binder = serviceRule.bindService(intent) 40 | val iface = AudioPluginInterface.Stub.asInterface(binder) 41 | 42 | val frameCount = 4096 43 | 44 | val instanceId = iface.beginCreate(pluginId, 44100) 45 | assert(instanceId >= 0) 46 | val instanceId2 = iface.beginCreate(pluginId, 44100) // can create multiple times 47 | assert(instanceId2 >= 0) 48 | assert(instanceId != instanceId2) 49 | 50 | iface.endCreate(instanceId) 51 | iface.endCreate(instanceId2) 52 | 53 | // FIXME: we have to set up shared memory FDs and call prepareMemory(), which is not doable 54 | // at this moment in the java stub. 55 | 56 | iface.prepare(instanceId, frameCount, pluginInfo.getPortCount()) 57 | iface.prepare(instanceId2, frameCount, pluginInfo.getPortCount()) 58 | 59 | iface.activate(instanceId) 60 | iface.activate(instanceId2) 61 | 62 | iface.deactivate(instanceId) 63 | iface.deactivate(instanceId2) 64 | 65 | iface.destroy(instanceId2) 66 | iface.destroy(instanceId) 67 | 68 | serviceRule.unbindService() 69 | } 70 | */ 71 | } 72 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/android/gen/include/aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is auto-generated. DO NOT MODIFY. 3 | * Using: /Users/atsushi/Library/Android/sdk/build-tools/35.0.1/aidl --lang=ndk -o /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen -h /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/cpp/android/gen/include -I /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/ /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterface.aidl /Users/atsushi/sources/AAP/aap-core/androidaudioplugin/src/main/aidl/org/androidaudioplugin/AudioPluginInterfaceCallback.aidl 4 | */ 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #ifdef BINDER_STABILITY_SUPPORT 14 | #include 15 | #endif // BINDER_STABILITY_SUPPORT 16 | 17 | namespace aidl { 18 | namespace org { 19 | namespace androidaudioplugin { 20 | class IAudioPluginInterfaceCallbackDelegator; 21 | 22 | class IAudioPluginInterfaceCallback : public ::ndk::ICInterface { 23 | public: 24 | typedef IAudioPluginInterfaceCallbackDelegator DefaultDelegator; 25 | static const char* descriptor; 26 | IAudioPluginInterfaceCallback(); 27 | virtual ~IAudioPluginInterfaceCallback(); 28 | 29 | static constexpr uint32_t TRANSACTION_hostExtension = FIRST_CALL_TRANSACTION + 0; 30 | static constexpr uint32_t TRANSACTION_requestProcess = FIRST_CALL_TRANSACTION + 1; 31 | 32 | static std::shared_ptr fromBinder(const ::ndk::SpAIBinder& binder); 33 | static binder_status_t writeToParcel(AParcel* parcel, const std::shared_ptr& instance); 34 | static binder_status_t readFromParcel(const AParcel* parcel, std::shared_ptr* instance); 35 | static bool setDefaultImpl(const std::shared_ptr& impl); 36 | static const std::shared_ptr& getDefaultImpl(); 37 | virtual ::ndk::ScopedAStatus hostExtension(int32_t in_instanceId, const std::string& in_uri, int32_t in_opcode) = 0; 38 | virtual ::ndk::ScopedAStatus requestProcess(int32_t in_instanceId) = 0; 39 | private: 40 | static std::shared_ptr default_impl; 41 | }; 42 | class IAudioPluginInterfaceCallbackDefault : public IAudioPluginInterfaceCallback { 43 | public: 44 | ::ndk::ScopedAStatus hostExtension(int32_t in_instanceId, const std::string& in_uri, int32_t in_opcode) override; 45 | ::ndk::ScopedAStatus requestProcess(int32_t in_instanceId) override; 46 | ::ndk::SpAIBinder asBinder() override; 47 | bool isRemote() override; 48 | }; 49 | } // namespace androidaudioplugin 50 | } // namespace org 51 | } // namespace aidl 52 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/AAPJniFacade.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AAPJNIFACADE_H 2 | #define AAP_CORE_AAPJNIFACADE_H 3 | 4 | #if ANDROID 5 | 6 | #include "aap/core/plugin-information.h" 7 | #include "aap/core/host/plugin-connections.h" 8 | #include "aap/core/host/plugin-host.h" 9 | #include "aap/core/host/plugin-instance.h" 10 | #include "jni.h" 11 | 12 | namespace aap { 13 | 14 | int getGuiInstanceSerial(); 15 | 16 | class GuiInstance { 17 | public: 18 | 19 | volatile void* view; 20 | std::string pluginId; 21 | int32_t instanceId; 22 | int32_t externalGuiInstanceId; 23 | int32_t internalGuiInstanceId; 24 | std::string lastError{""}; 25 | }; 26 | 27 | class AAPJniFacade { 28 | 29 | jclass getAudioPluginMidiSettingsClass(); 30 | 31 | public: 32 | static AAPJniFacade *getInstance(); 33 | 34 | void initializeJNIMetadata(); 35 | 36 | PluginInformation * 37 | pluginInformation_fromJava(JNIEnv *env, jobject pluginInformation); 38 | 39 | jobjectArray queryInstalledPluginsJNI(); 40 | 41 | void ensureServiceConnectedFromJni(jint connectorInstanceId, std::string servicePackageName, 42 | std::function callback); 43 | 44 | void addScopedClientConnection(int32_t connectorInstanceId, std::string packageName, std::string className, void* connectionData); 45 | 46 | void removeScopedClientConnection(int32_t connectorInstanceId, std::string packageName, std::string className); 47 | 48 | int32_t getConnectorInstanceId(PluginClientConnectionList *connections); 49 | 50 | aap::PluginClientConnectionList *getPluginConnectionListFromJni(jint connectorInstanceId, 51 | bool createIfNotExist); 52 | 53 | int32_t getMidiSettingsFromLocalConfig(std::string pluginId); 54 | 55 | void putMidiSettingsToSharedPreference(std::string pluginId, int32_t flags); 56 | 57 | void* getRemoteWebView(PluginClient* client, RemotePluginInstance* instance); 58 | void* createSurfaceControl(); 59 | void disposeSurfaceControl(void* handle); 60 | void showSurfaceControlView(void* handle); 61 | void hideSurfaceControlView(void* handle); 62 | void* getRemoteNativeView(PluginClient* client, RemotePluginInstance* instance); 63 | void connectRemoteNativeView(PluginClient* client, RemotePluginInstance* instance, int32_t width, int32_t height); 64 | 65 | void handleServiceConnectedCallback(std::string servicePackageName); 66 | 67 | jobject getPluginInstanceParameter(jlong nativeHost, jint instanceId, jint index); 68 | 69 | jobject getPluginInstancePort(jlong nativeHost, jint instanceId, jint index); 70 | }; 71 | 72 | } 73 | 74 | #endif // ANDROID 75 | 76 | #endif //AAP_CORE_AAPJNIFACADE_H 77 | -------------------------------------------------------------------------------- /samples/aaphostsample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id ("com.android.application") 3 | id ("kotlin-android") 4 | } 5 | 6 | apply { from ("../../common.gradle") } 7 | 8 | // What a mess... 9 | val enable_asan: Boolean by rootProject 10 | 11 | android { 12 | namespace = "org.androidaudioplugin.aaphostsample" 13 | defaultConfig { 14 | applicationId = "org.androidaudioplugin.aaphostsample" 15 | } 16 | buildFeatures { 17 | prefab = true 18 | } 19 | buildTypes { 20 | debug { 21 | packaging { 22 | jniLibs.keepDebugSymbols.add("**/*.so") 23 | } 24 | } 25 | release { 26 | isMinifyEnabled = false 27 | proguardFiles (getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 28 | } 29 | } 30 | packaging { 31 | if (enable_asan) 32 | jniLibs.useLegacyPackaging = true 33 | } 34 | 35 | // FIXME: it is annoying to copy this everywhere, but publish-pom.gradle.kts is incapable of importing this fragment... 36 | // It's been long time until I got this working, and I have no idea why it started working. 37 | // If you don't get this working, you are not alone: https://github.com/atsushieno/aap-core/issues/85 38 | // Also note that you have to use custom sdk channel so far: ./gradlew testDevice1DebugAndroidTest -Pandroid.sdk.channel=3 39 | testOptions { 40 | managedDevices { 41 | devices { 42 | maybeCreate ("testDevice1").apply { 43 | device = "Pixel 2" 44 | apiLevel = 30 45 | systemImageSource = "aosp-atd" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | dependencies { 53 | implementation (project(":androidaudioplugin")) 54 | implementation (project(":androidaudioplugin-ui-compose-app")) 55 | androidTestImplementation (project(":androidaudioplugin-testing")) 56 | 57 | runtimeOnly (libs.libcxx.provider) 58 | 59 | implementation (libs.androidx.core.ktx) 60 | implementation (libs.kotlin.stdlib.jdk8) 61 | implementation (libs.androidx.appcompat) 62 | implementation (libs.coroutines.core) 63 | implementation (libs.coroutines.android) 64 | 65 | implementation(platform(libs.compose.bom)) 66 | 67 | testImplementation (libs.junit) 68 | androidTestImplementation (libs.test.ext.junit) 69 | androidTestImplementation (libs.test.espresso.core) 70 | } 71 | 72 | // Starting AGP 7.0.0-alpha05, AGP stopped caring build dependencies and it broke builds. 73 | // This is a forcible workarounds to build libandroidaudioplugin.so in prior to referencing it. 74 | gradle.projectsEvaluated { 75 | tasks["mergeDebugNativeLibs"].dependsOn(rootProject.project("androidaudioplugin").tasks["mergeDebugNativeLibs"]) 76 | tasks["mergeReleaseNativeLibs"].dependsOn(rootProject.project("androidaudioplugin").tasks["mergeReleaseNativeLibs"]) 77 | } 78 | -------------------------------------------------------------------------------- /androidaudioplugin/src/main/cpp/core/aapxs/gui-aapxs.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "aap/core/aapxs/gui-aapxs.h" 3 | 4 | void aap::xs::AAPXSDefinition_Gui::aapxs_gui_process_incoming_plugin_aapxs_request( 5 | struct AAPXSDefinition *feature, AAPXSRecipientInstance *aapxsInstance, 6 | AndroidAudioPlugin *plugin, AAPXSRequestContext *request) { 7 | // FIXME: implement 8 | throw std::runtime_error("implement"); 9 | } 10 | 11 | void aap::xs::AAPXSDefinition_Gui::aapxs_gui_process_incoming_host_aapxs_request( 12 | struct AAPXSDefinition *feature, AAPXSRecipientInstance *aapxsInstance, 13 | AndroidAudioPluginHost *host, AAPXSRequestContext *request) { 14 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 15 | throw std::runtime_error("implement"); 16 | } 17 | 18 | void aap::xs::AAPXSDefinition_Gui::aapxs_gui_process_incoming_plugin_aapxs_reply( 19 | struct AAPXSDefinition *feature, AAPXSInitiatorInstance *aapxsInstance, 20 | AndroidAudioPlugin *plugin, AAPXSRequestContext *request) { 21 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 22 | throw std::runtime_error("implement"); 23 | } 24 | 25 | void aap::xs::AAPXSDefinition_Gui::aapxs_gui_process_incoming_host_aapxs_reply( 26 | struct AAPXSDefinition *feature, AAPXSInitiatorInstance *aapxsInstance, 27 | AndroidAudioPluginHost *host, AAPXSRequestContext *request) { 28 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 29 | throw std::runtime_error("implement"); 30 | } 31 | 32 | aap_gui_instance_id aap::xs::GuiClientAAPXS::createGui(std::string pluginId, int32_t instanceId, 33 | void *audioPluginView) { 34 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 35 | throw std::runtime_error("implement"); 36 | } 37 | 38 | int32_t aap::xs::GuiClientAAPXS::showGui(aap_gui_instance_id guiInstanceId) { 39 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 40 | throw std::runtime_error("implement"); 41 | } 42 | 43 | int32_t aap::xs::GuiClientAAPXS::hideGui(aap_gui_instance_id guiInstanceId) { 44 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 45 | throw std::runtime_error("implement"); 46 | } 47 | 48 | int32_t aap::xs::GuiClientAAPXS::resizeGui(aap_gui_instance_id guiInstanceId, int32_t width, 49 | int32_t height) { 50 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 51 | throw std::runtime_error("implement"); 52 | } 53 | 54 | int32_t aap::xs::GuiClientAAPXS::destroyGui(aap_gui_instance_id guiInstanceId) { 55 | // FIXME: implement, but we might rather just invoke JniFacade as we did in StandardExtensions v1 56 | throw std::runtime_error("implement"); 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_call: 7 | 8 | jobs: 9 | linux-build: 10 | name: build on linux 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - name: checkout 14 | uses: actions/checkout@v4 15 | with: 16 | submodules: recursive 17 | - name: apt update 18 | run: | 19 | sudo apt-get update 20 | - name: set up JDK 17 21 | uses: actions/setup-java@v4 22 | with: 23 | java-version: 17 24 | distribution: temurin 25 | - name: cache AVD 26 | uses: actions/cache@v4 27 | env: 28 | cache-name: cache-avd 29 | with: 30 | path: /home/runner/.android/gradle/avd 31 | key: ${{ runner.os }}-gradle-avd 32 | - name: cache gradle caches 33 | uses: actions/cache@v4 34 | env: 35 | cache-name: cache-gradle-caches 36 | with: 37 | path: /home/runner/.gradle/caches/ 38 | key: ${{ runner.os }}-gradle-caches 39 | - name: apt install 40 | run: | 41 | echo y | sudo apt-get install doxygen libxml2-dev libgrpc++-dev libgrpc-dev libprotobuf-dev protobuf-compiler protobuf-compiler-grpc graphviz cmake ninja-build 42 | - name: build 43 | run: ./gradlew build dokkaGenerate publishToMavenLocal 44 | - name: run instrumented tests (disabled) 45 | if: false 46 | # > Gradle was not able to complete device setup for: dev30_google_x86_Pixel_5 47 | # > The emulator failed to open the managed device to generate the snapshot. 48 | # also https://github.com/actions/virtual-environments/issues/183 49 | run: 50 | ./gradlew testDevice1DebugAndroidTest || exit 1 51 | ./gradlew testDevice2DebugAndroidTest || exit 1 52 | - name: upload artifact 53 | if: success() 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: aap-core-apps-and-libs 57 | path: | 58 | ./*/build/outputs/aar/*.aar 59 | samples/*/build/outputs/apk/debug/*.apk 60 | 61 | osx-build: 62 | name: build on osx 63 | runs-on: macos-14 64 | steps: 65 | - name: checkout 66 | uses: actions/checkout@v4 67 | with: 68 | submodules: recursive 69 | - name: set up JDK 17 70 | uses: actions/setup-java@v4 71 | with: 72 | java-version: 17 73 | distribution: temurin 74 | - name: cache AVD 75 | uses: actions/cache@v4 76 | env: 77 | cache-name: cache-avd 78 | with: 79 | path: /Users/runner/.android/gradle/avd 80 | key: ${{ runner.os }}-gradle-avd 81 | - name: cache gradle caches 82 | uses: actions/cache@v4 83 | env: 84 | cache-name: cache-gradle-caches 85 | with: 86 | path: /Users/runner/.gradle/caches/ 87 | key: ${{ runner.os }}-gradle-caches 88 | - name: install deps 89 | run: | 90 | brew install doxygen libxml2 ninja graphviz openssl 91 | - name: build 92 | run: ./gradlew build dokkaGenerate publishToMavenLocal 93 | env: 94 | ANDROID_SDK_ROOT: /Users/runner/Library/Android/sdk 95 | -------------------------------------------------------------------------------- /androidaudioplugin-manager/src/main/cpp/AudioGraph.h: -------------------------------------------------------------------------------- 1 | #ifndef AAP_CORE_AUDIOGRAPH_H 2 | #define AAP_CORE_AUDIOGRAPH_H 3 | 4 | #include 5 | #include 6 | 7 | #include "LocalDefinitions.h" 8 | #include "AudioDeviceManager.h" 9 | #include "AudioGraphNode.h" 10 | 11 | namespace aap { 12 | AAP_OPEN_CLASS class AudioGraph { 13 | int32_t sample_rate; 14 | int32_t frames_per_callback; 15 | int32_t num_channels; 16 | 17 | public: 18 | explicit AudioGraph(int32_t sampleRate, int32_t framesPerCallback, int32_t channelsInAudioBus) : 19 | sample_rate(sampleRate), 20 | frames_per_callback(framesPerCallback), 21 | num_channels(channelsInAudioBus) { 22 | } 23 | 24 | virtual void processAudio(AudioBuffer* audioData, int32_t numFrames) = 0; 25 | 26 | int32_t getSampleRate() { return sample_rate; } 27 | 28 | int32_t getFramesPerCallback() { return frames_per_callback; } 29 | 30 | int32_t getChannelsInAudioBus() { return num_channels; } 31 | }; 32 | 33 | class SimpleLinearAudioGraph : public AudioGraph { 34 | AudioDeviceInputNode input; 35 | AudioDeviceOutputNode output; 36 | AudioPluginNode plugin; 37 | AudioDataSourceNode audio_data; 38 | MidiSourceNode midi_input; 39 | MidiDestinationNode midi_output; 40 | std::vector nodes{}; 41 | bool is_processing{false}; 42 | 43 | static void audio_callback(void* callbackContext, AudioBuffer* audioData, int32_t numFrames) { 44 | ((SimpleLinearAudioGraph*) callbackContext)->processAudio(audioData, numFrames); 45 | } 46 | 47 | public: 48 | SimpleLinearAudioGraph(int32_t sampleRate, uint32_t framesPerCallback, int32_t channelsInAudioBus); 49 | virtual ~SimpleLinearAudioGraph(); 50 | 51 | void setPlugin(RemotePluginInstance* instance); 52 | 53 | void setAudioSource(uint8_t *data, int dataLength, const char *filename); 54 | 55 | void processAudio(AudioBuffer *audioData, int32_t numFrames) override; 56 | 57 | void addMidiEvent(uint8_t *data, int32_t dataLength, int64_t timestampInNanoseconds); 58 | 59 | void playAudioData(); 60 | 61 | bool isProcessing() { return is_processing; } 62 | 63 | void startProcessing(); 64 | 65 | void pauseProcessing(); 66 | 67 | void enableAudioRecorder(); 68 | 69 | void setPresetIndex(int index); 70 | }; 71 | 72 | // Not planned to implement so far. 73 | class BasicAudioGraph : public AudioGraph { 74 | public: 75 | BasicAudioGraph(int32_t sampleRate, int32_t framesPerCallback, int32_t channelsInAudioBus) : 76 | AudioGraph(sampleRate, framesPerCallback, channelsInAudioBus) { 77 | } 78 | 79 | void attachNode(AudioGraphNode* sourceNode, int32_t sourceOutputBusIndex, AudioGraphNode* destinationNode, int32_t destinationInputBusIndex) {} 80 | void detachNode(AudioGraphNode* sourceNode, int32_t sourceOutputBusIndex) {} 81 | }; 82 | } 83 | 84 | #endif //AAP_CORE_AUDIOGRAPH_H 85 | -------------------------------------------------------------------------------- /androidaudioplugin-testing/src/main/java/org/androidaudioplugin/androidaudioplugin/testing/AudioPluginServiceTesting.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.androidaudioplugin.testing 2 | 3 | import android.content.Context 4 | import junit.framework.Assert.assertEquals 5 | import kotlinx.coroutines.runBlocking 6 | import org.androidaudioplugin.AudioPluginServiceHelper 7 | import org.androidaudioplugin.PluginInformation 8 | import org.androidaudioplugin.PluginServiceInformation 9 | import org.androidaudioplugin.hosting.AudioPluginClientBase 10 | 11 | class AudioPluginServiceTesting(private val applicationContext: Context) { 12 | 13 | @Deprecated("Use testPluginServiceInfo", replaceWith = ReplaceWith("testPluginServiceInfo()")) 14 | fun getPluginServiceInfo() = testPluginServiceInformation {} 15 | 16 | fun testPluginServiceInformation(serviceInfoTest: (serviceInfo: PluginServiceInformation) -> Unit = {}) { 17 | val audioPluginServiceInfo = AudioPluginServiceHelper.getLocalAudioPluginService(applicationContext) 18 | assertEquals ("packageName", applicationContext.packageName, audioPluginServiceInfo.packageName) 19 | serviceInfoTest(audioPluginServiceInfo) 20 | } 21 | 22 | fun testSinglePluginInformation(pluginInfoTest: (info: PluginInformation) -> Unit) { 23 | testPluginServiceInformation { serviceInfo -> 24 | assertEquals("There are more than one plugins. Not suitable for this test function", 1, serviceInfo.plugins.size) 25 | pluginInfoTest(serviceInfo.plugins[0]) 26 | } 27 | } 28 | 29 | fun basicServiceOperationsForAllPlugins() { 30 | for (pluginInfo in AudioPluginServiceHelper.getLocalAudioPluginService(applicationContext).plugins) 31 | testInstancingAndProcessing(pluginInfo) 32 | } 33 | 34 | // cycle: number of audio instancing and processing cycle. It is multiplied by numParallelInstances. 35 | @Suppress("UnnecessaryVariable") 36 | fun testInstancingAndProcessing(pluginInfo: PluginInformation, cycles: Int = 5) { 37 | // number of parallel instances 38 | val numParallelInstances = 3 39 | 40 | val host = AudioPluginClientBase(applicationContext) 41 | val floatCount = 1024 42 | val controlBufferSize = 0x10000 43 | 44 | runBlocking { 45 | host.connectToPluginService(pluginInfo.packageName) 46 | } 47 | 48 | for (i in 0 until cycles) { 49 | val p = numParallelInstances 50 | val instances = ((0 until p).map { host.instantiateNativePlugin(pluginInfo) }) 51 | assert(instances.map { it.instanceId }.distinct().size == instances.size ) 52 | (0 until p).forEach { instances[it].prepare(floatCount, controlBufferSize) } 53 | (0 until p).forEach { instances[it].activate() } 54 | (0 until p).forEach { instances[it].process(floatCount, 0) } 55 | (0 until p).forEach { instances[it].deactivate() } 56 | (0 until p).forEach { instances[it].destroy() } 57 | } 58 | 59 | host.disconnectPluginService(pluginInfo.packageName) 60 | host.dispose() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /androidaudioplugin-ui-web/src/main/assets/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /docs/design/CLIENT_SERVICE.md: -------------------------------------------------------------------------------- 1 | # AAP interaction between client and service 2 | 3 | The basic messaging between AAP client (host) and service is achieved using NdkBinder and "AAP AIDL". 4 | 5 | ## AAP AIDL 6 | 7 | The AIDL provides primitive operations between client and service, namely: 8 | 9 | - `setCallback()` : sets callback (explained below) 10 | - `beginCreate()` : begins instantiation 11 | - `addExtension()` : adds an extension 12 | - `endCreate()` : ends instantiation 13 | - `prepareMemory()` : sets one port shared memory buffer 14 | - `prepare()` : finishes preparing shared buffers 15 | - `extension()` : performs extension operation in non-realtime mode 16 | - `activate()` : activates the plugin instance for realtime processing 17 | - `process()` : processes audio in realtime 18 | - `deactivate()` : deactivates the plugin instance from realtime processing 19 | - `destroy()` : terminates the plugin instance 20 | 21 | ## non-realtime Bi-directional messaging 22 | 23 | Bi-directional messaging will be enabled by sending `IAudioPluginInterfaceCallback` callback interface via `setCallback` request in AAP AIDL. It is important to note that it has to be done only once per plugin service connection, not per plugin service instance. 24 | 25 | It makes it possible to initiate an extension messaging from plugin service side, especially for non-realtime event notifications (such as parameter changes). 26 | 27 | For realtime event notifications (if ever any), it should be performed in each `process()` operation and MIDI output port should be used instead. Typical parameter changes could be notified in non-realtime manner. 28 | 29 | ## Instancing workflow 30 | 31 | AAP service begins with "create" operation. In AIDL it is split into three operation: `beginCreate()`, `addExtension()`, and `endCreate()`. Conceptually it could be understood like `create_instance(list)` (pseudo code). Extension (at this state) usually comes up with a shared memory FD with its size. 32 | 33 | For consistency in the future, the order of calls to `addExtension()` should be considered as significant (i.e. the extension list is ordered). Any extension that could affect other extensions should be added earlier. 34 | 35 | Once AAP instance is created, it will start configuring plugin. The most important part is port configuration. The client and service needs to determine what kind of ports will be used, for both "audio buses" and "MIDI ports". 36 | 37 | A traditional AAP service doesn't provide MIDI ports unless it was an instrument (synth) which is simply impossible to construct a bidirectional messaging transport. 38 | 39 | Some extensions will be used to achieve this negotiation. 40 | 41 | Once client and service got agreement, client starts sending `prepareMemory()` requests, to prepare port shared memory buffers. Once every port is prepared, it sends `prepare()` to finish it. These processes could be understood like `prepare(list)` (pseudo code). 42 | 43 | Once all ports are ready and it is preapared, then it can be activated by `activate()` to be ready for audio processing (by `process()` method). Audio processing would be ignored if it is not at activated state (it may not; AAP cannot prevent services to process audio at inactive state). 44 | 45 | 46 | -------------------------------------------------------------------------------- /androidaudioplugin-midi-device-service/src/main/java/org/androidaudioplugin/midideviceservice/AudioPluginMidiDeviceInstance.kt: -------------------------------------------------------------------------------- 1 | package org.androidaudioplugin.midideviceservice 2 | 3 | import android.content.Context 4 | import android.media.AudioManager 5 | import org.androidaudioplugin.hosting.AudioPluginClientBase 6 | 7 | // Unlike MidiReceiver, it is instantiated whenever the port is opened, and disposed every time it is closed. 8 | // By isolating most of the implementation here, it makes better lifetime management. 9 | class AudioPluginMidiDeviceInstance private constructor( 10 | // It is used to manage Service connections, not instancing (which is managed by native code). 11 | private val client: AudioPluginClientBase) { 12 | 13 | companion object { 14 | suspend fun create(pluginId: String, ownerService: AudioPluginMidiDevice, midiTransport: Int) : AudioPluginMidiDeviceInstance { 15 | val audioManager = ownerService.applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager 16 | val sampleRate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)?.toInt() ?: 44100 17 | val oboeFrameSize = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)?.toInt() ?: 1024 18 | 19 | val client = AudioPluginClientBase(ownerService.applicationContext) 20 | 21 | val ret = AudioPluginMidiDeviceInstance(client) 22 | 23 | // FIXME: adjust audioOutChannelCount and appFrameSize somewhere? 24 | 25 | ret.initializeMidiProcessor(client.serviceConnectionId, 26 | sampleRate, oboeFrameSize, ret.audioOutChannelCount, ret.aapFrameSize, 27 | ret.midiBufferSize, midiTransport) 28 | 29 | val pluginInfo = ownerService.plugins.first { p -> p.pluginId == pluginId } 30 | client.connectToPluginService(pluginInfo.packageName) 31 | ret.instantiatePlugin(pluginId) 32 | ret.activate() 33 | return ret 34 | } 35 | } 36 | 37 | private val audioOutChannelCount: Int = 2 38 | private val aapFrameSize = 512 39 | private val midiBufferSize = 4096 40 | 41 | fun onDeviceClosed() { 42 | deactivate() 43 | client.dispose() 44 | terminateMidiProcessor() 45 | } 46 | 47 | fun onSend(msg: ByteArray?, offset: Int, count: Int, timestamp: Long) { 48 | // We skip too lengthy MIDI buffer. 49 | val actualSize = if (count > midiBufferSize) midiBufferSize else count 50 | 51 | processMessage(msg, offset, actualSize, timestamp) 52 | } 53 | 54 | // Initialize basic native parts, without any plugin information. 55 | private external fun initializeMidiProcessor( 56 | connectorInstanceId: Int, 57 | sampleRate: Int, 58 | oboeFrameSize: Int, 59 | audioOutChannelCount: Int, 60 | aapFrameSize: Int, 61 | midiBufferSize: Int, 62 | midiTransport: Int) 63 | private external fun terminateMidiProcessor() 64 | private external fun instantiatePlugin(pluginId: String) 65 | private external fun processMessage(msg: ByteArray?, offset: Int, count: Int, timestampInNanoseconds: Long) 66 | private external fun activate() 67 | private external fun deactivate() 68 | } --------------------------------------------------------------------------------