Skip to content

Commit

Permalink
bump cmidi2 and add AAPXS MIDI2 sysex8 parser and generator (WIP).
Browse files Browse the repository at this point in the history
context: #169

The SysEx8 parser and generator implementation is still standalone.
They need to be integrated to `AAPXS[Client|Service]Instance`.

It makes use of new cmidi2 functions for SysEx8 parser, so bump cmidi2 too.
  • Loading branch information
atsushieno committed Sep 6, 2023
1 parent 8471699 commit ebc7664
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 38 deletions.
1 change: 1 addition & 0 deletions androidaudioplugin/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ target_compile_definitions (androidaudioplugin
target_include_directories (androidaudioplugin
PRIVATE
"../../../../include/"
"../../../../external/cmidi2"
)

if (ANDROID)
Expand Down
182 changes: 182 additions & 0 deletions androidaudioplugin/src/main/cpp/core/aap_midi2_helper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#ifndef AAP_CORE_HOSTING_UTILITY_H
#define AAP_CORE_HOSTING_UTILITY_H

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
#include <cmidi2.h>
#pragma clang diagnostic pop


namespace aap {
static inline void merge_event_inputs(void *mergeTmp, int32_t mergeBufSize, void* eventInputs, int32_t eventInputsSize, aap_buffer_t *buffer, PluginInstance* instance) {
if (eventInputsSize == 0)
return;
for (int i = 0; i < instance->getNumPorts(); i++) {
auto port = instance->getPort(i);
if (port->getContentType() == AAP_CONTENT_TYPE_MIDI2 && port->getPortDirection() == AAP_PORT_DIRECTION_INPUT) {
auto mbh = (AAPMidiBufferHeader*) buffer->get_buffer(*buffer, i);
size_t newSize = cmidi2_ump_merge_sequences((cmidi2_ump*) mergeTmp, mergeBufSize,
(cmidi2_ump*) eventInputs, (size_t) eventInputsSize,
(cmidi2_ump*) mbh + 1, (size_t) mbh->length);
mbh->length = newSize;
memcpy(mbh + 1, mergeTmp, newSize);
return;
}
}
}

static uint32_t aapMidi2ExtensionHelperGetUInt32(uint8_t* dst) {
if (cmidi2_util_is_platform_little_endian())
return dst[0] + (dst[1] << 8) + (dst[2] << 16) + (dst[1] << 24);
else
return *((uint32_t*) dst);
}

static void aapMidi2ExtensionHelperPutUInt32(uint8_t* dst, uint32_t value) {
if (cmidi2_util_is_platform_little_endian()) {
dst[0] = value & 0xFF;
dst[1] = (value >> 8) & 0xFF;
dst[2] = (value >> 16) & 0xFF;
dst[3] = value >> 24;
}
else
*((uint32_t*) dst) = value;
}

static void* aapMidi2ExtensionInvokeHelperSysEx8Forge(uint64_t data1, uint64_t data2, size_t index, void* context) {
auto forge = (cmidi2_ump_forge*) context;
if (cmidi2_ump_forge_add_packet_128(forge, data1, data2))
return nullptr;
else
return (void*) true;
}

/**
* Turns AAPXS extension invocation buffer into MIDI2 UMP.
*
* It requires an additional `uint8_t` buffer (`conversionHelperBuffer`) to
* process the actual conversion without extra memory allocation for RT safety.
*
* @param dst the destination buffer
* @param dstSizeInInt the size of `dst`
* @param conversionHelperBuffer the conversion buffer for temporary storage
* @param conversionHelperBufferSize the size of `conversionHelperBuffer`
* @param group "group" field as in MIDI UMP
* @param uri the extension URI (must be null terminated)
* @param data the extension invocation data
* @param dataSize the size of `data`
* @return `true` if success, `false` for failure (so far insufficient memory only).
*/
[[maybe_unused]] // FIXME: test!
static bool aapMidi2GenerateAAPXSSysEx8(uint32_t* dst,
size_t dstSizeInInt,
uint8_t* conversionHelperBuffer,
size_t conversionHelperBufferSize,
uint8_t group,
const char* uri,
const uint8_t* data,
size_t dataSize) {
size_t required = 16 + strlen(uri) + 4 + dataSize * sizeof(uint32_t);
if (dstSizeInInt < required || conversionHelperBufferSize < required)
return false;

uint8_t* sysex = conversionHelperBuffer;
uint8_t* ptr = sysex;
uint8_t sysexStart[] {
0x7Eu, 0x7Fu, // universal sysex
0, 1, // code
0, // extension flags
0, 0, 0, 0 // reserved
};
memcpy(ptr, sysexStart, sizeof(sysexStart));
ptr += sizeof(sysexStart);

// uri_size and uri
size_t strSize = strlen(uri);
aapMidi2ExtensionHelperPutUInt32(ptr, strSize);
ptr += sizeof(uint32_t);
memcpy(ptr, uri, strSize);
ptr += strSize;

// dataSize and data
aapMidi2ExtensionHelperPutUInt32(ptr, dataSize);
ptr += sizeof(uint32_t);
memcpy(ptr, data, dataSize);
ptr += dataSize;

// write sysex data to dst.
cmidi2_ump_forge forge;
cmidi2_ump_forge_init(&forge, dst, dstSizeInInt);
cmidi2_ump_sysex8_process(group, sysex, ptr - sysex, 0,
aapMidi2ExtensionInvokeHelperSysEx8Forge, &forge);

return true;
}

struct aap_midi2_aapxs_parse_context {
uint8_t group;
char uri[AAP_MAX_EXTENSION_URI_SIZE]; // must be null-terminated
uint8_t *data; // buffer must be allocated by the parsing host
uint32_t dataSize; // parsed result
uint8_t* conversionHelperBuffer;
size_t conversionHelperBufferSize;
};

cmidi2_ump_binary_read_state* sysex8_binary_reader_helper_select_stream(uint8_t targetStreamId, void* context) {
return (cmidi2_ump_binary_read_state*) context;
}

// Reads AAPXS SysEx8 UMP into `aap_midi2_aapxs_parse_context`.
// Returns true if it is successfully parsed and it turned out to be AAPXS SysEx8.
// In any other case, return false.
[[maybe_unused]] // FIXME: test!
static bool aapMidi2ParseAAPXSysEx8(aap_midi2_aapxs_parse_context* context, uint8_t* umpData, size_t umpSize) {
cmidi2_ump* ump = (cmidi2_ump*) umpData;
if (cmidi2_ump_get_message_type(ump) != CMIDI2_MESSAGE_TYPE_SYSEX8_MDS)
return false;
if (cmidi2_ump_get_status_code(ump) != CMIDI2_SYSEX_START)
return false;

// Retrieve simple binary data array.
context->group = cmidi2_ump_get_group(ump);
cmidi2_ump_binary_read_state state;
cmidi2_ump_binary_read_state_init(&state, nullptr, context->conversionHelperBuffer, context->conversionHelperBufferSize, false);
if (cmidi2_ump_get_sysex8_data(sysex8_binary_reader_helper_select_stream, &state, nullptr, ump, umpSize / 4) == 0)
return false;
if (state.resultCode != CMIDI2_BINARY_READER_RESULT_COMPLETE)
return false;

// Now state.data (== context->conversionHelperBuffer) contains the entire SysEx8 buffer.
// It's time to parse the buffer according to the AAPXS SysEx8:
// > `[5g sz si 7E] [7F co-de ext-flag] [re-se-rv-ed] [uri-size] [..uri..] [value-size] [..value..]`

// Check if it is long enough to contain the expected data...
if (state.dataSize < 20)
return false;

uint8_t* data = state.data;
// Check if this sysex8 is Universal SysEx, and contains code field for AAPXS
if (data[3] != 0x7E || data[4] != 0x7F || data[5] != 0 || data[6] != 1)
return false;

// filling in results...
context->group = data[0];

size_t uriSize = aapMidi2ExtensionHelperGetUInt32(data + 12);
if (state.dataSize < 20 + uriSize)
return false;
memcpy(context->uri, data + 16, uriSize);

size_t dataSize = aapMidi2ExtensionHelperGetUInt32(data + 16 + uriSize);
if (state.dataSize < 20 + uriSize + dataSize)
return false;
memcpy(context->data, data + 16 + uriSize, dataSize);
context->dataSize = dataSize;

return true;
}
}

#endif //AAP_CORE_HOSTING_UTILITY_H
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include "aap/core/host/shared-memory-store.h"
#include "plugin-service-list.h"
#include "audio-plugin-host-internals.h"
#include "utility.h"
#include "../aap_midi2_helper.h"
#include "aap/core/host/plugin-client-system.h"
#include "../extensions/parameters-service.h"
#include "../extensions/presets-service.h"
Expand Down
31 changes: 0 additions & 31 deletions androidaudioplugin/src/main/cpp/core/hosting/utility.h

This file was deleted.

2 changes: 1 addition & 1 deletion external/cmidi2
Submodule cmidi2 updated 3 files
+1 −1 README.md
+267 −91 cmidi2.h
+84 −0 cmidi2_test.c
1 change: 1 addition & 0 deletions include/aap/android-audio-plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ typedef struct aap_buffer_t {

/* The length of any plugin is limited to this number. */
const int32_t AAP_MAX_PLUGIN_ID_SIZE = 1024;
const int32_t AAP_MAX_EXTENSION_URI_SIZE = 1024;

/* forward declarations */
struct AndroidAudioPluginFactory;
Expand Down
10 changes: 5 additions & 5 deletions include/aap/ext/midi.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ typedef struct AAPMidiBufferHeader {
// messages in SysEx8 format. (see issue #73 for some background)
//
// The SysEx8 UMP packets consist of multiple UMP packets, which are like
// `[5g sz pc 7E] [7F co-de ext-flag] [re-se-rv-ed] [uri-size] [..uri..] [value-size] [..value..]`, where -
// `[5g sz si 7E] [7F co-de ext-flag] [re-se-rv-ed] [uri-size] [..uri..] [value-size] [..value..]`, where -
// - g : UMP group
// - sz : UMP status and size, always 1F
// - pc : UMP packet format, 00 or 01 depending on the size
// - si : stream Id, always 00. AAP MIDI sysex8 does not have to support simultaneous sysex8 streams.
// - co-de : AAP sysex8 code. Always 00-01 for realtime extension controls.
// - ext-flag : reserved; it may be used once we started supporting something like
// - ext : extension flag, reserved as 00; it may be used once we started supporting something like
// "local extension index" (LV2 URID alike).
// - re-se-rv-ed : reserved, always 00 00 00 00.
// - uri-size: string length for extension URI, in 4 bytes
Expand Down Expand Up @@ -68,10 +68,10 @@ typedef struct AAPMidiBufferHeader {
// 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 co-de ch] [k. n. idx.] [value...]`, where -
// looks like `[5g sz si 7E] [7F co-de ch] [k. n. idx.] [value...]`, where -
// - g : UMP group
// - sz : status and size, always 0F
// - pc : packet, always 00
// - si : stream Id, always 00. AAP MIDI sysex8 does not have to support simultaneous sysex8 streams.
// - ch : channel, 00-0F
// - co-de : AAP sysex8 code. Always 00-00 for AAP parameter changes.
// - k. : key for per-note parameter change, 00-7F
Expand Down

0 comments on commit ebc7664

Please sign in to comment.