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)
|
@ -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)
|
@ -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
|
@ -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;
|
||||
}
|
Loading…
Reference in new issue