Some commands, but they don't work
parent
d75f392284
commit
fb3f597a1b
|
@ -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)
|
9
base64.c
9
base64.c
|
@ -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)
|
||||
{
|
||||
|
|
4
base64.h
4
base64.h
|
@ -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);
|
||||
|
||||
|
|
10
board.cpp
10
board.cpp
|
@ -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()
|
||||
|
|
|
@ -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)
|
22
debugpins.h
22
debugpins.h
|
@ -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
|
44
main.cpp
44
main.cpp
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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.'
|
||||
)
|
||||
|
|
43
midix.cpp
43
midix.cpp
|
@ -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);
|
||||
|
|
15
midix.hpp
15
midix.hpp
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
69
sysex.hpp
69
sysex.hpp
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue