aboutsummaryrefslogtreecommitdiff
path: root/cyclone/hammer/seq.c
diff options
context:
space:
mode:
Diffstat (limited to 'cyclone/hammer/seq.c')
-rw-r--r--cyclone/hammer/seq.c825
1 files changed, 825 insertions, 0 deletions
diff --git a/cyclone/hammer/seq.c b/cyclone/hammer/seq.c
new file mode 100644
index 0000000..503b205
--- /dev/null
+++ b/cyclone/hammer/seq.c
@@ -0,0 +1,825 @@
+/* Copyright (c) 2002-2003 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/sq.h"
+#include "common/bifi.h"
+#include "common/mifi.h"
+#include "hammer/file.h"
+
+#define SEQ_DEBUG
+
+#define SEQ_INISIZE 256 /* LATER rethink */
+#define SEQ_EOM 255 /* end of message marker, LATER rethink */
+
+typedef struct _seqevent
+{
+ int e_delta;
+ unsigned char e_bytes[4];
+} t_seqevent;
+
+typedef struct _seq
+{
+ t_object x_ob;
+ t_canvas *x_canvas;
+ t_symbol *x_defname;
+ t_hammerfile *x_filehandle;
+ int x_isplaying;
+ int x_isrecording;
+ int x_playhead;
+ float x_tempo;
+ double x_prevtime;
+ unsigned char x_status;
+ int x_evesize;
+ int x_expectedsize;
+ int x_size; /* as allocated */
+ int x_nevents; /* as used */
+ t_seqevent *x_sequence;
+ t_seqevent x_seqini[SEQ_INISIZE];
+ t_clock *x_clock;
+ t_outlet *x_bangout;
+} t_seq;
+
+static t_class *seq_class;
+
+static void seq_doclear(t_seq *x, int dofree)
+{
+ if (dofree && x->x_sequence != x->x_seqini)
+ {
+ freebytes(x->x_sequence, x->x_size * sizeof(*x->x_sequence));
+ x->x_sequence = x->x_seqini;
+ x->x_size = SEQ_INISIZE;
+ }
+ x->x_nevents = 0;
+}
+
+static void seq_complete(t_seq *x)
+{
+ if (x->x_evesize < x->x_expectedsize)
+ {
+ /* CHECKED no warning if no data after status byte requiring data */
+ if (x->x_evesize > 1)
+ post("seq: truncated midi message"); /* CHECKED */
+ /* CHECKED nothing stored */
+ }
+ else
+ {
+ t_seqevent *ep = &x->x_sequence[x->x_nevents];
+ double elapsed = clock_gettimesince(x->x_prevtime);
+ ep->e_delta = (int)elapsed;
+ x->x_prevtime = clock_getlogicaltime();
+ if (x->x_evesize < 4)
+ ep->e_bytes[x->x_evesize] = SEQ_EOM;
+ x->x_nevents++;
+ if (x->x_nevents >= x->x_size)
+ {
+ int nexisting = x->x_size;
+ /* store-ahead scheme, LATER consider using x_currevent */
+ int nrequested = x->x_nevents + 1;
+#ifdef SEQ_DEBUG
+ post("growing...");
+#endif
+ x->x_sequence =
+ grow_withdata(&nrequested, &nexisting,
+ &x->x_size, x->x_sequence,
+ SEQ_INISIZE, x->x_seqini, sizeof(*x->x_sequence));
+ if (nrequested <= x->x_nevents)
+ x->x_nevents = 0;
+ }
+ }
+ x->x_evesize = 0;
+}
+
+static void seq_checkstatus(t_seq *x, unsigned char c)
+{
+ if (x->x_status && x->x_evesize > 1) /* LATER rethink */
+ seq_complete(x);
+ if (c < 192)
+ x->x_expectedsize = 3;
+ else if (c < 224)
+ x->x_expectedsize = 2;
+ else if (c < 240)
+ x->x_expectedsize = 3;
+ else if (c < 248)
+ {
+ /* FIXME */
+ x->x_expectedsize = -1;
+ }
+ else
+ {
+ x->x_sequence[x->x_nevents].e_bytes[0] = c;
+ x->x_evesize = x->x_expectedsize = 1;
+ seq_complete(x);
+ return;
+ }
+ x->x_status = x->x_sequence[x->x_nevents].e_bytes[0] = c;
+ x->x_evesize = 1;
+}
+
+static void seq_addbyte(t_seq *x, unsigned char c, int docomplete)
+{
+ x->x_sequence[x->x_nevents].e_bytes[x->x_evesize++] = c;
+ if (x->x_evesize == x->x_expectedsize)
+ {
+ seq_complete(x);
+ if (x->x_status)
+ {
+ x->x_sequence[x->x_nevents].e_bytes[0] = x->x_status;
+ x->x_evesize = 1;
+ }
+ }
+ else if (x->x_evesize == 4)
+ {
+ if (x->x_status != 240)
+ 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_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_isplaying = 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;
+ x->x_isrecording = 0;
+}
+
+static void seq_tick(t_seq *x)
+{
+ if (x->x_isplaying)
+ {
+ 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_isplaying) /* reentrancy protection */
+ return;
+ if (x->x_playhead < x->x_nevents)
+ {
+ ep++;
+ if (ep->e_delta <= 0)
+ /* continue output in the same scheduler event, LATER rethink */
+ {
+ x->x_playhead++;
+ bp = ep->e_bytes;
+ goto nextevent;
+ }
+ else clock_delay(x->x_clock, ep->e_delta);
+ }
+ else
+ {
+ seq_stopplayback(x);
+ /* CHECKED bang sent immediately _after_ last byte */
+ outlet_bang(x->x_bangout); /* LATER think about reentrancy */
+ }
+ }
+}
+
+/* CHECKED running status not used in playback */
+static void seq_dostart(t_seq *x, float tempo)
+{
+ if (x->x_isplaying)
+ {
+ /* CHECKED tempo change */
+ x->x_tempo = tempo;
+ /* FIXME update the clock */
+ }
+ else
+ {
+ if (x->x_isrecording) /* CHECKED 'start' stops recording */
+ seq_stoprecording(x);
+ /* CHECKED bang not sent if a sequence is empty */
+ if (x->x_nevents)
+ {
+ x->x_tempo = tempo;
+ x->x_playhead = 0;
+ x->x_isplaying = 1;
+ /* playback data never sent within the scheduler event of
+ a start message (even for the first delta <= 0), LATER rethink */
+ clock_delay(x->x_clock, x->x_sequence->e_delta);
+ }
+ }
+}
+
+static void seq_bang(t_seq *x)
+{
+ seq_dostart(x, 1.0);
+}
+
+static void seq_float(t_seq *x, t_float f)
+{
+ if (x->x_isrecording)
+ {
+ /* 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' resets recording */
+ if (x->x_isplaying) /* CHECKED 'record' stops playback */
+ seq_stopplayback(x);
+ seq_doclear(x, 0);
+ x->x_isrecording = 1;
+ x->x_prevtime = clock_getlogicaltime();
+ x->x_status = 0;
+ x->x_evesize = 0;
+ x->x_expectedsize = -1; /* LATER rethink */
+}
+
+static void seq_append(t_seq *x)
+{
+ if (x->x_isrecording)
+ return; /* CHECKME 'append' does not reset recording */
+ if (x->x_isplaying) /* CHECKME 'append' stops playback */
+ seq_stopplayback(x);
+ x->x_isrecording = 1;
+ x->x_prevtime = clock_getlogicaltime();
+ x->x_status = 0;
+ x->x_evesize = 0;
+ x->x_expectedsize = -1; /* LATER rethink */
+}
+
+static void seq_stop(t_seq *x)
+{
+ if (x->x_isplaying)
+ seq_stopplayback(x);
+ else if (x->x_isrecording)
+ seq_stoprecording(x);
+}
+
+static int seq_dogrowing(t_seq *x, int nevents)
+{
+ if (nevents > x->x_size)
+ {
+ int nrequested = nevents;
+#ifdef SEQ_DEBUG
+ post("growing...");
+#endif
+ x->x_sequence =
+ grow_nodata(&nrequested, &x->x_size, x->x_sequence,
+ SEQ_INISIZE, x->x_seqini, sizeof(*x->x_sequence));
+ if (nrequested < nevents)
+ {
+ x->x_nevents = 0;
+ return (0);
+ }
+ }
+ x->x_nevents = nevents;
+ return (1);
+}
+
+static int seq_seekhook(t_squiter *it, int offset)
+{
+ t_seq *x = (t_seq *)it->i_owner;
+ post("seek in %d", x->x_nevents);
+ it->i_nelems = x->x_nevents;
+ it->i_sequence = x->x_sequence;
+ if (offset < 0)
+ offset += it->i_nelems;
+ if (offset >= 0 && offset < it->i_nelems)
+ {
+ it->i_element = (t_seqevent *)it->i_sequence + offset;
+ it->i_index = offset;
+ return (1);
+ }
+ else return (0);
+}
+
+static void seq_incrhook(t_squiter *it)
+{
+ ((t_seqevent *)it->i_element)++;
+ it->i_index++;
+}
+
+/* LATER put seq_mfwrite_doit() functionality here */
+static void seq_getevehook(t_squiter *it, t_mifi_event *mev, int *ret)
+{
+ *ret = 1;
+}
+
+static void seq_setevehook(t_squiter *it, t_mifi_event *mev, int *ret)
+{
+ t_seqevent *sev = it->i_element;
+ sev->e_delta = mev->e_delay;
+ sev->e_bytes[0] = mev->e_status | mev->e_channel;
+ sev->e_bytes[1] = mev->e_data[0];
+ if (MIFI_ONE_DATABYTE(mev->e_status))
+ sev->e_bytes[2] = SEQ_EOM;
+ else
+ {
+ sev->e_bytes[2] = mev->e_data[1];
+ sev->e_bytes[3] = SEQ_EOM;
+ }
+ *ret = 1;
+}
+
+static t_float seq_gettimhook(t_squiter *it, int *ret)
+{
+ t_seqevent *sev = it->i_element;
+ *ret = 1;
+ return (sev->e_delta);
+}
+
+static void seq_settimhook(t_squiter *it, t_float f, int *ret)
+{
+ t_seqevent *sev = it->i_element;
+ sev->e_delta = f;
+ *ret = 1;
+}
+
+static t_symbol *seq_gettarhook(t_squiter *it, int *ret)
+{
+ *ret = 1;
+ return (0);
+}
+
+static void seq_settarhook(t_squiter *it, t_symbol *s, int *ret)
+{
+ *ret = 1;
+}
+
+static int seq_make_iterator(t_seq *x, t_mifi_stream *stp)
+{
+ t_squiter *it = squiter_new(stp);
+ if (it)
+ {
+ it->i_owner = x;
+ it->i_nelems = x->x_nevents;
+ it->i_sequence = it->i_element = x->x_sequence;
+ it->i_index = 0;
+ it->i_hooks[SQUITER_SEEKHOOK] = (t_squiterhook)seq_seekhook;
+ it->i_hooks[SQUITER_INCRHOOK] = (t_squiterhook)seq_incrhook;
+ it->i_hooks[SQUITER_GETEVEHOOK] = (t_squiterhook)seq_getevehook;
+ it->i_hooks[SQUITER_SETEVEHOOK] = (t_squiterhook)seq_setevehook;
+ it->i_hooks[SQUITER_GETTIMHOOK] = (t_squiterhook)seq_gettimhook;
+ it->i_hooks[SQUITER_SETTIMHOOK] = (t_squiterhook)seq_settimhook;
+ it->i_hooks[SQUITER_GETTARHOOK] = (t_squiterhook)seq_gettarhook;
+ it->i_hooks[SQUITER_SETTARHOOK] = (t_squiterhook)seq_settarhook;
+ return (1);
+ }
+ else return (0);
+}
+
+static t_mifi_stream *seq_makestream(t_seq *x)
+{
+ t_mifi_stream *stp = 0;
+ if (stp = mifi_stream_new())
+ {
+ if (seq_make_iterator(x, stp))
+ return (stp);
+ else
+ mifi_stream_free(stp);
+ }
+ return (0);
+}
+
+static int seq_comparehook(const void *e1, const void *e2)
+{
+ return (((t_seqevent *)e1)->e_delta > ((t_seqevent *)e2)->e_delta ? 1 : -1);
+}
+
+/* FIXME */
+static int seq_mfread(t_seq *x, char *path)
+{
+ int result = 0;
+ t_mifi_stream *stp = 0;
+ if (!(stp = seq_makestream(x)) ||
+ !mifi_read_start(stp, path, "", 0))
+ goto readfailed;
+#ifdef SEQ_DEBUG
+ if (stp->s_nframes)
+ post("midifile (format %d): %d tracks, %d ticks (%d smpte frames)",
+ stp->s_format, stp->s_hdtracks, stp->s_nticks, stp->s_nframes);
+ else
+ post("midifile (format %d): %d tracks, %d ticks per beat",
+ stp->s_format, stp->s_hdtracks, stp->s_nticks);
+#endif
+ if (mifi_read_analyse(stp) != MIFI_READ_EOF ||
+ !seq_dogrowing(x, stp->s_nevents) ||
+ !mifi_read_restart(stp) ||
+ mifi_read_doit(stp) != MIFI_READ_EOF)
+ goto readfailed;
+ squmpi_sort(stp);
+ qsort(x->x_sequence, stp->s_nevents, sizeof(*x->x_sequence),
+ seq_comparehook);
+ sq_fold_time(stp);
+#ifdef SEQ_DEBUG
+ post("finished reading %d events from midifile", stp->s_nevents);
+#endif
+ result = 1;
+readfailed:
+ if (stp)
+ {
+ mifi_read_end(stp);
+ mifi_stream_free(stp);
+ }
+ return (result);
+}
+
+/* FIXME */
+static int seq_mfwrite_doit(t_seq *x, t_mifi_stream *stp)
+{
+ t_mifi_event *mev = stp->s_auxeve;
+ t_seqevent *sev = x->x_sequence;
+ int nevents = x->x_nevents;
+ while (nevents--)
+ {
+ unsigned char *bp = sev->e_bytes;
+ int i;
+ mev->e_delay = (uint32)(sev->e_delta * stp->s_timecoef);
+ mev->e_status = *bp & 0xf0;
+ mev->e_channel = *bp & 0x0f;
+ /* FIXME sysex continuation */
+ for (i = 0, bp++; i < 3 && *bp != SEQ_EOM; i++, bp++)
+ mev->e_data[i] = *bp;
+ if (!mifi_write_event(stp, mev))
+ return (0);
+ sev++;
+ }
+ return (1);
+}
+
+/* FIXME */
+static int seq_mfwrite(t_seq *x, char *path)
+{
+ int result = 0;
+ t_mifi_stream *stp = 0;
+ if (!(stp = seq_makestream(x)))
+ goto writefailed;
+ stp->s_ntracks = 1;
+ stp->s_hdtracks = 1;
+ stp->s_format = 0;
+ if (!mifi_write_start(stp, path, ""))
+ goto writefailed;
+ mifi_event_settext(stp->s_auxeve, MIFI_META_TRACKNAME, "seq-track");
+ if (!mifi_write_start_track(stp) ||
+ !mifi_write_event(stp, stp->s_auxeve) ||
+ !seq_mfwrite_doit(x, stp) ||
+ !mifi_write_adjust_track(stp, 0))
+ goto writefailed;
+ result = 1;
+writefailed:
+ if (stp)
+ {
+ mifi_write_end(stp);
+ mifi_stream_free(stp);
+ }
+ return (result);
+}
+
+/* FIXME */
+/* CHECKED absolute timestamps, semi-terminated, verified */
+static int seq_frombinbuf(t_seq *x, t_binbuf *bb)
+{
+ int nevents = 0;
+ int ac = binbuf_getnatom(bb);
+ t_atom *av = binbuf_getvec(bb);
+ while (ac--)
+ if (av++->a_type == A_SEMI) /* FIXME parsing */
+ nevents++;
+ if (nevents)
+ {
+ t_seqevent *ep;
+ float prevtime = 0;
+ int i = -1;
+ if (!seq_dogrowing(x, nevents))
+ return (0);
+ nevents = 0;
+ ac = binbuf_getnatom(bb);
+ av = binbuf_getvec(bb);
+ ep = x->x_sequence;
+ while (ac--)
+ {
+ if (av->a_type == A_FLOAT)
+ {
+ if (i < 0)
+ {
+ ep->e_delta = av->a_w.w_float - prevtime;
+ prevtime = 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); /* LATER rethink */
+ }
+ else
+ {
+ int nlines = seq_frombinbuf(x, bb);
+ 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];
+ 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;
+ char path[MAXPDSTRING];
+ sys_bashfilename(buf, path);
+ if (!(fp = fopen(path, "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" 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 */
+ hammerpanel_open(x->x_filehandle);
+}
+
+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 */
+ hammerpanel_save(x->x_filehandle,
+ canvas_getdir(x->x_canvas), x->x_defname);
+}
+
+static void seq_print(t_seq *x)
+{
+ int nevents = x->x_nevents;
+ startpost("midiseq:"); /* CHECKED */
+ if (nevents)
+ {
+ t_seqevent *ep = x->x_sequence;
+ int truncated;
+ if (nevents > 16)
+ nevents = 16, truncated = 1;
+ else
+ truncated = 0;
+ while (nevents--)
+ {
+ unsigned char *bp = ep->e_bytes;
+ int i;
+ if (*bp < 128 || *bp == 247)
+ /* CHECKED (sysex continuation) */
+ startpost("\n(%d)->", ep->e_delta);
+ else
+ startpost("\n(%d)", ep->e_delta);
+ /* CHECKED space-separated, no semi */
+ postfloat((float)*bp);
+ for (i = 0, bp++; i < 3 && *bp != SEQ_EOM; i++, bp++)
+ postfloat((float)*bp);
+ ep++;
+ }
+ endpost();
+ if (truncated) post("..."); /* CHECKED */
+ }
+ else post(" no sequence"); /* CHECKED */
+}
+
+static void seq_free(t_seq *x)
+{
+ if (x->x_clock) clock_free(x->x_clock);
+ hammerfile_free(x->x_filehandle);
+ if (x->x_sequence != x->x_seqini)
+ freebytes(x->x_sequence, x->x_size * sizeof(*x->x_sequence));
+}
+
+static void *seq_new(t_symbol *s)
+{
+ t_seq *x = (t_seq *)pd_new(seq_class);
+ static int warned = 0;
+ if (!warned)
+ {
+ loud_warning((t_pd *)x, "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, 0);
+ x->x_prevtime = 0;
+ x->x_size = SEQ_INISIZE;
+ x->x_nevents = 0;
+ x->x_sequence = x->x_seqini;
+ 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_tick);
+ 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_stop,
+ gensym("stop"), 0);
+ 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);
+ hammerfile_setup(seq_class, 0);
+}