Some commands, but they don't work

trunk
alexis 2022-10-05 20:38:08 -06:00
parent d75f392284
commit fb3f597a1b
16 changed files with 687 additions and 83 deletions

20
avrstuff/pgmspace.h Normal file
View File

@ -0,0 +1,20 @@
// intellectual property is bullshit bgdc
#ifndef PGMSPACE_H
#define PGMSPACE_H
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#ifdef __AVR__
#else
#define __flash
#define PROGMEM
#define pgm_read_byte(x) (x)
#endif
#endif // !defined(PGMSPACE_H)

View File

@ -6,12 +6,13 @@
#include <stddef.h>
#include <inttypes.h>
// TODO make these progmem
static const uint8_t enc_lut[] =
#include <avrstuff/pgmspace.h>
static __flash const uint8_t enc_lut[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// ascii 0x20 through 0x7F
static const uint8_t dec_lut[] =
static __flash const uint8_t dec_lut[] =
{
// ! " # $ % & '
-1, -1, -1, -1, -1, -1, -1, -1,
@ -50,7 +51,7 @@ static const uint8_t dec_lut[] =
49, 50, 51, -1, -1, -1, -1, -1
};
#define DECODE(c) (((c) >= 0x20 && (c) <= 0x7F) ? dec_lut[(c) - 0x20] : (uint8_t)(-1))
#define DECODE(c) dec_lut[((c) - 0x20) & 0x3F]
static inline uint8_t divmod64(uint32_t * x)
{

View File

@ -18,6 +18,10 @@ size_t base64_encode(uint8_t * dest, uint8_t const * src, size_t len);
// (3/4) * rounded_up(len, 4) bytes (that is, enough bytes to encode src
// including possible padding bytes).
//
// This is a "dirty" decode -- embedded whitespace is not tolerated, and out-
// of-range characters result in corrupted results (though they will not cause
// any UB, out-of-bounds access, etc).
//
// Returns the total number of bytes used.
size_t base64_decode(uint8_t * dest, uint8_t const * src, size_t len);

View File

@ -1,4 +1,3 @@
#include "debugpins.h"
#include "board.hpp"
#include <avr/io.h>
#include <avr/fuse.h>
@ -29,7 +28,10 @@ void board_pins_init()
PORTE.PINCTRLUPD = 0xFF;
PORTF.PINCTRLUPD = 0xFF;
PORTG.PINCTRLUPD = 0xFF;
DEBUGPINS_INIT();
PIN_DRIVE_TO(PIN_DEBUG0, 0);
PIN_DRIVE_TO(PIN_DEBUG1, 0);
PIN_DRIVE_TO(PIN_DEBUG2, 0);
PIN_DRIVE_TO(PIN_DEBUG3, 0);
// Drive all the chip selects high
PIN_DRIVE_TO(PIN_MIXER_nCS, 1);
@ -56,19 +58,15 @@ void board_pins_init()
PORTMUX.SPIROUTEA = PORTMUX_SPI0_ALT2_gc;
// Set up the clocks
DEBUGPINS_START_INIT_CLOCK();
_PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA,
CLKCTRL_RUNSTDBY_bm |
CLKCTRL_CSUTHF_4K_gc | CLKCTRL_FRQRANGE_24M_gc
| CLKCTRL_ENABLE_bm);
while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_EXTS_bm));
DEBUGPINS_CLOCK_READY();
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_EXTCLK_gc);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0); // No prescaler
DEBUGPINS_RUN();
}
void board_peripherals_init()

192
cmd.py Executable file
View File

@ -0,0 +1,192 @@
#!/usr/bin/env python3
# intellectual property is bullshit bgdc
PORT = "/dev/tty.usbserial-2120"
BAUD = 32500
import base64
import binascii
import serial
import sys
class DummySerial:
def __init__(self, *args, **kwargs):
self.fifo = []
self.timeout = kwargs.get("timeout")
pass
def write(self, b):
print(binascii.hexlify(b).decode("ascii"))
for i in b:
self.fifo.append(i)
def read(self, n):
data = bytes(self.fifo[:n])
del self.fifo[:n]
return data
class hex32arg:
@classmethod
def encode(cls, cmd):
return b"%08X" % int(cmd[0], 16)
@classmethod
def decode(cls, cmd):
return (b"0x" + cmd).decode("ascii")
class dataarg:
@classmethod
def encode(cls, cmd):
data = binascii.unhexlify(cmd[0])
cksum = ~sum(data) & 0x7F
return bytes([cksum]) + base64.b64encode(data)
@classmethod
def decode(cls, cmd):
cksum_received = cmd[0]
data = base64.b64decode(cmd[1:])
cksum_actual = ~sum(data) & 0x7F
if cksum_received != cksum_actual:
print("warning: checksum fail")
return binascii.hexlify(data).decode("ascii")
SYSEX = [
(["storage", "response"], [0x01, 0x01], dataarg),
(["storage", "read"], [0x01, 0x02], hex32arg),
(["storage", "openwr"], [0x01, 0x03], hex32arg),
(["storage", "wrdata"], [0x01, 0x04], dataarg),
(["storage", "commit"], [0x01, 0x05], None),
(["storage", "cancel"], [0x01, 0x06], None),
(["status", "ack"], [0x7F, 0x01], None),
(["status", "fmt_err"], [0x7F, 0x02], None),
(["status", "unknown"], [0x7F, 0x03], None),
(["status", "busy"], [0x7F, 0x04], None),
(["status", "ioerr"], [0x7F, 0x05], None),
(["status", "cksum"], [0x7F, 0x06], None),
]
HLCOMMANDS = [
]
class Receiver:
def __init__(self, port):
self.s = port
def rx_one(self, timeout=0.1):
"""Receive a single MIDI packet. Returns it as bytes. On a timeout,
whatever was received is returned."""
timeout, self.s.timeout = self.s.timeout, timeout
buf = b""
while not buf or not (buf[0] & 0x80):
byte = self.s.read(1)
if not byte:
break
buf += byte
# Terminating conditions depend on the packet type. The following
# initial bytes specify a three-byte packet:
# 8x 9x Ax Bx Cx Ex
#
# The following specifies a two-byte packet:
# Dx
#
# F0 specifies a sysex packet, terminated by F7
#
# Other Fx packets are currently returned immediately after 1 byte.
if not buf:
term = lambda b: True
elif (buf[0] & 0xF0) in (0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xE0):
term = lambda b: len(b) == 3
elif (buf[0] & 0xF0) == 0xD0:
term = lambda b: len(b) == 2
elif buf[0] == 0xF0:
term = lambda b: b[-1] == 0xF7
else:
term = lambda b: True
while not term(buf):
byte = self.s.read(1)
if not byte:
break
buf += byte
timeout, self.s.timeout = self.s.timeout, timeout
return buf
def rx_until_status(self, timeout=0.1):
"""Receive packets, yielding them, until either a "status" response is
received or the interface times out."""
while True:
packet = self.rx_one(timeout=timeout)
is_status = packet and packet.startswith(b"\xF0\x7D\x7F")
yield packet
if is_status or not packet:
break
def main(argv):
s = serial.Serial(PORT, BAUD, timeout=0.1)
#s = DummySerial()
rx = Receiver(s)
for i in argv[1:]:
send(s, i)
for resp in rx.rx_until_status():
print(parse_response(resp))
def send(s, cmd):
if cmd.startswith(":"):
# sysex - split on :
cmdargs = cmd[1:].split(":")
for key, data, converter in SYSEX:
if cmdargs[:len(key)] == key:
if converter is not None:
data += converter.encode(cmdargs[len(key):])
s.write(b"\xF0\x7D" + bytes(data) + b"\xF7")
break
else:
print("error: cannot find command:", cmd)
elif cmd.startswith("!"):
cmdargs = cmd[1:].split(":")
for name, func in HLCOMMANDS:
if name == cmdargs[0]:
func(cmdargs[1:])
break
else:
print("error: cannot find command:", cmdargs[0])
def parse_response(r):
while r and ((r[0] & 0x80) == 0x00):
r.pop(0)
if not r:
return None
if r.startswith(b"\xF0\x7D"):
end = r.find(b"\xF7")
if end < 0:
return None
r = r[:end]
for key, data, converter in SYSEX:
if r[2:].startswith(bytes(data)):
arg = r[2 + len(key):]
if converter is not None:
arg_decoded = ":" + converter.decode(arg)
else:
arg_decoded = ""
return ":" + ":".join(key) + arg_decoded
if __name__ == "__main__":
main(sys.argv)

View File

@ -1,22 +0,0 @@
#pragma once
#include "boarddefs.h"
#define DEBUG_PINS_ARE_STARTUP_STATE 1
#if DEBUG_PINS_ARE_STARTUP_STATE
# define DEBUGPINS_INIT() do { \
PIN_DRIVE_TO(PIN_DEBUG0, 0); \
PIN_DRIVE_TO(PIN_DEBUG1, 0); \
PIN_DRIVE_TO(PIN_DEBUG2, 0); \
PIN_DRIVE_TO(PIN_DEBUG3, 0); \
} while (0)
# define DEBUGPINS_START_INIT_CLOCK() PIN_SET(PIN_DEBUG0)
# define DEBUGPINS_CLOCK_READY() PIN_SET(PIN_DEBUG1)
# define DEBUGPINS_RUN() PIN_SET(PIN_DEBUG3)
#endif

View File

@ -1,16 +1,16 @@
#include "board.hpp"
#include "midix.hpp"
#include "sysex.hpp"
#include <avrstuff/serial_avrdx.hpp>
#include <avrstuff/bufserial.hpp>
#include <avrstuff/spi_avrdx.hpp>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <assert.h>
#include <string.h>
static serial_avrdx<2, 8> usbmidi_usart;
static bufserial<64, 64> usbmidi_buf(usbmidi_usart);
static ringbuf<struct midi_message, 4> midi_q;
static serial_avrdx<2, 8> usbmidi_usart;
static midix midi(usbmidi_usart, midi_q);
// Plan: main pulls from an event queue. Events are generated from the MIDI
@ -27,12 +27,13 @@ int main(void)
usbmidi_usart.set_baud(31250);
usbmidi_usart.start(true, true, 'N', false);
usbmidi_buf.init();
sei();
spi_avrdx<0> hspi;
hspi.init(spi_avrdx<0>::clockdiv::div2, true, 0);
usbmidi_usart.rxcie(true);
for (;;)
{
char info[64];
@ -43,43 +44,16 @@ int main(void)
uint8_t cmd = MIDI_CMD(m.command);
uint8_t chan = MIDI_CMD_CHAN(m.command);
switch (cmd)
{
case MIDI_ICMD_NOP: sprintf_P(info, PSTR("I NOP ")); break;
case MIDI_ICMD_SYSEX: sprintf_P(info, PSTR("I SYSEX ")); break;
case MIDI_ICMD_SYSEX_OVERFLOW: sprintf_P(info, PSTR("I SYSEX_OVERFLOW ")); break;
case MIDI_CMD_NOTE_OFF: sprintf_P(info, PSTR("NOTE_OFF [%X]"), chan); break;
case MIDI_CMD_NOTE_ON: sprintf_P(info, PSTR("NOTE_ON [%X]"), chan); break;
case MIDI_CMD_AFTERTOUCH: sprintf_P(info, PSTR("AFTERTOUCH [%X]"), chan); break;
case MIDI_CMD_CC: sprintf_P(info, PSTR("CC [%X]"), chan); break;
case MIDI_CMD_PATCH: sprintf_P(info, PSTR("PATCH [%X]"), chan); break;
case MIDI_CMD_PRESSURE: sprintf_P(info, PSTR("PRESSURE [%X]"), chan); break;
case MIDI_CMD_PITCHBEND: sprintf_P(info, PSTR("PITCHBEND [%X]"), chan); break;
case MIDI_CMD_START_SYSEX: sprintf_P(info, PSTR("START_SYSEX ")); break;
case MIDI_CMD_TUNE_REQ: sprintf_P(info, PSTR("TUNE_REQ ")); break;
case MIDI_CMD_END_SYSEX: sprintf_P(info, PSTR("END_SYSEX ")); break;
case MIDI_CMD_SYSRESET: sprintf_P(info, PSTR("SYSRESET ")); break;
default: sprintf_P(info, PSTR("WTF [%02x]"), m.command); break;
}
usbmidi_buf.send_str(info);
if (cmd == MIDI_ICMD_SYSEX)
{
usbmidi_buf.send_str((char const *) m.content);
hspi.transfer_block(m.content, m.content, strlen((char const *) m.content));
hspi.transfer_block(m.content, NULL, strlen((char const *) m.content));
PIN_SET(PIN_DEBUG0);
sysex_handle(&m);
m.source->sysex_release_buffer(m.content);
PIN_CLR(PIN_DEBUG0);
}
else
{
for (int i = 0; i < midi_num_args(cmd); i++)
{
sprintf_P(info, PSTR(" %02x"), m.arguments[i]);
usbmidi_buf.send_str(info);
}
}
usbmidi_buf.send_str("\r\n");
}
}
@ -91,7 +65,7 @@ void operator delete(void * p, unsigned int sz)
ISR(USART2_DRE_vect)
{
usbmidi_buf.dre();
midi.dre();
}
ISR(USART2_RXC_vect)

View File

@ -6,11 +6,13 @@ dependencies = []
local_headers = ['.', 'etl/include']
sources = [
'main.cpp',
'board.cpp',
'midix.cpp',
'ascii85.c',
'base64.c',
'board.cpp',
'main.cpp',
'midix.cpp',
'stor.cpp',
'sysex.cpp',
]
_includes = local_headers

View File

@ -16,7 +16,7 @@ objdump = arch + '-objdump'
size = arch + '-size'
[built-in options]
c_args = ['-mrelax']
cpp_args = ['-mrelax']
c_link_args = ['-mrelax']
cpp_link_args = ['-mrelax']
c_args = ['-mrelax', '-ffunction-sections', '-fdata-sections']
cpp_args = ['-mrelax', '-ffunction-sections', '-fdata-sections']
c_link_args = ['-mrelax', '-Wl,--gc-sections']
cpp_link_args = ['-mrelax', '-Wl,--gc-sections']

View File

@ -9,6 +9,6 @@ option(
option(
'programmer',
type: 'array',
value: ['/dev/tty.usbserial-1110'],
value: ['/dev/tty.usbserial-2110'],
description: 'AVR Programmer to flash the MCU with.'
)

View File

@ -2,6 +2,13 @@
#include "midix.hpp"
#ifdef __AVR__
#include "board.hpp"
#else
#define PIN_SET(...)
#define PIN_CLR(...)
#endif
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
@ -9,6 +16,14 @@
void midix::rxc()
{
int c = m_serport.recv();
PIN_SET(PIN_DEBUG2);
PIN_CLR(PIN_DEBUG2);
if (c >= 0)
{
PIN_SET(PIN_DEBUG1);
PIN_CLR(PIN_DEBUG1);
}
if (c < 0)
{
@ -71,6 +86,34 @@ void midix::rxc()
}
}
void midix::dre()
{
uint8_t c;
bool has_c = m_outbuf.pop(c);
if (has_c)
m_serport.send(c);
else
m_serport.dreie(false);
}
void midix::send(uint8_t const * msg)
{
uint8_t b;
while ((b = *msg++))
{
while (!m_outbuf.push(b));
m_serport.dreie(true);
}
}
void midix::send_sysex(uint8_t const * msg)
{
send((uint8_t const *) "\xF0");
send(msg);
send((uint8_t const *) "\xF7");
}
void midix::sysex_release_buffer(uint8_t * buf)
{
assert(!m_sysex_buf_avail);

View File

@ -14,6 +14,9 @@
#include "midi.hpp"
#define MIDIX_SYSEX_LEN 64
#define MIDIX_SYSEX_OVERHEAD 2
// Must at least equal LEN + OVERHEAD, should be a power of 2
#define MIDIX_SYSEX_OUT_LEN 128
typedef iringbuf<struct midi_message> midi_iqueue;
@ -36,6 +39,16 @@ public:
// Receive Complete interrupt. Call within the serial port's RXC ISR.
void rxc();
// Data Register Empty interrupt. Call within the port's DRE ISR.
void dre();
// Transmit a raw message. Message is NUL terminated.
void send(uint8_t const * msg);
// Transmit a sysex message, adding the correct guard bytes. Message
// is NUL terminated.
void send_sysex(uint8_t const * msg);
// Release the sysex buffer. When a message is enqueued containing
// sysex content, it contains a reference to this midix's internal
// sysex buffer. This can only be used once, so it must be released
@ -64,6 +77,8 @@ private:
size_t m_message_pos;
bool m_receiving_sysex;
midi_iqueue & m_queue;
ringbuf<uint8_t, 128> m_outbuf;
};
#endif // !defined(MIDIX_HPP)

38
stor.cpp Normal file
View File

@ -0,0 +1,38 @@
// intellectual property is bullshit bgdc
#include "stor.hpp"
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <util/atomic.h>
uint8_t stor_buffer[STOR_SIZE];
size_t stor_cursor;
uint32_t stor_block_memo;
volatile bool stor_locked;
bool stor_try_lock()
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
bool was_locked = stor_locked;
stor_locked = true;
return !was_locked;
}
}
void stor_unlock()
{
stor_locked = false;
}
bool stor_read(size_t block)
{
return true;
}
bool stor_write(size_t block)
{
return true;
}

36
stor.hpp Normal file
View File

@ -0,0 +1,36 @@
// intellectual property is bullshit bgdc
// Storage interface. In the Poly-1, there is a single storage buffer, and it
// is locked when needed. This code manages locking and unlocking it and also
// moving data in and out of it.
#ifndef STOR_HPP
#define STOR_HPP
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#define STOR_SIZE 512
// Buffer accessed by stor functions
extern uint8_t stor_buffer[STOR_SIZE];
// Cursor for user use. Cleared at lock
extern size_t stor_cursor;
// Block number memo for user user.
extern uint32_t stor_block_memo;
extern volatile bool stor_locked;
// Try to lock the buffer. Returns whether the lock was successfully acquired.
bool stor_try_lock();
// Unlock the buffer.
void stor_unlock();
// Read from raw storage into the buffer. Returns false on failure.
bool stor_read(size_t block);
// Write to raw storage from the buffer. Returns false on failure.
bool stor_write(size_t block);
#endif // !defined(STOR_HPP)

250
sysex.cpp Normal file
View File

@ -0,0 +1,250 @@
// intellectual property is bullshit bgdc
#include "sysex.hpp"
#include "midix.hpp"
#include "stor.hpp"
extern "C" {
#include "base64.h"
}
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <string.h>
// Send a status code in response to a message
static void _send_status(struct midi_message const * msg, uint8_t status);
// Handle Storage group commands
static void _handle_storage(struct midi_message const * msg, size_t len);
// Handle Status group commands
static void _handle_status(struct midi_message const * msg, size_t len);
// Validate that the message's length is at least n. Returns false if not.
static bool _check_len(struct midi_message const * msg, size_t len, size_t n);
// Parse 8 hex characters and return the integer. Undefined results if invalid.
static uint32_t _parse_8hex(uint8_t const * s);
// Send a block of data, up to 32 bytes, as a SYSEX_STORAGE_RESPONSE
static void _send_storage_response(
struct midi_message const * msg, uint8_t const * data, uint8_t n
);
// Read in some data from a message as base64. Returns the number of bytes
// read. On checksum fail, returns SIZE_MAX.
static size_t _convert_message_data(uint8_t * dest, size_t dest_len,
struct midi_message const * msg, size_t msg_len
);
void sysex_handle(struct midi_message const * msg)
{
size_t len = strlen((char const *) msg->content);
if (msg->content[0] != MIDI_MANUF_ID)
// Not for us - return
return;
if (!_check_len(msg, len, 2))
return;
switch (msg->content[1])
{
case MIDI_SYSEX_STORAGE_GRP:
_handle_storage(msg, len);
break;
case MIDI_SYSEX_STATUS_GRP:
_handle_status(msg, len);
break;
default:
_send_status(msg, MIDI_SYSEX_STATUS_UNKNOWN);
return;
}
}
static void _send_status(struct midi_message const * msg, uint8_t status)
{
uint8_t message[] = {MIDI_MANUF_ID, MIDI_SYSEX_STATUS_GRP, status, 0};
msg->source->send_sysex(message);
}
static void _send_storage_response(
struct midi_message const * msg, uint8_t const * data, uint8_t n
)
{
uint8_t message[5 + 44] = {MIDI_MANUF_ID, MIDI_SYSEX_STORAGE_GRP,
MIDI_SYSEX_STORAGE_RESPONSE, 0};
// checksum
for (uint8_t i = 0; i < n; i++)
message[3] += data[n];
message[3] = (message[3] & 0x7F) ^ 0x7F;
base64_encode(&message[4], data, n);
msg->source->send_sysex(message);
}
static void _handle_storage(struct midi_message const * msg, size_t len)
{
if (!_check_len(msg, len, 3))
return;
switch (msg->content[3])
{
case MIDI_SYSEX_STORAGE_READ:
if (!_check_len(msg, len, 11))
return;
if (!stor_try_lock())
{
_send_status(msg, MIDI_SYSEX_STATUS_BUSY);
return;
}
if (!stor_read(_parse_8hex(&msg->content[4])))
{
_send_status(msg, MIDI_SYSEX_STATUS_IOERR);
stor_unlock();
return;
}
for (size_t curs = 0; curs < STOR_SIZE; curs += 32)
{
_send_storage_response(msg, &stor_buffer[curs], 32);
}
stor_unlock();
_send_status(msg, MIDI_SYSEX_STATUS_ACK);
break;
case MIDI_SYSEX_STORAGE_OPENWR:
if (!_check_len(msg, len, 11))
return;
if (stor_try_lock())
{
_send_status(msg, MIDI_SYSEX_STATUS_ACK);
stor_cursor = 0;
stor_block_memo = _parse_8hex(&msg->content[4]);
}
else
_send_status(msg, MIDI_SYSEX_STATUS_BUSY);
break;
case MIDI_SYSEX_STORAGE_WRDATA: {
size_t n = _convert_message_data(
&stor_buffer[stor_cursor],
512 - stor_cursor,
msg, len
);
if (n == SIZE_MAX)
_send_status(msg, MIDI_SYSEX_STATUS_CKSUM);
else
{
stor_cursor += n;
_send_status(msg, MIDI_SYSEX_STATUS_ACK);
}
break;
}
case MIDI_SYSEX_STORAGE_COMMIT:
if (!_check_len(msg, len, 11))
return;
if (stor_write(_parse_8hex(&msg->content[4])))
_send_status(msg, MIDI_SYSEX_STATUS_ACK);
else
_send_status(msg, MIDI_SYSEX_STATUS_IOERR);
stor_unlock();
break;
case MIDI_SYSEX_STORAGE_CANCEL:
stor_unlock();
_send_status(msg, MIDI_SYSEX_STATUS_ACK);
break;
default:
_send_status(msg, MIDI_SYSEX_STATUS_UNKNOWN);
break;
}
}
static void _handle_status(struct midi_message const * msg, size_t len)
{
if (!_check_len(msg, len, 3))
return;
switch (msg->content[2])
{
case MIDI_SYSEX_STATUS_ACK:
case MIDI_SYSEX_STATUS_FMT_ERR:
case MIDI_SYSEX_STATUS_UNKNOWN:
// Do not respond to recognized status messages.
break;
default:
_send_status(msg, MIDI_SYSEX_STATUS_UNKNOWN);
break;
}
}
static bool _check_len(struct midi_message const * msg, size_t len, size_t n)
{
if (len < n)
{
_send_status(msg, MIDI_SYSEX_STATUS_FMT_ERR);
return false;
}
else
return true;
}
static uint32_t _parse_8hex(uint8_t const * s)
{
uint32_t x = 0;
for (uint8_t i = 0; i < 8; i++)
{
char c = s[i];
x <<= 4;
if (c >= '0' && c <= '9')
x |= c - '0';
else if (c >= 'A' && c <= 'F')
x |= c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
x |= c - 'a' + 10;
}
return x;
}
static size_t _convert_message_data(uint8_t * dest, size_t dest_len,
struct midi_message const * msg, size_t msg_len
)
{
// Compute the length of base64 data
size_t src_len_base64 = msg_len - 4;
// Compute the destination length required to hold it
size_t len_needed = (4 * src_len_base64 + 2) / 3;
if (len_needed < dest_len)
return -1;
size_t n = base64_decode(dest, &msg->content[4], dest_len);
uint8_t checksum = 0;
for (size_t i = 0; i < n; i++)
checksum += dest[i];
checksum &= 0x7F;
checksum ^= 0x7F;
if (checksum == msg->content[3])
return n;
else
return -1;
}

View File

@ -7,39 +7,92 @@
#include <stddef.h>
#include <inttypes.h>
#include "midi.hpp"
// All sysex messages start with manuf id 7D (prototyping, test, private use,
// and experimentation).
//
//
// 7D 00 - front panel controls
// 7D 01 - storage commands
// 7D 02 - front panel controls
// 7D 7F - status codes
//
//
// -- Storage commands --
//
// Storage commands interface with the onboard storage (SD card). Large
// arguments are generally encoded using Ascii85.
// arguments are generally encoded using base64, while smaller arguments are
// generally ascii hex.
//
// 7D 01 01 ... - data response message, Ascii85
// 7D 01 02 A A A A A - request SD block AAA... (ascii85, data sent via 7D 01 01)
// 7D 01 03 A A A A A ... - write SD block AAA... (data sent as Ascii85)
// If data is not exactly 512 bytes long, nothing
// is written and an error SYSEX_FMT is sent.
// 7D 01 01 cksum ... - data response message, base64
// 7D 01 02 AAAAAAAA - request SD block AAA... (hex, data sent via 7D 01 01)
// 7D 01 03 AAAAAAAA - open SD block AAA... for write (hex). If a
// write is ongoing, will return STATUS_BUSY.
// 7D 01 04 cksum ... - append data to the write buffer.
// 7D 01 05 - commit the in-progress write
// 7D 01 06 - cancel the in-progress write
//
// -- Status codes --
// 7D 7F 01 - general ACK
// 7D 7F 02 - message format error
//
//
// == Writing data to storage ==
//
// To write a sector to the storage card, the sector must be opened using
// MIDI_SYSEX_STORAGE_OPENWR. The argument is a 32-bit (hex encoded) sector
// number, in 512-byte sectors. Following this, MIDI_SYSEX_STORAGE_WRDATA is
// sent repeatedly to append data to the write buffer. This data is encoded
// using base64, and up to (MIDIX_SYSEX_LEN - 8) base64 characters representing
// 42 bytes of data (assuming MIDIX_SYSEX_LEN is 64) may be sent at once. If
// more than 512 bytes are sent, the extras are dropped; if fewer than 512
// bytes are sent, the sector is padded with undefined data. Once the write
// buffer is full, commit it with MIDI_SYSEX_STORAGE_COMMIT or cancel the
// write with MIDI_SYSEX_STORAGE_CANCEL. TODO: add a command to query this
// length limit
//
// No matter how many midi ports exist on the system, there is only one storage
// buffer, so you must expect to receive MIDI_SYSEX_STATUS_BUSY in response to
// OPENWR. In this case, poll with the same message to await completion. A user-
// confirmed hard cancel may be performed by sending MIDI_SYSEX_STORAGE_CANCEL
// (this can cancel the buffer regardless of which interface holds it).
//
// == Reading data from storage ==
//
// To read a sector from the storage card, send the sector number in the same
// format as for a write, using MIDI_SYSEX_STORAGE_READ. The data will be sent
// back in a series of MIDI_SYSEX_STORAGE_RESPONSE packets, followed by a
// MIDI_SYSEX_STATUS_ACK to acknowledge completion, or a MIDI_SYSEX_STATUS_IOERR
// at any point to terminate the process and report an error.
//
// == Storage checksums ==
//
// Both MIDI_SYSEX_STORAGE_RESPONSE and MIDI_SYSEX_STORAGE_WRDATA contain a
// checksum byte. This byte is simply the bit-inverted lower 7 bits of the sum
// of all data bytes in the message, prior to encoding as base64. If you send
// data and it is received with a bad checksum, a MIDI_SYSEX_STATUS_CKSUM will
// be sent back to you; you must cancel the write operation and retry it.
// If you receive a bad checksum, your recourse is to request it again.
#define MIDI_MANUF_ID 0x7D
#define MIDI_SYSEX_STORAGE_GRP 0x01
#define MIDI_SYSEX_STORAGE_RESPONSE 0x01
#define MIDI_SYSEX_STORAGE_READ 0x02
#define MIDI_SYSEX_STORAGE_WRITE 0x03
#define MIDI_SYSEX_STORAGE_OPENWR 0x03
#define MIDI_SYSEX_STORAGE_WRDATA 0x04
#define MIDI_SYSEX_STORAGE_COMMIT 0x05
#define MIDI_SYSEX_STORAGE_CANCEL 0x06
#define MIDI_SYSEX_STATUS_GRP 0x7F
#define MIDI_SYSEX_STATUS_ACK 0x01
#define MIDI_SYSEX_STATUS_FMT_ERR 0x02
#define MIDI_SYSEX_STATUS_UNKNOWN 0x03
#define MIDI_SYSEX_STATUS_BUSY 0x04
#define MIDI_SYSEX_STATUS_IOERR 0x05
#define MIDI_SYSEX_STATUS_CKSUM 0x06
// Handle a sysex message
void sysex_handle(struct midi_message const * msg);
#endif // !defined(SYSEX_HPP)