diff --git a/androidaudioplugin-midi-device-service/src/main/cpp/AAPMidiProcessor.cpp b/androidaudioplugin-midi-device-service/src/main/cpp/AAPMidiProcessor.cpp index 29885181..0fc63c16 100644 --- a/androidaudioplugin-midi-device-service/src/main/cpp/AAPMidiProcessor.cpp +++ b/androidaudioplugin-midi-device-service/src/main/cpp/AAPMidiProcessor.cpp @@ -1,6 +1,6 @@ #include #include "aap/unstable/logging.h" -#include "aap/ext/aap-midi2.h" +#include "aap/ext/midi.h" #include "AAPMidiProcessor.h" namespace aapmidideviceservice { diff --git a/androidaudioplugin/src/main/cpp/core/extensions/midi2-service.h b/androidaudioplugin/src/main/cpp/core/extensions/midi-service.h similarity index 50% rename from androidaudioplugin/src/main/cpp/core/extensions/midi2-service.h rename to androidaudioplugin/src/main/cpp/core/extensions/midi-service.h index 942b6eab..a537921a 100644 --- a/androidaudioplugin/src/main/cpp/core/extensions/midi2-service.h +++ b/androidaudioplugin/src/main/cpp/core/extensions/midi-service.h @@ -6,7 +6,7 @@ #include #include "aap/android-audio-plugin.h" #include "aap/unstable/aapxs.h" -#include "aap/ext/aap-midi2.h" +#include "aap/ext/midi.h" #include "aap/unstable/logging.h" #include "aap/core/aapxs/extension-service.h" #include "extension-service-impl.h" @@ -14,32 +14,45 @@ namespace aap { // implementation details +const int32_t OPCODE_GET_MAPPING_POLICY = 0; -class Midi2PluginClientExtension : public PluginClientExtensionImplBase { + +class MidiPluginClientExtension : public PluginClientExtensionImplBase { class Instance { - friend class Midi2PluginClientExtension; + friend class MidiPluginClientExtension; aap_midi2_extension_t proxy{}; - //Midi2PluginClientExtension *owner; + MidiPluginClientExtension *owner; AAPXSClientInstance* aapxsInstance; + static enum aap_midi_mapping_policy internalGetMappingPolicy(AndroidAudioPluginExtensionTarget target, const char* pluginId) { + return ((Instance *) target.aapxs_context)->getMappingPolicy(pluginId); + } + public: - Instance(Midi2PluginClientExtension *owner, AAPXSClientInstance *clientInstance) - //: owner(owner) + Instance(MidiPluginClientExtension *owner, AAPXSClientInstance *clientInstance) + : owner(owner) { aapxsInstance = clientInstance; } - /* + enum aap_midi_mapping_policy getMappingPolicy(const char* pluginId) { + auto len = strlen(pluginId); + assert(len < MAX_PLUGIN_ID_SIZE); + *((int32_t*) aapxsInstance->data) = len; + strcpy((char*) ((int32_t*) aapxsInstance->data + 1), pluginId); + clientInvokePluginExtension(OPCODE_GET_MAPPING_POLICY); + return *((enum aap_midi_mapping_policy *) aapxsInstance->data); + } + void clientInvokePluginExtension(int32_t opcode) { owner->clientInvokePluginExtension(aapxsInstance, opcode); } - */ AAPXSProxyContext asProxy() { - proxy.context = this; - return AAPXSProxyContext{aapxsInstance, nullptr, &proxy}; + proxy.get_mapping_policy = internalGetMappingPolicy; + return AAPXSProxyContext{aapxsInstance, this, &proxy}; } }; @@ -47,19 +60,19 @@ class Midi2PluginClientExtension : public PluginClientExtensionImplBase { // to each Instance that at least lives as long as AAPXSClientInstance lifetime. // (Should we add `addDisposableListener` at AAPXSClient to make it possible to free // this Instance at plugin instance disposal? Maybe when if 1024 for instances sounds insufficient...) -#define MIDI2_AAPXS_MAX_INSTANCE_COUNT 1024 +#define MIDI_AAPXS_MAX_INSTANCE_COUNT 1024 - std::unique_ptr instances[MIDI2_AAPXS_MAX_INSTANCE_COUNT]{}; + std::unique_ptr instances[MIDI_AAPXS_MAX_INSTANCE_COUNT]{}; std::map instance_map{}; // map from instanceId to the index of the Instance in `instances`. public: - Midi2PluginClientExtension() + MidiPluginClientExtension() : PluginClientExtensionImplBase() { } AAPXSProxyContext asProxy(AAPXSClientInstance *clientInstance) override { size_t last = 0; - for (; last < MIDI2_AAPXS_MAX_INSTANCE_COUNT; last++) { + for (; last < MIDI_AAPXS_MAX_INSTANCE_COUNT; last++) { if (instances[last] == nullptr) break; if (instances[last]->aapxsInstance == clientInstance) @@ -71,30 +84,39 @@ class Midi2PluginClientExtension : public PluginClientExtensionImplBase { } }; -class Midi2PluginServiceExtension : public PluginServiceExtensionImplBase { +class MidiPluginServiceExtension : public PluginServiceExtensionImplBase { public: - Midi2PluginServiceExtension() - : PluginServiceExtensionImplBase(AAP_MIDI2_EXTENSION_URI) { + MidiPluginServiceExtension() + : PluginServiceExtensionImplBase(AAP_MIDI_EXTENSION_URI) { } // invoked by AudioPluginService void onInvoked(AndroidAudioPlugin* plugin, AAPXSServiceInstance *extensionInstance, int32_t opcode) override { + switch (opcode) { + case OPCODE_GET_MAPPING_POLICY: + auto len = *(int32_t*) extensionInstance->data; + assert(len < MAX_PLUGIN_ID_SIZE); + char* pluginId = (char*) calloc(len, 1); + strncpy(pluginId, (const char*) ((int32_t*) extensionInstance->data + 1), len); + *((int32_t*) extensionInstance->data) = getMidiSettingsFromSharedPreference(pluginId); + return; + } assert(false); // should not happen } }; -class Midi2ExtensionFeature : public PluginExtensionFeatureImpl { +class MidiExtensionFeature : public PluginExtensionFeatureImpl { std::unique_ptr client; std::unique_ptr service; public: - Midi2ExtensionFeature() - : PluginExtensionFeatureImpl(AAP_MIDI2_EXTENSION_URI, false, sizeof(aap_midi2_extension_t)), - client(std::make_unique()), - service(std::make_unique()) { + MidiExtensionFeature() + : PluginExtensionFeatureImpl(AAP_MIDI_EXTENSION_URI, false, sizeof(aap_midi2_extension_t)), + client(std::make_unique()), + service(std::make_unique()) { } PluginClientExtensionImplBase* getClient() { return client.get(); } diff --git a/androidaudioplugin/src/main/cpp/core/extensions/parameters-service.h b/androidaudioplugin/src/main/cpp/core/extensions/parameters-service.h index d87c811b..b017f528 100644 --- a/androidaudioplugin/src/main/cpp/core/extensions/parameters-service.h +++ b/androidaudioplugin/src/main/cpp/core/extensions/parameters-service.h @@ -14,8 +14,6 @@ namespace aap { // implementation details -const int32_t OPCODE_GET_MAPPING_POLICY = 0; - const int32_t MAX_PLUGIN_ID_SIZE = 1024; // FIXME: there should be some official definition. class ParametersPluginClientExtension : public PluginClientExtensionImplBase { @@ -27,10 +25,6 @@ class ParametersPluginClientExtension : public PluginClientExtensionImplBase { ParametersPluginClientExtension *owner; AAPXSClientInstance* aapxsInstance; - static enum aap_parameters_mapping_policy internalGetMappingPolicy(AndroidAudioPluginExtensionTarget target, const char* pluginId) { - return ((Instance *) target.aapxs_context)->getMappingPolicy(pluginId); - } - public: Instance(ParametersPluginClientExtension *owner, AAPXSClientInstance *clientInstance) : owner(owner) @@ -38,21 +32,11 @@ class ParametersPluginClientExtension : public PluginClientExtensionImplBase { aapxsInstance = clientInstance; } - enum aap_parameters_mapping_policy getMappingPolicy(const char* pluginId) { - auto len = strlen(pluginId); - assert(len < MAX_PLUGIN_ID_SIZE); - *((int32_t*) aapxsInstance->data) = len; - strcpy((char*) ((int32_t*) aapxsInstance->data + 1), pluginId); - clientInvokePluginExtension(OPCODE_GET_MAPPING_POLICY); - return *((enum aap_parameters_mapping_policy *) aapxsInstance->data); - } - void clientInvokePluginExtension(int32_t opcode) { owner->clientInvokePluginExtension(aapxsInstance, opcode); } AAPXSProxyContext asProxy() { - proxy.get_mapping_policy = internalGetMappingPolicy; return AAPXSProxyContext{aapxsInstance, this, &proxy}; } }; @@ -95,15 +79,6 @@ class ParametersPluginServiceExtension : public PluginServiceExtensionImplBase { // invoked by AudioPluginService void onInvoked(AndroidAudioPlugin* plugin, AAPXSServiceInstance *extensionInstance, int32_t opcode) override { - switch (opcode) { - case OPCODE_GET_MAPPING_POLICY: - auto len = *(int32_t*) extensionInstance->data; - assert(len < MAX_PLUGIN_ID_SIZE); - char* pluginId = (char*) calloc(len, 1); - strncpy(pluginId, (const char*) ((int32_t*) extensionInstance->data + 1), len); - *((int32_t*) extensionInstance->data) = getMidiSettingsFromSharedPreference(pluginId); - return; - } assert(false); // should not happen } }; diff --git a/androidaudioplugin/src/main/cpp/core/hosting/audio-plugin-host.cpp b/androidaudioplugin/src/main/cpp/core/hosting/audio-plugin-host.cpp index 5d7d2eaf..fbf53c8f 100644 --- a/androidaudioplugin/src/main/cpp/core/hosting/audio-plugin-host.cpp +++ b/androidaudioplugin/src/main/cpp/core/hosting/audio-plugin-host.cpp @@ -11,7 +11,7 @@ #include "aap/core/host/plugin-client-system.h" #include "../extensions/parameters-service.h" #include "../extensions/presets-service.h" -#include "../extensions/midi2-service.h" +#include "../extensions/midi-service.h" #include "../extensions/port-config-service.h" #define AAP_HOST_TAG "AAP_HOST" @@ -153,7 +153,7 @@ int32_t PluginSharedMemoryStore::allocateServiceBuffer(std::vector& cli std::unique_ptr aapxs_state{nullptr}; std::unique_ptr aapxs_presets{nullptr}; std::unique_ptr aapxs_port_config{nullptr}; -std::unique_ptr aapxs_midi2{nullptr}; +std::unique_ptr aapxs_midi2{nullptr}; std::unique_ptr aapxs_parameters{nullptr}; PluginHost::PluginHost(PluginListSnapshot* contextPluginList) @@ -177,7 +177,7 @@ PluginHost::PluginHost(PluginListSnapshot* contextPluginList) aapxs_registry->add(aapxs_state->asPublicApi()); // midi2 if (aapxs_midi2 == nullptr) - aapxs_midi2 = std::make_unique(); + aapxs_midi2 = std::make_unique(); aapxs_registry->add(aapxs_midi2->asPublicApi()); // parameters if (aapxs_parameters == nullptr) diff --git a/androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginService.kt b/androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginService.kt index 95ce0b64..1d06eae2 100644 --- a/androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginService.kt +++ b/androidaudioplugin/src/main/java/org/androidaudioplugin/AudioPluginService.kt @@ -12,7 +12,9 @@ import android.os.Build import org.androidaudioplugin.hosting.AudioPluginMidiSettings /** - * The Audio plugin service class. Every AAP (plugin) derives from this class, either directly or indirectly. + * The Audio plugin service class. It should not be derived. We check the service class name strictly. + * + * Every AAP should implement AudioPluginInterface.aidl, in native code. * * Typical user developers don't have to write any code to create their own plugins in Kotlin code. * They are implemented in native code to get closest to realtime audio processing. diff --git a/include/aap/core/aapxs/extension-service.h b/include/aap/core/aapxs/extension-service.h index adb8b82e..506c3464 100644 --- a/include/aap/core/aapxs/extension-service.h +++ b/include/aap/core/aapxs/extension-service.h @@ -7,12 +7,9 @@ #include #include #include "aap/unstable/aapxs.h" -#include "aap/ext/presets.h" #include "aap/unstable/logging.h" #include "../plugin-information.h" #include "aap/android-audio-plugin.h" -#include "aap/ext/presets.h" -#include "aap/ext/state.h" namespace aap { diff --git a/include/aap/core/aapxs/standard-extensions.h b/include/aap/core/aapxs/standard-extensions.h index 7386ec9f..7452874a 100644 --- a/include/aap/core/aapxs/standard-extensions.h +++ b/include/aap/core/aapxs/standard-extensions.h @@ -2,6 +2,9 @@ #define AAP_CORE_STANDARD_EXTENSIONS_H #include "extension-service.h" +#include "aap/ext/midi.h" +#include "aap/ext/state.h" +#include "aap/ext/presets.h" // FIXME: should this be moved to somewhere? extern "C" int32_t getMidiSettingsFromSharedPreference(std::string pluginId); @@ -46,6 +49,12 @@ virtual TYPE withParametersExtension(TYPE dummyValue, std::function func) = 0; +#define DEFINE_WITH_MIDI_EXTENSION(TYPE) \ +virtual TYPE withMidiExtension(TYPE dummyValue, std::function func) = 0; + + DEFINE_WITH_MIDI_EXTENSION(int32_t) + virtual void withMidiExtension(std::function func) = 0; + public: size_t getStateSize() { return withStateExtension/**/(0, [&](aap_state_extension_t* ext, AndroidAudioPluginExtensionTarget target) { @@ -101,7 +110,7 @@ virtual TYPE withParametersExtension(TYPE dummyValue, std::function*/(0, [&](aap_parameters_extension_t* ext, AndroidAudioPluginExtensionTarget target) { + return withMidiExtension(0, [&](aap_midi_extension_t* ext, AndroidAudioPluginExtensionTarget target) { if (ext && ext->get_mapping_policy) return (int32_t) ext->get_mapping_policy(target, pluginId.c_str()); else @@ -155,6 +164,17 @@ TYPE withParametersExtension(TYPE dummyValue, std::function func) override { return withExtension(false, 0, AAP_PARAMETERS_EXTENSION_URI, func); } + + DEFINE_WITH_MIDI_EXTENSION_LOCAL(int32_t) + virtual void withMidiExtension(std::function func) override { + withExtension(false, 0, AAP_MIDI_EXTENSION_URI, [&](aap_midi_extension_t* ext, AndroidAudioPluginExtensionTarget target) { + func(ext, target); + return 0; + }); + } + public: virtual AndroidAudioPlugin* getPlugin() = 0; }; @@ -206,6 +226,17 @@ TYPE withParametersExtension(TYPE dummyValue, std::function func) override { return withExtension(false, 0, AAP_MIDI_EXTENSION_URI, func); } + + DEFINE_WITH_MIDI_EXTENSION_REMOTE(int32_t) + virtual void withMidiExtension(std::function func) override { + withExtension(false, 0, AAP_MIDI_EXTENSION_URI, [&](aap_midi_extension_t* ext, AndroidAudioPluginExtensionTarget target) { + func(ext, target); + return 0; + }); + } + public: virtual AndroidAudioPlugin* getPlugin() = 0; virtual AAPXSClientInstanceManager* getAAPXSManager() = 0; diff --git a/include/aap/ext/aap-midi2.h b/include/aap/ext/aap-midi2.h deleted file mode 100644 index d99ef0c5..00000000 --- a/include/aap/ext/aap-midi2.h +++ /dev/null @@ -1,32 +0,0 @@ - -#ifndef ANDROIDAUDIOPLUGIN_MIDI2_EXTENSION_H_INCLUDED -#define ANDROIDAUDIOPLUGIN_MIDI2_EXTENSION_H_INCLUDED - -#include "../android-audio-plugin.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define AAP_MIDI2_EXTENSION_URI "urn://androidaudioplugin.org/extensions/midi2/v1" - -#define AAP_PROTOCOL_MIDI1_0 1 -#define AAP_PROTOCOL_MIDI2_0 2 - -typedef struct AAPMidiBufferHeader { - int32_t time_options{0}; - uint32_t length{0}; - uint32_t reserved[6]; -} AAPMidiBufferHeader; - -typedef struct aap_midi2_extension_t { - void *context; - int32_t protocol{0}; // 0: Unspecified / 1: MIDI1 / 2: MIDI2 - int32_t protocolVersion{0}; // MIDI1 = 0, MIDI2_V1 = 0 -} aap_midi2_extension_t; - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // ANDROIDAUDIOPLUGIN_MIDI2_EXTENSION_H_INCLUDED diff --git a/include/aap/ext/midi.h b/include/aap/ext/midi.h new file mode 100644 index 00000000..815536d3 --- /dev/null +++ b/include/aap/ext/midi.h @@ -0,0 +1,164 @@ + +#ifndef ANDROIDAUDIOPLUGIN_MIDI_EXTENSION_H_INCLUDED +#define ANDROIDAUDIOPLUGIN_MIDI_EXTENSION_H_INCLUDED + +#include "../android-audio-plugin.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define AAP_MIDI_EXTENSION_URI "urn://androidaudioplugin.org/extensions/midi2/v1" + +#define AAP_PROTOCOL_MIDI1_0 1 +#define AAP_PROTOCOL_MIDI2_0 2 + +typedef struct AAPMidiBufferHeader { + int32_t time_options{0}; + uint32_t length{0}; + uint32_t reserved[6]; +} AAPMidiBufferHeader; + +// +// This MIDI extension assumes that parameter updates by host are sent to the plugin using +// MIDI2 inputs, and parameter change notification back to the host is sent from the plugin +// using MIDI2 outputs. The source input is a MIDI2 input port, and the destination +// should be a MIDI2 output port. There is the only one input and the only one output. +// +// ## MIDI2-parameters mapping schemes +// +// The way how parameter changes and notifications are transmitted depends on get_mapping_policy(). +// extension function. It returns a set of flags where - +// - `AAP_PARAMETERS_MAPPING_POLICY_CC` indicates that the plugin will consume Control Change inputs +// by its own and cannot be used for parameter changes. +// - `AAP_PARAMETERS_MAPPING_POLICY_ACC` indicates that the plugin will consume Assignable Controller +// and Per-Note Assignable Controller inputs by its own and cannot be used for parameter changes. +// - `AAP_PARAMETERS_MAPPING_POLICY_PROGRAM` indicates that the plugin will consume Program Change +// inputs (and bank select to represent 8+ bits i.e. preset = bank * 128 + program) by its own +// and cannot be used for preset setter. +// - `AAP_PARAMETERS_MAPPING_POLICY_SYSEX8` indicates that the plugin will consume such a Universal +// SysEx8 message that conforms to certain SysEx8 packet by its own and cannot be used for +// parameter changes. It should be almost always disabled. Hosts may not allow it. +// And if it is enabled, then at least one of ..._CC or ..._ACC flag should be disabled. +// +// If a plugin does not want to let host send CC/ACC/Program (or SysEx8) for alternative purposes, +// then it should implement this extension to return its own mapping policy. +// +// AAP MidiDeviceService will translate those CC and ACC messages to parameter change SysEx8, and +// translate Program Changes to preset changes, unless the plugin requires them to be passed +// and not injected. +// +// The sysex8 as parameter change UMP consists of a single UMP packet (i.e. 16 bits) and +// looks like `5g sz pc 7E 7F 00 00 ch k.n.idx. value...`, where - +// - g : UMP group +// - sz : status and size, always 0F +// - pc : packet, always 00 +// - ch : channel, 00-0F +// - k. : key for per-note parameter change, 00-7F +// - n. : reserved, but it might be used for note ID for per note parameter change, 00-7F. +// If it is being used for note ID, host can assign a consistent number across note on/off +// w/ attribute and this message. But I find it ugly and impractical so far. +// - idx. : parameter index, 0-16383 +// - value... : parameter value, 32-bit float +// +// ## UI events +// +// At this state, we decided to not provide its own event queuing entrypoint in this extension. +// Host is responsible to send remote-UI-originated events within its `process()` calls. +// +// It is easier to ask every host developer to implement event input unification i.e. unify the UI +// event inputs into its MIDI2 inputs for `process()`, than asking every plugin developer to do it. +// AAP has two kinds of UIs: local in-plugin-process UI and remote in-host-process UI: +// +// - For remote UI, the UI is a Web UI which dispatches the input events to the host WebView using +// JavaScript interface, and there is (will be) the event dispatching API for that, as well as +// parameter change notification listener API. Then in terms of host-plugin interaction, it is +// totally a matter of `process()` +// +// - For local UI, it is totally within the plugin process, thus it is up to the plugin itself +// to implement how to interpret its own UI events to the MIDI2 input queue. +// +// In either way, host will receive parameter change notifications through the MIDI2 outputs, +// synchronously. For remote UI, the host will have to dispatch the change notifications to the +// Web UI (via the parameter change notification listener API). +// +// This decision is actually tentative and we may introduce additional event queuing function +// that would enhance use of parameters. +// +// ### Some technical background on parameters and UI +// +// In the world of audio plugin formats, there are two kinds of parameter support "extensions" : +// +// - LV2 has strict distinction between DSP and UI, which results in detached dynamic library +// entity (dll/dylib/so) for UI from DSP, and it needs certain interface (API) for those two. +// Since it is for UI, it is not a synchronous API. It writes events to the port, without +// reading any "response" notifications. They will be sent to output ports at some stage. +// - CLAP has parameters extension in totally different way. It has `flush()` that host can tell +// plugin to process parameters and get parameter change notifications synchronously. +// It does not care about UI/DSP separation. It only cares the interface between host and plugin. +// +// +// The in-process UI and any other out-process UIs, apart from the primary sequencer (DAW) MIDI2 +// inputs, should also be able to send parameter changes. That is why we will have `addEvents()` +// member function as part of this extension. +// Note that it is not usable for "receiving" change notifications, as change notifications +// would be often sent as to notify the "latest value" which cannot be really calculated without +// the actual audio processing inputs. +// To not interrupt realtime audio processing loop, additional events from UI should be enqueued, +// not processed within the call to the processing function. We could name the function as +// `process()` instead of `addEvents()` and specify outputs stream as an argument too, but that +// implies we generate the outputs *on time*, which is not realistic (the function should behave +// like "enqueue and return immediately", nothing to process further to get outputs). +// +// + +// keep these in sync with AudioPluginMidiSettings. +enum aap_midi_mapping_policy { + AAP_PARAMETERS_MAPPING_POLICY_NONE = 0, + AAP_PARAMETERS_MAPPING_POLICY_ACC = 1, + AAP_PARAMETERS_MAPPING_POLICY_CC = 2, + AAP_PARAMETERS_MAPPING_POLICY_SYSEX8 = 4, + AAP_PARAMETERS_MAPPING_POLICY_PROGRAM = 8, +}; + +static inline void aapMidi2ParameterSysex8(uint32_t *dst1, uint32_t *dst2, uint32_t *dst3, uint32_t *dst4, uint8_t group, uint8_t channel, uint8_t key, uint16_t extra, uint16_t index, float value) { + *dst1 = ((0x50 + group) << 24) + 0x7E; + *dst2 = 0x7F000000 + channel; + *dst3 = (key << 24) + (extra << 16) + index; + *dst4 = *(uint32_t*) (void*) &value; +} + +static inline bool aapReadMidi2ParameterSysex8(uint8_t* group, uint8_t* channel, + uint8_t* key, uint8_t* extra, uint16_t* index, float* value, + uint32_t src1, uint32_t src2, uint32_t src3, uint32_t src4) { + *group = (uint8_t) (src1 >> 24) & 0xF; + *channel = src2 & 0xF; + *key = src3 >> 24; + *extra = (src3 >> 16) & 0xFF; + *index = src3 & 0xFFFF; + *value = *(float*) &src4; + return (src1 >> 24) == 0x50 + *group && (src1 & 0xFF) == 0x7E && src2 == 0x7F000000 + *channel; +} + +typedef enum aap_midi_mapping_policy (*midi_extension_get_mapping_policy_func_t) (AndroidAudioPluginExtensionTarget target, const char* pluginId); + +// The MIDI extension + +typedef struct aap_midi_extension_t { + // indicates which UMP protocol it expects for the inputs: 0: Unspecified (MIDI2) / 1: MIDI1 / 2: MIDI2 + // Currently unused. + int32_t protocol{0}; + // indicates the expected UMP protocol version for the input: MIDI1 = 0, MIDI2_V1 = 0 + // Currently unused. + int32_t protocolVersion{0}; + // Returns the MIDI mapping policy flags that indicates the message types that the plugin may directly + // consume and thus cannot be mapped to other purposes (parameter changes and preset changes). + // The actual callee is most likely audio-plugin-host, not the plugin. + midi_extension_get_mapping_policy_func_t get_mapping_policy; +} aap_midi2_extension_t; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ANDROIDAUDIOPLUGIN_MIDI_EXTENSION_H_INCLUDED diff --git a/include/aap/ext/parameters.h b/include/aap/ext/parameters.h index 5bf9046f..abc39123 100644 --- a/include/aap/ext/parameters.h +++ b/include/aap/ext/parameters.h @@ -8,123 +8,12 @@ extern "C" { #endif -// This parameters extension assumes that parameter updates by host are sent to the plugin using -// MIDI2 inputs, and parameter change notification back to the host is sent from the plugin -// using MIDI2 outputs. The source input is a MIDI2 input port, and the destination -// should be a MIDI2 output port. There is the only one input and the only one output. -// -// ## MIDI2-parameters mapping schemes -// -// The way how parameter changes and notifications are transmitted depends on get_mapping_policy(). -// extension function. It returns a set of flags where - -// - `AAP_PARAMETERS_MAPPING_POLICY_CC` indicates that the plugin will consume Control Change inputs -// by its own and cannot be used for parameter changes. -// - `AAP_PARAMETERS_MAPPING_POLICY_ACC` indicates that the plugin will consume Assignable Controller -// and Per-Note Assignable Controller inputs by its own and cannot be used for parameter changes. -// - `AAP_PARAMETERS_MAPPING_POLICY_PROGRAM` indicates that the plugin will consume Program Change -// inputs (and bank select to represent 8+ bits i.e. preset = bank * 128 + program) by its own -// and cannot be used for preset setter. -// - `AAP_PARAMETERS_MAPPING_POLICY_SYSEX8` indicates that the plugin will consume such a Universal -// SysEx8 message that conforms to certain SysEx8 packet by its own and cannot be used for -// parameter changes. It should be almost always disabled. Hosts may not allow it. -// And if it is enabled, then at least one of ..._CC or ..._ACC flag should be disabled. -// -// If a plugin does not want to let host send CC/ACC/Program (or SysEx8) for alternative purposes, -// then it should implement this extension to return its own mapping policy. -// -// AAP MidiDeviceService will translate those CC and ACC messages to parameter change SysEx8, and -// translate Program Changes to preset changes, unless the plugin requires them to be passed -// and not injected. -// -// The sysex8 as parameter change UMP consists of a single UMP packet (i.e. 16 bits) and -// looks like `5g sz pc 7E 7F 00 00 ch k.n.idx. value...`, where - -// - g : UMP group -// - sz : status and size, always 0F -// - pc : packet, always 00 -// - ch : channel, 00-0F -// - k. : key for per-note parameter change, 00-7F -// - n. : reserved, but it might be used for note ID for per note parameter change, 00-7F. -// If it is being used for note ID, host can assign a consistent number across note on/off -// w/ attribute and this message. But I find it ugly and impractical so far. -// - idx. : parameter index, 0-16383 -// - value... : parameter value, 32-bit float -// -// ## UI events -// -// At this state, we decided to not provide its own event queuing entrypoint in this extension. -// Host is responsible to send remote-UI-originated events within its `process()` calls. -// -// It is easier to ask every host developer to implement event input unification i.e. unify the UI -// event inputs into its MIDI2 inputs for `process()`, than asking every plugin developer to do it. -// AAP has two kinds of UIs: local in-plugin-process UI and remote in-host-process UI: -// -// - For remote UI, the UI is a Web UI which dispatches the input events to the host WebView using -// JavaScript interface, and there is (will be) the event dispatching API for that, as well as -// parameter change notification listener API. Then in terms of host-plugin interaction, it is -// totally a matter of `process()` -// -// - For local UI, it is totally within the plugin process, thus it is up to the plugin itself -// to implement how to interpret its own UI events to the MIDI2 input queue. -// -// In either way, host will receive parameter change notifications through the MIDI2 outputs, -// synchronously. For remote UI, the host will have to dispatch the change notifications to the -// Web UI (via the parameter change notification listener API). -// -// This decision is actually tentative and we may introduce additional event queuing function -// that would enhance use of parameters. -// -// ### Some technical background on parameters and UI -// -// In the world of audio plugin formats, there are two kinds of parameter support "extensions" : -// -// - LV2 has strict distinction between DSP and UI, which results in detached dynamic library -// entity (dll/dylib/so) for UI from DSP, and it needs certain interface (API) for those two. -// Since it is for UI, it is not a synchronous API. It writes events to the port, without -// reading any "response" notifications. They will be sent to output ports at some stage. -// - CLAP has parameters extension in totally different way. It has `flush()` that host can tell -// plugin to process parameters and get parameter change notifications synchronously. -// It does not care about UI/DSP separation. It only cares the interface between host and plugin. -// -// -// The in-process UI and any other out-process UIs, apart from the primary sequencer (DAW) MIDI2 -// inputs, should also be able to send parameter changes. That is why we will have `addEvents()` -// member function as part of this extension. -// Note that it is not usable for "receiving" change notifications, as change notifications -// would be often sent as to notify the "latest value" which cannot be really calculated without -// the actual audio processing inputs. -// To not interrupt realtime audio processing loop, additional events from UI should be enqueued, -// not processed within the call to the processing function. We could name the function as -// `process()` instead of `addEvents()` and specify outputs stream as an argument too, but that -// implies we generate the outputs *on time*, which is not realistic (the function should behave -// like "enqueue and return immediately", nothing to process further to get outputs). -// -// - #define AAP_PARAMETERS_EXTENSION_URI "urn://androidaudioplugin.org/extensions/parameters/v1" #define AAP_PARAMETERS_XMLNS_URI "urn://androidaudioplugin.org/extensions/parameters" #define AAP_MAX_PARAMETER_NAME_CHARS 256 #define AAP_MAX_PARAMETER_PATH_CHARS 256 -static inline void aapMidi2ParameterSysex8(uint32_t *dst1, uint32_t *dst2, uint32_t *dst3, uint32_t *dst4, uint8_t group, uint8_t channel, uint8_t key, uint16_t extra, uint16_t index, float value) { - *dst1 = ((0x50 + group) << 24) + 0x7E; - *dst2 = 0x7F000000 + channel; - *dst3 = (key << 24) + (extra << 16) + index; - *dst4 = *(uint32_t*) (void*) &value; -} - -static inline bool aapReadMidi2ParameterSysex8(uint8_t* group, uint8_t* channel, - uint8_t* key, uint8_t* extra, uint16_t* index, float* value, - uint32_t src1, uint32_t src2, uint32_t src3, uint32_t src4) { - *group = (uint8_t) (src1 >> 24) & 0xF; - *channel = src2 & 0xF; - *key = src3 >> 24; - *extra = (src3 >> 16) & 0xFF; - *index = src3 & 0xFFFF; - *value = *(float*) &src4; - return (src1 >> 24) == 0x50 + *group && (src1 & 0xFF) == 0x7E && src2 == 0x7F000000 + *channel; -} - typedef struct aap_parameter_info_t { // Parameter ID. // For plugin users sake, parameter ID must be "stable", preserved for backward compatibility @@ -160,18 +49,8 @@ typedef struct aap_parameter_info_t { bool per_note_enabled; } aap_parameter_info_t; -// keep these in sync with AudioPluginMidiSettings. -enum aap_parameters_mapping_policy { - AAP_PARAMETERS_MAPPING_POLICY_NONE = 0, - AAP_PARAMETERS_MAPPING_POLICY_ACC = 1, - AAP_PARAMETERS_MAPPING_POLICY_CC = 2, - AAP_PARAMETERS_MAPPING_POLICY_SYSEX8 = 4, - AAP_PARAMETERS_MAPPING_POLICY_PROGRAM = 8, -}; - typedef int32_t (*parameters_extension_get_parameter_count_func_t) (AndroidAudioPluginExtensionTarget target); typedef aap_parameter_info_t* (*parameters_extension_get_parameter_func_t) (AndroidAudioPluginExtensionTarget target, int32_t index); -typedef enum aap_parameters_mapping_policy (*parameters_extension_get_mapping_policy_func_t) (AndroidAudioPluginExtensionTarget target, const char* pluginId); typedef struct aap_parameters_extension_t { // Returns the number of parameters. @@ -180,10 +59,6 @@ typedef struct aap_parameters_extension_t { // Returns the parameter information by parameter ID. // If the plugin does not provide the parameter list on aap_metadata, it is supposed to provide them here. parameters_extension_get_parameter_func_t get_parameter; - // Returns the MIDI mapping policy flags that indicates the message types that the plugin may directly - // consume and thus cannot be mapped to other purposes (parameter changes and preset changes). - // The actual callee is most likely audio-plugin-host, not the plugin. - parameters_extension_get_mapping_policy_func_t get_mapping_policy; } aap_parameters_extension_t; typedef void (*parameters_host_extension_on_parameter_list_changed_func_t) (AndroidAudioPluginHost* host, AndroidAudioPlugin *plugin); diff --git a/samples/aapbarebonepluginsample/build.gradle.kts b/samples/aapbarebonepluginsample/build.gradle.kts index ab964377..baa5a65d 100644 --- a/samples/aapbarebonepluginsample/build.gradle.kts +++ b/samples/aapbarebonepluginsample/build.gradle.kts @@ -31,7 +31,7 @@ android { buildTypes { debug { packagingOptions { - doNotStrip ("**/*.so") + jniLibs.keepDebugSymbols.add("**/*.so") } } release { diff --git a/samples/aapbarebonepluginsample/src/main/cpp/aapbarebonepluginsample.cpp b/samples/aapbarebonepluginsample/src/main/cpp/aapbarebonepluginsample.cpp index d351aa49..4ca75fd9 100644 --- a/samples/aapbarebonepluginsample/src/main/cpp/aapbarebonepluginsample.cpp +++ b/samples/aapbarebonepluginsample/src/main/cpp/aapbarebonepluginsample.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/samples/aapinstrumentsample/build.gradle.kts b/samples/aapinstrumentsample/build.gradle.kts index c78c7e13..65675910 100644 --- a/samples/aapinstrumentsample/build.gradle.kts +++ b/samples/aapinstrumentsample/build.gradle.kts @@ -28,7 +28,7 @@ android { buildTypes { debug { packagingOptions { - doNotStrip ("**/*.so") + jniLibs.keepDebugSymbols.add("**/*.so") } } release { diff --git a/samples/aapinstrumentsample/src/main/cpp/aapinstrumentsample.cpp b/samples/aapinstrumentsample/src/main/cpp/aapinstrumentsample.cpp index 525d2629..45d76bbd 100644 --- a/samples/aapinstrumentsample/src/main/cpp/aapinstrumentsample.cpp +++ b/samples/aapinstrumentsample/src/main/cpp/aapinstrumentsample.cpp @@ -10,7 +10,7 @@ extern "C" { #include "ayumi.h" #include "cmidi2.h" -#include +#include #include #include diff --git a/samples/aapxssample/build.gradle.kts b/samples/aapxssample/build.gradle.kts index c18a34a4..9c3d0310 100644 --- a/samples/aapxssample/build.gradle.kts +++ b/samples/aapxssample/build.gradle.kts @@ -31,7 +31,7 @@ android { buildTypes { debug { packagingOptions { - doNotStrip ("**/*.so") + jniLibs.keepDebugSymbols.add("**/*.so") } } release {