#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "abuf.h"
#include "defs.h"
#include "dev.h"
#include "file.h"
#include "midi.h"
#include "miofile.h"
#include "sysex.h"
#include "utils.h"
int port_open(struct port *);
void port_imsg(void *, unsigned char *, int);
void port_omsg(void *, unsigned char *, int);
void port_fill(void *, int);
void port_exit(void *);
struct midiops port_midiops = {
port_imsg,
port_omsg,
port_fill,
port_exit
};
#define MIDI_NEP 32
struct midi midi_ep[MIDI_NEP];
struct port *port_list = NULL;
unsigned int midi_portnum = 0;
struct midithru {
unsigned int txmask, rxmask;
#define MIDITHRU_NMAX 32
} midithru[MIDITHRU_NMAX];
const unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 };
const unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };
size_t
midiev_fmt(char *buf, size_t size, unsigned char *ev, size_t len)
{
const char *sep = "";
char *end = buf + size;
char *p = buf;
int i;
for (i = 0; i < len; i++) {
if (i == 1)
sep = " ";
p += snprintf(p, p < end ? end - p : 0, "%s%02x", sep, ev[i]);
}
return p - buf;
}
void
midi_init(void)
{
}
void
midi_done(void)
{
}
struct midi *
midi_new(struct midiops *ops, void *arg, int mode)
{
int i;
struct midi *ep;
for (i = 0, ep = midi_ep;; i++, ep++) {
if (i == MIDI_NEP)
return NULL;
if (ep->ops == NULL)
break;
}
ep->ops = ops;
ep->arg = arg;
ep->used = 0;
ep->len = 0;
ep->idx = 0;
ep->st = 0;
ep->last_st = 0;
ep->txmask = 0;
ep->num = i;
ep->self = 1 << i;
ep->tickets = 0;
ep->mode = mode;
if (ep->mode & MODE_MIDIIN)
abuf_init(&ep->obuf, MIDI_BUFSZ);
midi_tickets(ep);
return ep;
}
void
midi_del(struct midi *ep)
{
int i;
struct midi *peer;
ep->txmask = 0;
for (i = 0; i < MIDI_NEP; i++) {
peer = midi_ep + i;
if (peer->txmask & ep->self) {
peer->txmask &= ~ep->self;
midi_tickets(peer);
}
}
for (i = 0; i < MIDITHRU_NMAX; i++) {
midithru[i].txmask &= ~ep->self;
midithru[i].rxmask &= ~ep->self;
}
ep->ops = NULL;
if (ep->mode & MODE_MIDIIN) {
abuf_done(&ep->obuf);
}
}
void
midi_link(struct midi *ep, struct midi *peer)
{
if (ep->mode & MODE_MIDIOUT) {
ep->txmask |= peer->self;
midi_tickets(ep);
}
if (ep->mode & MODE_MIDIIN) {
#ifdef DEBUG
if (ep->obuf.used > 0) {
logx(0, "midi%u: linked with non-empty buffer", ep->num);
panic();
}
#endif
peer->txmask |= ep->self;
}
}
unsigned int
midi_rxmask(struct midi *ep)
{
int i, rxmask;
for (rxmask = 0, i = 0; i < MIDI_NEP; i++) {
if ((midi_ep[i].txmask & ep->self) == 0)
continue;
rxmask |= midi_ep[i].self;
}
return rxmask;
}
void
midi_tag(struct midi *ep, unsigned int tag)
{
struct midi *peer;
struct midithru *t = midithru + tag;
int i;
if (ep->mode & MODE_MIDIOUT) {
ep->txmask |= t->txmask;
midi_tickets(ep);
}
if (ep->mode & MODE_MIDIIN) {
#ifdef DEBUG
if (ep->obuf.used > 0) {
logx(0, "midi%u: tagged with non-empty buffer", ep->num);
panic();
}
#endif
for (i = 0; i < MIDI_NEP; i++) {
if (!(t->rxmask & (1 << i)))
continue;
peer = midi_ep + i;
peer->txmask |= ep->self;
}
}
if (ep->mode & MODE_MIDIOUT)
t->rxmask |= ep->self;
if (ep->mode & MODE_MIDIIN)
t->txmask |= ep->self;
}
unsigned int
midi_tags(struct midi *ep)
{
int i;
struct midithru *t;
unsigned int tags;
tags = 0;
for (i = 0; i < MIDITHRU_NMAX; i++) {
t = midithru + i;
if ((t->txmask | t->rxmask) & ep->self)
tags |= 1 << i;
}
return tags;
}
void
midi_send(struct midi *iep, unsigned char *msg, int size)
{
#ifdef DEBUG
char str[128];
#endif
struct midi *oep;
int i;
#ifdef DEBUG
logx(4, "midi%u: sending: %s", iep->num,
(midiev_fmt(str, sizeof(str), msg, size), str));
#endif
for (i = 0; i < MIDI_NEP ; i++) {
if ((iep->txmask & (1 << i)) == 0)
continue;
oep = midi_ep + i;
if (msg[0] <= 0x7f) {
if (oep->owner != iep)
continue;
} else if (msg[0] <= 0xf7)
oep->owner = iep;
#ifdef DEBUG
logx(4, "midi%u -> midi%u", iep->num, oep->num);
#endif
oep->ops->omsg(oep->arg, msg, size);
}
}
void
midi_tickets(struct midi *iep)
{
int i, tickets, avail, maxavail;
struct midi *oep;
if (iep->tickets >= MIDI_BUFSZ / 4)
return;
maxavail = MIDI_BUFSZ;
for (i = 0; i < MIDI_NEP ; i++) {
if ((iep->txmask & (1 << i)) == 0)
continue;
oep = midi_ep + i;
avail = oep->obuf.len - oep->obuf.used;
if (maxavail > avail)
maxavail = avail;
}
tickets = maxavail / 2 - iep->tickets;
if (tickets > 0) {
iep->tickets += tickets;
iep->ops->fill(iep->arg, tickets);
}
}
void
midi_fill(struct midi *oep)
{
int i;
struct midi *iep;
for (i = 0; i < MIDI_NEP; i++) {
iep = midi_ep + i;
if (iep->txmask & oep->self)
midi_tickets(iep);
}
}
void
midi_in(struct midi *iep, unsigned char *idata, int icount)
{
int i;
unsigned char c;
for (i = 0; i < icount; i++) {
c = *idata++;
if (c >= 0xf8) {
if (c != MIDI_ACK)
iep->ops->imsg(iep->arg, &c, 1);
} else if (c == SYSEX_END) {
if (iep->st == SYSEX_START) {
iep->msg[iep->idx++] = c;
iep->ops->imsg(iep->arg, iep->msg, iep->idx);
}
iep->st = iep->last_st;
if (iep->st)
iep->len = voice_len[(iep->st >> 4) & 7];
iep->idx = 0;
} else if (c >= 0xf0) {
iep->msg[0] = c;
iep->len = common_len[c & 7];
iep->st = c;
iep->idx = 1;
} else if (c >= 0x80) {
iep->msg[0] = c;
iep->len = voice_len[(c >> 4) & 7];
iep->last_st = iep->st = c;
iep->idx = 1;
} else if (iep->st) {
if (iep->idx == 0 && iep->st != SYSEX_START)
iep->msg[iep->idx++] = iep->st;
iep->msg[iep->idx++] = c;
if (iep->idx == iep->len) {
iep->ops->imsg(iep->arg, iep->msg, iep->idx);
if (iep->st >= 0xf0)
iep->st = 0;
iep->idx = 0;
} else if (iep->idx == MIDI_MSGMAX) {
iep->ops->imsg(iep->arg, iep->msg, iep->idx);
iep->idx = 0;
}
}
}
iep->tickets -= icount;
if (iep->tickets < 0)
iep->tickets = 0;
midi_tickets(iep);
}
void
midi_out(struct midi *oep, unsigned char *idata, int icount)
{
#ifdef DEBUG
char str[128];
#endif
unsigned char *odata;
int ocount;
while (icount > 0) {
if (oep->obuf.used == oep->obuf.len) {
#ifdef DEBUG
logx(2, "midi%u: too slow, discarding %d bytes",
oep->num, oep->obuf.used);
#endif
abuf_rdiscard(&oep->obuf, oep->obuf.used);
oep->owner = NULL;
return;
}
odata = abuf_wgetblk(&oep->obuf, &ocount);
if (ocount > icount)
ocount = icount;
memcpy(odata, idata, ocount);
#ifdef DEBUG
logx(4, "midi%u: out: %s", oep->num,
(midiev_fmt(str, sizeof(str), odata, ocount), str));
#endif
abuf_wcommit(&oep->obuf, ocount);
icount -= ocount;
idata += ocount;
}
}
void
midi_abort(struct midi *p)
{
int i;
struct midi *ep;
for (i = 0; i < MIDI_NEP; i++) {
ep = midi_ep + i;
if ((ep->txmask & p->self) || (p->txmask & ep->self))
ep->ops->exit(ep->arg);
}
}
void
midi_migrate(struct midi *oep, struct midi *nep)
{
struct midithru *t;
struct midi *ep;
int i;
for (i = 0; i < MIDITHRU_NMAX; i++) {
t = midithru + i;
if (t->txmask & oep->self) {
t->txmask &= ~oep->self;
t->txmask |= nep->self;
}
if (t->rxmask & oep->self) {
t->rxmask &= ~oep->self;
t->rxmask |= nep->self;
}
}
for (i = 0; i < MIDI_NEP; i++) {
ep = midi_ep + i;
if (ep->txmask & oep->self) {
ep->txmask &= ~oep->self;
ep->txmask |= nep->self;
}
}
for (i = 0; i < MIDI_NEP; i++) {
ep = midi_ep + i;
if (oep->txmask & ep->self) {
oep->txmask &= ~ep->self;
nep->txmask |= ep->self;
}
}
}
void
port_imsg(void *arg, unsigned char *msg, int size)
{
struct port *p = arg;
midi_send(p->midi, msg, size);
}
void
port_omsg(void *arg, unsigned char *msg, int size)
{
struct port *p = arg;
midi_out(p->midi, msg, size);
}
void
port_fill(void *arg, int count)
{
}
void
port_exit(void *arg)
{
#ifdef DEBUG
struct port *p = arg;
logx(0, "midi%u: port exit", p->midi->num);
panic();
#endif
}
struct port *
port_new(char *path, unsigned int mode, int hold)
{
struct port *c;
c = xmalloc(sizeof(struct port));
c->path = path;
c->state = PORT_CFG;
c->hold = hold;
c->midi = midi_new(&port_midiops, c, mode);
c->num = midi_portnum++;
c->alt_next = c;
c->next = port_list;
port_list = c;
return c;
}
void
port_del(struct port *c)
{
struct port **p;
if (c->state != PORT_CFG)
port_close(c);
midi_del(c->midi);
for (p = &port_list; *p != c; p = &(*p)->next) {
#ifdef DEBUG
if (*p == NULL) {
logx(0, "port to delete not on list");
panic();
}
#endif
}
*p = c->next;
xfree(c);
}
int
port_ref(struct port *c)
{
#ifdef DEBUG
logx(3, "midi%u: port requested", c->midi->num);
#endif
if (c->state == PORT_CFG && !port_open(c))
return 0;
return 1;
}
void
port_unref(struct port *c)
{
int i, rxmask;
#ifdef DEBUG
logx(3, "midi%u: port released", c->midi->num);
#endif
for (rxmask = 0, i = 0; i < MIDI_NEP; i++)
rxmask |= midi_ep[i].txmask;
if ((rxmask & c->midi->self) == 0 && c->midi->txmask == 0 &&
c->state == PORT_INIT && !c->hold)
port_drain(c);
}
struct port *
port_alt_ref(int num)
{
struct port *a, *p;
a = port_bynum(num);
if (a == NULL)
return NULL;
while (a->alt_next->num > a->num)
a = a->alt_next;
p = a;
while (1) {
if (port_ref(p))
break;
p = p->alt_next;
if (p == a)
return NULL;
}
return p;
}
struct port *
port_migrate(struct port *op)
{
struct port *np;
if (op->state == PORT_CFG)
return op;
np = op;
while (1) {
np = np->alt_next;
if (np == op) {
logx(2, "midi%u: no fall-back port found", op->midi->num);
return op;
}
if (port_ref(np))
break;
}
logx(2, "midi%u: switching to midi%u", op->midi->num, np->midi->num);
midi_migrate(op->midi, np->midi);
return np;
}
struct port *
port_bynum(int num)
{
struct port *p;
for (p = port_list; p != NULL; p = p->next) {
if (p->num == num)
return p;
}
return NULL;
}
int
port_open(struct port *c)
{
if (!port_mio_open(c)) {
logx(1, "midi%u: failed to open midi port", c->midi->num);
return 0;
}
c->state = PORT_INIT;
return 1;
}
int
port_close(struct port *c)
{
#ifdef DEBUG
if (c->state == PORT_CFG) {
logx(0, "midi%u: can't close port (not opened)", c->midi->num);
panic();
}
#endif
logx(2, "midi%u: closed", c->midi->num);
c->state = PORT_CFG;
port_mio_close(c);
return 1;
}
void
port_drain(struct port *c)
{
struct midi *ep = c->midi;
if (!(ep->mode & MODE_MIDIOUT) || ep->obuf.used == 0)
port_close(c);
else {
c->state = PORT_DRAIN;
#ifdef DEBUG
logx(3, "midi%u: draining", c->midi->num);
#endif
}
}
int
port_init(struct port *c)
{
if (c->hold)
return port_open(c);
return 1;
}
void
port_done(struct port *c)
{
if (c->state == PORT_INIT)
port_drain(c);
}