3262 lines
114 KiB
C++
3262 lines
114 KiB
C++
#ifndef CMIDI2_H_INCLUDED
|
|
#define CMIDI2_H_INCLUDED
|
|
|
|
#include <memory.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#define MIDI_2_0_RESERVED 0
|
|
#define JR_TIMESTAMP_TICKS_PER_SECOND 31250
|
|
|
|
#pragma GCC system_header
|
|
#pragma clang system_header
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
enum cmidi2_status_code
|
|
{
|
|
CMIDI2_STATUS_NOTE_OFF = 0x80,
|
|
CMIDI2_STATUS_NOTE_ON = 0x90,
|
|
CMIDI2_STATUS_PAF = 0xA0,
|
|
CMIDI2_STATUS_CC = 0xB0,
|
|
CMIDI2_STATUS_PROGRAM = 0xC0,
|
|
CMIDI2_STATUS_CAF = 0xD0,
|
|
CMIDI2_STATUS_PITCH_BEND = 0xE0,
|
|
CMIDI2_STATUS_PER_NOTE_RCC = 0x00,
|
|
CMIDI2_STATUS_PER_NOTE_ACC = 0x10,
|
|
CMIDI2_STATUS_RPN = 0x20,
|
|
CMIDI2_STATUS_NRPN = 0x30,
|
|
CMIDI2_STATUS_RELATIVE_RPN = 0x40,
|
|
CMIDI2_STATUS_RELATIVE_NRPN = 0x50,
|
|
CMIDI2_STATUS_PER_NOTE_PITCH_BEND = 0x60,
|
|
CMIDI2_STATUS_PER_NOTE_MANAGEMENT = 0xF0,
|
|
};
|
|
|
|
enum cmidi2_message_type
|
|
{
|
|
CMIDI2_MESSAGE_TYPE_UTILITY = 0,
|
|
CMIDI2_MESSAGE_TYPE_SYSTEM = 1,
|
|
CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL = 2,
|
|
CMIDI2_MESSAGE_TYPE_SYSEX7 = 3,
|
|
CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL = 4,
|
|
CMIDI2_MESSAGE_TYPE_SYSEX8_MDS = 5,
|
|
CMIDI2_MESSAGE_TYPE_FLEX_DATA = 0xD,
|
|
CMIDI2_MESSAGE_TYPE_UMP_STREAM = 0xF,
|
|
};
|
|
|
|
enum cmidi2_ci_protocol_bytes
|
|
{
|
|
CMIDI2_PROTOCOL_BYTES_TYPE = 1,
|
|
CMIDI2_PROTOCOL_BYTES_VERSION = 2,
|
|
CMIDI2_PROTOCOL_BYTES_EXTENSIONS = 3,
|
|
};
|
|
|
|
enum cmidi2_ci_protocol_values
|
|
{
|
|
CMIDI2_PROTOCOL_TYPE_MIDI1 = 1,
|
|
CMIDI2_PROTOCOL_TYPE_MIDI2 = 2,
|
|
CMIDI2_PROTOCOL_VERSION_MIDI1 = 0,
|
|
CMIDI2_PROTOCOL_VERSION_MIDI2_V1 = 0,
|
|
};
|
|
|
|
enum cmidi2_ci_protocol_extensions
|
|
{
|
|
CMIDI2_PROTOCOL_EXTENSIONS_JITTER = 1,
|
|
CMIDI2_PROTOCOL_EXTENSIONS_LARGER = 2, // only for MIDI 1.0 compat UMP
|
|
};
|
|
|
|
typedef struct cmidi2_ci_protocol_tag
|
|
{
|
|
uint8_t protocol_type; // cmidi2_ci_protocol_bytes
|
|
uint8_t version; // cmidi2_ci_protocol_values
|
|
uint8_t extensions; // cmidi2_ci_protocol_extensions (flags)
|
|
uint8_t reserved1;
|
|
uint8_t reserved2;
|
|
} cmidi2_ci_protocol;
|
|
|
|
enum cmidi2_cc
|
|
{
|
|
CMIDI2_CC_BANK_SELECT = 0x00,
|
|
CMIDI2_CC_MODULATION = 0x01,
|
|
CMIDI2_CC_BREATH = 0x02,
|
|
CMIDI2_CC_FOOT = 0x04,
|
|
CMIDI2_CC_PORTAMENTO_TIME = 0x05,
|
|
CMIDI2_CC_DTE_MSB = 0x06,
|
|
CMIDI2_CC_VOLUME = 0x07,
|
|
CMIDI2_CC_BALANCE = 0x08,
|
|
CMIDI2_CC_PAN = 0x0A,
|
|
CMIDI2_CC_EXPRESSION = 0x0B,
|
|
CMIDI2_CC_EFFECT_CONTROL_1 = 0x0C,
|
|
CMIDI2_CC_EFFECT_CONTROL_2 = 0x0D,
|
|
CMIDI2_CC_GENERAL_1 = 0x10,
|
|
CMIDI2_CC_GENERAL_2 = 0x11,
|
|
CMIDI2_CC_GENERAL_3 = 0x12,
|
|
CMIDI2_CC_GENERAL_4 = 0x13,
|
|
CMIDI2_CC_BANK_SELECT_LSB = 0x20,
|
|
CMIDI2_CC_MODULATION_LSB = 0x21,
|
|
CMIDI2_CC_BREATH_LSB = 0x22,
|
|
CMIDI2_CC_FOOT_LSB = 0x24,
|
|
CMIDI2_CC_PORTAMENTO_TIME_LSB = 0x25,
|
|
CMIDI2_CC_DTE_LSB = 0x26,
|
|
CMIDI2_CC_VOLUME_LSB = 0x27,
|
|
CMIDI2_CC_BALANCE_LSB = 0x28,
|
|
CMIDI2_CC_PAN_LSB = 0x2A,
|
|
CMIDI2_CC_EXPRESSION_LSB = 0x2B,
|
|
CMIDI2_CC_EFFECT1_LSB = 0x2C,
|
|
CMIDI2_CC_EFFECT2_LSB = 0x2D,
|
|
CMIDI2_CC_GENERAL_1_LSB = 0x30,
|
|
CMIDI2_CC_GENERAL_2_LSB = 0x31,
|
|
CMIDI2_CC_GENERAL_3_LSB = 0x32,
|
|
CMIDI2_CC_GENERAL_4_LSB = 0x33,
|
|
CMIDI2_CC_HOLD = 0x40,
|
|
CMIDI2_CC_PORTAMENTO_SWITCH = 0x41,
|
|
CMIDI2_CC_SOSTENUTO = 0x42,
|
|
CMIDI2_CC_SOFT_PEDAL = 0x43,
|
|
CMIDI2_CC_LEGATO = 0x44,
|
|
CMIDI2_CC_HOLD_2 = 0x45,
|
|
CMIDI2_CC_SOUND_CONTROLLER_1 = 0x46,
|
|
CMIDI2_CC_SOUND_CONTROLLER_2 = 0x47,
|
|
CMIDI2_CC_SOUND_CONTROLLER_3 = 0x48,
|
|
CMIDI2_CC_SOUND_CONTROLLER_4 = 0x49,
|
|
CMIDI2_CC_SOUND_CONTROLLER_5 = 0x4A,
|
|
CMIDI2_CC_SOUND_CONTROLLER_6 = 0x4B,
|
|
CMIDI2_CC_SOUND_CONTROLLER_7 = 0x4C,
|
|
CMIDI2_CC_SOUND_CONTROLLER_8 = 0x4D,
|
|
CMIDI2_CC_SOUND_CONTROLLER_9 = 0x4E,
|
|
CMIDI2_CC_SOUND_CONTROLLER_10 = 0x4F,
|
|
CMIDI2_CC_GENERAL_5 = 0x50,
|
|
CMIDI2_CC_GENERAL_6 = 0x51,
|
|
CMIDI2_CC_GENERAL_7 = 0x52,
|
|
CMIDI2_CC_GENERAL_8 = 0x53,
|
|
CMIDI2_CC_PORTAMENTO_CONTROL = 0x54,
|
|
CMIDI2_CC_RSD = 0x5B,
|
|
CMIDI2_CC_EFFECT_1 = 0x5B,
|
|
CMIDI2_CC_TREMOLO = 0x5C,
|
|
CMIDI2_CC_EFFECT_2 = 0x5C,
|
|
CMIDI2_CC_CSD = 0x5D,
|
|
CMIDI2_CC_EFFECT_3 = 0x5D,
|
|
CMIDI2_CC_CELESTE = 0x5E,
|
|
CMIDI2_CC_EFFECT_4 = 0x5E,
|
|
CMIDI2_CC_PHASER = 0x5F,
|
|
CMIDI2_CC_EFFECT_5 = 0x5F,
|
|
CMIDI2_CC_DTE_INCREMENT = 0x60,
|
|
CMIDI2_CC_DTE_DECREMENT = 0x61,
|
|
CMIDI2_CC_NRPN_LSB = 0x62,
|
|
CMIDI2_CC_NRPN_MSB = 0x63,
|
|
CMIDI2_CC_RPN_LSB = 0x64,
|
|
CMIDI2_CC_RPN_MSB = 0x65,
|
|
// CHANNEL MODE MESSAGES
|
|
CMIDI2_CC_ALL_SOUND_OFF = 0x78,
|
|
CMIDI2_CC_RESET_ALL_CONTROLLERS = 0x79,
|
|
CMIDI2_CC_LOCAL_CONTROL = 0x7A,
|
|
CMIDI2_CC_ALL_NOTES_OFF = 0x7B,
|
|
CMIDI2_CC_OMNI_MODE_OFF = 0x7C,
|
|
CMIDI2_CC_OMNI_MODE_ON = 0x7D,
|
|
CMIDI2_CC_POLY_MODE_ON_OFF = 0x7E,
|
|
CMIDI2_CC_POLY_MODE_ON = 0x7F,
|
|
};
|
|
|
|
enum cmidi2_rpn
|
|
{
|
|
CMIDI2_RPN_PITCH_BEND_SENSITIVITY = 0,
|
|
CMIDI2_RPN_FINE_TUNING = 1,
|
|
CMIDI2_RPN_COARSE_TUNING = 2,
|
|
CMIDI2_RPN_TUNING_PROGRAM = 3,
|
|
CMIDI2_RPN_TUNING_BANK_SELECT = 4,
|
|
CMIDI2_RPN_MODULATION_DEPTH = 5,
|
|
};
|
|
|
|
enum cmidi2_meta_event_type
|
|
{
|
|
CMIDI2_META_SEQUENCE_NUMBER = 0X00,
|
|
CMIDI2_META_TEXT = 0X01,
|
|
CMIDI2_META_COPYRIGHT = 0X02,
|
|
CMIDI2_META_TRACK_NAME = 0X03,
|
|
CMIDI2_META_INSTRUMENT_NAME = 0X04,
|
|
CMIDI2_META_LYRIC = 0X05,
|
|
CMIDI2_META_MARKER = 0X06,
|
|
CMIDI2_META_CUE = 0X07,
|
|
CMIDI2_META_CHANNEL_PREFIX = 0X20,
|
|
CMIDI2_META_END_OF_TRACK = 0X2F,
|
|
CMIDI2_META_TEMPO = 0X51,
|
|
CMIDI2_META_SMPTE_OFFSET = 0X54,
|
|
CMIDI2_META_TIME_SIGNATURE = 0X58,
|
|
CMIDI2_META_KEY_SIGNATURE = 0X59,
|
|
CMIDI2_META_SEQUENCER_SPECIFIC = 0X7F,
|
|
};
|
|
|
|
enum cmidi2_per_note_management_flags
|
|
{
|
|
CMIDI2_PER_NOTE_MANAGEMENT_RESET = 1,
|
|
CMIDI2_PER_NOTE_MANAGEMENT_DETACH = 2,
|
|
};
|
|
|
|
enum cmidi2_note_attribute_type
|
|
{
|
|
CMIDI2_ATTRIBUTE_TYPE_NONE = 0,
|
|
CMIDI2_ATTRIBUTE_TYPE_MANUFACTURER = 1,
|
|
CMIDI2_ATTRIBUTE_TYPE_PROFILE = 2,
|
|
CMIDI2_ATTRIBUTE_TYPE_PITCH7_9 = 3,
|
|
};
|
|
|
|
enum cmidi2_program_change_option_flags
|
|
{
|
|
CMIDI2_PROGRAM_CHANGE_OPTION_NONE = 0,
|
|
CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID = 1,
|
|
};
|
|
|
|
enum cmidi2_sysex_status
|
|
{
|
|
CMIDI2_SYSEX_IN_ONE_UMP = 0,
|
|
CMIDI2_SYSEX_START = 0x10,
|
|
CMIDI2_SYSEX_CONTINUE = 0x20,
|
|
CMIDI2_SYSEX_END = 0x30,
|
|
};
|
|
|
|
enum cmidi2_mixed_data_set_status
|
|
{
|
|
CMIDI2_MIXED_DATA_STATUS_HEADER = 0x80,
|
|
CMIDI2_MIXED_DATA_STATUS_PAYLOAD = 0x90,
|
|
};
|
|
|
|
enum cmidi2_system_message_status
|
|
{
|
|
CMIDI2_SYSTEM_STATUS_MIDI_TIME_CODE = 0xF1,
|
|
CMIDI2_SYSTEM_STATUS_SONG_POSITION = 0xF2,
|
|
CMIDI2_SYSTEM_STATUS_SONG_SELECT = 0xF3,
|
|
CMIDI2_SYSTEM_STATUS_TUNE_REQUEST = 0xF6,
|
|
CMIDI2_SYSTEM_STATUS_TIMING_CLOCK = 0xF8,
|
|
CMIDI2_SYSTEM_STATUS_START = 0xFA,
|
|
CMIDI2_SYSTEM_STATUS_CONTINUE = 0xFB,
|
|
CMIDI2_SYSTEM_STATUS_STOP = 0xFC,
|
|
CMIDI2_SYSTEM_STATUS_ACTIVE_SENSING = 0xFE,
|
|
CMIDI2_SYSTEM_STATUS_RESET = 0xFF,
|
|
};
|
|
|
|
// TODO: remove this. Use cmidi2_utility_message_status instead
|
|
enum cmidi2_jr_timestamp_status
|
|
{
|
|
CMIDI2_JR_CLOCK = 0x10,
|
|
CMIDI2_JR_TIMESTAMP = 0x20,
|
|
};
|
|
|
|
enum cmidi2_utility_message_status
|
|
{
|
|
CMIDI2_UTILITY_STATUS_NOOP = 0,
|
|
CMIDI2_UTILITY_STATUS_JR_CLOCK = 0x10,
|
|
CMIDI2_UTILITY_STATUS_JR_TIMESTAMP = 0x20,
|
|
CMIDI2_UTILITY_STATUS_DCTPQ = 0x30,
|
|
CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP = 0x40,
|
|
};
|
|
|
|
enum cmidi2_flex_data_status_bank
|
|
{
|
|
CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE = 0,
|
|
CMIDI2_FLEX_DATA_BANK_METADATA_TEXT = 1,
|
|
CMIDI2_FLEX_DATA_BANK_PERFORMANCE_TEXT = 2
|
|
};
|
|
enum cmidi2_flex_data_setup_status
|
|
{
|
|
CMIDI2_FLEX_DATA_STATUS_SET_TEMPO = 0,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_TIME_SIGNATURE = 1,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_METRONOME = 2,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_KEY_SIGNATURE = 5,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_CHORD_NAME = 6
|
|
};
|
|
enum cmidi2_flex_data_metadata_text_status
|
|
{
|
|
CMIDI2_FLEX_DATA_STATUS_UNKNOWN_METADATA_TEXT = 0,
|
|
CMIDI2_FLEX_DATA_STATUS_PROJECT_NAME = 1,
|
|
CMIDI2_FLEX_DATA_STATUS_SONG_NAME = 2,
|
|
CMIDI2_FLEX_DATA_STATUS_CLIP_NAME = 3,
|
|
CMIDI2_FLEX_DATA_STATUS_COPYRIGHT_NAME = 4,
|
|
CMIDI2_FLEX_DATA_STATUS_COMPOSER_NAME = 5,
|
|
CMIDI2_FLEX_DATA_STATUS_LYRICIST_NAME = 6,
|
|
CMIDI2_FLEX_DATA_STATUS_ARRANGER_NAME = 7,
|
|
CMIDI2_FLEX_DATA_STATUS_PUBLISHER_NAME = 8,
|
|
CMIDI2_FLEX_DATA_STATUS_PRIMARY_PERFORMER_NAME = 9,
|
|
CMIDI2_FLEX_DATA_STATUS_ACCOMPANYING_PERFORMAER_NAME = 10,
|
|
CMIDI2_FLEX_DATA_STATUS_RECORDING_DATE = 11,
|
|
CMIDI2_FLEX_DATA_STATUS_RECORDING_LOCATION = 12
|
|
};
|
|
enum cmidi2_flex_data_performance_text_status
|
|
{
|
|
CMIDI2_FLEX_DATA_STATUS_UNKNOWN_PERFORMANCE_TEXT = 0,
|
|
CMIDI2_FLEX_DATA_STATUS_LYRICS = 1,
|
|
CMIDI2_FLEX_DATA_STATUS_LYRICS_LANGUAGE = 2,
|
|
CMIDI2_FLEX_DATA_STATUS_RUBY = 3,
|
|
CMIDI2_FLEX_DATA_STATUS_RUBY_LANGUAGE = 4
|
|
};
|
|
|
|
enum cmidi2_ump_chord_name_sharps_flats
|
|
{
|
|
CMIDI2_UMP_CHORD_NAME_DOUBLE_SHARP = 2,
|
|
CMIDI2_UMP_CHORD_NAME_SHARP = 1,
|
|
CMIDI2_UMP_CHORD_NAME_NATURAL = 0,
|
|
CMIDI2_UMP_CHORD_NAME_FLAT = 0xF,
|
|
CMIDI2_UMP_CHORD_NAME_DOUBLE_FLAT = 0xE,
|
|
};
|
|
|
|
enum cmidi2_ump_chord_name_tonic_note
|
|
{
|
|
CMIDI2_UMP_CHORD_NAME_UNKNOWN = 0,
|
|
CMIDI2_UMP_CHORD_NAME_A = 1,
|
|
CMIDI2_UMP_CHORD_NAME_B = 2,
|
|
CMIDI2_UMP_CHORD_NAME_C = 3,
|
|
CMIDI2_UMP_CHORD_NAME_D = 4,
|
|
CMIDI2_UMP_CHORD_NAME_E = 5,
|
|
CMIDI2_UMP_CHORD_NAME_F = 6,
|
|
CMIDI2_UMP_CHORD_NAME_G = 7,
|
|
};
|
|
|
|
enum cmidi2_ump_chor_name_chord_type
|
|
{
|
|
CMIDI2_UMP_CHORD_TYPE_UNKNOWN = 0,
|
|
CMIDI2_UMP_CHORD_TYPE_MAJOR = 1,
|
|
CMIDI2_UMP_CHORD_TYPE_MAJOR_6TH = 2,
|
|
CMIDI2_UMP_CHORD_TYPE_MAJOR_7TH = 3,
|
|
CMIDI2_UMP_CHORD_TYPE_MAJOR_9TH = 4,
|
|
CMIDI2_UMP_CHORD_TYPE_MAJOR_11TH = 5,
|
|
CMIDI2_UMP_CHORD_TYPE_MAJOR_13TH = 6,
|
|
CMIDI2_UMP_CHORD_TYPE_MINOR = 7,
|
|
CMIDI2_UMP_CHORD_TYPE_MINOR_6TH = 8,
|
|
CMIDI2_UMP_CHORD_TYPE_MINOR_7TH = 9,
|
|
CMIDI2_UMP_CHORD_TYPE_MINOR_9TH = 10,
|
|
CMIDI2_UMP_CHORD_TYPE_MINOR_11TH = 11,
|
|
CMIDI2_UMP_CHORD_TYPE_MINOR_13TH = 12,
|
|
CMIDI2_UMP_CHORD_TYPE_DOMINANT = 13,
|
|
CMIDI2_UMP_CHORD_TYPE_DOMINANT_9TH = 14,
|
|
CMIDI2_UMP_CHORD_TYPE_DOMINANT_11TH = 15,
|
|
CMIDI2_UMP_CHORD_TYPE_DOMINANT_13TH = 16,
|
|
CMIDI2_UMP_CHORD_TYPE_AUGMENTED = 17,
|
|
CMIDI2_UMP_CHORD_TYPE_AUGMENTED_7TH = 18,
|
|
CMIDI2_UMP_CHORD_TYPE_DIMINISHED = 19,
|
|
CMIDI2_UMP_CHORD_TYPE_DIMINISHED_7TH = 20,
|
|
CMIDI2_UMP_CHORD_TYPE_HALF_DIMINISHED = 21,
|
|
CMIDI2_UMP_CHORD_TYPE_MAJOR_MINOR = 22,
|
|
CMIDI2_UMP_CHORD_TYPE_MINOR_MAJOR = 22, // same
|
|
CMIDI2_UMP_CHORD_TYPE_PEDAL = 23,
|
|
CMIDI2_UMP_CHORD_TYPE_POWER = 24,
|
|
CMIDI2_UMP_CHORD_TYPE_SUSPENDED_2ND = 25,
|
|
CMIDI2_UMP_CHORD_TYPE_SUSPENDED_4TH = 26,
|
|
CMIDI2_UMP_CHORD_TYPE_7_SUSPENDED_4TH = 27,
|
|
};
|
|
|
|
static inline uint8_t cmidi2_ump_get_num_bytes(uint32_t data)
|
|
{
|
|
switch (((data & 0xF0000000) >> 28) & 0xF)
|
|
{
|
|
case CMIDI2_MESSAGE_TYPE_UTILITY:
|
|
case CMIDI2_MESSAGE_TYPE_SYSTEM:
|
|
case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:
|
|
return 4;
|
|
case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:
|
|
case CMIDI2_MESSAGE_TYPE_SYSEX7:
|
|
return 8;
|
|
case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:
|
|
return 16;
|
|
}
|
|
return 0xFF; /* wrong */
|
|
}
|
|
|
|
typedef struct cmidi2_ump128
|
|
{
|
|
uint32_t p1;
|
|
uint32_t p2;
|
|
uint32_t p3;
|
|
uint32_t p4;
|
|
} cmidi2_ump128_t;
|
|
|
|
typedef struct cmidi2_ump_version
|
|
{
|
|
uint8_t major;
|
|
uint8_t minor;
|
|
} cmidi2_ump_version_t;
|
|
|
|
enum cmidi2_ump_stream_status
|
|
{
|
|
CMIDI2_UMP_STREAM_STATUS_ENDPOINT_DISCOVERY = 0,
|
|
CMIDI2_UMP_STREAM_STATUS_ENDPOINT_INFO = 1,
|
|
CMIDI2_UMP_STREAM_STATUS_DEVICE_IDENTITY = 2,
|
|
CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME = 3,
|
|
CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID = 4,
|
|
CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_REQUEST = 5,
|
|
CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_NOTIFICATION = 6,
|
|
CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_DISCOVERY = 0x10,
|
|
CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_INFO = 0x11,
|
|
CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME = 0x12,
|
|
CMIDI2_UMP_STREAM_STATUS_START_OF_CLIP = 0x20,
|
|
CMIDI2_UMP_STREAM_STATUS_END_OF_CLIP = 0x21,
|
|
};
|
|
|
|
enum cmidi2_ump_endpoint_filter_flags
|
|
{
|
|
CMIDI2_UMP_ENDPOINT_FILTER_ENDPOINT_INFO = 1,
|
|
CMIDI2_UMP_ENDPOINT_FILTER_DEVICE_IDENTITY = 2,
|
|
CMIDI2_UMP_ENDPOINT_FILTER_ENDPOINT_NAME = 4,
|
|
CMIDI2_UMP_ENDPOINT_FILTER_PRODUCT_INSTANCE_ID = 8,
|
|
CMIDI2_UMP_ENDPOINT_FILTER_STREAM_CONFIGURATION = 0x10,
|
|
};
|
|
|
|
enum cmidi2_ump_function_block_discovery_flags
|
|
{
|
|
CMIDI2_UMP_FUNCTION_BLOCK_FILTER_INFO = 1,
|
|
CMIDI2_UMP_FUNCTION_BLOCK_FILTER_NAME = 2,
|
|
};
|
|
|
|
// --------
|
|
// UMP generators
|
|
|
|
// 7.1 UMP Stream Messages
|
|
|
|
static inline cmidi2_ump128_t
|
|
cmidi2_ump_endpoint_discovery(cmidi2_ump_version_t version, uint8_t filterBitmap)
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)((CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_ENDPOINT_DISCOVERY << 16) + (version.major << 8)
|
|
+ version.minor),
|
|
(uint32_t)(filterBitmap & 0x1F), 0, 0};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_endpoint_info_notification(
|
|
cmidi2_ump_version_t version, bool isStaticFunctionBlock, uint8_t numFunctionBlocks,
|
|
bool midi2Capable, bool midi1Capable, bool rxJR, bool txJR)
|
|
{
|
|
cmidi2_ump128_t ret = {
|
|
(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_ENDPOINT_INFO << 16) + (version.major << 8) + version.minor,
|
|
(uint32_t)(isStaticFunctionBlock ? 1 << 31 : 0) + (numFunctionBlocks << 24)
|
|
+ (midi2Capable ? 0x1000 : 0) + (midi1Capable ? 0x100 : 0) + (rxJR ? 2 : 0)
|
|
+ (txJR ? 1 : 0),
|
|
0, 0};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_device_identity_notification(
|
|
uint32_t manufacturerIdIn7bitArray, uint8_t deviceFamilyLSB, uint8_t deviceFamilyMSB,
|
|
uint8_t deviceFamilyModelLSB, uint8_t deviceFamilyModelMSB,
|
|
uint32_t softwareRevisionIn7bitArray)
|
|
{
|
|
(void)deviceFamilyModelMSB;
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_DEVICE_IDENTITY << 16),
|
|
manufacturerIdIn7bitArray,
|
|
(uint32_t)(deviceFamilyLSB << 24) + (deviceFamilyMSB << 16) + (deviceFamilyModelLSB << 8)
|
|
+ deviceFamilyMSB,
|
|
softwareRevisionIn7bitArray};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t
|
|
cmidi2_ump_internal_name_notification(uint8_t statusCode, const char name[14])
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28) + (statusCode << 16) + (name[0] << 8)
|
|
+ name[1],
|
|
(uint32_t)(name[2] << 24) + (name[3] << 16) + (name[4] << 8) + name[5],
|
|
(uint32_t)(name[6] << 24) + (name[7] << 16) + (name[8] << 8) + name[9],
|
|
(uint32_t)(name[10] << 24) + (name[11] << 16) + (name[12] << 8) + name[13]};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_endpoint_name_notification(const char name[14])
|
|
{
|
|
return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_ENDPOINT_NAME, name);
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_product_instance_id_notification(const char id[14])
|
|
{
|
|
return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_PRODUCT_INSTANCE_ID, id);
|
|
}
|
|
|
|
static inline cmidi2_ump128_t
|
|
cmidi2_ump_stream_configuration_request(uint8_t protocol, bool rxJR, bool txJR)
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_REQUEST << 16) + (protocol << 8)
|
|
+ (rxJR ? 2 : 0) + (txJR ? 1 : 0),
|
|
0, 0, 0};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t
|
|
cmidi2_ump_stream_configuration_notification(uint8_t protocol, bool rxJR, bool txJR)
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_STREAM_CONFIGURATION_NOTIFICATION << 16) + (protocol << 8)
|
|
+ (rxJR ? 2 : 0) + (txJR ? 1 : 0),
|
|
0, 0, 0};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t
|
|
cmidi2_ump_function_block_discovery(uint8_t numFunctionBlocks, uint8_t filter)
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_DISCOVERY << 16) + (numFunctionBlocks << 8)
|
|
+ filter,
|
|
0, 0, 0};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_function_block_info_notification(
|
|
bool active, uint8_t numFunctionBlocks, uint8_t uiHint, uint8_t midi1, uint8_t direction,
|
|
uint8_t firstGroup, uint8_t numSpannedGroup, uint8_t midiCIVersionFormat,
|
|
uint8_t maxNumSysEx8Streams)
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_INFO << 16) + (active ? 0x8000 : 0)
|
|
+ (numFunctionBlocks << 8) + (uiHint << 4) + (midi1 << 2) + direction,
|
|
(uint32_t)(firstGroup << 24) + (numSpannedGroup << 16) + (midiCIVersionFormat << 8)
|
|
+ maxNumSysEx8Streams,
|
|
0, 0};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_function_block_name_notification(const char name[14])
|
|
{
|
|
return cmidi2_ump_internal_name_notification(CMIDI2_UMP_STREAM_STATUS_FUNCTION_BLOCK_NAME, name);
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_start_of_clip()
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_START_OF_CLIP << 16),
|
|
0, 0, 0};
|
|
return ret;
|
|
}
|
|
|
|
static inline cmidi2_ump128_t cmidi2_ump_end_of_clip()
|
|
{
|
|
cmidi2_ump128_t ret
|
|
= {(uint32_t)(CMIDI2_MESSAGE_TYPE_UMP_STREAM << 28)
|
|
+ (CMIDI2_UMP_STREAM_STATUS_END_OF_CLIP << 16),
|
|
0, 0, 0};
|
|
return ret;
|
|
}
|
|
|
|
// 7.2 Utility Messages
|
|
static inline uint32_t cmidi2_ump_noop(uint8_t group)
|
|
{
|
|
return (group & 0xF) << 24;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_jr_clock_direct(uint8_t group, uint32_t senderClockTime)
|
|
{
|
|
return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + senderClockTime;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_jr_clock(uint8_t group, double senderClockTime)
|
|
{
|
|
uint16_t value = (uint16_t)(senderClockTime * JR_TIMESTAMP_TICKS_PER_SECOND);
|
|
return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_CLOCK << 16) + value;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_jr_timestamp_direct(uint8_t group, uint16_t senderClockTimestamp)
|
|
{
|
|
return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16)
|
|
+ senderClockTimestamp;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_jr_timestamp(uint8_t group, double senderClockTimestamp)
|
|
{
|
|
uint16_t value = (uint16_t)(senderClockTimestamp * JR_TIMESTAMP_TICKS_PER_SECOND);
|
|
return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_JR_TIMESTAMP << 16) + value;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_dctpq(uint8_t group, uint32_t dctpq)
|
|
{
|
|
return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DCTPQ << 16) + dctpq;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_dcs(uint8_t group, uint32_t ticks)
|
|
{
|
|
// Note that unlike JR timestamp delta clockstamps accepts ticks up to 20 bits.
|
|
return cmidi2_ump_noop(group) + (CMIDI2_UTILITY_STATUS_DELTA_CLOCKSTAMP << 16)
|
|
+ (ticks & 0xFFFFF);
|
|
}
|
|
|
|
// 7.6 System Common and System Real Time Messages
|
|
static inline int32_t
|
|
cmidi2_ump_system_message(uint8_t group, uint8_t status, uint8_t midi1Byte2, uint8_t midi1Byte3)
|
|
{
|
|
return (CMIDI2_MESSAGE_TYPE_SYSTEM << 28) + ((group & 0xF) << 24) + (status << 16)
|
|
+ ((midi1Byte2 & 0x7F) << 8) + (midi1Byte3 & 0x7F);
|
|
}
|
|
|
|
// 7.3 MIDI 1.0 Channel Voice Messages
|
|
static inline int32_t cmidi2_ump_midi1_message(
|
|
uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4)
|
|
{
|
|
return (CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL << 28) + ((group & 0xF) << 24)
|
|
+ (((code & 0xF0) + (channel & 0xF)) << 16) + ((byte3 & 0x7F) << 8) + (byte4 & 0x7F);
|
|
}
|
|
|
|
static inline int32_t
|
|
cmidi2_ump_midi1_note_off(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity)
|
|
{
|
|
return cmidi2_ump_midi1_message(
|
|
group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, velocity & 0x7F);
|
|
}
|
|
|
|
static inline int32_t
|
|
cmidi2_ump_midi1_note_on(uint8_t group, uint8_t channel, uint8_t note, uint8_t velocity)
|
|
{
|
|
return cmidi2_ump_midi1_message(
|
|
group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, velocity & 0x7F);
|
|
}
|
|
|
|
static inline int32_t
|
|
cmidi2_ump_midi1_paf(uint8_t group, uint8_t channel, uint8_t note, uint8_t data)
|
|
{
|
|
return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_PAF, channel, note & 0x7F, data & 0x7F);
|
|
}
|
|
|
|
static inline int32_t
|
|
cmidi2_ump_midi1_cc(uint8_t group, uint8_t channel, uint8_t index, uint8_t data)
|
|
{
|
|
return cmidi2_ump_midi1_message(group, CMIDI2_STATUS_CC, channel, index & 0x7F, data & 0x7F);
|
|
}
|
|
|
|
static inline int32_t cmidi2_ump_midi1_program(uint8_t group, uint8_t channel, uint8_t program)
|
|
{
|
|
return cmidi2_ump_midi1_message(
|
|
group, CMIDI2_STATUS_PROGRAM, channel, program & 0x7F, MIDI_2_0_RESERVED);
|
|
}
|
|
|
|
static inline int32_t cmidi2_ump_midi1_caf(uint8_t group, uint8_t channel, uint8_t data)
|
|
{
|
|
return cmidi2_ump_midi1_message(
|
|
group, CMIDI2_STATUS_CAF, channel, data & 0x7F, MIDI_2_0_RESERVED);
|
|
}
|
|
|
|
static inline int32_t
|
|
cmidi2_ump_midi1_pitch_bend_direct(uint8_t group, uint8_t channel, uint16_t data)
|
|
{
|
|
return cmidi2_ump_midi1_message(
|
|
group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F);
|
|
}
|
|
|
|
static inline int32_t
|
|
cmidi2_ump_midi1_pitch_bend_split(uint8_t group, uint8_t channel, uint8_t dataLSB, uint8_t dataMSB)
|
|
{
|
|
return cmidi2_ump_midi1_message(
|
|
group, CMIDI2_STATUS_PITCH_BEND, channel, dataLSB & 0x7F, dataMSB & 0x7F);
|
|
}
|
|
|
|
static inline int32_t cmidi2_ump_midi1_pitch_bend(uint8_t group, uint8_t channel, int16_t data)
|
|
{
|
|
data += 8192;
|
|
return cmidi2_ump_midi1_message(
|
|
group, CMIDI2_STATUS_PITCH_BEND, channel, data & 0x7F, (data >> 7) & 0x7F);
|
|
}
|
|
|
|
// 7.4 MIDI 2.0 Channel Voice Messages
|
|
static inline int64_t cmidi2_ump_midi2_channel_message_8_8_16_16(
|
|
uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint16_t short1,
|
|
uint16_t short2)
|
|
{
|
|
return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24)
|
|
+ (((code & 0xF0) + (channel & 0xF)) << 16) + (byte3 << 8) + byte4)
|
|
<< 32)
|
|
+ ((uint64_t)short1 << 16) + short2;
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_channel_message_8_8_32(
|
|
uint8_t group, uint8_t code, uint8_t channel, uint8_t byte3, uint8_t byte4, uint32_t rest)
|
|
{
|
|
return (((uint64_t)(CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL << 28) + ((group & 0xF) << 24)
|
|
+ (((code & 0xF0) + (channel & 0xF)) << 16) + (byte3 << 8) + byte4)
|
|
<< 32)
|
|
+ rest;
|
|
}
|
|
|
|
static inline uint16_t cmidi2_ump_pitch_7_9(double semitone)
|
|
{
|
|
double actual = semitone < 0.0 ? 0.0 : semitone >= 128.0 ? 128.0 : semitone;
|
|
uint16_t dec = (uint16_t)actual;
|
|
double microtone = actual - dec;
|
|
return (dec << 9) + (int)(microtone * 512.0);
|
|
}
|
|
|
|
static inline uint16_t cmidi2_ump_pitch_7_9_split(uint8_t semitone, double microtone)
|
|
{
|
|
uint16_t ret = (uint16_t)(semitone & 0x7F) << 9;
|
|
double actual = microtone < 0.0 ? 0.0 : microtone > 1.0 ? 1.0 : microtone;
|
|
ret += (int)(actual * 512.0);
|
|
return ret;
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_note_off(
|
|
uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity,
|
|
uint16_t attributeData)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_16_16(
|
|
group, CMIDI2_STATUS_NOTE_OFF, channel, note & 0x7F, attributeType, velocity, attributeData);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_note_on(
|
|
uint8_t group, uint8_t channel, uint8_t note, uint8_t attributeType, uint16_t velocity,
|
|
uint16_t attributeData)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_16_16(
|
|
group, CMIDI2_STATUS_NOTE_ON, channel, note & 0x7F, attributeType, velocity, attributeData);
|
|
}
|
|
|
|
static inline int64_t
|
|
cmidi2_ump_midi2_paf(uint8_t group, uint8_t channel, uint8_t note, uint32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_PAF, channel, note & 0x7F, MIDI_2_0_RESERVED, data);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_per_note_rcc(
|
|
uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_PER_NOTE_RCC, channel, note & 0x7F, index, data);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_per_note_acc(
|
|
uint8_t group, uint8_t channel, uint8_t note, uint8_t index, uint32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_PER_NOTE_ACC, channel, note & 0x7F, index, data);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_per_note_management(
|
|
uint8_t group, uint8_t channel, uint8_t note, uint8_t optionFlags)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_PER_NOTE_MANAGEMENT, channel, note & 0x7F, optionFlags & 3, 0);
|
|
}
|
|
|
|
static inline int64_t
|
|
cmidi2_ump_midi2_cc(uint8_t group, uint8_t channel, uint8_t index, uint32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_CC, channel, index & 0x7F, MIDI_2_0_RESERVED, data);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_rpn(
|
|
uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_nrpn(
|
|
uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F, dataAkaDTE);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_relative_rpn(
|
|
uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_RELATIVE_RPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F,
|
|
dataAkaDTE);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_relative_nrpn(
|
|
uint8_t group, uint8_t channel, uint8_t bankAkaMSB, uint8_t indexAkaLSB, uint32_t dataAkaDTE)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_RELATIVE_NRPN, channel, bankAkaMSB & 0x7F, indexAkaLSB & 0x7F,
|
|
dataAkaDTE);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_program(
|
|
uint8_t group, uint8_t channel, uint8_t optionFlags, uint8_t program, uint8_t bankMSB,
|
|
uint8_t bankLSB)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_PROGRAM, channel, MIDI_2_0_RESERVED, optionFlags & 1,
|
|
((program & 0x7F) << 24) + (bankMSB << 8) + bankLSB);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_caf(uint8_t group, uint8_t channel, uint32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_CAF, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED, data);
|
|
}
|
|
|
|
static inline int64_t
|
|
cmidi2_ump_midi2_pitch_bend_direct(uint8_t group, uint8_t channel, uint32_t unsignedData)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_PITCH_BEND, channel, MIDI_2_0_RESERVED, MIDI_2_0_RESERVED,
|
|
unsignedData);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_pitch_bend(uint8_t group, uint8_t channel, int32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_pitch_bend_direct(group, channel, 0x80000000 + data);
|
|
}
|
|
|
|
static inline int64_t cmidi2_ump_midi2_per_note_pitch_bend_direct(
|
|
uint8_t group, uint8_t channel, uint8_t note, uint32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_channel_message_8_8_32(
|
|
group, CMIDI2_STATUS_PER_NOTE_PITCH_BEND, channel, note & 0x7F, MIDI_2_0_RESERVED, data);
|
|
}
|
|
|
|
static inline int64_t
|
|
cmidi2_ump_midi2_per_note_pitch_bend(uint8_t group, uint8_t channel, uint8_t note, uint32_t data)
|
|
{
|
|
return cmidi2_ump_midi2_per_note_pitch_bend_direct(group, channel, note, 0x80000000 + data);
|
|
}
|
|
|
|
// Common utility functions for sysex support
|
|
|
|
static inline uint8_t cmidi2_ump_get_byte_from_uint32(uint32_t src, uint8_t index)
|
|
{
|
|
return (uint8_t)(src >> ((7 - index) * 8) & 0xFF);
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_byte_from_uint64(uint64_t src, uint8_t index)
|
|
{
|
|
return (uint8_t)(src >> ((7 - index) * 8) & 0xFF);
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_sysex_get_num_packets(uint8_t numBytes, uint8_t radix)
|
|
{
|
|
return numBytes <= radix ? 1 : numBytes / radix + (numBytes % radix ? 1 : 0);
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_read_uint32_bytes_le(const void* sequence)
|
|
{
|
|
const uint8_t* bytes = (const uint8_t*)sequence;
|
|
uint32_t ret = 0;
|
|
for (int i = 0; i < 4; i++)
|
|
ret += ((uint32_t)bytes[i]) << (i * 8);
|
|
return ret;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_read_uint32_bytes_be(const void* sequence)
|
|
{
|
|
const uint8_t* bytes = (const uint8_t*)sequence;
|
|
uint32_t ret = 0;
|
|
for (int i = 0; i < 4; i++)
|
|
ret += ((uint32_t)bytes[i]) << ((3 - i) * 8);
|
|
return ret;
|
|
}
|
|
|
|
static inline bool cmidi2_util_is_platform_little_endian()
|
|
{
|
|
int i = 1;
|
|
return *(char*)&i;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_read_uint32_bytes(const void* sequence)
|
|
{
|
|
return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint32_bytes_le(sequence)
|
|
: cmidi2_ump_read_uint32_bytes_be(sequence);
|
|
}
|
|
|
|
static inline uint64_t cmidi2_ump_read_uint64_bytes_le(const void* sequence)
|
|
{
|
|
return ((uint64_t)cmidi2_ump_read_uint32_bytes_le(sequence) << 32)
|
|
+ cmidi2_ump_read_uint32_bytes_le((const uint8_t*)sequence + 4);
|
|
}
|
|
|
|
static inline uint64_t cmidi2_ump_read_uint64_bytes_be(const void* sequence)
|
|
{
|
|
return ((uint64_t)cmidi2_ump_read_uint32_bytes_be(sequence) << 32)
|
|
+ cmidi2_ump_read_uint32_bytes_be((const uint8_t*)sequence + 4);
|
|
}
|
|
|
|
static inline uint64_t cmidi2_ump_read_uint64_bytes(const void* sequence)
|
|
{
|
|
return cmidi2_util_is_platform_little_endian() ? cmidi2_ump_read_uint64_bytes_le(sequence)
|
|
: cmidi2_ump_read_uint64_bytes_be(sequence);
|
|
}
|
|
|
|
static inline void cmidi2_ump_sysex_get_packet_of(
|
|
uint64_t* result1, uint64_t* result2, uint8_t group, uint8_t numBytes, const void* srcData,
|
|
int32_t index, enum cmidi2_message_type messageType, int radix, bool hasStreamId,
|
|
uint8_t streamId)
|
|
{
|
|
uint8_t dst8[16];
|
|
memset(dst8, 0, 16);
|
|
const uint8_t* src8 = (const uint8_t*)srcData;
|
|
|
|
dst8[0] = (messageType << 4) + (group & 0xF);
|
|
|
|
enum cmidi2_sysex_status status;
|
|
uint8_t size;
|
|
if (numBytes <= radix)
|
|
{
|
|
status = CMIDI2_SYSEX_IN_ONE_UMP;
|
|
size = numBytes; // single packet message
|
|
}
|
|
else if (index == 0)
|
|
{
|
|
status = CMIDI2_SYSEX_START;
|
|
size = radix;
|
|
}
|
|
else
|
|
{
|
|
uint8_t isEnd = index == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1;
|
|
if (isEnd)
|
|
{
|
|
size = numBytes % radix ? numBytes % radix : radix;
|
|
status = CMIDI2_SYSEX_END;
|
|
}
|
|
else
|
|
{
|
|
size = radix;
|
|
status = CMIDI2_SYSEX_CONTINUE;
|
|
}
|
|
}
|
|
dst8[1] = status + size + (hasStreamId ? 1 : 0);
|
|
|
|
if (hasStreamId)
|
|
dst8[2] = streamId;
|
|
|
|
uint8_t dstOffset = hasStreamId ? 3 : 2;
|
|
for (uint8_t i = 0, j = index * radix; i < size; i++, j++)
|
|
dst8[i + dstOffset] = src8[j];
|
|
|
|
*result1 = cmidi2_ump_read_uint64_bytes_be(dst8);
|
|
if (result2)
|
|
*result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);
|
|
}
|
|
|
|
// 7.7 System Exclusive 7-Bit Messages
|
|
static inline uint64_t cmidi2_ump_sysex7_direct(
|
|
uint8_t group, uint8_t status, uint8_t numBytes, uint8_t data1, uint8_t data2, uint8_t data3,
|
|
uint8_t data4, uint8_t data5, uint8_t data6)
|
|
{
|
|
return (((uint64_t)((CMIDI2_MESSAGE_TYPE_SYSEX7 << 28) + ((group & 0xF) << 24)
|
|
+ ((status + numBytes) << 16)))
|
|
<< 32)
|
|
+ ((uint64_t)data1 << 40) + ((uint64_t)data2 << 32) + (data3 << 24) + (data4 << 16)
|
|
+ (data5 << 8) + data6;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_sysex7_get_sysex_length(const void* srcData)
|
|
{
|
|
int i = 0;
|
|
const uint8_t* csrc = (const uint8_t*)srcData;
|
|
while (csrc[i] != 0xF7)
|
|
i++;
|
|
/* This function automatically detects if 0xF0 is prepended and reduce length if it is. */
|
|
return i - (csrc[0] == 0xF0 ? 1 : 0);
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_sysex7_get_num_packets(uint8_t numSysex7Bytes)
|
|
{
|
|
return cmidi2_ump_sysex_get_num_packets(numSysex7Bytes, 6);
|
|
}
|
|
|
|
static inline uint64_t cmidi2_ump_sysex7_get_packet_of(
|
|
uint8_t group, uint8_t numBytes, const void* srcData, int32_t index)
|
|
{
|
|
uint64_t result;
|
|
int srcOffset = numBytes > 0 && ((const uint8_t*)srcData)[0] == 0xF0 ? 1 : 0;
|
|
cmidi2_ump_sysex_get_packet_of(
|
|
&result, NULL, group, numBytes, (const uint8_t*)srcData + srcOffset, index,
|
|
CMIDI2_MESSAGE_TYPE_SYSEX7, 6, false, 0);
|
|
return result;
|
|
}
|
|
|
|
/* process() - more complicated function */
|
|
|
|
// This returns NULL for success, or anything else for failure.
|
|
typedef void* (*cmidi2_ump_handler_u64)(uint64_t data, void* context);
|
|
|
|
// This returns NULL for success, or anything else that `sendUMP` returns for failure.
|
|
static inline void* cmidi2_ump_sysex7_process(
|
|
uint8_t group, void* sysex, cmidi2_ump_handler_u64 sendUMP, void* context)
|
|
{
|
|
int32_t length = cmidi2_ump_sysex7_get_sysex_length(sysex);
|
|
int32_t numPackets = cmidi2_ump_sysex7_get_num_packets(length);
|
|
for (int p = 0; p < numPackets; p++)
|
|
{
|
|
int64_t ump = cmidi2_ump_sysex7_get_packet_of(group, length, sysex, p);
|
|
void* retCode = sendUMP(ump, context);
|
|
if (retCode != 0)
|
|
return retCode;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// 7.8 System Exclusive 8-Bit Messages
|
|
|
|
static inline int8_t cmidi2_ump_sysex8_get_num_packets(uint8_t numBytes)
|
|
{
|
|
return cmidi2_ump_sysex_get_num_packets(numBytes, 13);
|
|
}
|
|
|
|
static inline void cmidi2_ump_sysex8_get_packet_of(
|
|
uint8_t group, uint8_t streamId, uint8_t numBytes, const void* srcData, int32_t index,
|
|
uint64_t* result1, uint64_t* result2)
|
|
{
|
|
cmidi2_ump_sysex_get_packet_of(
|
|
result1, result2, group, numBytes, srcData, index, CMIDI2_MESSAGE_TYPE_SYSEX8_MDS, 13, true,
|
|
streamId);
|
|
}
|
|
|
|
/* process() - more complicated function */
|
|
|
|
// This returns NULL for success, or anything else for failure.
|
|
typedef void* (*cmidi2_ump_handler_u128)(
|
|
uint64_t data1, uint64_t data2, size_t index, void* context);
|
|
|
|
// This returns NULL for success, or anything else that `sendUMP` returns for failure.
|
|
static inline void* cmidi2_ump_sysex8_process(
|
|
uint8_t group, void* sysex, uint32_t length, uint8_t streamId, cmidi2_ump_handler_u128 sendUMP,
|
|
void* context)
|
|
{
|
|
uint32_t numPackets = cmidi2_ump_sysex8_get_num_packets(length);
|
|
for (size_t p = 0; p < numPackets; p++)
|
|
{
|
|
uint64_t result1, result2;
|
|
cmidi2_ump_sysex8_get_packet_of(group, streamId, length, sysex, p, &result1, &result2);
|
|
void* retCode = sendUMP(result1, result2, p, context);
|
|
if (retCode != 0)
|
|
return retCode;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// 7.9 Mixed Data Set Message
|
|
|
|
static inline uint16_t cmidi2_ump_mds_get_num_chunks(uint32_t numTotalBytesInMDS)
|
|
{
|
|
uint32_t radix = 14 * 0x10000;
|
|
return numTotalBytesInMDS / radix + (numTotalBytesInMDS % radix ? 1 : 0);
|
|
}
|
|
|
|
// Returns -1 if input is out of range
|
|
static inline int32_t cmidi2_ump_mds_get_num_payloads(uint32_t numTotalBytesinChunk)
|
|
{
|
|
if (numTotalBytesinChunk > 14 * 65535)
|
|
return -1;
|
|
return numTotalBytesinChunk / 14 + (numTotalBytesinChunk % 14 ? 1 : 0);
|
|
}
|
|
|
|
static inline void cmidi2_ump_mds_get_header(
|
|
uint8_t group, uint8_t mdsId, uint16_t numBytesInChunk, uint16_t numChunks,
|
|
uint16_t chunkIndex, uint16_t manufacturerId, uint16_t deviceId, uint16_t subId,
|
|
uint16_t subId2, uint64_t* result1, uint64_t* result2)
|
|
{
|
|
uint8_t dst8[16];
|
|
memset(dst8, 0, 16);
|
|
|
|
dst8[0] = (CMIDI2_MESSAGE_TYPE_SYSEX8_MDS << 4) + (group & 0xF);
|
|
dst8[1] = CMIDI2_MIXED_DATA_STATUS_HEADER + mdsId;
|
|
*((uint16_t*)(void*)(dst8 + 2)) = numBytesInChunk;
|
|
*((uint16_t*)(void*)(dst8 + 4)) = numChunks;
|
|
*((uint16_t*)(void*)(dst8 + 6)) = chunkIndex;
|
|
*((uint16_t*)(void*)(dst8 + 8)) = manufacturerId;
|
|
*((uint16_t*)(void*)(dst8 + 10)) = deviceId;
|
|
*((uint16_t*)(void*)(dst8 + 12)) = subId;
|
|
*((uint16_t*)(void*)(dst8 + 14)) = subId2;
|
|
|
|
*result1 = cmidi2_ump_read_uint64_bytes_be(dst8);
|
|
if (result2)
|
|
*result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);
|
|
}
|
|
|
|
// srcData points to exact start of the source data.
|
|
static inline void cmidi2_ump_mds_get_payload_of(
|
|
uint8_t group, uint8_t mdsId, uint16_t numBytes, const void* srcData, uint64_t* result1,
|
|
uint64_t* result2)
|
|
{
|
|
uint8_t dst8[16];
|
|
memset(dst8, 0, 16);
|
|
const uint8_t* src8 = (const uint8_t*)srcData;
|
|
|
|
dst8[0] = (CMIDI2_MESSAGE_TYPE_SYSEX8_MDS << 4) + (group & 0xF);
|
|
dst8[1] = CMIDI2_MIXED_DATA_STATUS_PAYLOAD + mdsId;
|
|
|
|
uint8_t radix = 14;
|
|
uint8_t size = numBytes < radix ? numBytes % radix : radix;
|
|
|
|
for (uint8_t i = 0; i < size; i++)
|
|
dst8[i + 2] = src8[i];
|
|
|
|
*result1 = cmidi2_ump_read_uint64_bytes_be(dst8);
|
|
if (result2)
|
|
*result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);
|
|
}
|
|
|
|
/* process() - more complicated function */
|
|
// This returns NULL for success, or anything else for failure.
|
|
typedef void* (*cmidi2_mds_handler)(
|
|
uint64_t data1, uint64_t data2, size_t chunkId, size_t payloadId, void* context);
|
|
|
|
// This returns NULL for success, or anything else that `sendUMP` returns for failure.
|
|
static inline void* cmidi2_ump_mds_process(
|
|
uint8_t group, uint8_t mdsId, void* data, uint32_t length, cmidi2_mds_handler sendUMP,
|
|
void* context)
|
|
{
|
|
int32_t numChunks = cmidi2_ump_mds_get_num_chunks(length);
|
|
for (int c = 0; c < numChunks; c++)
|
|
{
|
|
int32_t maxChunkSize = 14 * 65535;
|
|
int32_t chunkSize = c + 1 == numChunks ? (int32_t)(length % maxChunkSize) : maxChunkSize;
|
|
int32_t numPayloads = cmidi2_ump_mds_get_num_payloads(chunkSize);
|
|
for (int p = 0; p < numPayloads; p++)
|
|
{
|
|
uint64_t result1, result2;
|
|
size_t offset = 14 * (65536 * c + p);
|
|
cmidi2_ump_mds_get_payload_of(
|
|
group, mdsId, chunkSize, (uint8_t*)data + offset, &result1, &result2);
|
|
void* retCode = sendUMP(result1, result2, c, p, context);
|
|
if (retCode)
|
|
return retCode;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// 7.5 Flex Data Message
|
|
|
|
static inline uint16_t cmidi2_ump_flex_data_get_num_packets(uint32_t numTotalBytesInFlexData)
|
|
{
|
|
return numTotalBytesInFlexData / 12 + (numTotalBytesInFlexData % 12 ? 1 : 0);
|
|
}
|
|
|
|
static inline void cmidi2_ump_flex_data_complete_packet(
|
|
uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode,
|
|
uint32_t data1, uint32_t data2, uint32_t data3, uint64_t* result1, uint64_t* result2)
|
|
{
|
|
uint8_t dst8[4];
|
|
memset(dst8, 0, 4);
|
|
dst8[0] = (CMIDI2_MESSAGE_TYPE_FLEX_DATA << 4) + (group & 0xF);
|
|
dst8[1] = (CMIDI2_SYSEX_IN_ONE_UMP << 2) + ((addressing & 0x3) << 4) + (channel & 0xF);
|
|
dst8[2] = statusBank;
|
|
dst8[3] = statusCode;
|
|
|
|
*result1 = ((uint64_t)cmidi2_ump_read_uint32_bytes_be(dst8) << 32) + data1;
|
|
*result2 = ((uint64_t)data2 << 32) + data3;
|
|
}
|
|
|
|
static inline void cmidi2_ump_flex_data_get_packet_of(
|
|
uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode,
|
|
uint16_t numBytes, const void* srcData, int32_t currentPacket, uint64_t* result1,
|
|
uint64_t* result2)
|
|
{
|
|
uint8_t dst8[16];
|
|
memset(dst8, 0, 16);
|
|
const uint8_t* src8 = (const uint8_t*)srcData;
|
|
|
|
dst8[0] = (CMIDI2_MESSAGE_TYPE_FLEX_DATA << 4) + (group & 0xF);
|
|
|
|
const size_t radix = 12;
|
|
enum cmidi2_sysex_status format;
|
|
uint8_t size;
|
|
if (numBytes <= radix)
|
|
{
|
|
format = CMIDI2_SYSEX_IN_ONE_UMP;
|
|
size = numBytes; // single packet message
|
|
}
|
|
else if (currentPacket == 0)
|
|
{
|
|
format = CMIDI2_SYSEX_START;
|
|
size = radix;
|
|
}
|
|
else
|
|
{
|
|
uint8_t isEnd = currentPacket == cmidi2_ump_sysex_get_num_packets(numBytes, radix) - 1;
|
|
if (isEnd)
|
|
{
|
|
size = numBytes % radix ? numBytes % radix : radix;
|
|
format = CMIDI2_SYSEX_END;
|
|
}
|
|
else
|
|
{
|
|
size = radix;
|
|
format = CMIDI2_SYSEX_CONTINUE;
|
|
}
|
|
}
|
|
dst8[1] = (format << 2) + ((addressing & 0x3) << 4) + (channel & 0xF);
|
|
dst8[2] = statusBank;
|
|
dst8[3] = statusCode;
|
|
|
|
for (uint8_t i = 0; i < size; i++)
|
|
dst8[i + 4] = src8[i];
|
|
|
|
*result1 = cmidi2_ump_read_uint64_bytes_be(dst8);
|
|
*result2 = cmidi2_ump_read_uint64_bytes_be(dst8 + 8);
|
|
}
|
|
|
|
/* process() - more complicated function */
|
|
// This returns NULL for success, or anything else for failure.
|
|
typedef void* (*cmidi2_flex_data_handler)(uint64_t data1, uint64_t data2, void* context);
|
|
|
|
// This returns NULL for success, or anything else that `sendUMP` returns for failure.
|
|
// `text` is usually a null-terminated text string, but it may contain `\0` as Melisma in lyricText. Hence we still need `length`.
|
|
static inline void* cmidi2_ump_flex_data_process(
|
|
uint8_t group, uint8_t addressing, uint8_t channel, uint8_t statusBank, uint8_t statusCode,
|
|
const char* text, uint32_t length, cmidi2_flex_data_handler sendUMP, void* context)
|
|
{
|
|
int32_t numPackets = cmidi2_ump_flex_data_get_num_packets(length);
|
|
for (int p = 0; p < numPackets; p++)
|
|
{
|
|
uint64_t result1, result2;
|
|
cmidi2_ump_flex_data_get_packet_of(
|
|
group, addressing, channel, statusBank, statusCode, length, text, p, &result1, &result2);
|
|
void* retCode = sendUMP(result1, result2, context);
|
|
if (retCode != 0)
|
|
return retCode;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// individual flex data message generators
|
|
|
|
static inline void cmidi2_ump_flex_data_set_tempo_direct(
|
|
uint8_t group, uint8_t channel, uint32_t tempoIn10NanosecondsPerQN, uint64_t* result1,
|
|
uint64_t* result2)
|
|
{
|
|
cmidi2_ump_flex_data_complete_packet(
|
|
group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_TEMPO, tempoIn10NanosecondsPerQN, 0, 0, result1, result2);
|
|
}
|
|
|
|
static inline void cmidi2_ump_flex_data_set_time_signature(
|
|
uint8_t group, uint8_t channel, uint8_t numerator, uint8_t denominator,
|
|
uint8_t numberOf32thNotes, uint64_t* result1, uint64_t* result2)
|
|
{
|
|
cmidi2_ump_flex_data_complete_packet(
|
|
group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_TIME_SIGNATURE,
|
|
(numerator << 24) + (denominator << 16) + (numberOf32thNotes << 8), 0, 0, result1, result2);
|
|
}
|
|
|
|
static inline void cmidi2_ump_flex_data_set_metronome(
|
|
uint8_t group, uint8_t channel, uint8_t clocksPerPrimaryClick, uint8_t barAccent1,
|
|
uint8_t barAccent2, uint8_t barAccent3, uint8_t subDivisionClicks1, uint8_t subDivisionClicks2,
|
|
uint64_t* result1, uint64_t* result2)
|
|
{
|
|
cmidi2_ump_flex_data_complete_packet(
|
|
group, 1, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_METRONOME,
|
|
(clocksPerPrimaryClick << 24) + (barAccent1 << 16) + (barAccent2 << 8) + barAccent3,
|
|
(subDivisionClicks1 << 24) + (subDivisionClicks2 << 16), 0, result1, result2);
|
|
}
|
|
|
|
static inline void cmidi2_ump_flex_data_set_key_signature(
|
|
uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t tonicNote,
|
|
uint64_t* result1, uint64_t* result2)
|
|
{
|
|
cmidi2_ump_flex_data_complete_packet(
|
|
group, addressing, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_KEY_SIGNATURE, (sharpsFlats << 24) + (tonicNote << 16), 0, 0,
|
|
result1, result2);
|
|
}
|
|
|
|
static inline void cmidi2_ump_flex_data_set_chord_name(
|
|
uint8_t group, uint8_t addressing, uint8_t channel, uint8_t sharpsFlats, uint8_t chordTonic,
|
|
uint8_t chordType, uint8_t alter1Type, uint8_t alter1Degree, uint8_t alter2Type,
|
|
uint8_t alter2Degree, uint8_t alter3Type, uint8_t alter3Degree, uint8_t alter4Type,
|
|
uint8_t alter4Degree, uint8_t bassSharpsFlats, uint8_t bassNote, uint8_t bassChordType,
|
|
uint8_t bassAlter1Type, uint8_t bassAlter1Degree, uint8_t bassAlter2Type,
|
|
uint8_t bassAlter2Degree, uint64_t* result1, uint64_t* result2)
|
|
{
|
|
cmidi2_ump_flex_data_complete_packet(
|
|
group, addressing, channel, CMIDI2_FLEX_DATA_BANK_SETUP_AND_PERFORMANCE,
|
|
CMIDI2_FLEX_DATA_STATUS_SET_CHORD_NAME,
|
|
(sharpsFlats << 28) + (chordTonic << 24) + (chordType << 16) + (alter1Type << 12)
|
|
+ (alter1Degree << 8) + (alter2Type << 4) + alter2Degree,
|
|
(alter3Type << 28) + (alter3Degree << 24) + (alter4Type << 20) + (alter4Degree << 16),
|
|
(bassSharpsFlats << 28) + (bassNote << 24) + (bassChordType << 16) + (bassAlter1Type << 12)
|
|
+ (bassAlter1Degree << 8) + (bassAlter2Type << 4) + bassAlter2Degree,
|
|
result1, result2);
|
|
}
|
|
|
|
// --------
|
|
// Strongly-typed(?) UMP.
|
|
// I kind of think those getters are overkill, so I would collect almost use `cmidi2_ump_get_xxx()`
|
|
// as those strongly typed functions, so that those who don't want them can safely ignore them.
|
|
|
|
typedef uint32_t cmidi2_ump;
|
|
|
|
static inline void cmidi2_ump_write32(cmidi2_ump* dst, uint32_t value)
|
|
{
|
|
dst[0] = value;
|
|
}
|
|
|
|
static inline void cmidi2_ump_write64(cmidi2_ump* dst, uint64_t value)
|
|
{
|
|
dst[0] = value >> 32;
|
|
dst[1] = value & 0xFFFFFFFF;
|
|
}
|
|
|
|
static inline void cmidi2_ump_write128(cmidi2_ump* dst, uint64_t value1, uint64_t value2)
|
|
{
|
|
dst[0] = value1 >> 32;
|
|
dst[1] = value1 & 0xFFFFFFFF;
|
|
dst[2] = value2 >> 32;
|
|
dst[3] = value2 & 0xFFFFFFFF;
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_byte_at(const cmidi2_ump* ump, uint8_t at)
|
|
{
|
|
ump += at / 4;
|
|
switch (at % 4)
|
|
{
|
|
case 0:
|
|
return (*ump & 0xFF000000) >> 24;
|
|
case 1:
|
|
return (*ump & 0xFF0000) >> 16;
|
|
case 2:
|
|
return (*ump & 0xFF00) >> 8;
|
|
case 3:
|
|
return *ump & 0xFF;
|
|
}
|
|
return 0; // This is unexpected.
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_message_type(const cmidi2_ump* ump)
|
|
{
|
|
return *ump >> 28;
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_message_size_bytes(const cmidi2_ump* ump)
|
|
{
|
|
switch (cmidi2_ump_get_message_type(ump))
|
|
{
|
|
case CMIDI2_MESSAGE_TYPE_UTILITY:
|
|
case CMIDI2_MESSAGE_TYPE_SYSTEM:
|
|
case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:
|
|
return 4;
|
|
case CMIDI2_MESSAGE_TYPE_SYSEX7:
|
|
case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:
|
|
return 8;
|
|
case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:
|
|
return 16;
|
|
}
|
|
return 0; // invalid
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_group(const cmidi2_ump* ump)
|
|
{
|
|
return (*ump >> 24) & 0xF;
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_status_code(const cmidi2_ump* ump)
|
|
{
|
|
return (*ump >> 16) & 0xF0;
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_channel(const cmidi2_ump* ump)
|
|
{
|
|
return (*ump >> 16) & 0xF;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_ump_get_32_to_64(const cmidi2_ump* ump)
|
|
{
|
|
return *(ump + 1);
|
|
}
|
|
|
|
static inline uint16_t cmidi2_ump_get_jr_clock_time(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_jr_timestamp_timestamp(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
|
|
static inline uint16_t cmidi2_ump_get_dctpq(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
|
|
static inline uint16_t cmidi2_ump_get_dcs(const cmidi2_ump* ump)
|
|
{
|
|
return ((cmidi2_ump_get_byte_at(ump, 1) & 0xF) << 16) + (cmidi2_ump_get_byte_at(ump, 2) << 8)
|
|
+ cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_system_message_byte2(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_system_message_byte3(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_midi1_byte2(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_byte3(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_midi1_note_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_note_velocity(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_paf_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_paf_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_cc_index(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_cc_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_program_program(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi1_caf_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_midi1_pitch_bend_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2) + cmidi2_ump_get_byte_at(ump, 3) * 0x80;
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_sysex7_num_bytes(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_channel(ump); // same bits
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_midi2_note_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_note_attribute_type(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_midi2_note_velocity(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 4) << 8) + (cmidi2_ump_get_byte_at(ump, 5));
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_midi2_note_attribute_data(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 6) << 8) + (cmidi2_ump_get_byte_at(ump, 7));
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_paf_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint32_t cmidi2_ump_get_midi2_paf_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_pnrcc_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_pnrcc_index(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint32_t cmidi2_ump_get_midi2_pnrcc_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_pnacc_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_pnacc_index(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint32_t cmidi2_ump_get_midi2_pnacc_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
static inline uint32_t cmidi2_ump_get_midi2_pn_management_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint32_t cmidi2_ump_get_midi2_pn_management_options(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_cc_index(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint32_t cmidi2_ump_get_midi2_cc_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
// absolute RPN or relative RPN
|
|
static inline uint8_t cmidi2_ump_get_midi2_rpn_msb(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
// absolute RPN or relative RPN
|
|
static inline uint8_t cmidi2_ump_get_midi2_rpn_lsb(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
// absolute RPN or relative RPN
|
|
static inline uint32_t cmidi2_ump_get_midi2_rpn_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
// absolute NRPN or relative NRPN
|
|
static inline uint8_t cmidi2_ump_get_midi2_nrpn_msb(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
// absolute NRPN or relative NRPN
|
|
static inline uint8_t cmidi2_ump_get_midi2_nrpn_lsb(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
// absolute NRPN or relative NRPN
|
|
static inline uint32_t cmidi2_ump_get_midi2_nrpn_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_program_options(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_program_program(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 4);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_program_bank_msb(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 6));
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_program_bank_lsb(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 7));
|
|
}
|
|
static inline uint32_t cmidi2_ump_get_midi2_caf_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
// either per-note or channel
|
|
static inline uint32_t cmidi2_ump_get_midi2_pitch_bend_data(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_32_to_64(ump);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_midi2_pn_pitch_bend_note(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
|
|
static inline uint8_t cmidi2_ump_get_sysex8_num_bytes(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_channel(ump); // same bits
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_sysex8_stream_id(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_byte_at(ump, 2);
|
|
}
|
|
static inline uint8_t cmidi2_ump_get_mds_mds_id(const cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_ump_get_channel(ump); // same bits
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_mds_num_chunk_bytes(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 2) << 8) + cmidi2_ump_get_byte_at(ump, 3);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_mds_num_chunks(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 4) << 8) + cmidi2_ump_get_byte_at(ump, 5);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_mds_chunk_index(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 6) << 8) + cmidi2_ump_get_byte_at(ump, 7);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_mds_manufacturer_id(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 8) << 8) + cmidi2_ump_get_byte_at(ump, 9);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_mds_device_id(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 10) << 8) + cmidi2_ump_get_byte_at(ump, 11);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_mds_sub_id_1(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 12) << 8) + cmidi2_ump_get_byte_at(ump, 13);
|
|
}
|
|
static inline uint16_t cmidi2_ump_get_mds_sub_id_2(const cmidi2_ump* ump)
|
|
{
|
|
return (cmidi2_ump_get_byte_at(ump, 14) << 8) + cmidi2_ump_get_byte_at(ump, 15);
|
|
}
|
|
|
|
// --------
|
|
// Realtime-safe SysEx8 binary reader
|
|
//
|
|
// The primary end-user facing function is `cmidi2_ump_get_sysex8_data()`, but the
|
|
// function signature needs some explanation as it requires some complicated processing.
|
|
//
|
|
// There can be multiple SysEx8 "streams" that simultaneously run within a UMP stream
|
|
// (e.g. sysex8 start packet for stream #1, sysex8 continue packet for stream #1,
|
|
// sysex8 start packet for stream #2, sysex8 continue packet for stream #1,
|
|
// sysex8 continue packet for stream #2, sysex8 complete packet for stream #3,
|
|
// sysex8 end packet for stream #2, sysex8 end packet for strean #1 ...).
|
|
//
|
|
// Those stream state must be preserved during UMP parsing. It is represented as
|
|
// `cmidi2_ump_binary_read_state` struct.
|
|
//
|
|
// There are handful of usage scenarios where we...
|
|
// - want to handle them all
|
|
// - want to handle only relevant ones
|
|
// - can control what we generate and determine that only one stream can appear so
|
|
// the context stream is always obvious
|
|
//
|
|
// To handle them all, we need "stream selector". There is a function type
|
|
// `cmidi2_ump_stream_selector_func`, and a function pointer is passed to the entry
|
|
// point `cmidi2_ump_get_sysex8_data()` function.
|
|
//
|
|
// Another control point is where we want to finish parsing sysex8. Whenever a relevant
|
|
// stream is parsed, `cmidi2_ump_get_syex8_data()` calls "continuity checker" function
|
|
// which is typed as `cmidi2_ump_binary_read_continuity_checker_func`.
|
|
// If this function returns true, then it completes parsing, returning the number of
|
|
// the parsed UMPs (in uint32_t length).
|
|
//
|
|
// See `testType5Messages_sysex8_reader_writer()` testcase for the actual usage example.
|
|
//
|
|
|
|
enum cmidi2_ump_binary_reader_result_code
|
|
{
|
|
CMIDI2_BINARY_READER_RESULT_INCOMPLETE = 0,
|
|
CMIDI2_BINARY_READER_RESULT_COMPLETE = 1,
|
|
CMIDI2_BINARY_READER_RESULT_NO_SPACE = 2,
|
|
};
|
|
|
|
typedef struct cmidi2_ump_binary_read_state
|
|
{
|
|
void* context;
|
|
uint8_t* data;
|
|
size_t dataCapacity;
|
|
size_t dataSize;
|
|
bool continueOnCompletion;
|
|
enum cmidi2_ump_binary_reader_result_code resultCode;
|
|
} cmidi2_ump_binary_read_state;
|
|
|
|
static inline void cmidi2_ump_binary_read_state_reset(cmidi2_ump_binary_read_state* state)
|
|
{
|
|
state->dataSize = 0;
|
|
state->resultCode = CMIDI2_BINARY_READER_RESULT_INCOMPLETE;
|
|
}
|
|
|
|
static inline void cmidi2_ump_binary_read_state_init(
|
|
cmidi2_ump_binary_read_state* state, void* context, uint8_t* dataBuffer, size_t dataCapacity,
|
|
bool continueOnCompletion)
|
|
{
|
|
state->context = context;
|
|
state->data = dataBuffer;
|
|
state->dataCapacity = dataCapacity;
|
|
state->continueOnCompletion = continueOnCompletion;
|
|
cmidi2_ump_binary_read_state_reset(state);
|
|
}
|
|
|
|
/// Binary stream selector for cmidi2_ump_get_sysex8_data() function.
|
|
/// If you do not support simultaneous streams, just return a reference to a fixed `cmidi2_ump_binary_read_state` instance.
|
|
/// If the targetSteramId does not indicate the streams the client handles, then it should return NULL.
|
|
/// Note that the resulting state must contain a non-null data and valid dataCapacity i.e. it must be already assigned.
|
|
/// Also note that the function implementation should not try to allocate a new instance of state
|
|
/// - otherwise it will break realtime safety.
|
|
typedef cmidi2_ump_binary_read_state* (*cmidi2_ump_stream_selector_func)(
|
|
uint8_t targetStreamId, void* context);
|
|
|
|
/// Binary stream continuity checker for cmidi2_ump_get_sysex8_data() function.
|
|
/// If you do not support simultaneous streams, then it should reset stream state (dataSize etc.) when new stream starts.
|
|
/// Return true to continue parsing, otherwise return false. It is useful to determine whether it
|
|
/// should break or not when a stream is fully read (status code is 3 = CMIDI2_SYSEX_END).
|
|
/// `cmidi2_ump_get_sysex8_data()` can take NULL for this function pointer at `continuityChecker` argument,
|
|
/// then it falls back to the "default" behavior.
|
|
/// By default, it finishes parsing when a stream completed and `continueOnCompletion` was `false`.
|
|
typedef bool (*cmidi2_ump_binary_read_continuity_checker_func)(
|
|
cmidi2_ump_binary_read_state* stream, cmidi2_ump* ump);
|
|
|
|
// it is a special copy function that only works with sysex8 memory state at `src` that can access beyond `sizeInBytes`.
|
|
static inline void cmidi2_internal_sysex8_copy_data_byte_swapping(
|
|
uint8_t* dst, uint8_t srcHead, uint32_t* srcTail, size_t sizeInBytes)
|
|
{
|
|
if (sizeInBytes == 0)
|
|
return;
|
|
|
|
// the first byte is at the lowest byte so it is always safe to copy as is.
|
|
dst[0] = srcHead;
|
|
uint8_t* d = dst + 1;
|
|
uint32_t* s = srcTail;
|
|
sizeInBytes--;
|
|
|
|
// copy the rest
|
|
size_t i = 0;
|
|
while (i + 3 < sizeInBytes)
|
|
{
|
|
uint32_t i32 = *s;
|
|
d[i++] = i32 >> 24;
|
|
d[i++] = (i32 >> 16) & 0xFF;
|
|
d[i++] = (i32 >> 8) & 0xFF;
|
|
d[i++] = i32 & 0xFF;
|
|
s++;
|
|
}
|
|
for (; i < sizeInBytes; i++)
|
|
d[i] = s[i + 3 - i % 4];
|
|
}
|
|
|
|
/// Parse and store sysex8 binary, using some fine-tuned behavioral functions.
|
|
/// Return the number of 32-bit ints (number of `cmidi2_ump`s)
|
|
static inline size_t cmidi2_ump_get_sysex8_data(
|
|
cmidi2_ump_stream_selector_func streamSelector, void* streamSelectorContext,
|
|
cmidi2_ump_binary_read_continuity_checker_func continuityChecker, const cmidi2_ump* ump,
|
|
const size_t umpCapacityInInt)
|
|
{
|
|
|
|
cmidi2_ump *umpPtr = (cmidi2_ump*)ump, *umpEnd = (cmidi2_ump*)ump + umpCapacityInInt;
|
|
for (; umpPtr < umpEnd; umpPtr += cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump))
|
|
{
|
|
if (cmidi2_ump_get_message_type(umpPtr) != CMIDI2_MESSAGE_TYPE_SYSEX8_MDS)
|
|
continue;
|
|
switch (cmidi2_ump_get_status_code(umpPtr))
|
|
{
|
|
case CMIDI2_SYSEX_IN_ONE_UMP:
|
|
case CMIDI2_SYSEX_START:
|
|
case CMIDI2_SYSEX_END:
|
|
case CMIDI2_SYSEX_CONTINUE:
|
|
break;
|
|
default:
|
|
continue; // the only expected value here is MDS (8 or 9)
|
|
}
|
|
|
|
cmidi2_ump_binary_read_state* state
|
|
= streamSelector(cmidi2_ump_get_sysex8_stream_id(umpPtr), streamSelectorContext);
|
|
if (state == NULL)
|
|
continue;
|
|
|
|
size_t copySize = cmidi2_ump_get_sysex8_num_bytes(umpPtr)
|
|
- 1; // SysEx8 size field contains the size byte itself, hence -1.
|
|
if (state->dataSize + copySize >= state->dataCapacity)
|
|
{
|
|
state->resultCode = CMIDI2_BINARY_READER_RESULT_NO_SPACE;
|
|
return umpPtr - ump;
|
|
}
|
|
|
|
if (cmidi2_util_is_platform_little_endian())
|
|
// We need to swap data along with byte order for resulting data...
|
|
cmidi2_internal_sysex8_copy_data_byte_swapping(
|
|
state->data + state->dataSize, (*umpPtr) & 0xFF, umpPtr + 1, copySize);
|
|
else
|
|
memcpy(state->data + state->dataSize, ((uint8_t*)(void*)umpPtr) + 3, copySize);
|
|
|
|
state->dataSize += copySize;
|
|
|
|
if (continuityChecker != NULL)
|
|
if (continuityChecker(state, umpPtr))
|
|
return umpPtr + cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump)
|
|
- ump; // "break here" is indicated
|
|
|
|
// otherwise default continuity checker
|
|
switch (cmidi2_ump_get_status_code(umpPtr))
|
|
{
|
|
case CMIDI2_SYSEX_IN_ONE_UMP:
|
|
case CMIDI2_SYSEX_END:
|
|
state->resultCode = CMIDI2_BINARY_READER_RESULT_COMPLETE;
|
|
if (state->continueOnCompletion)
|
|
return umpPtr + cmidi2_ump_get_message_size_bytes(umpPtr) / sizeof(cmidi2_ump)
|
|
- ump; // default "break here" condition.
|
|
break;
|
|
}
|
|
}
|
|
// finished parsing while no stream indicated "break here" for completion.
|
|
return umpPtr - ump;
|
|
}
|
|
|
|
// --------
|
|
// sequence iterator
|
|
|
|
/* byte stream splitter */
|
|
|
|
static inline void* cmidi2_ump_sequence_next(const void* ptr)
|
|
{
|
|
return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes(ptr));
|
|
}
|
|
|
|
// similar to LV2_ATOM Utilities API...
|
|
#define CMIDI2_UMP_SEQUENCE_FOREACH(ptr, numBytes, iter) \
|
|
for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \
|
|
(iter) = (uint8_t*)cmidi2_ump_sequence_next(iter))
|
|
|
|
static inline void* cmidi2_ump_sequence_next_le(const void* ptr)
|
|
{
|
|
return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_le(ptr));
|
|
}
|
|
|
|
#define CMIDI2_UMP_SEQUENCE_FOREACH_LE(ptr, numBytes, iter) \
|
|
for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \
|
|
(iter) = (uint8_t*)cmidi2_ump_sequence_next_le(iter))
|
|
|
|
static inline void* cmidi2_ump_sequence_next_be(const void* ptr)
|
|
{
|
|
return (uint8_t*)ptr + cmidi2_ump_get_num_bytes(cmidi2_ump_read_uint32_bytes_be(ptr));
|
|
}
|
|
|
|
#define CMIDI2_UMP_SEQUENCE_FOREACH_BE(ptr, numBytes, iter) \
|
|
for (uint8_t * (iter) = (uint8_t*)ptr; (iter) < ((uint8_t*)ptr) + numBytes; \
|
|
(iter) = (uint8_t*)cmidi2_ump_sequence_next_be(iter))
|
|
|
|
// --------
|
|
// MIDI CI support.
|
|
|
|
#define CMIDI2_CI_SUB_ID 0xD
|
|
#define CMIDI2_CI_SUB_ID_2_DISCOVERY_INQUIRY 0x70
|
|
#define CMIDI2_CI_SUB_ID_2_DISCOVERY_REPLY 0x71
|
|
#define CMIDI2_CI_SUB_ID_2_INVALIDATE_MUID 0x7E
|
|
#define CMIDI2_CI_SUB_ID_2_ACK 0x7F
|
|
#define CMIDI2_CI_SUB_ID_2_NAK 0x7F
|
|
#define CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_INQUIRY 0x10
|
|
#define CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_REPLY 0x11
|
|
#define CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL 0x12
|
|
#define CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_I2R 0x13
|
|
#define CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_R2I 0x14
|
|
#define CMIDI2_CI_SUB_ID_2_CONFIRM_NEW_PROTOCOL_ESTABLISHED 0x15
|
|
#define CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY 0x20
|
|
#define CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY_REPLY 0x21
|
|
#define CMIDI2_CI_SUB_ID_2_SET_PROFILE_ON 0x22
|
|
#define CMIDI2_CI_SUB_ID_2_SET_PROFILE_OFF 0x23
|
|
#define CMIDI2_CI_SUB_ID_2_PROFILE_ENABLED_REPORT 0x24
|
|
#define CMIDI2_CI_SUB_ID_2_PROFILE_DISABLED_REPORT 0x25
|
|
#define CMIDI2_CI_SUB_ID_2_PROFILE_SPECIFIC_DATA 0x2F
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_INQUIRY 0x30
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY 0x31
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_HAS_DATA 0x32
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_HAS_DATA_REPLY 0x33
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA 0x34
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA_REPLY 0x35
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA 0x36
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA_REPLY 0x37
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE 0x38
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE_REPLY 0x39
|
|
#define CMIDI2_CI_SUB_ID_2_PROPERTY_NOTIFY 0x3F
|
|
#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES 0x40
|
|
#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES_REPLY 0x41
|
|
#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT 0x42
|
|
#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_REPLY 0x43
|
|
#define CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_END 0x44
|
|
|
|
#define CMIDI2_CI_PROTOCOL_NEGOTIATION_SUPPORTED 2
|
|
#define CMIDI2_CI_PROFILE_CONFIGURATION_SUPPORTED 4
|
|
#define CMIDI2_CI_PROPERTY_EXCHANGE_SUPPORTED 8
|
|
|
|
#define CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK 0x7F
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t type;
|
|
uint8_t version;
|
|
uint8_t extensions;
|
|
uint8_t reserved1;
|
|
uint8_t reserved2;
|
|
} cmidi2_ci_protocol_type_info;
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t fixed_7e; // 0x7E
|
|
uint8_t bank;
|
|
uint8_t number;
|
|
uint8_t version;
|
|
uint8_t level;
|
|
} cmidi2_profile_id;
|
|
|
|
// Assumes the input value is already 7-bit encoded if required.
|
|
static inline void cmidi2_ci_direct_uint16_at(uint8_t* buf, uint16_t v)
|
|
{
|
|
buf[0] = v & 0xFF;
|
|
buf[1] = (v >> 8) & 0xFF;
|
|
}
|
|
|
|
// Assumes the input value is already 7-bit encoded if required.
|
|
static inline void cmidi2_ci_direct_uint32_at(uint8_t* buf, uint32_t v)
|
|
{
|
|
buf[0] = v & 0xFF;
|
|
buf[1] = (v >> 8) & 0xFF;
|
|
buf[2] = (v >> 16) & 0xFF;
|
|
buf[3] = (v >> 24) & 0xFF;
|
|
}
|
|
|
|
static inline void cmidi2_ci_7bit_int14_at(uint8_t* buf, uint16_t v)
|
|
{
|
|
buf[0] = v & 0x7F;
|
|
buf[1] = (v >> 7) & 0x7F;
|
|
}
|
|
|
|
static inline void cmidi2_ci_7bit_int21_at(uint8_t* buf, uint32_t v)
|
|
{
|
|
buf[0] = v & 0x7F;
|
|
buf[1] = (v >> 7) & 0x7F;
|
|
buf[2] = (v >> 14) & 0x7F;
|
|
}
|
|
|
|
static inline void cmidi2_ci_7bit_int28_at(uint8_t* buf, uint32_t v)
|
|
{
|
|
buf[0] = v & 0x7F;
|
|
buf[1] = (v >> 7) & 0x7F;
|
|
buf[2] = (v >> 14) & 0x7F;
|
|
buf[3] = (v >> 21) & 0x7F;
|
|
}
|
|
|
|
static inline void cmidi2_ci_message_common(
|
|
uint8_t* buf, uint8_t destination, uint8_t sysexSubId2, uint8_t versionAndFormat,
|
|
uint32_t sourceMUID, uint32_t destinationMUID)
|
|
{
|
|
buf[0] = 0x7E;
|
|
buf[1] = destination;
|
|
buf[2] = CMIDI2_CI_SUB_ID;
|
|
buf[3] = sysexSubId2;
|
|
buf[4] = versionAndFormat;
|
|
cmidi2_ci_direct_uint32_at(buf + 5, sourceMUID);
|
|
cmidi2_ci_direct_uint32_at(buf + 9, destinationMUID);
|
|
}
|
|
|
|
// Discovery
|
|
|
|
static inline void cmidi2_ci_discovery_common(
|
|
uint8_t* buf, uint8_t sysexSubId2, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint32_t deviceManufacturer3Bytes, uint16_t deviceFamily,
|
|
uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel, uint8_t ciCategorySupported,
|
|
uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, sysexSubId2, versionAndFormat, sourceMUID,
|
|
destinationMUID);
|
|
cmidi2_ci_direct_uint32_at(
|
|
buf + 13,
|
|
deviceManufacturer3Bytes); // the last byte is extraneous, but will be overwritten next.
|
|
cmidi2_ci_direct_uint16_at(buf + 16, deviceFamily);
|
|
cmidi2_ci_direct_uint16_at(buf + 18, deviceFamilyModelNumber);
|
|
// LAMESPEC: Software Revision Level does not mention in which endianness this field is stored.
|
|
cmidi2_ci_direct_uint32_at(buf + 20, softwareRevisionLevel);
|
|
buf[24] = ciCategorySupported;
|
|
cmidi2_ci_direct_uint32_at(buf + 25, receivableMaxSysExSize);
|
|
buf[29] = initiatorOutputPathId;
|
|
}
|
|
|
|
static inline void cmidi2_ci_discovery(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t deviceManufacturer,
|
|
uint16_t deviceFamily, uint16_t deviceFamilyModelNumber, uint32_t softwareRevisionLevel,
|
|
uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize, uint8_t initiatorOutputPathId)
|
|
{
|
|
cmidi2_ci_discovery_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_DISCOVERY_INQUIRY, versionAndFormat, sourceMUID, 0x7F7F7F7F,
|
|
deviceManufacturer, deviceFamily, deviceFamilyModelNumber, softwareRevisionLevel,
|
|
ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId);
|
|
}
|
|
|
|
static inline void cmidi2_ci_discovery_reply(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint32_t deviceManufacturer, uint16_t deviceFamily, uint16_t deviceFamilyModelNumber,
|
|
uint32_t softwareRevisionLevel, uint8_t ciCategorySupported, uint32_t receivableMaxSysExSize,
|
|
uint8_t initiatorOutputPathId, uint8_t functionBlockOr7Fh)
|
|
{
|
|
cmidi2_ci_discovery_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_DISCOVERY_REPLY, versionAndFormat, sourceMUID, destinationMUID,
|
|
deviceManufacturer, deviceFamily, deviceFamilyModelNumber, softwareRevisionLevel,
|
|
ciCategorySupported, receivableMaxSysExSize, initiatorOutputPathId);
|
|
buf[30] = functionBlockOr7Fh;
|
|
}
|
|
|
|
static inline void cmidi2_ci_discovery_invalidate_muid(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t targetMUID)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, 0x7F, CMIDI2_CI_SUB_ID_2_INVALIDATE_MUID, versionAndFormat, sourceMUID, 0x7F7F7F7F);
|
|
cmidi2_ci_direct_uint32_at(buf + 13, targetMUID);
|
|
}
|
|
|
|
static inline void cmidi2_ci_ack_nak_common(
|
|
uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t statusCode,
|
|
uint8_t statusData, uint8_t* details5Bytes, uint16_t messageLength, const char* messageText)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, deviceId, CMIDI2_CI_SUB_ID_2_ACK, versionAndFormat, sourceMUID, destinationMUID);
|
|
buf[13] = originalTransactionSubID2Class;
|
|
buf[14] = statusCode;
|
|
buf[15] = statusData;
|
|
memcpy(buf + 16, details5Bytes, 5);
|
|
cmidi2_ci_direct_uint16_at(buf + 21, messageLength);
|
|
memcpy(buf + 23, messageText, messageLength);
|
|
}
|
|
|
|
static inline void cmidi2_ci_ack(
|
|
uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t ackStatusCode,
|
|
uint8_t ackStatusData, uint8_t* ackDetails5Bytes, uint16_t messageLength,
|
|
const char* messageText)
|
|
{
|
|
cmidi2_ci_ack_nak_common(
|
|
buf, deviceId, versionAndFormat, sourceMUID, destinationMUID, originalTransactionSubID2Class,
|
|
ackStatusCode, ackStatusData, ackDetails5Bytes, messageLength, messageText);
|
|
}
|
|
|
|
static inline void cmidi2_ci_nak(
|
|
uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t originalTransactionSubID2Class, uint8_t nakStatusCode,
|
|
uint8_t nakStatusData, uint8_t* nakDetails5Bytes, uint16_t messageLength,
|
|
const char* messageText)
|
|
{
|
|
cmidi2_ci_ack_nak_common(
|
|
buf, deviceId, versionAndFormat, sourceMUID, destinationMUID, originalTransactionSubID2Class,
|
|
nakStatusCode, nakStatusData, nakDetails5Bytes, messageLength, messageText);
|
|
}
|
|
|
|
// Protocol Negotiation
|
|
// Note that it was removed in MIDI 2.0 specification June 2023 Updates (MIDI-CI 1.2).
|
|
|
|
static inline void cmidi2_ci_protocol_info(uint8_t* buf, cmidi2_ci_protocol_type_info info)
|
|
{
|
|
buf[0] = info.type;
|
|
buf[1] = info.version;
|
|
buf[2] = info.extensions;
|
|
buf[3] = info.reserved1;
|
|
buf[4] = info.reserved2;
|
|
}
|
|
|
|
static inline void cmidi2_ci_protocols(
|
|
uint8_t* buf, uint8_t numSupportedProtocols, cmidi2_ci_protocol_type_info* protocolTypes)
|
|
{
|
|
buf[0] = numSupportedProtocols;
|
|
for (int i = 0; i < numSupportedProtocols; i++)
|
|
cmidi2_ci_protocol_info(buf + 1 + i * 5, protocolTypes[i]);
|
|
}
|
|
|
|
static inline void cmidi2_ci_protocol_negotiation(
|
|
uint8_t* buf, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t authorityLevel, uint8_t numSupportedProtocols,
|
|
cmidi2_ci_protocol_type_info* protocolTypes)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, 0x7F,
|
|
isReply ? CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_REPLY
|
|
: CMIDI2_CI_SUB_ID_2_PROTOCOL_NEGOTIATION_INQUIRY,
|
|
1, sourceMUID, destinationMUID);
|
|
buf[13] = authorityLevel;
|
|
cmidi2_ci_protocols(buf + 14, numSupportedProtocols, protocolTypes);
|
|
}
|
|
|
|
static inline void cmidi2_ci_protocol_set(
|
|
uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel,
|
|
cmidi2_ci_protocol_type_info newProtocolType)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, 0x7F, CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL, 1, sourceMUID, destinationMUID);
|
|
buf[13] = authorityLevel;
|
|
cmidi2_ci_protocol_info(buf + 14, newProtocolType);
|
|
}
|
|
|
|
static inline void cmidi2_ci_protocol_test(
|
|
uint8_t* buf, bool isInitiatorToResponder, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t authorityLevel, uint8_t* testData48Bytes)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, 0x7F,
|
|
isInitiatorToResponder ? CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_I2R
|
|
: CMIDI2_CI_SUB_ID_2_TEST_NEW_PROTOCOL_R2I,
|
|
1, sourceMUID, destinationMUID);
|
|
buf[13] = authorityLevel;
|
|
memcpy(buf + 14, testData48Bytes, 48);
|
|
}
|
|
|
|
static inline void cmidi2_ci_protocol_confirm_established(
|
|
uint8_t* buf, uint32_t sourceMUID, uint32_t destinationMUID, uint8_t authorityLevel)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, 0x7F, CMIDI2_CI_SUB_ID_2_CONFIRM_NEW_PROTOCOL_ESTABLISHED, 1, sourceMUID,
|
|
destinationMUID);
|
|
buf[13] = authorityLevel;
|
|
}
|
|
|
|
static inline int32_t cmidi2_ci_try_parse_new_protocol(uint8_t* buf, size_t length)
|
|
{
|
|
return (length != 19 || buf[0] != 0x7E || buf[1] != 0x7F || buf[2] != CMIDI2_CI_SUB_ID
|
|
|| buf[3] != CMIDI2_CI_SUB_ID_2_SET_NEW_PROTOCOL || buf[4] != 1)
|
|
? 0
|
|
: buf[14];
|
|
}
|
|
|
|
// Profile Configuration
|
|
|
|
static inline void cmidi2_ci_profile(uint8_t* buf, cmidi2_profile_id info)
|
|
{
|
|
buf[0] = info.fixed_7e;
|
|
buf[1] = info.bank;
|
|
buf[2] = info.number;
|
|
buf[3] = info.version;
|
|
buf[4] = info.level;
|
|
}
|
|
|
|
static inline void cmidi2_ci_profile_inquiry(
|
|
uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY, 1, sourceMUID, destinationMUID);
|
|
}
|
|
|
|
static inline void cmidi2_ci_profile_inquiry_reply(
|
|
uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t numEnabledProfiles, cmidi2_profile_id* enabledProfiles, uint8_t numDisabledProfiles,
|
|
cmidi2_profile_id* disabledProfiles)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_INQUIRY_REPLY, 1, sourceMUID, destinationMUID);
|
|
buf[13] = numEnabledProfiles;
|
|
for (int i = 0; i < numEnabledProfiles; i++)
|
|
cmidi2_ci_profile(buf + 14 + i * 5, enabledProfiles[i]);
|
|
uint32_t pos = 14 + numEnabledProfiles * 5;
|
|
buf[pos++] = numDisabledProfiles;
|
|
for (int i = 0; i < numDisabledProfiles; i++)
|
|
cmidi2_ci_profile(buf + pos + i * 5, disabledProfiles[i]);
|
|
}
|
|
|
|
static inline void cmidi2_ci_profile_set(
|
|
uint8_t* buf, uint8_t destination, bool turnOn, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
cmidi2_profile_id profile)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, destination,
|
|
turnOn ? CMIDI2_CI_SUB_ID_2_SET_PROFILE_ON : CMIDI2_CI_SUB_ID_2_SET_PROFILE_OFF, 1,
|
|
sourceMUID, destinationMUID);
|
|
cmidi2_ci_profile(buf + 13, profile);
|
|
}
|
|
|
|
static inline void cmidi2_ci_profile_report(
|
|
uint8_t* buf, uint8_t source, bool isEnabledReport, uint32_t sourceMUID,
|
|
cmidi2_profile_id profile)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, source,
|
|
isEnabledReport ? CMIDI2_CI_SUB_ID_2_PROFILE_ENABLED_REPORT
|
|
: CMIDI2_CI_SUB_ID_2_PROFILE_DISABLED_REPORT,
|
|
1, sourceMUID, 0x7F7F7F7F);
|
|
cmidi2_ci_profile(buf + 13, profile);
|
|
}
|
|
|
|
static inline void cmidi2_ci_profile_specific_data(
|
|
uint8_t* buf, uint8_t source, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
cmidi2_profile_id profile, uint32_t dataSize, void* data)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, source, CMIDI2_CI_SUB_ID_2_PROFILE_SPECIFIC_DATA, 1, sourceMUID, destinationMUID);
|
|
cmidi2_ci_profile(buf + 13, profile);
|
|
cmidi2_ci_direct_uint32_at(buf + 18, dataSize);
|
|
memcpy(buf + 22, data, dataSize);
|
|
}
|
|
|
|
// Property Exchange
|
|
|
|
static inline void cmidi2_ci_property_get_capabilities(
|
|
uint8_t* buf, uint8_t destination, bool isReply, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t maxSupportedRequests)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, destination,
|
|
isReply ? CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY
|
|
: CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_INQUIRY,
|
|
1, sourceMUID, destinationMUID);
|
|
buf[13] = maxSupportedRequests;
|
|
}
|
|
|
|
// common to all of: has data & reply, get data & reply, set data & reply, subscribe & reply, notify
|
|
static inline void cmidi2_ci_property_common(
|
|
uint8_t* buf, uint8_t destination, uint8_t messageTypeSubId2, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t requestId, uint16_t headerSize, void* header,
|
|
uint16_t numChunks, uint16_t chunkIndex, uint16_t dataSize, void* data)
|
|
{
|
|
cmidi2_ci_message_common(buf, destination, messageTypeSubId2, 1, sourceMUID, destinationMUID);
|
|
buf[13] = requestId;
|
|
cmidi2_ci_direct_uint16_at(buf + 14, headerSize);
|
|
memcpy(buf + 16, header, headerSize);
|
|
cmidi2_ci_direct_uint16_at(buf + 16 + headerSize, numChunks);
|
|
cmidi2_ci_direct_uint16_at(buf + 18 + headerSize, chunkIndex);
|
|
cmidi2_ci_direct_uint16_at(buf + 20 + headerSize, dataSize);
|
|
memcpy(buf + 22 + headerSize, data, dataSize);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_get_capabilities_reply(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t maxSupportedRequests, uint8_t peMajorVersion, uint8_t peMinorVersion)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK,
|
|
CMIDI2_CI_SUB_ID_2_PROPERTY_CAPABILITIES_REPLY, versionAndFormat, sourceMUID,
|
|
destinationMUID);
|
|
buf[13] = maxSupportedRequests;
|
|
buf[14] = peMajorVersion;
|
|
buf[15] = peMinorVersion;
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_data_common(
|
|
uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t requestId, size_t headerSize, const uint8_t* headerData,
|
|
uint16_t numChunksInMessage, uint16_t currentChunk, uint16_t propertyDataLength,
|
|
const char* propertyData)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, subId2, versionAndFormat, sourceMUID,
|
|
destinationMUID);
|
|
buf[13] = requestId;
|
|
buf[14] = headerSize % 0xFF;
|
|
buf[15] = headerSize / 0xFF;
|
|
memcpy(buf + 16, headerData, headerSize);
|
|
cmidi2_ci_direct_uint16_at(buf + headerSize + 16, numChunksInMessage);
|
|
cmidi2_ci_direct_uint16_at(buf + headerSize + 18, currentChunk);
|
|
cmidi2_ci_direct_uint16_at(buf + headerSize + 20, propertyDataLength);
|
|
if (propertyData)
|
|
memcpy(buf + headerSize + 22, propertyData, propertyDataLength);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_get_data(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t requestId, size_t headerSize, const uint8_t* headerData)
|
|
{
|
|
cmidi2_ci_property_data_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA, versionAndFormat, sourceMUID, destinationMUID,
|
|
requestId, headerSize, headerData, 1, 1, 0, NULL);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_get_data_reply(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,
|
|
uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)
|
|
{
|
|
cmidi2_ci_property_data_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_PROPERTY_GET_DATA_REPLY, versionAndFormat, sourceMUID,
|
|
destinationMUID, requestId, headerSize, headerData, numChunksInMessage, currentChunk,
|
|
propertyDataLength, propertyData);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_set_data(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,
|
|
uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)
|
|
{
|
|
cmidi2_ci_property_data_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA, versionAndFormat, sourceMUID, destinationMUID,
|
|
requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,
|
|
propertyData);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_set_data_reply(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t requestId, size_t headerSize, const uint8_t* headerData)
|
|
{
|
|
cmidi2_ci_property_data_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SET_DATA_REPLY, versionAndFormat, sourceMUID,
|
|
destinationMUID, requestId, headerSize, headerData, 1, 1, 0, NULL);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_subscribe(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,
|
|
uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)
|
|
{
|
|
cmidi2_ci_property_data_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE, versionAndFormat, sourceMUID, destinationMUID,
|
|
requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,
|
|
propertyData);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_subscribe_reply(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,
|
|
uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)
|
|
{
|
|
cmidi2_ci_property_data_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_PROPERTY_SUBSCRIBE_REPLY, versionAndFormat, sourceMUID,
|
|
destinationMUID, requestId, headerSize, headerData, numChunksInMessage, currentChunk,
|
|
propertyDataLength, propertyData);
|
|
}
|
|
|
|
static inline void cmidi2_ci_property_notify(
|
|
uint8_t* buf, uint8_t versionAndFormat, uint32_t sourceMUID, uint32_t destinationMUID,
|
|
uint8_t requestId, size_t headerSize, const uint8_t* headerData, uint16_t numChunksInMessage,
|
|
uint16_t currentChunk, uint16_t propertyDataLength, const char* propertyData)
|
|
{
|
|
cmidi2_ci_property_data_common(
|
|
buf, CMIDI2_CI_SUB_ID_2_PROPERTY_NOTIFY, versionAndFormat, sourceMUID, destinationMUID,
|
|
requestId, headerSize, headerData, numChunksInMessage, currentChunk, propertyDataLength,
|
|
propertyData);
|
|
}
|
|
|
|
// Process Inquiry
|
|
|
|
static inline void cmidi2_ci_process_get_capabilities(
|
|
uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID)
|
|
{
|
|
(void)subId2;
|
|
cmidi2_ci_message_common(
|
|
buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK, CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES,
|
|
versionAndFormat, sourceMUID, destinationMUID);
|
|
}
|
|
|
|
static inline void cmidi2_ci_process_get_capabilities_reply(
|
|
uint8_t* buf, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t processInquirySupportedFeatures)
|
|
{
|
|
(void)subId2;
|
|
cmidi2_ci_message_common(
|
|
buf, CMIDI2_CI_DEVICE_ID_WHOLE_FUNCTION_BLOCK,
|
|
CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES_REPLY, versionAndFormat, sourceMUID,
|
|
destinationMUID);
|
|
buf[13] = processInquirySupportedFeatures;
|
|
}
|
|
|
|
static inline void cmidi2_ci_process_midi_report_common(
|
|
uint8_t* buf, uint8_t deviceId, uint8_t subId2, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages,
|
|
uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages)
|
|
{
|
|
(void)subId2;
|
|
cmidi2_ci_message_common(
|
|
buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_CAPABILITIES, versionAndFormat, sourceMUID,
|
|
destinationMUID);
|
|
buf[13] = messageDataControl;
|
|
buf[14] = requestedSystemMessages;
|
|
buf[15] = 0; // reserved
|
|
buf[16] = requestedChannelControllerMessages;
|
|
buf[17] = requestedNoteDataMessages;
|
|
}
|
|
|
|
static inline void cmidi2_ci_process_get_midi_report(
|
|
uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages,
|
|
uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages)
|
|
{
|
|
cmidi2_ci_process_midi_report_common(
|
|
buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT, versionAndFormat, sourceMUID,
|
|
destinationMUID, messageDataControl, requestedSystemMessages,
|
|
requestedChannelControllerMessages, requestedNoteDataMessages);
|
|
}
|
|
|
|
static inline void cmidi2_ci_process_get_midi_report_reply(
|
|
uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID, uint8_t messageDataControl, uint8_t requestedSystemMessages,
|
|
uint8_t requestedChannelControllerMessages, uint8_t requestedNoteDataMessages)
|
|
{
|
|
cmidi2_ci_process_midi_report_common(
|
|
buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_REPLY, versionAndFormat,
|
|
sourceMUID, destinationMUID, messageDataControl, requestedSystemMessages,
|
|
requestedChannelControllerMessages, requestedNoteDataMessages);
|
|
}
|
|
|
|
static inline void cmidi2_ci_process_get_midi_report_end(
|
|
uint8_t* buf, uint8_t deviceId, uint8_t versionAndFormat, uint32_t sourceMUID,
|
|
uint32_t destinationMUID)
|
|
{
|
|
cmidi2_ci_message_common(
|
|
buf, deviceId, CMIDI2_CI_SUB_ID_2_PROCESS_GET_MIDI_REPORT_END, versionAndFormat, sourceMUID,
|
|
destinationMUID);
|
|
}
|
|
|
|
// Miscellaneous MIDI Utilities
|
|
|
|
/** Encodes `value` into `bytes` stream. Returns the length of the encoded value in bytes. */
|
|
static inline uint8_t cmidi2_midi1_write_7bit_encoded_int(uint8_t* bytes, uint32_t value)
|
|
{
|
|
uint8_t pos = 0;
|
|
for (;; pos++)
|
|
{
|
|
bytes[pos] = value % 0x80;
|
|
if (value >= 0x80)
|
|
{
|
|
value /= 0x80;
|
|
bytes[pos] |= 0x80;
|
|
}
|
|
else
|
|
return pos + 1;
|
|
}
|
|
}
|
|
|
|
/** Returns the length of `value` when it would be encoded into a byte stream. */
|
|
static inline uint8_t cmidi2_midi1_get_7bit_encoded_int_length(uint32_t value)
|
|
{
|
|
for (uint8_t ret = 1;; ret++)
|
|
{
|
|
if (value >= 0x80)
|
|
value /= 0x80;
|
|
else
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Returns the 7-bit encoded value in `bytes` stream of `length` bytes. */
|
|
static inline uint32_t cmidi2_midi1_get_7bit_encoded_int(uint8_t* bytes, uint32_t length)
|
|
{
|
|
uint8_t* start = bytes;
|
|
uint32_t value = 0;
|
|
for (int digits = 0;; digits++)
|
|
{
|
|
if (bytes >= start + length)
|
|
break;
|
|
value += (0x7F & (*bytes)) << (digits * 7);
|
|
if (*bytes < 0x80)
|
|
break;
|
|
bytes++;
|
|
}
|
|
bytes++;
|
|
return value;
|
|
}
|
|
|
|
static inline uint32_t cmidi2_midi1_get_message_size(uint8_t* bytes, uint32_t length)
|
|
{
|
|
uint32_t metaLength;
|
|
uint8_t* start = bytes;
|
|
uint8_t* end = bytes + length;
|
|
switch (bytes[0])
|
|
{
|
|
case 0xF0:
|
|
for (bytes++; bytes < end; bytes++)
|
|
if (*bytes == 0xF7)
|
|
break;
|
|
bytes++;
|
|
break;
|
|
case 0xFF:
|
|
bytes++;
|
|
metaLength = cmidi2_midi1_get_7bit_encoded_int(bytes, end - bytes);
|
|
bytes += metaLength + cmidi2_midi1_get_7bit_encoded_int_length(metaLength);
|
|
break;
|
|
default:
|
|
switch (bytes[0] & 0xF0)
|
|
{
|
|
case 0xC0:
|
|
case 0xD0:
|
|
bytes += 2;
|
|
break;
|
|
default:
|
|
bytes += 3;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
return bytes - start;
|
|
}
|
|
|
|
// MIDI1 to UMP Translator
|
|
|
|
/* Should we define some structs like this to support conversion from MIDI1 UMP to MIDI2 UMP and complicate the API?
|
|
I'm not sure if there are enough need, and it's better to avoid another cmidi2_convert_midi1_to_ump() (200-ish LoC).
|
|
|
|
typedef struct cmidi2_midi1_sequence_midi1_bytes {
|
|
// input MIDI1 messages or SMF events.
|
|
uint8_t *midi1;
|
|
// size of the input stream to process in bytes
|
|
size_t midi1_num_bytes;
|
|
// it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the MIDI1 stream.
|
|
size_t midi1_proceeded_bytes;
|
|
} cmidi2_midi1_sequence_midi1_bytes;
|
|
|
|
typedef struct cmidi2_midi1_sequence_umps {
|
|
// MIDI1 UMP stream.
|
|
cmidi2_ump* ump;
|
|
// size (capacity) of the output stream in bytes
|
|
size_t ump_num_bytes;
|
|
// it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the UMP stream.
|
|
size_t ump_proceeded_bytes;
|
|
} cmidi2_midi1_sequence_ump;
|
|
*/
|
|
|
|
enum cmidi2_translator_endianness
|
|
{
|
|
CMIDI2_TRANSLATOR_DEFAULT_ENDIAN,
|
|
CMIDI2_TRANSLATOR_BIG_ENDIAN,
|
|
CMIDI2_TRANSLATOR_LITTLE_ENDIAN
|
|
};
|
|
|
|
// Conversion from MIDI1 message bytes or SMF event list to MIDI2 UMP stream
|
|
//
|
|
// The conversion requires some preserved context e.g. RPN/NRPN/DTE
|
|
// MSB/LSB, so we take this struct.
|
|
typedef struct cmidi2_midi_conversion_context
|
|
{
|
|
// When it is true, it means the input MIDI1 stream contains delta time.
|
|
// TODO: implement support for it
|
|
bool is_midi1_smf;
|
|
// Context tempo as in SMF specification defines.
|
|
// If the MIDI1 stream is SMF and non-SMPTE delta time is used, then we have to calculate
|
|
// the actual delta time length in SMPTE (then to JR timestamp).
|
|
// TODO: implement support for it
|
|
int32_t tempo;
|
|
// input MIDI1 messages or SMF events.
|
|
uint8_t* midi1;
|
|
// size of the input stream to process in bytes
|
|
size_t midi1_num_bytes;
|
|
// it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the MIDI1 stream.
|
|
size_t midi1_proceeded_bytes;
|
|
// output UMP stream.
|
|
cmidi2_ump* ump;
|
|
// size (capacity) of the output stream in bytes
|
|
size_t ump_num_bytes;
|
|
// it is updated as per cmidi2_convert_midi1_messages_to_ump() proceeds the UMP stream.
|
|
size_t ump_proceeded_bytes;
|
|
// DTE conversion target.
|
|
// cmidi2_convert_midi1_messages_to_ump() will return *_INVALID_RPN or *_INVALID_NRPN
|
|
// for such invalid sequences, and to report that correctly we need to preserve CC status.
|
|
// They are initialized to 0x8080 that implies both bank (MSB) and index (LSB) are invalid (> 0x7F).
|
|
// When they are assigned valid values, then (context_[n]rpn & 0x8080) will become 0.
|
|
// When cmidi2_convert_midi1_messages_to_ump() encountered DTE LSB, they are consumed
|
|
// and reset to the initial value (0x8080).
|
|
int32_t context_rpn;
|
|
int32_t context_nrpn;
|
|
int32_t context_dte;
|
|
// MIDI 2.0 Defalult Translation (UMP specification Appendix D.3) accepts only DTE LSB
|
|
// as the conversion terminator, but cmidi2 allows DTE LSB to come first,
|
|
// if this flag is enabled.
|
|
bool allow_reordered_dte;
|
|
// Bank Select CC is preserved for the next program change.
|
|
// The initial value is 0x8080, same as RPN/NRPN/DTE.
|
|
// After program change is set, it is reset to the initial value.
|
|
uint32_t context_bank;
|
|
// Group can be specified.
|
|
uint8_t group;
|
|
// Destination protocol: can be MIDI1 UMP or MIDI2 UMP.
|
|
enum cmidi2_ci_protocol_values midi_protocol;
|
|
// Sysex conversion can be done to sysex8
|
|
bool use_sysex8;
|
|
// Determine whether delta time is skipped or not (inserted like SMF) in the output MIDI1 stream.
|
|
bool skip_delta_time;
|
|
// UMP serialization endianness
|
|
int32_t ump_serialization_endianness;
|
|
} cmidi2_midi_conversion_context;
|
|
|
|
enum cmidi2_midi_conversion_result
|
|
{
|
|
CMIDI2_CONVERSION_RESULT_OK = 0,
|
|
CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE = 1,
|
|
CMIDI2_CONVERSION_RESULT_INVALID_SYSEX = 0x10,
|
|
CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE = 0x11,
|
|
CMIDI2_CONVERSION_RESULT_INVALID_STATUS = 0x13,
|
|
CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7 = 0x20,
|
|
};
|
|
|
|
static inline void
|
|
cmidi2_midi_conversion_context_initialize(cmidi2_midi_conversion_context* context)
|
|
{
|
|
context->is_midi1_smf = false;
|
|
context->tempo = 500000;
|
|
context->midi1 = NULL;
|
|
context->midi1_num_bytes = 0;
|
|
context->midi1_proceeded_bytes = 0;
|
|
context->ump = NULL;
|
|
context->ump_num_bytes = 0;
|
|
context->ump_proceeded_bytes = 0;
|
|
context->context_rpn = 0x8080;
|
|
context->context_nrpn = 0x8080;
|
|
context->context_dte = 0x8080;
|
|
context->context_bank = 0x8080;
|
|
context->allow_reordered_dte = false;
|
|
context->group = 0;
|
|
context->midi_protocol = CMIDI2_PROTOCOL_TYPE_MIDI2;
|
|
context->use_sysex8 = false;
|
|
context->skip_delta_time = false;
|
|
context->ump_serialization_endianness = CMIDI2_TRANSLATOR_DEFAULT_ENDIAN;
|
|
}
|
|
|
|
typedef struct cmidi2_convert_sysex_context
|
|
{
|
|
cmidi2_midi_conversion_context* conversion_context;
|
|
size_t dst_offset;
|
|
} cmidi2_convert_sysex_context;
|
|
|
|
static inline void*
|
|
cmidi2_internal_convert_add_midi1_sysex7_ump_to_list(uint64_t data, void* context)
|
|
{
|
|
cmidi2_convert_sysex_context* s7ctx = (cmidi2_convert_sysex_context*)context;
|
|
s7ctx->conversion_context->ump[s7ctx->dst_offset] = data >> 32;
|
|
s7ctx->conversion_context->ump[s7ctx->dst_offset + 1] = data & 0xFFFFFFFF;
|
|
s7ctx->conversion_context->ump_proceeded_bytes += 2;
|
|
return NULL;
|
|
}
|
|
|
|
static inline void* cmidi2_internal_convert_add_midi1_sysex8_ump_to_list(
|
|
uint64_t data1, uint64_t data2, size_t index, void* context)
|
|
{
|
|
(void)index;
|
|
cmidi2_convert_sysex_context* s8ctx = (cmidi2_convert_sysex_context*)context;
|
|
s8ctx->conversion_context->ump[s8ctx->dst_offset] = data1 >> 32;
|
|
s8ctx->conversion_context->ump[s8ctx->dst_offset + 1] = data1 & 0xFFFFFFFF;
|
|
s8ctx->conversion_context->ump[s8ctx->dst_offset + 2] = data2 >> 32;
|
|
s8ctx->conversion_context->ump[s8ctx->dst_offset + 3] = data2 & 0xFFFFFFFF;
|
|
s8ctx->conversion_context->ump_proceeded_bytes += 4;
|
|
return NULL;
|
|
}
|
|
|
|
static inline uint64_t
|
|
cmidi2_internal_convert_midi1_dte_to_ump(cmidi2_midi_conversion_context* context, uint8_t channel)
|
|
{
|
|
bool isRpn = (context->context_rpn & 0x8080) == 0;
|
|
uint8_t msb = (isRpn ? context->context_rpn : context->context_nrpn) >> 8;
|
|
uint8_t lsb = (isRpn ? context->context_rpn : context->context_nrpn) & 0xFF;
|
|
int32_t data = (context->context_dte >> 8 << 25) + ((context->context_dte & 0x7F) << 18);
|
|
// reset RPN/NRPN/DTE status to the initial values.
|
|
context->context_rpn = 0x8080;
|
|
context->context_nrpn = 0x8080;
|
|
context->context_dte = 0x8080;
|
|
return isRpn ? cmidi2_ump_midi2_rpn(context->group, channel, msb, lsb, data)
|
|
: cmidi2_ump_midi2_nrpn(context->group, channel, msb, lsb, data);
|
|
}
|
|
|
|
static inline uint32_t cmidi2_internal_swap_endian(uint32_t v)
|
|
{
|
|
return ((v & 0xFF) << 24) + (((v >> 8) & 0xFF) << 16) + (((v >> 16) & 0xFF) << 8)
|
|
+ ((v >> 24) & 0xFF);
|
|
}
|
|
|
|
/** converts MIDI1 bytestream which can contain deltaTime in SMF, to MIDI2 UMP stream.
|
|
* The resulting stream is native endianness.
|
|
*/
|
|
static enum cmidi2_midi_conversion_result
|
|
cmidi2_convert_midi1_to_ump(cmidi2_midi_conversion_context* context)
|
|
{
|
|
uint8_t* dst = (uint8_t*)context->ump;
|
|
size_t sLen = context->midi1_num_bytes;
|
|
size_t dLen = context->ump_num_bytes;
|
|
uint8_t* sIdx = (uint8_t*)&context->midi1_proceeded_bytes;
|
|
uint8_t* dIdx = (uint8_t*)&context->ump_proceeded_bytes;
|
|
|
|
while (*sIdx < sLen)
|
|
{
|
|
// FIXME: implement deltaTime to JR Timestamp conversion.
|
|
|
|
if (context->midi1[*sIdx] == 0xF0)
|
|
{
|
|
// sysex
|
|
uint8_t* f7
|
|
= (uint8_t*)memchr(context->midi1 + *sIdx, 0xF7, context->midi1_num_bytes - *sIdx);
|
|
if (f7 == NULL)
|
|
{
|
|
return CMIDI2_CONVERSION_RESULT_INVALID_SYSEX; // error
|
|
}
|
|
size_t sysexSize = f7 - context->midi1 - *sIdx - 1; // excluding 0xF7
|
|
size_t numPackets = context->use_sysex8 ? cmidi2_ump_sysex8_get_num_packets(sysexSize)
|
|
: cmidi2_ump_sysex7_get_num_packets(sysexSize);
|
|
if (dLen - *dIdx < numPackets)
|
|
return CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE;
|
|
cmidi2_convert_sysex_context sysExCtx;
|
|
sysExCtx.conversion_context = context;
|
|
sysExCtx.dst_offset = *dIdx;
|
|
if (context->use_sysex8)
|
|
{
|
|
// ignoring the return code as it never returns non-NULL... (size is already verified)
|
|
cmidi2_ump_sysex8_process(
|
|
context->group, context->midi1 + *sIdx, sysexSize, 0,
|
|
cmidi2_internal_convert_add_midi1_sysex8_ump_to_list, &sysExCtx);
|
|
}
|
|
else
|
|
{
|
|
// ignoring the return code as it never returns non-NULL... (size is already verified)
|
|
cmidi2_ump_sysex7_process(
|
|
context->group, context->midi1 + *sIdx,
|
|
cmidi2_internal_convert_add_midi1_sysex7_ump_to_list, &sysExCtx);
|
|
}
|
|
*sIdx += sysexSize + 1; // +1 for 0xF7
|
|
}
|
|
else
|
|
{
|
|
// fixed sized message
|
|
size_t len = cmidi2_midi1_get_message_size(context->midi1 + *sIdx, sLen - *sIdx);
|
|
uint8_t byte2 = context->midi1[*sIdx + 1];
|
|
uint8_t byte3 = len > 2 ? context->midi1[*sIdx + 2] : 0;
|
|
uint8_t channel = context->midi1[*sIdx] & 0xF;
|
|
if (context->midi_protocol == CMIDI2_PROTOCOL_TYPE_MIDI1)
|
|
{
|
|
// generate MIDI1 UMPs
|
|
dst[*dIdx] = cmidi2_ump_midi1_message(
|
|
context->group, context->midi1[*sIdx] & 0xF0, channel, byte2, byte3);
|
|
*sIdx += len;
|
|
*dIdx += 4;
|
|
}
|
|
else
|
|
{
|
|
// generate MIDI2 UMPs
|
|
uint64_t m2;
|
|
const int8_t NO_ATTRIBUTE_TYPE = 0;
|
|
const int16_t NO_ATTRIBUTE_DATA = 0;
|
|
bool bankValid, bankMsbValid, bankLsbValid;
|
|
bool skipEmitUmp = false;
|
|
switch (context->midi1[*sIdx] & 0xF0)
|
|
{
|
|
case CMIDI2_STATUS_NOTE_OFF:
|
|
m2 = cmidi2_ump_midi2_note_off(
|
|
context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA);
|
|
break;
|
|
case CMIDI2_STATUS_NOTE_ON:
|
|
m2 = cmidi2_ump_midi2_note_on(
|
|
context->group, channel, byte2, NO_ATTRIBUTE_TYPE, byte3 << 9, NO_ATTRIBUTE_DATA);
|
|
break;
|
|
case CMIDI2_STATUS_PAF:
|
|
m2 = cmidi2_ump_midi2_paf(context->group, channel, byte2, byte3 << 25);
|
|
break;
|
|
case CMIDI2_STATUS_CC:
|
|
switch (byte2)
|
|
{
|
|
case CMIDI2_CC_RPN_MSB:
|
|
context->context_rpn = (context->context_rpn & 0xFF) | (byte3 << 8);
|
|
skipEmitUmp = true;
|
|
break;
|
|
case CMIDI2_CC_RPN_LSB:
|
|
context->context_rpn = (context->context_rpn & 0xFF00) | byte3;
|
|
skipEmitUmp = true;
|
|
break;
|
|
case CMIDI2_CC_NRPN_MSB:
|
|
context->context_nrpn = (context->context_nrpn & 0xFF) | byte3 << 8;
|
|
skipEmitUmp = true;
|
|
break;
|
|
case CMIDI2_CC_NRPN_LSB:
|
|
context->context_nrpn = (context->context_nrpn & 0xFF00) | byte3;
|
|
skipEmitUmp = true;
|
|
break;
|
|
case CMIDI2_CC_DTE_MSB:
|
|
context->context_dte = (context->context_dte & 0xFF) | (byte3 << 8);
|
|
|
|
if (context->allow_reordered_dte && (context->context_dte & 0x8080) == 0)
|
|
m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel);
|
|
else
|
|
skipEmitUmp = true;
|
|
|
|
break;
|
|
case CMIDI2_CC_DTE_LSB:
|
|
context->context_dte = (context->context_dte & 0xFF00) | byte3;
|
|
|
|
if ((context->context_dte & 0x8000) && !context->allow_reordered_dte)
|
|
return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;
|
|
if ((context->context_rpn & 0x8080) && (context->context_nrpn & 0x8080))
|
|
return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;
|
|
m2 = cmidi2_internal_convert_midi1_dte_to_ump(context, channel);
|
|
|
|
break;
|
|
case CMIDI2_CC_BANK_SELECT:
|
|
context->context_bank = (context->context_bank & 0xFF) | (byte3 << 8);
|
|
skipEmitUmp = true;
|
|
break;
|
|
case CMIDI2_CC_BANK_SELECT_LSB:
|
|
context->context_bank = (context->context_bank & 0xFF00) | byte3;
|
|
skipEmitUmp = true;
|
|
break;
|
|
default:
|
|
m2 = cmidi2_ump_midi2_cc(context->group, channel, byte2, byte3 << 25);
|
|
break;
|
|
}
|
|
break;
|
|
case CMIDI2_STATUS_PROGRAM:
|
|
bankMsbValid = (context->context_bank & 0x8000) == 0;
|
|
bankLsbValid = (context->context_bank & 0x80) == 0;
|
|
bankValid = bankMsbValid || bankLsbValid;
|
|
m2 = cmidi2_ump_midi2_program(
|
|
context->group, channel,
|
|
bankValid ? CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID
|
|
: CMIDI2_PROGRAM_CHANGE_OPTION_NONE,
|
|
byte2, bankMsbValid ? context->context_bank >> 8 : 0,
|
|
bankLsbValid ? context->context_bank & 0x7F : 0);
|
|
context->context_bank = 0x8080;
|
|
break;
|
|
case CMIDI2_STATUS_CAF:
|
|
m2 = cmidi2_ump_midi2_caf(context->group, channel, byte2 << 25);
|
|
break;
|
|
case CMIDI2_STATUS_PITCH_BEND:
|
|
// Note: Pitch Bend values in the MIDI 1.0 Protocol are presented as Little Endian.
|
|
m2 = cmidi2_ump_midi2_pitch_bend_direct(
|
|
context->group, channel, ((byte3 << 7) + byte2) << 18);
|
|
break;
|
|
default:
|
|
return CMIDI2_CONVERSION_RESULT_INVALID_STATUS;
|
|
}
|
|
if (!skipEmitUmp)
|
|
{
|
|
int platEndian = cmidi2_util_is_platform_little_endian()
|
|
? CMIDI2_TRANSLATOR_LITTLE_ENDIAN
|
|
: CMIDI2_TRANSLATOR_BIG_ENDIAN;
|
|
int actualEndian
|
|
= context->ump_serialization_endianness != CMIDI2_TRANSLATOR_DEFAULT_ENDIAN
|
|
? context->ump_serialization_endianness
|
|
: platEndian;
|
|
if (platEndian != actualEndian)
|
|
m2 = (((uint64_t)cmidi2_internal_swap_endian(m2 >> 32)) << 32)
|
|
| cmidi2_internal_swap_endian(m2 & 0xFFFFFFFF);
|
|
*(uint32_t*)(dst + *dIdx) = m2 >> 32;
|
|
*dIdx += 4;
|
|
*(uint32_t*)(dst + *dIdx) = m2 & 0xFFFFFFFF;
|
|
*dIdx += 4;
|
|
}
|
|
*sIdx += len;
|
|
}
|
|
}
|
|
}
|
|
// incomplete UMP sequence will be reported as CMIDI2_CONVERSION_RESULT_OUT_OF_SPACE,
|
|
// so it is safe to judge that incomplete RPN/NRPN/DTE state at this state means invalid.
|
|
if (context->context_rpn != 0x8080 || context->context_nrpn != 0x8080
|
|
|| context->context_dte != 0x8080)
|
|
return CMIDI2_CONVERSION_RESULT_INVALID_DTE_SEQUENCE;
|
|
|
|
return CMIDI2_CONVERSION_RESULT_OK;
|
|
}
|
|
|
|
// UMP to MIDI1 Translator
|
|
|
|
static int32_t cmidi2_internal_convert_jr_timestamp_to_timecode(
|
|
int32_t deltaTime, cmidi2_midi_conversion_context* context)
|
|
{
|
|
(void)context;
|
|
// FIXME: implement
|
|
return deltaTime;
|
|
}
|
|
|
|
static size_t cmidi2_internal_add_midi1_delta_time(
|
|
uint8_t* dst, cmidi2_midi_conversion_context* context, int32_t deltaTime)
|
|
{
|
|
if (!context || context->skip_delta_time)
|
|
return 0;
|
|
size_t* dIdx = (size_t*)&context->midi1_proceeded_bytes;
|
|
int32_t len = cmidi2_midi1_get_7bit_encoded_int_length(deltaTime);
|
|
cmidi2_midi1_write_7bit_encoded_int(dst, deltaTime);
|
|
*dIdx += len;
|
|
return len;
|
|
}
|
|
|
|
/// Convert one single UMP (without JR Timestamp) to MIDI 1.0 Message (without delta time)
|
|
/// It is a lengthy function, so it is recommended to wrap it in another non-inline function.
|
|
static inline size_t cmidi2_convert_single_ump_to_timed_midi1(
|
|
uint8_t* dst, size_t maxBytes, cmidi2_ump* ump, int32_t deltaTime,
|
|
cmidi2_midi_conversion_context* context, uint8_t* sysex7Buffer, size_t* sysex7BufferIndex)
|
|
{
|
|
(void)maxBytes;
|
|
size_t midiEventSize = 0;
|
|
uint64_t sysex7U64;
|
|
uint8_t sysex7NumBytesInUmp;
|
|
|
|
uint8_t messageType = cmidi2_ump_get_message_type(ump);
|
|
uint8_t statusCode = cmidi2_ump_get_status_code(ump); // may not apply, but won't break.
|
|
|
|
#define CMIDI2_INTERNAL_ADD_DELTA_TIME \
|
|
dst += cmidi2_internal_add_midi1_delta_time(dst, context, deltaTime); \
|
|
dst[0] = statusCode | cmidi2_ump_get_channel(ump);
|
|
|
|
switch (messageType)
|
|
{
|
|
case CMIDI2_MESSAGE_TYPE_SYSTEM:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 1;
|
|
switch (statusCode)
|
|
{
|
|
case 0xF1:
|
|
case 0xF3:
|
|
case 0xF9:
|
|
dst[1] = cmidi2_ump_get_system_message_byte2(ump);
|
|
midiEventSize = 2;
|
|
break;
|
|
}
|
|
break;
|
|
case CMIDI2_MESSAGE_TYPE_MIDI_1_CHANNEL:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 3;
|
|
dst[1] = cmidi2_ump_get_midi1_byte2(ump);
|
|
switch (statusCode)
|
|
{
|
|
case 0xC0:
|
|
case 0xD0:
|
|
midiEventSize = 2;
|
|
break;
|
|
default:
|
|
dst[2] = cmidi2_ump_get_midi1_byte3(ump);
|
|
break;
|
|
}
|
|
break;
|
|
case CMIDI2_MESSAGE_TYPE_MIDI_2_CHANNEL:
|
|
// FIXME: convert MIDI2 to MIDI1 as long as possible
|
|
switch (statusCode)
|
|
{
|
|
case CMIDI2_STATUS_RPN:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 12;
|
|
dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC;
|
|
dst[1] = CMIDI2_CC_RPN_MSB;
|
|
dst[2] = cmidi2_ump_get_midi2_rpn_msb(ump);
|
|
dst[3] = dst[0]; // CC + channel
|
|
dst[4] = CMIDI2_CC_RPN_LSB;
|
|
dst[5] = cmidi2_ump_get_midi2_rpn_lsb(ump);
|
|
dst[6] = dst[0]; // CC + channel
|
|
dst[7] = CMIDI2_CC_DTE_MSB;
|
|
dst[8] = (cmidi2_ump_get_midi2_rpn_data(ump) >> 25) & 0x7F;
|
|
dst[9] = dst[0]; // CC + channel
|
|
dst[10] = CMIDI2_CC_DTE_LSB;
|
|
dst[11] = (cmidi2_ump_get_midi2_rpn_data(ump) >> 18) & 0x7F;
|
|
break;
|
|
case CMIDI2_STATUS_NRPN:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 12;
|
|
dst[0] = cmidi2_ump_get_channel(ump) + CMIDI2_STATUS_CC;
|
|
dst[1] = CMIDI2_CC_NRPN_MSB;
|
|
dst[2] = cmidi2_ump_get_midi2_nrpn_msb(ump);
|
|
dst[3] = dst[0]; // CC + channel
|
|
dst[4] = CMIDI2_CC_NRPN_LSB;
|
|
dst[5] = cmidi2_ump_get_midi2_nrpn_lsb(ump);
|
|
dst[6] = dst[0]; // CC + channel
|
|
dst[7] = CMIDI2_CC_DTE_MSB;
|
|
dst[8] = (cmidi2_ump_get_midi2_nrpn_data(ump) >> 25) & 0x7F;
|
|
dst[9] = dst[0]; // CC + channel
|
|
dst[10] = CMIDI2_CC_DTE_LSB;
|
|
dst[11] = (cmidi2_ump_get_midi2_nrpn_data(ump) >> 18) & 0x7F;
|
|
break;
|
|
case CMIDI2_STATUS_NOTE_OFF:
|
|
case CMIDI2_STATUS_NOTE_ON:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 3;
|
|
dst[1] = cmidi2_ump_get_midi2_note_note(ump);
|
|
dst[2] = cmidi2_ump_get_midi2_note_velocity(ump) / 0x200;
|
|
break;
|
|
case CMIDI2_STATUS_PAF:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 3;
|
|
dst[1] = cmidi2_ump_get_midi2_paf_note(ump);
|
|
dst[2] = cmidi2_ump_get_midi2_paf_data(ump) / 0x2000000;
|
|
break;
|
|
case CMIDI2_STATUS_CC:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 3;
|
|
dst[0] = statusCode | cmidi2_ump_get_channel(ump);
|
|
dst[1] = cmidi2_ump_get_midi2_cc_index(ump);
|
|
dst[2] = cmidi2_ump_get_midi2_cc_data(ump) / 0x2000000;
|
|
break;
|
|
case CMIDI2_STATUS_PROGRAM:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
dst[0] = statusCode | cmidi2_ump_get_channel(ump);
|
|
if (cmidi2_ump_get_midi2_program_options(ump) & CMIDI2_PROGRAM_CHANGE_OPTION_BANK_VALID)
|
|
{
|
|
midiEventSize = 8;
|
|
dst[6] = dst[0]; // copy
|
|
dst[7] = cmidi2_ump_get_midi2_program_program(ump);
|
|
dst[0] = (dst[6] & 0xF) + CMIDI2_STATUS_CC;
|
|
dst[1] = 0; // Bank MSB
|
|
dst[2] = cmidi2_ump_get_midi2_program_bank_msb(ump);
|
|
dst[3] = (dst[6] & 0xF) + CMIDI2_STATUS_CC;
|
|
dst[4] = 32; // Bank LSB
|
|
dst[5] = cmidi2_ump_get_midi2_program_bank_lsb(ump);
|
|
}
|
|
else
|
|
{
|
|
midiEventSize = 2;
|
|
dst[1] = cmidi2_ump_get_midi2_program_program(ump);
|
|
}
|
|
break;
|
|
case CMIDI2_STATUS_CAF:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 2;
|
|
dst[1] = cmidi2_ump_get_midi2_caf_data(ump) / 0x2000000;
|
|
break;
|
|
case CMIDI2_STATUS_PITCH_BEND:
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 3;
|
|
uint32_t pitchBendV1 = cmidi2_ump_get_midi2_pitch_bend_data(ump) / 0x40000;
|
|
dst[1] = pitchBendV1 % 0x80;
|
|
dst[2] = pitchBendV1 / 0x80;
|
|
break;
|
|
// skip for other status bytes; we cannot support them.
|
|
}
|
|
break;
|
|
case CMIDI2_MESSAGE_TYPE_SYSEX7:
|
|
if (sysex7Buffer)
|
|
{
|
|
// sysex7 buffer is processed at cmidi2_convert_ump_to_midi1().
|
|
midiEventSize = 0;
|
|
sysex7U64 = cmidi2_ump_read_uint64_bytes(ump);
|
|
sysex7NumBytesInUmp = cmidi2_ump_get_sysex7_num_bytes(ump);
|
|
for (size_t i = 0; i < sysex7NumBytesInUmp; i++)
|
|
sysex7Buffer[*sysex7BufferIndex + i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i);
|
|
*sysex7BufferIndex += sysex7NumBytesInUmp;
|
|
}
|
|
else
|
|
{
|
|
// minimal implementation for single-byte sysex7
|
|
CMIDI2_INTERNAL_ADD_DELTA_TIME
|
|
midiEventSize = 1 + cmidi2_ump_get_sysex7_num_bytes(ump);
|
|
sysex7U64 = cmidi2_ump_read_uint64_bytes(ump);
|
|
for (size_t i = 0; i < midiEventSize - 1; i++)
|
|
dst[i] = cmidi2_ump_get_byte_from_uint64(sysex7U64, 2 + i);
|
|
}
|
|
break;
|
|
case CMIDI2_MESSAGE_TYPE_SYSEX8_MDS:
|
|
// By the UMP specification they cannot be translated in Default Translation
|
|
midiEventSize = 0;
|
|
break;
|
|
}
|
|
return midiEventSize;
|
|
}
|
|
|
|
// Left for API backward compatibility.
|
|
static inline size_t
|
|
cmidi2_convert_single_ump_to_midi1(uint8_t* dst, size_t maxBytes, cmidi2_ump* ump)
|
|
{
|
|
return cmidi2_convert_single_ump_to_timed_midi1(dst, maxBytes, ump, 0, NULL, NULL, 0);
|
|
}
|
|
|
|
#define IS_JR_TIMESTAMP(ump) \
|
|
(cmidi2_ump_get_message_type(ump) == CMIDI2_MESSAGE_TYPE_UTILITY \
|
|
&& cmidi2_ump_get_status_code(ump) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)
|
|
|
|
static enum cmidi2_midi_conversion_result
|
|
cmidi2_convert_ump_to_midi1(cmidi2_midi_conversion_context* context)
|
|
{
|
|
uint8_t* dst = (uint8_t*)context->midi1;
|
|
size_t sLen = context->ump_num_bytes;
|
|
size_t dLen = context->midi1_num_bytes;
|
|
size_t* sIdx = (size_t*)&context->ump_proceeded_bytes;
|
|
size_t* dIdx = (size_t*)&context->midi1_proceeded_bytes;
|
|
uint8_t sysex7_buffer[1024];
|
|
size_t sysex7_buffer_index = 0;
|
|
|
|
while (*sIdx < sLen)
|
|
{
|
|
int32_t deltaTimeInJRTimestamp = 0;
|
|
cmidi2_ump* ump;
|
|
do
|
|
{
|
|
ump = (cmidi2_ump*)((uint8_t*)context->ump + *sIdx);
|
|
if (IS_JR_TIMESTAMP(ump))
|
|
{
|
|
if (!context->skip_delta_time)
|
|
deltaTimeInJRTimestamp += cmidi2_ump_get_jr_timestamp_timestamp(ump);
|
|
}
|
|
else
|
|
break;
|
|
*sIdx += 4; // 4 = sizeof JR Timestamp message
|
|
} while (*sIdx < sLen);
|
|
|
|
if (*sIdx >= sLen)
|
|
break;
|
|
|
|
int32_t deltaTime
|
|
= cmidi2_internal_convert_jr_timestamp_to_timecode(deltaTimeInJRTimestamp, context);
|
|
|
|
int32_t len = cmidi2_convert_single_ump_to_timed_midi1(
|
|
dst + *dIdx, dLen - *dIdx, ump, deltaTime, context, sysex7_buffer, &sysex7_buffer_index);
|
|
*dIdx += len;
|
|
*sIdx += cmidi2_ump_get_num_bytes(*ump);
|
|
|
|
if (cmidi2_ump_get_message_type(ump) == CMIDI2_MESSAGE_TYPE_SYSEX7)
|
|
{
|
|
switch (cmidi2_ump_get_status_code(ump))
|
|
{
|
|
case CMIDI2_SYSEX_END:
|
|
case CMIDI2_SYSEX_IN_ONE_UMP:
|
|
if (!context->skip_delta_time)
|
|
cmidi2_internal_add_midi1_delta_time(dst + *dIdx, context, deltaTime);
|
|
dst[*dIdx] = 0xF0;
|
|
*dIdx += 1;
|
|
memcpy(dst + *dIdx, sysex7_buffer, sysex7_buffer_index);
|
|
*dIdx += sysex7_buffer_index;
|
|
sysex7_buffer_index = 0;
|
|
dst[*dIdx] = 0xF7;
|
|
*dIdx += 1;
|
|
break;
|
|
}
|
|
}
|
|
deltaTime = 0;
|
|
}
|
|
|
|
return sysex7_buffer_index > 0 ? CMIDI2_CONVERSION_RESULT_INCOMPLETE_SYSEX7
|
|
: CMIDI2_CONVERSION_RESULT_OK;
|
|
}
|
|
|
|
// UMP forge (like LV2 Atom Forge, but flat and simpler)
|
|
|
|
typedef struct cmidi2_ump_forge
|
|
{
|
|
cmidi2_ump* ump;
|
|
size_t capacity;
|
|
size_t offset;
|
|
} cmidi2_ump_forge;
|
|
|
|
static inline void
|
|
cmidi2_ump_forge_init(cmidi2_ump_forge* forge, cmidi2_ump* buffer, size_t capacityInBytes)
|
|
{
|
|
forge->ump = buffer;
|
|
forge->capacity = capacityInBytes;
|
|
forge->offset = 0;
|
|
}
|
|
|
|
static inline bool cmidi2_ump_forge_add_packet_32(cmidi2_ump_forge* forge, uint32_t ump)
|
|
{
|
|
int size = 4;
|
|
if (forge->offset + size > forge->capacity)
|
|
return false;
|
|
uint8_t* p = (uint8_t*)forge->ump + forge->offset;
|
|
cmidi2_ump_write32((cmidi2_ump*)p, ump);
|
|
forge->offset += size;
|
|
return true;
|
|
}
|
|
|
|
static inline bool cmidi2_ump_forge_add_packet_64(cmidi2_ump_forge* forge, uint64_t ump)
|
|
{
|
|
int size = 8;
|
|
if (forge->offset + size > forge->capacity)
|
|
return false;
|
|
uint8_t* p = (uint8_t*)forge->ump + forge->offset;
|
|
cmidi2_ump_write64((cmidi2_ump*)p, ump);
|
|
forge->offset += size;
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
cmidi2_ump_forge_add_packet_128(cmidi2_ump_forge* forge, uint64_t ump1, uint64_t ump2)
|
|
{
|
|
int size = 16;
|
|
if (forge->offset + size > forge->capacity)
|
|
return false;
|
|
uint8_t* p = ((uint8_t*)forge->ump) + forge->offset;
|
|
cmidi2_ump_write128((cmidi2_ump*)p, ump1, ump2);
|
|
forge->offset += size;
|
|
return true;
|
|
}
|
|
|
|
static inline bool cmidi2_ump_forge_add_single_packet(cmidi2_ump_forge* forge, cmidi2_ump* ump)
|
|
{
|
|
int size = cmidi2_ump_get_message_size_bytes(ump);
|
|
if (forge->offset + size > forge->capacity)
|
|
return false;
|
|
memcpy((uint8_t*)forge->ump + forge->offset, ump, size);
|
|
forge->offset += size;
|
|
return true;
|
|
}
|
|
|
|
static inline bool
|
|
cmidi2_ump_forge_add_packets(cmidi2_ump_forge* forge, cmidi2_ump* ump, int32_t size)
|
|
{
|
|
if (forge->offset + size > forge->capacity)
|
|
return false;
|
|
memcpy((uint8_t*)forge->ump + forge->offset, ump, size);
|
|
forge->offset += size;
|
|
return true;
|
|
}
|
|
|
|
// UMP sequence merger
|
|
|
|
static inline bool cmidi2_internal_ump_merge_sequence_write_delta_time(
|
|
int32_t timestamp1, int32_t timestamp2, int32_t* lastTimestamp, void* dst, int32_t* dIdx,
|
|
size_t dstSize)
|
|
{
|
|
int32_t deltaTime = (timestamp1 <= timestamp2 ? timestamp1 : timestamp2) - *lastTimestamp;
|
|
uint8_t jrTSSize = deltaTime / 0x10000 + (deltaTime % 0x10000 ? 1 : 0);
|
|
if (*dIdx + jrTSSize * 4 > (int64_t)dstSize)
|
|
return false;
|
|
for (int32_t dt = deltaTime; dt > 0; dt -= 0x10000)
|
|
{
|
|
cmidi2_ump_write32(
|
|
(cmidi2_ump*)((uint8_t*)dst + *dIdx),
|
|
cmidi2_ump_jr_timestamp_direct(0, deltaTime > 0xFFFF ? 0xFFFF : deltaTime));
|
|
*dIdx += 4;
|
|
}
|
|
*lastTimestamp += deltaTime;
|
|
return true;
|
|
}
|
|
|
|
static inline size_t cmidi2_ump_merge_sequences(
|
|
cmidi2_ump* dst, size_t dstCapacity, cmidi2_ump* seq1, size_t seq1Size, cmidi2_ump* seq2,
|
|
size_t seq2Size)
|
|
{
|
|
int32_t dIdx = 0, seq1Idx = 0, seq2Idx = 0;
|
|
int32_t timestamp1 = 0, timestamp2 = 0, lastTimestamp = 0;
|
|
while (true)
|
|
{
|
|
cmidi2_ump* s1 = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);
|
|
while (cmidi2_ump_get_message_type(s1) == CMIDI2_MESSAGE_TYPE_UTILITY
|
|
&& cmidi2_ump_get_status_code(s1) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)
|
|
{
|
|
timestamp1 += cmidi2_ump_get_jr_timestamp_timestamp(s1);
|
|
seq1Idx += 4;
|
|
if (seq1Idx >= (int64_t)seq1Size)
|
|
break;
|
|
s1 = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);
|
|
}
|
|
cmidi2_ump* s2 = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);
|
|
while (cmidi2_ump_get_message_type(s2) == CMIDI2_MESSAGE_TYPE_UTILITY
|
|
&& cmidi2_ump_get_status_code(s2) == CMIDI2_UTILITY_STATUS_JR_TIMESTAMP)
|
|
{
|
|
timestamp2 += cmidi2_ump_get_jr_timestamp_timestamp(s2);
|
|
seq2Idx += 4;
|
|
if (seq2Idx >= (int64_t)seq2Size)
|
|
break;
|
|
s2 = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);
|
|
}
|
|
if (seq1Idx >= (int64_t)seq1Size || seq2Idx >= (int64_t)seq2Size)
|
|
break;
|
|
if (!cmidi2_internal_ump_merge_sequence_write_delta_time(
|
|
timestamp1, timestamp2, &lastTimestamp, dst, &dIdx, dstCapacity))
|
|
break;
|
|
|
|
if (timestamp1 <= timestamp2)
|
|
{
|
|
cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);
|
|
uint8_t size = cmidi2_ump_get_message_size_bytes(sp);
|
|
if (size == 0)
|
|
return dIdx; // invalid bytes
|
|
memcpy((uint8_t*)dst + dIdx, sp, size);
|
|
seq1Idx += size;
|
|
dIdx += size;
|
|
}
|
|
else
|
|
{
|
|
cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);
|
|
uint8_t size = cmidi2_ump_get_message_size_bytes(sp);
|
|
if (size == 0)
|
|
return dIdx; // invalid bytes
|
|
memcpy((uint8_t*)dst + dIdx, sp, size);
|
|
seq2Idx += size;
|
|
dIdx += size;
|
|
}
|
|
}
|
|
if (!cmidi2_internal_ump_merge_sequence_write_delta_time(
|
|
timestamp1, timestamp2, &lastTimestamp, dst, &dIdx, dstCapacity))
|
|
return dIdx;
|
|
if (seq1Idx < (int64_t)seq1Size && dIdx + (int64_t)seq1Size - seq1Idx < (int64_t)dstCapacity)
|
|
{
|
|
cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq1 + seq1Idx);
|
|
memcpy((uint8_t*)dst + dIdx, sp, seq1Size - seq1Idx);
|
|
dIdx += seq1Size - seq1Idx;
|
|
}
|
|
if (seq2Idx < (int64_t)seq2Size && dIdx + (int64_t)seq2Size - seq2Idx < (int64_t)dstCapacity)
|
|
{
|
|
cmidi2_ump* sp = (cmidi2_ump*)((uint8_t*)seq2 + seq2Idx);
|
|
memcpy((uint8_t*)dst + dIdx, sp, seq2Size - seq2Idx);
|
|
dIdx += seq2Size - seq2Idx;
|
|
}
|
|
return dIdx;
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* CMIDI2_H_INCLUDED */
|