/*

    MidiIn -- C/python module for parsing MIDI input

    Copyright (C) 1999-2000  Eric S. Tiedemann

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Contact: Eric S. Tiedemann <est@hyperreal.org>

*/

#include <stdlib.h>

static const int EOX = 0xf7;
static const int SEX = 0xf0;

struct MidiIn
{
  int status; 
  int status3;
  int thirdp;
  int second;
  int (*cb)(void *, const char *, int, int, int, int);
  void *cb_data;
  int (*sysex_cb)(void *, int, const unsigned char *);
  void *sysex_cb_data;
  void (*nomem_cb)(void *);
  void *nomem_cb_data;
  unsigned char *sexbuf;
  int sexbufsz;
  int sexbufn;
};

  
struct MidiIn *
MidiIn_New()
{
  struct MidiIn *m = (struct MidiIn *) calloc(1, sizeof(struct MidiIn));

  return m;
}

static int
channel(int b)
{
    return (b & 0xF) + 1;
}

/* note: nargs includes channel number */
static struct statustab_s {int status; char *name; int nargs;}
statustab[] = 
{
  0x80, "note-off", 3,
  0x90, "note-on", 3,
  0xa0, "poly-key-pressure", 3,
  0xb0, "control-change", 3,
  0xc0, "program-change", 2,
  0xd0, "channel-pressure", 2,
  0xe0, "pitch-bend", 2,	/* note: the 2 bytes are combined */

  0xf0, "system-exclusive", 0,	/* handled separately */

  0xf1, "MTC-quarter-frame", 1,
  0xf2, "song-position-pointer", 1, /* note: the 2 bytes are combined */
  0xf3, "song-select", 1,
  0xf6, "tune-request", 0,
  0xf7, "end-of-exclusive", 0,	/* handled separately */

  0xf8, "timing-clock", 0,
  0xfa, "start", 0,
  0xfb, "continue", 0,
  0xfc, "stop", 0,
  0xfe, "active-sensing", 0,
  0xff, "system-reset", 0,
  -1, "???", 0,
};

static struct statustab_s *
find_status_entry(int s)
{
  struct statustab_s *st = statustab;

  s = s < 0xF0 ? (s & 0xF0) : s;

  while (st->status != -1 && st->status != s)
    st++;

  return st;
}

static const char *
status2name(int s)
{
  return find_status_entry(s)->name;
}
  
static int
status2nargs(int s)
{
  return find_status_entry(s)->nargs;
}

static const char *
channel_mode_message(int i)
{
  switch (i)
    {
    case 120: return("all-sound-off");
    case 121: return("reset-all-controllers");
    case 122: return("local-control");
    case 123: return("all-notes-off");
    case 124: return("omni-off");
    case 125: return("omni-on");
    case 126: return("mono-on");
    case 127: return("poly-on");
    default: return "???";
    }
}

static int
channel_mode_nargs(int v)
{
  if (v == 122 || v == 126)
    return 2;
  else
    return 1;
}

static int
channel_mode_normalize(int v, int b)
{
  if (v == 122)
    return (b ? 1 : 0);
  else if (v == 126)
    {
      if (b > 16)
	return 0;
      else
	return b;
    }
  else
    return 0;
}

static int
invoke_callback(struct MidiIn *m, int e, int v, int w)
{
  /*fprintf(stderr, "%02x %d %d\n", e, v, w);*/

  if (m->cb)
    {
      if ((e & 0xF0) == 0xB0)
	{
	  /* handle special values of v for control change*/
	  if (v > 119 && v < 128)
	    return m->cb(m->cb_data, channel_mode_message(v), 
			 channel_mode_nargs(v), channel(e), 
			 channel_mode_normalize(v, w), 0);
	  else
	    {
	      return m->cb(m->cb_data, status2name(e), status2nargs(e), 
			   channel(e), v, w);
	    }
	}
      else if (e == SEX)
	;			/* shouldn't be here actually */
      else if ((e & 0xF0) == 0xE0)
	return m->cb(m->cb_data, status2name(e), 
		     2, channel(e), v + w * 128, 0);
      else if (e == 0xF2)
	return m->cb(m->cb_data, status2name(e), 1, v + w * 128, 0, 0);
      else if (e < 0xF0)
	return m->cb(m->cb_data, status2name(e), status2nargs(e), 
		     channel(e), v, w);
      else
	return m->cb(m->cb_data, status2name(e), status2nargs(e), v, w, 0);
    }
}

static int
real_time(int b)
{
  switch (b)
  {
    case 0xf8:
    case 0xfa:
    case 0xfb: 
    case 0xfc: 
    case 0xfe: 
    case 0xff: return 1;
    default: return 0;
  }
}

static int
status_byte(int b)
{
  return b & 0x80;
}

#define SEXBUF_INITIAL_SIZE 1024

static int
sysexb(struct MidiIn *m, int b)
{
  /*fprintf(stderr, "in sysexb %d:%d\n", m->sexbufn, m->sexbufsz);*/

  if (!m->sexbuf)
    {
      if (!(m->sexbuf = (unsigned char *) malloc(SEXBUF_INITIAL_SIZE)))
	{
	  if (m->nomem_cb)
	    m->nomem_cb(m->nomem_cb_data);
	  return 0;
	}
      else
	{
	  m->sexbufsz = SEXBUF_INITIAL_SIZE;
	  m->sexbufn = 0;
	}
    }

  if (m->sexbufn == m->sexbufsz)
    {
      if (!(m->sexbuf = (unsigned char *) malloc(m->sexbufsz * 2)))
	{
	  free(m->sexbuf);
	  m->sexbufn = m->sexbufsz = 0;
	  m->sexbuf = 0;
	  if (m->nomem_cb)
	    m->nomem_cb(m->nomem_cb_data);
	  return 0;
	}
      else
	{
	  m->sexbufsz *= 2;
	}
    }

  /*fprintf(stderr, "about to append\n");*/

  m->sexbuf[m->sexbufn++] = b;

  return 1;
}

static int
sysexend(struct MidiIn *m)
{
  int rc = 1;

  /*fprintf(stderr, "in sysexend %d:%d\n", m->sexbufn, m->sexbufsz);*/

  if (m->sexbuf)
    {
      if (m->sysex_cb)
	rc = m->sysex_cb(m->sysex_cb_data, m->sexbufn, m->sexbuf);

      free(m->sexbuf);
      m->sexbufn = m->sexbufsz = 0;
      m->sexbuf = 0;
    }
  else
    /* empty sysex */
    {
      unsigned char buf[1];

      if (m->sysex_cb)
	rc = m->sysex_cb(m->sysex_cb_data, 0, buf);
    }

  return rc;
}

int
MidiIn_InByte(struct MidiIn *m, int b)
{ 
  /*printf("%d\n", b);*/
  int rc = 1;

  if (status_byte(b))
    {
      if (real_time(b))
	rc = invoke_callback(m, b, 0, 0);
      else if (b == EOX)
	{
	  if (m->status == SEX)
	    {
	      rc = sysexend(m);
	      m->status = 0;
	    }
	  else
	    /* complain? */
	    ;			
	}
      else
	{
	  if (m->status == SEX)
	    rc = sysexend(m);
	  m->status = b;
	  if (b == 0xF6)
	    {
	      if (!invoke_callback(m, b, 0, 0))
		rc = 0;
	    }
	}
    }
  else				/* data byte */
    {
      if (m->status == SEX)
	rc = sysexb(m, b);
      else if (m->thirdp)
	{
	  if (m->status3)
	    {
	      rc = invoke_callback(m, m->status3, m->second, b);
	      m->status3 = 0;
	    }
	  else
	    rc = invoke_callback(m, m->status, m->second, b);
	  m->thirdp = 0;
	}
      else
	{
	  if (m->status)
	    {
	      if (m->status < 0xC0)
		{
		  m->thirdp = 1;
		  m->second = b;
		}
	      else
		{
		  if (m->status < 0xE0)
		    rc = invoke_callback(m, m->status, b, 0);
		  else
		    {
		      if (m->status < 0xF0)
			{
			  m->thirdp = 1;
			  m->second = b;
			}
		      else
			{
			  if (m->status == 0xF2)
			    {
			      m->thirdp = 1;
			      m->second = b;
			      m->status3 = m->status;
			      m->status = 0;
			    }
			  else
			    {
			      if (m->status == 0xF3 || m->status == 0xF1)
				{
				  rc = invoke_callback(m, m->status, b, 0);
				  m->status = 0;
				}
			      else
				{
				  /* ignore strange status byte */
				  m->status = 0; 
				}
			    }
			}
		    }
		}
	    }
	  else
	    ; /* ignore data byte with no status on record */
	}
    }

  return rc;
}

int
MidiIn_InBytes(struct MidiIn *m, unsigned char *bs, size_t n)
{ 
  size_t i;

  for (i = 0; i < n; i++)
    if (!MidiIn_InByte(m, bs[i]))
      return 0;

  return 1;
}

#define PYTHON
#ifdef PYTHON

#include <Python.h>

typedef struct
{
  PyObject_HEAD
  struct MidiIn *o;
} PyMidiIn;

#define PyMidiIn_Check(o) ((o)->ob_type == &PyMidiIn_Type)

static PyObject *
PyMidiIn_inbytes(PyObject *self, PyObject *args)
{
  unsigned char *s;
  int n;

  if (!PyArg_Parse(args, "s#;MIDI._In.inbytes", &s, &n))
    return NULL;

  if (!MidiIn_InBytes(((PyMidiIn *) self)->o, s, n))
    return NULL;

  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *
PyMidiIn_read(PyObject *self, PyObject *args)
{
  PyObject *f;
  FILE *fp;
  int errp = 0;
  size_t n;
  char buf[1];

  if (!PyArg_Parse(args, "O;MIDI._In.read", &f))
    return NULL;

  if (!PyFile_Check(f))
    {
      PyErr_SetString(PyExc_TypeError, "argument must be a file");
      return 0;
    }

  Py_INCREF(f);

  fp = PyFile_AsFile(f);

  while (1)
    {
      Py_BEGIN_ALLOW_THREADS
      n = fread(buf, 1, sizeof(buf), fp);
      Py_END_ALLOW_THREADS

      if (n == 0)
	break;

      if (!MidiIn_InBytes(((PyMidiIn *) self)->o, buf, n))
	{
	  errp = 1;
	  break;
	}
    }

  if (errp)
    {
      Py_DECREF(f);
      return 0;
    }

  if (ferror(fp))
    {
      Py_DECREF(f);
      PyErr_SetFromErrno(PyExc_IOError);
      return 0;
    }

  Py_DECREF(f);
  Py_INCREF(Py_None);
  return Py_None;
}

static PyObject *
PyMidiIn_settab(PyObject *self, PyObject *args)
{
  unsigned char *s;
  int n;
  PyObject *o;

  if (!PyArg_Parse(args, "O;MIDI._In.settab", &o))
    return NULL;

  ((PyMidiIn *) self)->o->cb_data = o;

  Py_INCREF(Py_None);
  return Py_None;
}

static struct PyMethodDef PyMidiIn_methods[] = {
  {"inbytes", PyMidiIn_inbytes},
  {"read", PyMidiIn_read},
  {"settab", PyMidiIn_settab},
  {NULL, NULL}
};

static PyObject *
PyMidiIn_getattr(PyObject *self, char *name)
{
  if (strcmp(name, "__methods__") == 0)
    return Py_BuildValue("(s)", "inbytes");
  else
    return Py_FindMethod(PyMidiIn_methods, self, name);
}

static void
PyMidiIn_del(PyMidiIn *o)
{
  Py_XDECREF((PyObject *) (o->o->cb_data));
  Py_XDECREF((PyObject *) (o->o->sysex_cb_data));
  Py_XDECREF((PyObject *) (o->o->nomem_cb_data));

  return;
}

static PyTypeObject PyMidiIn_Type =
{
  PyObject_HEAD_INIT(&PyType_Type)
  0,
  "MidiIn",
  sizeof(PyMidiIn),
  0,

  (destructor) PyMidiIn_del,
  0,
  PyMidiIn_getattr,
  0,
  0,
  0,

  0,
  0,
  0,

  0,
  0,
  0,
};

static PyObject *
msgargs2tuple(const char *s, int nargs, int c, int v, int w)
{
  switch (nargs)
    {
    case 0:
      return Py_BuildValue("(s)", s);
      break;
    case 1:
      return Py_BuildValue("(si)", s, c);
      break;
    case 2:
      return Py_BuildValue("(sii)", s, c, v);
      break;
    case 3:
      return Py_BuildValue("(siii)", s, c, v, w);
      break;
    default:
      return 0;
    }
}

static int
callcbx(void *d, const char *s, int nargs, int c, int v, int w)
{
  PyObject *p = (PyObject *) d, *res = 0, *args = 0;

  if (!(args = msgargs2tuple(s, nargs, c, v, w)))
    return 0;

  res = PyObject_CallObject(p, args);

  Py_DECREF(args);

  if (!res)
    return 0;
  else
    {
      Py_DECREF(res);
      return 1;
    }
}

static int
callcb(void *d, const char *s, int nargs, int c, int v, int w)
{
  int rc;

  rc = callcbx(d, s, nargs, c, v, w);

  return rc;
}

static int
callcb1x(void *d, const char *s, int nargs, int c, int v, int w)
{
  PyObject *tab = (PyObject *) d, *res = 0, *args =  0, *tab1 = 0;
  int rc = 1;

  if (!tab || PyObject_Not(tab))
    return 1;

  if (!(args = msgargs2tuple(s, nargs, c, v, w)))
    return 0;

  /* all further returns are by goto to callout or out */

  if (!PyDict_Check(tab))
    {
      res = PyObject_CallObject(tab, args);
      goto callout;
    }
  else
    {
      int iargs[3];
      int i, j;
      PyObject *tab0;

      tab1 = PyDict_GetItemString(tab, (char *) s);

      if (!tab1)
	{
	  PyErr_Clear();
	  goto out;
	}

      /* Tear down the callback table structure based on number of
	 args received. */
      iargs[0] = c;
      iargs[1] = v;
      iargs[2] = w;

      for (i = 0; PyList_Check(tab1); i++)
	{
	  if (i == nargs)
	    {
	      /* mmm..magic numbers :9 */
	      char buf[100];
	      sprintf(buf, 
		      "insufficient arguments (%d) for message type %.50s", 
		      nargs, s);
	      PyErr_SetString(PyExc_RuntimeError, buf);
	      rc = 0;
	      goto out;
	    }

	  j = iargs[i];
	  
	  if (j < 0 || j >= PyList_Size(tab1))
	    {
	      /* mmm..magic numbers :9 */
	      char buf[100];
	      sprintf(buf, 
		      "invalid value (%d) for argument number %d of %.50s",
		      j, i+1, s);
	      PyErr_SetString(PyExc_RuntimeError, buf);
	      rc = 0;
	      goto out;
	    }

	  tab0 = PyList_GetItem(tab1, j);
	  Py_DECREF(tab1);
	  tab1 = tab0;
	}

      if (PyObject_IsTrue(tab1))
	{
	  res = PyObject_CallObject(tab1, args);
	  goto callout;
	}
      else
	{
	  goto out;
	}
    }

 callout:

  if (!res)
    rc = 0;
  else
    Py_DECREF(res);

 out:

  Py_XDECREF(args);
  Py_XDECREF(tab1);

  return rc;
}

static int
callcb1(void *d, const char *s, int nargs, int c, int v, int w)
{
  int rc;

  rc = callcb1x(d, s, nargs, c, v, w);

  return rc;
}

static int
callscbx(void *d, int n, const unsigned char *bs)
{
  PyObject *p = (PyObject *) d;

  /*fprintf(stderr, "about to call scb with %d bytes\n", n);*/

  if (p)
    {
      PyObject *res = PyObject_CallFunction(p, "s#", bs, n);

      if (res)
        {
          Py_DECREF(res);
          return 1;
        }
      else
        return 0;
    }

  return 1;
}

static int
callscb(void *d, int n, const unsigned char *bs)
{
  int rc;

  rc = callscbx(d, n, bs);

  return rc;
}

static void
nomemcb(void *v)
{
  PyErr_SetString(PyExc_MemoryError, "sysex buffer out of memory");
}

static PyObject *
PyMidiIn_New(PyObject *self, PyObject *args)
{
  PyObject *cb, *scb = 0;
  PyMidiIn *s;
  struct MidiIn *m;

  if (!PyArg_ParseTuple(args, "O|O;_MidiIn.MidiIn", &cb, &scb))
    return NULL;

  if (!PyCallable_Check(cb))
    {
      PyErr_SetString(PyExc_TypeError, "first arg must be callable");
      return 0;
    }

  if (scb && !PyCallable_Check(scb))
    {
      PyErr_SetString(PyExc_TypeError, "second arg must be callable");
      return 0;
    }

  m = MidiIn_New();
  
  if (!m)
    {
      PyErr_SetString(PyExc_MemoryError, "MIDI._In out of memory");
      return 0;
    }

  s = PyObject_NEW(PyMidiIn, &PyMidiIn_Type);

  if (!s)
    return 0;

  s->o = m;
  m->cb = callcb;
  m->cb_data = cb;
  m->sysex_cb = callscb;
  m->sysex_cb_data = scb;
  m->nomem_cb = nomemcb;

  Py_INCREF(cb);

  if (scb)
    Py_INCREF(scb);

  return (PyObject *) s;
}

static PyObject *
PyMidiIn_New1(PyObject *self, PyObject *args)
{
  PyObject *tab, *scb = 0;
  PyMidiIn *s;
  struct MidiIn *m;

  if (!PyArg_ParseTuple(args, "O|O;MIDI._In", &tab, &scb))
    return NULL;

  if (scb && !PyCallable_Check(scb))
    {
      PyErr_SetString(PyExc_TypeError, "second arg must be callable");
      return 0;
    }

  m = MidiIn_New();
  
  if (!m)
    /* should set memory exception */
    return 0;

  s = PyObject_NEW(PyMidiIn, &PyMidiIn_Type);

  if (!s)
    return 0;

  s->o = m;
  m->cb = callcb1;
  m->cb_data = tab;
  m->sysex_cb = callscb;
  m->sysex_cb_data = scb;
  m->nomem_cb = nomemcb;

  Py_INCREF(tab);

  if (scb)
    Py_INCREF(scb);

  return (PyObject *) s;
}

static PyMethodDef midiinMethods[] =
{
  {"MidiIn", PyMidiIn_New, METH_VARARGS},
  {"new1", PyMidiIn_New1, METH_VARARGS},
  {"settab", PyMidiIn_settab, METH_VARARGS},
  {0, 0, 0}
};

DL_EXPORT(void)
init_In()
{
  Py_InitModule("_In", midiinMethods);
}
#endif

/*#define TEST*/
#ifdef TEST

#include <stdio.h>

int
foo(void *data, const char *s, int nargs, int c, int u, int v)
{
  printf("(%s %d %d %d %d)\n", s, nargs, c, u, v);
  return 1;
}

int
main()
{
  int c;
  int n = 0;
  struct MidiIn *m = MidiIn_New();

  if (!m)
    return 1;

  m->cb = foo;
  while (n++ < 1000)
    MidiIn_InByte(m, getchar());

  return 0;
}
#endif

