bluez5: implement BLE MIDI parser

This commit is contained in:
Pauli Virtanen 2022-11-01 23:34:12 +02:00 committed by Wim Taymans
parent 931e8da3ea
commit e3cf7f6d87
3 changed files with 237 additions and 0 deletions

View file

@ -32,6 +32,7 @@ bluez5_sources = [
'dbus-manager.c',
'dbus-monitor.c',
'midi-enum.c',
'midi-parser.c',
]
bluez5_data = ['bluez-hardware.conf']

View file

@ -0,0 +1,205 @@
/* BLE MIDI parser
*
* Copyright © 2022 Pauli Virtanen
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <spa/utils/defs.h>
#include "midi.h"
enum midi_event_class {
MIDI_BASIC,
MIDI_SYSEX,
MIDI_SYSCOMMON,
MIDI_REALTIME,
MIDI_ERROR
};
static enum midi_event_class midi_event_info(uint8_t status, unsigned int *size)
{
switch (status) {
case 0x80 ... 0x8f:
case 0x90 ... 0x9f:
case 0xa0 ... 0xaf:
case 0xb0 ... 0xbf:
case 0xe0 ... 0xef:
*size = 3;
return MIDI_BASIC;
case 0xc0 ... 0xcf:
case 0xd0 ... 0xdf:
*size = 2;
return MIDI_BASIC;
case 0xf0:
/* variable; count only status byte here */
*size = 1;
return MIDI_SYSEX;
case 0xf1:
case 0xf3:
*size = 2;
return MIDI_SYSCOMMON;
case 0xf2:
*size = 3;
return MIDI_SYSCOMMON;
case 0xf6:
case 0xf7:
*size = 1;
return MIDI_SYSCOMMON;
case 0xf8 ... 0xff:
*size = 1;
return MIDI_REALTIME;
case 0xf4:
case 0xf5:
default:
/* undefined MIDI status */
*size = 0;
return MIDI_ERROR;
}
}
static void timestamp_set_high(uint16_t *time, uint8_t byte)
{
*time = (byte & 0x3f) << 7;
}
static void timestamp_set_low(uint16_t *time, uint8_t byte)
{
if ((*time & 0x7f) > (byte & 0x7f))
*time += 0x80;
*time &= ~0x7f;
*time |= byte & 0x7f;
}
int spa_bt_midi_parser_parse(struct spa_bt_midi_parser *parser,
const uint8_t *src, size_t src_size, bool only_time,
void (*event)(void *user_data, uint16_t time, uint8_t *event, size_t event_size),
void *user_data)
{
const uint8_t *src_end = src + src_size;
uint8_t running_status = 0;
uint16_t time;
uint8_t byte;
#define NEXT() do { if (src == src_end) return -EINVAL; byte = *src++; } while (0)
#define PUT(byte) do { if (only_time) { parser->size++; break; } \
if (parser->size == sizeof(parser->buf)) return -ENOSPC; \
parser->buf[parser->size++] = (byte); } while (0)
/* Header */
NEXT();
if (!(byte & 0x80))
return -EINVAL;
timestamp_set_high(&time, byte);
while (src < src_end) {
NEXT();
if (!parser->sysex) {
uint8_t status = 0;
unsigned int event_size;
if (byte & 0x80) {
/* Timestamp */
timestamp_set_low(&time, byte);
NEXT();
/* Status? */
if (byte & 0x80) {
parser->size = 0;
PUT(byte);
status = byte;
}
}
if (status == 0) {
/* Running status */
parser->size = 0;
PUT(running_status);
PUT(byte);
status = running_status;
}
switch (midi_event_info(status, &event_size)) {
case MIDI_BASIC:
running_status = (event_size > 1) ? status : 0;
break;
case MIDI_REALTIME:
case MIDI_SYSCOMMON:
/* keep previous running status */
break;
case MIDI_SYSEX:
parser->sysex = true;
/* XXX: not fully clear if SYSEX can be running status, assume no */
running_status = 0;
continue;
default:
goto malformed;
}
/* Event data */
while (parser->size < event_size) {
NEXT();
if (byte & 0x80) {
/* BLE MIDI allows no interleaved events */
goto malformed;
}
PUT(byte);
}
event(user_data, time, parser->buf, parser->size);
} else {
if (byte & 0x80) {
/* Timestamp */
timestamp_set_low(&time, byte);
NEXT();
if (byte == 0xf7) {
/* Sysex end */
PUT(byte);
event(user_data, time, parser->buf, parser->size);
parser->sysex = false;
} else {
/* Interleaved realtime event */
unsigned int event_size;
if (midi_event_info(byte, &event_size) != MIDI_REALTIME)
goto malformed;
spa_assert(event_size == 1);
event(user_data, time, &byte, 1);
}
} else {
PUT(byte);
}
}
}
#undef NEXT
#undef PUT
return 0;
malformed:
/* Error (potentially recoverable) */
return -EINVAL;
}

View file

@ -42,4 +42,35 @@
#define BT_MIDI_SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700"
#define BT_MIDI_CHR_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3"
#define MIDI_BUF_SIZE 8192
struct spa_bt_midi_parser {
unsigned int size;
unsigned int sysex:1;
uint8_t buf[MIDI_BUF_SIZE];
};
static inline void spa_bt_midi_parser_init(struct spa_bt_midi_parser *parser)
{
parser->size = 0;
parser->sysex = 0;
}
static inline void spa_bt_midi_parser_dup(struct spa_bt_midi_parser *src, struct spa_bt_midi_parser *dst, bool only_time)
{
dst->size = src->size;
dst->sysex = src->sysex;
if (!only_time)
memcpy(dst->buf, src->buf, src->size);
}
/**
* Parse a single BLE MIDI data packet to normalized MIDI events.
*/
int spa_bt_midi_parser_parse(struct spa_bt_midi_parser *parser,
const uint8_t *src, size_t src_size,
bool only_time,
void (*event)(void *user_data, uint16_t time, uint8_t *event, size_t event_size),
void *user_data);
#endif