| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2015,2016 Felipe F. Tonello <eu@felipetonello.com> |
| * Copyright (C) 2016 ROLI Ltd. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| * |
| */ |
| |
| #include <glib.h> |
| |
| /* Avoid linkage problem on unit-tests */ |
| #ifndef MIDI_TEST |
| #include "src/backtrace.h" |
| #define MIDI_ASSERT(_expr) btd_assert(_expr) |
| #else |
| #define MIDI_ASSERT(_expr) g_assert(_expr) |
| #endif |
| #include "libmidi.h" |
| |
| inline static void buffer_append_byte(struct midi_buffer *buffer, |
| const uint8_t byte) |
| { |
| buffer->data[buffer->len++] = byte; |
| } |
| |
| inline static void buffer_append_data(struct midi_buffer *buffer, |
| const uint8_t *data, size_t size) |
| { |
| memcpy(buffer->data + buffer->len, data, size); |
| buffer->len += size; |
| } |
| |
| inline static uint8_t buffer_reverse_get(struct midi_buffer *buffer, size_t i) |
| { |
| MIDI_ASSERT(buffer->len > i); |
| return buffer->data[buffer->len - (i + 1)]; |
| } |
| |
| inline static void buffer_reverse_set(struct midi_buffer *buffer, size_t i, |
| const uint8_t byte) |
| { |
| MIDI_ASSERT(buffer->len > i); |
| buffer->data[buffer->len - (i + 1)] = byte; |
| } |
| |
| inline static size_t parser_get_available_size(struct midi_write_parser *parser) |
| { |
| return parser->stream_size - parser->midi_stream.len; |
| } |
| |
| inline static uint8_t sysex_get(const snd_seq_event_t *ev, size_t i) |
| { |
| MIDI_ASSERT(ev->data.ext.len > i); |
| return ((uint8_t*)ev->data.ext.ptr)[i]; |
| } |
| |
| inline static void append_timestamp_high_maybe(struct midi_write_parser *parser) |
| { |
| uint8_t timestamp_high = 0x80; |
| |
| if (midi_write_has_data(parser)) |
| return; |
| |
| parser->rtime = g_get_monotonic_time() / 1000; /* convert µs to ms */ |
| timestamp_high |= (parser->rtime & 0x1F80) >> 7; |
| /* set timestampHigh */ |
| buffer_append_byte(&parser->midi_stream, timestamp_high); |
| } |
| |
| inline static void append_timestamp_low(struct midi_write_parser *parser) |
| { |
| const uint8_t timestamp_low = 0x80 | (parser->rtime & 0x7F); |
| buffer_append_byte(&parser->midi_stream, timestamp_low); |
| } |
| |
| int midi_write_init(struct midi_write_parser *parser, size_t buffer_size) |
| { |
| int err; |
| |
| parser->rtime = 0; |
| parser->rstatus = SND_SEQ_EVENT_NONE; |
| parser->stream_size = buffer_size; |
| |
| parser->midi_stream.data = malloc(buffer_size); |
| if (!parser->midi_stream.data) |
| return -ENOMEM; |
| |
| parser->midi_stream.len = 0; |
| |
| err = snd_midi_event_new(buffer_size, &parser->midi_ev); |
| if (err < 0) |
| free(parser->midi_stream.data); |
| |
| return err; |
| } |
| |
| int midi_read_init(struct midi_read_parser *parser) |
| { |
| int err; |
| |
| parser->rstatus = 0; |
| parser->rtime = -1; |
| parser->timestamp = 0; |
| parser->timestamp_low = 0; |
| parser->timestamp_high = 0; |
| |
| parser->sysex_stream.data = malloc(MIDI_SYSEX_MAX_SIZE); |
| if (!parser->sysex_stream.data) |
| return -ENOMEM; |
| |
| parser->sysex_stream.len = 0; |
| |
| err = snd_midi_event_new(MIDI_MSG_MAX_SIZE, &parser->midi_ev); |
| if (err < 0) |
| free(parser->sysex_stream.data); |
| |
| return err; |
| } |
| |
| /* Algorithm: |
| 1) check initial timestampLow: |
| if used_sysex == 0, then tsLow = 1, else tsLow = 0 |
| 2) calculate sysex size of current packet: |
| 2a) first check special case: |
| if midi->out_length - 1 (tsHigh) - tsLow == |
| sysex_length - used_sysex |
| size is: min(midi->out_length - 1 - tsLow, |
| sysex_length - used_sysex - 1) |
| 2b) else size is: min(midi->out_length - 1 - tsLow, |
| sysex_length - used_sysex) |
| 3) check if packet contains F7: fill respective tsLow byte |
| */ |
| static void read_ev_sysex(struct midi_write_parser *parser, const snd_seq_event_t *ev, |
| midi_read_ev_cb write_cb, void *user_data) |
| { |
| unsigned int used_sysex = 0; |
| |
| /* We need at least 2 bytes (timestampLow + F0) */ |
| if (parser_get_available_size(parser) < 2) { |
| /* send current message and start new one */ |
| write_cb(parser, user_data); |
| midi_write_reset(parser); |
| append_timestamp_high_maybe(parser); |
| } |
| |
| /* timestampLow on initial F0 */ |
| if (sysex_get(ev, 0) == 0xF0) |
| append_timestamp_low(parser); |
| |
| do { |
| unsigned int size_of_sysex; |
| |
| append_timestamp_high_maybe(parser); |
| |
| size_of_sysex = MIN(parser_get_available_size(parser), |
| ev->data.ext.len - used_sysex); |
| |
| if (parser_get_available_size(parser) == ev->data.ext.len - used_sysex) |
| size_of_sysex--; |
| |
| buffer_append_data(&parser->midi_stream, |
| ev->data.ext.ptr + used_sysex, |
| size_of_sysex); |
| used_sysex += size_of_sysex; |
| |
| if (parser_get_available_size(parser) <= 1 && |
| buffer_reverse_get(&parser->midi_stream, 0) != 0xF7) { |
| write_cb(parser, user_data); |
| midi_write_reset(parser); |
| } |
| } while (used_sysex < ev->data.ext.len); |
| |
| /* check for F7 and update respective timestampLow byte */ |
| if (midi_write_has_data(parser) && |
| buffer_reverse_get(&parser->midi_stream, 0) == 0xF7) { |
| /* remove 0xF7 from buffer, append timestamp and add 0xF7 back again */ |
| parser->midi_stream.len--; |
| append_timestamp_low(parser); |
| buffer_append_byte(&parser->midi_stream, 0xF7); |
| } |
| } |
| |
| static void read_ev_others(struct midi_write_parser *parser, const snd_seq_event_t *ev, |
| midi_read_ev_cb write_cb, void *user_data) |
| { |
| int length; |
| |
| /* check for running status */ |
| if (parser->rstatus != ev->type) { |
| snd_midi_event_reset_decode(parser->midi_ev); |
| append_timestamp_low(parser); |
| } |
| |
| /* each midi message has timestampLow byte to follow */ |
| length = snd_midi_event_decode(parser->midi_ev, |
| parser->midi_stream.data + |
| parser->midi_stream.len, |
| parser_get_available_size(parser), |
| ev); |
| |
| if (length == -ENOMEM) { |
| /* remove previously added timestampLow */ |
| if (parser->rstatus != ev->type) |
| parser->midi_stream.len--; |
| write_cb(parser, user_data); |
| /* cleanup state for next packet */ |
| snd_midi_event_reset_decode(parser->midi_ev); |
| midi_write_reset(parser); |
| append_timestamp_high_maybe(parser); |
| append_timestamp_low(parser); |
| length = snd_midi_event_decode(parser->midi_ev, |
| parser->midi_stream.data + |
| parser->midi_stream.len, |
| parser_get_available_size(parser), |
| ev); |
| } |
| |
| if (length > 0) |
| parser->midi_stream.len += length; |
| } |
| |
| void midi_read_ev(struct midi_write_parser *parser, const snd_seq_event_t *ev, |
| midi_read_ev_cb write_cb, void *user_data) |
| { |
| MIDI_ASSERT(write_cb); |
| |
| append_timestamp_high_maybe(parser); |
| |
| /* SysEx is special case: |
| SysEx has two timestampLow bytes, before F0 and F7 |
| */ |
| if (ev->type == SND_SEQ_EVENT_SYSEX) |
| read_ev_sysex(parser, ev, write_cb, user_data); |
| else |
| read_ev_others(parser, ev, write_cb, user_data); |
| |
| parser->rstatus = ev->type; |
| |
| if (parser_get_available_size(parser) == 0) { |
| write_cb(parser, user_data); |
| midi_write_reset(parser); |
| } |
| } |
| |
| static void update_ev_timestamp(struct midi_read_parser *parser, |
| snd_seq_event_t *ev, uint16_t ts_low) |
| { |
| int delta_timestamp; |
| int delta_rtime; |
| int64_t rtime_current; |
| uint16_t timestamp; |
| |
| /* time_low overwflow results on time_high to increment by one */ |
| if (parser->timestamp_low > ts_low) |
| parser->timestamp_high++; |
| |
| timestamp = (parser->timestamp_high << 7) | parser->timestamp_low; |
| |
| rtime_current = g_get_monotonic_time() / 1000; /* convert µs to ms */ |
| delta_timestamp = timestamp - (int)parser->timestamp; |
| delta_rtime = rtime_current - parser->rtime; |
| |
| if (delta_rtime > MIDI_MAX_TIMESTAMP) |
| parser->rtime = rtime_current; |
| else { |
| |
| /* If delta_timestamp is way to big than delta_rtime, |
| this means that the device sent a message in the past, |
| so we have to compensate for this. */ |
| if (delta_timestamp > 7000 && delta_rtime < 1000) |
| delta_timestamp = 0; |
| |
| /* check if timestamp did overflow */ |
| if (delta_timestamp < 0) { |
| /* same timestamp in the past problem */ |
| if ((delta_timestamp + MIDI_MAX_TIMESTAMP) > 7000 && |
| delta_rtime < 1000) |
| delta_timestamp = 0; |
| else |
| delta_timestamp = delta_timestamp + MIDI_MAX_TIMESTAMP; |
| } |
| |
| parser->rtime += delta_timestamp; |
| } |
| |
| parser->timestamp += delta_timestamp; |
| if (parser->timestamp > MIDI_MAX_TIMESTAMP) |
| parser->timestamp %= MIDI_MAX_TIMESTAMP + 1; |
| |
| /* set event timestamp */ |
| /* TODO: update event timestamp here! */ |
| } |
| |
| static size_t handle_end_of_sysex(struct midi_read_parser *parser, |
| snd_seq_event_t *ev, |
| const uint8_t *data, |
| size_t sysex_length) |
| { |
| uint8_t time_low; |
| |
| /* At this time, timestampLow is copied as the last byte, |
| instead of 0xF7 */ |
| buffer_append_data(&parser->sysex_stream, data, sysex_length); |
| |
| time_low = buffer_reverse_get(&parser->sysex_stream, 0) & 0x7F; |
| |
| /* Remove timestamp byte */ |
| buffer_reverse_set(&parser->sysex_stream, 0, 0xF7); |
| |
| /* Update event */ |
| update_ev_timestamp(parser, ev, time_low); |
| snd_seq_ev_set_sysex(ev, parser->sysex_stream.len, |
| parser->sysex_stream.data); |
| |
| return sysex_length + 1; /* +1 because of timestampLow */ |
| } |
| |
| |
| |
| size_t midi_read_raw(struct midi_read_parser *parser, const uint8_t *data, |
| size_t size, snd_seq_event_t *ev /* OUT */) |
| { |
| size_t midi_size = 0; |
| size_t i = 0; |
| bool err = false; |
| |
| if (parser->timestamp_high == 0) |
| parser->timestamp_high = data[i++] & 0x3F; |
| |
| snd_midi_event_reset_encode(parser->midi_ev); |
| |
| /* timestamp byte */ |
| if (data[i] & 0x80) { |
| update_ev_timestamp(parser, ev, data[i] & 0x7F); |
| |
| /* check for wrong BLE-MIDI message size */ |
| if (++i == size) { |
| err = true; |
| goto _finish; |
| } |
| } |
| |
| /* cleanup sysex_stream if message is broken or is a new SysEx */ |
| if (data[i] >= 0x80 && data[i] != 0xF7 && parser->sysex_stream.len > 0) |
| parser->sysex_stream.len = 0; |
| |
| switch (data[i]) { |
| case 0xF8 ... 0XFF: |
| /* System Real-Time Messages */ |
| midi_size = 1; |
| break; |
| |
| /* System Common Messages */ |
| case 0xF0: /* SysEx Start */ { |
| uint8_t *pos; |
| |
| /* cleanup Running Status Message */ |
| parser->rstatus = 0; |
| |
| /* Avoid parsing if SysEx is contained in one BLE packet */ |
| if ((pos = memchr(data + i, 0xF7, size - i))) { |
| const size_t sysex_length = pos - (data + i); |
| midi_size = handle_end_of_sysex(parser, ev, data + i, |
| sysex_length); |
| } else { |
| buffer_append_data(&parser->sysex_stream, data + i, size - i); |
| err = true; /* Not an actual error, just incomplete message */ |
| midi_size = size - i; |
| } |
| |
| goto _finish; |
| } |
| |
| case 0xF1: |
| case 0xF3: |
| midi_size = 2; |
| break; |
| case 0xF2: |
| midi_size = 3; |
| break; |
| case 0xF4: |
| case 0xF5: /* Ignore */ |
| i++; |
| err = true; |
| goto _finish; |
| break; |
| case 0xF6: |
| midi_size = 1; |
| break; |
| case 0xF7: /* SysEx End */ |
| buffer_append_byte(&parser->sysex_stream, 0xF7); |
| snd_seq_ev_set_sysex(ev, parser->sysex_stream.len, |
| parser->sysex_stream.data); |
| |
| midi_size = 1; /* timestampLow was alredy processed */ |
| goto _finish; |
| |
| case 0x80 ... 0xEF: |
| /* |
| * Channel Voice Messages, Channel Mode Messages |
| * and Control Change Messages. |
| */ |
| parser->rstatus = data[i]; |
| midi_size = (data[i] >= 0xC0 && data[i] <= 0xDF) ? 2 : 3; |
| break; |
| |
| case 0x00 ... 0x7F: |
| |
| /* Check for SysEx messages */ |
| if (parser->sysex_stream.len > 0) { |
| uint8_t *pos; |
| |
| if ((pos = memchr(data + i, 0xF7, size - i))) { |
| const size_t sysex_length = pos - (data + i); |
| midi_size = handle_end_of_sysex(parser, ev, data + i, |
| sysex_length); |
| } else { |
| buffer_append_data(&parser->sysex_stream, data + i, size - i); |
| err = true; /* Not an actual error, just incomplete message */ |
| midi_size = size - i; |
| } |
| |
| goto _finish; |
| } |
| |
| /* Running State Message was not set */ |
| if (parser->rstatus == 0) { |
| midi_size = 1; |
| err = true; |
| goto _finish; |
| } |
| |
| snd_midi_event_encode_byte(parser->midi_ev, parser->rstatus, ev); |
| midi_size = (parser->rstatus >= 0xC0 && parser->rstatus <= 0xDF) ? 1 : 2; |
| break; |
| } |
| |
| if ((i + midi_size) > size) { |
| err = true; |
| goto _finish; |
| } |
| |
| snd_midi_event_encode(parser->midi_ev, data + i, midi_size, ev); |
| |
| _finish: |
| if (err) |
| ev->type = SND_SEQ_EVENT_NONE; |
| |
| return i + midi_size; |
| } |