/* Copyright (c) 2002-2005 krzYszcz and others.
 * For information on usage and redistribution, and for a DISCLAIMER OF ALL
 * WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* CHECKED no sharing of data among seq objects having the same creation arg */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "m_pd.h"
#include "shared.h"
#include "common/loud.h"
#include "common/grow.h"
#include "common/fitter.h"
#include "common/mifi.h"
#include "unstable/forky.h"
#include "hammer/file.h"

#ifdef KRZYSZCZ
#define SEQ_DEBUG
#endif

#define SEQ_INISEQSIZE       256   /* LATER rethink */
#define SEQ_INITEMPOMAPSIZE  128   /* LATER rethink */
#define SEQ_EOM              255   /* end of message marker, LATER rethink */
#define SEQ_TICKSPERSEC       48
#define SEQ_MINTICKDELAY       1.  /* LATER rethink */
#define SEQ_TICKEPSILON  ((double).0001)
#define SEQ_STARTEPSILON          .0001  /* if inside: play unmodified */
#define SEQ_TEMPOEPSILON          .0001  /* if inside: pause */

#define SEQ_ISRUNNING(x)  ((x)->x_prevtime > (double).0001)
#define SEQ_ISPAUSED(x)  ((x)->x_prevtime <= (double).0001)

enum { SEQ_IDLEMODE, SEQ_RECMODE, SEQ_PLAYMODE, SEQ_SLAVEMODE };

typedef struct _seqevent
{
    double         e_delta;
    unsigned char  e_bytes[4];
} t_seqevent;

typedef struct _seqtempo
{
    double  t_scoretime;  /* score ticks from start */
    double  t_sr;         /* score ticks per second */
} t_seqtempo;

typedef struct _seq
{
    t_object       x_ob;
    t_canvas      *x_canvas;
    t_symbol      *x_defname;
    t_hammerfile  *x_filehandle;
    int            x_mode;
    int            x_playhead;
    double         x_nextscoretime;
    float          x_timescale;
    float          x_newtimescale;
    double         x_prevtime;
    double         x_slaveprevtime;
    double         x_clockdelay;
    unsigned char  x_status;
    int            x_evelength;
    int            x_expectedlength;
    int            x_eventreadhead;
    int            x_seqsize;  /* as allocated */
    int            x_nevents;  /* as used */
    t_seqevent    *x_sequence;
    t_seqevent     x_seqini[SEQ_INISEQSIZE];
    int            x_temporeadhead;
    int            x_tempomapsize;  /* as allocated */
    int            x_ntempi;        /* as used */
    t_seqtempo    *x_tempomap;
    t_seqtempo     x_tempomapini[SEQ_INITEMPOMAPSIZE];
    t_clock       *x_clock;
    t_clock       *x_slaveclock;
    t_outlet      *x_bangout;
} t_seq;

static t_class *seq_class;

static void seq_doclear(t_seq *x, int dofree)
{
    if (dofree)
    {
	if (x->x_sequence != x->x_seqini)
	{
	    freebytes(x->x_sequence, x->x_seqsize * sizeof(*x->x_sequence));
	    x->x_sequence = x->x_seqini;
	    x->x_seqsize = SEQ_INISEQSIZE;
	}
	if (x->x_tempomap != x->x_tempomapini)
	{
	    freebytes(x->x_tempomap,
		      x->x_tempomapsize * sizeof(*x->x_tempomap));
	    x->x_tempomap = x->x_tempomapini;
	    x->x_tempomapsize = SEQ_INITEMPOMAPSIZE;
	}
    }
    x->x_nevents = 0;
    x->x_ntempi = 0;
}

static int seq_dogrowing(t_seq *x, int nevents, int ntempi)
{
    if (nevents > x->x_seqsize)
    {
	int nrequested = nevents;
#ifdef SEQ_DEBUG
	loudbug_post("growing for %d events...", nevents);
#endif
	x->x_sequence =
	    grow_nodata(&nrequested, &x->x_seqsize, x->x_sequence,
			SEQ_INISEQSIZE, x->x_seqini, sizeof(*x->x_sequence));
	if (nrequested < nevents)
	{
	    x->x_nevents = 0;
	    x->x_ntempi = 0;
	    return (0);
	}
    }
    if (ntempi > x->x_tempomapsize)
    {
	int nrequested = ntempi;
#ifdef SEQ_DEBUG
	loudbug_post("growing for %d tempi...", ntempi);
#endif
	x->x_tempomap =
	    grow_nodata(&nrequested, &x->x_tempomapsize, x->x_tempomap,
			SEQ_INITEMPOMAPSIZE, x->x_tempomapini,
			sizeof(*x->x_tempomap));
	if (nrequested < ntempi)
	{
	    x->x_ntempi = 0;
	    return (0);
	}
    }
    x->x_nevents = nevents;
    x->x_ntempi = ntempi;
    return (1);
}

static void seq_complete(t_seq *x)
{
    if (x->x_evelength < x->x_expectedlength)
    {
	/* CHECKED no warning if no data after status byte requiring data */
	if (x->x_evelength > 1)
	    post("seq: truncated midi message");  /* CHECKED */
	/* CHECKED nothing stored */
    }
    else
    {
	t_seqevent *ep = &x->x_sequence[x->x_nevents];
	ep->e_delta = clock_gettimesince(x->x_prevtime);
	x->x_prevtime = clock_getlogicaltime();
	if (x->x_evelength < 4)
	    ep->e_bytes[x->x_evelength] = SEQ_EOM;
	x->x_nevents++;
	if (x->x_nevents >= x->x_seqsize)
	{
	    int nexisting = x->x_seqsize;
	    /* store-ahead scheme, LATER consider using x_currevent */
	    int nrequested = x->x_nevents + 1;
#ifdef SEQ_DEBUG
	    loudbug_post("growing...");
#endif
	    x->x_sequence =
		grow_withdata(&nrequested, &nexisting,
			      &x->x_seqsize, x->x_sequence,
			      SEQ_INISEQSIZE, x->x_seqini,
			      sizeof(*x->x_sequence));
	    if (nrequested <= x->x_nevents)
		x->x_nevents = 0;
	}
    }
    x->x_evelength = 0;
}

static void seq_checkstatus(t_seq *x, unsigned char c)
{
    if (x->x_status && x->x_evelength > 1)  /* LATER rethink */
	seq_complete(x);
    if (c < 192)
	x->x_expectedlength = 3;
    else if (c < 224)
	x->x_expectedlength = 2;
    else if (c < 240)
	x->x_expectedlength = 3;
    else if (c < 248)
    {
	/* FIXME */
	x->x_expectedlength = -1;
    }
    else
    {
	x->x_sequence[x->x_nevents].e_bytes[0] = c;
	x->x_evelength = x->x_expectedlength = 1;
	seq_complete(x);
	return;
    }
    x->x_status = x->x_sequence[x->x_nevents].e_bytes[0] = c;
    x->x_evelength = 1;
}

static void seq_addbyte(t_seq *x, unsigned char c, int docomplete)
{
    x->x_sequence[x->x_nevents].e_bytes[x->x_evelength++] = c;
    if (x->x_evelength == x->x_expectedlength)
    {
	seq_complete(x);
	if (x->x_status)
	{
	    x->x_sequence[x->x_nevents].e_bytes[0] = x->x_status;
	    x->x_evelength = 1;
	}
    }
    else if (x->x_evelength == 4)
    {
	if (x->x_status != 240)
	    loudbug_bug("seq_addbyte");
	/* CHECKED sysex is broken into 4-byte packets marked with
	   the actual delta time of last byte received in a packet */
	seq_complete(x);
    }
    else if (docomplete) seq_complete(x);
}

static void seq_endofsysex(t_seq *x)
{
    seq_addbyte(x, 247, 1);
    x->x_status = 0;
}

static void seq_stoprecording(t_seq *x)
{
    if (x->x_status == 240)
    {
	post("seq: incomplete sysex");  /* CHECKED */
	seq_endofsysex(x);  /* CHECKED 247 added implicitly */
    }
    else if (x->x_status)
	seq_complete(x);
    /* CHECKED running status used in recording, but not across recordings */
    x->x_status = 0;
}

static void seq_stopplayback(t_seq *x)
{
    /* FIXME */
    /* CHECKED "seq: incomplete sysex" at playback stop, 247 added implicitly */
    /* CHECKME resetting controllers, etc. */
    /* CHECKED bang not sent if playback stopped early */
    clock_unset(x->x_clock);
    x->x_playhead = 0;
    x->x_nextscoretime = 0.;
}

static void seq_stopslavery(t_seq *x)
{
    /* FIXME */
    clock_unset(x->x_clock);
    clock_unset(x->x_slaveclock);
    x->x_playhead = 0;
    x->x_nextscoretime = 0.;
}

static void seq_startrecording(t_seq *x, int modechanged)
{
    x->x_prevtime = clock_getlogicaltime();
    x->x_status = 0;
    x->x_evelength = 0;
    x->x_expectedlength = -1;  /* LATER rethink */
}

/* CHECKED running status not used in playback */
static void seq_startplayback(t_seq *x, int modechanged)
{
    /* CHECKED bang not sent if sequence is empty */
    if (x->x_nevents)
    {
	if (modechanged)
	{
	    x->x_playhead = 0;
	    x->x_nextscoretime = x->x_sequence->e_delta;
	    /* playback data never sent within the scheduler event of
	       a start message (even for the first delta <= 0), LATER rethink */
	    x->x_clockdelay = x->x_sequence->e_delta * x->x_newtimescale;
	}
	else
	{  /* CHECKED timescale change */
	    if (SEQ_ISRUNNING(x))
		x->x_clockdelay -= clock_gettimesince(x->x_prevtime);
	    x->x_clockdelay *= x->x_newtimescale / x->x_timescale;
	}
	if (x->x_clockdelay < 0.)
	    x->x_clockdelay = 0.;
	x->x_timescale = x->x_newtimescale;
	clock_delay(x->x_clock, x->x_clockdelay);
	x->x_prevtime = clock_getlogicaltime();
    }
    else x->x_mode = SEQ_IDLEMODE;
}

static void seq_startslavery(t_seq *x, int modechanged)
{
    if (x->x_nevents)
    {
	x->x_playhead = 0;
	x->x_nextscoretime = 0.;
	x->x_prevtime = 0.;
	x->x_slaveprevtime = 0.;
    }
    else x->x_mode = SEQ_IDLEMODE;
}

static void seq_setmode(t_seq *x, int newmode)
{
    int changed = (x->x_mode != newmode);
    if (changed)
    {
	switch (x->x_mode)
	{
	case SEQ_IDLEMODE:
	    break;
	case SEQ_RECMODE:
	    seq_stoprecording(x);
	    break;
	case SEQ_PLAYMODE:
	    seq_stopplayback(x);
	    break;
	case SEQ_SLAVEMODE:
	    seq_stopslavery(x);
	    break;
	default:
	    loudbug_bug("seq_setmode (old)");
	    return;
	}
	x->x_mode = newmode;
    }
    switch (newmode)
    {
    case SEQ_IDLEMODE:
	break;
    case SEQ_RECMODE:
	seq_startrecording(x, changed);
	break;
    case SEQ_PLAYMODE:
	seq_startplayback(x, changed);
	break;
    case SEQ_SLAVEMODE:
	seq_startslavery(x, changed);
	break;
    default:
	loudbug_bug("seq_setmode (new)");
    }
}

static void seq_settimescale(t_seq *x, float newtimescale)
{
    if (newtimescale < 1e-20)
	x->x_newtimescale = 1e-20;
    else if (newtimescale > 1e20)
	x->x_newtimescale = 1e20;
    else
	x->x_newtimescale = newtimescale;
}

static void seq_clocktick(t_seq *x)
{
    if (x->x_mode == SEQ_PLAYMODE || x->x_mode == SEQ_SLAVEMODE)
    {
	t_seqevent *ep = &x->x_sequence[x->x_playhead++];
	unsigned char *bp = ep->e_bytes;
nextevent:
	outlet_float(((t_object *)x)->ob_outlet, *bp++);
	if (*bp != SEQ_EOM)
	{
	    outlet_float(((t_object *)x)->ob_outlet, *bp++);
	    if (*bp != SEQ_EOM)
	    {
		outlet_float(((t_object *)x)->ob_outlet, *bp++);
		if (*bp != SEQ_EOM)
		    outlet_float(((t_object *)x)->ob_outlet, *bp++);
	    }
	}
	if (x->x_mode != SEQ_PLAYMODE && x->x_mode != SEQ_SLAVEMODE)
	    return;  /* protecting against outlet -> 'stop' etc. */
	if (x->x_playhead < x->x_nevents)
	{
	    ep++;
	    x->x_nextscoretime += ep->e_delta;
	    if (ep->e_delta < SEQ_TICKEPSILON)
		/* continue output in the same scheduler event, LATER rethink */
	    {
		x->x_playhead++;
		bp = ep->e_bytes;
		goto nextevent;
	    }
	    else
	    {
		x->x_clockdelay = ep->e_delta * x->x_timescale;
		if (x->x_clockdelay < 0.)
		    x->x_clockdelay = 0.;
		clock_delay(x->x_clock, x->x_clockdelay);
		x->x_prevtime = clock_getlogicaltime();
	    }
	}
	else
	{
	    seq_setmode(x, SEQ_IDLEMODE);
	    /* CHECKED bang sent immediately _after_ last byte */
	    outlet_bang(x->x_bangout);  /* LATER think about reentrancy */
	}
    }
}

/* timeout handler ('tick' is late) */
static void seq_slaveclocktick(t_seq *x)
{
    if (x->x_mode == SEQ_SLAVEMODE) clock_unset(x->x_clock);
}

/* LATER dealing with self-invokation (outlet -> 'tick') */
static void seq_tick(t_seq *x)
{
    if (x->x_mode == SEQ_SLAVEMODE)
    {
	if (x->x_slaveprevtime > 0)
	{
	    double elapsed = clock_gettimesince(x->x_slaveprevtime);
	    if (elapsed < SEQ_MINTICKDELAY)
		return;
	    clock_delay(x->x_slaveclock, elapsed);
	    seq_settimescale(x, (float)(elapsed * (SEQ_TICKSPERSEC / 1000.)));
	    if (SEQ_ISRUNNING(x))
	    {
		x->x_clockdelay -= clock_gettimesince(x->x_prevtime);
		x->x_clockdelay *= x->x_newtimescale / x->x_timescale;
	    }
	    else x->x_clockdelay =
		x->x_sequence[x->x_playhead].e_delta * x->x_newtimescale;
	    if (x->x_clockdelay < 0.)
		x->x_clockdelay = 0.;
	    clock_delay(x->x_clock, x->x_clockdelay);
	    x->x_prevtime = clock_getlogicaltime();
	    x->x_slaveprevtime = x->x_prevtime;
	    x->x_timescale = x->x_newtimescale;
	}
	else
	{
	    x->x_clockdelay = 0.;  /* redundant */
	    x->x_prevtime = 0.;    /* redundant */
	    x->x_slaveprevtime = clock_getlogicaltime();
	    x->x_timescale = 1.;       /* redundant */
	}
    }
}

/* CHECKED bang does the same as 'start 1024', not 'start <current-timescale>'
   (also if already in SEQ_PLAYMODE) */
static void seq_bang(t_seq *x)
{
    seq_settimescale(x, 1.);
    seq_setmode(x, SEQ_PLAYMODE);  /* CHECKED 'bang' stops recording */
}

static void seq_float(t_seq *x, t_float f)
{
    if (x->x_mode == SEQ_RECMODE)
    {
	/* CHECKED noninteger and out of range silently truncated */
	unsigned char c = (unsigned char)f;
	if (c < 128)
	{
	    if (x->x_status) seq_addbyte(x, c, 0);
	}
	else if (c != 254)  /* CHECKED active sensing ignored */
	{
	    if (x->x_status == 240)
	    {
		if (c == 247) seq_endofsysex(x);
		else
		{
		    /* CHECKED rt bytes alike */
		    post("seq: unterminated sysex");  /* CHECKED */
		    seq_endofsysex(x);  /* CHECKED 247 added implicitly */
		    seq_checkstatus(x, c);
		}
	    }
	    else if (c != 247) seq_checkstatus(x, c);
	}
    }
}

static void seq_symbol(t_seq *x, t_symbol *s)
{
    loud_nomethod((t_pd *)x, &s_symbol);  /* CHECKED */
}

static void seq_list(t_seq *x, t_symbol *s, int ac, t_atom *av)
{
    if (ac && av->a_type == A_FLOAT) seq_float(x, av->a_w.w_float);
    /* CHECKED anything else/more silently ignored */
}

static void seq_record(t_seq *x)
{
    /* CHECKED 'record' stops playback, resets recording */
    seq_doclear(x, 0);
    seq_setmode(x, SEQ_RECMODE);
}

static void seq_append(t_seq *x)
{
    /* CHECKED 'append' stops playback */
    /* CHECKED if in SEQ_RECMODE, 'append' resets the timer */
    seq_setmode(x, SEQ_RECMODE);
}

static void seq_start(t_seq *x, t_floatarg f)
{
    if (f < -SEQ_STARTEPSILON)
    {
	/* FIXME */
	seq_setmode(x, SEQ_SLAVEMODE);
    }
    else
    {
	seq_settimescale(x, (f > SEQ_STARTEPSILON ? 1024. / f : 1.));
	seq_setmode(x, SEQ_PLAYMODE);  /* CHECKED 'start' stops recording */
    }
}

static void seq_stop(t_seq *x)
{
    seq_setmode(x, SEQ_IDLEMODE);
}

/* CHECKED first delta time is set permanently (it is stored in a file) */
static void seq_delay(t_seq *x, t_floatarg f)
{
    if (x->x_nevents)
	/* CHECKED signed/unsigned bug (not emulated) */
	x->x_sequence->e_delta = (f > SEQ_TICKEPSILON ? f : 0.);
}

/* CHECKED all delta times are set permanently (they are stored in a file) */
static void seq_hook(t_seq *x, t_floatarg f)
{
    int nevents;
    if (nevents = x->x_nevents)
    {
	t_seqevent *ev = x->x_sequence;
	if (f < 0)
	    f = 0;  /* CHECKED signed/unsigned bug (not emulated) */
	while (nevents--) ev++->e_delta *= f;
    }
}

static void seq_pause(t_seq *x)
{
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	fittermax_warning(*(t_pd *)x, "'pause' not supported in Max");
	warned = 1;
    }
    if (x->x_mode == SEQ_PLAYMODE && SEQ_ISRUNNING(x))
    {
	x->x_clockdelay -= clock_gettimesince(x->x_prevtime);
	if (x->x_clockdelay < 0.)
	    x->x_clockdelay = 0.;
	clock_unset(x->x_clock);
	x->x_prevtime = 0.;
    }
}

static void seq_continue(t_seq *x)
{
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	fittermax_warning(*(t_pd *)x, "'continue' not supported in Max");
	warned = 1;
    }
    if (x->x_mode == SEQ_PLAYMODE && SEQ_ISPAUSED(x))
    {
	if (x->x_clockdelay < 0.)
	    x->x_clockdelay = 0.;
	clock_delay(x->x_clock, x->x_clockdelay);
	x->x_prevtime = clock_getlogicaltime();
    }
}

static void seq_goto(t_seq *x, t_floatarg f1, t_floatarg f2)
{
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	fittermax_warning(*(t_pd *)x, "'goto' not supported in Max");
	warned = 1;
    }
    if (x->x_nevents)
    {
	t_seqevent *ev;
	int ndx, nevents = x->x_nevents;
	double ms = (double)f1 * 1000. + f2, sum;
	if (ms <= SEQ_TICKEPSILON)
	    ms = 0.;
	if (x->x_mode != SEQ_PLAYMODE)
	{
	    seq_settimescale(x, x->x_timescale);
	    seq_setmode(x, SEQ_PLAYMODE);
	    /* clock_delay() has been called in setmode, LATER avoid */
	    clock_unset(x->x_clock);
	    x->x_prevtime = 0.;
	}
	for (ndx = 0, ev = x->x_sequence, sum = SEQ_TICKEPSILON; ndx < nevents;
	     ndx++, ev++)
	{
	    if ((sum += ev->e_delta) >= ms)
	    {
		x->x_playhead = ndx;
		x->x_nextscoretime = sum;
		x->x_clockdelay = sum - SEQ_TICKEPSILON - ms;
		if (x->x_clockdelay < 0.)
		    x->x_clockdelay = 0.;
		if (SEQ_ISRUNNING(x))
		{
		    clock_delay(x->x_clock, x->x_clockdelay);
		    x->x_prevtime = clock_getlogicaltime();
		}
		break;
	    }
	}
    }
}

static void seq_scoretime(t_seq *x, t_symbol *s)
{
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	fittermax_warning(*(t_pd *)x, "'scoretime' not supported in Max");
	warned = 1;
    }
    if (s && s->s_thing &&
	x->x_mode == SEQ_PLAYMODE)  /* LATER other modes */
    {
	t_atom aout[2];
	double ms, clockdelay = x->x_clockdelay;
	t_float f1, f2;
	if (SEQ_ISRUNNING(x))
	    clockdelay -= clock_gettimesince(x->x_prevtime);
	ms = x->x_nextscoretime - clockdelay / x->x_timescale;
	/* Send ms as a pair of floats (f1, f2) = (coarse in sec, fine in msec).
	   Any ms may then be exactly reconstructed as (double)f1 * 1000. + f2.
	   Currently, f2 may be negative.  LATER consider truncating f1 so that
	   only significant digits are on (when using 1 ms resolution, a float
	   stores only up to 8.7 minutes without distortion, 100 ms resolution
	   gives 14.5 hours of non-distorted floats, etc.) */
	f1 = ms * .001;
	f2 = ms - (double)f1 * 1000.;
	if (f2 < .001 && f2 > -.001)
	    f2 = 0.;
	SETFLOAT(&aout[0], f1);
	SETFLOAT(&aout[1], f2);
	pd_list(s->s_thing, &s_list, 2, aout);
    }
}

static void seq_tempo(t_seq *x, t_floatarg f)
{
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	fittermax_warning(*(t_pd *)x, "'tempo' not supported in Max");
	warned = 1;
    }
    if (f > SEQ_TEMPOEPSILON)
    {
	seq_settimescale(x, 1. / f);
	if (x->x_mode == SEQ_PLAYMODE)
	    seq_startplayback(x, 0);
    }
    /* FIXME else pause, LATER reverse playback if (f < -SEQ_TEMPOEPSILON) */
}

static void seq_cd(t_seq *x, t_symbol *s)
{
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	fittermax_warning(*(t_pd *)x, "'cd' not supported in Max");
	warned = 1;
    }
    hammerpanel_setopendir(x->x_filehandle, s);
}

static void seq_pwd(t_seq *x, t_symbol *s)
{
    t_symbol *dir;
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	fittermax_warning(*(t_pd *)x, "'pwd' not supported in Max");
	warned = 1;
    }
    if (s && s->s_thing && (dir = hammerpanel_getopendir(x->x_filehandle)))
	pd_symbol(s->s_thing, dir);
}

static int seq_eventcomparehook(const void *e1, const void *e2)
{
    return (((t_seqevent *)e1)->e_delta > ((t_seqevent *)e2)->e_delta ? 1 : -1);
}

static int seq_tempocomparehook(const void *t1, const void *t2)
{
    return (((t_seqtempo *)t1)->t_scoretime >
	    ((t_seqtempo *)t2)->t_scoretime ? 1 : -1);
}

static int seq_mrhook(t_mifiread *mr, void *hookdata, int evtype)
{
    t_seq *x = (t_seq *)hookdata;
    double scoretime = mifiread_getscoretime(mr);
    if (evtype >= 0xf0)
    {
    }
    else if (evtype >= 0x80)
    {
	if (x->x_eventreadhead < x->x_nevents)
	{
	    t_seqevent *sev = &x->x_sequence[x->x_eventreadhead++];
	    int status = mifiread_getstatus(mr);
	    sev->e_delta = scoretime;
	    sev->e_bytes[0] = status | mifiread_getchannel(mr);
	    sev->e_bytes[1] = mifiread_getdata1(mr);
	    if (MIFI_ONEDATABYTE(status))
		sev->e_bytes[2] = SEQ_EOM;
	    else
	    {
		sev->e_bytes[2] = mifiread_getdata2(mr);
		sev->e_bytes[3] = SEQ_EOM;
	    }
	}
	else if (x->x_eventreadhead == x->x_nevents)
	{
	    loudbug_bug("seq_mrhook 1");
	    x->x_eventreadhead++;
	}
    }
    else if (evtype == MIFIMETA_TEMPO)
    {
	if (x->x_temporeadhead < x->x_ntempi)
	{
	    t_seqtempo *stm = &x->x_tempomap[x->x_temporeadhead++];
	    stm->t_scoretime = scoretime;
	    stm->t_sr = mifiread_gettempo(mr);
#ifdef SEQ_DEBUG
	    loudbug_post("tempo %g at %g", stm->t_sr, scoretime);
#endif
	}
	else if (x->x_temporeadhead == x->x_ntempi)
	{
	    loudbug_bug("seq_mrhook 2");
	    x->x_temporeadhead++;
	}
    }
    return (1);
}

/* apply tempo and fold */
static void seq_foldtime(t_seq *x, double deftempo)
{
    t_seqevent *sev;
    t_seqtempo *stm = x->x_tempomap;
    double coef = 1000. / deftempo;
    int ex, tx = 0;
    double prevscoretime = 0.;
    while (tx < x->x_ntempi && stm->t_scoretime < SEQ_TICKEPSILON)
	tx++, coef = 1000. / stm++->t_sr;
    for (ex = 0, sev = x->x_sequence; ex < x->x_nevents; ex++, sev++)
    {
	double clockdelta = 0.;
	while (tx < x->x_ntempi && stm->t_scoretime <= sev->e_delta)
	{
	    clockdelta += (stm->t_scoretime - prevscoretime) * coef;
	    prevscoretime = stm->t_scoretime;
	    tx++;
	    coef = 1000. / stm++->t_sr;
	}
	clockdelta += (sev->e_delta - prevscoretime) * coef;
	prevscoretime = sev->e_delta;
	sev->e_delta = clockdelta;
    }
}

static int seq_mfread(t_seq *x, char *path)
{
    int result = 0;
    t_mifiread *mr = mifiread_new((t_pd *)x);
    if (!mifiread_open(mr, path, "", 0))
	goto mfreadfailed;
#ifdef SEQ_DEBUG
    loudbug_startpost("midifile (format %d): %d tracks, %d ticks",
		      mifiread_getformat(mr), mifiread_gethdtracks(mr),
		      mifiread_getbeatticks(mr));
    if (mifiread_getnframes(mr))
	loudbug_post(" (%d smpte frames)", mifiread_getnframes(mr));
    else
	loudbug_post(" per beat");
#endif
    if (!seq_dogrowing(x, mifiread_getnevents(mr), mifiread_getntempi(mr)))
	goto mfreadfailed;
    x->x_eventreadhead = 0;
    x->x_temporeadhead = 0;
    if (mifiread_doit(mr, seq_mrhook, x) != MIFIREAD_EOF)
	goto mfreadfailed;
    if (x->x_eventreadhead < x->x_nevents)
    {
	loudbug_bug("seq_mfread 1");
	loudbug_post("declared %d events, got %d",
		     x->x_nevents, x->x_eventreadhead);
	x->x_nevents = x->x_eventreadhead;
    }
    if (x->x_nevents)
	qsort(x->x_sequence, x->x_nevents, sizeof(*x->x_sequence),
	      seq_eventcomparehook);
    if (x->x_temporeadhead < x->x_ntempi)
    {
	loudbug_bug("seq_mfread 2");
	loudbug_post("declared %d tempi, got %d",
		     x->x_ntempi, x->x_temporeadhead);
	x->x_ntempi = x->x_temporeadhead;
    }
    if (x->x_ntempi)
	qsort(x->x_tempomap, x->x_ntempi, sizeof(*x->x_tempomap),
	      seq_tempocomparehook);
    seq_foldtime(x, mifiread_getdeftempo(mr));
#ifdef SEQ_DEBUG
    loudbug_post("seq: got %d events from midi file", x->x_nevents);
#endif
    result = 1;
mfreadfailed:
    mifiread_free(mr);
    return (result);
}

static int seq_mfwrite(t_seq *x, char *path)
{
    int result = 0;
    t_seqevent *sev = x->x_sequence;
    int nevents = x->x_nevents;
    t_mifiwrite *mw = mifiwrite_new((t_pd *)x);
    if (!mifiwrite_open(mw, path, "", 1, 1))
	goto mfwritefailed;
    if (!mifiwrite_opentrack(mw, "seq-track", 1))
	goto mfwritefailed;
    while (nevents--)
    {
	unsigned char *bp = sev->e_bytes;
	unsigned status = *bp & 0xf0;
	if (status > 127 && status < 240)
	{
	    if (!mifiwrite_channelevent(mw, sev->e_delta, status, *bp & 0x0f,
					bp[1], bp[2]))  /* SEQ_EOM ignored */
	    {
		loud_error((t_pd *)x, "cannot write channel event %d", status);
		goto mfwritefailed;
	    }
	}
	/* FIXME system, sysex (first, and continuation) */
	sev++;
    }
    if (!mifiwrite_closetrack(mw, 0., 1))
	goto mfwritefailed;
    mifiwrite_close(mw);
    result = 1;
mfwritefailed:
    if (!result)
	loud_errand((t_pd *)x,
		    "while saving sequence into midi file \"%s\"", path);
    mifiwrite_free(mw);
    return (result);
}

/* CHECKED text file input: absolute timestamps, semi-terminated, verified */
/* FIXME prevent loading .pd files... */
static int seq_fromatoms(t_seq *x, int ac, t_atom *av, int abstime)
{
    int i, nevents = 0;
    t_atom *ap;
    for (i = 0, ap = av; i < ac; i++, ap++)
	if (ap->a_type == A_SEMI)  /* FIXME parsing */
	    nevents++;
    if (nevents)
    {
	t_seqevent *ep;
	float prevtime = 0;
	if (!seq_dogrowing(x, nevents, 0))
	    return (0);
	i = -1;
	nevents = 0;
	ep = x->x_sequence;
	while (ac--)
	{
	    if (av->a_type == A_FLOAT)
	    {
		if (i < 0)
		{
		    if (abstime)
		    {
			ep->e_delta = av->a_w.w_float - prevtime;
			prevtime = av->a_w.w_float;
		    }
		    else ep->e_delta = av->a_w.w_float;
		    i = 0;
		}
		else if (i < 4)
		    ep->e_bytes[i++] = av->a_w.w_float;
		/* CHECKME else */
	    }
	    else if (av->a_type == A_SEMI && i > 0)
	    {
		if (i < 4)
		    ep->e_bytes[i] = SEQ_EOM;
		nevents++;
		ep++;
		i = -1;
	    }
	    /* CHECKME else */
	    av++;
	}
	x->x_nevents = nevents;
    }
    return (nevents);
}

static void seq_tobinbuf(t_seq *x, t_binbuf *bb)
{
    int nevents = x->x_nevents;
    t_seqevent *ep = x->x_sequence;
    t_atom at[5];
    float timestamp = 0;
    while (nevents--)
    {
	unsigned char *bp = ep->e_bytes;
	int i;
	t_atom *ap = at;
	timestamp += ep->e_delta;
	SETFLOAT(ap, timestamp);  /* CHECKED same for sysex continuation */
	ap++;
	SETFLOAT(ap, *bp);
	for (i = 0, ap++, bp++; i < 3 && *bp != SEQ_EOM; i++, ap++, bp++)
	    SETFLOAT(ap, *bp);
	binbuf_add(bb, i + 2, at);
	binbuf_addsemi(bb);
	ep++;
    }
}

static void seq_textread(t_seq *x, char *path)
{
    t_binbuf *bb;
    bb = binbuf_new();
    if (binbuf_read(bb, path, "", 0))
    {
	/* CHECKED no complaint, open dialog presented */
	hammerpanel_open(x->x_filehandle, 0);  /* LATER rethink */
    }
    else
    {
	int nlines = /* CHECKED absolute timestamps */
	    seq_fromatoms(x, binbuf_getnatom(bb), binbuf_getvec(bb), 1);
	if (nlines < 0)
	    /* CHECKED "bad MIDI file (truncated)" alert, even if a text file */
	    loud_error((t_pd *)x, "bad text file (truncated)");
	else if (nlines == 0)
	{
	    /* CHECKED no complaint, sequence erased, LATER rethink */
	}
    }
    binbuf_free(bb);
}

static void seq_textwrite(t_seq *x, char *path)
{
    t_binbuf *bb;
    bb = binbuf_new();
    seq_tobinbuf(x, bb);
    /* CHECKED empty sequence stored as an empty file */
    if (binbuf_write(bb, path, "", 0))
    {
	/* CHECKME complaint and FIXME */
	loud_error((t_pd *)x, "error writing text file");
    }
    binbuf_free(bb);
}

static void seq_doread(t_seq *x, t_symbol *fn, int creation)
{
    char buf[MAXPDSTRING];
    /* FIXME use open_via_path() */
    if (x->x_canvas)
	canvas_makefilename(x->x_canvas, fn->s_name, buf, MAXPDSTRING);
    else
    {
    	strncpy(buf, fn->s_name, MAXPDSTRING);
    	buf[MAXPDSTRING-1] = 0;
    }
    if (creation)
    {
	/* loading during object creation -- CHECKED no warning if a file
	   specified with an arg does not exist, LATER rethink */
	FILE *fp;
	if (!(fp = sys_fopen(buf, "r")))
	    return;
	fclose(fp);
    }
    /* CHECKED all cases: arg or not, message and creation */
    post("seq: reading %s", fn->s_name);
    if (!seq_mfread(x, buf))
	seq_textread(x, buf);
}

static void seq_dowrite(t_seq *x, t_symbol *fn)
{
    char buf[MAXPDSTRING], *dotp;
    if (x->x_canvas)
	canvas_makefilename(x->x_canvas, fn->s_name, buf, MAXPDSTRING);
    else
    {
    	strncpy(buf, fn->s_name, MAXPDSTRING);
    	buf[MAXPDSTRING-1] = 0;
    }
    post("seq: writing %s", fn->s_name);  /* CHECKED arg or not */
    /* save as text for any extension other then ".mid" */
    if ((dotp = strrchr(fn->s_name, '.')) && strcmp(dotp + 1, "mid"))
	seq_textwrite(x, buf);
    else  /* save as mf for ".mid" (FIXME ignore case?) or no extension at all,
	     LATER rethink */
	seq_mfwrite(x, buf);
}

static void seq_readhook(t_pd *z, t_symbol *fn, int ac, t_atom *av)
{
    seq_doread((t_seq *)z, fn, 0);
}

static void seq_writehook(t_pd *z, t_symbol *fn, int ac, t_atom *av)
{
    seq_dowrite((t_seq *)z, fn);
}

static void seq_read(t_seq *x, t_symbol *s)
{
    if (s && s != &s_)
	seq_doread(x, s, 0);
    else  /* CHECKED no default file name */
	/* start in a dir last read from, if any, otherwise in a canvas dir */
	hammerpanel_open(x->x_filehandle, 0);
}

static void seq_write(t_seq *x, t_symbol *s)
{
    if (s && s != &s_)
	seq_dowrite(x, s);
    else  /* CHECKED creation arg is a default file name */
	hammerpanel_save(x->x_filehandle,
			 /* always start in canvas dir */
			 canvas_getdir(x->x_canvas), x->x_defname);
}

static void seq_eventstring(t_seq *x, char *buf, t_seqevent *ep, int editable)
{
    unsigned char *bp = ep->e_bytes;
    int i;
    if (editable)
	sprintf(buf, "%g", ep->e_delta);
    else if (*bp < 128 || *bp == 247)
	sprintf(buf, "(%g)->", ep->e_delta);
    else
	sprintf(buf, "(%g)", ep->e_delta);
    buf += strlen(buf);
    sprintf(buf, " %g", (float)*bp);
    for (i = 0, bp++; i < 3 && *bp != SEQ_EOM; i++, bp++)
    {
	buf += strlen(buf);
	sprintf(buf, " %g", (float)*bp);
    }
}

static void seq_print(t_seq *x)
{
    int nevents = x->x_nevents;
    startpost("midiseq:");  /* CHECKED */
    if (nevents)
    {
	t_seqevent *ep = x->x_sequence;
	char buf[MAXPDSTRING+2];
	int truncated;
	if (nevents > 16)
	    nevents = 16, truncated = 1;
	else
	    truncated = 0;
	endpost();
	while (nevents--)
	{  /* CHECKED bytes are space-separated, no semi */
	    seq_eventstring(x, buf, ep, 0);
	    post(buf);
	    ep++;
	}
	if (truncated) post("...");  /* CHECKED */
    }
    else post(" no sequence");  /* CHECKED */
}

static void seq_editorhook(t_pd *z, t_symbol *s, int ac, t_atom *av)
{
    seq_fromatoms((t_seq *)z, ac, av, 0);
}

static void seq_click(t_seq *x, t_floatarg xpos, t_floatarg ypos,
		      t_floatarg shift, t_floatarg ctrl, t_floatarg alt)
{
    t_seqevent *ep = x->x_sequence;
    int nevents = x->x_nevents;
    char buf[MAXPDSTRING+2];
    hammereditor_open(x->x_filehandle,
		      (x->x_defname && x->x_defname != &s_ ?
		       x->x_defname->s_name : "<anonymous>"), 0);
    while (nevents--)
    {  /* LATER rethink sysex continuation */
	seq_eventstring(x, buf, ep, 1);
	strcat(buf, ";\n");
	hammereditor_append(x->x_filehandle, buf);
	ep++;
    }
    hammereditor_setdirty(x->x_filehandle, 0);
}

static void seq_free(t_seq *x)
{
    if (x->x_clock) clock_free(x->x_clock);
    if (x->x_slaveclock) clock_free(x->x_slaveclock);
    if (x->x_filehandle) hammerfile_free(x->x_filehandle);
    if (x->x_sequence != x->x_seqini)
	freebytes(x->x_sequence, x->x_seqsize * sizeof(*x->x_sequence));
    if (x->x_tempomap != x->x_tempomapini)
	freebytes(x->x_tempomap, x->x_tempomapsize * sizeof(*x->x_tempomap));
}

static void *seq_new(t_symbol *s)
{
    t_seq *x = (t_seq *)pd_new(seq_class);
    static int warned = 0;
    if (fittermax_get() && !warned)
    {
	loud_warning((t_pd *)x, 0, "seq is not ready yet");
	warned = 1;
    }
    x->x_canvas = canvas_getcurrent();
    x->x_filehandle = hammerfile_new((t_pd *)x, 0, seq_readhook, seq_writehook,
				     seq_editorhook);
    x->x_timescale = 1.;
    x->x_newtimescale = 1.;
    x->x_prevtime = 0.;
    x->x_slaveprevtime = 0.;
    x->x_seqsize = SEQ_INISEQSIZE;
    x->x_nevents = 0;
    x->x_sequence = x->x_seqini;
    x->x_tempomapsize = SEQ_INITEMPOMAPSIZE;
    x->x_ntempi = 0;
    x->x_tempomap = x->x_tempomapini;
    outlet_new((t_object *)x, &s_anything);
    x->x_bangout = outlet_new((t_object *)x, &s_bang);
    if (s && s != &s_)
    {
	x->x_defname = s;  /* CHECKME if 'read' changes this */
	seq_doread(x, s, 1);
    }
    else x->x_defname = &s_;
    x->x_clock = clock_new(x, (t_method)seq_clocktick);
    x->x_slaveclock = clock_new(x, (t_method)seq_slaveclocktick);
    return (x);
}

void seq_setup(void)
{
    seq_class = class_new(gensym("seq"),
			  (t_newmethod)seq_new,
			  (t_method)seq_free,
			  sizeof(t_seq), 0,
			  A_DEFSYM, 0);
    class_addbang(seq_class, seq_bang);
    class_addfloat(seq_class, seq_float);
    /* CHECKED symbol rejected */
    class_addsymbol(seq_class, seq_symbol);
    /* CHECKED 1st atom of a list accepted if a float, ignored if a symbol */
    class_addlist(seq_class, seq_list);
    class_addmethod(seq_class, (t_method)seq_record,
		    gensym("record"), 0);
    class_addmethod(seq_class, (t_method)seq_append,
		    gensym("append"), 0);
    class_addmethod(seq_class, (t_method)seq_start,
		    gensym("start"), A_DEFFLOAT, 0);
    class_addmethod(seq_class, (t_method)seq_stop,
		    gensym("stop"), 0);
    class_addmethod(seq_class, (t_method)seq_tick,
		    gensym("tick"), 0);
    class_addmethod(seq_class, (t_method)seq_delay,
		    gensym("delay"), A_FLOAT, 0);  /* CHECKED arg obligatory */
    class_addmethod(seq_class, (t_method)seq_hook,
		    gensym("hook"), A_FLOAT, 0);   /* CHECKED arg obligatory */
    class_addmethod(seq_class, (t_method)seq_read,
		    gensym("read"), A_DEFSYM, 0);
    class_addmethod(seq_class, (t_method)seq_write,
		    gensym("write"), A_DEFSYM, 0);
    class_addmethod(seq_class, (t_method)seq_print,
		    gensym("print"), 0);

    /* incompatible extensions */
    class_addmethod(seq_class, (t_method)seq_pause,
		    gensym("pause"), 0);
    class_addmethod(seq_class, (t_method)seq_continue,
		    gensym("continue"), 0);
    class_addmethod(seq_class, (t_method)seq_goto,
		    gensym("goto"), A_DEFFLOAT, A_DEFFLOAT, 0);
    class_addmethod(seq_class, (t_method)seq_scoretime,
		    gensym("scoretime"), A_SYMBOL, 0);
    class_addmethod(seq_class, (t_method)seq_tempo,
		    gensym("tempo"), A_FLOAT, 0);
    class_addmethod(seq_class, (t_method)seq_cd,
		    gensym("cd"), A_DEFSYM, 0);
    class_addmethod(seq_class, (t_method)seq_pwd,
		    gensym("pwd"), A_SYMBOL, 0);
    class_addmethod(seq_class, (t_method)seq_click,
		    gensym("click"),
		    A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
    hammerfile_setup(seq_class, 0);
    fitter_setup(seq_class, 0);
}