midish-1.4.0004077500017510001751000000000001501104452700114255ustar00alexalexmidish-1.4.0/.gitignore010066400017510001751000000003061501104363400134660ustar00alexalex# CVS default ignores begin tags TAGS .make.state .nse_depinfo *~ #* .#* ,* _$* *$ *.old *.bak *.BAK *.orig *.rej .del-* *.a *.olb *.o *.obj *.so *.exe *.Z *.elc *.ln core # CVS default ignores end midish-1.4.0/Makefile.in010066400017510001751000000114051501104363400135450ustar00alexalex# # extra -l options for respective libraries # RT_LDADD = @rt_ldadd@ ALSA_LDADD = @alsa_ldadd@ SNDIO_LDADD = @sndio_ldadd@ # # extra -I, -L, and -D options # INCLUDE = @include@ LIB = @lib@ DEFS = @defs@ @vars@ # # binaries, documentation, man pages and examples will be installed in # ${BIN_DIR}, ${MAN1_DIR}, ${DOC_DIR} and ${EXAMPLES_DIR} # BIN_DIR = @bindir@ MAN1_DIR = @mandir@/man1 DOC_DIR = @datadir@/doc/midish EXAMPLES_DIR = @datadir@/examples/midish all: midish install: midish mkdir -p ${DESTDIR}${BIN_DIR} ${DESTDIR}${MAN1_DIR} \ ${DESTDIR}${DOC_DIR} ${DESTDIR}${EXAMPLES_DIR} cp midish smfplay smfrec ${DESTDIR}${BIN_DIR} cp midish.1 smfplay.1 smfrec.1 ${DESTDIR}${MAN1_DIR} cp README manual.html ${DESTDIR}${DOC_DIR} cp midishrc sample.msh ${DESTDIR}${EXAMPLES_DIR} uninstall: cd ${DESTDIR}${BIN_DIR} && rm -f midish smfplay smfrec cd ${DESTDIR}${MAN1_DIR} && rm -f midish.1 smfplay.1 smfrec.1 cd ${DESTDIR}${DOC_DIR} && rm -f README manual.html cd ${DESTDIR}${EXAMPLES_DIR} && rm -f midishrc sample.msh check: midish @cd regress && ./run-test *.cmd clean: rm -f -- midish ${OBJS} cd regress && rm -f -- *.tmp1 *.tmp2 *.log *.diff distclean: clean rm -f -- Makefile # ---------------------------------------------------------- dependencies --- OBJS = \ builtin.o cons.o conv.o data.o ev.o exec.o filt.o frame.o help.o \ main.o mdep.o mdep_raw.o mdep_alsa.o mdep_sndio.o metro.o mididev.o \ mixout.o mux.o name.o node.o norm.o parse.o pool.o saveload.o smf.o song.o \ state.o str.o sysex.o textio.o timo.o track.o tty.o undo.o user.o utils.o midish: ${OBJS} ${CC} ${LDFLAGS} ${LIB} -o midish ${OBJS} \ ${RT_LDADD} ${ALSA_LDADD} ${SNDIO_LDADD} .c.o: ${CC} ${CFLAGS} ${INCLUDE} ${DEFS} -c $< builtin.o: builtin.c utils.h defs.h node.h exec.h name.h str.h \ data.h cons.h tty.h frame.h state.h ev.h help.h song.h \ track.h filt.h sysex.h metro.h timo.h user.h smf.h \ saveload.h textio.h mux.h mididev.h norm.h builtin.h \ version.h undo.h cons.o: cons.c utils.h textio.h cons.h tty.h user.h conv.o: conv.c utils.h state.h ev.h defs.h conv.h data.o: data.c utils.h str.h cons.h tty.h data.h ev.o: ev.c utils.h ev.h defs.h str.h cons.h tty.h exec.o: exec.c utils.h exec.h name.h str.h data.h node.h cons.h \ tty.h filt.o: filt.c utils.h ev.h defs.h filt.h pool.h mux.h cons.h \ tty.h frame.o: frame.c utils.h track.h ev.h defs.h filt.h frame.h \ state.h pool.h help.o: help.c help.h main.o: main.c utils.h str.h cons.h tty.h ev.h defs.h mux.h \ track.h frame.h state.h song.h name.h filt.h sysex.h \ metro.h timo.h user.h mididev.h textio.h mdep.o: mdep.c defs.h mux.h mididev.h cons.h tty.h user.h exec.h \ name.h str.h utils.h mdep_alsa.o: mdep_alsa.c utils.h mididev.h str.h mdep_raw.o: mdep_raw.c utils.h cons.h tty.h mididev.h str.h mdep_sndio.o: mdep_sndio.c utils.h cons.h tty.h mididev.h str.h metro.o: metro.c utils.h mux.h metro.h ev.h defs.h timo.h song.h \ name.h str.h track.h frame.h state.h filt.h sysex.h mididev.o: mididev.c utils.h defs.h mididev.h pool.h cons.h tty.h \ str.h ev.h sysex.h mux.h timo.h conv.h mixout.o: mixout.c utils.h ev.h defs.h filt.h pool.h mux.h timo.h \ state.h mux.o: mux.c utils.h ev.h defs.h cons.h tty.h mux.h mididev.h \ sysex.h timo.h state.h conv.h norm.h mixout.h name.o: name.c utils.h name.h str.h node.o: node.c utils.h str.h data.h node.h exec.h name.h cons.h \ tty.h user.h textio.h norm.o: norm.c utils.h ev.h defs.h norm.h pool.h mux.h filt.h \ mixout.h state.h timo.h parse.o: parse.c data.h parse.h node.h utils.h exec.h name.h \ str.h cons.h tty.h pool.o: pool.c utils.h pool.h saveload.o: saveload.c utils.h name.h str.h mididev.h song.h track.h ev.h \ defs.h frame.h state.h filt.h sysex.h metro.h timo.h \ textio.h saveload.h conv.h version.h cons.h tty.h smf.o: smf.c utils.h mididev.h sysex.h track.h ev.h defs.h song.h name.h \ str.h frame.h state.h filt.h metro.h timo.h smf.h cons.h \ tty.h conv.h song.o: song.c utils.h mididev.h mux.h track.h ev.h defs.h \ frame.h state.h filt.h song.h name.h str.h sysex.h \ metro.h timo.h cons.h tty.h mixout.h norm.h undo.h state.o: state.c utils.h pool.h state.h ev.h defs.h str.o: str.c utils.h str.h sysex.o: sysex.c utils.h sysex.h defs.h pool.h textio.o: textio.c utils.h textio.h cons.h tty.h timo.o: timo.c utils.h timo.h track.o: track.c utils.h pool.h track.h ev.h defs.h tty.o: tty.c tty.h utils.h undo.o: undo.c utils.h mididev.h mux.h track.h ev.h defs.h \ frame.h state.h filt.h song.h name.h str.h sysex.h \ metro.h timo.h cons.h tty.h mixout.h norm.h undo.h user.o: user.c utils.h defs.h node.h exec.h name.h str.h data.h \ cons.h tty.h textio.h parse.h mux.h mididev.h track.h \ ev.h song.h frame.h state.h filt.h sysex.h metro.h \ timo.h user.h builtin.h smf.h saveload.h utils.o: utils.c utils.h tty.h midish-1.4.0/README010066400017510001751000000014341501104363400123610ustar00alexalexMidish is an open-source MIDI sequencer/filter for Unix-like operating systems. Implemented as a simple command-line interpreter, it's intended to be lightweight, fast and reliable for real-time performance. Important features: - multiple MIDI devices handling - synchronization to external audio/MIDI hardware/software - real-time MIDI filtering/routing (controller mapping, keyboard splitting, ...) - track recording, metronome - basic track editing (insert, copy, delete, ...) - progressive track quantisation - import and export of standard MIDI files - tempo and time-signature changes - system exclusive messages handling - unlimited undo Contents: - midish - the sequencer/filter - smfplay, smfrec - MIDI file player and recorder See also the user manual (manual.html). midish-1.4.0/builtin.c010066400017510001751000002274371501104363400133300ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "utils.h" #include "defs.h" #include "node.h" #include "exec.h" #include "data.h" #include "cons.h" #include "frame.h" #include "help.h" #include "song.h" #include "user.h" #include "smf.h" #include "saveload.h" #include "textio.h" #include "mux.h" #include "mididev.h" #include "norm.h" #include "builtin.h" #include "version.h" #include "undo.h" unsigned blt_info(struct exec *o, struct data **r) { exec_dumpprocs(o); exec_dumpvars(o); return 1; } unsigned blt_shut(struct exec *o, struct data **r) { unsigned i; struct ev ev; struct mididev *dev; /* * XXX: should raise mode to SONG_IDLE and * use mixout */ if (!song_try_mode(usong, 0)) { return 0; } mux_open(); for (dev = mididev_list; dev != NULL; dev = dev->next) { for (i = 0; i < EV_MAXCH; i++) { ev.cmd = EV_XCTL; ev.dev = dev->unit; ev.ch = i; ev.ctl_num = 121; ev.ctl_val = 0; mux_putev(&ev); ev.cmd = EV_XCTL; ev.dev = dev->unit; ev.ch = i; ev.ctl_num = 123; ev.ctl_val = 0; mux_putev(&ev); ev.cmd = EV_BEND; ev.dev = dev->unit; ev.ch = i; ev.bend_val = EV_BEND_DEFAULT; mux_putev(&ev); } } mux_close(); return 1; } unsigned blt_proclist(struct exec *o, struct data **r) { struct proc *i; struct data *d, *n; d = data_newlist(NULL); PROC_FOREACH(i, o->procs) { if (i->code->vmt == &node_vmt_slist) { n = data_newref(i->name.str); data_listadd(d, n); } } *r = d; return 1; } unsigned blt_builtinlist(struct exec *o, struct data **r) { struct proc *i; struct data *d, *n; d = data_newlist(NULL); PROC_FOREACH(i, o->procs) { if (i->code->vmt == &node_vmt_builtin) { n = data_newref(i->name.str); data_listadd(d, n); } } *r = d; return 1; } unsigned blt_version(struct exec *o, struct data **r) { cons_err(VERSION); return 1; } unsigned blt_panic(struct exec *o, struct data **r) { panic(); /* not reached */ return 0; } unsigned blt_debug(struct exec *o, struct data **r) { extern unsigned filt_debug, mididev_debug, mux_debug, mixout_debug, norm_debug, pool_debug, song_debug, timo_debug; char *flag; long value; if (!exec_lookupname(o, "flag", &flag) || !exec_lookuplong(o, "value", &value)) { return 0; } if (str_eq(flag, "filt")) { filt_debug = value; } else if (str_eq(flag, "mididev")) { mididev_debug = value; } else if (str_eq(flag, "mixout")) { mixout_debug = value; } else if (str_eq(flag, "mux")) { mux_debug = value; } else if (str_eq(flag, "norm")) { norm_debug = value; } else if (str_eq(flag, "pool")) { pool_debug = value; } else if (str_eq(flag, "song")) { song_debug = value; } else if (str_eq(flag, "timo")) { timo_debug = value; } else { cons_errs(o->procname, "unknuwn debug-flag"); return 0; } return 1; } unsigned blt_exec(struct exec *o, struct data **r) { char *filename; if (!exec_lookupstring(o, "filename", &filename)) { return 0; } return exec_runfile(o, filename); } unsigned blt_print(struct exec *o, struct data **r) { struct var *arg; struct data *d; arg = exec_varlookup(o, "..."); if (!arg) { log_puts("blt_print: no variable argument list\n"); return 0; } for (d = arg->data->val.list; d != NULL; d = d->next) data_print(d); textout_putstr(tout, "\n"); *r = data_newnil(); return 1; } unsigned blt_err(struct exec *o, struct data **r) { char *msg; if (!exec_lookupstring(o, "message", &msg)) { return 0; } cons_err(msg); return 0; } unsigned blt_h(struct exec *o, struct data **r) { char *name; struct data *d; struct help *h; struct var *arg; arg = exec_varlookup(o, "..."); if (!arg) { log_puts("blt_h: no variable argument list\n"); return 0; } d = arg->data->val.list; if (d == NULL) { name = "intro"; } else if (d->next == NULL) { if (d->type != DATA_REF) { cons_errs(o->procname, "keyword expected"); return 0; } name = d->val.str; } else { cons_errs(o->procname, "only one keyword expected"); return 0; } h = help_list; while (1) { if (h->key == NULL) { cons_errss(o->procname, name, "no help available"); return 0; } if (str_eq(h->key, name)) break; h++; } help_fmt(h->text); return 1; } unsigned blt_ev(struct exec *o, struct data **r) { struct evspec es; if (!exec_lookupevspec(o, "evspec", &es, 0)) { return 0; } if (!song_try_curev(usong)) { return 0; } usong->curev = es; return 1; } unsigned blt_cc(struct exec *o, struct data **r, int input) { struct songchan *t; struct var *arg; arg = exec_varlookup(o, "channame"); if (!arg) { log_puts("blt_cc: channame: no such param\n"); return 0; } if (arg->data->type == DATA_NIL) { song_setcurchan(usong, NULL, input); return 1; } if (!exec_lookupchan_getref(o, "channame", &t, input)) { return 0; } if (!song_try_curchan(usong, input)) { return 0; } song_setcurchan(usong, t, input); return 1; } unsigned blt_ci(struct exec *o, struct data **r) { return blt_cc(o, r, 1); } unsigned blt_co(struct exec *o, struct data **r) { return blt_cc(o, r, 0); } unsigned blt_getc(struct exec *o, struct data **r, int input) { struct songchan *cur; song_getcurchan(usong, &cur, input); if (cur) { *r = data_newref(cur->name.str); } else { *r = data_newnil(); } return 1; } unsigned blt_geti(struct exec *o, struct data **r) { return blt_getc(o, r, 1); } unsigned blt_geto(struct exec *o, struct data **r) { return blt_getc(o, r, 0); } unsigned blt_cx(struct exec *o, struct data **r) { struct songsx *t; struct var *arg; arg = exec_varlookup(o, "sysexname"); if (!arg) { log_puts("blt_setx: 'sysexname': no such param\n"); return 0; } if (arg->data->type == DATA_NIL) { song_setcursx(usong, NULL); return 1; } if (!exec_lookupsx(o, "sysexname", &t)) { return 0; } if (!song_try_cursx(usong)) { return 0; } song_setcursx(usong, t); return 1; } unsigned blt_getx(struct exec *o, struct data **r) { struct songsx *cur; song_getcursx(usong, &cur); if (cur) { *r = data_newref(cur->name.str); } else { *r = data_newnil(); } return 1; } unsigned blt_setunit(struct exec *o, struct data **r) { long tpu; struct songtrk *t; if (!exec_lookuplong(o, "tics_per_unit", &tpu)) { return 0; } if ((tpu % DEFAULT_TPU) != 0 || tpu < DEFAULT_TPU) { cons_errs(o->procname, "tpu must be multiple of 96 tics"); return 0; } if (tpu > TPU_MAX) { cons_errs(o->procname, "tpu too large"); return 0; } if (!song_try_mode(usong, 0)) { return 0; } undo_track_save(usong, &usong->meta, o->procname, NULL); track_prescale(&usong->meta, usong->tics_per_unit, tpu); undo_track_diff(usong); SONG_FOREACH_TRK(usong, t) { undo_track_save(usong, &t->track, NULL, NULL); track_prescale(&t->track, usong->tics_per_unit, tpu); undo_track_diff(usong); } undo_scale(usong, NULL, NULL, usong->tics_per_unit, tpu); undo_setuint(usong, NULL, NULL, &usong->curquant, usong->curquant * tpu / usong->tics_per_unit); undo_setuint(usong, NULL, NULL, &usong->tics_per_unit, tpu); return 1; } unsigned blt_getunit(struct exec *o, struct data **r) { *r = data_newlong(usong->tics_per_unit); return 1; } unsigned blt_goto(struct exec *o, struct data **r) { long measure; if (!exec_lookuplong(o, "measure", &measure)) { return 0; } if (measure < 0) { cons_errs(o->procname, "measure cant be negative"); return 0; } if (!song_try_curpos(usong)) { return 0; } usong->curpos = measure; if (usong->mode >= SONG_IDLE) song_setmode(usong, SONG_IDLE); song_goto(usong, usong->curpos); mux_flush(); return 1; } unsigned blt_getpos(struct exec *o, struct data **r) { *r = data_newlong(usong->curpos); return 1; } unsigned blt_sel(struct exec *o, struct data **r) { long len; if (!exec_lookuplong(o, "length", &len)) { return 0; } if (len < 0) { cons_errs(o->procname, "'measures' parameter cant be negative"); return 0; } if (!song_try_curlen(usong)) { return 0; } usong->curlen = len; return 1; } unsigned blt_getlen(struct exec *o, struct data **r) { *r = data_newlong(usong->curlen); return 1; } unsigned blt_loop(struct exec *o, struct data **r) { if (!song_try_mode(usong, SONG_IDLE)) { return 0; } usong->loop = 1; return 1; } unsigned blt_noloop(struct exec *o, struct data **r) { if (!song_try_mode(usong, SONG_IDLE)) { return 0; } usong->loop = 0; return 1; } unsigned blt_setq(struct exec *o, struct data **r) { long step; struct var *arg; arg = exec_varlookup(o, "step"); if (!arg) { log_puts("blt_setq: step: no such param\n"); return 0; } if (!song_try_curquant(usong)) { return 0; } if (arg->data->type == DATA_NIL) { usong->curquant = 0; return 1; } else if (arg->data->type == DATA_LONG) { step = arg->data->val.num; if (step < 1 || step > 96) { cons_errs(o->procname, "the step must be in the 1..96 range"); return 0; } if (usong->tics_per_unit % step != 0) { cons_errs(o->procname, "the step must divide the unit note"); return 0; } usong->curquant = usong->tics_per_unit / step; return 1; } cons_errs(o->procname, "the step must nil or integer"); return 0; } unsigned blt_getq(struct exec *o, struct data **r) { if (usong->curquant > 0) *r = data_newlong(usong->tics_per_unit / usong->curquant); else *r = data_newnil(); return 1; } unsigned blt_fac(struct exec *o, struct data **r) { long tpu; if (!exec_lookuplong(o, "tempo_factor", &tpu)) { return 0; } if (tpu < 50 || tpu > 200) { cons_errs(o->procname, "factor must be between 50 and 200"); return 0; } usong->tempo_factor = 0x100 * 100 / tpu; if (mux_isopen) mux_chgtempo(usong->tempo_factor * usong->tempo / 0x100); return 1; } unsigned blt_getfac(struct exec *o, struct data **r) { *r = data_newlong(usong->tempo_factor * 100 / 0x100); return 1; } unsigned blt_ct(struct exec *o, struct data **r) { struct songtrk *t; struct var *arg; arg = exec_varlookup(o, "trackname"); if (!arg) { log_puts("blt_sett: 'trackname': no such param\n"); return 0; } if (arg->data->type == DATA_NIL) { song_setcurtrk(usong, NULL); return 1; } if (!exec_lookuptrack(o, "trackname", &t)) { return 0; } song_setcurtrk(usong, t); song_setcurfilt(usong, t->curfilt); return 1; } unsigned blt_gett(struct exec *o, struct data **r) { struct songtrk *cur; song_getcurtrk(usong, &cur); if (cur) { *r = data_newref(cur->name.str); } else { *r = data_newnil(); } return 1; } unsigned blt_cf(struct exec *o, struct data **r) { struct songfilt *f; struct var *arg; arg = exec_varlookup(o, "filtname"); if (!arg) { log_puts("blt_setf: filtname: no such param\n"); return 0; } if (arg->data->type == DATA_NIL) { f = NULL; } else if (arg->data->type == DATA_REF) { f = song_filtlookup(usong, arg->data->val.ref); if (!f) { cons_errss(o->procname, arg->data->val.ref, "no such filt"); return 0; } } else { cons_errs(o->procname, "bad filter name"); return 0; } song_setcurtrk(usong, NULL); song_setcurfilt(usong, f); return 1; } unsigned blt_getf(struct exec *o, struct data **r) { struct songfilt *cur; song_getcurfilt(usong, &cur); if (cur) { *r = data_newref(cur->name.str); } else { *r = data_newnil(); } return 1; } unsigned blt_mute(struct exec *o, struct data **r) { struct songtrk *t; if (!exec_lookuptrack(o, "trackname", &t)) { return 0; } song_trkmute(usong, t); return 1; } unsigned blt_unmute(struct exec *o, struct data **r) { struct songtrk *t; if (!exec_lookuptrack(o, "trackname", &t)) { return 0; } song_trkunmute(usong, t); return 1; } unsigned blt_getmute(struct exec *o, struct data **r) { struct songtrk *t; if (!exec_lookuptrack(o, "trackname", &t)) { return 0; } *r = data_newlong(t->mute); return 1; } unsigned blt_ls(struct exec *o, struct data **r) { char map[DEFAULT_MAXNCHANS]; struct songtrk *t; struct songchan *c; struct songfilt *f; struct songsx *s; struct sysex *x; unsigned i, count; unsigned dev, ch; /* * print info about channels */ textout_putstr(tout, "outlist {\n"); textout_shiftright(tout); textout_putstr(tout, "# chan_name, {devicenum, midichan}\n"); SONG_FOREACH_CHAN(usong, c) { if (c->isinput) continue; textout_putstr(tout, c->name.str); textout_putstr(tout, "\t"); textout_putstr(tout, "{"); textout_putlong(tout, c->dev); textout_putstr(tout, " "); textout_putlong(tout, c->ch); textout_putstr(tout, "}"); textout_putstr(tout, "\n"); } textout_shiftleft(tout); textout_putstr(tout, "}\n"); textout_putstr(tout, "inlist {\n"); textout_shiftright(tout); textout_putstr(tout, "# chan_name, {devicenum, midichan}\n"); SONG_FOREACH_CHAN(usong, c) { if (!c->isinput) continue; textout_putstr(tout, c->name.str); textout_putstr(tout, "\t"); textout_putstr(tout, "{"); textout_putlong(tout, c->dev); textout_putstr(tout, " "); textout_putlong(tout, c->ch); textout_putstr(tout, "}"); textout_putstr(tout, "\n"); } textout_shiftleft(tout); textout_putstr(tout, "}\n"); /* * print info about filters */ textout_putstr(tout, "filtlist {\n"); textout_shiftright(tout); textout_putstr(tout, "# filter_name\n"); SONG_FOREACH_FILT(usong, f) { textout_putstr(tout, f->name.str); textout_putstr(tout, "\n"); /* * XXX: add info about input and output sets */ } textout_shiftleft(tout); textout_putstr(tout, "}\n"); /* * print info about tracks */ textout_putstr(tout, "tracklist {\n"); textout_shiftright(tout); textout_putstr(tout, "# track_name, default_filter, used_channels, flags\n"); SONG_FOREACH_TRK(usong, t) { textout_putstr(tout, t->name.str); textout_putstr(tout, "\t"); if (t->curfilt != NULL) { textout_putstr(tout, t->curfilt->name.str); } else { textout_putstr(tout, "nil"); } textout_putstr(tout, "\t{"); track_chanmap(&t->track, map); for (i = 0, count = 0; i < DEFAULT_MAXNCHANS; i++) { if (map[i]) { if (count) { textout_putstr(tout, " "); } dev = i / 16; ch = i % 16; c = song_chanlookup_bynum(usong, dev, ch, 0); if (c) { textout_putstr(tout, c->name.str); } else { textout_putstr(tout, "{"); textout_putlong(tout, dev); textout_putstr(tout, " "); textout_putlong(tout, ch); textout_putstr(tout, "}"); } count++; } } textout_putstr(tout, "}"); if (t->mute) { textout_putstr(tout, " mute"); } textout_putstr(tout, "\n"); } textout_shiftleft(tout); textout_putstr(tout, "}\n"); /* * print info about sysex banks */ textout_putstr(tout, "sysexlist {\n"); textout_shiftright(tout); textout_putstr(tout, "# sysex_name, number_messages\n"); SONG_FOREACH_SX(usong, s) { textout_putstr(tout, s->name.str); textout_putstr(tout, "\t"); i = 0; for (x = s->sx.first; x != NULL; x = x->next) { i++; } textout_putlong(tout, i); textout_putstr(tout, "\n"); } textout_shiftleft(tout); textout_putstr(tout, "}\n"); /* * print current values */ textout_putstr(tout, "curout "); song_getcurchan(usong, &c, 0); if (c) { textout_putstr(tout, c->name.str); } else { textout_putstr(tout, "nil"); } textout_putstr(tout, "\n"); textout_putstr(tout, "curin "); song_getcurchan(usong, &c, 1); if (c) { textout_putstr(tout, c->name.str); } else { textout_putstr(tout, "nil"); } textout_putstr(tout, "\n"); textout_putstr(tout, "curfilt "); song_getcurfilt(usong, &f); if (f) { textout_putstr(tout, f->name.str); } else { textout_putstr(tout, "nil"); } textout_putstr(tout, "\n"); textout_putstr(tout, "curtrack "); song_getcurtrk(usong, &t); if (t) { textout_putstr(tout, t->name.str); } else { textout_putstr(tout, "nil"); } textout_putstr(tout, "\n"); textout_putstr(tout, "cursysex "); song_getcursx(usong, &s); if (s) { textout_putstr(tout, s->name.str); } else { textout_putstr(tout, "nil"); } textout_putstr(tout, "\n"); textout_putstr(tout, "curquant "); if (usong->curquant > 0) { textout_putlong(tout, usong->tics_per_unit / usong->curquant); } else { textout_putstr(tout, "nil"); } textout_putstr(tout, "\n"); textout_putstr(tout, "curev "); evspec_output(&usong->curev, tout); textout_putstr(tout, "\n"); textout_putstr(tout, "curpos "); textout_putlong(tout, usong->curpos); textout_putstr(tout, "\n"); textout_putstr(tout, "curlen "); textout_putlong(tout, usong->curlen); textout_putstr(tout, "\n"); textout_putstr(tout, "tap "); textout_putstr(tout, song_tap_modestr[usong->tap_mode]); textout_putstr(tout, "\n"); textout_putstr(tout, "tapev "); evspec_output(&usong->tap_evspec, tout); textout_putstr(tout, "\n"); return 1; } unsigned blt_save(struct exec *o, struct data **r) { char *filename; if (!exec_lookupstring(o, "filename", &filename)) { return 0; } song_stop(usong); song_save(usong, filename); return 1; } unsigned blt_load(struct exec *o, struct data **r) { struct song *newsong; char *filename; unsigned res; if (!exec_lookupstring(o, "filename", &filename)) { return 0; } song_stop(usong); newsong = song_new(); res = song_load(newsong, filename); if (res) { song_delete(usong); usong = newsong; cons_putpos(usong->curpos, 0, 0); } else song_delete(newsong); return res; } unsigned blt_reset(struct exec *o, struct data **r) { song_stop(usong); song_done(usong); evpat_reset(); song_init(usong); cons_putpos(usong->curpos, 0, 0); return 1; } unsigned blt_export(struct exec *o, struct data **r) { char *filename; if (!exec_lookupstring(o, "filename", &filename)) { return 0; } song_stop(usong); return song_exportsmf(usong, filename); } unsigned blt_import(struct exec *o, struct data **r) { char *filename; struct song *sng; if (!exec_lookupstring(o, "filename", &filename)) { return 0; } song_stop(usong); sng = song_importsmf(filename); if (sng == NULL) { return 0; } song_delete(usong); usong = sng; cons_putpos(usong->curpos, 0, 0); return 1; } unsigned blt_idle(struct exec *o, struct data **r) { song_idle(usong); if (user_flag_batch) { cons_err("press ^C to stop idling"); while (mux_mdep_wait(0)) ; /* nothing */ cons_err("idling stopped"); song_stop(usong); } return 1; } unsigned blt_play(struct exec *o, struct data **r) { if (usong->tap_mode && (mididev_clksrc || mididev_mtcsrc)) { cons_errs(o->procname, "can't start in tap mode with external clock"); return 0; } song_play(usong); if (user_flag_batch) { cons_err("press ^C to stop playback"); while (!usong->complete && mux_mdep_wait(0)) ; /* nothing */ cons_err("playback stopped"); song_stop(usong); } return 1; } unsigned blt_rec(struct exec *o, struct data **r) { if (usong->tap_mode && (mididev_clksrc || mididev_mtcsrc)) { cons_errs(o->procname, "can't start in tap mode with external clock"); return 0; } song_record(usong); if (user_flag_batch) { cons_err("press ^C to stop recording"); while (mux_mdep_wait(0)) ; /* nothing */ cons_err("recording stopped"); song_stop(usong); } return 1; } unsigned blt_stop(struct exec *o, struct data **r) { song_stop(usong); return 1; } unsigned blt_tempo(struct exec *o, struct data **r) { long tempo; if (!exec_lookuplong(o, "beats_per_minute", &tempo)) { return 0; } if (tempo < 40 || tempo > 240) { cons_errs(o->procname, "tempo must be between 40 and 240 beats per measure"); return 0; } if (!song_try_meta(usong)) { return 0; } undo_track_save(usong, &usong->meta, o->procname, NULL); track_settempo(&usong->meta, usong->curpos, tempo); undo_track_diff(usong); return 1; } unsigned blt_mins(struct exec *o, struct data **r) { long amount; struct data *sig; unsigned tic, len, qstep; unsigned bpm, tpb, obpm, otpb; unsigned long usec24; struct songtrk *t; struct seqptr *sp; struct track tn; struct ev ev; if (!exec_lookuplong(o, "amount", &amount) || !exec_lookuplist(o, "sig", &sig)) { return 0; } track_timeinfo(&usong->meta, usong->curpos, &tic, &usec24, &obpm, &otpb); if (sig == NULL) { bpm = obpm; tpb = otpb; } else if (sig->type == DATA_LONG && sig->next != NULL && sig->next->type == DATA_LONG && sig->next->next == NULL) { bpm = sig->val.num; sig = sig->next; if (sig->val.num != 1 && sig->val.num != 2 && sig->val.num != 4 && sig->val.num != 8) { cons_errs(o->procname, "denominator must be 1, 2, 4 or 8"); return 0; } tpb = usong->tics_per_unit / sig->val.num; } else { cons_errs(o->procname, "signature must be {num denom} or {} list"); return 0; } if (!song_try_meta(usong)) { return 0; } len = amount * bpm * tpb; track_init(&tn); sp = seqptr_new(&tn); seqptr_ticput(sp, tic); ev.cmd = EV_TIMESIG; ev.timesig_beats = bpm; ev.timesig_tics = tpb; seqptr_evput(sp, &ev); if (tic == 0) { /* dont remove initial tempo */ ev.cmd = EV_TEMPO; ev.tempo_usec24 = usec24; seqptr_evput(sp, &ev); } seqptr_ticput(sp, len); ev.cmd = EV_TIMESIG; ev.timesig_beats = obpm; ev.timesig_tics = otpb; seqptr_evput(sp, &ev); seqptr_del(sp); undo_track_save(usong, &usong->meta, o->procname, NULL); track_ins(&usong->meta, tic, len); track_merge(&usong->meta, &tn); undo_track_diff(usong); track_done(&tn); qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } SONG_FOREACH_TRK(usong, t) { undo_track_save(usong, &t->track, NULL, NULL); track_ins(&t->track, tic, len); undo_track_diff(usong); } usong->curlen += amount; return 1; } unsigned blt_mdup(struct exec *o, struct data **r) { long where; unsigned stic, etic, wtic, qstep; unsigned sbpm, stpb, wbpm, wtpb; unsigned long usec24; struct songtrk *t; struct seqptr *sp; struct track paste, copy; struct ev ev; if (!exec_lookuplong(o, "where", &where)) { return 0; } if (where >= 0) where = usong->curpos + usong->curlen + where; else { where = usong->curpos - where; if (where < 0) where = 0; } if (!song_try_meta(usong)) { return 0; } track_timeinfo(&usong->meta, usong->curpos, &stic, NULL, &sbpm, &stpb); track_timeinfo(&usong->meta, usong->curpos + usong->curlen, &etic, NULL, NULL, NULL); track_timeinfo(&usong->meta, where, &wtic, &usec24, &wbpm, &wtpb); /* * backup and restore time signature, * possibly the tempo */ track_init(&paste); sp = seqptr_new(&paste); seqptr_ticput(sp, wtic); ev.cmd = EV_TIMESIG; ev.timesig_beats = sbpm; ev.timesig_tics = stpb; seqptr_evput(sp, &ev); if (wtic == 0) { /* dont remove initial tempo */ ev.cmd = EV_TEMPO; ev.tempo_usec24 = usec24; seqptr_evput(sp, &ev); } seqptr_ticput(sp, etic - stic); ev.cmd = EV_TIMESIG; ev.timesig_beats = wbpm; ev.timesig_tics = wtpb; seqptr_evput(sp, &ev); seqptr_del(sp); /* * copy and shift the portion to duplicate */ track_init(©); track_move(&usong->meta, stic, etic - stic, NULL, ©, 1, 0); track_shift(©, wtic); track_merge(&paste, ©); track_done(©); /* * insert space and merge the duplicated portion */ undo_track_save(usong, &usong->meta, o->procname, NULL); track_ins(&usong->meta, wtic, etic - stic); track_merge(&usong->meta, &paste); undo_track_diff(usong); track_done(&paste); qstep = usong->curquant / 2; if (stic > qstep && wtic > qstep) { stic -= qstep; wtic -= qstep; etic -= qstep; } SONG_FOREACH_TRK(usong, t) { track_init(&paste); track_move(&t->track, stic, etic - stic, NULL, &paste, 1, 0); track_shift(&paste, wtic); undo_track_save(usong, &t->track, NULL, NULL); track_ins(&t->track, wtic, etic - stic); track_merge(&t->track, &paste); undo_track_diff(usong); track_done(&paste); } return 1; } unsigned blt_mcut(struct exec *o, struct data **r) { unsigned bpm, tpb, stic, etic, qstep; unsigned long usec24; struct songtrk *t; struct seqptr *sp; struct track paste; struct ev ev; struct track t1, t2; if (!song_try_mode(usong, 0)) { return 0; } track_timeinfo(&usong->meta, usong->curpos, &stic, NULL, NULL, NULL); track_timeinfo(&usong->meta, usong->curpos + usong->curlen, &etic, &usec24, &bpm, &tpb); track_init(&paste); sp = seqptr_new(&paste); seqptr_ticput(sp, stic); ev.cmd = EV_TIMESIG; ev.timesig_beats = bpm; ev.timesig_tics = tpb; seqptr_evput(sp, &ev); if (stic == 0) { /* dont remove initial tempo */ ev.cmd = EV_TEMPO; ev.tempo_usec24 = usec24; seqptr_evput(sp, &ev); } seqptr_del(sp); track_init(&t1); track_init(&t2); undo_track_save(usong, &usong->meta, o->procname, NULL); track_move(&usong->meta, 0, stic, NULL, &t1, 1, 1); track_move(&usong->meta, etic, ~0U, NULL, &t2, 1, 1); track_shift(&t2, stic); track_clear(&usong->meta); track_merge(&usong->meta, &t1); track_merge(&usong->meta, &paste); if (!track_isempty(&t2)) { track_merge(&usong->meta, &t2); } undo_track_diff(usong); track_done(&t1); track_done(&t2); qstep = usong->curquant / 2; if (stic > qstep) { stic -= qstep; etic -= qstep; } SONG_FOREACH_TRK(usong, t) { undo_track_save(usong, &t->track, NULL, NULL); track_cut(&t->track, stic, etic - stic); undo_track_diff(usong); } usong->curlen = 0; return 1; } unsigned blt_minfo(struct exec *o, struct data **r) { struct seqptr *mp; unsigned meas, tpb, otpb, bpm, obpm; unsigned long tempo1, otempo1, tempo2, otempo2; int stop = 0; textout_putstr(tout, "{\n"); textout_shiftright(tout); textout_putstr(tout, "# meas\tsig\ttempo\n"); otpb = 0; obpm = 0; otempo1 = otempo2 = 0; meas = 0; mp = seqptr_new(&usong->meta); while (!stop) { /* * scan for a time signature change */ while (seqptr_evget(mp)) { /* nothing */ } seqptr_getsign(mp, &bpm, &tpb); seqptr_gettempo(mp, &tempo1); if (seqptr_skip(mp, tpb * bpm) > 0) stop = 1; seqptr_gettempo(mp, &tempo2); if (tpb != otpb || bpm != obpm || tempo1 != otempo1 || tempo2 != otempo2) { otpb = tpb; obpm = bpm; otempo1 = tempo1; otempo2 = tempo2; textout_putlong(tout, meas); textout_putstr(tout, "\t{"); textout_putlong(tout, bpm); textout_putstr(tout, " "); textout_putlong(tout, usong->tics_per_unit / tpb); textout_putstr(tout, "}\t"); textout_putlong(tout, 60L * 24000000L / (tempo1 * tpb)); if (tempo2 != tempo1) { textout_putstr(tout, "\t# - "); textout_putlong(tout, 60L * 24000000L / (tempo2 * tpb)); } textout_putstr(tout, "\n"); } meas++; } seqptr_del(mp); textout_shiftleft(tout); textout_putstr(tout, "}\n"); return 1; } unsigned blt_mtempo(struct exec *o, struct data **r) { unsigned tic, bpm, tpb; unsigned long usec24; track_timeinfo(&usong->meta, usong->curpos, &tic, &usec24, &bpm, &tpb); *r = data_newlong(60L * 24000000L / (usec24 * tpb)); return 1; } unsigned blt_msig(struct exec *o, struct data **r) { unsigned tic, bpm, tpb; unsigned long usec24; track_timeinfo(&usong->meta, usong->curpos, &tic, &usec24, &bpm, &tpb); *r = data_newlist(NULL); data_listadd(*r, data_newlong(bpm)); data_listadd(*r, data_newlong(usong->tics_per_unit / tpb)); return 1; } unsigned blt_mend(struct exec *o, struct data **r) { *r = data_newlong(song_endpos(usong)); return 1; } unsigned blt_ctlconf_any(struct exec *o, struct data **r, int isfine) { char *name; unsigned num, old, val; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupname(o, "name", &name) || !exec_lookupctl(o, "ctl", &num) || !exec_lookupval(o, "defval", isfine, &val)) { return 0; } if (evctl_lookup(name, &old)) { evctl_unconf(old); } evctl_unconf(num); if (!isfine && val != EV_UNDEF) val <<= 7; evctl_conf(num, name, val); return 1; } unsigned blt_ctlconf(struct exec *o, struct data **r) { return blt_ctlconf_any(o, r, 0); } unsigned blt_ctlconfx(struct exec *o, struct data **r) { return blt_ctlconf_any(o, r, 1); } unsigned blt_ctlunconf(struct exec *o, struct data **r) { char *name; unsigned num; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupname(o, "name", &name)) { return 0; } if (!evctl_lookup(name, &num)) { cons_errss(o->procname, name, "no such controller"); return 0; } evctl_unconf(num); return 1; } unsigned blt_ctlinfo(struct exec *o, struct data **r) { evctltab_output(evctl_tab, tout); return 1; } unsigned blt_evpat(struct exec *o, struct data **r) { struct data *byte; struct var *arg; char *name, *ref; unsigned char *pattern; unsigned spec, size, cmd; if (!song_try_mode(usong, 0)) return 0; if (!exec_lookupname(o, "name", &ref)) return 0; if (evpat_lookup(ref, &cmd)) { if (!song_try_ev(usong, cmd)) return 0; evpat_unconf(cmd); } for (cmd = 0; cmd < EV_NUMCMD; cmd++) { if (evinfo[cmd].ev && str_eq(evinfo[cmd].ev, ref)) { cons_errs(o->procname, "name already in use"); return 0; } } for (cmd = EV_PAT0;; cmd++) { if (cmd == EV_PAT0 + EV_NPAT) { cons_errs(o->procname, "too many sysex patterns"); return 0; } if (evinfo[cmd].ev == NULL) break; } name = str_new(ref); pattern = xmalloc(EV_PATSIZE, "evpat"); arg = exec_varlookup(o, "pattern"); if (!arg) { log_puts("exec_lookupev: no such var\n"); panic(); } if (arg->data->type != DATA_LIST) { cons_errs(o->procname, "data must be a list"); goto err1; } size = 0; for (byte = arg->data->val.list; byte != NULL; byte = byte->next) { if (size == EV_PATSIZE) { cons_errs(o->procname, "pattern too long"); goto err1; } if (byte->type == DATA_LONG) { if (byte->val.num < 0 || byte->val.num > 0xff) { cons_errs(o->procname, "out of range byte in sysex pattern"); goto err1; } pattern[size++] = byte->val.num; } else if (byte->type == DATA_REF) { if (str_eq(byte->val.ref, "v0") || str_eq(byte->val.ref, "v0_hi")) { spec = EV_PATV0_HI; } else if (str_eq(byte->val.ref, "v0_lo")) { spec = EV_PATV0_LO; } else if (str_eq(byte->val.ref, "v1") || str_eq(byte->val.ref, "v1_hi")) { spec = EV_PATV1_HI; } else if (str_eq(byte->val.ref, "v1_lo")) { spec = EV_PATV1_LO; } else { cons_errs(o->procname, "bad atom in sysex pattern"); goto err1; } pattern[size++] = spec; } else { cons_errs(o->procname, "bad pattern"); goto err1; } } if (!evpat_set(cmd, name, pattern, size)) goto err1; return 1; err1: xfree(pattern); str_delete(name); return 0; } unsigned blt_evinfo(struct exec *o, struct data **r) { evpat_output(tout); return 1; } unsigned blt_metro(struct exec *o, struct data **r) { char *mstr; unsigned mask; if (!exec_lookupname(o, "onoff", &mstr)) { return 0; } if (!metro_str2mask(&usong->metro, mstr, &mask)) { cons_errs(o->procname, "mode must be 'on', 'off' or 'rec'"); return 0; } metro_setmask(&usong->metro, mask); return 1; } unsigned blt_metrocf(struct exec *o, struct data **r) { struct ev evhi, evlo; if (!exec_lookupev(o, "eventhi", &evhi, 0) || !exec_lookupev(o, "eventlo", &evlo, 0)) { return 0; } if (evhi.cmd != EV_NON && evlo.cmd != EV_NON) { cons_errs(o->procname, "note-on event expected"); return 0; } metro_shut(&usong->metro); usong->metro.hi = evhi; usong->metro.lo = evlo; return 1; } unsigned blt_tap(struct exec *o, struct data **r) { char *mstr; int mode; if (!exec_lookupname(o, "mode", &mstr)) { return 0; } if (str_eq(mstr, "off")) { mode = 0; } else if (str_eq(mstr, "start")) { mode = SONG_TAP_START; } else if (str_eq(mstr, "tempo")) { mode = SONG_TAP_TEMPO; } else { cons_errs(o->procname, "mode must be 'off', 'start' or 'tempo'"); return 0; } usong->tap_mode = mode; return 1; } unsigned blt_tapev(struct exec *o, struct data **r) { struct evspec es; if (!exec_lookupevspec(o, "evspec", &es, 0)) { return 0; } usong->tap_evspec = es; return 1; } unsigned blt_undo(struct exec *o, struct data **r) { if (!song_try_mode(usong, 0)) return 0; undo_pop(usong); return 1; } unsigned blt_undolist(struct exec *o, struct data **r) { struct undo *u; for (u = usong->undo; u != NULL; u = u->next) { if (u->func == NULL) continue; textout_putstr(tout, u->func); if (u->name != NULL) { textout_putstr(tout, " "); textout_putstr(tout, u->name); if (u->type == UNDO_EMPTY && (u->next == NULL || u->next->name != NULL)) textout_putstr(tout, " (no-op)"); } textout_putstr(tout, "\n"); } return 1; } unsigned blt_tlist(struct exec *o, struct data **r) { struct data *d, *n; struct songtrk *i; d = data_newlist(NULL); SONG_FOREACH_TRK(usong, i) { n = data_newref(i->name.str); data_listadd(d, n); } *r = d; return 1; } unsigned blt_tnew(struct exec *o, struct data **r) { char *tren; struct songtrk *t; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupname(o, "trackname", &tren)) { return 0; } t = song_trklookup(usong, tren); if (t != NULL) { cons_errs(o->procname, "track already exists"); return 0; } t = undo_tnew_do(usong, o->procname, tren); return 1; } unsigned blt_tdel(struct exec *o, struct data **r) { struct songtrk *t; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } undo_tdel_do(usong, t, o->procname); return 1; } unsigned blt_tren(struct exec *o, struct data **r) { char *name; struct songtrk *t; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!exec_lookupname(o, "newname", &name)) { return 0; } if (song_trklookup(usong, name)) { cons_errs(o->procname, "name already used by another track"); return 0; } undo_setstr(usong, o->procname, &t->name.str, name); return 1; } unsigned blt_texists(struct exec *o, struct data **r) { char *name; struct songtrk *t; if (!exec_lookupname(o, "trackname", &name)) { return 0; } t = song_trklookup(usong, name); *r = data_newlong(t != NULL ? 1 : 0); return 1; } unsigned blt_taddev(struct exec *o, struct data **r) { long measure, beat, tic; struct ev ev; struct seqptr *tp; struct songtrk *t; unsigned pos, bpm, tpb; if (!exec_lookuplong(o, "measure", &measure) || !exec_lookuplong(o, "beat", &beat) || !exec_lookuplong(o, "tic", &tic) || !exec_lookupev(o, "event", &ev, 0)) { return 0; } song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } track_timeinfo(&usong->meta, measure, &pos, NULL, &bpm, &tpb); if (beat < 0 || (unsigned)beat >= bpm || tic < 0 || (unsigned)tic >= tpb) { cons_errs(o->procname, "beat/tick must fit in the measure"); return 0; } undo_track_save(usong, &t->track, o->procname, t->name.str); pos += beat * tpb + tic; tp = seqptr_new(&t->track); seqptr_seek(tp, pos); seqptr_evput(tp, &ev); seqptr_del(tp); undo_track_diff(usong); return 1; } unsigned blt_tsetf(struct exec *o, struct data **r) { struct songtrk *t; struct songfilt *f; struct var *arg; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } arg = exec_varlookup(o, "filtname"); if (!arg) { log_puts("blt_tsetf: filtname: no such param\n"); return 0; } if (arg->data->type == DATA_NIL) { f = NULL; } else if (arg->data->type == DATA_REF) { f = song_filtlookup(usong, arg->data->val.ref); if (!f) { cons_errs(o->procname, "no such filt"); return 0; } } else { cons_errs(o->procname, "bad filt name"); return 0; } t->curfilt = f; song_setcurfilt(usong, f); return 1; } unsigned blt_tgetf(struct exec *o, struct data **r) { struct songtrk *t; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (t->curfilt) { *r = data_newref(t->curfilt->name.str); } else { *r = data_newnil(); } return 1; } unsigned blt_tcheck(struct exec *o, struct data **r) { struct songtrk *t; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_check(&t->track); undo_track_diff(usong); return 1; } unsigned blt_trewrite(struct exec *o, struct data **r) { struct songtrk *t; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_rewrite(&t->track); undo_track_diff(usong); return 1; } unsigned blt_tcut(struct exec *o, struct data **r) { unsigned tic, len, qstep; struct songtrk *t; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + usong->curlen) - tic; qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_cut(&t->track, tic, len); usong->curlen = 0; undo_track_diff(usong); return 1; } unsigned blt_tins(struct exec *o, struct data **r) { unsigned tic, len, qstep; struct songtrk *t; long amount; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!exec_lookuplong(o, "amount", &amount)) { return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + amount) - tic; qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_ins(&t->track, tic, len); usong->curlen += amount; undo_track_diff(usong); return 1; } unsigned blt_tclr(struct exec *o, struct data **r) { struct songtrk *t; unsigned tic, len, qstep; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + usong->curlen) - tic; qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } else if (tic + len > qstep) { len -= qstep; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_move(&t->track, tic, len, &usong->curev, NULL, 0, 1); undo_track_diff(usong); return 1; } unsigned blt_tpaste(struct exec *o, struct data **r) { struct songtrk *t; struct track copy; unsigned tic, tic2, qstep; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = CLIP_OFFS; tic2 = track_findmeasure(&usong->meta, usong->curpos); qstep = usong->curquant / 2; if (tic > qstep && tic2 > qstep) { tic -= qstep; tic2 -= qstep; } track_init(©); track_move(&usong->clip, tic, ~0U, &usong->curev, ©, 1, 0); if (!track_isempty(©)) { copy.first->delta += tic2; undo_track_save(usong, &t->track, o->procname, t->name.str); track_merge(&t->track, ©); undo_track_diff(usong); } track_done(©); return 1; } unsigned blt_tcopy(struct exec *o, struct data **r) { struct songtrk *t; unsigned tic, len, tic2, qstep; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + usong->curlen) - tic; tic2 = CLIP_OFFS; qstep = usong->curquant / 2; if (tic > qstep && tic2 > qstep) { tic -= qstep; tic2 -= qstep; } else if (tic + len > qstep) { len -= qstep; } track_clear(&usong->clip); track_move(&t->track, tic, len, &usong->curev, &usong->clip, 1, 0); track_shift(&usong->clip, tic2); return 1; } unsigned blt_tmerge(struct exec *o, struct data **r) { struct songtrk *src, *dst; if (!exec_lookuptrack(o, "source", &src)) { return 0; } song_getcurtrk(usong, &dst); if (dst == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!song_try_trk(usong, dst)) { return 0; } undo_track_save(usong, &dst->track, o->procname, dst->name.str); track_merge(&src->track, &dst->track); undo_track_diff(usong); return 1; } unsigned blt_tquant_common(struct exec *o, struct data **r, int all) { struct songtrk *t; unsigned tic, len, qstep, offset; long rate; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!exec_lookuplong(o, "rate", &rate)) { return 0; } if (rate > 100) { cons_errs(o->procname, "rate must be between 0 and 100"); return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + usong->curlen) - tic; qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; offset = qstep; } else { offset = 0; if (tic + len > qstep) len -= qstep; } undo_track_save(usong, &t->track, o->procname, t->name.str); if (all) { track_quantize(&t->track, &usong->curev, tic, len, offset, 2 * qstep, rate); } else { track_quantize_frame(&t->track, &usong->curev, tic, len, offset, 2 * qstep, rate); } undo_track_diff(usong); return 1; } unsigned blt_tquant(struct exec *o, struct data **r) { cons_err("warning, tquant is deprecated, use tquanta instead"); return blt_tquant_common(o, r, 1); } unsigned blt_tquanta(struct exec *o, struct data **r) { return blt_tquant_common(o, r, 1); } unsigned blt_tquantf(struct exec *o, struct data **r) { return blt_tquant_common(o, r, 0); } unsigned blt_ttransp(struct exec *o, struct data **r) { struct songtrk *t; long halftones; unsigned tic, len, qstep; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!exec_lookuplong(o, "halftones", &halftones)) { return 0; } if (halftones < -64 || halftones >= 63) { cons_errs(o->procname, "argument not in the -64..63 range"); return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + usong->curlen) - tic; qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } else if (tic + len > qstep) { len -= qstep; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_transpose(&t->track, tic, len, &usong->curev, halftones); undo_track_diff(usong); return 1; } unsigned blt_tvcurve(struct exec *o, struct data **r) { struct songtrk *t; unsigned tic, len, qstep; long weight; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!exec_lookuplong(o, "weight", &weight)) return 0; if (weight < -63 || weight > 63) { cons_errs(o->procname, "weight must be in the -63..63 range"); return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + usong->curlen) - tic; qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } else if (tic + len > qstep) { len -= qstep; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_vcurve(&t->track, tic, len, &usong->curev, weight); undo_track_diff(usong); return 1; } unsigned blt_tevmap(struct exec *o, struct data **r) { struct songtrk *t; struct evspec from, to; unsigned tic, len, qstep; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } if (!exec_lookupevspec(o, "from", &from, 0) || !exec_lookupevspec(o, "to", &to, 0)) { return 0; } if (!song_try_trk(usong, t)) { return 0; } tic = track_findmeasure(&usong->meta, usong->curpos); len = track_findmeasure(&usong->meta, usong->curpos + usong->curlen) - tic; qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } else if (tic + len > qstep) { len -= qstep; } undo_track_save(usong, &t->track, o->procname, t->name.str); track_evmap(&t->track, tic, len, &usong->curev, &from, &to); undo_track_diff(usong); return 1; } unsigned blt_tclist(struct exec *o, struct data **r) { struct songtrk *t; struct songchan *c; struct data *num; char map[DEFAULT_MAXNCHANS]; unsigned i; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } *r = data_newlist(NULL); track_chanmap(&t->track, map); for (i = 0; i < DEFAULT_MAXNCHANS; i++) { if (map[i]) { c = song_chanlookup_bynum(usong, i / 16, i % 16, 0); if (c != 0) { data_listadd(*r, data_newref(c->name.str)); } else { num = data_newlist(NULL); data_listadd(num, data_newlong(i / 16)); data_listadd(num, data_newlong(i % 16)); data_listadd(*r, num); } } } return 1; } unsigned blt_tinfo(struct exec *o, struct data **r) { struct songtrk *t; struct seqptr *mp, *tp; struct state *st; unsigned len, count, count_next, tpb, bpm, m; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } textout_putstr(tout, "{\n"); textout_shiftright(tout); m = 0; count_next = 0; tp = seqptr_new(&t->track); mp = seqptr_new(&usong->meta); for (;;) { /* * scan for a time signature change */ while (seqptr_evget(mp)) { /* nothing */ } seqptr_getsign(mp, &bpm, &tpb); /* * count starting events */ len = bpm * tpb; count = count_next; count_next = 0; for (;;) { len -= seqptr_ticskip(tp, len); if (len == 0) break; st = seqptr_evget(tp); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) { if (state_inspec(st, &usong->curev)) { if (len >= usong->curquant / 2) count++; else count_next++; } } } if (m >= usong->curpos && m < usong->curpos + usong->curlen) { textout_putlong(tout, count); textout_putstr(tout, " "); if (len > 0) { if (len < usong->curquant / 2) { textout_putlong(tout, count_next); textout_putstr(tout, " "); } } } if (len > 0) break; (void)seqptr_skip(mp, bpm * tpb); m++; } seqptr_del(mp); seqptr_del(tp); textout_putstr(tout, "\n"); textout_shiftleft(tout); textout_putstr(tout, "}\n"); return 1; } void blt_tdump_output(struct state *s, unsigned meas, unsigned beat, unsigned tick, struct textout *f) { char posbuf[32]; struct seqev *se; unsigned phase; unsigned delta; int more = 0; se = s->pos; phase = ev_phase(&se->ev); delta = 0; for (;;) { if (ev_match(&se->ev, &s->ev)) { if (more) { snprintf(posbuf, sizeof(posbuf), "%10u", delta); } else { snprintf(posbuf, sizeof(posbuf), "%04u:%02u:%02u", meas, beat, tick); more = 1; } textout_putstr(tout, posbuf); textout_putstr(f, " "); ev_output(&se->ev, f); textout_putstr(f, "\n"); phase = ev_phase(&se->ev); if (phase & EV_PHASE_LAST) break; } se = se->next; delta += se->delta; } } unsigned blt_tdump(struct exec *o, struct data **r) { struct state *s; struct songtrk *t; struct seqptr *mp, *tp; unsigned meas, beat, tick, tpb, bpm; unsigned mdelta, tdelta; unsigned abspos, start, end, qstep; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } start = track_findmeasure(&usong->meta, usong->curpos); end = track_findmeasure(&usong->meta, usong->curpos + usong->curlen); qstep = usong->curquant / 2; if (start > qstep) start -= qstep; abspos = meas = beat = tick = 0; mp = seqptr_new(&usong->meta); tp = seqptr_new(&t->track); textout_putstr(tout, "{\n"); textout_shiftright(tout); while (1) { /* * scan for a time signature change */ while (seqptr_evget(mp)) ; /* nothing */ while (1) { s = seqptr_evget(tp); if (s == NULL) break; if (!(s->phase & EV_PHASE_FIRST)) continue; if (state_inspec(s, &usong->curev) && abspos >= start && abspos < end) blt_tdump_output(s, meas, beat, tick, tout); } /* * move to the next non empty tick: the next tic is the * smaller position of the next event of each track */ tdelta = tp->pos->delta - tp->delta; if (tdelta == 0) break; mdelta = mp->pos->delta - mp->delta; if (mdelta > 0) { if (mdelta < tdelta) tdelta = mdelta; seqptr_ticskip(mp, tdelta); } seqptr_ticskip(tp, tdelta); abspos += tdelta; seqptr_getsign(mp, &bpm, &tpb); tick += tdelta; beat += tick / tpb; tick = tick % tpb; meas += beat / bpm; beat = beat % bpm; } textout_shiftleft(tout); textout_putstr(tout, "}\n"); seqptr_del(tp); seqptr_del(mp); return 1; } unsigned blt_clist(struct exec *o, struct data **r, int input) { struct data *d, *n; struct songchan *i; d = data_newlist(NULL); SONG_FOREACH_CHAN(usong, i) { if (!!i->isinput != !!input) continue; n = data_newref(i->name.str); data_listadd(d, n); } *r = d; return 1; } unsigned blt_ilist(struct exec *o, struct data **r) { return blt_clist(o, r, 1); } unsigned blt_olist(struct exec *o, struct data **r) { return blt_clist(o, r, 0); } unsigned blt_cexists(struct exec *o, struct data **r, int input) { struct songchan *c; char *name; if (!exec_lookupname(o, "channame", &name)) { return 0; } c = song_chanlookup(usong, name, input); *r = data_newlong(c != NULL ? 1 : 0); return 1; } unsigned blt_iexists(struct exec *o, struct data **r) { return blt_cexists(o, r, 1); } unsigned blt_oexists(struct exec *o, struct data **r) { return blt_cexists(o, r, 0); } unsigned blt_cnew(struct exec *o, struct data **r, int input) { char *name; struct songchan *c; unsigned dev, ch; if (!exec_lookupname(o, "channame", &name) || !exec_lookupchan_getnum(o, "channum", &dev, &ch, input)) { return 0; } c = song_chanlookup(usong, name, input); if (c != NULL) { cons_errss(o->procname, name, "already exists"); return 0; } c = song_chanlookup_bynum(usong, dev, ch, input); if (c != NULL) { cons_errs(o->procname, "dev/chan number already in use"); return 0; } if (dev > EV_MAXDEV || ch > EV_MAXCH) { cons_errs(o->procname, "dev/chan number out of bounds"); return 0; } if (!song_try_curchan(usong, input)) { return 0; } undo_cnew_do(usong, dev, ch, input, o->procname, name); return 1; } unsigned blt_inew(struct exec *o, struct data **r) { return blt_cnew(o, r, 1); } unsigned blt_onew(struct exec *o, struct data **r) { return blt_cnew(o, r, 0); } unsigned blt_cdel(struct exec *o, struct data **r, int input) { struct songchan *c; song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } if (!song_try_chan(usong, c, input)) { return 0; } undo_cdel_do(usong, c, o->procname); return 1; } unsigned blt_idel(struct exec *o, struct data **r) { return blt_cdel(o, r, 1); } unsigned blt_odel(struct exec *o, struct data **r) { return blt_cdel(o, r, 0); } unsigned blt_cren(struct exec *o, struct data **r, int input) { struct songchan *c; char *name; song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } if (!exec_lookupname(o, "newname", &name)) { return 0; } if (song_chanlookup(usong, name, input)) { cons_errss(o->procname, name, "channel name already in use"); return 0; } if (c->filt && song_filtlookup(usong, name)) { cons_errss(o->procname, name, "filt name already in use"); return 0; } undo_setstr(usong, o->procname, &c->name.str, name); if (c->filt) undo_setstr(usong, NULL, &c->filt->name.str, name); return 1; } unsigned blt_iren(struct exec *o, struct data **r) { return blt_cren(o, r, 1); } unsigned blt_oren(struct exec *o, struct data **r) { return blt_cren(o, r, 0); } unsigned blt_cset(struct exec *o, struct data **r, int input) { struct evspec from, to; struct songchan *c, *i; struct songfilt *f; unsigned dev, ch; song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } if (!exec_lookupchan_getnum(o, "channum", &dev, &ch, input)) { return 0; } i = song_chanlookup_bynum(usong, dev, ch, input); if (i != NULL) { cons_errs(o->procname, "dev/chan number already used"); return 0; } if (!song_try_chan(usong, c, input)) { return 0; } SONG_FOREACH_FILT(usong, f) { if (!song_try_filt(usong, f)) return 0; } evspec_reset(&from); from.dev_min = from.dev_max = c->dev; from.ch_min = from.ch_max = c->ch; evspec_reset(&to); to.dev_min = to.dev_max = dev; to.ch_min = to.ch_max = ch; undo_setuint(usong, o->procname, c->name.str, &c->dev, dev); undo_setuint(usong, NULL, c->name.str, &c->ch, ch); SONG_FOREACH_FILT(usong, f) { undo_filt_save(usong, &f->filt, NULL, f->name.str); if (input) filt_chgin(&f->filt, &from, &to, 0); else filt_chgout(&f->filt, &from, &to, 0); } undo_track_save(usong, &c->conf, NULL, c->name.str); track_setchan(&c->conf, dev, ch); undo_track_diff(usong); return 1; } unsigned blt_iset(struct exec *o, struct data **r) { return blt_cset(o, r, 1); } unsigned blt_oset(struct exec *o, struct data **r) { return blt_cset(o, r, 0); } unsigned blt_cgetc(struct exec *o, struct data **r, int input) { struct songchan *c; song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } *r = data_newlong(c->ch); return 1; } unsigned blt_igetc(struct exec *o, struct data **r) { return blt_cgetc(o, r, 1); } unsigned blt_ogetc(struct exec *o, struct data **r) { return blt_cgetc(o, r, 0); } unsigned blt_cgetd(struct exec *o, struct data **r, int input) { struct songchan *c; song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } *r = data_newlong(c->dev); return 1; } unsigned blt_igetd(struct exec *o, struct data **r) { return blt_cgetd(o, r, 1); } unsigned blt_ogetd(struct exec *o, struct data **r) { return blt_cgetd(o, r, 0); } unsigned blt_caddev(struct exec *o, struct data **r, int input) { struct songchan *c; struct ev ev; song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } if (!exec_lookupev(o, "event", &ev, input)) { return 0; } if (!EV_ISVOICE(&ev)) { cons_errs(o->procname, "only voice events can be added to channels"); return 0; } if (ev.ch != c->ch || ev.dev != c->dev) { cons_errs(o->procname, "event device or channel mismatch channel ones"); return 0; } if (ev_phase(&ev) != (EV_PHASE_FIRST | EV_PHASE_LAST)) { cons_errs(o->procname, "event must be stateless"); return 0; } undo_track_save(usong, &c->conf, o->procname, c->name.str); song_confev(usong, c, &ev); undo_track_diff(usong); return 1; } unsigned blt_iaddev(struct exec *o, struct data **r) { return blt_caddev(o, r, 1); } unsigned blt_oaddev(struct exec *o, struct data **r) { return blt_caddev(o, r, 0); } unsigned blt_crmev(struct exec *o, struct data **r, int input) { struct songchan *c; struct evspec es; if (!song_try_mode(usong, 0)) { return 0; } song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } if (!exec_lookupevspec(o, "evspec", &es, input)) { return 0; } undo_track_save(usong, &c->conf, o->procname, c->name.str); song_unconfev(usong, c, &es); undo_track_diff(usong); return 1; } unsigned blt_irmev(struct exec *o, struct data **r) { return blt_crmev(o, r, 1); } unsigned blt_ormev(struct exec *o, struct data **r) { return blt_crmev(o, r, 0); } unsigned blt_cinfo(struct exec *o, struct data **r, int input) { struct songchan *c; song_getcurchan(usong, &c, input); if (c == NULL) { cons_errs(o->procname, "no current chan"); return 0; } track_output(&c->conf, tout); textout_putstr(tout, "\n"); return 1; } unsigned blt_iinfo(struct exec *o, struct data **r) { return blt_cinfo(o, r, 1); } unsigned blt_oinfo(struct exec *o, struct data **r) { return blt_cinfo(o, r, 0); } unsigned blt_flist(struct exec *o, struct data **r) { struct data *d, *n; struct songfilt *i; d = data_newlist(NULL); SONG_FOREACH_FILT(usong, i) { n = data_newref(i->name.str); data_listadd(d, n); } *r = d; return 1; } unsigned blt_fnew(struct exec *o, struct data **r) { char *name; struct songfilt *i; if (!exec_lookupname(o, "filtname", &name)) { return 0; } i = song_filtlookup(usong, name); if (i != NULL) { cons_errs(o->procname, "filt already exists"); return 0; } undo_fnew_do(usong, o->procname, name); return 1; } unsigned blt_fdel(struct exec *o, struct data **r) { struct songfilt *f; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } undo_fdel_do(usong, f, o->procname); return 1; } unsigned blt_fren(struct exec *o, struct data **r) { struct songfilt *f; struct songchan *c; char *name; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } if (!exec_lookupname(o, "newname", &name)) { return 0; } if (song_filtlookup(usong, name)) { cons_errss(o->procname, name, "filt name already in use"); return 0; } SONG_FOREACH_CHAN(usong, c) { if (c->filt == f) { cons_errss(o->procname, name, "rename channel to rename this filter"); return 0; } } undo_setstr(usong, o->procname, &f->name.str, name); return 1; } unsigned blt_fexists(struct exec *o, struct data **r) { char *name; struct songfilt *f; if (!exec_lookupname(o, "filtname", &name)) { return 0; } f = song_filtlookup(usong, name); *r = data_newlong(f != NULL ? 1 : 0); return 1; } unsigned blt_finfo(struct exec *o, struct data **r) { struct songfilt *f; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } filt_output(&f->filt, tout); textout_putstr(tout, "\n"); return 1; } unsigned blt_freset(struct exec *o, struct data **r) { struct songfilt *f; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } if (mux_isopen) norm_shut(); undo_filt_save(usong, &f->filt, o->procname, f->name.str); filt_reset(&f->filt); return 1; } unsigned blt_fmap(struct exec *o, struct data **r) { struct songfilt *f; struct evspec from, to; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } if (!exec_lookupevspec(o, "from", &from, 1) || !exec_lookupevspec(o, "to", &to, 0)) { return 0; } if (mux_isopen) norm_shut(); undo_filt_save(usong, &f->filt, o->procname, f->name.str); filt_mapnew(&f->filt, &from, &to); return 1; } unsigned blt_funmap(struct exec *o, struct data **r) { struct songfilt *f; struct evspec from, to; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } if (!exec_lookupevspec(o, "from", &from, 1) || !exec_lookupevspec(o, "to", &to, 0)) { return 0; } if (mux_isopen) norm_shut(); undo_filt_save(usong, &f->filt, o->procname, f->name.str); filt_mapdel(&f->filt, &from, &to); return 1; } unsigned blt_ftransp(struct exec *o, struct data **r) { struct songfilt *f; struct evspec es; long plus; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } if (!exec_lookupevspec(o, "evspec", &es, 0) || !exec_lookuplong(o, "plus", &plus)) { return 0; } if (plus < -63 || plus > 63) { cons_errs(o->procname, "plus must be in the -63..63 range"); return 0; } if ((es.cmd != EVSPEC_ANY && es.cmd != EVSPEC_NOTE) || (es.cmd == EVSPEC_NOTE && (es.v0_min != 0 || es.v0_max != EV_MAXCOARSE))) { cons_errs(o->procname, "set must contain full range notes"); return 0; } if (mux_isopen) norm_shut(); undo_filt_save(usong, &f->filt, o->procname, f->name.str); filt_transp(&f->filt, &es, plus); return 1; } unsigned blt_fvcurve(struct exec *o, struct data **r) { struct songfilt *f; struct evspec es; long weight; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } if (!exec_lookupevspec(o, "evspec", &es, 0) || !exec_lookuplong(o, "weight", &weight)) { return 0; } if (weight < -63 || weight > 63) { cons_errs(o->procname, "plus must be in the -63..63 range"); return 0; } if (es.cmd != EVSPEC_ANY && es.cmd != EVSPEC_NOTE) { cons_errs(o->procname, "set must contain notes"); return 0; } if (mux_isopen) norm_shut(); undo_filt_save(usong, &f->filt, o->procname, f->name.str); filt_vcurve(&f->filt, &es, weight); return 1; } unsigned blt_fchgxxx(struct exec *o, struct data **r, int input, int swap) { struct songfilt *f; struct evspec from, to; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } if (!exec_lookupevspec(o, "from", &from, input) || !exec_lookupevspec(o, "to", &to, input)) { return 0; } if (evspec_isec(&from, &to)) { cons_errs(o->procname, "\"from\" and \"to\" event ranges must be disjoint"); } if (mux_isopen) norm_shut(); undo_filt_save(usong, &f->filt, o->procname, f->name.str); if (input) filt_chgin(&f->filt, &from, &to, swap); else filt_chgout(&f->filt, &from, &to, swap); return 1; } unsigned blt_fchgin(struct exec *o, struct data **r) { return blt_fchgxxx(o, r, 1, 0); } unsigned blt_fchgout(struct exec *o, struct data **r) { return blt_fchgxxx(o, r, 0, 0); } unsigned blt_fswapin(struct exec *o, struct data **r) { return blt_fchgxxx(o, r, 1, 1); } unsigned blt_fswapout(struct exec *o, struct data **r) { return blt_fchgxxx(o, r, 0, 1); } unsigned blt_xlist(struct exec *o, struct data **r) { struct data *d, *n; struct songsx *i; d = data_newlist(NULL); SONG_FOREACH_SX(usong, i) { n = data_newref(i->name.str); data_listadd(d, n); } *r = d; return 1; } unsigned blt_xexists(struct exec *o, struct data **r) { char *name; struct songsx *c; if (!exec_lookupname(o, "sysexname", &name)) { return 0; } c = song_sxlookup(usong, name); *r = data_newlong(c != NULL ? 1 : 0); return 1; } unsigned blt_xnew(struct exec *o, struct data **r) { char *name; struct songsx *c; if (!exec_lookupname(o, "sysexname", &name)) { return 0; } c = song_sxlookup(usong, name); if (c != NULL) { cons_errs(o->procname, "sysex already exists"); return 0; } if (!song_try_cursx(usong)) { return 0; } c = undo_xnew_do(usong, o->procname, name); return 1; } unsigned blt_xdel(struct exec *o, struct data **r) { struct songsx *c; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } if (!song_try_sx(usong, c)) { return 0; } undo_xdel_do(usong, o->procname, c); return 1; } unsigned blt_xren(struct exec *o, struct data **r) { struct songsx *c; char *name; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } if (!exec_lookupname(o, "newname", &name)) { return 0; } if (song_sxlookup(usong, name)) { cons_errss(o->procname, name, "already in use"); return 0; } if (!song_try_sx(usong, c)) { return 0; } undo_setstr(usong, o->procname, &c->name.str, name); return 1; } unsigned blt_xinfo(struct exec *o, struct data **r) { struct songsx *c; struct sysex *e; unsigned i; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } textout_putstr(tout, "{\n"); textout_shiftright(tout); for (e = c->sx.first; e != NULL; e = e->next) { textout_putlong(tout, e->unit); textout_putstr(tout, " { "); if (e->first) { for (i = 0; i < e->first->used; i++) { if (i > 10) { textout_putstr(tout, "... "); break; } textout_putbyte(tout, e->first->data[i]); textout_putstr(tout, " "); } } textout_putstr(tout, "}\n"); } textout_shiftleft(tout); textout_putstr(tout, "}\n"); return 1; } unsigned blt_xrm(struct exec *o, struct data **r) { struct songsx *c; struct sysex *x, *xnext; struct data *d; unsigned int pos, match; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } if (!exec_lookuplist(o, "data", &d)) { return 0; } if (!song_try_sx(usong, c)) { return 0; } pos = 0; undo_start(usong, o->procname, c->name.str); for (x = c->sx.first; x != NULL; x = xnext) { xnext = x->next; if (!data_matchsysex(d, x, &match)) return 0; if (match) undo_xrm_do(usong, NULL, c, pos); else pos++; } return 1; } unsigned blt_xsetd(struct exec *o, struct data **r) { struct songsx *c; struct sysex *x; struct data *d; unsigned int match; long unit; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } if (!exec_lookuplong(o, "devnum", &unit) || !exec_lookuplist(o, "data", &d)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS) { cons_errs(o->procname, "devnum out of range"); return 0; } if (!song_try_sx(usong, c)) { return 0; } undo_start(usong, o->procname, c->name.str); for (x = c->sx.first; x != NULL; x = x->next) { if (!data_matchsysex(d, x, &match)) { return 0; } if (match) { undo_setuint(usong, o->procname, c->name.str, &x->unit, unit); } } return 1; } unsigned blt_xadd(struct exec *o, struct data **r) { struct songsx *c; struct sysex *x; struct data *byte; struct var *arg; long unit; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } if (!exec_lookuplong(o, "devnum", &unit)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS) { cons_errs(o->procname, "devnum out of range"); return 0; } arg = exec_varlookup(o, "data"); if (!arg) { log_puts("exec_lookupev: no such var\n"); panic(); } if (arg->data->type != DATA_LIST) { cons_errs(o->procname, "data must be a list of numbers"); return 0; } if (!song_try_sx(usong, c)) { return 0; } x = sysex_new(unit); for (byte = arg->data->val.list; byte != 0; byte = byte->next) { if (byte->type != DATA_LONG) { cons_errs(o->procname, "only bytes allowed as data"); sysex_del(x); return 0; } if (byte->val.num < 0 || byte->val.num > 0xff) { cons_errs(o->procname, "data out of range"); sysex_del(x); return 0; } sysex_add(x, byte->val.num); } if (!sysex_check(x)) { cons_errs(o->procname, "bad sysex format"); sysex_del(x); return 0; } if (x->first) { undo_xadd_do(usong, o->procname, c, x); } else { sysex_del(x); } return 1; } unsigned blt_ximport(struct exec *o, struct data **r) { struct songsx *c; struct var *arg; char *path; long unit; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } if (!exec_lookuplong(o, "devnum", &unit)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS) { cons_errs(o->procname, "devnum out of range"); return 0; } arg = exec_varlookup(o, "path"); if (!arg) { log_puts("blt_ximport: path: no such param\n"); return 0; } if (arg->data->type != DATA_STRING) { cons_errs(o->procname, "path must be string"); return 0; } path = arg->data->val.str; if (!syx_import(path, &c->sx, unit)) return 0; return 1; } unsigned blt_xexport(struct exec *o, struct data **r) { struct songsx *c; struct var *arg; char *path; song_getcursx(usong, &c); if (c == NULL) { cons_errs(o->procname, "no current sysex"); return 0; } arg = exec_varlookup(o, "path"); if (!arg) { log_puts("blt_xexport: path: no such param\n"); return 0; } if (arg->data->type != DATA_STRING) { cons_errs(o->procname, "path must be string"); return 0; } path = arg->data->val.str; if (!syx_export(path, &c->sx)) return 0; return 1; } unsigned blt_dlist(struct exec *o, struct data **r) { struct data *d, *n; struct mididev *i; d = data_newlist(NULL); for (i = mididev_list; i != NULL; i = i->next) { n = data_newlong(i->unit); data_listadd(d, n); } *r = d; return 1; } unsigned blt_dnew(struct exec *o, struct data **r) { long unit; char *path, *modename; struct var *arg; unsigned mode; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplong(o, "devnum", &unit) || !exec_lookupname(o, "mode", &modename)) { return 0; } arg = exec_varlookup(o, "path"); if (!arg) { log_puts("blt_dnew: path: no such param\n"); return 0; } if (arg->data->type == DATA_NIL) { path = NULL; } else if (arg->data->type == DATA_STRING) { path = arg->data->val.str; } else { cons_errs(o->procname, "path must be string or nil"); return 0; } if (str_eq(modename, "ro")) { mode = MIDIDEV_MODE_IN; } else if (str_eq(modename, "wo")) { mode = MIDIDEV_MODE_OUT; } else if (str_eq(modename, "rw")) { mode = MIDIDEV_MODE_IN | MIDIDEV_MODE_OUT; } else { cons_errss(o->procname, modename, "bad mode (allowed: ro, wo, rw)"); return 0; } return mididev_attach(unit, path, mode); } unsigned blt_ddel(struct exec *o, struct data **r) { long unit; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplong(o, "devnum", &unit)) { return 0; } if (mididev_mtcsrc == mididev_byunit[unit]) mididev_mtcsrc = NULL; if (mididev_clksrc == mididev_byunit[unit]) mididev_clksrc = NULL; return mididev_detach(unit); } unsigned blt_dmtcrx(struct exec *o, struct data **r) { struct var *arg; long unit; if (!song_try_mode(usong, 0)) { return 0; } arg = exec_varlookup(o, "devnum"); if (!arg) { log_puts("blt_dmtcrx: no such var\n"); panic(); } if (arg->data->type == DATA_NIL) { mididev_mtcsrc = NULL; return 0; } else if (arg->data->type == DATA_LONG) { unit = arg->data->val.num; if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } mididev_mtcsrc = mididev_byunit[unit]; mididev_clksrc = NULL; return 1; } cons_errs(o->procname, "bad argument type for 'unit'"); return 0; } unsigned blt_dmmctx(struct exec *o, struct data **r) { struct data *units, *n; unsigned i, tx[DEFAULT_MAXNDEVS]; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplist(o, "devlist", &units)) { return 0; } for (i = 0; i < DEFAULT_MAXNDEVS; i++) tx[i] = 0; for (n = units; n != NULL; n = n->next) { if (n->type != DATA_LONG || n->val.num < 0 || n->val.num >= DEFAULT_MAXNDEVS || !mididev_byunit[n->val.num]) { cons_errs(o->procname, "bad device number"); return 0; } tx[n->val.num] = 1; } for (i = 0; i < DEFAULT_MAXNDEVS; i++) { if (mididev_byunit[i]) mididev_byunit[i]->sendmmc = tx[i]; } return 1; } unsigned blt_dclkrx(struct exec *o, struct data **r) { struct var *arg; long unit; if (!song_try_mode(usong, 0)) { return 0; } arg = exec_varlookup(o, "devnum"); if (!arg) { log_puts("blt_dclkrx: no such var\n"); panic(); } if (arg->data->type == DATA_NIL) { mididev_clksrc = NULL; return 0; } else if (arg->data->type == DATA_LONG) { unit = arg->data->val.num; if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } mididev_clksrc = mididev_byunit[unit]; mididev_mtcsrc = NULL; return 1; } cons_errs(o->procname, "bad argument type for 'unit'"); return 0; } unsigned blt_dclktx(struct exec *o, struct data **r) { struct data *units, *n; unsigned i, tx[DEFAULT_MAXNDEVS]; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplist(o, "devlist", &units)) { return 0; } for (i = 0; i < DEFAULT_MAXNDEVS; i++) tx[i] = 0; for (n = units; n != NULL; n = n->next) { if (n->type != DATA_LONG || n->val.num < 0 || n->val.num >= DEFAULT_MAXNDEVS || !mididev_byunit[n->val.num]) { cons_errs(o->procname, "bad device number"); return 0; } tx[n->val.num] = 1; } for (i = 0; i < DEFAULT_MAXNDEVS; i++) { if (mididev_byunit[i]) mididev_byunit[i]->sendclk = tx[i]; } return 1; } unsigned blt_dclkrate(struct exec *o, struct data **r) { long unit, tpu; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplong(o, "devnum", &unit) || !exec_lookuplong(o, "tics_per_unit", &tpu)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } if (tpu < DEFAULT_TPU || (tpu % DEFAULT_TPU)) { cons_errs(o->procname, "device tpu must be multiple of 96"); return 0; } mididev_byunit[unit]->ticrate = tpu; return 1; } unsigned blt_dinfo(struct exec *o, struct data **r) { struct mididev *dev; long unit; int i, more; if (!exec_lookuplong(o, "devnum", &unit)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } dev = mididev_byunit[unit]; textout_putstr(tout, "{\n"); textout_shiftright(tout); textout_putstr(tout, "devnum "); textout_putlong(tout, unit); textout_putstr(tout, "\n"); if (mididev_mtcsrc == dev) { textout_putstr(tout, "mtcrx\t\t\t# master MTC source\n"); } if (dev->sendmmc) { textout_putstr(tout, "mmctx\t\t\t# sends MMC messages\n"); } if (mididev_clksrc == dev) { textout_putstr(tout, "clkrx\t\t\t# master clock source\n"); } if (dev->sendclk) { textout_putstr(tout, "clktx\t\t\t# sends clock ticks\n"); } textout_putstr(tout, "ixctl {"); for (i = 0, more = 0; i < 32; i++) { if (dev->ixctlset & (1 << i)) { if (more) textout_putstr(tout, " "); textout_putlong(tout, i); more = 1; } } textout_putstr(tout, "}\n"); textout_putstr(tout, "oxctl {"); for (i = 0, more = 0; i < 32; i++) { if (dev->oxctlset & (1 << i)) { if (more) textout_putstr(tout, " "); textout_putlong(tout, i); more = 1; } } textout_putstr(tout, "}\n"); textout_putstr(tout, "iev {"); for (i = 0, more = 0; i < EV_NUMCMD; i++) { if (dev->ievset & (1 << i)) { if (more) textout_putstr(tout, " "); textout_putstr(tout, evinfo[i].ev); more = 1; } } textout_putstr(tout, "}\n"); textout_putstr(tout, "oev {"); for (i = 0, more = 0; i < EV_NUMCMD; i++) { if (dev->oevset & (1 << i)) { if (more) textout_putstr(tout, " "); textout_putstr(tout, evinfo[i].ev); more = 1; } } textout_putstr(tout, "}\n"); textout_putstr(tout, "clkrate "); textout_putlong(tout, mididev_byunit[unit]->ticrate); textout_putstr(tout, "\n"); textout_shiftleft(tout); textout_putstr(tout, "}\n"); return 1; } unsigned blt_dixctl(struct exec *o, struct data **r) { long unit; struct data *list; unsigned ctlset; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplong(o, "devnum", &unit) || !exec_lookuplist(o, "ctlset", &list)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } if (!data_getctlset(list, &ctlset)) { return 0; } mididev_byunit[unit]->ixctlset = ctlset; return 1; } unsigned blt_doxctl(struct exec *o, struct data **r) { long unit; struct data *list; unsigned ctlset; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplong(o, "devnum", &unit) || !exec_lookuplist(o, "ctlset", &list)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } if (!data_getctlset(list, &ctlset)) { return 0; } mididev_byunit[unit]->oxctlset = ctlset; return 1; } unsigned blt_diev(struct exec *o, struct data **r) { long unit; struct data *list; unsigned flags; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplong(o, "devnum", &unit) || !exec_lookuplist(o, "flags", &list)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } if (!data_getxev(list, &flags)) { return 0; } mididev_byunit[unit]->ievset = flags; return 1; } unsigned blt_doev(struct exec *o, struct data **r) { long unit; struct data *list; unsigned flags; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookuplong(o, "devnum", &unit) || !exec_lookuplist(o, "flags", &list)) { return 0; } if (unit < 0 || unit >= DEFAULT_MAXNDEVS || !mididev_byunit[unit]) { cons_errs(o->procname, "bad device number"); return 0; } if (!data_getxev(list, &flags)) { return 0; } mididev_byunit[unit]->oevset = flags; return 1; } midish-1.4.0/builtin.h010066400017510001751000000210331501104363400133150ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_BUILTIN_H #define MIDISH_BUILTIN_H struct exec; struct data; unsigned blt_info(struct exec *, struct data **); unsigned blt_shut(struct exec *, struct data **); unsigned blt_proclist(struct exec *, struct data **); unsigned blt_builtinlist(struct exec *, struct data **); unsigned blt_version(struct exec *, struct data **); unsigned blt_panic(struct exec *, struct data **); unsigned blt_debug(struct exec *, struct data **); unsigned blt_exec(struct exec *, struct data **); unsigned blt_print(struct exec *, struct data **); unsigned blt_err(struct exec *, struct data **); unsigned blt_h(struct exec *, struct data **); unsigned blt_ev(struct exec *, struct data **); unsigned blt_ci(struct exec *, struct data **); unsigned blt_geti(struct exec *, struct data **); unsigned blt_co(struct exec *, struct data **); unsigned blt_geto(struct exec *, struct data **); unsigned blt_cx(struct exec *, struct data **); unsigned blt_getx(struct exec *, struct data **); unsigned blt_setunit(struct exec *, struct data **); unsigned blt_getunit(struct exec *, struct data **); unsigned blt_loop(struct exec *, struct data **); unsigned blt_noloop(struct exec *, struct data **); unsigned blt_goto(struct exec *, struct data **); unsigned blt_getpos(struct exec *, struct data **); unsigned blt_sel(struct exec *, struct data **); unsigned blt_getlen(struct exec *, struct data **); unsigned blt_setq(struct exec *, struct data **); unsigned blt_getq(struct exec *, struct data **); unsigned blt_fac(struct exec *, struct data **); unsigned blt_getfac(struct exec *, struct data **); unsigned blt_ct(struct exec *, struct data **); unsigned blt_gett(struct exec *, struct data **); unsigned blt_cf(struct exec *, struct data **); unsigned blt_getf(struct exec *, struct data **); unsigned blt_mute(struct exec *, struct data **); unsigned blt_unmute(struct exec *, struct data **); unsigned blt_getmute(struct exec *, struct data **); unsigned blt_ls(struct exec *, struct data **); unsigned blt_save(struct exec *, struct data **); unsigned blt_load(struct exec *, struct data **); unsigned blt_reset(struct exec *, struct data **); unsigned blt_export(struct exec *, struct data **); unsigned blt_import(struct exec *, struct data **); unsigned blt_idle(struct exec *, struct data **); unsigned blt_play(struct exec *, struct data **); unsigned blt_rec(struct exec *, struct data **); unsigned blt_stop(struct exec *, struct data **); unsigned blt_tempo(struct exec *, struct data **); unsigned blt_mins(struct exec *, struct data **); unsigned blt_mcut(struct exec *, struct data **); unsigned blt_mdup(struct exec *, struct data **); unsigned blt_minfo(struct exec *, struct data **); unsigned blt_mtempo(struct exec *, struct data **); unsigned blt_msig(struct exec *, struct data **); unsigned blt_mend(struct exec *, struct data **); unsigned blt_ctlconf(struct exec *, struct data **); unsigned blt_ctlconfx(struct exec *, struct data **); unsigned blt_ctlunconf(struct exec *, struct data **); unsigned blt_ctlinfo(struct exec *, struct data **); unsigned blt_evpat(struct exec *, struct data **); unsigned blt_evinfo(struct exec *, struct data **); unsigned blt_metro(struct exec *, struct data **); unsigned blt_metrocf(struct exec *, struct data **); unsigned blt_tap(struct exec *, struct data **); unsigned blt_tapev(struct exec *, struct data **); unsigned blt_undo(struct exec *, struct data **); unsigned blt_undolist(struct exec *, struct data **); unsigned blt_tlist(struct exec *, struct data **); unsigned blt_tnew(struct exec *, struct data **); unsigned blt_tdel(struct exec *, struct data **); unsigned blt_tren(struct exec *, struct data **); unsigned blt_texists(struct exec *, struct data **); unsigned blt_taddev(struct exec *, struct data **); unsigned blt_tsetf(struct exec *, struct data **); unsigned blt_tgetf(struct exec *, struct data **); unsigned blt_tcheck(struct exec *, struct data **); unsigned blt_trewrite(struct exec *, struct data **); unsigned blt_tcut(struct exec *, struct data **); unsigned blt_tins(struct exec *, struct data **); unsigned blt_tclr(struct exec *, struct data **); unsigned blt_tpaste(struct exec *, struct data **); unsigned blt_tcopy(struct exec *, struct data **); unsigned blt_tmerge(struct exec *, struct data **); unsigned blt_tquant(struct exec *, struct data **); unsigned blt_tquanta(struct exec *, struct data **); unsigned blt_tquantf(struct exec *, struct data **); unsigned blt_ttransp(struct exec *, struct data **); unsigned blt_tvcurve(struct exec *, struct data **); unsigned blt_tevmap(struct exec *, struct data **); unsigned blt_tclist(struct exec *, struct data **); unsigned blt_tinfo(struct exec *, struct data **); unsigned blt_tdump(struct exec *, struct data **); unsigned blt_ilist(struct exec *, struct data **); unsigned blt_iexists(struct exec *, struct data **); unsigned blt_inew(struct exec *, struct data **); unsigned blt_idel(struct exec *, struct data **); unsigned blt_iren(struct exec *, struct data **); unsigned blt_iset(struct exec *, struct data **); unsigned blt_igetc(struct exec *, struct data **); unsigned blt_igetd(struct exec *, struct data **); unsigned blt_iaddev(struct exec *, struct data **); unsigned blt_irmev(struct exec *, struct data **); unsigned blt_iinfo(struct exec *, struct data **); unsigned blt_olist(struct exec *, struct data **); unsigned blt_oexists(struct exec *, struct data **); unsigned blt_onew(struct exec *, struct data **); unsigned blt_odel(struct exec *, struct data **); unsigned blt_oren(struct exec *, struct data **); unsigned blt_oset(struct exec *, struct data **); unsigned blt_ogetc(struct exec *, struct data **); unsigned blt_ogetd(struct exec *, struct data **); unsigned blt_oaddev(struct exec *, struct data **); unsigned blt_ormev(struct exec *, struct data **); unsigned blt_oinfo(struct exec *, struct data **); unsigned blt_flist(struct exec *, struct data **); unsigned blt_fnew(struct exec *, struct data **); unsigned blt_fdel(struct exec *, struct data **); unsigned blt_fren(struct exec *, struct data **); unsigned blt_fexists(struct exec *, struct data **); unsigned blt_finfo(struct exec *, struct data **); unsigned blt_freset(struct exec *, struct data **); unsigned blt_fmap(struct exec *, struct data **); unsigned blt_funmap(struct exec *, struct data **); unsigned blt_ftransp(struct exec *, struct data **); unsigned blt_fvcurve(struct exec *, struct data **); unsigned blt_fchgin(struct exec *, struct data **); unsigned blt_fchgout(struct exec *, struct data **); unsigned blt_fswapin(struct exec *, struct data **); unsigned blt_fswapout(struct exec *, struct data **); unsigned blt_xlist(struct exec *, struct data **); unsigned blt_xexists(struct exec *, struct data **); unsigned blt_xnew(struct exec *, struct data **); unsigned blt_xdel(struct exec *, struct data **); unsigned blt_xren(struct exec *, struct data **); unsigned blt_xinfo(struct exec *, struct data **); unsigned blt_xrm(struct exec *, struct data **); unsigned blt_xsetd(struct exec *, struct data **); unsigned blt_xadd(struct exec *, struct data **); unsigned blt_ximport(struct exec *, struct data **); unsigned blt_xexport(struct exec *, struct data **); unsigned blt_dlist(struct exec *, struct data **); unsigned blt_dnew(struct exec *, struct data **); unsigned blt_ddel(struct exec *, struct data **); unsigned blt_dmtcrx(struct exec *, struct data **); unsigned blt_dmmctx(struct exec *, struct data **); unsigned blt_dclkrx(struct exec *, struct data **); unsigned blt_dclktx(struct exec *, struct data **); unsigned blt_dclkrate(struct exec *, struct data **); unsigned blt_dinfo(struct exec *, struct data **); unsigned blt_dixctl(struct exec *, struct data **); unsigned blt_doxctl(struct exec *, struct data **); unsigned blt_diev(struct exec *, struct data **); unsigned blt_doev(struct exec *, struct data **); #endif /* MIDISH_BUILTIN_H */ midish-1.4.0/configure010077500017510001751000000053561501104363400134170ustar00alexalex#!/bin/sh # # defaults # prefix=/usr/local # where to install midish alsa=no # do we want alsa support ? sndio=no # do we want sndio support ? vars= # variables definitions passed as-is bindir= # path where to install binaries datadir= # path where to install doc and examples mandir= # path where to install man pages defs= # no extra #defines lib= # extra path to libraries include= # extra path to header files rt_ldadd= # extra -l's for posix real-time extensions sndio_ldadd= # extra -l's for sndio(7) alsa_ldadd= # extra -l's for ALSA # # few OS-specific tweaks # case `uname` in Linux) alsa=yes rt_ldadd="-lrt" ;; OpenBSD) sndio=yes ;; esac # # display help screeen # help() { cat << END Usage: configure [options] --prefix=DIR set install prefix to DIR [$prefix] --bindir=DIR install executables in DIR [\$prefix/bin] --datadir=DIR install read-only data in DIR [\$prefix/share] --mandir=DIR install man pages in DIR [\$prefix/man] --enable-alsa enable alsa sequencer backend [$alsa] --disable-alsa disable alsa sequencer backend --enable-sndio enable libsndio backend [$sndio] --disable-sndio disable libsndio backend END } # shell word separator (none) IFS='' # sed-quoted new-line nl='\ ' for i; do case "$i" in --prefix=*) prefix="${i#--prefix=}" shift;; --bindir=*) bindir="${i#--bindir=}" shift;; --datadir=*) datadir="${i#--datadir=}" shift;; --mandir=*) mandir="${i#--mandir=}" shift;; --enable-alsa) alsa=yes shift;; --disable-alsa) alsa=no shift;; --enable-sndio) sndio=yes shift;; --disable-sndio) sndio=no shift;; CC=*|CFLAGS=*|LDFLAGS=*) vars="$vars$i$nl" shift;; *) help exit 1 ;; esac done bindir="${bindir:-$prefix/bin}" datadir="${datadir:-$prefix/share}" mandir="${mandir:-$prefix/man}" # # add parameters specific to backends # if [ $sndio = yes ]; then defs="$defs -DUSE_SNDIO" sndio_ldadd="-lsndio" alsa=no elif [ $alsa = yes ]; then defs="$defs -DUSE_ALSA" alsa_ldadd="-lasound" else defs="$defs -DUSE_RAW" fi echo "configure: creating Makefile" sed \ -e "s:@bindir@:$bindir:" \ -e "s:@datadir@:$datadir:" \ -e "s:@mandir@:$mandir:" \ -e "s:@defs@:$defs:" \ -e "s:@include@:$include:" \ -e "s:@lib@:$lib:" \ -e "s:@rt_ldadd@:$rt_ldadd:" \ -e "s:@sndio_ldadd@:$sndio_ldadd:" \ -e "s:@alsa_ldadd@:$alsa_ldadd:" \ -e "s:@vars@:$vars:" \ < Makefile.in >Makefile if [ ! -e version.h ]; then echo "#define VERSION \"midish (unknown release)\"" >version.h fi echo echo "bindir................... $bindir" echo "datadir.................. $datadir" echo "mandir................... $mandir" echo "alsa..................... $alsa" echo "sndio.................... $sndio" echo echo "Do \"make && make install\" to compile and install midish" echo midish-1.4.0/cons.c010066400017510001751000000045731501104363400126160ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "utils.h" #include "textio.h" #include "cons.h" #include "tty.h" #include "user.h" /* * print song position */ void cons_putpos(unsigned measure, unsigned beat, unsigned tic) { char buf[32]; if (user_flag_verb) { fprintf(stdout, "+pos %u %u %u\n", measure, beat, tic); fflush(stdout); } if (cons_isatty && tic == 0) { snprintf(buf, sizeof(buf), "[%04u:%02u]> ", measure, beat); el_setprompt(buf); } } /* * print song position */ void cons_puttag(char *tag) { if (user_flag_verb) { fprintf(stdout, "+%s\n", tag); fflush(stdout); } } /* * print "+ready" */ void cons_ready(void) { if (user_flag_verb) { fprintf(stdout, "+ready\n"); fflush(stdout); } } /* * follows routines that report user non-fatal errors please use them * instead of log_xxx (the latter are only for debugging) */ void cons_err(char *mesg) { log_puts(mesg); log_puts("\n"); } void cons_errs(char *s, char *mesg) { log_puts(s); log_puts(": "); log_puts(mesg); log_puts("\n"); } void cons_erru(unsigned long u, char *mesg) { log_putu(u); log_puts(": "); log_puts(mesg); log_puts("\n"); } void cons_errss(char *s0, char *s1, char *mesg) { log_puts(s0); log_puts(": "); log_puts(s1); log_puts(": "); log_puts(mesg); log_puts("\n"); } void cons_errsu(char *s, unsigned long u, char *mesg) { log_puts(s); log_puts(": "); log_putu(u); log_puts(": "); log_puts(mesg); log_puts("\n"); } void cons_erruu(unsigned long u0, unsigned long u1, char *mesg) { log_putu(u0); log_puts(": "); log_putu(u1); log_puts(": "); log_puts(mesg); log_puts("\n"); } midish-1.4.0/cons.h010066400017510001751000000024421501104363400126140ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_CONS_H #define MIDISH_CONS_H #include "tty.h" void cons_init(struct el_ops *, void *); void cons_done(void); void cons_putpos(unsigned, unsigned, unsigned); void cons_puttag(char *); void cons_ready(void); void cons_err(char *); void cons_errs(char *, char *); void cons_erru(unsigned long, char *); void cons_errss(char *, char *, char *); void cons_errsu(char *, unsigned long, char *); void cons_erruu(unsigned long, unsigned long, char *); extern int cons_isatty; #endif /* MIDISH_CONS_H */ midish-1.4.0/conv.c010066400017510001751000000233131501104363400126120ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * This module provides to midish events that are self-contained, and * that do not depend on any context. Standard MIDI events/messages * aren't context free: for instance the meaning of a "data entry" * controller depends of the last NRPN/RPN controller; dealing with * contexts would overcomplicate most midish code (filters, tracks), * that's why we define those context-free events: XCTL, NRPN, RPN, * and XPC. * * This module make convertions between context-free events (XPC, * XCTL, RPN, NRPN) and basic events (PC, CTL). For instatance a bank * controller followed by a prog change will be converted to a * "extended" prog change (XPC) that contains also the bank number. * In order to generate XPCs whose context is the current bank number, * we keep the bank number into a statelist. Similarly the current * NRPN and RPN numbers are kept. * * To keep the state, we use statelist_xxx() functions. However since * we store only controllers, we roll simplified lookup() and update() * functions. */ #include "utils.h" #include "state.h" #include "conv.h" #define CHAN_MATCH(e1, e2) \ ((e1)->ch == (e2)->ch && (e1)->dev == (e2)->dev) #define CTL_MATCH(e1, e2) \ ((e1)->ctl_num == (e2)->ctl_num && CHAN_MATCH((e1), (e2))) /* * create a state for the given controller event. If there is already * one, then update it. */ void conv_setctl(struct statelist *slist, struct ev *ev) { struct state *i; for (i = slist->first; i != NULL; i = i->next) { if (CTL_MATCH(&i->ev, ev)) { i->ev.ctl_val = ev->ctl_val; return; } } i = state_new(); statelist_add(slist, i); i->ev = *ev; } /* * return the state (ie the value) of the given controller number with * the same channel/device as the given event. If there is no state * recorded, then return EV_UNDEF */ unsigned conv_getctl(struct statelist *slist, struct ev *ev, unsigned num) { struct state *i; for (i = slist->first; i != NULL; i = i->next) { if (i->ev.ctl_num == num && CHAN_MATCH(&i->ev, ev)) { return i->ev.ctl_val; } } return EV_UNDEF; } /* * delete the state of the given controller number with the * same channel/device as the given event. */ void conv_rmctl(struct statelist *slist, struct ev *ev, unsigned num) { struct state *i; for (i = slist->first; i != NULL; i = i->next) { if (i->ev.ctl_num == num && CHAN_MATCH(&i->ev, ev)) { statelist_rm(slist, i); state_del(i); break; } } } /* * return the 14bit value of a pair of (high, low) controllers with * the same device/channel as the given event. If the state of of one * of the high or low controllers is missing, then EV_UNDEF is * returned. */ unsigned conv_getctx(struct statelist *slist, struct ev *ev, unsigned hi, unsigned lo) { unsigned vhi, vlo; vlo = conv_getctl(slist, ev, lo); if (vlo == EV_UNDEF) { return EV_UNDEF; } vhi = conv_getctl(slist, ev, hi); if (vhi == EV_UNDEF) { return EV_UNDEF; } return vlo + (vhi << 7); } /* * convert an old-style event (ie CTL, PC) to a context-free event (ie * XCTL, NRPN, RPN, XPC). If an event is available, 'rev' parameter is * filled and 1 is returned. */ unsigned conv_packev(struct statelist *l, unsigned xctlset, unsigned flags, struct ev *ev, struct ev *rev) { unsigned num, val; if (ev->cmd == EV_PC) { rev->cmd = EV_XPC; rev->dev = ev->dev; rev->ch = ev->ch; rev->pc_prog = ev->v0; rev->pc_bank = (flags & CONV_XPC) ? conv_getctx(l, ev, BANK_HI, BANK_LO) : 0; return 1; } else if (ev->cmd == EV_CTL) { switch (ev->ctl_num) { case BANK_HI: if (!(flags & CONV_XPC)) break; conv_rmctl(l, ev, BANK_LO); conv_setctl(l, ev); return 0; case RPN_HI: if (!(flags & CONV_XPC)) break; conv_rmctl(l, ev, NRPN_LO); conv_rmctl(l, ev, RPN_LO); conv_setctl(l, ev); return 0; case NRPN_HI: if (!(flags & CONV_NRPN)) break; conv_rmctl(l, ev, RPN_LO); conv_rmctl(l, ev, NRPN_LO); conv_setctl(l, ev); return 0; case DATAENT_HI: if (!(flags & (CONV_RPN | CONV_NRPN))) break; conv_rmctl(l, ev, DATAENT_LO); conv_setctl(l, ev); return 0; case BANK_LO: if (!(flags & CONV_XPC)) break; conv_setctl(l, ev); return 0; case NRPN_LO: if (!(flags & CONV_NRPN)) break; conv_rmctl(l, ev, RPN_LO); conv_setctl(l, ev); return 0; case RPN_LO: if (!(flags & CONV_RPN)) break; conv_rmctl(l, ev, NRPN_LO); conv_setctl(l, ev); return 0; case DATAENT_LO: if (!(flags & (CONV_RPN | CONV_NRPN))) break; num = conv_getctx(l, ev, NRPN_HI, NRPN_LO); if (num != EV_UNDEF) { rev->cmd = EV_NRPN; } else { num = conv_getctx(l, ev, RPN_HI, NRPN_LO); if (num == EV_UNDEF) return 0; rev->cmd = EV_RPN; } val = conv_getctl(l, ev, DATAENT_HI); if (val == EV_UNDEF) return 0; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = num; rev->ctl_val = ev->ctl_val + (val << 7); return 1; } if (ev->ctl_num < 32) { if (EVCTL_ISFINE(xctlset, ev->ctl_num)) { conv_setctl(l, ev); return 0; } } else if (ev->ctl_num < 64) { num = ev->ctl_num - 32; if (EVCTL_ISFINE(xctlset, num)) { val = conv_getctl(l, ev, num); if (val == EV_UNDEF) return 0; rev->ctl_num = num; rev->ctl_val = ev->ctl_val + (val << 7); goto done; } } rev->ctl_num = ev->ctl_num; rev->ctl_val = ev->ctl_val << 7; done: rev->cmd = EV_XCTL; rev->dev = ev->dev; rev->ch = ev->ch; return 1; } else { *rev = *ev; return 1; } } /* * convert a context-free event (XCTL, RPN, NRPN, XPC) to an array of * old-style events (CTL, PC). Renturn the number of events filled in * the array. */ unsigned conv_unpackev(struct statelist *slist, unsigned xctlset, unsigned flags, struct ev *ev, struct ev *rev) { unsigned val, hi; unsigned nev = 0; if (ev->cmd == EV_XCTL) { switch (ev->ctl_num) { case BANK_HI: case BANK_LO: if (flags & CONV_XPC) return 0; break; case NRPN_HI: case NRPN_LO: if (flags & CONV_NRPN) return 0; break; case RPN_HI: case RPN_LO: if (flags & CONV_RPN) return 0; break; case DATAENT_HI: case DATAENT_LO: if (flags & (CONV_NRPN | CONV_RPN)) return 0; break; } if (ev->ctl_num < 32 && EVCTL_ISFINE(xctlset, ev->ctl_num)) { hi = ev->ctl_val >> 7; val = conv_getctl(slist, ev, ev->ctl_num); if (val != hi || val == EV_UNDEF) { rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = ev->ctl_num; rev->ctl_val = hi; conv_setctl(slist, rev); rev++; nev++; } rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = ev->ctl_num + 32; rev->ctl_val = ev->ctl_val & 0x7f; rev++; nev++; return nev; } else { rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = ev->ctl_num; rev->ctl_val = ev->ctl_val >> 7; return 1; } } else if (ev->cmd == EV_XPC) { if (flags & CONV_XPC) { val = conv_getctx(slist, ev, BANK_HI, BANK_LO); if (val != ev->pc_bank && ev->pc_bank != EV_UNDEF) { rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = BANK_HI; rev->ctl_val = ev->pc_bank >> 7; conv_setctl(slist, rev); rev++; nev++; rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = BANK_LO; rev->ctl_val = ev->pc_bank & 0x7f; conv_setctl(slist, rev); rev++; nev++; } } rev->cmd = EV_PC; rev->dev = ev->dev; rev->ch = ev->ch; rev->v0 = ev->pc_prog; rev++; nev++; return nev; } else if (ev->cmd == EV_NRPN) { if (!(flags & CONV_NRPN)) return 0; val = conv_getctx(slist, ev, NRPN_HI, NRPN_LO); if (val != ev->rpn_num) { conv_rmctl(slist, ev, RPN_HI); conv_rmctl(slist, ev, RPN_LO); rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = NRPN_HI; rev->ctl_val = ev->rpn_num >> 7; conv_setctl(slist, rev); rev++; nev++; rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = NRPN_LO; rev->ctl_val = ev->rpn_num & 0x7f; conv_setctl(slist, rev); rev++; nev++; } dataentry: rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = DATAENT_HI; rev->ctl_val = ev->rpn_val >> 7; rev++; nev++; rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = DATAENT_LO; rev->ctl_val = ev->rpn_val & 0x7f; rev++; nev++; return nev; } else if (ev->cmd == EV_RPN) { if (!(flags & CONV_RPN)) return 0; val = conv_getctx(slist, ev, RPN_HI, RPN_LO); if (val != ev->rpn_num) { conv_rmctl(slist, ev, NRPN_HI); conv_rmctl(slist, ev, NRPN_LO); rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = RPN_HI; rev->ctl_val = ev->rpn_num >> 7; conv_setctl(slist, rev); rev++; nev++; rev->cmd = EV_CTL; rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = RPN_LO; rev->ctl_val = ev->rpn_num & 0x7f; conv_setctl(slist, rev); rev++; nev++; } goto dataentry; } else { *rev = *ev; return 1; } } midish-1.4.0/conv.h010066400017510001751000000023751501104363400126240ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_CONV_H #define MIDISH_CONV_H #include "ev.h" #define CONV_NUMREV 4 /* * constants to define a set of packed events */ #define CONV_XPC (1 << EV_XPC) #define CONV_NRPN (1 << EV_NRPN) #define CONV_RPN (1 << EV_RPN) struct ev; struct statelist; unsigned conv_packev(struct statelist *, unsigned, unsigned, struct ev *, struct ev *); unsigned conv_unpackev(struct statelist *, unsigned, unsigned, struct ev *, struct ev *); #endif /* MIDISH_CONV_H */ midish-1.4.0/data.c010066400017510001751000000327731501104363400125700ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * the data structure is used to store values of user variables, (used * by the interpreter, see node.c). This module implements basic * methods to manipulate data structures and the arithmetic primitives * used by the interpreter. * * currently allowed data types are * - 'nil' (ie no value) * - long signed integer * - string * - reference, ie an identifier (function name, track name...) * - an user type 'void *addr' pointer * - a list of values */ #include "utils.h" #include "str.h" #include "cons.h" #include "data.h" /* * allocate a new data structure and initialize it as 'nil' */ struct data * data_newnil(void) { struct data *o; o = (struct data *)xmalloc(sizeof(struct data), "data"); o->type = DATA_NIL; o->next = NULL; return o; } /* * allocate a new data structure and initialize with the given integer */ struct data * data_newlong(long val) { struct data *o; o = data_newnil(); o->val.num = val; o->type = DATA_LONG; return o; } /* * allocate a new data structure and initialize with (a copy of) the * given string */ struct data * data_newstring(char *val) { struct data *o; o = data_newnil(); o->val.str = str_new(val); o->type = DATA_STRING; return o; } /* * allocate a new data structure and initialize with (a copy of) the * given reference */ struct data * data_newref(char *val) { struct data *o; o = data_newnil(); o->val.ref = str_new(val); o->type = DATA_REF; return o; } /* * allocate a new data structure and initialize with the given list * (not copied). The list argument is the first item of a linked list * of data structures */ struct data * data_newlist(struct data *list) { struct data *o; o = data_newnil(); o->val.list = list; o->type = DATA_LIST; return o; } /* * allocate a new data structure and initialize with the given user * type */ struct data * data_newuser(void *addr) { struct data *o; o = data_newnil(); o->type = DATA_USER; o->val.user = addr; return o; } /* * allocate a new data structure and initialize with the given range */ struct data * data_newrange(long min, long max) { struct data *o; o = data_newnil(); o->type = DATA_RANGE; o->val.range.min = min; o->val.range.max = max; return o; } /* * return the number of data structures contained in the given data * structure */ unsigned data_numitem(struct data *o) { struct data *i; unsigned n; n = 1; if (o->type == DATA_LIST) { for (i = o->val.list; i != NULL; i = i->next) { n += data_numitem(i); } } return n; } /* * add to the end of given 'o' data structure (must be of type * DATA_LIST) the given data structure (can be of any type) */ void data_listadd(struct data *o, struct data *v) { struct data **i; i = &o->val.list; while (*i != NULL) { i = &(*i)->next; } v->next = NULL; *i = v; } /* * remove the given data struct from the given list */ void data_listremove(struct data *o, struct data *v) { struct data **i; i = &o->val.list; while (*i != NULL) { if (*i == v) { *i = v->next; v->next = NULL; return; } i = &(*i)->next; } log_puts("data_listremove: not found\n"); panic(); } /* * clear a data structure and set it to by of type 'DATA_NIL' */ void data_clear(struct data *o) { struct data *i, *inext; switch(o->type) { case DATA_STRING: str_delete(o->val.str); break; case DATA_REF: str_delete(o->val.ref); break; case DATA_LIST: for (i = o->val.list; i != NULL; i = inext) { inext = i->next; data_delete(i); } break; case DATA_LONG: case DATA_NIL: case DATA_USER: case DATA_RANGE: break; default: log_puts("data_clear: unknown type\n"); panic(); break; } o->type = DATA_NIL; } void data_delete(struct data *o) { data_clear(o); xfree(o); } void data_log(struct data *o) { struct data *i; switch(o->type) { case DATA_NIL: log_puts("(nil)"); break; case DATA_USER: log_puts("(user)"); break; case DATA_LONG: log_puti(o->val.num); break; case DATA_STRING: log_puts("\""); log_puts(o->val.str); log_puts("\""); break; case DATA_REF: log_puts("@"); str_log(o->val.ref); break; case DATA_LIST: log_puts("{"); for (i = o->val.list; i != NULL; i = i->next) { data_log(i); if (i->next) { log_puts(" "); } } log_puts("}"); break; case DATA_RANGE: log_puti(o->val.range.min); log_puts(":"); log_puti(o->val.range.max); break; default: log_puts("(unknown type)"); break; } } /* * copy src into dst, never fails */ void data_assign(struct data *dst, struct data *src) { struct data *n, *i, **j; if (dst == src) { log_puts("data_assign: src and dst are the same\n"); panic(); } data_clear(dst); switch(src->type) { case DATA_NIL: break; case DATA_LONG: dst->type = DATA_LONG; dst->val.num = src->val.num; break; case DATA_STRING: dst->type = DATA_STRING; dst->val.str = str_new(src->val.str); break; case DATA_REF: dst->type = DATA_REF; dst->val.ref = str_new(src->val.ref); break; case DATA_LIST: dst->type = DATA_LIST; dst->val.list = NULL; for (i = src->val.list; i != NULL; i = i->next) { n = data_newnil(); data_assign(n, i); for (j = &dst->val.list; *j != NULL; j = &(*j)->next) ; /* noting */ n->next = NULL; *j = n; } break; case DATA_RANGE: dst->type = DATA_RANGE; dst->val.range.min = src->val.range.min; dst->val.range.max = src->val.range.max; break; default: log_puts("data_assign: bad data type\n"); panic(); } } /* * return 1 if op1 et op2 are identical, 0 overwise */ unsigned data_id(struct data *op1, struct data *op2) { struct data *i1, *i2; if (op1->type != op2->type) { return 0; } switch(op1->type) { case DATA_NIL: return 1; case DATA_LONG: return op1->val.num == op2->val.num ? 1 : 0; case DATA_STRING: return str_eq(op1->val.str, op2->val.str); case DATA_REF: return str_eq(op1->val.ref, op2->val.ref); case DATA_LIST: i1 = op1->val.list; i2 = op2->val.list; for (;;) { if (i1 == NULL && i2 == NULL) { return 1; } else if (i1 == NULL || i2 == NULL || !data_id(i1, i2)) { return 0; } i1 = i1->next; i2 = i2->next; } /* not reached */ break; case DATA_RANGE: return op1->val.range.min == op2->val.range.min && op1->val.range.max == op2->val.range.max; break; default: log_puts("data_id: bad data types\n"); panic(); } /* not reached */ return 0; } /* * return 1 if the argument is * - a ref * - a not empty string or list * - a non-zero integer * and 0 overwise */ unsigned data_eval(struct data *o) { switch(o->type) { case DATA_NIL: return 0; case DATA_LONG: return o->val.num != 0 ? 1 : 0; case DATA_STRING: return *o->val.str != '\0' ? 1 : 0; case DATA_REF: return 1; case DATA_LIST: return o->val.list != NULL ? 1 : 0; case DATA_RANGE: return 1; default: log_puts("data_eval: bad data type\n"); panic(); } /* not reached */ return 0; } /* * Each of the following routines appies an unary operator to the * first argument. Return 1 on success, 0 on failure */ unsigned data_neg(struct data *op1) { if (op1->type == DATA_LONG) { op1->val.num = - op1->val.num; return 1; } cons_err("bad types in unary minus"); return 0; } unsigned data_bitnot(struct data *op1) { if (op1->type == DATA_LONG) { op1->val.num = ~ op1->val.num; return 1; } cons_err("bad type in bitwise not ('~')"); return 0; } unsigned data_not(struct data *op1) { if (data_eval(op1)) { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 0; } else { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 1; } return 1; } /* * Each of the following routines calculates the result of a binary * oprator applied to the couple of arguments and stores the result in * the first argument. Return 1 on success, 0 on failure */ unsigned data_add(struct data *op1, struct data *op2) { struct data **i; char *s1, *s2; if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num += op2->val.num; return 1; } else if (op1->type == DATA_LIST && op2->type == DATA_LIST) { /* * concatenate 2 lists */ for (i = &op1->val.list; *i != NULL; i = &(*i)->next) ; /* nothing */ *i = op2->val.list; op2->val.list = NULL; return 1; } else if (op1->type == DATA_STRING && op2->type == DATA_STRING) { /* * concatenate 2 strings */ s1 = op1->val.str; s2 = op2->val.str; op1->val.str = str_cat(s1, s2); str_delete(s1); return 1; } cons_err("bad types in addition"); return 0; } unsigned data_sub(struct data *op1, struct data *op2) { struct data **i, *j; if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num -= op2->val.num; return 1; } else if (op1->type == DATA_LIST && op2->type == DATA_LIST) { /* * remove from the first list all elements * that are present in the second list */ i = &op1->val.list; while (*i != NULL) { for (j = op2->val.list; j != NULL; j = j->next) { if (data_id(*i, j)) { goto found; } } i = &(*i)->next; continue; found: j = *i; *i = j->next; data_delete(j); continue; } return 1; } cons_err("bad types in substraction"); return 0; } unsigned data_mul(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num *= op2->val.num; return 1; } cons_err("bad types in multiplication ('*')"); return 0; } unsigned data_div(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { if (op2->val.num == 0) { cons_err("division by zero"); return 0; } op1->val.num /= op2->val.num; return 1; } cons_err("bad types in division ('/') "); return 0; } unsigned data_mod(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { if (op2->val.num == 0) { cons_err("division by zero"); return 0; } op1->val.num %= op2->val.num; return 1; } cons_err("bad types in division ('%')"); return 0; } unsigned data_lshift(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num <<= op2->val.num; return 1; } cons_err("bad types in left shift ('<<')"); return 0; } unsigned data_rshift(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num >>= op2->val.num; return 1; } cons_err("bad types in right shift ('>>')"); return 0; } unsigned data_bitor(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num |= op2->val.num; return 1; } cons_err("bad types in bitwise or ('|')"); return 0; } unsigned data_bitand(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num &= op2->val.num; return 1; } cons_err("bad types in bitwise and ('&')"); return 0; } unsigned data_bitxor(struct data *op1, struct data *op2) { if (op1->type == DATA_LONG && op2->type == DATA_LONG) { op1->val.num ^= op2->val.num; return 1; } cons_err("bad types in bitwise xor ('^')"); return 0; } unsigned data_eq(struct data *op1, struct data *op2) { if (data_id(op1, op2)) { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 1; } else { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 0; } return 1; } unsigned data_neq(struct data *op1, struct data *op2) { if (data_id(op1, op2)) { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 0; } else { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 1; } return 1; } unsigned data_lt(struct data *op1, struct data *op2) { if (op1->type != DATA_LONG || op2->type != DATA_LONG) { cons_err("bad types in '<'"); return 0; } op1->val.num = op1->val.num < op2->val.num ? 1 : 0; return 1; } unsigned data_le(struct data *op1, struct data *op2) { if (op1->type != DATA_LONG || op2->type != DATA_LONG) { cons_err("bad types in '<='"); return 0; } op1->val.num = op1->val.num <= op2->val.num ? 1 : 0; return 1; } unsigned data_gt(struct data *op1, struct data *op2) { if (op1->type != DATA_LONG || op2->type != DATA_LONG) { cons_err("bad types in '>'"); return 0; } op1->val.num = op1->val.num > op2->val.num ? 1 : 0; return 1; } unsigned data_ge(struct data *op1, struct data *op2) { if (op1->type != DATA_LONG || op2->type != DATA_LONG) { cons_err("bad types in '>='"); return 0; } op1->val.num = op1->val.num >= op2->val.num ? 1 : 0; return 1; } unsigned data_and(struct data *op1, struct data *op2) { if (data_eval(op1) && data_eval(op2)) { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 1; } else { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 0; } return 1; } unsigned data_or(struct data *op1, struct data *op2) { if (data_eval(op1) || data_eval(op2)) { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 1; } else { data_clear(op1); op1->type = DATA_LONG; op1->val.num = 0; } return 1; } midish-1.4.0/data.h010066400017510001751000000061501501104363400125630ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_DATA_H #define MIDISH_DATA_H #define DATA_MAXNITEMS 4096 struct name; /* * the following represents a "value" for the interpreter. all types * use the same strucure */ struct data { #define DATA_NIL 0 #define DATA_LONG 1 #define DATA_STRING 2 #define DATA_REF 3 #define DATA_LIST 4 #define DATA_USER 5 #define DATA_RANGE 6 unsigned type; /* type of the value */ union { char *str; /* if string */ long num; /* if a number */ struct data *list; /* if a list of values */ char *ref; /* if a reference (name) */ void *user; /* user defined */ struct { /* a range */ long min; long max; } range; } val; struct data *next; }; struct data *data_newnil(void); struct data *data_newlong(long); struct data *data_newstring(char *); struct data *data_newref(char *); struct data *data_newlist(struct data *); struct data *data_newuser(void *); struct data *data_newrange(long, long); void data_delete(struct data *); void data_setfield(struct data *, char *); void data_log(struct data *); void data_listadd(struct data *, struct data *); void data_listremove(struct data *, struct data *); struct data *data_listlookup(struct data *, struct name *); void data_assign(struct data *, struct data *); unsigned data_eval(struct data *); unsigned data_id(struct data *, struct data *); unsigned data_not(struct data *); unsigned data_and(struct data *, struct data *); unsigned data_or(struct data *, struct data *); unsigned data_eq(struct data *, struct data *); unsigned data_neq(struct data *, struct data *); unsigned data_lt(struct data *, struct data *); unsigned data_le(struct data *, struct data *); unsigned data_gt(struct data *, struct data *); unsigned data_ge(struct data *, struct data *); unsigned data_add(struct data *, struct data *); unsigned data_sub(struct data *, struct data *); unsigned data_neg(struct data *); unsigned data_mul(struct data *, struct data *); unsigned data_div(struct data *, struct data *); unsigned data_mod(struct data *, struct data *); unsigned data_lshift(struct data *, struct data *); unsigned data_rshift(struct data *, struct data *); unsigned data_bitand(struct data *, struct data *); unsigned data_bitor(struct data *, struct data *); unsigned data_bitxor(struct data *, struct data *); unsigned data_bitnot(struct data *); #endif /* MIDISH_DATA_H */ midish-1.4.0/defs.h010066400017510001751000000077221501104363400126010ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_DEFAULT_H #define MIDISH_DEFAULT_H /* * convert tempo (beats per minute) to tic length (number of 24-th of * microsecond) */ #define TEMPO_TO_USEC24(tempo,tpb) (60L * 24000000L / ((tempo) * (tpb))) /* * units for absolute postions, ie we use MTC_SEC units per second * this number mus be multiple of all supported quarter frame frequencies * ie 96, 100, 120 */ #define MTC_SEC 2400 /* * MTC counters wrap every 24 hours */ #define MTC_PERIOD (24 * 60 * 60 * MTC_SEC) /* * special meaning controller numbers */ #define BANK_HI 0 #define BANK_LO 32 #define DATAENT_HI 6 #define DATAENT_LO 38 #define NRPN_HI 99 #define NRPN_LO 98 #define RPN_HI 101 #define RPN_LO 100 /* * define MIN and MAX values of event parameters */ #define TEMPO_MIN TEMPO_TO_USEC24(240, TIMESIG_TICS_MAX) #define TEMPO_MAX TEMPO_TO_USEC24(20, 24) #define TIMESIG_TICS_MAX (TPU_MAX / 4) #define TIMESIG_BEATS_MAX 100 #define TPU_MAX (96 * 40) /* * maximum number of midi devices supported by midish */ #define DEFAULT_MAXNDEVS 16 /* * maximum number of instruments */ #define DEFAULT_MAXNCHANS (DEFAULT_MAXNDEVS * 16) /* * maximum number of events */ #define DEFAULT_MAXNSEQEVS 400000 /* * maximum number of tracks */ #define DEFAULT_MAXNSEQPTRS 200 /* * maximum number of filter states (roughly maximum number of * simultaneous notes */ #define DEFAULT_MAXNSTATES 10000 /* * maximum number of system exclusive messages */ #define DEFAULT_MAXNSYSEXS 2000 /* * maximum number of chunks (each sysex is a set of chunks) */ #define DEFAULT_MAXNCHUNKS (DEFAULT_MAXNSYSEXS * 2) /* * default number of tics per beat */ #define DEFAULT_TPB 24 /* * default beats per measure */ #define DEFAULT_BPM 4 /* * default number of tic per unit note */ #define DEFAULT_TPU 96 /* * default tempo */ #define DEFAULT_TEMPO 120 /* * default tempo in 24-th of microsecond period */ #define DEFAULT_USEC24 TEMPO_TO_USEC24(DEFAULT_TEMPO, DEFAULT_TPB) /* * default MTC or MMC frames-per-second, used to transmit initial * postition when starting. The choice is arbitrary, be we use 25fps as * the period is multiple of 1ms. */ #define DEFAULT_FPS 25 /* * number of milliseconds to wait between the instrumet config is sent * and the playback is stared */ #define DEFAULT_CHANWAIT 200 /* * nmber of milliseconds to wait after each sysex message is sent */ #define DEFAULT_SXWAIT 20 /* * metronome click length in 24-th of microsecond (30ms) */ #define DEFAULT_METRO_CLICKLEN (24 * 1000 * 30) /* * default metronome device and midi channel */ #define DEFAULT_METRO_DEV 0 #define DEFAULT_METRO_CHAN 9 /* * default metronome click notes and velocities */ #define DEFAULT_METRO_HI_NOTE 67 #define DEFAULT_METRO_HI_VEL 127 #define DEFAULT_METRO_LO_NOTE 68 #define DEFAULT_METRO_LO_VEL 90 /* * max memory usage allowed for undo */ #define UNDO_MAXSIZE (4 * 1024 * 1024) /* * output source prioriries */ #define PRIO_INPUT 0 #define PRIO_TRACK 1 #define PRIO_CHAN 2 /* * how to relocate, used by song_loc() & friends */ #define LOC_MEAS 0 /* measure number */ #define LOC_MTC 1 /* MTC/MMC absolute time */ #define LOC_SPP 2 /* MIDI song position pointer */ #endif /* MIDISH_DEFAULT_H */ midish-1.4.0/ev.c010066400017510001751000000567221501104363400122710ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * ev is an "extended" midi event */ #include "utils.h" #include "ev.h" #include "str.h" #include "cons.h" struct evinfo evinfo[EV_NUMCMD] = { { "nil", "none", 0, 0, 0, 0, 0, 0, 0, NULL }, { NULL, "any", EV_HAS_DEV | EV_HAS_CH, 0, 0, 0, 0, 0, 0, NULL }, { "tempo", NULL, 0, 1, 0xdeadbeef, TEMPO_MIN, TEMPO_MAX, 0, 0, NULL }, { "timesig", NULL, 0, 2, 0xdeadbeef, 1, 16, 1, 32, NULL }, { "nrpn", "nrpn", EV_HAS_DEV | EV_HAS_CH, 2, 2, 0, EV_MAXFINE, 0, EV_MAXFINE, NULL }, { "rpn", "rpn", EV_HAS_DEV | EV_HAS_CH, 2, 2, 0, EV_MAXFINE, 0, EV_MAXFINE, NULL }, { "xctl", "xctl", EV_HAS_DEV | EV_HAS_CH, 2, 1, 0, EV_MAXCOARSE, 0, EV_MAXFINE, NULL }, { "xpc", "xpc", EV_HAS_DEV | EV_HAS_CH, 2, 2, 0, EV_MAXFINE, 0, EV_MAXCOARSE, NULL }, { "noff", NULL, EV_HAS_DEV | EV_HAS_CH, 2, 0xdeadbeef, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE, NULL }, { "non", "note", EV_HAS_DEV | EV_HAS_CH, 2, 1, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE, NULL }, { "kat", NULL, EV_HAS_DEV | EV_HAS_CH, 2, 0xdeadbeef, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE, NULL }, { "ctl", "ctl", EV_HAS_DEV | EV_HAS_CH, 2, 1, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE, NULL }, { "pc", "pc", EV_HAS_DEV | EV_HAS_CH, 1, 1, 0, EV_MAXCOARSE, 0, 0, NULL }, { "cat", "cat", EV_HAS_DEV | EV_HAS_CH, 1, 0, 0, EV_MAXCOARSE, 0, 0, NULL }, { "bend", "bend", EV_HAS_DEV | EV_HAS_CH, 1, 0, 0, EV_MAXFINE, 0, 0, NULL }, { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* unused slot */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 0 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 1 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 2 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 3 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 4 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 5 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 6 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 7 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 8 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 9 */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 0xa */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 0xb */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 0xc */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 0xd */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL }, /* sysex pattern 0xe */ { NULL, NULL, 0, 0, 0, 0, 0, 0, 0, NULL } /* sysex pattern 0xf */ }; struct evctl evctl_tab[EV_MAXCOARSE + 1]; /* * return the 'name' of the given event */ char * ev_getstr(struct ev *ev) { if (ev->cmd >= EV_NUMCMD) { return NULL; /* log_puts("ev_getstr: invalid cmd\n"); panic(); */ } return evinfo[ev->cmd].ev; } /* * find the event EV_XX constant corresponding to the given string */ unsigned ev_str2cmd(struct ev *ev, char *str) { unsigned i; for (i = 0; i < EV_NUMCMD; i++) { if (evinfo[i].ev && str_eq(evinfo[i].ev, str)) { ev->cmd = i; return 1; } } return 0; } /* * return the phase of the event within a frame: * * - EV_PHASE_FIRST is set if the event can be the first event in a * sequence (example: note-on, bender != 0x4000) * * - EV_PHASE_NEXT is set if the given event can be the next event * in a frame after a 'first event' but not the last one * (example: key after-touch, beder != 0x4000) * * - EV_PHASE_LAST is set if the given event can be the last event * in a frame (example: note-off, any unknown controller) */ unsigned ev_phase(struct ev *ev) { unsigned phase; switch(ev->cmd) { case EV_NOFF: phase = EV_PHASE_LAST; break; case EV_NON: phase = EV_PHASE_FIRST; break; case EV_KAT: phase = EV_PHASE_NEXT; break; case EV_CAT: if (ev->cat_val != EV_CAT_DEFAULT) { phase = EV_PHASE_FIRST | EV_PHASE_NEXT; } else { phase = EV_PHASE_LAST; } break; case EV_XCTL: if (!EV_CTL_ISFRAME(ev)) { phase = EV_PHASE_FIRST | EV_PHASE_LAST; } else { if (ev->ctl_val != EV_CTL_DEFVAL(ev)) { phase = EV_PHASE_FIRST | EV_PHASE_NEXT; } else { phase = EV_PHASE_LAST; } } break; case EV_BEND: if (ev->bend_val != EV_BEND_DEFAULT) { phase = EV_PHASE_FIRST | EV_PHASE_NEXT; } else { phase = EV_PHASE_LAST; } break; default: phase = EV_PHASE_FIRST | EV_PHASE_LAST; break; } return phase; } /* * check if the given event matches the given frame (if so, this means * that, iether the event is part of the frame, either there is a * conflict between the frame and the event) */ unsigned ev_eq(struct ev *e1, struct ev *e2) { unsigned nparams; if (e1->cmd != e2->cmd) return 0; if ((evinfo[e1->cmd].flags & EV_HAS_DEV) && e1->dev != e2->dev) return 0; if ((evinfo[e1->cmd].flags & EV_HAS_CH) && e1->ch != e2->ch) return 0; nparams = evinfo[e1->cmd].nparams; if (nparams > 0) { if (e1->v0 != e2->v0) return 0; if (nparams > 1) { if (e1->v1 != e2->v1) return 0; } } return 1; } /* * check if the given event matches the given frame (if so, this means * that, iether the event is part of the frame, either there is a * conflict between the frame and the event) */ unsigned ev_match(struct ev *st, struct ev *ev) { switch (st->cmd) { case EV_NON: case EV_NOFF: case EV_KAT: if (!EV_ISNOTE(ev) || st->note_num != ev->note_num || st->ch != ev->ch || st->dev != ev->dev) { return 0; } break; case EV_XCTL: case EV_NRPN: case EV_RPN: if (st->cmd != ev->cmd || st->dev != ev->dev || st->ch != ev->ch || st->v0 != ev->v0) { return 0; } break; case EV_BEND: case EV_CAT: case EV_XPC: if (st->cmd != ev->cmd || st->dev != ev->dev || st->ch != ev->ch) { return 0; } break; case EV_TEMPO: case EV_TIMESIG: if (st->cmd != ev->cmd) { return 0; } break; default: if (EV_ISSX(st)) { if (ev->cmd != st->cmd) return 0; break; } log_puts("ev_match: "); ev_log(st); log_puts(": bad event type\n"); panic(); break; } return 1; } /* * dump the event structure on stderr, for debug purposes */ void ev_log(struct ev *ev) { char *cmdstr; cmdstr = ev_getstr(ev); if (cmdstr == NULL) { log_puts("unkw("); log_putu(ev->cmd); log_puts(")"); } else { log_puts(cmdstr); switch(ev->cmd) { case EV_NON: case EV_NOFF: case EV_KAT: case EV_CTL: case EV_NRPN: case EV_RPN: case EV_XPC: case EV_XCTL: log_puts(" {"); log_putx(ev->dev); log_puts(" "); log_putx(ev->ch); log_puts("} "); log_putx(ev->v0); log_puts(" "); log_putx(ev->v1); break; case EV_BEND: case EV_CAT: case EV_PC: log_puts(" {"); log_putx(ev->dev); log_puts(" "); log_putx(ev->ch); log_puts("} "); log_putx(ev->v0); break; case EV_TEMPO: log_puts(" "); log_putu((unsigned)ev->tempo_usec24); break; case EV_TIMESIG: log_puts(" "); log_putx(ev->timesig_beats); log_puts(" "); log_putx(ev->timesig_tics); break; default: if (EV_ISSX(ev)) { log_puts(" "); log_putx(ev->dev); if (evinfo[ev->cmd].nparams >= 1) { log_puts(" "); log_putx(ev->v0); } if (evinfo[ev->cmd].nparams >= 2) { log_puts(" "); log_putx(ev->v1); } } } } } /* * transform "in" (matching "from" spec) into "out" so it matches "to" * spec. The "from" and "to" specs _must_ have the same dev, ch, v0 and * v1 ranges (to ensure the mapping must be bijective). This routine is * supposed to be fast since it's called for each incoming event. */ void ev_map(struct ev *in, struct evspec *from, struct evspec *to, struct ev *out) { if (from->cmd == EVSPEC_ANY) { out->cmd = in->cmd; out->dev = in->dev - from->dev_min + to->dev_min; out->ch = in->ch - from->ch_min + to->ch_min; out->v0 = in->v0; out->v1 = in->v1; return; } if (from->cmd == EVSPEC_NOTE) out->cmd = in->cmd; else out->cmd = to->cmd; if (evinfo[out->cmd].flags & EV_HAS_DEV) { out->dev = to->dev_min; if (evinfo[from->cmd].flags & EV_HAS_DEV) out->dev += in->dev - from->dev_min; } if (evinfo[out->cmd].flags & EV_HAS_CH) { out->ch = to->ch_min; if (evinfo[from->cmd].flags & EV_HAS_CH) out->ch += in->ch - from->ch_min; } switch (evinfo[from->cmd].nparams) { case 0: switch (evinfo[to->cmd].nparams) { case 0: break; case 1: out->v0 = to->v0_min; break; case 2: out->v0 = to->v0_min; out->v1 = to->v1_min; break; } break; case 1: switch (evinfo[to->cmd].nparams) { case 0: break; case 1: out->v0 = in->v0 - from->v0_min + to->v0_min; break; case 2: out->v0 = to->v0_min; out->v1 = in->v0 - from->v0_min + to->v1_min; break; } break; case 2: switch (evinfo[to->cmd].nparams) { case 0: break; case 1: out->v0 = in->v1 - from->v1_min + to->v0_min; break; case 2: out->v0 = in->v0 - from->v0_min + to->v0_min; out->v1 = in->v1 - from->v1_min + to->v1_min; break; } break; } } /* * find the EVSPEC_XX constant corresponding to * the given string */ unsigned evspec_str2cmd(struct evspec *ev, char *str) { unsigned i; for (i = 0; i < EV_NUMCMD; i++) { if (evinfo[i].spec != NULL && str_eq(evinfo[i].spec, str)) { ev->cmd = i; return 1; } } return 0; } /* * reset the evspec structure with "select any event" */ void evspec_reset(struct evspec *o) { o->cmd = EVSPEC_ANY; o->dev_min = 0; o->dev_max = EV_MAXDEV; o->ch_min = 0; o->ch_max = EV_MAXCH; o->v0_min = evinfo[o->cmd].v0_min; o->v0_max = evinfo[o->cmd].v0_max; o->v1_min = evinfo[o->cmd].v1_min; o->v1_max = evinfo[o->cmd].v1_max; } /* * dump the event structure on stderr (debug purposes) */ void evspec_log(struct evspec *o) { unsigned i; i = 0; for (;;) { if (i == EV_NUMCMD) { log_puts("unk("); log_putu(o->cmd); log_puts(")"); break; } if (o->cmd == i) { if (evinfo[i].spec == NULL) { log_puts("bad("); log_putu(o->cmd); log_puts(")"); } else { log_puts(evinfo[i].spec); } break; } i++; } if (evinfo[o->cmd].flags & EV_HAS_DEV) { log_puts(" "); log_putu(o->dev_min); log_puts(":"); log_putu(o->dev_max); } if (evinfo[o->cmd].flags & EV_HAS_CH) { log_puts(" "); log_putu(o->ch_min); log_puts(":"); log_putu(o->ch_max); } if (evinfo[o->cmd].nparams >= 1) { log_puts(" "); log_putu(o->v0_min); log_puts(":"); log_putu(o->v0_max); } if (evinfo[o->cmd].nparams >= 2) { log_puts(" "); log_putu(o->v1_min); log_puts(":"); log_putu(o->v1_max); } } /* * check if the given state belongs to the event spec */ unsigned evspec_matchev(struct evspec *es, struct ev *ev) { if (es->cmd == EVSPEC_EMPTY) return 0; if (es->cmd == EVSPEC_NOTE && !EV_ISNOTE(ev)) return 0; if (es->cmd != EVSPEC_ANY) { if (es->cmd == EVSPEC_NOTE) { if (!EV_ISNOTE(ev)) return 0; } else { if (es->cmd != ev->cmd) return 0; } } if ((evinfo[es->cmd].flags & EV_HAS_DEV) && (evinfo[ev->cmd].flags & EV_HAS_DEV)) { if (ev->dev < es->dev_min || ev->dev > es->dev_max) return 0; } if ((evinfo[es->cmd].flags & EV_HAS_CH) && (evinfo[ev->cmd].flags & EV_HAS_CH)) { if (ev->ch < es->ch_min || ev->ch > es->ch_max) return 0; } if (evinfo[es->cmd].nparams > 0 && evinfo[ev->cmd].nparams > 0) { if (ev->v0 < es->v0_min || ev->v0 > es->v0_max) return 0; } if (evinfo[es->cmd].nparams > 1 && evinfo[ev->cmd].nparams > 1) { if (ev->v1 < es->v1_min || ev->v1 > es->v1_max) return 0; } return 1; } /* * check if both sets are the same */ unsigned evspec_eq(struct evspec *es1, struct evspec *es2) { if (es1->cmd != es2->cmd) { return 0; } if (evinfo[es1->cmd].flags & EV_HAS_DEV) { if (es1->dev_min != es2->dev_min || es1->dev_max != es2->dev_max) { return 0; } } if (evinfo[es1->cmd].flags & EV_HAS_CH) { if (es1->ch_min != es2->ch_min || es1->ch_max != es2->ch_max) { return 0; } } if (evinfo[es1->cmd].nparams > 0) { if (es1->v0_min != es2->v0_min || es1->v0_max != es2->v0_max) { return 0; } } if (evinfo[es1->cmd].nparams > 1) { if (es1->v1_min != es2->v1_min || es1->v1_max != es2->v1_max) { return 0; } } return 1; } /* * check if there is intersection between two evspecs */ unsigned evspec_isec(struct evspec *es1, struct evspec *es2) { if (es1->cmd == EVSPEC_EMPTY || es2->cmd == EVSPEC_EMPTY) { return 0; } if (es1->cmd != EVSPEC_ANY && es2->cmd != EVSPEC_ANY && es1->cmd != es2->cmd) { return 0; } if ((evinfo[es1->cmd].flags & EV_HAS_DEV) && (evinfo[es2->cmd].flags & EV_HAS_DEV)) { if (es1->dev_min > es2->dev_max || es1->dev_max < es2->dev_min) { return 0; } } if ((evinfo[es1->cmd].flags & EV_HAS_CH) && (evinfo[es2->cmd].flags & EV_HAS_CH)) { if (es1->ch_min > es2->ch_max || es1->ch_max < es2->ch_min) { return 0; } } if (evinfo[es1->cmd].nparams > 0 && evinfo[es2->cmd].nparams > 0) { if (es1->v0_min > es2->v0_max || es1->v0_max < es2->v0_min) { return 0; } } if (evinfo[es1->cmd].nparams > 1 && evinfo[es2->cmd].nparams > 1) { if (es1->v1_min > es2->v1_max || es1->v1_max < es2->v1_min) { return 0; } } return 1; } /* * check if the first evspec is included in the second one * note: any evspec includes itself */ unsigned evspec_in(struct evspec *es1, struct evspec *es2) { if (es1->cmd == EVSPEC_EMPTY) { return 1; } if (es2->cmd == EVSPEC_EMPTY) { return 0; } if (es1->cmd == EVSPEC_ANY && es2->cmd != EVSPEC_ANY) { return 0; } if (es2->cmd != EVSPEC_ANY && es2->cmd != es1->cmd) { return 0; } if ((evinfo[es1->cmd].flags & EV_HAS_DEV) && (evinfo[es2->cmd].flags & EV_HAS_DEV)) { if (es1->dev_min < es2->dev_min || es1->dev_max > es2->dev_max) { return 0; } } if ((evinfo[es1->cmd].flags & EV_HAS_CH) && (evinfo[es2->cmd].flags & EV_HAS_CH)) { if (es1->ch_min < es2->ch_min || es1->ch_max > es2->ch_max) { return 0; } } if (evinfo[es1->cmd].nparams > 0 && evinfo[es2->cmd].nparams > 0) { if (es1->v0_min < es2->v0_min || es1->v0_max > es2->v0_max) { return 0; } } if (evinfo[es1->cmd].nparams > 1 && evinfo[es2->cmd].nparams > 1) { if (es1->v1_min < es2->v1_min || es1->v1_max > es2->v1_max) { return 0; } } return 1; } /* * check it the given map can work with ev_map() */ int evspec_isamap(struct evspec *from, struct evspec *to) { if ((from->cmd == EVSPEC_NOTE && to->cmd != EVSPEC_NOTE) || (from->cmd != EVSPEC_NOTE && to->cmd == EVSPEC_NOTE)) { cons_err("note may only be used in both map args"); return 0; } if ((from->cmd == EVSPEC_ANY && to->cmd != EVSPEC_ANY) || (from->cmd != EVSPEC_ANY && to->cmd == EVSPEC_ANY)) { cons_err("any may only be used in both map args"); return 0; } if (evinfo[from->cmd].flags & EV_HAS_DEV && from->dev_max - from->dev_min != to->dev_max - to->dev_min) { cons_err("dev ranges must have the same size"); return 0; } if (evinfo[from->cmd].flags & EV_HAS_CH && from->ch_max - from->ch_min != to->ch_max - to->ch_min) { cons_err("chan ranges must have the same size"); return 0; } switch (evinfo[from->cmd].nparams) { case 0: switch (evinfo[to->cmd].nparams) { case 0: break; case 1: if (to->v0_max != to->v0_min) { cons_err("v0 ranges not empty"); return 0; } break; case 2: if (to->v0_max != to->v0_min || to->v1_max != to->v1_min) { cons_err("v0/v1 ranges not empty"); return 0; } break; } break; case 1: switch (evinfo[to->cmd].nparams) { case 0: if (from->v0_max != from->v0_min) { cons_err("v0 ranges not empty"); return 0; } break; case 1: if (from->v0_max - from->v0_min != to->v0_max - to->v0_min) { cons_err("v0 ranges not of the same sizes"); return 0; } break; case 2: if (to->v0_max != to->v0_min) { cons_err("v0 range not empty"); return 0; } if (from->v0_max - from->v0_min != to->v1_max - to->v1_min) { cons_err("v0/v1 ranges not of the same sizes"); return 0; } break; } break; case 2: switch (evinfo[to->cmd].nparams) { case 0: if (from->v0_max != from->v0_min || from->v1_max != from->v1_min) { cons_err("v0/v1 ranges not empty"); return 0; } break; case 1: if (from->v0_max != from->v0_min) { cons_err("v0 range not empty"); return 0; } if (from->v1_max - from->v1_min != to->v0_max - to->v0_min) { cons_err("v1/v0 ranges not of the same sizes"); return 0; } break; case 2: if (from->v0_max - from->v0_min != to->v0_max - to->v0_min || from->v1_max - from->v1_min != to->v1_max - to->v1_min) { cons_err("x0/v1 ranges not of the same sizes"); return 0; } break; } break; } return 1; } /* * transform "in" spec (included in "from" spec) into "out" spec * (included in "to" spec). This routine works in exactly the same way * as ev_map() but for specs instead of events; so it has the same * semantics and constraints. */ void evspec_map(struct evspec *in, struct evspec *from, struct evspec *to, struct evspec *out) { int offs; /* * any -> any is special */ if (from->cmd == EVSPEC_ANY) { out->cmd = in->cmd; offs = to->dev_min - from->dev_min; out->dev_min = in->dev_min + offs; out->dev_max = in->dev_max + offs; offs = to->ch_min - from->ch_min; out->ch_min = in->ch_min + offs; out->ch_max = in->ch_max + offs; out->v0_min = in->v0_min; out->v0_max = in->v0_max; out->v1_min = in->v1_min; out->v1_max = in->v1_max; return; } /* * note are always mapped to notes */ if (from->cmd == EVSPEC_NOTE) out->cmd = in->cmd; else out->cmd = to->cmd; if (evinfo[out->cmd].flags & EV_HAS_DEV) { out->dev_min = to->dev_min; out->dev_max = to->dev_max; if (evinfo[from->cmd].flags & EV_HAS_DEV) { out->dev_min += in->dev_min - from->dev_min; out->dev_max += in->dev_max - from->dev_min; } } if (evinfo[out->cmd].flags & EV_HAS_CH) { out->ch_min = to->ch_min; out->ch_max = to->ch_max; if (evinfo[from->cmd].flags & EV_HAS_CH) { out->ch_min += in->ch_min - from->ch_min; out->ch_max += in->ch_max - from->ch_min; } } switch (evinfo[from->cmd].nparams) { case 0: switch (evinfo[to->cmd].nparams) { case 0: break; case 1: out->v0_min = to->v0_min; out->v0_max = to->v0_max; break; case 2: out->v0_min = to->v0_min; out->v0_max = to->v0_max; out->v1_min = to->v1_min; out->v1_max = to->v1_max; break; } break; case 1: switch (evinfo[to->cmd].nparams) { case 0: break; case 1: offs = to->v0_min - from->v0_min; out->v0_min = in->v0_min + offs; out->v0_max = in->v0_max + offs; break; case 2: out->v0_min = to->v0_min; out->v0_max = to->v0_max; offs = to->v1_min - from->v0_min; out->v1_min = in->v0_min + offs; out->v1_max = in->v0_max + offs; break; } break; case 2: switch (evinfo[to->cmd].nparams) { case 0: break; case 1: offs = to->v0_min - from->v1_min; out->v0_min = in->v1_min + offs; out->v0_max = in->v1_max + offs; break; case 2: offs = to->v0_min - from->v0_min; out->v0_min = in->v0_min + offs; out->v0_max = in->v0_max + offs; offs = to->v1_min - from->v1_min; out->v1_min = in->v1_min - offs; out->v1_max = in->v1_max - offs; break; } break; } } /* * configure a controller (set the name and default value) */ void evctl_conf(unsigned num, char *name, unsigned defval) { struct evctl *ctl = &evctl_tab[num]; if (name) { ctl->name = str_new(name); } ctl->defval = defval; } /* * unconfigure a controller (clear its name unset set its default * value tu "unknown") */ void evctl_unconf(unsigned i) { struct evctl *ctl = &evctl_tab[i]; if (ctl->name != NULL) { str_delete(ctl->name); ctl->name = NULL; } ctl->defval = EV_UNDEF; } /* * find the controller number corresponding the given controller * name. Return 1 if found, 0 if not */ unsigned evctl_lookup(char *name, unsigned *ret) { unsigned i; struct evctl *ctl; for (i = 0; i < EV_MAXCOARSE + 1; i++) { ctl = &evctl_tab[i]; if (ctl->name != NULL && str_eq(ctl->name, name)) { *ret = i; return 1; } } return 0; } /* * initialize the controller table */ void evctl_init(void) { unsigned i; for (i = 0; i < EV_MAXCOARSE + 1; i++) { evctl_tab[i].name = NULL; evctl_tab[i].defval = EV_UNDEF; } /* * some defaults, for testing ... */ evctl_conf(1, "mod", 0); evctl_conf(7, "vol", EV_UNDEF); evctl_conf(11, "expr", EV_MAXCOARSE << 7); evctl_conf(64, "sustain", 0); } /* * free the controller table */ void evctl_done(void) { unsigned i; for (i = 0; i < EV_MAXCOARSE + 1; i++) { if (evctl_tab[i].name != NULL) { str_delete(evctl_tab[i].name); } } } /* * find the sysex pattern corresponding to the given name */ unsigned evpat_lookup(char *name, unsigned *ret) { int cmd; for (cmd = EV_PAT0; cmd < EV_PAT0 + EV_NPAT; cmd++) { if (evinfo[cmd].ev != NULL && str_eq(evinfo[cmd].ev, name)) { *ret = cmd; return 1; } } return 0; } /* * free the given sysex pattern */ void evpat_unconf(unsigned cmd) { str_delete(evinfo[cmd].ev); xfree(evinfo[cmd].pattern); evinfo[cmd].ev = NULL; evinfo[cmd].spec = NULL; evinfo[cmd].pattern = NULL; } void evpat_reset(void) { unsigned cmd; for (cmd = EV_PAT0; cmd < EV_PAT0 + EV_NPAT; cmd++) { if (evinfo[cmd].ev != NULL) evpat_unconf(cmd); } } unsigned evpat_set(unsigned cmd, char *name, unsigned char *pattern, unsigned size) { unsigned i; int has_v0_hi, has_v0_lo, has_v1_hi, has_v1_lo; /* * check pattern */ if (size < 2 || pattern[0] != 0xf0 || pattern[size - 1] != 0xf7) { cons_err("sysex pattern must be in the 0xf0 ... 0xf7 format"); return 0; } has_v0_hi = has_v0_lo = has_v1_hi = has_v1_lo = 0; for (i = 1; i < size - 1; i++) { switch (pattern[i]) { case EV_PATV0_HI: has_v0_hi++; break; case EV_PATV0_LO: has_v0_lo++; break; case EV_PATV1_HI: has_v1_hi++; break; case EV_PATV1_LO: has_v1_lo++; break; default: if (pattern[i] > 0x7f) { cons_err("sysex pattern data out of range"); return 0; } } } if (has_v0_hi > 1 || has_v0_lo > 1 || has_v1_hi > 1 || has_v1_lo > 1) { cons_err("duplicate atom in sysex pattern"); return 0; } if (has_v0_lo && !has_v0_hi) { cons_err("v0_lo but no v0_hi in sysex pattern"); return 0; } if (has_v1_lo && !has_v1_hi) { cons_err("v1_lo but no v1_hi in sysex pattern"); return 0; } evinfo[cmd].pattern = pattern; evinfo[cmd].ev = evinfo[cmd].spec = name; evinfo[cmd].flags = EV_HAS_DEV; evinfo[cmd].nparams = has_v0_hi + has_v1_hi; evinfo[cmd].nranges = evinfo[cmd].nparams; evinfo[cmd].v0_min = 0; evinfo[cmd].v0_max = EV_MAXFINE; evinfo[cmd].v1_min = 0; evinfo[cmd].v1_max = EV_MAXFINE; #if 0 log_puts("evpat: nparams = "); log_putu(evinfo[cmd].nparams); log_puts("\n"); #endif return 1; } midish-1.4.0/ev.h010066400017510001751000000151571501104363400122730ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_EV_H #define MIDISH_EV_H #include "defs.h" /* * number of user-parametrable sysex patterns */ #define EV_NPAT 16 #define EV_NULL 0 /* "null" or end-of-track */ #define EV_TEMPO 0x2 /* tempo change */ #define EV_TIMESIG 0x3 /* time signature change */ #define EV_NRPN 0x4 /* NRPN + data entry */ #define EV_RPN 0x5 /* RPN + data entry */ #define EV_XCTL 0x6 /* 14bit controller */ #define EV_XPC 0x7 /* prog change + bank select */ #define EV_NOFF 0x8 /* MIDI note off */ #define EV_NON 0x9 /* MIDI note on */ #define EV_KAT 0xa /* MIDI key after-toutch */ #define EV_CTL 0xb /* MIDI controller */ #define EV_PC 0xc /* MIDI prog. change */ #define EV_CAT 0xd /* MIDI channel aftertouch */ #define EV_BEND 0xe /* MIDI pitch bend */ #define EV_PAT0 0x10 /* user sysex pattern */ #define EV_NUMCMD (EV_PAT0 + EV_NPAT) #define EV_ISVOICE(ev) (((ev)->cmd <= EV_BEND) && ((ev)->cmd >= EV_NRPN)) #define EV_ISMETA(ev) (((ev)->cmd <= EV_TIMESIG) && ((ev)->cmd >= EV_TEMPO)) #define EV_ISNOTE(ev) ((ev)->cmd == EV_NON || \ (ev)->cmd == EV_NOFF || \ (ev)->cmd == EV_KAT) #define EV_ISSX(ev) ((ev)->cmd >= EV_PAT0 && \ (ev)->cmd < EV_PAT0 + EV_NPAT) /* * some special values for events */ #define EV_NOFF_DEFAULTVEL 100 /* defaul note-off velocity */ #define EV_BEND_DEFAULT 0x2000 /* defaul bender value */ #define EV_CAT_DEFAULT 0 /* default channel aftertouch value */ #define EV_CTL_UNKNOWN 255 /* unknown controller number */ /* * an event: structure used to store MIDI events and some * midish-sepcific events (tempo changes, etc...). This structure have * to be kept as small as possible, because its used to store events * on tracks, that may contain a lot of events */ struct ev { unsigned char cmd, dev, ch; #define note_num v0 #define note_vel v1 #define note_kat v1 #define ctl_num v0 #define ctl_val v1 #define pc_prog v1 #define pc_bank v0 #define cat_val v0 #define bend_val v0 #define rpn_num v0 #define rpn_val v1 #define tempo_usec24 v0 #define timesig_beats v0 #define timesig_tics v1 unsigned v0, v1; #define EV_UNDEF 0xffff #define EV_MAXDEV (DEFAULT_MAXNDEVS - 1) #define EV_MAXCH 15 #define EV_MAXCOARSE 0x7f #define EV_MAXFINE 0x3fff }; /* * event phase bitmasks */ #define EV_PHASE_FIRST 1 #define EV_PHASE_NEXT 2 #define EV_PHASE_LAST 4 /* * range of events, the cmd argument is the event type. To facilitate * matching 'struct ev' agains 'struct evspec', we try (when possible) * to use the same constants. Currently this works for all events * except EV_NON, EV_KAT, EV_NOFF, which all correspond to EVSPEC_NOTE */ struct evspec { #define EVSPEC_EMPTY EV_NULL #define EVSPEC_ANY 1 #define EVSPEC_NOTE EV_NON #define EVSPEC_CTL EV_CTL #define EVSPEC_PC EV_PC #define EVSPEC_CAT EV_CAT #define EVSPEC_BEND EV_BEND #define EVSPEC_NRPN EV_NRPN #define EVSPEC_RPN EV_RPN #define EVSPEC_XCTL EV_XCTL #define EVSPEC_XPC EV_XPC unsigned cmd; unsigned dev_min, dev_max; /* except for EMPTY */ unsigned ch_min, ch_max; /* except for EMPTY */ unsigned v0_min, v0_max; /* except for EMPTY, ANY */ unsigned v1_min, v1_max; /* except for EMPTY, ANY, CAT, PC */ }; /* * we use a static array (indexed by 'cmd') of the following * structures to lookup for events properties */ struct evinfo { char *ev, *spec; #define EV_HAS_DEV 0x01 /* if ev->dev is used */ #define EV_HAS_CH 0x02 /* if ev->ch is used */ unsigned flags; /* bitmap of above */ unsigned nparams; /* number of params (ie v0, v1) used */ unsigned nranges; /* number of ranges (ie v0, v1) used */ unsigned v0_min; /* min ev->v0 */ unsigned v0_max; /* max ev->v0 */ unsigned v1_min; /* min ev->v1 */ unsigned v1_max; /* max ev->v1 */ /* * sysex patterns specific */ #define EV_PATV0_HI 0x80 #define EV_PATV0_LO 0x81 #define EV_PATV1_HI 0x82 #define EV_PATV1_LO 0x83 #define EV_PATSUM 0x84 #define EV_PATNEGSUM 0x85 #define EV_PATSIZE 32 unsigned char *pattern; }; extern struct evinfo evinfo[EV_NUMCMD]; void ev_log(struct ev *); unsigned ev_str2cmd(struct ev *, char *); unsigned ev_phase(struct ev *); unsigned ev_eq(struct ev *, struct ev *); unsigned ev_match(struct ev *, struct ev *); void ev_map(struct ev *, struct evspec *, struct evspec *, struct ev *); unsigned evspec_str2cmd(struct evspec *, char *); void evspec_log(struct evspec *); void evspec_reset(struct evspec *); unsigned evspec_matchev(struct evspec *, struct ev *); unsigned evspec_eq(struct evspec *, struct evspec *); unsigned evspec_isec(struct evspec *, struct evspec *); unsigned evspec_in(struct evspec *, struct evspec *); int evspec_isamap(struct evspec *, struct evspec *); void evspec_map(struct evspec *, struct evspec *, struct evspec *, struct evspec *); /* * describes a controller number; this structures defines * how varius other routines bahave when a controller * event with the same number is found */ struct evctl { char *name; /* controller name or NULL if none */ unsigned defval; /* default value if type == EVCTL_FRAME */ }; extern struct evctl evctl_tab[EV_MAXCOARSE + 1]; #define EV_CTL_ISPARAM(ev) \ (evctl_tab[(ev)->ctl_num].defval == EV_UNDEF) #define EV_CTL_ISFRAME(ev) \ (evctl_tab[(ev)->ctl_num].defval != EV_UNDEF) #define EV_CTL_DEFVAL(ev) \ (evctl_tab[(ev)->ctl_num].defval) /* * return true if the given controller number is 14bit (fine) * and false if it is 7bit (coarse). The 'ctlbits' is * a 32bit bitmap, it is stored in per-device basis in * mididev structure */ #define EVCTL_ISFINE(xctlset, num) ((xctlset) & (1 << (num))) void evctl_conf(unsigned, char *, unsigned); void evctl_unconf(unsigned); unsigned evctl_lookup(char *, unsigned *); void evctl_init(void); void evctl_done(void); unsigned evctl_isreserved(unsigned); void evpat_unconf(unsigned); unsigned evpat_lookup(char *, unsigned *); unsigned evpat_set(unsigned, char *, unsigned char *, unsigned); void evpat_reset(void); #endif /* MIDISH_EV_H */ midish-1.4.0/exec.c010066400017510001751000000170661501104363400126010ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * this module implements the execution environment of scripts: * variables lists, procedure lists and various primitives * to handle them. * * a variable is a simple (name, value) pair. The name is stored * in a name structure (see name.h) and the value is stored in * a data structure (see data.h). * * a procedure is a (name, args, code) triplet. The name is the * (unique) name that identifies the procedure, 'args' is the * list of argument names and 'code' is the tree containing the * instructions of the procedure */ #include "utils.h" #include "exec.h" #include "data.h" #include "node.h" #include "cons.h" /* for cons_errxxx */ /* ----------------------------------------------- variable lists --- */ /* * create a new variable with the given name and value * and insert it on the given variable list */ struct var * var_new(struct name **list, char *name, struct data *data) { struct var *o; o = (struct var *)xmalloc(sizeof(struct var), "var"); o->data = data; name_init(&o->name, name); name_insert(list, (struct name *)o); return o; } /* * delete the given variable from the given list */ void var_delete(struct name **list, struct var *o) { name_remove(list, (struct name *)o); if (o->data != NULL) { data_delete(o->data); } name_done(&o->name); xfree(o); } /* * print "name = value" on stderr */ void var_log(struct var *o) { str_log(o->name.str); log_puts(" = "); if (o->data != NULL) data_log(o->data); else log_puts(""); } /* * delete all variables and clear the given list */ void var_empty(struct name **list) { while (*list != NULL) { var_delete(list, (struct var *)*list); } } /* * allocate a new procedure with the given name */ struct proc * proc_new(char *name) { struct proc *o; o = (struct proc *)xmalloc(sizeof(struct proc), "proc"); name_init(&o->name, name); o->args = NULL; o->code = NULL; return o; } /* * delete the given procedure: free name, arguments and code */ void proc_delete(struct proc *o) { node_delete(o->code); name_empty(&o->args); name_done(&o->name); xfree(o); } /* * free all procedures and clear the given list */ void proc_empty(struct name **first) { struct name *i; while (*first) { i = *first; name_remove(first, i); proc_delete((struct proc *)i); } *first = NULL; } /* * dump a procedure on stderr in the following format: * name (arg1, arg2, ...) * code_tree */ void proc_log(struct proc *o) { struct name *i; str_log(o->name.str); log_puts("("); if (o->args) { i = o->args; for (;;) { log_puts(i->str); i = i->next; if (!i) { break; } log_puts(", "); } } log_puts(")\n"); node_log(o->code, 0); } /* * create a new empty execution environment */ struct exec * exec_new(void) { struct exec *o; o = (struct exec *)xmalloc(sizeof(struct exec), "exec"); o->procs = NULL; o->globals = NULL; o->locals = &o->globals; o->procname = "top-level"; o->depth = 0; o->result = RESULT_OK; return o; } /* * delete the given execution environment. Must not be * called inside a procedure */ void exec_delete(struct exec *o) { if (o->depth != 0) { log_puts("exec_done: depth != 0\n"); panic(); } var_empty(&o->globals); proc_empty(&o->procs); xfree(o); } /* * find the variable with the given name in the * execution environment. If there a matching variable * in the local list we return it, else we search in the * global list. */ struct var * exec_varlookup(struct exec *o, char *name) { struct name *var; var = name_lookup(o->locals, name); if (var != NULL) { return (struct var *)var; } if (o->locals != &o->globals) { var = name_lookup(&o->globals, name); if (var != NULL) { return (struct var *)var; } } return NULL; } /* * find the procedure with the given name */ struct proc * exec_proclookup(struct exec *o, char *name) { return (struct proc *)name_lookup(&o->procs, name); } /* * add a new built-in procedure in the exec environment. */ void exec_newbuiltin(struct exec *o, char *name, unsigned func(struct exec *, struct data **), struct name *args) { struct proc *newp; newp = proc_new(name); newp->args = args; newp->code = node_new(&node_vmt_builtin, data_newuser((void *)func)); name_add(&o->procs, (struct name *)newp); } /* * add a new global variable in the exec environment */ void exec_newvar(struct exec *o, char *name, struct data *val) { var_new(&o->globals, name, val); } /* * dump all procs in the following format: * proc1(arg1, arg2, ...) * proc2(arg1, arg2, ...) * ... */ void exec_dumpprocs(struct exec *o) { struct proc *p; struct name *n; PROC_FOREACH(p, o->procs) { log_puts(p->name.str); log_puts("("); for (n = p->args; n != NULL; n = n->next) { log_puts(n->str); if (n->next) { log_puts(", "); } } log_puts(")\n"); } } /* * dump all global variables in the following format: * var1 = value * var2 = value * ... */ void exec_dumpvars(struct exec *o) { struct var *v; VAR_FOREACH(v, o->globals) { log_puts(v->name.str); log_puts(" = "); data_log(v->data); log_puts("\n"); } } /* * find a variable with the given name with value of type DATA_REF */ unsigned exec_lookupname(struct exec *o, char *name, char **val) { struct var *var; var = exec_varlookup(o, name); if (var == NULL || var->data->type != DATA_REF) { cons_errss(o->procname, name, "must be set to a reference"); return 0; } *val = var->data->val.ref; return 1; } /* * find a variable with the given name with value of type DATA_STRING */ unsigned exec_lookupstring(struct exec *o, char *name, char **val) { struct var *var; var = exec_varlookup(o, name); if (var == NULL || var->data->type != DATA_STRING) { cons_errss(o->procname, name, "must be set to a string"); return 0; } *val = var->data->val.str; return 1; } /* * find a variable with the given name with value of type DATA_LONG */ unsigned exec_lookuplong(struct exec *o, char *name, long *val) { struct var *var; var = exec_varlookup(o, name); if (var == NULL || var->data->type != DATA_LONG) { cons_errss(o->procname, name, "must be set to a number"); return 0; } *val = var->data->val.num; return 1; } /* * find a variable with the given name with value of type DATA_LIST */ unsigned exec_lookuplist(struct exec *o, char *name, struct data **val) { struct var *var; var = exec_varlookup(o, name); if (var == NULL || var->data->type != DATA_LIST) { cons_errss(o->procname, name, "must be set to a list"); return 0; } *val = var->data->val.list; return 1; } /* * find a variable with the given name convert its value to * a boolean and return it */ unsigned exec_lookupbool(struct exec *o, char *name, long *val) { struct var *var; unsigned res; var = exec_varlookup(o, name); if (var == NULL) { cons_errss(o->procname, name, "must be set to a bool"); return 0; } res = data_eval(var->data); *val = res; return 1; } midish-1.4.0/exec.h010066400017510001751000000060631501104363400126010ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_EXEC_H #define MIDISH_EXEC_H #include "name.h" enum RESULT { RESULT_ERR = 0, RESULT_OK, RESULT_BREAK, RESULT_CONTINUE, RESULT_RETURN, RESULT_EXIT }; struct data; struct var; struct proc; struct node; struct tree; struct exec; /* * a variable is a (identifier, value) pair */ struct var { struct name name; /* name (identifier) */ struct data *data; /* value, see data.h */ }; #define VAR_FOREACH(i,list) \ for (i = (struct var *)(list); \ i != NULL; \ i = (struct var *)(i->name.next)) /* * a procedure is a name, a list of argument names * and a the actual code (tree) */ struct proc { struct name name; struct name *args; struct node *code; }; #define PROC_FOREACH(i,list) \ for (i = (struct proc *)(list); \ i != NULL; \ i = (struct proc *)(i->name.next)) /* * exec is the interpreter's environment */ struct exec { struct name *globals; /* list of global variables */ struct name **locals; /* pointer to list of local variables */ struct name *procs; /* list of user and built-in procs */ char *procname; /* current proc name, for err messages */ #define EXEC_MAXDEPTH 40 unsigned depth; /* max depth of nested proc calls */ unsigned result; /* result of last operation */ }; struct var *var_new(struct name **, char *, struct data *); void var_delete(struct name **, struct var *); void var_log(struct var *); void var_empty(struct name **); struct exec *exec_new(void); void exec_delete(struct exec *); struct proc *exec_proclookup(struct exec *, char *); struct var *exec_varlookup(struct exec *, char *); void exec_newbuiltin(struct exec *, char *, unsigned (*)(struct exec *, struct data **), struct name *); void exec_newvar(struct exec *, char *, struct data *); void exec_dumpprocs(struct exec *); void exec_dumpvars(struct exec *); unsigned exec_lookupname(struct exec *, char *, char **); unsigned exec_lookupstring(struct exec *, char *, char **); unsigned exec_lookuplong(struct exec *, char *, long *); unsigned exec_lookuplist(struct exec *, char *, struct data **); unsigned exec_lookupbool(struct exec *, char *, long *); struct proc *proc_new(char *); void proc_delete(struct proc *); void proc_empty(struct name **); void proc_log(struct proc *); #endif /* MIDISH_EXEC_H */ midish-1.4.0/filt.c010066400017510001751000000230031501104363400125770ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * filt.c * * a simple midi filter. Rewrites input events according a set * of user-configurable rules. * */ #include "utils.h" #include "ev.h" #include "filt.h" #include "pool.h" #include "mux.h" #include "cons.h" unsigned filt_debug = 0; void rule_log(struct evspec *from, struct evspec *to) { evspec_log(from); log_puts(" > "); evspec_log(to); } /* * allocate and insert a new leaf node at the given location */ struct filtnode * filtnode_new(struct evspec *from, struct filtnode **loc) { struct filtnode *s; s = xmalloc(sizeof(struct filtnode), "filtnode"); s->dstlist = NULL; s->es = *from; s->next = *loc; *loc = s; return s; } /* * delete the branch at the given location */ void filtnode_del(struct filtnode **loc) { struct filtnode *s = *loc; while (s->dstlist) filtnode_del(&s->dstlist); *loc = s->next; xfree(s); } /* * find a node (or create one) such that the given evspec includes the previous * nodes and is included in the next nodes. Remove nodes that cause conflicts. */ struct filtnode * filtnode_mksrc(struct filtnode **root, struct evspec *from) { struct filtnode **ps, *s; /* * delete nodes causing conflicts */ for (ps = root; (s = *ps) != NULL;) { if (evspec_isec(&s->es, from) && !evspec_in(from, &s->es)) { if (filt_debug) { log_puts("filtnode_mksrc: "); evspec_log(&s->es); log_puts(": src intersect\n"); } filtnode_del(ps); continue; } ps = &s->next; } /* * find the correct place where to insert the source */ for (ps = root; (s = *ps) != NULL;) { if (evspec_eq(from, &s->es)) { if (filt_debug) log_puts("filtnode_mksrc: exact match\n"); return s; } if (evspec_in(from, &s->es)) { if (filt_debug) { log_puts("filtnode_mksrc: "); evspec_log(from); log_puts(" in "); evspec_log(&s->es); log_puts("\n"); } break; } if (filt_debug) { log_puts("filtnode_mksrc: "); evspec_log(from); log_puts(": skipped\n"); } ps = &s->next; } return filtnode_new(from, ps); } /* * find a node (or create one) such that the given evspec has no intersection * with the nodes on the list. Remove nodes that may cause this. */ struct filtnode * filtnode_mkdst(struct filtnode *s, struct evspec *to) { struct filtnode *d, **pd; /* * remove conflicting destinations */ for (pd = &s->dstlist; (d = *pd) != NULL;) { if (evspec_eq(&d->es, to)) return d; if (evspec_isec(&d->es, to) || to->cmd == EVSPEC_EMPTY || d->es.cmd == EVSPEC_EMPTY) { if (filt_debug) { log_puts("filtnode_mkdst: "); rule_log(&s->es, &d->es); log_puts(": src intersect\n"); } filtnode_del(pd); continue; } pd = &d->next; } return filtnode_new(to, pd); } /* * initialize a filter */ void filt_init(struct filt *o) { o->map = NULL; o->vcurve = NULL; o->transp = NULL; } /* * remove all filtering rules and all states */ void filt_reset(struct filt *o) { while (o->map) filtnode_del(&o->map); while (o->transp) filtnode_del(&o->transp); while (o->vcurve) filtnode_del(&o->vcurve); } /* * destroy a filter */ void filt_done(struct filt *o) { filt_reset(o); o->map = o->transp = o->vcurve = (void *)0xdeadbeef; } /* * return velocity adjusted by curve with the given weight. * the weight must be in the 1..127 range, 64 means neutral */ unsigned vcurve(unsigned nweight, unsigned x) { if (x == 0) return 0; nweight--; if (x <= nweight) { if (nweight == 0) return 127; else return 1 + (126 - nweight) * (x - 1) / nweight; } else { if (nweight == 126) return 1; else return 127 - nweight * (127 - x) / (126 - nweight); } } /* * match event against all sources and for each source * generate output events */ unsigned filt_do(struct filt *o, struct ev *in, struct ev *out) { struct ev *ev; struct filtnode *s; struct filtnode *d; unsigned nev, i; nev = 0; for (s = o->map;; s = s->next) { if (s == NULL) break; if (filt_debug) { log_puts("filt_do: in = "); ev_log(in); log_puts("\n"); } if (evspec_matchev(&s->es, in)) { for (d = s->dstlist; d != NULL; d = d->next) { if (d->es.cmd == EVSPEC_EMPTY) continue; ev_map(in, &s->es, &d->es, &out[nev]); if (filt_debug) { log_puts("filt_do: ("); rule_log(&s->es, &d->es); log_puts("): "); ev_log(in); log_puts(" -> "); ev_log(&out[nev]); log_puts("\n"); } nev++; } break; } } if (!EV_ISNOTE(in)) return nev; for (i = 0, ev = out; i < nev; i++, ev++) { for (d = o->vcurve; d != NULL; d = d->next) { if (!evspec_matchev(&d->es, ev)) continue; ev->note_vel = vcurve(d->u.vel.nweight, ev->note_vel); break; } for (d = o->transp; d != NULL; d = d->next) { if (!evspec_matchev(&d->es, ev)) continue; ev->note_num += d->u.transp.plus; ev->note_num &= 0x7f; break; } } return nev; } /* * remove all rules that are included in the from->to argument. */ void filt_mapdel(struct filt *f, struct evspec *from, struct evspec *to) { struct filtnode *s, **ps; struct filtnode *d, **pd; for (ps = &f->map; (s = *ps) != NULL;) { if (evspec_in(&s->es, from)) { for (pd = &s->dstlist; (d = *pd) != NULL;) { if (evspec_in(&d->es, to)) { if (filt_debug) { log_puts("filt_mapdel: "); rule_log(&s->es, &d->es); log_puts(": removed\n"); } filtnode_del(pd); continue; } pd = &d->next; } } if (s->dstlist == NULL) { if (filt_debug) { log_puts("filt_mapdel: "); evspec_log(&s->es); log_puts(": empty, removed\n"); } filtnode_del(ps); continue; } ps = &s->next; } } /* * add a new rule to map events in "from" range to events in "to" range */ void filt_mapnew(struct filt *f, struct evspec *from, struct evspec *to) { struct filtnode *s; if (filt_debug) { log_puts("filt_mapnew: adding "); rule_log(from, to); log_puts("\n"); } /* * check if ranges are ok, do nothing if they are not */ if (to->cmd != EVSPEC_EMPTY && !evspec_isamap(from, to)) return; s = filtnode_mksrc(&f->map, from); filtnode_mkdst(s, to); } struct filtnode * filt_detach(struct filt *o) { struct filtnode *list, *s; for (list = NULL; (s = o->map) != NULL;) { o->map = s->next; s->next = list; list = s; } return list; } void filt_chgin(struct filt *o, struct evspec *from, struct evspec *to, int swap) { struct evspec newspec; struct filtnode *s, *list; struct filtnode *d; list = filt_detach(o); while ((s = list) != NULL) { if (evspec_in(&s->es, from)) { evspec_map(&s->es, from, to, &newspec); } else if (swap && evspec_in(&s->es, to)) { evspec_map(&s->es, to, from, &newspec); } else { newspec = s->es; } if (filt_debug) { log_puts("filt_chgin: "); evspec_log(&s->es); log_puts(" -> "); evspec_log(&newspec); log_puts("\n"); } while ((d = s->dstlist) != NULL) { filt_mapnew(o, &newspec, &d->es); filtnode_del(&s->dstlist); } filtnode_del(&list); } } void filt_chgout(struct filt *o, struct evspec *from, struct evspec *to, int swap) { struct evspec newspec; struct filtnode *s, *list; struct filtnode *d; list = filt_detach(o); while ((s = list) != NULL) { while ((d = s->dstlist) != NULL) { if (evspec_in(&d->es, from)) { evspec_map(&d->es, from, to, &newspec); } else if (swap && evspec_in(&d->es, to)) { evspec_map(&d->es, to, from, &newspec); } else { newspec = d->es; } if (filt_debug) { log_puts("filt_chgout: "); evspec_log(&d->es); log_puts(" -> "); evspec_log(&newspec); log_puts("\n"); } filt_mapnew(o, &s->es, &newspec); filtnode_del(&s->dstlist); } filtnode_del(&list); } } void filt_transp(struct filt *f, struct evspec *from, int plus) { struct filtnode *s; if (from->cmd != EVSPEC_ANY && from->cmd != EVSPEC_NOTE) { log_puts("filt_transp: set must contain notes\n"); return; } if (from->cmd == EVSPEC_NOTE && (from->v0_min != 0 || from->v0_max != EV_MAXCOARSE)) { log_puts("filt_transp: note range must be full\n"); return; } s = filtnode_mksrc(&f->transp, from); s->u.transp.plus = plus & 0x7f; } void filt_vcurve(struct filt *f, struct evspec *from, int weight) { struct filtnode *s; if (from->cmd != EVSPEC_ANY && from->cmd != EVSPEC_NOTE) { log_puts("filt_vcurve: set must contain notes\n"); return; } s = filtnode_mksrc(&f->vcurve, from); s->u.vel.nweight = (64 - weight) & 0x7f; } unsigned filt_evcnt(struct filt *f, unsigned cmd) { struct filtnode *s; struct filtnode *d; unsigned cnt = 0; for (s = f->map; s != NULL; s = s->next) { if (s->es.cmd == cmd) cnt++; for (d = s->dstlist; d != NULL; d = d->next) { if (d->es.cmd == cmd) cnt++; } } for (s = f->vcurve; s != NULL; s = s->next) { if (s->es.cmd == cmd) cnt++; } for (s = f->transp; s != NULL; s = s->next) { if (s->es.cmd == cmd) cnt++; } return cnt; } midish-1.4.0/filt.h010066400017510001751000000042241501104363400126100ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_FILT_H #define MIDISH_FILT_H #include "ev.h" /* * source against which the input event is matched */ struct filtnode { struct evspec es; /* events handled by this branch */ struct filtnode *dstlist; /* destinations for this source */ struct filtnode *next; /* next source in the list */ union { struct { unsigned nweight; } vel; struct { int plus; } transp; } u; }; #define FILT_MAXNRULES 32 struct filt { struct filtnode *map; /* root of map rules */ struct filtnode *vcurve; /* root of vcurve rules */ struct filtnode *transp; /* root of transp rules */ }; unsigned vcurve(unsigned, unsigned); void filt_init(struct filt *); void filt_done(struct filt *); void filt_reset(struct filt *); unsigned filt_do(struct filt *, struct ev *, struct ev *); void filt_mapnew(struct filt *, struct evspec *, struct evspec *); void filt_mapdel(struct filt *, struct evspec *, struct evspec *); void filt_chgin(struct filt *, struct evspec *, struct evspec *, int); void filt_chgout(struct filt *, struct evspec *, struct evspec *, int); void filt_transp(struct filt *, struct evspec *, int); void filt_vcurve(struct filt *, struct evspec *, int); unsigned filt_evcnt(struct filt *, unsigned); struct filtnode *filtnode_new(struct evspec *, struct filtnode **); void filtnode_del(struct filtnode **); extern unsigned filt_debug; #endif /* MIDISH_FILT_H */ midish-1.4.0/frame.c010066400017510001751000001275401501104363400127460ustar00alexalex/* * Copyright (c) 2003-2019 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * a seqptr structure points to a location of the associated track and * can move forward, read events and write events. The location is * defined by the current tic and the current event within the * tick. In some sense a seqptr structure is for a track what is a * head for a tape. * * The seqptr structure contain a cursor (a pos/delta pair) which is * used to walk through the track as follows: * * - In play mode, the field 'o->pos' points to the event to be * played. The 'delta' field contains the number of ticks elapsed * since the last played event. Thus when o->delta reaches * o->pos->delta, the event is to be played. * * - In record mode, the field 'o->pos' points to the next event * that the event being recorded. * * It maintains a "state list" that contains the complete state of the * track at the current postion: the list of all sounding notes, the * state of all controllers etc... This allows to ensure full * consistency when a track is modified. So, always use the following * 6 primitives to modify a track. * * Moving within the track: * * there is no low-level primitives for moving forward, instead * reading primitives should be used (and the result should be * ignored). That's because the state list have to be kept up to * date. Thus there is no way to go backward. * * Reading: * * there are 2 low-level primitives for reading: seqptr_ticskip() * skips empty tics (moves forward) and seqptr_evget() reads * events. There can be multiple seqptr structures reading the * same track. * * Writing: * * there are 2 low-level routines for writing: seqptr_ticput() * adds empty tics and seqptr_evput() adds an event at the * current postion. The state list is updated as new events were * read with seqptr_evget(). If there is a writer, there must not * be readers. In order to keep track consistency, events must * only be appended at the end-of-track; indeed, if we write an * arbitrary event in the middle of a track, generally it isn't * possible to solve all conflicts. * * Erasing: * * there are 2 low-level routines for erasing: seqptr_ticdel() * deletes empty tics and seqptr_evdel() deletes the next event. * The state list is not updated since the current position * haven't changed. However, to keep state of erased events both * functions take as optionnal argument a state list, that is * updated as events and blank space were read with * seqptr_evget() and seqptr_ticskip() * * * Common errors and pitfals * ------------------------- * * before adding new code, or changing existing code check for the * following errors: * * - only call seqptr_evput() at the end of track. the only * exception of this rule, is when a track is completely * rewritten. So the following loop: * * for (;;) { * st = seqptr_evdel(sp, &slist); * seqptr_evput(sp, &st->ev); * ... * } * * is ok only if _all_ events are removed. This is the only * correct way of consistetly modifying a track. * * - when rewriting a track one must use a separate statelist * for events being removed, so the seqptr->statelist is used for * writing new events. When starting rewriting a track on a * given position, be sure to initialise the 'deleted events' * statelist with statelist_dup(), and not statelist_init(). * Example: * * seqptr_skip(sp, pos); * statelist_dup(&slist); * for (;;) { * seqptr_evdel(sp, &slist); * ... * } * * - when working with a statelist initialised with statlist_dup() * be aware that tags are not copied. The only fields that are copied * are those managed by statelist_update() routine. So we must first * duplicate the statelist and then tag states. Example: * * seqptr_skip(sp, pos); * statelist_dup(&slist, &sp->slist); * for (st = slist.first; st != NULL; st = st->next) { * st->tag = ... * } * * it is _not_ ok, to iterate over sp->statelist and then to dup it. * * - seqptr_tic{skip,put,del}() outdates the statelist of the * seqptr. This purges unused states and updates the * STATE_CHANGED flag. However, if we use seqptr_ticskip() with * seqptr_evdel(), we'll not outdate the right state list. So we * have to rewrite blank space too (not only events) as * follows: * * for (;;) { * delta = seqptr_ticdel(sp, &slist); * seqptr_ticput(sp, delta); * * st = seqptr_evdel(sp, &slist); * seqptr_evput(sp, st->ev); * } * */ /* * TODO: * * - seqptr_merge1() and seqptr_merge2 are supposed to use * seqptr_restore() and seqptr_cancel() * * - seqptr_cancel() and seqptr_restore() shouldn't * write new events if the current state is * the same as the event we write */ #include "utils.h" #include "track.h" #include "defs.h" #include "filt.h" #include "frame.h" #include "pool.h" struct pool seqptr_pool; void seqptr_pool_init(unsigned size) { pool_init(&seqptr_pool, "seqptr", sizeof(struct seqptr), size); } void seqptr_pool_done(void) { pool_done(&seqptr_pool); } /* * initialize a seqptr structure at the beginning of * the given track. */ struct seqptr * seqptr_new(struct track *t) { struct seqptr *sp; sp = (struct seqptr *)pool_new(&seqptr_pool); statelist_init(&sp->statelist); sp->link = NULL; sp->pos = t->first; sp->delta = 0; sp->tic = 0; return sp; } /* * release the seqptr structure, free statelist etc... */ void seqptr_del(struct seqptr *sp) { if (sp->link != NULL) sp->link->link = NULL; statelist_done(&sp->statelist); pool_del(&seqptr_pool, sp); } /* * return true if the end of the track is reached */ int seqptr_eot(struct seqptr *sp) { return sp->delta == sp->pos->delta && sp->pos->ev.cmd == EV_NULL; } /* * link a reader and a writer so that reader sees consistently * changes by the writer */ void seqptr_link(struct seqptr *sp1, struct seqptr *sp2) { if (sp1->link != NULL || sp2->link != NULL) { log_puts("seqptr structures already linked\n"); panic(); } sp1->link = sp2; sp2->link = sp1; } /* * return the state structure of the next available event or NULL if * there is no next event in the current tick. The state list is * updated accordingly. */ struct state * seqptr_evget(struct seqptr *sp) { struct state *st; if (sp->delta != sp->pos->delta || sp->pos->ev.cmd == EV_NULL) { return 0; } st = statelist_update(&sp->statelist, &sp->pos->ev); if (st->flags & STATE_NEW) { st->pos = sp->pos; st->tic = sp->tic; } sp->pos = sp->pos->next; sp->delta = 0; return st; } /* * delete the next event from the track. If the 'slist' argument is * not NULL, then the state is updated as it was read with * seqptr_evget */ struct state * seqptr_evdel(struct seqptr *sp, struct statelist *slist) { struct state *st; struct seqev *next; if (sp->delta != sp->pos->delta || sp->pos->ev.cmd == EV_NULL) { return NULL; } if (slist) st = statelist_update(slist, &sp->pos->ev); else st = NULL; next = sp->pos->next; next->delta += sp->pos->delta; /* unlink and delete sp->pos */ *(sp->pos->prev) = next; next->prev = sp->pos->prev; seqev_del(sp->pos); /* fix current position */ sp->pos = next; return st; } /* * insert an event and put the cursor just after it, the state list is * updated and the state of the new event is returned. */ struct state * seqptr_evput(struct seqptr *sp, struct ev *ev) { struct seqptr *link; struct seqev *se; se = seqev_new(); se->ev = *ev; se->delta = sp->delta; sp->pos->delta -= sp->delta; /* link to the list */ se->next = sp->pos; se->prev = sp->pos->prev; *se->prev = se; sp->pos->prev = &se->next; /* if there's a reader update its pointer */ link = sp->link; if (link != NULL && link->pos == sp->pos) link->pos = se; /* fix the position pointer and update the state */ sp->pos = se; return seqptr_evget(sp); } /* * move forward until the next event, but not more than 'max' * tics. The number of tics we moved is returned. States of all * terminated events are purged */ unsigned seqptr_ticskip(struct seqptr *sp, unsigned max) { unsigned ntics; ntics = sp->pos->delta - sp->delta; if (ntics > max) { ntics = max; } if (ntics > 0) { sp->delta += ntics; sp->tic += ntics; statelist_outdate(&sp->statelist); } return ntics; } /* * remove blank space at the current position, same semantics as * seqptr_ticskip() */ unsigned seqptr_ticdel(struct seqptr *sp, unsigned max, struct statelist *slist) { struct seqptr *link; unsigned ntics; ntics = sp->pos->delta - sp->delta; if (ntics > max) { ntics = max; } sp->pos->delta -= ntics; if (slist != NULL && max > 0) { statelist_outdate(slist); } /* shift writer if affected */ link = sp->link; if (link != NULL && link->pos == sp->pos && link->delta >= sp->delta) { sp->link->delta -= ntics; sp->link->tic -= ntics; } return ntics; } /* * insert blank space at the current position */ void seqptr_ticput(struct seqptr *sp, unsigned ntics) { struct seqptr *link; if (ntics == 0) return; sp->pos->delta += ntics; sp->delta += ntics; sp->tic += ntics; statelist_outdate(&sp->statelist); /* shift writer if affected */ link = sp->link; if (link != NULL && link->pos == sp->pos && link->delta >= sp->delta) { sp->link->delta += ntics; sp->link->tic += ntics; } } /* * move forward 'ntics', if the end-of-track is reached then return * the number of reamaining tics. Used for reading on a track */ unsigned seqptr_skip(struct seqptr *sp, unsigned ntics) { unsigned delta; while (ntics > 0) { while (seqptr_evget(sp)) ; /* nothing */ delta = seqptr_ticskip(sp, ntics); /* * check if the end of the track was reached */ if (delta == 0) break; ntics -= delta; } return ntics; } /* * move forward 'ntics', if the end-of-track is reached then fill with * blank space. Used for writing on a track */ void seqptr_seek(struct seqptr *sp, unsigned ntics) { ntics = seqptr_skip(sp, ntics); if (ntics > 0) seqptr_ticput(sp, ntics); } /* * move the next frame of the current tick to the given track. Must * not be called on a non-frame-starting event. */ void seqptr_framerm(struct seqptr *sp, struct track *f) { struct seqev **save_pos, *se, *spos, *fpos; unsigned save_delta, sdelta, phase; if (sp->delta != sp->pos->delta) { log_puts("seqptr_framerm: not called at event position\n"); panic(); } if (sp->pos->ev.cmd == EV_NULL) { log_puts("seqptr_framerm: no event to remove\n"); panic(); } track_clear(f); fpos = f->first; /* * Save current postition. */ save_pos = sp->pos->prev; save_delta = sp->delta; /* * Move all events that belong to the frame. */ spos = sp->pos; sdelta = sp->delta; phase = ev_phase(&spos->ev); /* move event to frame track */ se = spos; spos = se->next; seqev_rm(se); seqev_ins(fpos, se); for (;;) { if (phase & EV_PHASE_LAST) break; if (spos->ev.cmd == EV_NULL) { /* * XXX: this can't happen, call panic() here ? */ log_puts("seqptr_framerm: unterminated frame\n"); track_clear(f); return; } /* move to next event */ fpos->delta += spos->delta - sdelta; sdelta = spos->delta; /* process next event */ if (ev_match(&f->first->ev, &spos->ev)) { phase = ev_phase(&spos->ev); /* move event to frame track */ se = spos; spos = se->next; seqev_rm(se); seqev_ins(fpos, se); } else { /* skip event */ spos = spos->next; sdelta = 0; } } /* * restore position. */ sp->pos = *save_pos; sp->delta = save_delta; } /* * insert the given frame at the end of the current tick. As the first * event of the frame is not seen yet, the state list is not * affected. As this is a stateless operation, it's the caller * responsability to avoid conflicts. */ void seqptr_frameadd(struct seqptr *sp, struct track *f) { struct seqev *se, *spos, **save_pos; unsigned ntics, offs, sdelta, save_delta; /* * Save current postition. */ save_pos = sp->pos->prev; save_delta = sp->delta; spos = sp->pos; sdelta = sp->delta; for (;;) { if (f->first->ev.cmd == EV_NULL) break; se = f->first; offs = se->delta; se->delta = 0; seqev_rm(se); /* * move forward offs ticks, possibly inserting * empty space */ while (1) { ntics = spos->delta - sdelta; if (ntics > offs) ntics = offs; sdelta += ntics; offs -= ntics; if (offs == 0) break; /* if reached the end, append space */ if (spos->ev.cmd == EV_NULL) { spos->delta += offs; sdelta += offs; offs = 0; break; } spos = spos->next; sdelta = 0; } /* make the event the last of the tick */ while (sdelta == spos->delta && spos->ev.cmd != EV_NULL) { sdelta = 0; spos = spos->next; } se->delta = sdelta; spos->delta -= sdelta; sdelta = 0; /* link to the list */ se->next = spos; se->prev = spos->prev; *se->prev = se; spos->prev = &se->next; } /* * Restore current position. */ sp->pos = *save_pos; sp->delta = save_delta; } /* * generate an event that will suspend the frame of the given state; * the state is unchanged and may belong to any statelist. Return 1 * if an event was generated. */ unsigned seqptr_cancel(struct seqptr *sp, struct state *st) { struct ev ev; if (!EV_ISNOTE(&st->ev) && !(st->phase & EV_PHASE_LAST)) { if (state_cancel(st, &ev)) { seqptr_evput(sp, &ev); } return 1; } return 0; } /* * generate an event that will restore the frame of the given * state; the state is unchanged and may belong to any statelist. * Return 1 if an event was generated. */ unsigned seqptr_restore(struct seqptr *sp, struct state *st) { struct ev ev; if (!EV_ISNOTE(&st->ev) && !(st->phase & EV_PHASE_LAST)) { if (state_restore(st, &ev)) { seqptr_evput(sp, &ev); } return 1; } return 0; } /* * erase the event contained in the given state. Everything happens as * the event never existed on the track. Returns the new state, or * NULL if there is no more state. */ void seqptr_rmlast(struct seqptr *sp, struct state **pst) { struct state *st = *pst; struct seqev *i, *prev, *cur, *next; #ifdef FRAME_DEBUG log_puts("seqptr_rmlast: "); ev_log(&st->ev); log_puts(" removing last event\n"); #endif /* * start a the first event of the frame and iterate until the * current postion. Store in 'cur' the event to delete and in * 'prev' the event before 'cur' that belongs to the same * frame */ i = cur = st->pos; prev = NULL; for (;;) { i = i->next; if (i == sp->pos) { break; } if (state_match(st, &i->ev)) { prev = cur; cur = i; } } /* * remove the event from the track * (but not the blank space) */ next = cur->next; next->delta += cur->delta; if (next == sp->pos) { sp->delta += cur->delta; } next->prev = cur->prev; *(cur->prev) = next; seqev_del(cur); /* * update the state; if we deleted the first event of the * frame, the state no more exists, so purge it */ if (prev == NULL) { statelist_rm(&sp->statelist, st); state_del(st); *pst = NULL; } else { st->ev = prev->ev; st->phase = st->pos == prev ? EV_PHASE_FIRST : EV_PHASE_NEXT; } } /* * erase the frame contained in the given state until the given * position. Everything happens as the frame never existed on the * track. Returns always NULL, for consistency with seqptr_rmlast(). */ void seqptr_rmprev(struct seqptr *sp, struct state **pst) { struct state *st = *pst; struct seqev *i, *next; #ifdef FRAME_DEBUG log_puts("seqptr_rmprev: "); ev_log(&st->ev); log_puts(" removing whole frame\n"); #endif /* * start a the first event of the frame and iterate until the * current postion removing all events of the frame. */ i = st->pos; for (;;) { if (state_match(st, &i->ev)) { /* * remove the event from the track * (but not the blank space) */ next = i->next; next->delta += i->delta; if (next == sp->pos) { sp->delta += i->delta; } next->prev = i->prev; *(i->prev) = next; seqev_del(i); i = next; } else { i = i->next; } if (i == sp->pos) { break; } } statelist_rm(&sp->statelist, st); state_del(st); *pst = NULL; } /* * merge low priority event: if s1 doen't conflict with high priority * events in the track, then store it in the track. Else drop it. This * routine must be called before seqptr_evmerge2() within the current * tick. */ struct state * seqptr_evmerge1(struct seqptr *pd, struct state *s1) { struct state *sd; /* * ignore bogus events */ if (s1->flags & (STATE_BOGUS | STATE_NESTED)) return NULL; sd = statelist_lookup(&pd->statelist, &s1->ev); if (sd != NULL) { if (sd->tag == 0 && !(sd->phase & EV_PHASE_LAST)) return NULL; } else if (!(s1->phase & EV_PHASE_FIRST)) return NULL; if (sd == NULL || !state_eq(sd, &s1->ev)) sd = seqptr_evput(pd, &s1->ev); sd->tag = 1; return sd; } /* * merge high priority event: if ev2 conflicts with low priority events * in the track, discard them and store ev2. This routine must be * called once seqptr_evmerge1() was called for all low prio events * within the current tick. */ unsigned seqptr_evmerge2(struct seqptr *pd, struct statelist *slist1, struct ev *ev2, struct ev *ca) { struct state *sd, *s1; unsigned phase2; unsigned rc = 0; phase2 = ev_phase(ev2); sd = statelist_lookup(&pd->statelist, ev2); if (sd != NULL) { if (sd->tag == 0) { /* EV_PHASE_LAST is never ambigous */ if (phase2 == EV_PHASE_LAST && !EV_ISNOTE(&sd->ev)) { s1 = statelist_lookup(slist1, ev2); if (s1 != NULL && s1->phase != EV_PHASE_LAST) { if (!state_eq(s1, ev2)) sd = seqptr_evput(pd, &s1->ev); sd->tag = 1; return 0; } } } else { /* * phase could be (FIRST | NEXT) instead of * NEXT only, but in this case we wouldn't be * here because sd would be NULL. */ if (phase2 & EV_PHASE_FIRST) { if (EV_ISNOTE(&sd->ev)) { if (sd->phase != EV_PHASE_LAST) { rc = state_cancel(sd, ca); seqptr_rmprev(pd, &sd); } } else if (sd->flags & STATE_CHANGED) seqptr_rmlast(pd, &sd); } } } else { /* * If there's no state, this is necessarily the a new * frame */ if (!(phase2 & EV_PHASE_FIRST)) { log_puts("seqptr_evmerge2: "); ev_log(ev2); log_puts(": missing state\n"); panic(); } } if (sd == NULL || !state_eq(sd, ev2)) sd = seqptr_evput(pd, ev2); sd->tag = 0; return rc; } /* * Merge track "src" (high priority) in track "dst" (low prority) * resolving all conflicts, so that "dst" is consistent. */ void track_merge(struct track *dst, struct track *src) { struct state *s; struct seqptr *p2, *pd; struct statelist orglist; unsigned delta1, delta2, deltad; struct ev ca; pd = seqptr_new(dst); p2 = seqptr_new(src); statelist_init(&orglist); for (;;) { /* * remove all events from 'dst' and put them back on * on 'dst' by merging them with the state table of * 'src'. The 'orglist' state table is updated so it * always contain the exact state of the original * 'dst' track. */ for(;;) { s = seqptr_evdel(pd, &orglist); if (s == NULL) break; seqptr_evmerge1(pd, s); } /* * move all events from 'src' to 'dst' by merging them * with the original state of 'dst'. */ for (;;) { s = seqptr_evget(p2); if (s == NULL) break; seqptr_evmerge2(pd, &orglist, &s->ev, &ca); } /* * move to the next non empty tick: the next tic is the * smaller position of the next event of each track */ delta1 = pd->pos->delta - pd->delta; delta2 = p2->pos->delta - p2->delta; if (delta1 > 0) { deltad = delta1; if (delta2 > 0 && delta2 < deltad) deltad = delta2; } else if (delta2 > 0) { deltad = delta2; } else { /* both delta1 and delta2 are zero */ break; } (void)seqptr_ticskip(p2, deltad); (void)seqptr_ticdel(pd, deltad, &orglist); seqptr_ticput(pd, deltad); } statelist_done(&orglist); seqptr_del(p2); seqptr_del(pd); track_chomp(dst); } /* * move/copy/blank a portion of the given track. All operations are * consistent: notes are always completely copied/moved/erased and * controllers (and other) are cut when necessary * * if the 'copy' flag is set, then the selection is copied in * track 'dst'. If 'blank' flag is set, then the selection is * cleanly removed from the 'src' track */ void track_move(struct track *src, unsigned start, unsigned len, struct evspec *es, struct track *dst, unsigned copy, unsigned blank) { unsigned delta; struct seqptr *sp, *dp; /* current src & dst track states */ struct statelist slist; /* original src track state */ struct state *st; #define TAG_KEEP 1 /* frame is not erased */ #define TAG_COPY 2 /* frame is copied */ /* please gcc */ sp = dp = NULL; if (len == 0) return; if (copy) { track_clear(dst); dp = seqptr_new(dst); } sp = seqptr_new(src); /* * go to the start position and tag all frames as * not being copied and not being erased */ (void)seqptr_skip(sp, start); statelist_dup(&slist, &sp->statelist); for (st = slist.first; st != NULL; st = st->next) { st->tag = TAG_KEEP; } /* * cancel/tag frames that will be erased (blank only) */ if (blank) { for (st = slist.first; st != NULL; st = st->next) { if (!EV_ISNOTE(&st->ev) && state_inspec(st, es) && seqptr_cancel(sp, st)) st->tag &= ~TAG_KEEP; } } /* * copy the first tic: tag/copy/erase new frames. This is the * last chance for already tagged frames to terminate and to * avoid being restored in the copy * */ for (;;) { st = seqptr_evdel(sp, &slist); if (st == NULL) break; if ((st->phase & EV_PHASE_FIRST) || (st->phase & EV_PHASE_NEXT && !EV_ISNOTE(&st->ev))) { st->tag &= ~TAG_COPY; if (state_inspec(st, es)) st->tag |= TAG_COPY; } if (st->phase & EV_PHASE_FIRST) { st->tag &= ~TAG_KEEP; } if (copy && (st->tag & TAG_COPY)) seqptr_evput(dp, &st->ev); if (!blank || (st->tag & TAG_KEEP)) { seqptr_evput(sp, &st->ev); } } /* * in the copy, restore frames that weren't updated by the * first tic. */ if (copy) { for (st = slist.first; st != NULL; st = st->next) { if (EV_ISNOTE(&st->ev) || !state_inspec(st, es)) continue; if (!(st->tag & TAG_COPY) && seqptr_restore(dp, st)) { st->tag |= TAG_COPY; } } } /* * tag/copy/erase frames during 'len' tics */ for (;;) { delta = seqptr_ticdel(sp, len, &slist); if (copy) seqptr_ticput(dp, delta); seqptr_ticput(sp, delta); len -= delta; if (len == 0) break; st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) { st->tag = state_inspec(st, es) ? TAG_COPY : TAG_KEEP; } if (copy && (st->tag & TAG_COPY)) { seqptr_evput(dp, &st->ev); } if (!blank || (st->tag & TAG_KEEP)) { seqptr_evput(sp, &st->ev); } } /* * cancel all copied frames (that are tagged). * cancelled frames are untagged, so they will stop * being copied */ if (copy) { for (st = slist.first; st != NULL; st = st->next) { if (seqptr_cancel(dp, st)) st->tag &= ~TAG_COPY; } } /* * move the first tic of the 'end' boundary. New frames are * tagged as "not to erase". This is the last chance for * untagged frames (those being erased) to terminate and to * avoid being restored */ for (;;) { st = seqptr_evdel(sp, &slist); if (st == NULL) break; if ((st->phase & EV_PHASE_FIRST) || (st->phase & EV_PHASE_NEXT && !EV_ISNOTE(&st->ev))) { st->tag |= TAG_KEEP; } if (st->phase & EV_PHASE_FIRST) st->tag &= ~TAG_COPY; if (copy && (st->tag & TAG_COPY)) { seqptr_evput(dp, &st->ev); } if (!blank || (st->tag & TAG_KEEP)) { seqptr_evput(sp, &st->ev); } } /* * retore/tag frames that are not tagged. */ for (st = slist.first; st != NULL; st = st->next) { if (!(st->tag & TAG_KEEP) && seqptr_restore(sp, st)) { st->tag |= TAG_KEEP; } } /* * copy frames for whose state couldn't be * canceled (note events) */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); if (copy) seqptr_ticput(dp, delta); seqptr_ticput(sp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) { st->tag &= ~TAG_COPY; st->tag |= TAG_KEEP; } if (copy && (st->tag & TAG_COPY)) { seqptr_evput(dp, &st->ev); } if (!blank || (st->tag & TAG_KEEP)) { seqptr_evput(sp, &st->ev); } } statelist_done(&slist); seqptr_del(sp); if (copy) { seqptr_del(dp); track_chomp(dst); } if (blank) track_chomp(src); #undef TAG_BLANK #undef TAG_COPY } /* * quantize the given track */ void track_quantize(struct track *src, struct evspec *es, unsigned start, unsigned len, unsigned offset, unsigned quant, unsigned rate) { unsigned tic, qtic; struct track qt; struct seqptr *sp, *qp; struct state *st; struct statelist slist; unsigned remaind; unsigned fluct, notes; int ofs, delta; track_init(&qt); sp = seqptr_new(src); qp = seqptr_new(&qt); /* * go to start position and untag all events * (tagged = will be quantized) */ (void)seqptr_skip(sp, start); statelist_dup(&slist, &sp->statelist); for (st = slist.first; st != NULL; st = st->next) { st->tag = 0; } seqptr_seek(qp, start); tic = qtic = start; ofs = 0; /* * go ahead and copy all events to quantize during 'len' tics, * while stretching the time scale in the destination track */ fluct = 0; notes = 0; for (;;) { delta = seqptr_ticdel(sp, start + len - tic, &slist); seqptr_ticput(sp, delta); tic += delta; if (tic >= start + len) break; st = seqptr_evdel(sp, &slist); if (st == NULL) break; remaind = quant != 0 ? (tic - start + offset) % quant : 0; if (remaind < quant / 2) { ofs = - ((remaind * rate + 99) / 100); } else { ofs = ((quant - remaind) * rate + 99) / 100; } delta = tic + ofs - qtic; #ifdef FRAME_DEBUG if (delta < 0) { log_puts("track_quantize: delta < 0\n"); panic(); } #endif seqptr_ticput(qp, delta); qtic += delta; if (st->phase & EV_PHASE_FIRST) { if (evspec_matchev(es, &st->ev)) { st->tag = 1; if (EV_ISNOTE(&st->ev)) { fluct += (ofs < 0) ? -ofs : ofs; notes++; } } else st->tag = 0; } if (st->tag) { seqptr_evput(qp, &st->ev); } else { seqptr_evput(sp, &st->ev); } } /* * finish quantised (tagged) events */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); seqptr_ticput(sp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) st->tag = 0; seqptr_ticput(qp, delta); if (st->tag) { seqptr_evput(qp, &st->ev); } else { seqptr_evput(sp, &st->ev); } } track_merge(src, &qt); statelist_done(&slist); seqptr_del(sp); seqptr_del(qp); track_done(&qt); if (notes > 0) { log_puts("quantize: "); log_putu(notes); log_puts(" notes, fluctuation = "); log_putu(100 * fluct / notes); log_puts("% of a tick\n"); } } /* * quantize the given track by moving frames */ void track_quantize_frame(struct track *src, struct evspec *es, unsigned start, unsigned len, unsigned offset, unsigned quant, unsigned rate) { unsigned tic, qtic; struct track qt, frame; struct seqptr *sp, *qp; struct state *st; unsigned remaind; unsigned fluct, notes; int ofs, delta; sp = seqptr_new(src); /* * go to start position */ if (seqptr_skip(sp, start) > 0) { seqptr_del(sp); return; } track_init(&qt); qp = seqptr_new(&qt); seqptr_seek(qp, start); track_init(&frame); tic = qtic = start; ofs = 0; /* * go ahead and copy all events to quantize during 'len' tics, * while stretching the time scale in the destination track */ fluct = 0; notes = 0; for (;;) { delta = seqptr_ticskip(sp, ~0U); tic += delta; if (tic >= start + len) break; remaind = quant != 0 ? (tic - start + offset) % quant : 0; if (remaind < quant / 2) { ofs = - ((remaind * rate + 99) / 100); } else { ofs = ((quant - remaind) * rate + 99) / 100; } delta = tic + ofs - qtic; #ifdef FRAME_DEBUG if (delta < 0) { log_puts("track_quantize_frame: delta < 0\n"); panic(); } #endif seqptr_seek(qp, delta); qtic += delta; if (seqptr_eot(sp)) break; st = statelist_lookup(&sp->statelist, &sp->pos->ev); if (st != NULL && !(st->phase & EV_PHASE_LAST)) { /* * There's a state for this event in the * source track, which means the frame * is not being quantized */ #ifdef FRAME_DEBUG if (sp->pos->ev.cmd != EV_NULL) { ev_log(&sp->pos->ev); log_puts(": skipped (not ours)\n"); } #endif seqptr_evget(sp); continue; } st = statelist_lookup(&qp->statelist, &sp->pos->ev); if (st != NULL && !(st->phase & EV_PHASE_LAST)) { /* * There's as state for this event, which is * part of a conflicting frame. Just skip it. */ #ifdef FRAME_DEBUG if (sp->pos->ev.cmd != EV_NULL) { ev_log(&sp->pos->ev); log_puts(": skipped (conflict)\n"); } #endif seqptr_evget(sp); continue; } if (!evspec_matchev(es, &sp->pos->ev)) { /* * Doesn't match selection, Skip this event. */ seqptr_evget(sp); continue; } seqptr_framerm(sp, &frame); if (EV_ISNOTE(&frame.first->ev)) { fluct += (ofs < 0) ? -ofs : ofs; notes++; } seqptr_frameadd(qp, &frame); while (seqptr_evget(qp)) ; } statelist_empty(&sp->statelist); statelist_empty(&qp->statelist); seqptr_del(sp); seqptr_del(qp); track_done(&frame); track_merge(src, &qt); track_done(&qt); if (notes > 0) { log_puts("quantize: "); log_putu(notes); log_puts(" notes, fluctuation = "); log_putu(100 * fluct / notes); log_puts("% of a tick\n"); } } /* * First step to time-scale of the given track: round event positions * and convert tempo/signature events without scaling the track. */ void track_prescale(struct track *t, unsigned oldunit, unsigned newunit) { struct seqptr *sp; struct statelist slist; struct state *st; struct ev ev; unsigned delta, round, err; round = oldunit / newunit; if (round == 0) round = 1; err = 0; sp = seqptr_new(t); statelist_init(&slist); for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist) + err; err = delta % round; delta -= err; seqptr_ticput(sp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) { break; } switch (st->ev.cmd) { case EV_TEMPO: ev.cmd = st->ev.cmd; ev.tempo_usec24 = st->ev.tempo_usec24 * oldunit / newunit; seqptr_evput(sp, &ev); break; case EV_TIMESIG: ev.cmd = st->ev.cmd; ev.timesig_beats = st->ev.timesig_beats; ev.timesig_tics = st->ev.timesig_tics * newunit / oldunit; seqptr_evput(sp, &ev); break; default: seqptr_evput(sp, &st->ev); break; } } statelist_done(&slist); seqptr_del(sp); } /* * Finalize time-scaling the given track in such a way that 'oldunit' * ticks will correspond to 'newunit'. */ void track_scale(struct track *t, unsigned oldunit, unsigned newunit) { struct seqptr *sp; struct statelist slist; struct state *st; unsigned delta; sp = seqptr_new(t); statelist_init(&slist); for (;;) { delta = newunit * seqptr_ticdel(sp, ~0U, &slist); seqptr_ticput(sp, delta / oldunit); st = seqptr_evdel(sp, &slist); if (st == NULL) break; seqptr_evput(sp, &st->ev); } statelist_done(&slist); seqptr_del(sp); } /* * transpose the given track */ void track_transpose(struct track *src, unsigned start, unsigned len, struct evspec *es, int halftones) { unsigned delta, tic; struct track qt; struct seqptr *sp, *qp; struct state *st; struct statelist slist; struct ev ev; track_init(&qt); sp = seqptr_new(src); qp = seqptr_new(&qt); /* * go to t start position and untag all frames * (tagged = will be transposed) */ (void)seqptr_skip(sp, start); statelist_dup(&slist, &sp->statelist); for (st = slist.first; st != NULL; st = st->next) { st->tag = 0; } seqptr_seek(qp, start); tic = start; /* * go ahead and copy all events to transpose during 'len' tics, */ for (;;) { delta = seqptr_ticdel(sp, len, &slist); seqptr_ticput(sp, delta); seqptr_ticput(qp, delta); tic += delta; if (tic >= start + len) break; st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) { if (EV_ISNOTE(&st->ev) && state_inspec(st, es)) st->tag = 1; else st->tag = 0; } if (st->tag) { ev = st->ev; ev.note_num += (128 + halftones); ev.note_num &= 0x7f; seqptr_evput(qp, &ev); } else { seqptr_evput(sp, &st->ev); } } /* * finish transposed (tagged) frames */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); seqptr_ticput(sp, delta); seqptr_ticput(qp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) st->tag = 0; if (st->tag) { ev = st->ev; ev.note_num += (128 + halftones); ev.note_num &= 0x7f; seqptr_evput(qp, &ev); } else { seqptr_evput(sp, &st->ev); } } track_merge(src, &qt); statelist_done(&slist); seqptr_del(sp); seqptr_del(qp); track_done(&qt); } /* * apply velocity curve to given track */ void track_vcurve(struct track *src, unsigned start, unsigned len, struct evspec *es, int weight) { unsigned delta, tic; struct seqptr *sp; struct state *st; struct statelist slist; struct ev ev; /* put weight from -63:63 to 1:127 range */ weight = (64 - weight) & 0x7f; sp = seqptr_new(src); statelist_dup(&slist, &sp->statelist); tic = 0; /* * rewrite all events, modifying selected ones */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); seqptr_ticput(sp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) break; tic += delta; if ((st->phase & EV_PHASE_FIRST) && tic >= start && tic < start + len && EV_ISNOTE(&st->ev) && state_inspec(st, es)) { ev = st->ev; ev.note_vel = vcurve(weight, ev.note_vel); seqptr_evput(sp, &ev); } else { seqptr_evput(sp, &st->ev); } } statelist_done(&slist); seqptr_del(sp); } /* * rewrite the track frame-by-frame */ void track_rewrite(struct track *src) { struct statelist slist; struct track dst, frame; struct seqptr *sp, *dp; unsigned delta; track_check(src); track_init(&frame); sp = seqptr_new(src); statelist_init(&slist); track_init(&dst); dp = seqptr_new(&dst); /* * reconstruct frame-by-frame */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); seqptr_seek(dp, delta); if (seqptr_eot(sp)) break; seqptr_framerm(sp, &frame); seqptr_frameadd(dp, &frame); } statelist_empty(&dp->statelist); seqptr_del(dp); statelist_done(&slist); seqptr_del(sp); track_swap(src, &dst); track_done(&dst); } /* * check (and fix) the given track for inconsistencies */ void track_check(struct track *src) { struct seqptr *sp; struct state *dst, *st, *stnext; struct statelist slist; unsigned delta; sp = seqptr_new(src); statelist_init(&slist); /* * reconstruct the track skipping bogus events, * see statelist_update() for definition of bogus */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); seqptr_ticput(sp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) { break; } if (st->phase & EV_PHASE_FIRST) { if (st->flags & STATE_BOGUS) { log_puts("track_check: "); ev_log(&st->ev); log_puts(": bogus\n"); st->tag = 0; } else if (st->flags & STATE_NESTED) { log_puts("track_check: "); ev_log(&st->ev); log_puts(": nested\n"); st->tag = 0; } else { st->tag = 1; } } if (st->tag) { /* * dont duplicate events */ dst = statelist_lookup(&sp->statelist, &st->ev); if (dst == NULL || !state_eq(dst, &st->ev)) { seqptr_evput(sp, &st->ev); } else { log_puts("track_check: "); ev_log(&st->ev); log_puts(": duplicated\n"); } } } /* * undo (erase) all unterminated frames */ for (st = sp->statelist.first; st != NULL; st = stnext) { stnext = st->next; if (!(st->phase & EV_PHASE_LAST)) { log_puts("track_check: "); ev_log(&st->ev); log_puts(": unterminated\n"); seqptr_rmprev(sp, &st); } } /* * statelist_done() will complain about bogus frames. Since * bugs are fixed in the track, we empty slist to avoid * warning messages */ statelist_empty(&slist); statelist_done(&slist); seqptr_del(sp); } /* * get the current tempo (at the current position) */ struct state * seqptr_getsign(struct seqptr *sp, unsigned *bpm, unsigned *tpb) { struct ev ev; struct state *st; ev.cmd = EV_TIMESIG; st = statelist_lookup(&sp->statelist, &ev); if (bpm) *bpm = (st == NULL) ? DEFAULT_BPM : st->ev.timesig_beats; if (tpb) *tpb = (st == NULL) ? DEFAULT_TPB : st->ev.timesig_tics; return st; } /* * get the current tempo (at the current position) */ struct state * seqptr_gettempo(struct seqptr *sp, unsigned long *usec24) { struct ev ev; struct state *st; ev.cmd = EV_TEMPO; st = statelist_lookup(&sp->statelist, &ev); if (usec24) *usec24 = (st == NULL) ? DEFAULT_USEC24 : st->ev.tempo_usec24; return st; } /* * try to move 'm0' measures forward; the current postition MUST be * the beginning of a measure and the state table must be up to date. * Return the number of tics remaining until the requested measure * (only on premature end-of-track) */ unsigned seqptr_skipmeasure(struct seqptr *sp, unsigned meas) { unsigned m, bpm, tpb, tics_per_meas, delta; for (m = 0; m < meas; m++) { while (seqptr_evget(sp)) { /* nothing */ } seqptr_getsign(sp, &bpm, &tpb); tics_per_meas = bpm * tpb; delta = seqptr_skip(sp, tics_per_meas); if (delta > 0) return (meas - m - 1) * tics_per_meas + delta; } return 0; } /* * convert a measure number to a tic number using * meta-events from the given track */ unsigned track_findmeasure(struct track *t, unsigned m) { struct seqptr *sp; unsigned tic; sp = seqptr_new(t); tic = seqptr_skipmeasure(sp, m); tic += sp->tic; seqptr_del(sp); #ifdef FRAME_DEBUG log_puts("track_findmeasure: "); log_putu(m); log_puts(" -> "); log_putu(tic); log_puts("\n"); #endif return tic; } /* * return the absolute tic, the tempo and the time signature * corresponding to the given measure number */ void track_timeinfo(struct track *t, unsigned meas, unsigned *abs, unsigned long *usec24, unsigned *bpm, unsigned *tpb) { struct seqptr *sp; unsigned tic; sp = seqptr_new(t); tic = seqptr_skipmeasure(sp, meas); tic += sp->tic; /* * move to the last event, so all meta events enter the * state list */ while (seqptr_evget(sp)) { /* nothing */ } if (abs) { *abs = tic; } seqptr_getsign(sp, bpm, tpb); seqptr_gettempo(sp, usec24); seqptr_del(sp); } /* * go to the given measure and set the tempo */ void track_settempo(struct track *t, unsigned measure, unsigned tempo) { struct seqptr *sp; struct state *st; struct statelist slist; struct ev ev; unsigned long usec24, old_usec24; unsigned tic, bpm, tpb; unsigned delta; /* * go to the requested position, insert blank if necessary */ sp = seqptr_new(t); tic = seqptr_skipmeasure(sp, measure); if (tic) { seqptr_ticput(sp, tic); } statelist_dup(&slist, &sp->statelist); /* * remove tempo events at the current tic */ for (;;) { st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->ev.cmd != EV_TEMPO) seqptr_evput(sp, &st->ev); } /* * if needed, insert a new tempo event */ seqptr_getsign(sp, &bpm, &tpb); usec24 = TEMPO_TO_USEC24(tempo, tpb); seqptr_gettempo(sp, &old_usec24); if (usec24 != old_usec24) { ev.cmd = EV_TEMPO; ev.tempo_usec24 = usec24; seqptr_evput(sp, &ev); } /* * move next events, skipping duplicate tempos */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); seqptr_ticput(sp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->ev.cmd != EV_TEMPO || st->ev.tempo_usec24 != usec24) { usec24 = st->ev.tempo_usec24; seqptr_evput(sp, &st->ev); } } seqptr_del(sp); statelist_done(&slist); } /* * add an event to the first given "config" track, if there is such an * event, then replace it. */ void track_confev(struct track *src, struct ev *ev) { struct seqptr *sp; struct statelist slist; struct state *st; if (ev_phase(ev) != (EV_PHASE_FIRST | EV_PHASE_LAST)) { log_puts("track_confev: "); ev_log(ev); log_puts(": bad phase, ignored"); log_puts("\n"); return; } sp = seqptr_new(src); statelist_init(&slist); /* * rewrite the track, removing frames matching the event */ for (;;) { (void)seqptr_ticdel(sp, ~0U, &slist); st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) { st->tag = state_match(st, ev) ? 0 : 1; } if (st->tag) { seqptr_evput(sp, &st->ev); } } seqptr_evput(sp, ev); statelist_done(&slist); seqptr_del(sp); } /* * remove a set of events from the "config" track */ void track_unconfev(struct track *src, struct evspec *es) { struct statelist slist; struct seqptr *sp; struct state *st; sp = seqptr_new(src); statelist_init(&slist); /* * rewrite the track, removing frames matching the spec */ for (;;) { (void)seqptr_ticdel(sp, ~0U, &slist); st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) { st->tag = state_inspec(st, es) ? 0 : 1; } if (st->tag) { seqptr_evput(sp, &st->ev); } } statelist_done(&slist); seqptr_del(sp); } /* * insert the given amount of blank space at the given position */ void track_ins(struct track *t, unsigned stic, unsigned len) { struct track t1, t2; track_init(&t1); track_init(&t2); track_move(t, 0 , stic, NULL, &t1, 1, 1); track_move(t, stic, ~0U, NULL, &t2, 1, 1); track_shift(&t2, stic + len); track_clear(t); track_merge(t, &t1); if (!track_isempty(&t2)) { track_merge(t, &t2); } track_done(&t1); track_done(&t2); } /* * cut the given portion of the track */ void track_cut(struct track *t, unsigned stic, unsigned len) { struct track t1, t2; track_init(&t1); track_init(&t2); track_move(t, 0, stic, NULL, &t1, 1, 1); track_move(t, stic + len, ~0U, NULL, &t2, 1, 1); track_shift(&t2, stic); track_clear(t); track_merge(t, &t1); if (!track_isempty(&t2)) { track_merge(t, &t2); } track_done(&t1); track_done(&t2); } /* * map current selection to the given evspec */ void track_evmap(struct track *src, unsigned start, unsigned len, struct evspec *es, struct evspec *from, struct evspec *to) { unsigned delta, tic; struct track qt; struct seqptr *sp, *qp; struct state *st; struct statelist slist; struct ev ev; if (!evspec_isamap(from, to)) return; track_init(&qt); sp = seqptr_new(src); qp = seqptr_new(&qt); /* * go to t start position and untag all frames * (tagged = will be mapped) */ (void)seqptr_skip(sp, start); statelist_dup(&slist, &sp->statelist); for (st = slist.first; st != NULL; st = st->next) { st->tag = 0; } seqptr_seek(qp, start); tic = start; /* * go ahead and copy all events to map during 'len' tics, */ for (;;) { delta = seqptr_ticdel(sp, len, &slist); seqptr_ticput(sp, delta); seqptr_ticput(qp, delta); tic += delta; if (tic >= start + len) break; st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) { if (state_inspec(st, es) && state_inspec(st, from)) st->tag = 1; else st->tag = 0; } if (st->tag) { ev_map(&st->ev, from, to, &ev); seqptr_evput(qp, &ev); } else { seqptr_evput(sp, &st->ev); } } /* * finish mapped (tagged) frames */ for (;;) { delta = seqptr_ticdel(sp, ~0U, &slist); seqptr_ticput(sp, delta); seqptr_ticput(qp, delta); st = seqptr_evdel(sp, &slist); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) st->tag = 0; if (st->tag) { ev_map(&st->ev, from, to, &ev); seqptr_evput(qp, &ev); } else { seqptr_evput(sp, &st->ev); } } track_merge(src, &qt); statelist_done(&slist); seqptr_del(sp); seqptr_del(qp); track_done(&qt); } midish-1.4.0/frame.h010066400017510001751000000067661501104363400127610ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_FRAME_H #define MIDISH_FRAME_H #include "state.h" struct seqptr { struct statelist statelist; struct seqptr *link; /* opposite direction seqptr */ struct seqev *pos; /* next event (current position) */ unsigned delta; /* tics until the next event */ unsigned tic; /* absolute tic of the current pos */ }; struct track; struct evspec; void seqptr_pool_init(unsigned); void seqptr_pool_done(void); struct seqptr *seqptr_new(struct track *); void seqptr_del(struct seqptr *); void seqptr_link(struct seqptr *, struct seqptr *); int seqptr_eot(struct seqptr *); struct state *seqptr_evget(struct seqptr *); struct state *seqptr_evdel(struct seqptr *, struct statelist *); struct state *seqptr_evput(struct seqptr *, struct ev *); unsigned seqptr_ticskip(struct seqptr *, unsigned); unsigned seqptr_ticdel(struct seqptr *, unsigned, struct statelist *); void seqptr_ticput(struct seqptr *, unsigned); unsigned seqptr_skip(struct seqptr *, unsigned); void seqptr_seek(struct seqptr *, unsigned); struct state *seqptr_getsign(struct seqptr *, unsigned *, unsigned *); struct state *seqptr_gettempo(struct seqptr *, unsigned long *); unsigned seqptr_skipmeasure(struct seqptr *, unsigned); struct state *seqptr_evmerge1(struct seqptr *, struct state *); unsigned seqptr_evmerge2(struct seqptr *, struct statelist *, struct ev *, struct ev *); void track_merge(struct track *, struct track *); unsigned track_findmeasure(struct track *, unsigned); void track_timeinfo(struct track *, unsigned, unsigned *, unsigned long *, unsigned *, unsigned *); void track_settempo(struct track *, unsigned, unsigned); void track_move(struct track *, unsigned, unsigned, struct evspec *, struct track *, unsigned, unsigned); void track_quantize(struct track *, struct evspec *, unsigned, unsigned, unsigned, unsigned, unsigned); void track_quantize_frame(struct track *, struct evspec *, unsigned, unsigned, unsigned, unsigned, unsigned); void track_prescale(struct track *, unsigned, unsigned); void track_scale(struct track *, unsigned, unsigned); void track_transpose(struct track *, unsigned, unsigned, struct evspec *, int); void track_evmap(struct track *, unsigned, unsigned, struct evspec *, struct evspec *, struct evspec *); void track_vcurve(struct track *, unsigned, unsigned, struct evspec *, int); void track_check(struct track *); void track_rewrite(struct track *); void track_confev(struct track *, struct ev *); void track_unconfev(struct track *, struct evspec *); void track_ins(struct track *, unsigned, unsigned); void track_cut(struct track *, unsigned, unsigned); #endif /* MIDISH_FRAME_H */ midish-1.4.0/help.c010066400017510001751000000630751501104363400126060ustar00alexalex/* * Copyright (c) 2018 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "textio.h" #include "help.h" struct help help_list[] = { {"tlist", "tlist\n" "\n" "Return the list of tracks."}, {"tnew", "tnew name\n" "\n" "Create a track with the given name."}, {"tdel", "tdel\n" "\n" "Delete the current track."}, {"tren", "tren name\n" "\n" "Rename the current track to the given name."}, {"texists", "texists name\n" "\n" "Return 1 if a track with the give name exists, 0 otherwise."}, {"taddev", "taddev measure beat tic event\n" "\n" "Put the given event at the give position of the current track."}, {"tsetf", "tsetf filtname\n" "\n" "Set the default filter of the current track to the given filter."}, {"tgetf", "tgetf\n" "\n" "Return the default filter of the current track, returns nil\n" "if none."}, {"tcheck", "tcheck\n" "\n" "Check the current track for orphaned notes, nested notes, and " "other anomalies. Remove multiple controllers in the same clock " "tick."}, {"trewrite", "trewrite\n" "\n" "Rewrite the current track note-by-note."}, {"tcut", "tcut\n" "\n" "Cut the current selection of the current track."}, {"tclr", "tclr\n" "\n" "Clear the current selection of the current track. Only events " "matching the current event selection (see the ev command) are " "removed."}, {"tins", "tins amount\n" "\n" "Insert the given amount of empty measures at the current position " "in the current track."}, {"tpaste", "tpaste\n" "\n" "Paste track portion (copied with tcopy) at the current position " "of the current track."}, {"tcopy", "tcopy\n" "\n" "Copy the current selection of the current track so that it could " "be pasted with the tpaste command. " "Only events matching the current event selection " "are copied (see ev command)."}, {"tmerge", "tmerge source\n" "\n" "Merge the given track into the current track."}, {"tquanta", "tquanta rate\n" "\n" "Round event positions to the nearest exact position, as defined " "by the setq command. This affects events of the current " "selection of the current track. Event positions are rounded to " "the nearest tick multiple of the quantization step}, Rate must be " "between 0 and 100: 0 means no quantization and 100 means full " "quantization."}, {"tquantf", "tquantf rate\n" "\n" "Round frame positions (eg. note positions) to the nearest exact " "position, as defined by the setq command. Frame length (eg. " "note lengths) is preserved. Rate must be between 0 and 100: " "0 means no quantization and 100 means full quantization."}, {"ttransp", "ttransp halftones\n" "\n" "Transpose note events of current selection of the current track, " "by the given number of half-tones. Only events matching the current " "event selection are transposed (see ev command)."}, {"tvcurve", "tvcurve weight\n" "\n" "Adjust velocity of note events, using the given weight in " "the -63..63 range. Applies only to note events of current " "selection of the current track (see ev command)."}, {"tevmap", "tevmap source dest\n" "\n" "Convert events matching the given source event set to the given destination " "event set. Changes apply to in the current selection of the current track. " "Both event sets must have the same number of devices, " "channels, notes, controllers etc.."}, {"mute", "mute trackname\n" "\n" "Mute the given track."}, {"unmute", "unmute trackname\n" "\n" "Unmute the given track."}, {"getmute", "getmute trackname\n" "\n" "Return 1 if the given track is muted and 0 otherwise."}, {"tclist", "tclist\n" "\n" "Return the list of channels used by events stored in the " "current track."}, {"tinfo", "tinfo\n" "\n" "Scan the current selection of the current track, an for each " "measure display the number of events that match the current " "event selection."}, {"tdump", "tdump\n" "\n" "Display selected events of the current track."}, {"inew", "inew name channel\n" "\n" "Create an new input with the given name associated " "to the given device and MIDI channel pair."}, {"iset", "iset channel\n" "\n" "Set the device and channel number of the current input. All " "filters are updated to use the new channel setting as if the " "appropriate fchin command was called for each filter."}, {"idel", "idel\n" "\n" "Delete the current input."}, {"iren", "iren newname\n" "\n" "Rename the current input to the given name."}, {"ilist", "ilist\n" "\n" "Return the list of inputs."}, {"iexists", "iexists channame\n" "\n" "Return 1 if an input with the give name exists, 0 otherwise."}, {"igetc", "igetc\n" "\n" "Return the MIDI channel number of the current input."}, {"igetd", "igetd\n" "\n" "Return the device number of the current input."}, {"iaddev", "iaddev event\n" "\n" "Add the given event to the configuration of the " "current channel, it's not used yet."}, {"irmev", "irmev evspec\n" "\n" "Remove all events matching the given evspec from the configuration " "of the current channel."}, {"iinfo", "iinfo\n" "\n" "Print all config events of the current input."}, {"onew", "onew name channel\n" "\n" "Create an new output with the given name and the given device " "and MIDI channel. Outputs contain a built-in filter with the " "same name}, by defaut it maps all inputs to the newly created " "output."}, {"odel", "odel\n" "\n" "Delete current output."}, {"oset", "oset channel\n" "\n" "Set the device and MIDI channel numbers of the current output. " "All filters are updated to use the new setting as if the " "appropriate fchout command was called for each filter."}, {"oren", "oren newname\n" "\n" "Rename the current output to the given name."}, {"oexists", "oexists channame\n" "\n" "Return 1 if an output with the give name exists, 0 otherwise."}, {"ogetc", "ogetc\n" "\n" "Return the MIDI channel number of the current output."}, {"ogetd", "ogetd\n" "\n" "Return the device number of the current output."}, {"oaddev", "oaddev event\n" "\n" "Add the given event to the configuration of the current output."}, {"ormev", "ormev evspec\n" "\n" "Remove all events matching the given set from the configuration " "of the current output."}, {"oinfo", "oinfo\n" "\n" "Print all config events of the current output."}, {"olist", "olist\n" "\n" "Return list of outputs."}, {"fnew", "fnew filtname\n" "\n" "Create an new filter with the given name."}, {"fdel", "fdel\n" "\n" "Delete the current filter."}, {"fren", "fren newname\n" "\n" "Rename the current filter to the given name."}, {"fexists", "fexists filtname\n" "\n" "Return 1 if a filter with the given name exists, 0 otherwise."}, {"freset", "freset\n" "\n" "Remove all rules from the current filter."}, {"finfo", "finfo\n" "\n" "List all rules of the current filter."}, {"flist", "flist\n" "\n" "Return the list of existing filters."}, {"fchgin", "fchgin old_evspec new_evspec\n" "\n" "Rewrite current filter rules to consume new event set in place " "of the old event set. This means that each rule previously " "matching old set on the input will start " "matching new set instead."}, {"fswapin", "fswapin evspec1 evspec2\n" "\n" "Similar to the fchgin command but swap the two event sets in " "the source events set of each rule."}, {"fchgout", "fchgout from to\n" "\n" "Rewrite all filtering rules of the current filter to produce " "the new event set instead of the old event set. This means " "that each rule previously producing the old set will " "start producing new set instead."}, {"fswapout", "fswapout from to\n" "\n" "Similar to the fchgout command, but swap the two event sets in " "the destination events set of each rule."}, {"fmap", "fmap source dest\n" "\n" "Add a new mapping rule to the current filter. The events matching " "the source event set will be converted to the destination event set. " "Source and destination sets must have the same number of devices, " "channels, notes, controllers etc.."}, {"funmap", "funmap source dest\n" "\n" "Remove the given event mapping rules from the current filter. Any " "mapping rule with source included in the given source and destination " "inluded in the given destination is deleted."}, {"ftransp", "ftransp evspec half_tones\n" "\n" "Transpose events matching the given event set by the given number " "of halftones."}, {"fvcurve", "fvcurve evspec weight\n" "\n" "Adjust velocity of the given note events, using " "the given weight in the -63..63 range. If the weight is " "negative then sensitivity is decreased. If it's positive then " "sensitivity is increased. If it's zero the velocity is unchanged."}, {"xnew", "xnew sysexname\n" "\n" "Create a new bank of sysex messages with the given name."}, {"xdel", "xdel\n" "\n" "Delete the current bank of sysex messages."}, {"xren", "xren newname\n" "\n" "Rename the current sysex bank to the given name."}, {"xexists", "xexists sysexname\n" "\n" "Return 1 if a sysex bank with the given name exists, 0 otherwise."}, {"xrm", "xrm data\n" "\n" "Remove all sysex messages starting with the given pattern from the " "current sysex bank. The given pattern is a list of bytes}, an empty " "pattern matches any sysex message."}, {"xsetd", "xsetd device pattern\n" "\n" "Set device number of all sysex messages starting " "with the given pattern in the current sysex bank. The given pattern " "is a list of bytes}, an empty pattern matches any sysex message."}, {"xadd", "xadd device message\n" "\n" "Add the given sysex message to the current sysex bank. The message is a " "list containing the system exclusive message bytes. The given device number " "specifies the device to which the message will be sent when performance mode " "is entered."}, {"xinfo", "xinfo\n" "\n" "Print all sysex messages of the current sysex bank. Messages that are " "too long to be desplayed on a single line are truncated and dots " "are displayed."}, {"ximport", "ximport device path\n" "\n" "Replace contents of the current sysex bank by contents of the given " ".syx file}, messages are assigned to the given device number."}, {"xexport", "xexport path\n" "\n" "Store contents of the current sysex bank in the given .syx file."}, {"xlist", "xlist\n" "\n" "Return the list of sysex banks."}, {"i", "i\n" "\n" "Enter performance mode. Start processing MIDI input and " "generating MIDI output. Data passes through the current filter " "(if any) or through the current track's filter (if any)."}, {"p", "p\n" "\n" "Play the song from the current position. Input passes through the " "current filter (if any) or through the current track's filter " "(if any)."}, {"r", "r\n" "\n" "Play the song and record the input. Input passes through the current " "filter (if any) or through the current track's filter (if any). " "On startup, this command plays one measure of countdown before the data " "start being recorded."}, {"s", "s\n" "\n" "Stop performance and release MIDI devices."}, {"ev", "ev evspec\n" "\n" "Set the current event selection. Most track editing commands will " "act only on events matching evspec ignoring all other events."}, {"setq", "setq step\n" "\n" "Set the current quantization step to the given note value, as follows:\n" "\t4 -> quarter note\n" "\t6 -> quarter note triplet\n" "\t8 -> eighth note\n" "\t12 -> eighth note triplet\n" "\tetc...\n" "\n" "The quantization step will be used by tquanta and tquantf funnctions " "to optimize event selection. If the special nil value is specified " "as quantization step, then quatization is disabled."}, {"getq", "getq\n" "\n" "Return the current quatization step."}, {"g", "g measure\n" "\n" "Set the current song position pointer to the given measure number. " "Record and playback will start a that position. This also defines " "the beginning of the current selection used by most track editing " "commands."}, {"getpos", "getpos\n" "\n" "Return the current song position pointer which is also the start " "position of the current selection."}, {"sel", "sel length\n" "\n" "Set the length of the current selection to " "the given number of measures. " "The current selection start at the current position " "set with the g command."}, {"getlen", "getlen\n" "\n" "Return the length (in measures) of the current selection."}, {"loop", "loop\n" "\n" "Enable loop mode. When playback is started, the current " "selection is repeated forever."}, {"noloop", "noloop\n" "\n" "Disable loop mode."}, {"ct", "ct trackname\n" "\n" "Set the current track. The current track " "is the one that will be recorded. Most track editing " "commands act on it."}, {"gett", "gett\n" "\n" "Return the current track (if any) or nil."}, {"cf", "cf filtname\n" "\n" "Set the current filter to the given filter. The current filter " "is the one used to process input MIDI events in performance mode. " "It's also the one affected by all filter editing commands."}, {"getf", "getf\n" "\n" "Return the current filter or nil if none."}, {"cx", "cx sysexname\n" "\n" "Set the current sysex bank, i.e. the one that will be recorded. " "The current sysex back is the one affected by all sysex editing " "commands."}, {"getx", "getx\n" "\n" "Return the current sysex bank or nil if none."}, {"ci", "ci channame\n" "\n" "Set the current (named) input channel. All input channel " "editing commands will act on it."}, {"geti", "geti\n" "\n" "Return the name of the current input channel or nil if none."}, {"co", "co channame\n" "\n" "Set the current (named) output channel. All output channel " "editing commands will act on it."}, {"geto", "geto\n" "\n" "Return the name of the current output channel or nil if none."}, {"setunit", "setunit tics_per_unit\n" "\n" "Set the time resolution of the sequencer to given number of " "ticks per whole note (1 unit note = 4 quarter notes). " "The default is 96 ticks, which is the default of the MIDI standard."}, {"getunit", "getunit\n" "\n" "Return the number of ticks in a whole note."}, {"fac", "fac tempo_factor\n" "\n" "Set the tempo factor for play and record to the given integer " "value. The tempo factor must be between 50 (play half of the " "real tempo) and 200 (play at twice the real tempo)."}, {"getfac", "getfac\n" "\n" "Return the current tempo factor."}, {"t", "t beats_per_minute\n" "\n" "Set the tempo at the current song position."}, {"mins", "mins amount sig\n" "\n" "Insert the given amount of blank measures at the " "current song position. " "The time signature used is in the {num,~denom} format. " "If the time signature is " "an empty list (i.e. {}) then the time signature at the " "current position is used."}, {"mcut", "mcut\n" "\n" "Cut the current selection of all tracks, including the " "time structure."}, {"mdup", "mdup where\n" "\n" "Duplicate the current selection inserting a copy of it at " "the given position in measures. " "If the given position is positive it's relative to the end " "of the current selection}, if it's negative it's relative to " "the beginning of the current selection."}, {"minfo", "minfo\n" "\n" "Print the meta-track (tempo changes, time signature changes."}, {"mtempo", "mtempo\n" "\n" "Return the tempo at the current song position. The unit is beats " "per minute."}, {"msig", "msig\n" "\n" "Return the time signature at the current song position. The result " "is a two number list: numerator and denominator."}, {"mend", "mend\n" "\n" "Return the ending measure of the song (i.e. its size in measures)."}, {"ls", "ls\n" "\n" "List all tracks, channels, filters and various default values."}, {"save", "save filename\n" "\n" "Save the song into the given file. The file name is a " "quoted string."}, {"load", "load filename\n" "\n" "Load the song from the given file. The file name is a " "quoted string. The current song will be overwritten."}, {"reset", "reset\n" "\n" "Destroy completely the song, useful to start a new song without " "restarting the program."}, {"export", "export filename\n" "\n" "Save the song into the given standard MIDI file. The file name " "is a quoted string."}, {"import", "import filename\n" "\n" "Load the song from the given standard MIDI file. The file name " "is a quoted string. The current song will be overwritten. " "Only MIDI file formats 0 and 1 are supported."}, {"u", "u\n" "\n" "Undo last operation saved for undo."}, {"ul", "ul\n" "\n" "List operations saved for undo."}, {"dlist", "dlist\n" "\n" "Return the list of attached devices (list of numbers)."}, {"dnew", "dnew devnum path mode\n" "\n" "Add the given MIDI device number, with the given path and mode. " "The mode may be on if the following:\n" " ro - read-only, for input only devices\n" " wo - write-only, for output only devices\n" " rw - read and write.\n" "\n" "If midish is configured to use ALSA (default on Linux systems) " "then the device path is the ALSA sequencer port, as " "listed by the aseqdump linux command (ex. 28:0). " "If nil is given instead of the path, then the port is not " "connected to any existing port}, this allows other ALSA sequencer " "clients to subscribe to it and to provide events to midish or to " "consume events midish sends to the port."}, {"ddel", "ddel devnum\n" "\n" "Remove the given device number."}, {"dmtcrx", "dmtcrx devnum\n" "\n" "Use given device number as MTC source. " "In this case, midish will relocate, start and stop according to " "incoming MTC messages. Midish will generate its clock ticks from " "MTC, meaning that it will run at the same speed as the MTC device. " "This is useful to synchronize midish to an audio multi-tracker or any " "MTC capable audio application. If the device number is nil, then MTC " "messages are ignored and the internal timer will be used instead."}, {"dmmctx", "dmmctx devlist\n" "\n" "Configure the given devices to transmit MMC start, stop " "and relocate messages. Useful to control MMC-capable audio " "applications from midish. By default, devices transmit MMC."}, {"dclktx", "dclktx devlist\n" "\n" "Configure the given devices to transmit MIDI clock information " "(MIDI ticks, MIDI start and MIDI stop events). Useful to " "synchronize an external sequencer to midish."}, {"dclkrx", "dclkrx devnum\n" "\n" "Set the given device number to be the master MIDI clock source. " "It will give midish MIDI ticks, MIDI start and MIDI stop events. This " "useful to synchronize midish to an external sequencer. " "If the device number is nil, then the internal clock will be " "used and midish will act as master clock."}, {"dclkrate", "dclkrate devnum tics_per_unit\n" "\n" "Set the number of ticks in a whole note that are transmitted to the " "MIDI device. Default value is 96 ticks. This is the standard MIDI " "value and its not recommended to change it."}, {"dinfo", "dinfo devnum\n" "\n" "Print some information about the MIDI device."}, {"dixctl", "dixctl devnum ctlset\n" "\n" "Setup the list of controllers that are expected to be received " "as 14-bit numbers (i.e. both coarse and fine MIDI controller messages " "will be expected). By default only coarse values are used, if unsure " "let this list empty."}, {"doxctl", "doxctl devnum ctlset\n" "\n" "Setup the list of controllers that will be transmitted " "as 14-bit numbers (both coarse and fine MIDI controller messages)."}, {"diev", "diev devnum flags\n" "\n" "Configure the device to process as a single event the following " "patterns of input MIDI messages.\n" "\n" " * xpc - group bank select controllers (0 and 32) with program\n" " changes into a signle xpc event.\n" "\n" " * nrpn - group NRPN controllers (98 and 99) with data entry\n" " controllers (6 and 38) into a single nrpn event.\n" "\n" " * rpn - similart to nrpn, but for RPN controllers (100 and 101).\n" "\n" "By default all of the above are enabled, which allows banks, NRPNs " "and RPNs to be handled by midish the standard way. " "It makes sense to disable grouping of above messages on rare hardware " "that maps above-mentioned controller numbers (0, 6, 32, 38, 98, 99, " "100, 101) to other parameters than bank number and NRPN/RPN."}, {"doev", "doev devnum flags\n" "\n" "Same as the diev functino, but for output messages."}, {"ctlconf", "ctlconf name number defval\n" "\n" "Configure the given controller number with the given name, " "and default value. If the default value is nil then the " "corresponding controller events are not grouped into frames."}, {"ctlconfx", "ctlconfx name ctl defval\n" "\n" "Same as ctlconf, but for 14-bit controllers. Thus defval is in the " "range 0..16383."}, {"ctlunconf", "ctlunconf name\n" "\n" "Unconfigure the given controller."}, {"ctlinfo", "ctlinfo\n" "\n" "Print the list of configured controllers."}, {"evpat", "evpat name pattern\n" "\n" "Define a new event type corresponding to the given system exclusive " "message pattern. The pattern is a list of bytes or event parameter " "identifiers (aka atoms). The following atoms are supported: v0, v0_lo, " "v0_hi, v1, v1_lo, v1_hi. They correspond to the full 7-bit value (coarse " "parameter), the low 7-bit nibble and the high 7-bit nibble (fine grained " "parameters) of the first and second parameters respectively. Example:\n" "\n" "\tevpat master {0xf0 0x7f 0x7f 0x04 0x01 v0_lo v0_hi 0xf7}\n" "\n" "defines a new event type for the standard master volume system exclusive " "message."}, {"evinfo", "evinfo\n" "\n" "Print the list of event patterns."}, {"m", "m onoff\n" "\n" "Set the mode of the metronome. The following modes are available:\n" " on - turned on for both playback and recording\n" " rec - turned on for recording only\n" " off - turned off"}, {"metrocf", "metrocf eventhi eventlo\n" "\n" "Select the notes that the metronome plays. The pair of events must " "be note-on events."}, {"tap", "tap mode\n" "\n" "Set the way start is triggered by events. The following modes are " "available:\n" " off - no events trigger start\n" " start - a single event triggers start\n" " tempo - two events trigger start}, the time between them\n" " corresponds to one beat and is used to determine the\n" " initial tempo."}, {"tapev", "tapev evspec\n" "\n" "Set events set used to trigger start when tap mode is set to " "start or tempo."}, {"info", "info\n" "\n" "Display the list of built-in and user-defined procedures and global " "variables."}, {"print", "print value\n" "\n" "Display the given value."}, {"err", "err message\n" "\n" "Display the given string and abort the current statement."}, {"h", "h command\n" "\n" "Describe the given command."}, {"exec", "exec filename\n" "\n" "Read and execute the give script (the file name is a quoted " "string). The execution of the script is aborted on error. If the " "script executes an exit statement, only the script is terminated."}, {"debug", "debug flag value\n" "\n" "Set given debug-flag to given the (integer) value. " "If the value is 0, the corresponding debug-info is turned off. " "The flag may be:\n" " filt - show events passing through the current filter\n" " mididev - show raw MIDI traffic\n" " mixout - show conflicts in the output MIDI merger\n" " norm - show events in the input normalizer\n" " pool - show pool usage on exit\n" " song - show start/stop events\n" " timo - show timer internal errors\n" " mem - show memory usage"}, {"version", "version\n" "\n" "Display midish version."}, {"panic", "panic\n" "\n" "Abort (and core-dump)."}, {"shut", "shut\n" "\n" "Stop all sounds."}, {"proclist", "proclist\n" "\n" "Return the list of user-defined procs."}, {"builtinlist", "builtinlist\n" "\n" "Return the list of builtin commands."}, {"intro", "To obtain help about any midish command, type:\n" "\n" " h \n"}, {NULL, NULL} }; #define IS_SEP(c) ((c) == ' ' || (c) == '\n' || (c) == 0) void help_fmt(char *text) { #define LINE_LEN 68 char out[LINE_LEN + 1 + 1], *endl; size_t out_len, word_len; int c, t; endl = text; out_len = word_len = 0; for (;;) { c = endl[word_len]; /* if not an end-of-word, continue */ if (!IS_SEP(c)) { word_len++; continue; } /* finished a word, check if flush is needed */ if (out_len + word_len > LINE_LEN) { /* flush */ out[out_len++] = '\n'; out[out_len++] = 0; textout_putstr(tout, out); out_len = 0; /* discard space separator */ endl++; word_len--; } /* copy word */ while (word_len > 0) { t = *endl++; if (t == '~') t = ' '; out[out_len++] = t; word_len--; } /* finished a word, check if flush is needed */ if (c == '\n' || c == 0) { /* flush */ out[out_len++] = '\n'; out[out_len++] = 0; textout_putstr(tout, out); out_len = 0; /* discard space separator */ endl++; word_len--; } if (c == 0) break; word_len++; } } midish-1.4.0/help.h010066400017510001751000000017061501104363400126040ustar00alexalex/* * Copyright (c) 2018 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_HELP_H #define MIDISH_HELP_H struct help { char *key; char *text; }; extern struct help help_list[]; void help_fmt(char *); #endif /* MIDISH_HELP_H */ midish-1.4.0/main.c010066400017510001751000000027621501104363400125760ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "utils.h" #include "str.h" #include "cons.h" #include "ev.h" #include "mux.h" #include "track.h" #include "frame.h" #include "song.h" #include "user.h" #include "filt.h" #include "mididev.h" #include "defs.h" #include "sysex.h" #include "textio.h" int main(int argc, char **argv) { int ch; unsigned exitcode; while ((ch = getopt(argc, argv, "bv")) != -1) { switch (ch) { case 'b': user_flag_batch = 1; break; case 'v': user_flag_verb = 1; break; default: goto err; } } argc -= optind; argv += optind; if (argc >= 1) { err: fputs("usage: midish [-bv]\n", stderr); return 0; } exitcode = user_mainloop(); return exitcode ? 0 : 1; } midish-1.4.0/manual.html010066400017510001751000003143761501104363400136600ustar00alexalex Midish user's manual

Midish user's manual and tutorial

Table of contents

1 Introduction

1.1 What is midish?

Midish is an open-source MIDI sequencer/filter for Unix-like operating systems (tested on OpenBSD and Linux). Implemented as a simple command-line interpreter (like a shell) it's intended to be lightweight, fast and reliable for real-time performance.

Important features:

  • real-time MIDI filtering/routing (controller mapping, keyboard splitting, ...)
  • track recording, metronome
  • track editing (insert, copy, delete, ...)
  • progressive track quantization
  • multiple MIDI devices handling
  • synchronization to external audio and MIDI software/hardware
  • import and export of standard MIDI files
  • tempo and time-signature changes
  • system exclusive messages handling

Midish is open-source software distributed under a BSD-style license.

1.2 Installation

Requirements:

  • A MIDI sound module and a MIDI keyboard (without any MIDI devices midish will be probably useless).
  • A POSIX unix-like operating system with raw, ALSA or sndio MIDI support.
  • Basic development tools (C complier, make utility, etc)
  • The ALSA or sndio libraries on Linux and OpenBSD respectively.

Certain Linux distributions split libraries in two packages: one with run-time files only and one with development files. Both are necessary to build midish. Usually development packages have the ``-dev'' or ``-devel'' suffix. For instance, on Debian, package name is ``libasound2-dev''.

To install midish:

  1. Untar and gunzip the tar-ball:
    gunzip midish-1.0.tar.gz
    tar -xf midish-1.0.tar
    cd midish-1.0
    
  2. Configure midish, type ``./configure'', it will select reasonable defaults.

    On Linux systems it will use ALSA devices, on OpenBSD it will use sndio(7) devices and on other systems it will use raw MIDI devices. Binaries, scripts, examples and documentation will be installed in the /usr/local directory subtree; this can be overridden with the ``--prefix'' option. Example:

    ./configure --prefix=$HOME
    
  3. Compile midish, just type ``make''.
  4. Install binaries, documentation and examples by typing ``make install'', possibly as root.
  5. If there isn't a ``/etc/midishrc'' file, then copy the sample file by typing:
    cp midishrc /etc
    
  6. Read the documentation and modify ``/etc/midishrc'' in order to choose the default MIDI device by using the ``dnew'' function, example (if you're using raw devices):
    dnew 0 "/dev/rmidi3" rw
    
    or if you're using ALSA (default on Linux), the following formats are accepted:
    dnew 0 "28:0" rw
    dnew 0 "FLUID Synth (qsynth)" wo 
    
    on OpenBSD, the following formats are accepted:
    dnew 0 "rmidi/3" rw
    dnew 0 "midithru/0" rw
    dnew 0 "snd/0" rw
    
    see next section for details.

1.3 Invocation

Once started, midish prompts for commands. Example:

print "hello world"
It can be used to configure MIDI devices, create tracks, define channel/controller mappings, route events from one device to another, play/record a song etc.

Once MIDI devices are set up, one of the performance modes can be started or stopped with one of the following single letter commands:

  • ``i'' - idle, open MIDI devices but do nothing, just process input and send it to the output.
  • ``p'' - play, open MIDI devices and start playback
  • ``r'' - record, open MIDI devices and start playback and recording
  • ``s'' - stop one of the above and close MIDI devices.

In performance mode certain features are not available (like most editing functions). Thus performance mode should be disabled in order to be able to edit the song; furthermore MIDI devices are closed and thus are available to other applications.

1.4 How does it work

Midish uses the following objects to represent a project:

  • MIDI devices:
    these are actual MIDI devices (keyboards, sound modules, external sequencers...) from which events are received and/or to which they are sent.
  • Inputs:
    they represent ``{device channel}'' pair on which MIDI events arrive, like a MIDI keyboard, a MIDI control surface etc...
  • Output:
    they represent ``{device channel}'' pair on which MIDI events are sent, like synths. Outputs hold properties like patch number, volume, reverb depth or other controllers.
  • Filters:
    a filter is a set of rules that determines which MIDI event to discard and how to transform incoming events. The filter also "sanitizes" the input MIDI stream by removing nested notes, duplicate controllers and other anomalies.
  • Tracks:
    tracks represent pieces of music: they hold MIDI voice events (notes, controllers ...). Tracks aren't bound to a particular device or channel and can contain events from any device and channel.
  • Sysex banks:
    a sysex bank is a set of system exclusive messages. They will be sent to MIDI devices when performance mode is entered.
  • The "meta-track":
    it is a particular hidden track that contains only special events like tempo changes and time signature changes.
  • Song parameters:
    these are miscellaneous parameters like the metronome configuration, the current position, current selection...

Above objects are grouped in a project (a song) and manipulated in prompt mode by issuing interactively commands.

Performance mode is used to play/record the project. When performance mode is entered, MIDI devices are opened, and all sysex messages and output configuration events are sent. There are three performance modes:

  • "Idle" mode
    The MIDI input passes through the current filter and the result is sent to the MIDI output. No tracks are played or recorded.
    +---------+          +------------+          +----------+
    |         |          |            |          |          |
    | MIDI in |--------->|   filter   |--------->| MIDI out |
    |         |          |            |          |          |
    +---------+          +------------+          +----------+
    
  • Play mode
    The MIDI input passes through the current filter the result is mixed with the currently played tracks and finally sent to the MIDI output. No tracks are recorded.
                            +--------------+
                            | track_1 play |---\
                            +--------------+   |
                                               |
                                 ...        ---+
                                               |
                            +--------------+   |
                            | track_N play |---+
                            +--------------+   |
                                               |
    +---------+             +------------+     |    +----------+
    |         |             |            |     \--->|          |
    | MIDI in |------------>|   filter   |--------->| MIDI out |
    |         |             |            |          |          |
    +---------+             +------------+          +----------+
    
  • Record mode
    The MIDI input passes through the current filter and is recorded on the current track. The result is mixed with the currently played tracks and finally sent to the MIDI output. System exclusive messages are recorded to the current sysex without being passed through the filter.
                            +--------------+
                            | track_1 play |---\
                            +--------------+   |
                                               |
                                 ...        ---+
                                               |
                            +--------------+   |
                            | track_N play |---+
                            +--------------+   |
                                               |
    +---------+             +------------+     |    +----------+
    |         |             |            |     \--->|          |
    | MIDI in |-----+------>|   filter   |----+---->| MIDI out |
    |         |     |       |            |    |     |          |
    +---------+     |       +------------+    |     +----------+
                    |                         |
                    |                         |     +----------------+
                    |       +--------------+  \---->| track_X record |
                    \------>| sysex record |        +----------------+
                            +--------------+
    

The above performance modes are started with the single letter commands ``i'', ``p'' and ``r'' respectively. Certain functions are not available during performance mode; to stop it, use the ``s'' function.

1.5 An example

Suppose that there are two devices:

  • ``/dev/rmidi4'' - a MIDI sound module
  • ``/dev/rmidi3'' - a MIDI keyboard

In this case, the ``/etc/midishrc'' file probably should contain the following lines:

dnew 0 "/dev/rmidi4" wo         # attach the module as dev number 0
dnew 1 "/dev/rmidi3" ro         # attach the keyboard as dev number 1

the ``wo'' parameter means that the device will be opened in write-only mode.

If you're using ALSA, instead of /dev/rmidi3 and /dev/rmidi4, use the ALSA sequencer ports, as listed by ``aseqdump -l'', example:

dnew 0 "28:0" wo                # attach the module as dev number 0
dnew 1 "32:0" ro                # attach the keyboard as dev number 1

Alsa also accepts client names, instead of client numbers, ex:

dnew 0 "FLUID Synth (qsynth)" wo 

If you're using OpenBSD, insted of /dev/rmidi3 and /dev/rmidi4, use the sndio(7) port names:

dnew 0 "rmidi/3" wo             # attach the module as dev number 0
dnew 1 "rmidi/4" ro             # attach the keyboard as dev number 1

The following session shows how to record a simple track. First, we define a filter named ``piano'' that routes any event from device 1, channel 0 (the keyboard input) to device 0, channel 5 (the sound module output). Then we create a new track ``pi1'', we start recording and we save the song into a file.

send EOF character (control-D) to quit
[0000:00]> fnew piano                        # create filter "piano"
[0000:00]> fmap {any {1 0}} {any {0 5}}      # dev=1,ch=0 -> dev=0,ch=5
[0000:00]> tnew pi1                          # create track "pi1"
[0000:00]> r                                 # start recording
[0006:02]> s                                 # stop recording
[0000:00]> save "mysong.msh"                 # save the song into a file
[0000:00]>                                   # EOF (control-D) to quit

2 Devices setup

In midish, MIDI devices are numbered from 0 to 15. Each MIDI device has its device number. For instance, suppose that there is a MIDI sound module known as ``/dev/rmidi3'' and a MIDI keyboard known as ``/dev/rmidi4''. The following commands will configure the module as device number 0 and the keyboard as device number 1:

dnew 0 "/dev/rmidi4" rw
dnew 1 "/dev/rmidi3" rw

If you're using ALSA, then use ALSA port names (as listed by ``aseqdump -l'' command) instead of the device node paths. If ``nil'' is given instead of the path, then the port is not connected to any existing port; this allows other ALSA sequencer clients to subscribe to it and to provide events to midish or to consume events midish sends to it.

If you're using OpenBSD, then use sndio(7) port names (hardware ports, software MIDI thru boxes, aucat(1) control devices).

Note: To make easier the import/export procedure from systems with different configurations, it's strongly recommended to attach the main sound module (the mostly used one) as device number 0.

In order to check that the sound module is properly configured play the demo song:

send EOF character (control-D) to quit
[0000:00]> load "sample.msh"         # load the file
[0000:00]> p                         # start playback
[0004:02]> s                         # stop playback

When devices are set up, put the corresponding dnew commands in the user's ``$HOME/.midishrc''. It will be automatically executed the next time midish is run.

If midish is sent SIGUSR1, it reopens all MIDI ports that are registered with ``dnew`` but were not present during the initialization.

3 Inputs

Inputs are named ``{device channel}'' pairs on which MIDI events arrive. Inputs are handled by two-item lists, like this:

{1 0}                   # device 1, MIDI channel 0

Inputs are named using the ``inew'' command, as follows:

inew keyboard {1 0}

this defines the ``keyboard'' symbol that may be used instead of the ``{1 0}'' pair.

Note: Unlike simple ``{device channel}'' pairs, inputs are used internally by certain functions. For instance, inputs are used to create default filter rules.

4 Outputs

Outputs are named ``{device channel}'' pairs where go outgoing MIDI events. They are handled by two-item lists, like this:

{0 4}                   # device 0, MIDI channel 4

Outputs are named using the ``onew'' command, as follows:

onew mybass {0 4}

this defines an output named ``mybass'' that may be used instead of the ``{0 4}'' pair.

Outputs have a filter with the same name; it defines how input events are routed to the output. The filter comes with a default rule that routes all named inputs to the given output. See filter section for further details.

5 Output and input configuration events

Inputs and outputs may hold configuration events (like program changes and controllers) to be attached. Such events are sent when performance mode is entered, for instance just before playback is started.

For instance, to set program number 34 on output ``mybass'', attach a ``program change'' event as follows. First make ``mybass'' the current output:

co mybass
then, set its program number:
oaddev {pc mybass 34}

the list argument gives the event to attach to the output. See the event section for more details about events.

To set its volume (controller 7) to 120:

oaddev {ctl mybass 7 120}

If a setting is set multiple times, the last one is kept. For instance, the following will change the volume to 125 by replacing the above event:

oaddev {ctl mybass 7 125}
Events attached to inputs work the same way. When performance mode is entered, they are sent as if they come from the input. Consequently they pass through the current filter and will be recorded on the current track in record mode. This makes them useful only in very specific cases.

6 Events and event sets specification

6.1 Event specification

An event is specified as a list containing:

  • a reference from the following list: noff, non, kat, xctl, xpc, cat, bend rpn, nrpn.
  • an input or output
  • a number
  • a second number (not for ``cat'' and ``bend'')

Event references correspond to the following MIDI events:

ref. name MIDI event
noff note off
non note on
kat key after-touch (poly)
ctl 7-bit controller
xctl 14-bit controller
xpc bank and program change
cat channel after-touch (mono)
bend pitch bend
rpn RPN change
nrpn NRPN change

Examples:
note-on event on device 2, channel 9, note 64 with velocity 100:

{non {2 9} 64 100}

program change device 1, channel 3, patch 34, bank 1234

{xpc {1 3} 1234 34}

set controller number 7 to 99 on the ``drums'' output:

{ctl drums 7 99}

6.2 Event set specification

Filter and track editing functions use event sets to select the set of events they will affect. For instance a filter may check if incoming events match an user supplied event set in order to decide whether to drop or to keep the incoming event. The user must specify event sets as lists of parameter ranges. An event set is a list containing:

  • A keyword specifying the type of the selected events: any, note, ctl, pc, cat, bend, rpn, nrpn xpc, xctl, none
  • an optional input or output. If a name is given, its interpreted as an input or output created with inew or onew, else a ``{device channel}'' pair may be used. If a single number is given, it matches all MIDI channels of the given device number. Alternatively, a list containing a device number range and a MIDI channel number range may be used (see examples below).
  • an optional number range, specified as a list of two number separated by the ``..'' operator, like ``12..65''.
  • an optional second number range, in the same format as the first one (not permitted for ``pc'', ``bend'' and ``cat'')

In the above, empty lists can also be used. An empty list means "match everything". If the ``none'' keyword is used as event set type then the event set is the empty set, i.e. it matches no events.

Examples:

{}                      # match everything
{ any }                 # match everything
{ none }                # match nothing
{ any 1 }		# match anything on device 1
{ any bass }            # match anything on "bass" output
{ any {1 4} }           # match anything on device 1, channel 4
{ note }                # match note events
{ note {1 9} }          # match notes on dev 1, channel 9
{ note {0 3..5} }       # match notes on device 0, channel 3, 4, 5
{ note {} 0..64 }       # match notes between 0 an 64
{ ctl bass }            # match controllers on "bass" output
{ ctl bass 7 }          # match controller 7 on "bass" output
{ bend {} }             # match bender
{ xctl {} 19 }          # match 14-bit controller number 19
{ xpc  {} 1234 21 }     # match patch 21 on bank 1234
{ nrpn {} 21 }          # match NRPN 21 change

6.3 User-defined events

Short system-exclusive patterns can be handled as events. Such events are defined with the evpat function.

7 Filtering/routing

A filter transforms incoming MIDI events and send them to the output. The filter also sanitizes the input MIDI stream by removing nested notes, duplicate controllers and other anomalies. Filters are in general used to:

  • route events from inputs to outputs
  • map a controller to another
  • split the keyboard
  • drop unwanted events
  • etc...

Multiple filters may be defined, however only the current filter will run in performance mode. If no filters are defined or if there is no current filter then, in performance mode, input is sent to the output as-is. In any cases input events are checked for inconsistencies: nested note-on, orphaned note-off and duplicate controller, bender and aftertouch events are removed. The rate of controller, bender and aftertouch events is normalized in order not to flood output devices.

Filters are defined as follows:

fnew myfilt                     # create filter "myfilt"

initially the filter is empty and will send input to output as-is. Once the filter is created, filtering rules may be added, modified and removed. The filtering rules may be listed with the finfo function. All rules may be removed with the freset function. See filtering functions section for details.

7.1 Filter rules

The filter is configured through a list of filtering rules provided by the user. Each rule teaches the filter which incoming events to transform on which outgoing events. A rule is defined as

  • a source event set
  • a destination event set

When the filter is running if an incoming event is contained in the source set, then it is rewritten to match the destination set. With the following rule:

note {1 3} -> note {2 8}

note events arriving on device 1 channel 3 are sent to device 2, channel 8.

The source and destination event sets must be of the same type and must contain ranges of the same size. If the source and destination event sets contain ranges, then the incoming event will be rewritten to match the destination by being shifted. For instance, the following rule:

note {0 0} 40..49 -> note {0 0} 60..69

will map notes on input ``{0 0}'' by rewriting:

  • note 40 into note 60
  • note 41 into note 61
  • note 42 into note 62
  • ...
  • note 49 into note 69

In order to discard a set of input events, it's possible to create rules with the empty set as destination set. For instance to discard the volume controller (number 7) on input ``{0 0}'' the following rule might be used:

ctl {0 0} 7 -> none

Rules are created and deleted with the fmap and funmap functions respectively. For instance, to create a filter with the above rules:

send EOF character (control-D) to quit
[0000:00]> fnew myfilt
[0000:00]> fmap {note {1 3}}        {note {2 8}}
[0000:00]> fmap {note {0 0} 40..49} {note {0 0} 60..69}
[0000:00]> fmap {ctl {0 0} 7}       {none}
[0000:00]> finfo
{
        evmap xctl {0 0} 7 > none
        evmap note {0 0} 40..49 > note {0 0} 60..69
        evmap note {1 3} 0..127 > note {2 8} 0..127
}

7.2 Rules hierarchy

Filters may contain multiple rules. If the input event matches the source set of two rules, then the most specific rule wins, i.e. the event is run only through the rule with the smaller source set. If both rules have the same source set, then the event is duplicated and run through both rules.

This mechanism is useful to define a default rule with a large source set and then to refine it by defining exceptions to it. For instance consider the following filter:

fnew myfilt
fmap {any kbd} {any piano}
fmap {ctl kbd 1} {ctl piano 11}

the first rule will map all events coming from the ``kbd'' input to the ``piano'' output; the second rule maps controller 1 (modulation) to controller 11 (expression). Controller 1 events match source sets of both rules, but they will be run through the second rule only, because it has the smaller set. The order in which rules are created is not important because the fmap and funmap functions sort them.

In order to avoid inconsistencies there's a constraint on the source sets of rules of the same filter: if two source sets overlap, then one of them must contain the other one. The user doesn't need to care about this constraint since fmap and funmap functions will do the right thing in all cases.

7.3 Examples

7.3.1 Device redirections

The following example defines a filter that routes events from device number 1 (the MIDI keyboard) to device number 0 (the sound module).

fnew mydevmap                           # define filter "mydevmap"
fmap {any 1} {any 0}                    # make it route device 1 -> device 0

To test the filter, start performance mode using the ``i'' command and then type ``s'' to stop performance mode

7.3.2 Input to output mappings

The following example defines a filter that routes events from device 1, midi channel 0 (first channel of the keyboard) to device 0, midi channel 9 (default drum channel of the sound module).

fnew mydrums                            # define filter "mydrums"
fmap {any {1 0}} {any {0 9}}            # route dev/chan {1 0} to {0 9}

To test the filter, start performance mode using the ``i'' command. Playing on channel 0 of the keyboard will make sound channel 9 of the sound-module. To stop performance mode, use ``s'' command.

7.3.3 Controller mappings

The following example adds a new rule to the above filter that maps the modulation wheel (controller 1) of the input (i.e. device 1, midi channel 0) to the expression controller (number 11) of the output (device 0, midi channel 9).

fmap {ctl {1 0} 1} {ctl {0 9} 11}

Rules of the filter may be listed with the finfo function. It displays them as follows:

{
        evmap any {1 0} > any {0 9}
        evmap xctl {1 0} 1 > xctl {0 9} 11
}

7.3.4 Transpose

The following will transpose notes on ``{0 2}'' by 12 halftones:

fnew mypiano                                    # define filter ``mypiano''
fmap {any {1 0}} {any  {0 2}}                   # route {1 0} -> {0 9}
ftransp {any {0 2}} 12                          # transpose by 12 halftones

First we create the filter, and we add a ``default'' rule to map anything coming from ``{1 0}'' (i.e. the MIDI keyboard) to ``{0 2}''. The second rule transposes anything the filter generated on output ``{0 2}''. Note that the order of the rules is not important, the ``ftransp'' rule always applies to outgoing events.

7.3.5 Keyboard splits

In the same way it is possible to create a keyboard-split with two rules. The following example splits the keyboard in two parts (left and right) on note 64 (note E3, the middle of the keyboard). Notes on the left part will be routed to channel 3 of the sound module and notes on the right part will be routed to channel 2 of the sound module.

fnew mysplit
fmap {any {1 0}} {any {0 2}}
fmap {any {1 0}} {any {0 3}}
fmap {note {1 0} 0..63}   {note {0 2} 0..63}
fmap {note {1 0} 64..127} {note {0 3} 64..127}

8 Time structure

In midish, time is split in measures. Each measure is split in beats and each beat is split in ticks. The tick is the fundamental time unit in midish. Duration of ticks is fixed by the tempo. By default midish uses:

  • 24 ticks per beat
  • 4 beats per measure
  • 120 beats per minute

From the musical point of view, a beat often corresponds to a quarter note, to an eighth note etc... By default a whole note corresponds to 96 ticks, thus by default one beat corresponds to one quarter note, i.e. the time signature is 4/4.

8.1 Moving within the song

The following selects the current position in the song to measure number 3:

g 3

This will make ``p'' and ``r'' commands start at this particular position instead of measure number 0. Furthermore all track editing function will process the track starting at this position.

8.2 Metronome

The metronome has the following states:

  • off - metronome is disabled
  • on - metronome is enabled
  • rec - metronome is enabled only for recording (default)

The ``m'' command changes the mode of the metronome, example:

m on            # switch the metronome on
p               # start playback
s               # stop

The metronome has two kind of click-sounds:

  • high-click: on the first beat of the measure
  • low-click: on the other beats.

The click-sound can be configured by giving a couple of note-on events, as follows:

metrocf {non {0 9} 48 127} {non {0 9} 64 100}

this configures the high-click with note 48, velocity 127 on device 0, channel 9 and the low-click with note 64, velocity 100 on device 0, channel 9.

8.3 Time signature changes

Time signature changes are achieved by inserting or deleting measures. The following starts a song with time signature of 6/8 (at measure 0) and change the time signature to 4/4 at measure 2 during 3 measures:

g 0                     # go to measure 0
mins 4 {6 8}            # insert 4 measures at 8/6
g 2                     # move to measure 2
mins 3 {4 4}            # insert 3 measure at 4/4
m on                    # turn metronome on
p                       # test it, i.e. start playback
s                       # stop playback

To suppress measure number 2 (the first 4/4 measure)

g 2                     # go to measure 2
sel 1                   # select 1 measure
mcut                    # remove it
m on                    # switch the metronome on
p                       # test it
s                       #

To get the time signature at any given measure number, the msig function can be used. It returns the denominator and the numerator in a two integer list. For instance, to print the time signature at measure number 17:

g 17                    # go to measure 17
print [msig]            # print what msig returned      

A handy way to duplicate the time structure of a portion of the song is given by the mdup function. It copies the current selection at another place of the song:

g 17                    # go to measure 17
sel 16                  # select 16 measures
mdup 0                  # copy them behind the selection

The parameter to mdup is the number of measures to leave between the the original and the point where the replica is inserted.

8.4 Tempo changes

Tempo changes are achieved simply by moving within the song and using the ``t'' command to change the tempo at the current position. The tempo must be provided in beats per minute. For instance, the following changes tempo on measure 0 to 100 beats per minute and on measure 2 to 180 beats per minute.

g 0                     # go to measure 0
t 100                   # set tempo to 100 bpm
g 2                     # go to measure 2
t 180                   # set tempo to 180 bpm

To get the tempo at any given measure number, the mtempo function can be used. It returns the tempo in beats per minute. For instance, to print the tempo at measure number 17:

g 17                    # go to measure 17
print [msig]            # print what mtempo returned

9 Tracks

A track is a piece of music, namely an ordered in time list of MIDI events. In play mode, midish play simultaneously all defined tracks, in record-mode it plays all defined tracks and records the current track.

Tracks aren't assigned to any particular ``{device channel}'' pair. A track may contain MIDI data from any device and channel. A track can have its current filter; in this case, MIDI events are passed through that filter before being recorded. If the track has no current filter, then the song current filter is used instead. If there is neither track current filter nor song current filter, then MIDI events from all devices are recorded as-is.

9.1 Recording a track without a filter

The following defines a track and record events as-is from all MIDI devices:

[0000:00]> tnew mytrack      # create a new track
[0000:00]> r                 # start recording
[0003:02]> s                 # stop

tracks are played as follows:

[0000:00]> p                 # start playback
[0001:01]> s                 # stop playback

However, with the above configuration this will not work as expected because events from the input keyboard (device number 1) will be recorded as-is and then sent back to the device number 1 instead of being sent to the sound module (device number 0).

9.2 Recording a track with a filter

The following creates a filter and uses it to record to the above track:

[0000:00]> fnew mypiano                      # create the filter
[0000:00]> fmap {any {1 0}} {any {0 0}}      # dev1/chan0 -> dev0/chan0
[0000:00]> tnew mytrack                      # create the track
[0000:00]> r                                 # start recording
[0001:03]> s                                 # stop

This setup, is more suitable for multitracking. The correct approach for multitracking is to create a filter for each musical instrument, and then to use this filter to record one or more tracks per instrument.

9.3 Basics about editing tracks

Most track editing functions use the project context, i.e. a set of ``current values'':

  • current track - the track that will be processed, it's selected with the ``ct'' command.
  • current position - the measure number where processing will start. It's set using the ``g'' command
  • current selection - number of measures to process. It's set using the ``sel'' command
  • current event set - events type that will be processed. It's set using the ``ev'' command. Most of the time all events types need to be processed, but sometimes one might want to process only a subset (eg. only controller number 7), leaving the rest as-is.
  • current quantization step - this defines how notes at the selection boundaries are handled. It's set using the ``setq'' command. Using a quantization step allows to properly process events at the selection boundaries, by slightly enlarging the selection in order to select for processing events that would be outside the selection.

So editing tracks consists in modifying above parameters and issuing commands to process the current selection.

9.4 Copy, cut, insert

To clear the current selection of the current track:

tclr

above command will clear only events matching the current event type, which by defaults is set to ``any event''. To clear only controller number 7 of the current selection:

ev {ctl {} 7}
tclr

see the event set section for more details. If you don't plan to continue working only on controller number 7 events, then don't forget to revert the current event selection to the default value (which is ``{}'', all events).

To cut a piece of a track, for instance to cut 2 measures starting at measure number 5:

g 5                     # move to measure 5
sel 2                   # select 2 measures
tcut                    # cut them

this command removes 2 measures of ``time'' from the current track; thus this will shift all measures following the current position by 2 measures.

The following inserts 2 blank measures at measure number 3:

g 3
tins 2

similarly, since this commands insert time, this will shift all measures following the current position by 2 measures. Note: the tcut and tins functions cause a part of the track to be shifted. If there are signature changes in the project, the track contents may no more correspond to the project's signature. The correct way of cutting or inserting a portion of the project is to use mcut and mins, which preserve the signature.

The current selection of a track could be copied into another track. For instance the following will copy the current selection at measure 5 of track ``mypiano2''.

tcopy           # copy current selection
ct mypiano2     # change current track to ``mypiano2''
g 5             # go to measure 5
tpaste          # paste the copy

A complete portion of the project (all tracks and time structure included) can be copied with the mdup function. It copies the current selection and inserts it at the position given as argument to mdup. The argument is relative to the end of the current selection (if positive) or to the beginning of the current selection (if negative). Example:

g 17            # go to measure 17
sel 4           # select 4 measures
mdup 0          # create 3 copies
mdup 0
mdup 0

9.5 Track quantization

A track can be quantized by rounding event positions to the nearest exact position. The following will quantize the current selection of the current track by rounding event positions to the nearest quarter note.

setq 4          # quarter note = 1/4-th of a whole
tquanta 75

The last arguments gives the percent of quantization. 100% means full quantization and 0% leans no quantization at all. This is useful because full quantization often sounds too regular especially on acoustic patches.

There are two quantization algorithms:

  • tquanta - quantizes all selected events (including note-off events). This always preserves event order.
  • tquantf - quantizes only frame positions. In this case, note positions are modified without changing their lengths which, for instance, is recommended for staccato playing. As event order can't be preserved, in some highly pathologic cases, notes would be dropped to avoid overlapping notes.

9.6 Checking a track

It is possible that a MIDI device transmits bogus MIDI data. The following scans the track and removes bogus notes and unused controller events:

ct badtrack
tcheck

This function can be useful to remove nested notes when a track is recorded twice (or more) without being erased.

10 Frames, more about filtering and editing

In midish, MIDI events are packed into frames. For instance a note-on event followed by a note-off with the same note number constitute a frame. All filtering and editing functions work on frames, not on events. That means that all events within a frame are processed consistently. For instance, deleting a note-on event will also delete related note-off and key-aftertouch events. This ensures full consistency of tracks and MIDI I/O streams.

10.1 Note frames

Note frames are made of a starting "non" event any optional "kat" events and the stopping "noff" event. In editing functions note frames are copied/moved/deleted as a whole; they are never truncated. The starting event (note on) determines whether the frame is selected. For instance, in tclr function, only note frames whose "non" events are in the selected region are erased.

Conflicting note frames (i.e. with the same note number) are never merged. An attempt to copy a note frame on the top of a second one will erase the second one. This avoids having nested notes.

10.2 Pitch bend frames

A pitch bend frame is made of "bend" events. It starts with a "bend" event whose value is different from the default value (i.e. different from 0x3FFF). It stops when the value reaches the default value of 0x3FFF. In editing functions a "bend" frame may be truncated or split into multiple frames, however resulting frames always terminate with the default value. For instance, tclr may erase the middle of "bend" frame resulting in two new "bend" frames (the beginning and the ending of the old one).

Conflicting "bend" frames are merged. An attempt to copy a frame on top of another one will overwrite conflicting regions of the second one and "glue" the rest; this will result in a single frame, containing chunks of both original frames.

10.3 Controller frames

The way controller events are packed in a frame depend on the controller type. Currently the following controllers are supported:

  • Parameters: this controller change a parameter of the channel but it doesn't have a special default value. For instance volume controller, reverb depth etc... These controllers are not packed together, they form single event frames. This is the default behavior for most controllers.
  • Frames: these are controllers that have special default value and that are packed in frames, similar to "bend" frames. For instance modulation wheel, sustain pedal. For those frames special care is taken in editing functions.

The user can specify for each controller number the desired behavior. The ctlconf function configures the controller. As arguments, it takes the name of the controller (an identifier) the controller number and the default value of the controller. If the default value is ``nil'' (i.e. no default value), then the controller is considered as of type parameter. For instance, following command:

ctlconf expr 11 127

configures controller number 11 of type frame and with default value of 127. The controller name can be any identifier, it can be used for other functions to reference a controller. The ctlinfo function can be used to display the current configuration of all controllers:

ctltab {
        #
        # name  number  defval
        #
        mod     1       0
        vol     7       nil
        sustain 64      0
}

10.4 Other frames

Channel aftertouch events are packed in channel aftertouch frames. They behave exactly as pitch bender frames, except that the default value is zero.

Program/bank change, NRPN and RPN events are not packed together, they are single event frames.

11 System exclusive messages

Midish can send system exclusive messages to MIDI devices before starting performance mode. Typically, this feature can be used to change the configuration of MIDI devices. System exclusive (aka "sysex") messages are stored into named banks. To create a sysex bank named ``mybank'':

xnew mybank

Then, messages can be added:

xadd 0 {0xF0 0x7E 0x7F 0x09 0x01 0xF7}

This will store the "General-MIDI ON" messages into the bank. The second argument (here "0") is the device number to which the message will be sent when performance mode is entered. To send the latter messages to the corresponding device, just enter performance mode using the ``i'' command.

Sysex messages can be recorded from MIDI devices, this is useful to save "bulk dumps" from synthesizers. Sysex messages are automatically recorded on the current bank. So, to record a sysex:

cx mybank               # set current sysex bank
r                       # start recording

The next time performance mode is entered, recorded sysex messages will be sent back to the device. Information about the recorded sysex messages can be obtained as follows:

xinfo

A bank can be cleared by:

xrm {}

the second argument is a (empty) pattern, that matches any sysex message in the bank. The following will remove only sysex messages starting with 0xF0 0x7E 0x7F:

xrm {0xF0 0x7E 0x7F}

Sysex messages recorded from any device can be configured to be sent to other devices. To change the device number of all messages to 1:

xsetd 1 {}

the second argument is an empty pattern, thus it matches any sysex message in the bank. The following will change the device number of only sysex messages starting with 0xF0 0x7E 0x7F:

xsetd 1 {0xF0 0x7E 0x7F}

12 Obtaining information

The following functions gives some information about midish objects:

ls              # summary
minfo           # list tempo and signature  changes
iinfo           # list config events of current input
oinfo           # list config events of current output
finfo           # list rules of current filter
tinfo           # list events distribution of current track
dinfo 0         # list device 0 properties

Objects can be listed as follows:

print [tlist]   # print tracks list
print [ilist]   # print inputs list
print [olist]   # print outputs list
print [flist]   # print filters list
print [xlist]   # print sysex banks list

Current values can be obtained as follows:

print [getunit]         # ticks per whole note
print [getpos]          # print current position
print [getlen]          # print current selection length
print [getf]            # current filter
print [gett]            # current track
print [getx]            # current sysex bank
print [tgetf]           # default filter of current track

The device and the MIDI channel of a input or output may be obtained as follows:

print [igetc]           # print midi chan number of current input
print [igetd]           # print device number of current input
print [ogetc]           # print midi chan number of current output
print [ogetd]           # print device number of current output

To check if object exists:

print [iexists myinput]
print [oexists myouput]
print [fexists myfilt]
print [texists mytrack]
print [xexists mysx]

this will print 1 if the corresponding object exists and 0 otherwise.

13 Saving and loading songs

A song can be saved into a file as follows:

save "myfile.msh"

In a similar way, the song can be load from a file as follows:

load "myfile.msh"

All inputs, outputs, filters, tracks, their properties, and values of the current track, current filter are saved and restored. However, note that the local settings (like device configuration, metronome settings) are not saved.

14 Import/export standard MIDI files

Standard MIDI files type 0 or 1 can be imported. Each track in the standard MIDI file corresponds to a track in midish. Tracks are named ``trk00'', ``trk01'', ... All MIDI events are assigned to device number 0. Only the following meta events are handled:

  • tempo changes
  • time signature changes

all meta-events are removed from the "voice" tracks and are moved into the midish's meta-track. Finally tracks are checked for anomalies. Example:

import "mysong.mid"

Midish songs can be exported into standard MIDI files. Tempo changes and time signature changes are exported to a meta-track (first track of the MIDI file). Each output is exported as a track containing its configuration events. Voice tracks are exported as is in separate tracks. Note that device numbers of MIDI events are not stored in the MIDI file because the file format does not allow this. Example:

export "mysong.mid"

15 Undo

Most operations may be undone with the ``u'' command, example:

tquant 90               # quantize current track
u                       # restore current track

The the ``ul'' command lists the previous command calls that may be undone.

Theres no way to redo operations that are undone.

16 The interpreter's language

Even to achieve some simple tasks with midish, it's sometimes necessary to write several long statements. To make midish more usable, it suggested to use variables and/or to define procedures, as follows.

16.1 Global variables

Variables can be used to store numbers, strings and references to tracks, inputs, outputs and filters, like:

let x = 53              # store 53 into "x"
print $x                # prints "53"

The ``let'' keyword is used to assign values to variables and the dollar sign (``$'') is used to obtain variable values.

16.2 Defining simple procedures

For instance, let us create a procedure named ``gmon'' that creates a sysex bank and stores a the standard sysex message to turn on General MIDI mode on device number 0:

proc gmon {
        xnew gm
        xadd 0 { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 }
}

The ``proc'' keyword is followed by the procedure name ``gmon'' and then follows a list of statements between braces.

Procedures can take arguments. For instance, to improve above procedure to take the device number as argument:

proc gmon devnum {
        xnew gmon
        xadd $devnum { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 }
}

After the name of the procedure follows the argument names list that can be arbitrary identifiers. The value of an argument is obtained by preceding the variable name by the dollar sign ("$").

If the last argument name is the "..." string, then a variable argument list could be used. In this case, in the code block between braces, the "..." token will is replaced by the variable list of arguments. For instance:

proc gmon ... {
        xnew gmon
	for devnum in ... {
	        xadd $devnum { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 }
	}
}

A lot of similar procedures are defined in the sample ``midishrc'' file, shipped in the source tar-ball.

Procedure and variables definitions can be stored in the ``~/.midishrc'' file (or ``/etc/midishrc''). It will be automatically executed the next time you run midish.

17 Changes since last release

  • Add "undo" support, allowing the effects of most functions to be undone with the u function. The ul command lists changes that can be undone.
  • Add minimal loop support with the loop and noloop commands.
  • Add support for tap tempo with tap and tapev.
  • Add new tquantf quantization algorithm; the old tquant function was renamed to tquanta.
  • Add inline help: how the h displays function description.
  • Add the tvcurve command modify velocity of notes in tracks.
  • Add the tdump to display track contents.
  • Allow fmap and tevmap to change events types, for instance to map pitch-bend to a controller.
  • Increase max number of events to handle very large MIDI files.
  • Swap parameters of the xpc event: now bank number comes before the program number.
  • Delete the rmidish utility as midish has a built-in line editor.

    18 Project attributes

    18.1 Device attributes

    The following table summarizes the device attributes:

    attribute description
    device number integer that is used to reference the device
    clkrate number of ticks per whole note, default is 96, which corresponds to the MIDI standard
    clock ``tx'' flag boolean; if it is set, the real-time MIDI clock events (like start, stop, ticks) are transmitted to the MIDI device.
    ixctlset list of continuous controllers that are expected to be received with 14-bit precision.
    oxctlset list of continuous controllers that will be transmitted with 14-bit precision
    iev list of compound event types the device transmits; it's a subset of ``xpc'', ``nrpn'', ``rpn''.
    oev list of compound event types the device accepts; it's a subset of ``xpc'', ``nrpn'', ``rpn''.

    18.2 Input and output attributes

    The following table summarizes the input or output attributes:

    attribute description
    name identifier used to reference the input or output
    {dev chan} device and MIDI channel where events are sent to or received to
    conf events that are sent when performance mode is entered

    18.3 Filter attributes

    The following table summarizes the filter attributes:

    attribute description
    name identifier used to reference the filter
    rules set set of rules that handle MIDI events

    18.4 Track attributes

    The following table summarizes the track attributes:

    attribute description
    name identifier used to reference the track
    mute flag if true then the track is silent during playback
    current filter default filter. The track is recorded with this filter. If there is no current filter, then is is recorded with the song's default filter.

    18.5 Sysex attributes

    The following table summarizes the sysex back attributes:

    attribute description
    name identifier used to reference the sysex back
    list of messages each message in the list contains the actual message and the device number to which the message has to be sent.

    18.6 Song attributes

    The following table summarizes the song attributes:

    attribute description
    meta track a track containing tempo changes and time signature changes
    ticks_per_unit number of MIDI ticks per whole note, the default value is 96 which corresponds to the MIDI standard.
    tempo_factor number by which the tempo is multiplied on play and record.
    metronome mode can be ``on'', ``off'' or ``rec'', see ``m'' command.
    metro_hi a note-on event that is sent on the beginning of every measure if the metronome is enabled
    metro_lo a note-on event that is sent on the beginning of every beat if the metronome is enabled
    current track default track: the track that will be recorded in record mode
    current filter default filter. The filter with which the default track is recorded if it hasn't its default filter.
    current input default input. This value isn't used in real-time, however it can be used as default value when adding new rules to the filter.
    current output default output. This value isn't used in real-time, however it can be used as default value when adding new rules to the filter.
    current position current position (in measures) within the song. Playback and record start from this positions. It is also user as the beginning of the current selection
    current selection length (in measures) of the current selection. This value isn't used in real-time, however it is used as default value in track editing functions.
    current quant step current quantization step. This value isn't used in real-time, however it's used as default value for the track editing functions
    current event set current event selection This value isn't used in real-time, however it is used as default value for the track editing functions
    tap start mode one of ``off'', ``start'' or ``tempo''. See ``tap'' command.
    tap start events Events used to trigger start and/or measure tempo.

    19 Language reference

    19.1 Lexical structure

    The input line is split into tokens:

    • Identifiers
      an identifier is a sequence of up to 32 characters, digits and underscores (``_''). However, an identifier cannot start with a digit. Examples:
      mytrack _mytrack  my34_track23          # good
      123mytrack                              # bad, starts with digit
      mytrackabcdefghijklmnopqrstuvwxyz       # bad, to long
      
    • Numbers
      numbers should be in decimal or hex format and may not exceed 2^31. Hex numbers are preceded by "0x", as in the C language. Examples:

      token value
      123 123 in decimal
      0x100 256 in hex
      0xF0 240 in hex

    • String constants
      string constants are sequence of printable characters enclosed in pairs of double quotes. Thus a string cannot contain double quotes, tabs, newlines.
    • Keywords
      valid keywords are: nil, let, if, else, for, in, return, exit and proc.
    • Operators, separators etc...
      sequences of meta-characters like ``('', ``)'', ``+'', ``$'', newline character, ``;'', etc...
    • Comments
      comments are ignored, they start with ``#'' and end with the new line character.

    Multiple lines ending with ``\'' are parsed as a single line. Anything else generates a ``bad token'' error.

    19.2 Statements

    Any input line can be ether a function definition or a statement. Most statements end with the ``;'' character. However, in order to improve interactivity, the newline character can be used instead. Thus, the newline character cannot be used as a space. A statement can be:

    • A procedure call, example:
      myproc arg1 arg2
      
    • An assignment using the ``let'' keyword, examples:
      let x = 123
      let y = 1 + 2 * (3 + x)
      
      The left-hand side should be the name of a variable and the right hand side an expression (see the expression section).
    • An ``if..else'' statement, example:
      if $i {
              print "i is not zero";
      } else {
              print "i is zero";
      }
      

      the ``else'' and the second block are not mandatory. Note that since newline character is interpreted as a ``;'', the line cannot be broken in an arbitrary way. If the expression following the ``if'' keyword is true the first block is executed, otherwise the second one (if any) is executed. The expression is evaluated in the following way:

      expression bool. value
      non-zero integer true
      zero integer false
      non-empty list true
      empty list false
      non-empty string true
      empty string false
      any name true
      nil false

    • A loop over a list:
      for i in {"bli" "bla" "blu"} {
              print $i;
      }
      

      the block is executed for each value of the list to which ``$i'' is set.

    • A return statement. The return statement ends the procedure execution and returns a value to the caller. Example:
      return $x * $x;
      
    • An exit statement. The exit statement ends the execution of the current script. It takes no arguments. Example:
      exit
      

    19.3 Expressions

    An expression can be an arithmetic expression of constants, expressions, variable values, return values of function calls. The following constant types are supported:

    token type
    ``"this is a string"'' a string
    ``12345'' a number
    ``mytrack'' a reference
    ``nil'' has no value

    Variable are referenced by their identifier. Value of a variable is obtained with the ``$'' character.

    let i = 123             # puts 123 in i
    print $i                # prints the value of i
    

    The following operators are recognized:

    oper. usage associativity
    {} list definition left to right
    () grouping
    [] function call
    ! logical NOT right to left
    ~ bitwise NOT
    - unary minus
    * multiplication left to right
    / division
    % reminder
    + addition left to right
    - subtraction
    << left shift left to right
    >> right shift
    < less left to right
    <= less or equal
    > greater
    >= greater or equal
    == equal left to right
    != not equal
    & bitwise AND left to right
    ^ bitwise XOR
    | bitwise OR
    && logical AND left to right
    || logical OR
    .. range definition left to right

    Examples:

    2 * (3 + 4) + $x
    

    is an usual integer arithmetic expression.

    [tlist]
    

    is the returned value of the procedure ``tlist''.

    12..56
    

    is the range of integer between 12 and 65 (included).

    {"bla" 3 zer}
    

    is a list containing the string ``"bla"'' the integer 3 and the name ``zer''. A list is a set of expressions separated by spaces and enclosed between braces, a more complicated example is:

    {"hello" 1+2*3 mytrack $i [myproc] {a b c}}
    

    19.4 Procedure definition

    A procedure is defined with the keyword ``proc'' followed by the name of the procedure, the names of its arguments and a block containing its body, example:

    proc doubleprint x y {
            print $x
            print $y
    }
    

    Arguments and variables defined within a procedure are local to that procedure and may shadow a global variable with the same name. The return value is given to the caller with a ``return'' statement:

    proc square x {
            return $x * $x
    }
    

    20 Function reference

    20.1 Track functions

    tlist
    return the list of names of the tracks in the song example:
    print [tlist]
    
    tnew trackname
    create an empty track named ``trackname''
    tdel
    delete the current track.
    tren newname
    change the name of the current track to ``newname''
    texists trackname
    return 1 if ``trackname'' is a track, 0 otherwise
    taddev measure beat tick ev
    put the event ``ev'' on the current track at the position given by ``measure'', ``beat'' and ``tick''
    tsetf filtname
    set the default filter (for recording) of the current track to ``filtname''. It will be used in performace mode if there is no current filter.
    tgetf
    return the default filter (for recording) of the current track, returns ``nil'' if none
    tcheck
    check the current track for orphaned notes, nested notes and other anomalies; also removes multiple controllers in the same tick
    tcut
    cut the current selection of the current track.
    tclr
    clear the current selection of the current track. only events matching the current event selection (see ev function) are removed.
    tins amount
    insert ``amount'' empty measures in the current track, at the current position.
    tpaste
    copy the hidden temporary track (filled by tcopy) on the current position of the current track. the current event selection (see ev function) are copied
    tcopy
    copy the current selection of the current track into a hidden temporary track. Only events matching the current event selection (see ev function) are copied
    tquanta rate
    Round event positions to the nearest exact position, as defined by the see setq function. This affects events of the current selection of the current track. Event positions are rounded to the nearest tick multiple of the quantization step; Rate must be between 0 and 100: 0 means no quantization and 100 means full quantization.
    tquantf rate
    Round frame positions (eg. note positions) to the nearest exact position, as defined by the see setq function. Frame length (eg. note lengths) is preserved. Rate must be between 0 and 100: 0 means no quantization and 100 means full quantization.
    ttransp halftones
    transpose note events of current selection of the current track, by ``halftones'' half tones. Only events matching the current event selection (see ev function) are transposed.
    tvcurve weight
    Adjust velocity of note events, using the given ``weight'' in the -63..63 range. Applies only to note events of current selection of the current track, (see ev function).
    tevmap evspec1 evspec2
    convert events matching evspec1 (source) into events matching evspec2 (destination) in the current selection of the current track. Both evspec1 and evspec2 must have the same number of devices, channels, notes, controllers etc..
    trackmerge sourcetrack
    merge the ``sourcetrack'' into the current track
    mute trackname
    Mute the given track, i.e. events from ``trackname'' will not be played during record/playback.
    unmute trackname
    Unmute the given track, i.e. events from ``trackname'' will be played during record/playback.
    getmute trackname
    Return 1 if the given track is muted and 0 otherwise.
    tclist
    Return the list of outputs used by events stored in the current track.
    tinfo
    scan the current selection of the current track, an for each measure display the number of events that match the current event selection
    tdump
    Display selected events of the current track.

    20.2 Input functions

    inew name {dev chan}
    create an new input with th given name and assigned the given device and MIDI channel.
    iset {dev chan}
    set the ``{device channel}'' pair of the current input. All filters are updated to use the new setting as if the appropriate fchin function was invoked for each filter.
    idel
    delete current input.
    iren newname
    rename the current input to ``newname''
    iexists name
    return 1 if ``name'' is an input, 0 otherwise
    igetc
    return the MIDI channel number of the current input
    igetd name
    return the device number of the current input
    iaddev event
    add the event to the configuration of the current input, it's not used yet.
    irmev evspec
    remove all events matching ``evspec'' (see event ranges) from the configuration of the current input
    iinfo
    print all events on the config of the current input.

    20.3 Output functions

    onew name {dev chan}
    create an new output with the given name and assigned the given device and MIDI channel numbers. Outputs have a filter with the same name; initially it maps all inputs to the newly created output.
    oset {dev chan}
    set the ``{device channel}'' pair of the current output. All filters are updated to use the new setting as if the appropriate fchout function was invoked for each filter.
    odel
    delete current output.
    oren newname
    rename the current output to ``newname''
    iexists name
    return 1 if ``name'' is an output, 0 otherwise
    ogetc
    return the MIDI channel number of the current output
    ogetd name
    return the device number of the output.
    oaddev event
    add given event to the configuration of the current output.
    ormev evspec
    remove all events matching ``evspec'' (see event ranges) from the configuration of the current output.
    oinfo
    print all events of the config of the current output.

    20.4 Filter functions

    fnew filtname
    create an new filter named ``filtname''
    fdel filtname
    delete the current filter.
    fren newname
    rename the current filter to ``newname''
    fexists filtname
    return 1 if ``filtname'' is a filter, 0 otherwise
    freset
    remove all rules from the current filter.
    finfo
    list all fitering rules of the current filter
    fchgin old_evspec new_evspec
    rewrite all filtering rules of the current filter to consume ``new_evspec'' events instead of ``old_evspec'' events. This means that each rule that would consume ``old_evspec'' on the input will start consuming ``new_evspec'' instead.
    fswapin evspec1 evspec2
    Similar to fchgin but swap ``evspec1'' and ``evspec2'' in the source events set of each rule.
    fchgout old_evspec new_evspec
    rewrite all filtering rules of the current filter to produce ``new_evspec'' events instead of ``old_evspec'' events. This means that each rule that would produce ``old_evspec'' on the output will start producing ``new_evspec'' instead.
    fswapout evspec1 evspec2
    Similar to fchgout but swap ``evspec1'' and ``evspec2'' in the destination events set of each rule.
    fmap evspec1 evspec2
    add a new rule to the current filter, to make it convert events matching evspec1 (source) into events matching evspec2 (destination). Both evspec1 and evspec2 must have the same number of devices, channels, notes, controllers etc..
    funmap evspec1 evspec2
    remove event maps from the current filter. Any mapping with source included in evspec1 and destination inluded in evspec2 is deleted.
    ftransp evspec halftones
    transpose events generated by the filter and matching ``evspec'' by the give number of halftones
    fvcurve evspec weight
    adjusts velocity of note events produced by the filter, using the given ``weight'' in the -63..63 range. If ``weight'' is:
    • negative - sensitivity is decreased
    • positive - sensitivity is increased
    • zero - the velocity is unchanged

    20.5 System exclusive messages functions

    xnew sysexname
    create a new bank of sysex messages named ``sysexname''
    xdel
    delete the current bank of sysex messages.
    xren newname
    rename the current sysex bank to ``newname''
    xexists sysexname
    return 1 if ``sysexname'' is a sysex bank, 0 otherwise
    xrm pattern
    remove all sysex messages starting with ``pattern'' from the current sysex bank. The given pattern is a list of bytes; an empty pattern matches any sysex message.
    xsetd newdev pattern
    set device number to ``newdev'' on all sysex messages starting with ``pattern'' in the current sysex bank. The given pattern is a list of bytes; an empty pattern matches any sysex message.
    xadd devnum data
    add to the current sysex bank a new sysex message. ``data'' is a list containing the MIDI system exclusive message and ``devname'' is the device number to which the message will be sent when performance mode is entered
    xinfo
    print all sysex messages of the current sysex bank. Messages that are too long to be desplayed on a single line are truncated and the ``...'' string is displayed.
    ximport devnum path
    replace contents of the current sysex bank by contents of the given .syx file; messages are assigned to ``devnum'' device number.
    xexport path
    store contents of the current sysex bank in the given .syx file

    20.6 Real-time functions

    i
    enter ``idle'' performance mode. Start processing MIDI input and generating MIDI output. data passes through the current filter (if any) or through the current track's filter (if any).
    p
    play the song from the current position. Input passes through the current filter (if any) or through the current track's filter (if any).
    r
    play the song and record the input. Input passes through the current filter (if any) or through the current track's filter (if any). On startup, this function play one measure of countdown before the data start being recorded.
    s
    stop performance and release MIDI devices. I.e. stop the effect ``i'', ``p'' or ``r'' functions;
    sendraw device arrayofbytes
    send raw MIDI data to device number ``device'', for debugging purposes only.

    20.7 Song functions

    ev evspec
    set the current event selection. Most track editing functions will act only on events matching "evspec", ignoring all other events.
    setq step
    set the current quantization step to the given note value, as follow:
    • 4 - quarter note
    • 6 - quarter note triplet
    • 8 - eighth note
    • 12 - eighth note triplet
    • 16 - sixteenth note
    • 24 - sixteenth note triplet
    • etc...

    The quantization step will be used by tquanta and tquantf functions and also by all editing functions to optimize event selection. If the special ``nil'' value is specified as quantization step, then quatization is disabled.

    getq
    return the current quatization step
    g measure
    set the current song position pointer to the given measure number. Record and playback will start a that position. This also defines the beginning of the current selection used by most track editing functions.
    getpos
    return the current song position pointer which is also the start position of the current selection.
    sel length
    set the length of the current selection to ``length'' measures. The current selection start at the current position set with the ``g'' function.
    getlen
    return the length (in measures) of the current selection.
    loop
    Enable loop mode. When playback or recording is started, the current selection is repeated forever.
    noloop
    Disable loop mode.
    ct trackname
    set the current track. The current track is the one that will be recorded. Most track editing functions act on it.
    gett
    return the current track (if any) or ``nil''
    cf filtname
    set the current filter to ``filtname''. The current filter is the one used to process input MIDI events in performance mode. It's also the one affected by all filter editing functions.
    getf
    return the current filter or ``nil'' if none
    cx sysexname
    set the current sysex bank, i.e. the one that will be recorded. The current sysex back is the one affected by all sysex editing functions.
    getx
    return the current sysex bank or ``nil'' if none
    ci name
    set the current (named) input. All input editing functions will act on it.
    geti
    return the name of the current input or ``nil'' if none
    co channame
    set the current (named) output. All output editing functions will act on it.
    geto
    return the name of the current output or ``nil'' if none
    setunit ticks_per_unit
    set the time resolution of the sequencer to ``tpu'' ticks in a whole note (1 unit note = 4 quarter notes). The default is 96 ticks, which is the default of the MIDI standard.
    getunit
    return the number of ticks in a whole note
    fac tempo_factor
    set the tempo factor for play and record to the given integer value. The tempo factor must be between 50 (play half of the real tempo) and 200 (play at twice the real tempo).
    getfac
    return the current tempo factor
    t beats_per_minute
    set the tempo at the current song position
    mins amount {num denom}
    insert ``amount'' blank measures at the current song position. The time signature used is num/denom. If the time signature is an empty list (i.e. ``{}'') then the time signature at the current position is used.
    mcut
    cut the current selection of all tracks, including the time structure.
    mdup where
    duplicate the current selection inserting a copy of it at the position given by the ``where'' parameter. The target position is a measure number relative to the current selection to be copied. If ``where'' is positive it's relative to the end of the current selection; if it's negative it's relative to the beginning of the current selection.
    minfo
    print the meta-track (tempo changes, time signature changes.
    mtempo
    Return the tempo at the current song position. The unit is beats per minute.
    msig
    Return the time signature at the current song position. The result is a two number list: numerator and denominator.
    mend
    Return the ending measure of the song (i.e. its size in measures).
    ls
    list all tracks, inputs, outputs, filters and various default values
    save filename
    save the song into the given file. The ``filename'' is a quoted string.
    load filename
    load the song from a file named ``filename''. the current song is destroyed, even if the load command fails.
    reset
    destroy completely the song, useful to start a new song without restarting the program
    export filename
    save the song into a standard MIDI file, ``filename'' is a quoted string.
    import filename
    load the song from a standard MIDI file, ``filename'' is a quoted string. Only MIDI file ``type 1'' and ``type 0'' are supported.
    u
    undo last operation saved for undo.
    ul
    list operations saved for undo.

    20.8 Device functions

    dlist
    return the list of attached devices (list of numbers)
    dnew devnum filename mode
    attach MIDI device ``filename'' as device number ``devnum''; ``filename'' is a quoted string. The ``mode'' argument is the name of the mode, it can be on if the following:
    • ``ro'' - read-only, for input only devices
    • ``wo'' - write-only, for output only devices
    • ``rw'' - read and write.

    If midish is configured to use ALSA (default on Linux systems) then ``filename'' should contain the ALSA sequencer port, as listed by ``aseqdump -l'', (eg. ``28:0'', ``FLUID Synth (qsynth)''). If ``nil'' is given instead of the path, then the port is not connected to any existing port; this allows other ALSA sequencer clients to subscribe to it and to provide events to midish or to consume events midish sends to it.

    ddel devnum
    detach device number ``devnum''
    dmtcrx devnum
    use device number ``devnum'' as MTC source. In this case, midish will relocate, start and stop according to incoming MTC messages. Midish will generate its clock ticks from MTC, meaning that it will run at the same speed as the MTC device. This is useful to synchronize midish to an audio multi-tracker or any MTC capable audio application. If ``devnum'' is ``nil'', then MTC messages are ignored and the internal timer will be used instead.
    dmmctx { devnum1 devnum2 ... }
    Configure the given devices to transmit MMC start, stop and relocate messages. Useful to control MMC-capable audio applications from midish. By default, devices transmit MMC.
    dclktx { devnum1 devnum2 ... }
    Configure the given devices to transmit MIDI clock information (MIDI ticks, MIDI start and MIDI stop events). Useful to synchronize an external sequencer to midish.
    dclkrx devnum
    set device number ``devnum'' to be the master MIDI clock source. It will give midish MIDI ticks, MIDI start and MIDI stop events. This useful to synchronize midish to an external sequencer. If ``devnum'' is ``nil'', then the internal clock will be used and midish will act as master device.
    dclkrate devnum ticrate
    set the number of ticks in a whole note that are transmitted to the MIDI device (if dclktx was called for it). Default value is 96 ticks. This is the standard MIDI value and its not recommended to change it.
    dinfo devnum
    Print some information about the MIDI device.
    dixctl devnum list
    Setup the list of controllers that are expected to be received as 14-bit numbers (i.e. both coarse and fine MIDI controller messages will be expected). By default only coarse values are used, if unsure let this list empty.
    devoxctl devnum list
    Setup the list of controllers that will be transmitted as 14-bit numbers (both coarse and fine MIDI controller messages).
    diev devnum list
    Configure the device to process as a single event the following patterns of input MIDI messages.
    • ``xpc'' - group bank select controllers (0 and 32) with program changes into a signle ``xpc'' event.
    • ``nrpn'' - group NRPN controllers (98 and 99) with data entry controllers (6 and 38) into a single ``nrpn'' event.
    • ``rpn'' - same as ``nrpn'', but for RPN controllers (100 and 101).
    By default all of the above are enabled, which allows banks, NRPNs and RPNs to be handled by midish the standard way. It makes sense to disable grouping of above messages on rare hardware that maps above-mentioned controller numbers (0, 6, 32, 38, 98, 99, 100, 101) to other parameters than bank number and NRPN/RPN.
    doev devnum list
    Same as diev but for output MIDI messages.

    20.9 Event functions

    ctlconf ctlname ctlnumber defval
    Configure controller number ``ctlnumber'' with name ``ctlname'', and default value ``defval''. If defval is ``nil'' then there is no default value and corresponding controller events are not grouped into frames. See sec. Controller frames.
    ctlconfx ctlname ctlnumber defval
    Same as ctlconf function, but for 14-bit controllers. Thus defval is in the range 0..16383.
    ctlconf ctlname
    Unconfigure the given controller. ``ctlname'' is the identifier that was used with ctlconf
    ctlinfo
    Print the list of configured controllers
    evpat name sysex_pattern
    Define a new event type corresponding to the given system exclusive message pattern. The pattern is a list of bytes or event parameter identifiers (aka atoms). The following atoms are supported: v0, v0_lo, v0_hi, v1, v1_lo, v1_hi. They correspond to the full 7-bit value (coarse parameter), the low 7-bit nibble and the high 7-bit nibble (fine grained parameters) of the first and second parameters respectively. Example:
    evpat master {0xf0 0x7f 0x7f 0x04 0x01 v0_lo v0_hi 0xf7}
    
    defines a new event type for the standard master volume system exclusive message.
    evinfo
    Print the list of event patterns.

    20.10 Misc. functions

    m mode
    Set the mode of the metronome. The following modes are available:
    • ``on'' - turned on for both playback and record
    • ``rec'' - turned on for record only
    • ``off'' - turned off
    metrocf eventhi eventlo
    select the notes that the metronome plays. The pair of events must be note-ons
    tap mode
    Set the way start is triggered by events. The following modes are available:
    • ``off'' - no events trigger start
    • ``start'' - a single event triggers start
    • ``tempo'' - two events trigger start; the time between them corresponds to one beat and is used to determine the initial tempo.
    tapev evspec
    Events set used to trigger start when tap mode is ``start'' or ``tempo''.
    info
    display the list of built-in and user-defined procedures and global variables
    print expression
    display the value of the expression
    err string
    display the given string and abort the statement being executed.
    h funcname
    display list of arguments function ``funcname''
    exec filename
    read and executes the script from a file, ``filename'' is a quoted string. The execution of the script is aborted on error. If the script executes an exit statement, only the script is terminated.
    debug flag val
    set debug-flag ``flag'' to (integer) value ``val''. It's a developer knob. If ``val=0'' the corresponding debug-info are turned off. ``flag'' can be:
    • ``filt'' - show events passing through the current filter
    • ``mididev'' - show raw MIDI traffic on stderr
    • ``mixout'' - show conflicts in the output MIDI merger
    • ``norm'' - show events in the input normalizer
    • ``pool'' - show pool usage on exit
    • ``song'' - show start/stop events
    • ``timo'' - show timer internal errors
    • ``mem'' - show memory usage
    version
    Display midish version.
    panic
    Cause the sequencer to core-dump, useful to developpers.
    proclist
    Return the list of all user defined procs.
    builtinlist
    Return a list of all builtin commands.

    21 Using midish in other programs

    21.1 Creating scripts: batch mode

    Midish could be used from general purpose scripting languages to do MIDI-related tasks. This is accomplished by starting the ``midish'' binary and writing commands to it's standard input. To ease this process, midish should be started in batch mode, with the -b flag. In batch mode the ``~/.midishrc'' and ``/etc/midishrc'' files are not parsed, errors cause midish to exit, and ``p'', ``r'' and ``i'' commands are blocking.

    For instance the following simple shell script will play, on the ``/dev/rmidi1'' device, standard midi files enumerated on the command line:

    #!/bin/sh
    
    trap : 2
    
    for arg; do
    midish -b <<END
    dnew 0 "/dev/rmidi1" wo
    import "$arg"
    p
    END
    done
    

    The ``smfplay'' and ``smfrec'' files shipped in the source tar-balls are examples of such scripts.

    21.2 Creating front-ends: verbose mode

    A program that wants to use a midish feature, may start midish and issue commands on its standard input. Then, the standard output of midish could be parsed so the program can obtain the desired information (if any).

    To ease this process, the midish binary can be started with the -v flag; in this case it will write additional information on its standard output, allowing the caller to be notified of changes of the state of midish. The information is written on a single line starting with the + sign, as follows:

    • +ready
      means that midish is ready to parse and execute a new line. A front-end should always wait for this string before issuing any command.
    • +pos measure beat tick
      is written during performace mode on every beat, it gives the current song position.

    No midish function (like print) can generate a line starting with the + sign, so it is safe to assume that such lines are synchronization lines and not the output of a function. Furthermore, such lines will never appear in the middle of the output of a function. Additional information may be available in the same format in future versions of midish; thus front-ends should be able to ignore unknown lines starting with +.

    Generally, any front-end should use a loop similar to the following:

    while (!eof) {
            command = get_command_from_user();
            wait_for("+ready");
            write_to_midish_stdin(command);
            result = parse_midish_stdout();
            do_something(result);
    }
    

    The ``rmidish'' program shipped in the source tar-ball is and example of such front-end.

    22 Examples

    22.1 Example - MIDI filtering

    The following session show how to configure a keyboard split:

    send EOF character (control-D) to quit
    [0000:00]> inew kbd {1 0}
    [0000:00]> onew bass {0 5}
    [0000:00]> oaddev {pc bass 33}
    [0000:00]> onew piano {0 6}
    [0000:00]> oaddev {pc piano 2}
    [0000:00]> fnew split
    [0000:00]> fmap {any kbd} {any bass}
    [0000:00]> fmap {any kbd} {any piano}
    [0000:00]> fmap {note kbd 12..62} {note bass 0..50}
    [0000:00]> fmap {note kbd 63..127} {note piano 63..127}
    [0000:00]> finfo
    {
            evmap any {1 0} > any {0 5}
            evmap any {1 0} > any {0 6}
            evmap note {1 0} 12..62 > note {0 5} 0..50
            evmap note {1 0} 63..127 > note {0 6} 63..127
    }
    [0000:00]> i
    [0000:00]> s
    [0000:00]> save "piano-bass.msh"
    

    First we set the default input to device 1, channel 6, on which the keyboard is available. Then we define 2 named-channels ``bass'' on device 0, channel 5 and ``piano'' on device 0 channel 6. Then we assign patches to the respective channels. After this, we define a new filter ``split'' and we add rules corresponding to the keyboard-split on note number 62 (note D3), the bass is transposed by -12 half-tones (one octave).

    22.2 Example - recording a track

    The following session show how to record a track.

    send EOF character (control-D) to quit
    [0000:00]> inew kbd {1 0}                    # select default input
    [0000:00]> onew drums {0 9}                  # create drum output
    [0000:00]> tnew dr1                          # create track ``dr1''
    [0000:00]> t 90                              # tempo to 90 bpm
    [0000:00]> r                                 # start recording
    [0003:03]> s                                 # stop
    [0000:00]> setq 16                           # set quantization step
    [0000:00]> sel 32                            # select 32 measures
    [0000:00]> tquanta 75                        # quantize to 75%
    [0000:00]> p                                 # play
    [0001:02]> s                                 # stop playing
    [0000:00]> save "myrythm.msh"                # save to a file
    [0000:00]>                                   # hit ^D to quit
    

    first, we set the default input to ``{1 0}'' (the keyboard). Then, we define the ``drum'' output as device 0, channel 9, this creates a default filter that maps ``kbd'' to ``drums''. Then we define a new track named ``dr1'' an we start recording. Then, we set the quantization step to 16 (sixteenth note), we select the first 32 measures of the track and we quantize them. Finally, we start playback and we save the song into a file.

    23 Copyright

    Copyright (c) 2003-2010 Alexandre Ratchov <alex@caoua.org>
    Copyright (c) 2008 Willem van Engen <wvengen@stack.nl>

    Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

    24 Thanks

    Many thanks to all who contributed (new features, bug fixes, bug reports, packaging efforts and other improvements): Julien Claassen, Karim Saddem, Marcell Mars, Richard L. Hamilton, Samuel Mimram, Will Woodruff, and Willem van Engen.

    Copyright (c) 2003-2019 Alexandre Ratchov
    Last updated jun 5, 2019
    midish-1.4.0/mdep.c010066400017510001751000000254151501104363400125770ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * machine and OS dependent code */ #include #include #include #include #include #ifdef __APPLE__ #include #endif #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "mux.h" #include "mididev.h" #include "cons.h" #include "user.h" #include "exec.h" #include "tty.h" #include "utils.h" #define TIMER_USEC 1000 #ifndef RC_NAME #define RC_NAME "midishrc" #endif #ifndef RC_DIR #define RC_DIR "/etc" #endif #define MIDI_BUFSIZE 1024 #define MAXFDS (DEFAULT_MAXNDEVS + 1) volatile sig_atomic_t cons_quit = 0, resize_flag = 0, cont_flag = 0, usr1_flag = 0; struct timespec ts, ts_last; int cons_eof, cons_isatty; #if defined(__APPLE__) && !defined(CLOCK_MONOTONIC) #define CLOCK_MONOTONIC 0 int clock_gettime(int which, struct timespec *ts) { static mach_timebase_info_data_t info; unsigned long long ns; if (info.denom == 0) mach_timebase_info(&info); ns = mach_absolute_time() * info.numer / info.denom; ts->tv_sec = ns / 1000000000L; ts->tv_nsec = ns % 1000000000L; return 1; } #endif /* * handler for SIGALRM, invoked periodically */ void mdep_sigalrm(int i) { /* nothing to do, we only want poll() to return EINTR */ } void mdep_sigwinch(int s) { resize_flag = 1; } void mdep_sigcont(int s) { cont_flag = 1; } void mdep_sigusr1(int s) { usr1_flag = 1; } /* * start the mux, must be called just after devices are opened */ void mux_mdep_open(void) { static struct sigaction sa; struct itimerval it; sigset_t set; sigemptyset(&set); sigaddset(&set, SIGPIPE); if (sigprocmask(SIG_BLOCK, &set, NULL)) { log_perror("mux_mdep_open: sigprocmask"); exit(1); } if (clock_gettime(CLOCK_MONOTONIC, &ts_last) < 0) { log_perror("mux_mdep_open: clock_gettime"); exit(1); } sa.sa_flags = SA_RESTART; sa.sa_handler = mdep_sigalrm; sigfillset(&sa.sa_mask); if (sigaction(SIGALRM, &sa, NULL) < 0) { log_perror("mux_mdep_open: sigaction"); exit(1); } it.it_interval.tv_sec = 0; it.it_interval.tv_usec = TIMER_USEC; it.it_value.tv_sec = 0; it.it_value.tv_usec = TIMER_USEC; if (setitimer(ITIMER_REAL, &it, NULL) < 0) { log_perror("mux_mdep_open: setitimer"); exit(1); } } /* * stop the mux, must be called just before devices are closed */ void mux_mdep_close(void) { struct itimerval it; it.it_value.tv_sec = 0; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_REAL, &it, NULL) < 0) { log_perror("mux_mdep_close: setitimer"); exit(1); } } /* * wait until an input device becomes readable or * until the next clock tick. Then process all events. * Return 0 if interrupted by a signal */ int mux_mdep_wait(int docons) { int i, res, revents; nfds_t nfds; struct pollfd *pfd, *tty_pfds, pfds[MAXFDS]; struct mididev *dev; unsigned char midibuf[MIDI_BUFSIZE]; long long delta_nsec; nfds = 0; if (docons && !cons_eof) { tty_pfds = &pfds[nfds]; if (cons_isatty) nfds += tty_pollfd(tty_pfds); else { tty_pfds->fd = STDIN_FILENO; tty_pfds->events = POLLIN; nfds++; } } else tty_pfds = NULL; if (cons_quit) { fprintf(stderr, "\n--interrupt--\n"); cons_quit = 0; if (cons_isatty) tty_int(); return 0; } if (resize_flag) { resize_flag = 0; if (cons_isatty) tty_winch(); } if (cont_flag) { cont_flag = 0; if (cons_isatty) tty_reset(); } if (usr1_flag) { usr1_flag = 0; for (dev = mididev_list; dev != NULL; dev = dev->next) { if (dev->eof) { mididev_close(dev); mididev_open(dev); if (!dev->eof) { log_puti(dev->unit); log_puts(": device reopened\n"); } } } } for (dev = mididev_list; dev != NULL; dev = dev->next) { if (!(dev->mode & MIDIDEV_MODE_IN) || dev->eof) { dev->pfd = NULL; continue; } pfd = &pfds[nfds]; nfds += dev->ops->pollfd(dev, pfd, POLLIN); dev->pfd = pfd; } res = poll(pfds, nfds, -1); if (res < 0 && errno != EINTR) { log_perror("mux_mdep_wait: poll"); exit(1); } if (res > 0) { for (dev = mididev_list; dev != NULL; dev = dev->next) { pfd = dev->pfd; if (pfd == NULL) continue; revents = dev->ops->revents(dev, pfd); if (revents & POLLIN) { res = dev->ops->read(dev, midibuf, MIDI_BUFSIZE); if (dev->eof) { mux_errorcb(dev->unit); continue; } if (dev->isensto > 0) { dev->isensto = MIDIDEV_ISENSTO; } mididev_inputcb(dev, midibuf, res); } if (revents & POLLHUP) { dev->eof = 1; mux_errorcb(dev->unit); } } } if (mux_isopen) { if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) { log_perror("mux_mdep_wait: clock_gettime"); panic(); } /* * number of micro-seconds between now and the last * time we called poll(). Warning: because of system * clock changes this value can be negative. */ delta_nsec = 1000000000LL * (ts.tv_sec - ts_last.tv_sec); delta_nsec += ts.tv_nsec - ts_last.tv_nsec; if (delta_nsec > 0) { ts_last = ts; if (delta_nsec < 1000000000LL) { /* * update the current position, * (time unit = 24th of microsecond) */ mux_timercb(24 * delta_nsec / 1000); } else { /* * delta is too large (eg. the program was * suspended and then resumed), just ignore it */ log_puts("ignored huge clock delta\n"); } } } log_flush(); if (tty_pfds) { if (cons_isatty) { revents = tty_revents(tty_pfds); if (revents & POLLHUP) cons_eof = 1; } else { if (tty_pfds->revents & POLLIN) { res = read(STDIN_FILENO, midibuf, MIDI_BUFSIZE); if (res < 0) { cons_eof = 1; log_perror("stdin"); } else if (res == 0) { cons_eof = 1; user_onchar(NULL, -1); } else { for (i = 0; i < res; i++) user_onchar(NULL, midibuf[i]); } } } } return 1; } /* * sleep for 'millisecs' milliseconds useful when sending system * exclusive messages * * IMPORTANT : must never be called from inside mux_run() */ void mux_sleep(unsigned millisecs) { int res, delta_msec; struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts_last) < 0) { log_perror("mux_sleep: clock_gettime"); exit(1); } ts.tv_sec = ts_last.tv_sec + millisecs / 1000; ts.tv_nsec = ts_last.tv_nsec + (millisecs % 1000) * 1000000; if (ts.tv_nsec >= 1000000000) { ts.tv_sec++; ts.tv_nsec -= 1000000000; } while (1) { delta_msec = (ts.tv_sec - ts_last.tv_sec) * 1000 + (ts.tv_nsec - ts_last.tv_nsec) / 1000000; if (delta_msec <= 0) break; res = poll(NULL, 0, delta_msec); if (res >= 0) break; if (errno != EINTR) { log_perror("mux_sleep: poll"); exit(1); } if (clock_gettime(CLOCK_MONOTONIC, &ts_last) < 0) { log_perror("mux_sleep: clock_gettime"); exit(1); } } } void cons_mdep_sigint(int s) { if (cons_quit) _exit(1); cons_quit = 1; } void cons_init(struct el_ops *el_ops, void *el_arg) { struct sigaction sa; cons_eof = 0; cons_quit = 0; sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = cons_mdep_sigint; if (sigaction(SIGINT, &sa, NULL) < 0) { log_perror("cons_mdep_init: sigaction(int)"); exit(1); } sa.sa_handler = mdep_sigwinch; if (sigaction(SIGWINCH, &sa, NULL) < 0) { log_perror("cons_mdep_init: sigaction(winch) failed"); exit(1); } sa.sa_handler = mdep_sigcont; if (sigaction(SIGCONT, &sa, NULL) < 0) { log_perror("cons_mdep_init: sigaction(cont) failed"); exit(1); } sa.sa_handler = mdep_sigusr1; if (sigaction(SIGUSR1, &sa, NULL) < 0) { log_perror("cons_mdep_init: sigaction(usr1) failed"); exit(1); } if (!user_flag_batch && !user_flag_verb && tty_init()) { cons_isatty = 1; el_init(el_ops, el_arg); el_setprompt("> "); tty_reset(); } else cons_isatty = 0; } void cons_done(void) { struct sigaction sa; if (cons_isatty) { el_done(); tty_done(); } sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; if (sigaction(SIGINT, &sa, NULL) < 0) { log_perror("cons_mdep_done: sigaction(int)"); exit(1); } if (sigaction(SIGWINCH, &sa, NULL) < 0) { log_perror("cons_mdep_done: sigaction(winch)"); exit(1); } if (sigaction(SIGCONT, &sa, NULL) < 0) { log_perror("cons_mdep_done: sigaction(cont)"); exit(1); } if (sigaction(SIGUSR1, &sa, NULL) < 0) { log_perror("cons_mdep_done: sigaction(usr1)"); exit(1); } } /* * start $HOME/.midishrc script, if it doesn't exist then * try /etc/midishrc */ unsigned exec_runrcfile(struct exec *o) { char *home; char name[PATH_MAX]; struct stat st; home = getenv("HOME"); if (home != NULL) { snprintf(name, PATH_MAX, "%s" "/" "." RC_NAME, home); if (stat(name, &st) == 0) { return exec_runfile(o, name); } } if (stat(RC_DIR "/" RC_NAME, &st) == 0) { return exec_runfile(o, RC_DIR "/" RC_NAME); } return 1; } void user_oncompl_path(char *text, int *rstart, int *rend) { char str[PATH_MAX + 1]; struct dirent *dent; DIR *dirp; struct stat sb; size_t len, path_len; int dir_start, dir_end, start, end; dir_start = *rstart; start = end = *rend; while (1) { if (start == dir_start) { dir_end = start; str[0] = '.'; str[1] = 0; break; } if (text[start - 1] == '/') { dir_end = start; len = dir_end - dir_start; if (len >= PATH_MAX) return; memcpy(str, text + dir_start, len); str[len] = 0; break; } start--; } dirp = opendir(str); if (dirp == NULL) return; path_len = strlen(str); str[path_len++] = '/'; while (1) { dent = readdir(dirp); if (dent == NULL) break; if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; len = strlen(dent->d_name); if (path_len + len >= PATH_MAX) continue; memcpy(str + path_len, dent->d_name, len + 1); if (stat(str, &sb) == -1) continue; if (S_ISREG(sb.st_mode)) str[path_len + len++] = '"'; else if (S_ISDIR(sb.st_mode)) str[path_len + len++] = '/'; else continue; str[path_len + len] = 0; el_compladd(str + path_len); } closedir(dirp); *rstart = start; *rend = end; } midish-1.4.0/mdep_alsa.c010066400017510001751000000201021501104363400135630ustar00alexalex/* * Copyright (c) 2008 Willem van Engen * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef USE_ALSA #include #include #include #include #include #include #include #include #include #include "utils.h" #include "mididev.h" #include "str.h" struct alsa { struct mididev mididev; /* device stuff */ snd_seq_t *seq_handle; /* sequencer connection */ int port; /* port id of midish endpoint */ char *path; /* e.g. "128:0", translated in dst */ snd_midi_event_t *iparser; /* midi input event parser */ snd_midi_event_t *oparser; /* midi output event parser */ int nfds; }; void alsa_open(struct mididev *); unsigned alsa_read(struct mididev *, unsigned char *, unsigned); unsigned alsa_write(struct mididev *, unsigned char *, unsigned); unsigned alsa_nfds(struct mididev *); unsigned alsa_pollfd(struct mididev *, struct pollfd *, int); int alsa_revents(struct mididev *, struct pollfd *); void alsa_close(struct mididev *); void alsa_del(struct mididev *); struct devops alsa_ops = { alsa_open, alsa_read, alsa_write, alsa_nfds, alsa_pollfd, alsa_revents, alsa_close, alsa_del }; void alsa_err(const char *file, int ln, const char *fn, int e, const char *fmt, ...) { va_list ap; if (e != EINTR) { fprintf(stderr, "%s: ", fn); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (e != 0) fprintf(stderr, ": %s", snd_strerror(e)); fprintf(stderr, "\n"); } } struct mididev * alsa_new(char *path, unsigned mode) { struct alsa *dev; dev = xmalloc(sizeof(struct alsa), "alsa"); mididev_init(&dev->mididev, &alsa_ops, mode); dev->path = (path != NULL) ? str_new(path) : NULL; dev->seq_handle = NULL; dev->port = -1; dev->iparser = NULL; dev->oparser = NULL; return (struct mididev *)&dev->mididev; } void alsa_del(struct mididev *addr) { struct alsa *dev = (struct alsa *)addr; mididev_done(&dev->mididev); if (dev->path) str_delete(dev->path); xfree(dev); } void alsa_open(struct mididev *addr) { struct alsa *dev = (struct alsa *)addr; struct snd_seq_addr dst; unsigned int mode; char name[32]; /* * alsa displays annoying ``Interrupted system call'' messages caused * by poll(4) system call being interrupted by SIGALRM, which is not * an error. So, add an error handler that ignores EINTR. */ (void)snd_lib_error_set_handler(alsa_err); if (snd_seq_open(&dev->seq_handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) { log_puts("alsa_open: could not open ALSA sequencer\n"); dev->mididev.eof = 1; return; } snprintf(name, sizeof(name), "midish/%u", dev->mididev.unit); if (snd_seq_set_client_name(dev->seq_handle, name) < 0) { log_puts("alsa_open: could set client name\n"); dev->mididev.eof = 1; return; } mode = 0; if (dev->mididev.mode & MIDIDEV_MODE_IN) { mode |= SND_SEQ_PORT_CAP_WRITE; if (dev->path == NULL) mode |= SND_SEQ_PORT_CAP_SUBS_WRITE; } if (dev->mididev.mode & MIDIDEV_MODE_OUT) { mode |= SND_SEQ_PORT_CAP_READ; if (dev->path == NULL) mode |= SND_SEQ_PORT_CAP_SUBS_READ; } if (dev->mididev.mode == (MIDIDEV_MODE_IN | MIDIDEV_MODE_OUT)) mode |= SND_SEQ_PORT_CAP_DUPLEX; dev->port = snd_seq_create_simple_port(dev->seq_handle, "default", mode, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); if (dev->port < 0) { log_puts("alsa_open: could not create port\n"); dev->mididev.eof = 1; return; } /* * now we have the port, create parsers */ if (dev->mididev.mode & MIDIDEV_MODE_IN) { if (snd_midi_event_new(MIDIDEV_BUFLEN, &dev->iparser) < 0) { dev->iparser = NULL; log_puts("alsa_open: could not create in parser\n"); dev->mididev.eof = 1; return; } } if (dev->mididev.mode & MIDIDEV_MODE_OUT) { if (snd_midi_event_new(MIDIDEV_BUFLEN, &dev->oparser) < 0) { dev->oparser = NULL; log_puts("alsa_open: couldn't create out parser\n"); dev->mididev.eof = 1; return; } } /* * if destination is specified, connect the port */ if (dev->path != NULL) { if (snd_seq_parse_address(dev->seq_handle, &dst, dev->path) < 0) { log_puts("alsa_open: couldn't parse alsa port\n"); dev->mididev.eof = 1; return; } if ((dev->mididev.mode & MIDIDEV_MODE_IN) && snd_seq_connect_from(dev->seq_handle, dev->port, dst.client, dst.port) < 0) { log_puts("alsa_open: couldn't connect to input\n"); dev->mididev.eof = 1; return; } if ((dev->mididev.mode & MIDIDEV_MODE_OUT) && snd_seq_connect_to(dev->seq_handle, dev->port, dst.client, dst.port) < 0) { log_puts("alsa_open: couldn't connect to output\n"); dev->mididev.eof = 1; return; } } dev->nfds = snd_seq_poll_descriptors_count(dev->seq_handle, POLLIN); } void alsa_close(struct mididev *addr) { struct alsa *dev = (struct alsa *)addr; if (dev->iparser) { snd_midi_event_free(dev->iparser); dev->iparser = NULL; } if (dev->oparser) { snd_midi_event_free(dev->oparser); dev->oparser = NULL; } if (dev->port) { snd_seq_delete_simple_port(dev->seq_handle, dev->port); dev->port = -1; } (void)snd_seq_close(dev->seq_handle); dev->seq_handle = NULL; dev->mididev.eof = 1; } unsigned alsa_read(struct mididev *addr, unsigned char *buf, unsigned count) { struct alsa *dev = (struct alsa *)addr; unsigned todo = count; snd_seq_event_t *ev; long len; int err; if (!dev->seq_handle || !dev->iparser) return 0; while (todo > 0 && snd_seq_event_input_pending(dev->seq_handle, 1) > 0) { err = snd_seq_event_input(dev->seq_handle, &ev); if (err < 0) { log_puts("alsa_read: snd_seq_event_input() failed\n"); dev->mididev.eof = 1; return 0; } len = snd_midi_event_decode(dev->iparser, buf, todo, ev); if (len < 0) { /* fails for ALSA specific stuff we dont care about */ continue; } todo -= len; buf += len; } return count - todo; } unsigned alsa_write(struct mididev *addr, unsigned char *buf, unsigned count) { struct alsa *dev = (struct alsa *)addr; unsigned todo = count; snd_seq_event_t ev; long len; if (!dev->seq_handle || !dev->oparser) return 0; while (todo > 0) { /* * encode to sequencer commands */ len = snd_midi_event_encode(dev->oparser, buf, todo, &ev); if (len < 0) { log_puts("alsa_write: failed to encode buf\n"); dev->mididev.eof = 1; return 0; } buf += len; todo -= len; if (ev.type == SND_SEQ_EVENT_NONE) continue; snd_seq_ev_set_direct(&ev); snd_seq_ev_set_dest(&ev, SND_SEQ_ADDRESS_SUBSCRIBERS, 255); snd_seq_ev_set_source(&ev, dev->port); if (snd_seq_event_output_direct(dev->seq_handle, &ev) < 0) { dev->mididev.eof = 1; return 0; } } return count; } unsigned alsa_nfds(struct mididev *addr) { struct alsa *dev = (struct alsa *)addr; return dev->nfds; } unsigned alsa_pollfd(struct mididev *addr, struct pollfd *pfd, int events) { struct alsa *dev = (struct alsa *)addr; if (!dev->seq_handle) { log_puts("alsa_pollfd: no handle\n"); return 0; } return snd_seq_poll_descriptors(dev->seq_handle, pfd, INT_MAX, events); } int alsa_revents(struct mididev *addr, struct pollfd *pfd) { struct alsa *dev = (struct alsa *)addr; unsigned short revents; if (!dev->seq_handle) { log_puts("alsa_revents: no handle\n"); return 0; } if (snd_seq_poll_descriptors_revents(dev->seq_handle, pfd, dev->nfds, &revents) < 0) { log_puts("alsa_revents: snd_..._revents() failed\n"); return 0; } return revents; } #endif midish-1.4.0/mdep_raw.c010066400017510001751000000070511501104363400134440ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef USE_RAW #include #include #include #include #include #include "utils.h" #include "cons.h" #include "mididev.h" #include "str.h" struct raw { struct mididev mididev; /* device stuff */ char *path; /* eg. "/dev/rmidi3" */ int fd; /* file desc. */ }; void raw_open(struct mididev *); unsigned raw_read(struct mididev *, unsigned char *, unsigned); unsigned raw_write(struct mididev *, unsigned char *, unsigned); unsigned raw_nfds(struct mididev *); unsigned raw_pollfd(struct mididev *, struct pollfd *, int); int raw_revents(struct mididev *, struct pollfd *); void raw_close(struct mididev *); void raw_del(struct mididev *); struct devops raw_ops = { raw_open, raw_read, raw_write, raw_nfds, raw_pollfd, raw_revents, raw_close, raw_del }; struct mididev * raw_new(char *path, unsigned mode) { struct raw *dev; if (path == NULL) { cons_err("path must be set for raw devices"); return NULL; } dev = xmalloc(sizeof(struct raw), "raw"); mididev_init(&dev->mididev, &raw_ops, mode); dev->path = str_new(path); return (struct mididev *)&dev->mididev; } void raw_del(struct mididev *addr) { struct raw *dev = (struct raw *)addr; mididev_done(&dev->mididev); str_delete(dev->path); xfree(dev); } void raw_open(struct mididev *addr) { struct raw *dev = (struct raw *)addr; int mode; if (dev->mididev.mode == MIDIDEV_MODE_IN) { mode = O_RDONLY; } else if (dev->mididev.mode == MIDIDEV_MODE_OUT) { mode = O_WRONLY; } else if (dev->mididev.mode == (MIDIDEV_MODE_IN | MIDIDEV_MODE_OUT)) { mode = O_RDWR; } else { log_puts("raw_open: not allowed mode\n"); panic(); mode = 0; } dev->fd = open(dev->path, mode, 0666); if (dev->fd < 0) { log_perror(dev->path); dev->mididev.eof = 1; return; } } void raw_close(struct mididev *addr) { struct raw *dev = (struct raw *)addr; if (dev->fd < 0) return; (void)close(dev->fd); dev->fd = -1; } unsigned raw_read(struct mididev *addr, unsigned char *buf, unsigned count) { struct raw *dev = (struct raw *)addr; ssize_t res; res = read(dev->fd, buf, count); if (res < 0) { log_perror(dev->path); dev->mididev.eof = 1; return 0; } return res; } unsigned raw_write(struct mididev *addr, unsigned char *buf, unsigned count) { struct raw *dev = (struct raw *)addr; ssize_t res; res = write(dev->fd, buf, count); if (res < 0) { log_perror(dev->path); dev->mididev.eof = 1; return 0; } return res; } unsigned raw_nfds(struct mididev *addr) { return 1; } unsigned raw_pollfd(struct mididev *addr, struct pollfd *pfd, int events) { struct raw *dev = (struct raw *)addr; pfd->fd = dev->fd; pfd->events = events; pfd->revents = 0; return 1; } int raw_revents(struct mididev *addr, struct pollfd *pfd) { return pfd->revents; } #endif midish-1.4.0/mdep_sndio.c010066400017510001751000000073341501104363400137730ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef USE_SNDIO #include #include #include "utils.h" #include "cons.h" #include "mididev.h" #include "str.h" struct sndio { struct mididev mididev; /* device stuff */ struct mio_hdl *hdl; /* sndio handle */ char *path; /* eg. "/dev/rmidi3" */ }; void sndio_open(struct mididev *); unsigned sndio_read(struct mididev *, unsigned char *, unsigned); unsigned sndio_write(struct mididev *, unsigned char *, unsigned); unsigned sndio_nfds(struct mididev *); unsigned sndio_pollfd(struct mididev *, struct pollfd *, int); int sndio_revents(struct mididev *, struct pollfd *); void sndio_close(struct mididev *); void sndio_del(struct mididev *); struct devops sndio_ops = { sndio_open, sndio_read, sndio_write, sndio_nfds, sndio_pollfd, sndio_revents, sndio_close, sndio_del }; struct mididev * sndio_new(char *path, unsigned mode) { struct sndio *dev; if (path == NULL) { cons_err("path must be set for raw devices"); return NULL; } dev = xmalloc(sizeof(struct sndio), "sndio"); mididev_init(&dev->mididev, &sndio_ops, mode); dev->path = str_new(path); return (struct mididev *)&dev->mididev; } void sndio_del(struct mididev *addr) { struct sndio *dev = (struct sndio *)addr; mididev_done(&dev->mididev); str_delete(dev->path); xfree(dev); } void sndio_open(struct mididev *addr) { struct sndio *dev = (struct sndio *)addr; int mode = 0; if (dev->mididev.mode & MIDIDEV_MODE_OUT) mode |= MIO_OUT; if (dev->mididev.mode & MIDIDEV_MODE_IN) mode |= MIO_IN; dev->hdl = mio_open(dev->path, mode, 0); if (dev->hdl == NULL) { log_puts("sndio_open: "); log_puts(dev->path); log_puts(": failed to open device\n"); dev->mididev.eof = 1; return; } } void sndio_close(struct mididev *addr) { struct sndio *dev = (struct sndio *)addr; if (dev->hdl == NULL) return; mio_close(dev->hdl); dev->hdl = NULL; } unsigned sndio_read(struct mididev *addr, unsigned char *buf, unsigned count) { struct sndio *dev = (struct sndio *)addr; size_t res; res = mio_read(dev->hdl, buf, count); if (res == 0 && mio_eof(dev->hdl)) { log_puts("sndio_read: "); log_puts(dev->path); log_puts(": read failed\n"); dev->mididev.eof = 1; return 0; } return res; } unsigned sndio_write(struct mididev *addr, unsigned char *buf, unsigned count) { struct sndio *dev = (struct sndio *)addr; size_t res; res = mio_write(dev->hdl, buf, count); if (res < count) { log_puts("sndio_write: "); log_puts(dev->path); log_puts(": short write\n"); dev->mididev.eof = 1; return 0; } return res; } unsigned sndio_nfds(struct mididev *addr) { struct sndio *dev = (struct sndio *)addr; return mio_nfds(dev->hdl); } unsigned sndio_pollfd(struct mididev *addr, struct pollfd *pfd, int events) { struct sndio *dev = (struct sndio *)addr; return mio_pollfd(dev->hdl, pfd, events); } int sndio_revents(struct mididev *addr, struct pollfd *pfd) { struct sndio *dev = (struct sndio *)addr; return mio_revents(dev->hdl, pfd); } #endif midish-1.4.0/metro.c010066400017510001751000000065111501104363400127740ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "utils.h" #include "mux.h" #include "metro.h" #include "song.h" void metro_tocb(void *); /* * initialize the metronome with some defaults */ void metro_init(struct metro *o) { o->mode = 0; o->mask = (1 << SONG_REC); o->hi.cmd = EV_NON; o->hi.dev = DEFAULT_METRO_DEV; o->hi.ch = DEFAULT_METRO_CHAN; o->hi.note_num = DEFAULT_METRO_HI_NOTE; o->hi.note_vel = DEFAULT_METRO_HI_VEL; o->lo.cmd = EV_NON; o->lo.dev = DEFAULT_METRO_DEV; o->lo.ch = DEFAULT_METRO_CHAN; o->lo.note_num = DEFAULT_METRO_LO_NOTE; o->lo.note_vel = DEFAULT_METRO_LO_VEL; o->ev = NULL; timo_set(&o->to, metro_tocb, o); } /* * free the metronome */ void metro_done(struct metro *o) { /* nothing for now */ } /* * callback that sends the note-off event of the click */ void metro_tocb(void *addr) { struct metro *o = (struct metro *)addr; struct ev ev; if (o->ev == NULL) { log_puts("metro_tocb: no click sounding\n"); panic(); } ev.cmd = EV_NOFF; ev.dev = o->ev->dev; ev.ch = o->ev->ch; ev.note_num = o->ev->note_num; ev.note_vel = EV_NOFF_DEFAULTVEL; mux_putev(&ev); o->ev = NULL; } /* * if the position pointer is on the first tic * of a beat then send a "click" to the midi output * note: the output is not flushed */ void metro_tic(struct metro *o, unsigned beat, unsigned tic) { if ((o->mask & (1 << o->mode)) && tic == 0) { /* * if the last metronome click is sounding * abord the timeout and stop the click */ if (o->ev) { log_puts("metro_tic: nested clicks\n"); timo_del(&o->to); metro_tocb(o); } if (beat == 0) { o->ev = &o->hi; } else { o->ev = &o->lo; } mux_putev(o->ev); /* * schedule the corrsponding note off in 30ms */ timo_add(&o->to, DEFAULT_METRO_CLICKLEN); } } /* * send the note off event of the click (if any) */ void metro_shut(struct metro *o) { if (o->ev) { timo_del(&o->to); metro_tocb(o); } } /* * set the mode of the metronome */ void metro_setmode(struct metro *o, unsigned mode) { if ((o->mask & (1 << o->mode)) && !(o->mask & (1 << mode))) metro_shut(o); o->mode = mode; } /* * change the current mask */ void metro_setmask(struct metro *o, unsigned mask) { if ((o->mask & (1 << o->mode)) && !(mask & (1 << o->mode))) metro_shut(o); o->mask = mask; } unsigned metro_str2mask(struct metro *o, char *mstr, unsigned *rmask) { unsigned mask; if (str_eq(mstr, "on")) { mask = (1 << SONG_PLAY) + (1 << SONG_REC); } else if (str_eq(mstr, "rec")) { mask = (1 << SONG_REC); } else if (str_eq(mstr, "off")) { mask = 0; } else { return 0; } *rmask = mask; return 1; } midish-1.4.0/metro.h010066400017510001751000000027251501104363400130040ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_METRO_H #define MIDISH_METRO_H #include "ev.h" #include "timo.h" struct metro { unsigned mode; /* same as song->mode */ unsigned mask; /* enabled if (mask | mode) != 0 */ struct ev hi, lo; /* high and low click note-on events */ struct ev *ev; /* event currently sounding (or NULL) */ struct timo to; /* timeout for the noteoff */ }; void metro_init(struct metro *); void metro_done(struct metro *); void metro_tic(struct metro *, unsigned, unsigned); void metro_shut(struct metro *); void metro_setmode(struct metro *, unsigned); void metro_setmask(struct metro *, unsigned); unsigned metro_str2mask(struct metro *, char *, unsigned *); #endif /* MIDISH_METRO_H */ midish-1.4.0/mididev.c010066400017510001751000000354011501104363400132670ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * mididev is a generic midi device structure it doesn't contain any * device-specific fields and shoud be extended by other structures * * this module also, manages a global table of generic midi * devices. The table is indexed by the device "unit" number, the * same that is stored in the event structure * * this modules converts midi bytes (ie 'unsigned char') to midi events * (struct ev) and calls mux_xxx callbacks to handle midi * input. Similarly, it converts midi events to bytes and sends them on * the wire * * the module provides the following methods: * * - mididev_open() to open the device and setup the parser * * - mididev_close() to release the device and the parser * * - mididev_inputcb() routine called by the lower layer when * midi input has been read(), basically it decodes the midi byte * stream and calls mux_xxx() routines * * - mididev_put{ev,start,stop,tic,ack}() routines send respectively * a voice event, clock start, clock stop, clock tick and midi * active sense. * */ #include "utils.h" #include "defs.h" #include "mididev.h" #include "pool.h" #include "cons.h" #include "str.h" #include "ev.h" #include "sysex.h" #include "mux.h" #include "timo.h" #include "conv.h" #define MIDI_SYSEXSTART 0xf0 #define MIDI_QFRAME 0xf1 #define MIDI_SYSEXSTOP 0xf7 #define MIDI_TIC 0xf8 #define MIDI_START 0xfa #define MIDI_STOP 0xfc #define MIDI_ACK 0xfe #define MTC_FPS_24 0 #define MTC_FPS_25 1 #define MTC_FPS_30 3 #define MTC_FULL_LEN 10 /* size of MTC ``full frame'' sysex */ unsigned mididev_debug = 0; unsigned mididev_evlen[] = { 2, 2, 2, 2, 1, 1, 2, 0 }; #define MIDIDEV_EVLEN(status) (mididev_evlen[((status) >> 4) & 7]) struct mididev *mididev_list, *mididev_clksrc, *mididev_mtcsrc; struct mididev *mididev_byunit[DEFAULT_MAXNDEVS]; /* * initialize the mtc "parser" to a state, when a full message or 2 complete * frames are needed to lock to the master */ void mtc_init(struct mtc *mtc) { mtc->tps = 0; mtc->qfr = 0; mtc->pos = 0xdeadbeef; mtc->state = MTC_STOP; mtc->timo = 0; }; /* * convert MTC-style frames per second into MTC_SEC units * return 0 if not supported */ int mtc_setfps(struct mtc *mtc, unsigned id) { switch (id) { case MTC_FPS_24: mtc->tps = MTC_SEC / (24 * 4); break; case MTC_FPS_25: mtc->tps = MTC_SEC / (25 * 4); break; case MTC_FPS_30: mtc->tps = MTC_SEC / (30 * 4); break; default: if (mtc->tps != 0) { log_putu(id); log_puts(": not supported MTC frame rate\n"); } mtc->tps = 0; return 0; } return 1; } /* * called when timeout expires, ie MTC stopped */ void mtc_timo(struct mtc *mtc) { if (mididev_debug) log_puts("mtc_timo: stopped\n"); mtc->state = MTC_STOP; mux_mtcstop(); } /* * handle a quarter frame message */ void mtc_tick(struct mtc *mtc, unsigned data) { unsigned pos; int delta; if (mtc->state == MTC_STOP) return; if (data >> 4 != mtc->qfr) { if (mididev_debug) log_puts("mtc sync err\n"); return; } if (mtc->state == MTC_RUN) { mtc->pos += mtc->tps; if (mtc->pos >= MTC_PERIOD) mtc->pos -= MTC_PERIOD; mux_mtctick(mtc->tps * (24000000 / MTC_SEC)); } else { mtc->state = MTC_RUN; mux_mtctick(0); } mtc->nibble[mtc->qfr++] = data & 0xf; if (mtc->qfr < 8) return; mtc->timo = 24000000 / 4; pos = mtc->tps * 4 * (mtc->nibble[0] + (mtc->nibble[1] << 4)) + MTC_SEC * (mtc->nibble[2] + (mtc->nibble[3] << 4)) + MTC_SEC * 60 * (mtc->nibble[4] + (mtc->nibble[5] << 4)) + MTC_SEC * 3600 * (mtc->nibble[6] + ((mtc->nibble[7] & 1) << 4)); pos += 7 * mtc->tps; if (pos >= MTC_PERIOD) pos -= MTC_PERIOD; if (pos != mtc->pos) { delta = (int)pos - (int)mtc->pos; if (delta < MTC_PERIOD / 2) delta += MTC_PERIOD; if (delta >= MTC_PERIOD / 2) delta -= MTC_PERIOD; log_puts("mtc_tick: went off by "); log_puti(delta); log_puts(" ticks\n"); if (delta > 0 && delta < MTC_SEC / 6) { mux_mtctick(delta); mtc->pos = pos; } else { mtc->state = MTC_STOP; mux_mtcstop(); } } mtc->qfr = 0; } /* * handle a full frame message */ void mtc_full(struct mtc *mtc, struct sysex *x) { unsigned char *data; if (x->first == NULL || x->first->next != NULL || x->first->used != 10) return; data = x->first->data; if (data[1] != 0x7f || data[2] != 0x7f || data[3] != 0x01 || data[4] != 0x01) return; if (!mtc_setfps(mtc, data[5] >> 5)) return; mtc->qfr = 0; mtc->pos = MTC_SEC * 3600 * (data[5] & 0x1f) + MTC_SEC * 60 * data[6] + MTC_SEC * data[7] + mtc->tps * 4 * data[8]; mtc->state = MTC_START; if (mididev_debug) { log_puts("mtc_full: start at "); log_putu(mtc->pos); log_puts("\n"); } mux_mtcstart(mtc->pos); } /* * initialize the device independent part of the device structure */ void mididev_init(struct mididev *o, struct devops *ops, unsigned mode) { /* * by default we don't transmit realtime information * (midi_tic, midi_start, midi_stop etc...) */ o->ops = ops; o->sendclk = 0; o->sendmmc = 1; o->ticrate = DEFAULT_TPU; o->ticdelta = 0xdeadbeef; o->mode = mode; o->ixctlset = 0; /* all input controllers are 7bit */ o->oxctlset = 0; o->ievset = CONV_XPC | CONV_NRPN | CONV_RPN; o->oevset = CONV_XPC | CONV_NRPN | CONV_RPN; o->eof = 1; /* * reset parser */ o->oused = 0; o->istatus = o->ostatus = 0; o->isysex = NULL; o->runst = 1; o->sync = 0; } /* * release the device independent part of the device structure: for * future use */ void mididev_done(struct mididev *o) { if (mux_isopen) mididev_close(o); } /* * open the device and initialize the parser */ void mididev_open(struct mididev *o) { o->eof = 0; o->oused = 0; o->istatus = o->ostatus = 0; o->isysex = NULL; mtc_init(&o->imtc); o->ops->open(o); } /* * close the device */ void mididev_close(struct mididev *o) { mididev_flush(o); o->ops->close(o); o->eof = 1; } /* * flush the given midi device */ void mididev_flush(struct mididev *o) { unsigned count, todo; unsigned char *buf; unsigned i; if (!o->eof) { if (mididev_debug && o->oused > 0) { log_puts("mididev_flush: "); log_putu(timo_abstime / 24); log_puts(": dev "); log_putu(o->unit); log_puts(":"); for (i = 0; i < o->oused; i++) { log_puts(" "); log_putx(o->obuf[i]); } log_puts("\n"); } todo = o->oused; buf = o->obuf; while (todo > 0) { count = o->ops->write(o, buf, todo); if (o->eof) break; todo -= count; buf += count; } if (o->oused) o->osensto = MIDIDEV_OSENSTO; } o->oused = 0; } /* * mididev_inputcb is called when midi data becomes available * it calls mux_evcb */ void mididev_inputcb(struct mididev *o, unsigned char *buf, unsigned count) { struct ev ev; unsigned i, data; if (!(o->mode & MIDIDEV_MODE_IN)) { log_puts("received data from output only device\n"); return; } if (mididev_debug) { log_puts("mididev_inputcb: "); log_putu(timo_abstime / 24); log_puts(": dev "); log_putu(o->unit); log_puts(":"); for (i = 0; i < count; i++) { log_puts(" "); log_putx(buf[i]); } log_puts("\n"); } while (count != 0) { data = *buf; count--; buf++; if (data >= 0xf8) { switch(data) { case MIDI_TIC: if (o == mididev_clksrc) mux_ticcb(); break; case MIDI_START: if (o == mididev_clksrc) { o->ticdelta = o->ticrate; mux_startcb(); } break; case MIDI_STOP: if (o == mididev_clksrc) mux_stopcb(); break; case MIDI_ACK: mux_ackcb(o->unit); break; default: if (mididev_debug) { log_puts("mididev_inputcb: "); log_putx(data); log_puts(" : skipped unimplemented message\n"); } break; } } else if (data >= 0x80) { if (mididev_debug && o->istatus >= 0x80 && o->icount > 0 && o->icount < MIDIDEV_EVLEN(o->istatus)) { /* * midi spec says messages can be aborted * by status byte, so don't trigger an error */ log_puts("mididev_inputcb: "); log_putx(o->istatus); log_puts(": skipped aborted message\n"); } o->istatus = data; o->icount = 0; switch(data) { case MIDI_SYSEXSTART: if (o->isysex) { if (mididev_debug) log_puts("mididev_inputcb: previous sysex aborted\n"); sysex_del(o->isysex); } o->isysex = sysex_new(o->unit); sysex_add(o->isysex, data); break; case MIDI_SYSEXSTOP: if (o->isysex) { sysex_add(o->isysex, data); if (o == mididev_mtcsrc) mtc_full(&o->imtc, o->isysex); mux_sysexcb(o->unit, o->isysex); o->isysex = NULL; } o->istatus = 0; break; default: /* * sysex message without the stop byte * is considered as aborted. */ if (o->isysex) { if (mididev_debug) log_puts("mididev_inputcb: current sysex aborted\n"); sysex_del(o->isysex); o->isysex = NULL; } break; } } else if (o->istatus >= 0x80 && o->istatus < 0xf0) { o->idata[o->icount] = (unsigned char)data; o->icount++; if (o->icount == MIDIDEV_EVLEN(o->istatus)) { o->icount = 0; ev.cmd = o->istatus >> 4; ev.dev = o->unit; ev.ch = o->istatus & 0x0f; if (ev.cmd == EV_NON && o->idata[1] == 0) { ev.cmd = EV_NOFF; ev.note_num = o->idata[0]; ev.note_vel = EV_NOFF_DEFAULTVEL; } else if (ev.cmd == EV_BEND) { ev.bend_val = ((unsigned)o->idata[1] << 7) + o->idata[0]; } else { ev.v0 = o->idata[0]; ev.v1 = o->idata[1]; } mux_evcb(o->unit, &ev); } } else if (o->istatus == MIDI_SYSEXSTART) { sysex_add(o->isysex, data); } else if (o->istatus == MIDI_QFRAME) { /* * NOTE: MIDI uses running status only for voice events * so, if you add new system common messages * here don't forget to reset the running status */ if (o == mididev_mtcsrc) mtc_tick(&o->imtc, data); o->istatus = 0; } } } /* * write a single midi byte to the output buffer, if * it is full, flush it. Shouldn't we inline it? */ void mididev_out(struct mididev *o, unsigned data) { if (!(o->mode & MIDIDEV_MODE_OUT)) { return; } if (o->oused == MIDIDEV_BUFLEN) { mididev_flush(o); } o->obuf[o->oused] = (unsigned char)data; o->oused++; } void mididev_putstart(struct mididev *o) { mididev_out(o, MIDI_START); if (o->sync) mididev_flush(o); } void mididev_putstop(struct mididev *o) { mididev_out(o, MIDI_STOP); if (o->sync) mididev_flush(o); } void mididev_puttic(struct mididev *o) { mididev_out(o, MIDI_TIC); if (o->sync) mididev_flush(o); } void mididev_putack(struct mididev *o) { mididev_out(o, MIDI_ACK); if (o->sync) mididev_flush(o); } /* * convert a voice event to byte stream and queue * it for sending */ void mididev_putev(struct mididev *o, struct ev *ev) { unsigned char *p; unsigned s; if (EV_ISSX(ev)) { o->ostatus = 0; p = evinfo[ev->cmd].pattern; for (;;) { switch (*p) { case EV_PATV0_HI: mididev_out(o, ev->v0 >> 7); break; case EV_PATV0_LO: mididev_out(o, ev->v0 & 0x7f); break; case EV_PATV1_HI: mididev_out(o, ev->v1 >> 7); break; case EV_PATV1_LO: mididev_out(o, ev->v1 & 0x7f); break; default: mididev_out(o, *p); if (*p == 0xf7) goto end; } p++; } } if (!EV_ISVOICE(ev)) { return; } if (ev->cmd == EV_NOFF) { s = ev->ch + (EV_NON << 4); if (!o->runst || s != o->ostatus) { o->ostatus = s; mididev_out(o, s); } mididev_out(o, ev->note_num); mididev_out(o, 0); } else if (ev->cmd == EV_BEND) { s = ev->ch + (EV_BEND << 4); if (!o->runst || s != o->ostatus) { o->ostatus = s; mididev_out(o, s); } mididev_out(o, ev->bend_val & 0x7f); mididev_out(o, ev->bend_val >> 7); } else { s = ev->ch + (ev->cmd << 4); if (!o->runst || s != o->ostatus) { o->ostatus = s; mididev_out(o, s); } mididev_out(o, ev->v0); if (MIDIDEV_EVLEN(s) == 2) { mididev_out(o, ev->v1); } } end: if (o->sync) mididev_flush(o); } /* * queue raw data for sending */ void mididev_sendraw(struct mididev *o, unsigned char *buf, unsigned len) { if (!(o->mode & MIDIDEV_MODE_OUT)) { return; } while (len > 0) { if (o->oused == MIDIDEV_BUFLEN) { mididev_flush(o); } o->obuf[o->oused] = *buf; o->oused++; len--; buf++; } /* * since we don't parse the buffer, reset running status */ o->ostatus = 0; if (o->sync) mididev_flush(o); } /* * initialize the device table */ void mididev_listinit(void) { unsigned i; for (i = 0; i < DEFAULT_MAXNDEVS; i++) { mididev_byunit[i] = NULL; } mididev_list = NULL; mididev_mtcsrc = NULL; /* no external timer, use internal timer */ mididev_clksrc = NULL; /* no clock source, use internal clock */ } /* * unregister all entries of the device table */ void mididev_listdone(void) { unsigned i; struct mididev *dev; for (i = 0; i < DEFAULT_MAXNDEVS; i++) { dev = mididev_byunit[i]; if (dev != NULL) { dev->ops->del(dev); mididev_byunit[i] = NULL; } } mididev_clksrc = NULL; mididev_list = NULL; } /* * register a new device number (ie "unit") */ unsigned mididev_attach(unsigned unit, char *path, unsigned mode) { struct mididev *dev; if (unit >= DEFAULT_MAXNDEVS) { cons_err("given unit is too large"); return 0; } if (mididev_byunit[unit] != NULL) { cons_err("device already exists"); return 0; } #if defined(USE_SNDIO) dev = sndio_new(path, mode); #elif defined(USE_ALSA) dev = alsa_new(path, mode); #else dev = raw_new(path, mode); #endif if (dev == NULL) return 0; dev->next = mididev_list; mididev_list = dev; mididev_byunit[unit] = dev; dev->unit = unit; return 1; } /* * unregister the given device number */ unsigned mididev_detach(unsigned unit) { struct mididev **i, *dev; if (unit >= DEFAULT_MAXNDEVS || mididev_byunit[unit] == NULL) { cons_err("no such device"); return 0; } if (mididev_byunit[unit] == mididev_clksrc || mididev_byunit[unit] == mididev_mtcsrc) { cons_err("cant detach master device"); return 0; } for (i = &mididev_list; *i != NULL; i = &(*i)->next) { dev = *i; if (dev->unit == unit) { *i = dev->next; dev->ops->del(dev); mididev_byunit[unit] = NULL; return 1; } } log_puts("mididev_detach: the device is not in the list\n"); panic(); return 0; } midish-1.4.0/mididev.h010066400017510001751000000120011501104363400132630ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_MIDIDEV_H #define MIDISH_MIDIDEV_H /* * timeouts for active sensing * (as usual units are 24th of microsecond) */ #define MIDIDEV_OSENSTO (250 * 24 * 1000) #define MIDIDEV_ISENSTO (350 * 24 * 1000) /* * modes for devices */ #define MIDIDEV_MODE_IN 1 /* can input */ #define MIDIDEV_MODE_OUT 2 /* can output */ /* * device output buffer length in bytes */ #define MIDIDEV_BUFLEN 0x400 struct pollfd; struct mididev; struct ev; struct devops { /* * open the device or set the ``eof'' flag on error */ void (*open)(struct mididev *); /* * try to read the given number of bytes, and return the number * of bytes actually read, set the ``eof'' flag on error */ unsigned (*read)(struct mididev *, unsigned char *, unsigned); /* * try to write the given number of bytes, and return the number * of bytes actually written, set the ``eof'' flag on error */ unsigned (*write)(struct mididev *, unsigned char *, unsigned); /* * return the number of pollfd structures the device requires */ unsigned (*nfds)(struct mididev *); /* * fill the given array of pollfd structures with the given * events so that poll(2) can be called, return the number of * elements filled */ unsigned (*pollfd)(struct mididev *, struct pollfd *, int); /* * return the events set in the array of pollfd structures set * by the poll(2) syscall */ int (*revents)(struct mididev *, struct pollfd *); /* * close the device */ void (*close)(struct mididev *); /* * free the mididev structure and associated resources */ void (*del)(struct mididev *); }; /* * private structure for the MTC messages parser */ struct mtc { unsigned char nibble[8]; /* nibbles of hr:min:sec:fr */ unsigned qfr; /* quarter frame counter */ unsigned tps; /* ticks per second */ unsigned pos; /* absolute tick */ #define MTC_STOP 0 /* stopped */ #define MTC_START 1 /* got a full frame but no tick yet */ #define MTC_RUN 2 /* got at least 1 tick */ unsigned state; /* one of above */ unsigned timo; }; struct mididev { struct devops *ops; /* * device list and iteration stuff */ struct pollfd *pfd; struct mididev *next; /* * device settings */ unsigned unit; /* index in the mididev table */ unsigned ticrate, ticdelta; /* tick rate (default 96) */ unsigned sendclk; /* send MIDI clock */ unsigned sendmmc; /* send MMC start/stop/relocate */ unsigned isensto, osensto; /* active sensing timeouts */ unsigned mode; /* read, write */ unsigned ixctlset, oxctlset; /* bitmap of 14bit controllers */ unsigned ievset, oevset; /* bitmap of CONV_{XPC,NRPN,RPN} */ unsigned eof; /* i/o error pending */ unsigned runst; /* use running status for output */ unsigned sync; /* flush buffer after each message */ /* * midi events parser state */ unsigned istatus; /* input running status */ unsigned icount; /* bytes in idata[] */ unsigned char idata[2]; /* current event's data */ struct sysex *isysex; /* input sysex */ struct mtc imtc; /* MTC parser */ unsigned oused; /* bytes in obuf */ unsigned ostatus; /* output running status */ unsigned char obuf[MIDIDEV_BUFLEN]; /* output buffer */ }; void mididev_init(struct mididev *, struct devops *, unsigned); void mididev_done(struct mididev *); void mididev_flush(struct mididev *); void mididev_putstart(struct mididev *); void mididev_putstop(struct mididev *); void mididev_puttic(struct mididev *); void mididev_putack(struct mididev *); void mididev_putev(struct mididev *, struct ev *); void mididev_sendraw(struct mididev *, unsigned char *, unsigned); void mididev_open(struct mididev *); void mididev_close(struct mididev *); void mididev_inputcb(struct mididev *, unsigned char *, unsigned); void mtc_timo(struct mtc *); /* XXX, use timeouts */ extern unsigned mididev_debug; extern struct mididev *mididev_list; extern struct mididev *mididev_clksrc; extern struct mididev *mididev_mtcsrc; extern struct mididev *mididev_byunit[]; struct mididev *raw_new(char *, unsigned); struct mididev *alsa_new(char *, unsigned); struct mididev *sndio_new(char *, unsigned); void mididev_listinit(void); void mididev_listdone(void); unsigned mididev_attach(unsigned, char *, unsigned); unsigned mididev_detach(unsigned); #endif /* MIDISH_MIDIDEV_H */ midish-1.4.0/midish.1010066400017510001751000000044251501104363400130430ustar00alexalex.\" .\" Copyright (c) 2003-2010 Alexandre Ratchov .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd June 25, 2006 .Dt MIDISH 1 .Os .Sh NAME .Nm midish .Nd MIDI sequencer and filter .Sh SYNOPSIS .Nm midish .Op Fl bhv .Sh DESCRIPTION Midish is a MIDI sequencer/filter implemented as an interactive command-line interpreter. It supports: .Pp .Bl -bullet -offset indent -compact .It Multiple MIDI devices handling .It Synchronisation to external MIDI devices .It Filtering/routing (controller mapping, keyboard splitting, ...) .It Track recording, editing, quantisation .It Import and export of standard MIDI files .It Tempo and time-signature changes, user configurable metronome .It System exclusive messages handling .El .Pp The options are as follows: .Bl -tag -width Ds .It Fl b Do not process .Pa "$HOME/.midishrc" or .Pa "/etc/midishrc" and stop on the first error on the standard input. Useful for scripting. .It Fl h Print usage information. .It Fl v Print additional info before each line of input, useful to front-ends and for debugging. .El .Pp Once midish started, the interpreter processes the .Pa "$HOME/.midishrc" file (or .Pa "/etc/midishrc" if the latter doesn't exist) and starts prompting for commands. For further information about the syntax of .Nm refer to the .Dq Midish user's manual . .Sh FILES .Bl -tag -width "$HOME/.midishrc" -compact .It Pa "$HOME/.midishrc" startup script .It Pa "/etc/midishrc" startup script (if .Pa "$HOME/.midishrc" doesn't exist) .El .Sh SEE ALSO .Xr midiplay 1 , .Xr smfplay 1 , .Xr midi 4 .Pp .Lk http://www.midish.org/manual.html User's manual and tutorial midish-1.4.0/midishrc010066400017510001751000000042361501104363400132310ustar00alexalex# # midi devices config # #dnew 0 "/dev/rmidi4" rw #dnew 1 "/dev/rmidi3" rw # # # # function to make the current filter drop controller number ``ictl'' # on the current input # proc ctldrop ctlno { for i in [ilist] { fmap {ctl $i $ctlno} {none} } } # # function to make the current filter route controller number ``ictl'' # on the current input to controller ``octl'' on the current output # proc ctlmap ic oc { for i in [ilist] { fmap {ctl $i $ic} {ctl [getc] $oc} } } # # unmute all tracks # proc nomute { for i in [tlist] { unmute $i } } # # mute all tracks but current # proc solo { for i in [tlist] { mute $i } unmute [gett] } # # function to add a sysex that turns on general midi # proc gmon devnum { xnew gmon xadd $devnum { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 } } # # function to set program number of the current output to th given # general midi program number # proc gmp patch { oaddev { pc [geto] ($patch - 1) } } # # set volume (controller number 7) of the current output # proc vol val { oaddev {ctl [geto] 7 $val} } # # set pan (controller number 10) of the current output # proc pan val { oaddev {ctl [geto] 10 $val} } # # set reverb (controller number 91) of the current output # proc reverb val { oaddev {ctl [geto] 91 $val}; } # # set chorus (controller number 93) of the current output # proc chorus val { oaddev {ctl [geto] 93 $val}; } # # set RPN to the given value for the current output # proc rpn addr val { oaddev {rpn [geto] $addr $val} } # # set NRPN to the given value for the current output # proc nrpn addr val { oaddev {nrpn [geto] $addr $val} } # # XV-2020 specific macros # # configures a instrument with the given bank/patch or bank rhythm # bank 0,1,2,3 corresponds to preset A, B, C and D # patches/rhythms are counted from 0 to 127 # proc xvp bank patch { oaddev { xpc [geto] ($patch) (11200 + $bank) } } proc xvr bank patch { oaddev { xpc [geto] ($patch) (11072 + $bank) } } # # generate a sysex message that set parameter on # address (a0,a1,a2,a3) to val # proc xvparam a0 a1 a2 a3 val { return { \ 0xf0 0x41 0x7f 0x00 0x10 0x12 $a0 $a1 $a2 $a3 $val \ 128 - ($a0 + $a1 + $a2 + $a3 + $val) % 128 \ 0xf7 } } midish-1.4.0/mixout.c010066400017510001751000000075731501104363400132040ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * mixout.c * * mixes multiple sources and send the result tout the midi output, a * state list is maintained to avoid conflict. * * each source has it's ID, if there are conflicting events from * different sources (differents IDs), then the lower ID may cancel * the higher ID source. * * For continuous controllers terminated states are kept for around 1 * second, this will block events from other lower prority * sources. For instance, this can be used to adjust a controller in * realtime while a track using the same controller is playing (input * ID is zero, and has precedence over tracks). * */ #include "utils.h" #include "ev.h" #include "filt.h" #include "pool.h" #include "mux.h" #include "timo.h" #include "state.h" #define MIXOUT_TIMO (1000000UL) #define MIXOUT_MAXTICS 24 void mixout_timocb(void *); struct statelist mixout_slist; struct timo mixout_timo; unsigned mixout_debug = 0; void mixout_start(void) { statelist_init(&mixout_slist); timo_set(&mixout_timo, mixout_timocb, NULL); timo_add(&mixout_timo, MIXOUT_TIMO); if (mixout_debug) { log_puts("mixout_start()\n"); } } void mixout_stop(void) { if (mixout_debug) { log_puts("mixout_stop()\n"); } timo_del(&mixout_timo); statelist_done(&mixout_slist); } void mixout_putev(struct ev *ev, unsigned id) { struct state *os; struct ev ca; if (mixout_debug >= 3) { log_puts("mixout_putev: "); ev_log(ev); log_puts(" ("); log_putu(id); log_puts(")\n"); } os = statelist_lookup(&mixout_slist, ev); if (os != NULL && os->tag != id) { if (os->tag < id) { if (mixout_debug) { log_puts("mixout_putev: "); ev_log(ev); log_puts(" ("); log_putu(id); log_puts(": ignored after "); ev_log(&os->ev); log_puts(" ("); log_putu(os->tag); log_puts(")\n"); } return; } if (state_cancel(os, &ca)) { if (mixout_debug) { log_puts("mixout_putev: "); ev_log(ev); log_puts(" ("); log_putu(id); log_puts(": will kick older: "); ev_log(&os->ev); log_puts(" ("); log_putu(os->tag); log_puts(")\n"); } statelist_update(&mixout_slist, &ca); mux_putev(&ca); } if (mixout_debug) { log_puts("mixout_putev: "); ev_log(ev); log_puts(" won\n"); } } os = statelist_update(&mixout_slist, ev); os->tag = id; os->tic = 0; if ((os->flags & (STATE_BOGUS | STATE_NESTED)) == 0) mux_putev(ev); else { if (mixout_debug) { log_puts("mixout_putev: "); ev_log(ev); log_puts(" nested or bogus\n"); } } } void mixout_timocb(void *addr) { struct state *i, *inext; for (i = mixout_slist.first; i != NULL; i = inext) { inext = i->next; /* * purge states that are no more used */ if (i->phase == EV_PHASE_LAST) { statelist_rm(&mixout_slist, i); state_del(i); } else if (i->phase == (EV_PHASE_FIRST | EV_PHASE_LAST)) { if (i->tic >= MIXOUT_MAXTICS) { if (mixout_debug >= 2) { log_puts("mixout_timo: "); state_log(i); log_puts(": timed out\n"); } statelist_rm(&mixout_slist, i); state_del(i); } else { i->flags &= ~STATE_CHANGED; i->tic++; } } } timo_add(&mixout_timo, MIXOUT_TIMO); } midish-1.4.0/mixout.h010066400017510001751000000020141501104363400131720ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_MIXOUT_H #define MIDISH_MIXOUT_H #include "state.h" #include "timo.h" void mixout_start(void); void mixout_stop(void); void mixout_putev(struct ev *, unsigned); extern unsigned mixout_debug; #endif /* MIDISH_FILT_H */ midish-1.4.0/mux.c010066400017510001751000000377271501104363400124740ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * the multiplexer handles : * - midi input and output * - internal external/timer * * the clock unit is the 24th of microsecond (thus the tempo is stored * with the same accuracy as in standard midi files). * * the timer has the following states: * STOP -> STARTWAIT -> START -> FIRST_TIC -> NEXT_TIC -> STOP * * STARTWAIT: * * we're waiting (forever) for a "start" MIDI event; once it is * received, we switch to the next state (START state). If the * internal clock source is used, then we switch immediately to * next state. * * START: * * we just received the "start" MIDI event, so we wait for the * first "tick" MIDI event; once it's received we switch to the * next state (FIRST_TIC). If the internal clock source is used * we wait MUX_START_DELAY (0.1 second) and we switch to the next * state. * * FIRST_TIC: * * we received the first "tick" event after a "start" event, * the music starts now, so call appropriate call-backs to do so * and wait for the next "tick" event. * * NEXT_TIC: * * we received another "tick" event, move the music one * step forward. * * STOP: * * nothing to do, ignore any MIDI sync events. * */ #include "utils.h" #include "ev.h" #include "cons.h" #include "defs.h" #include "mux.h" #include "mididev.h" #include "sysex.h" #include "timo.h" #include "state.h" #include "conv.h" #include "norm.h" #include "mixout.h" /* * MUX_START_DELAY: * * delay between the START event and the first TIC in 24ths of a micro * second, here we use 1 tic at 30bpm */ #define MUX_START_DELAY (24000000UL / 3) unsigned mux_isopen = 0; unsigned mux_debug = 0; unsigned mux_ticrate; unsigned long mux_ticlength, mux_curpos, mux_nextpos; unsigned mux_curtic; unsigned mux_phase, mux_reqphase; unsigned mux_manualstart = 1; void *mux_addr; unsigned long mux_wallclock; struct statelist mux_istate, mux_ostate; /* * the following are defined in mdep.c */ void mux_mdep_open(void); void mux_mdep_close(void); void mux_sendstop(void); void mux_logphase(unsigned phase); void mux_chgphase(unsigned phase); /* * initialize all structures and open all midi devices */ void mux_open(void) { struct mididev *i; timo_init(); statelist_init(&mux_istate); statelist_init(&mux_ostate); mixout_start(); norm_start(); /* * default tempo is 120 beats per minutes with 24 tics per * beat (time unit = 24th of microsecond) */ mux_ticlength = TEMPO_TO_USEC24(DEFAULT_TEMPO, DEFAULT_TPB); /* * default tics per second = 96 */ mux_ticrate = DEFAULT_TPU; /* * reset tic counters of devices */ mux_isopen = 1; for (i = mididev_list; i != NULL; i = i->next) { i->ticdelta = i->ticrate; i->isensto = 0; i->osensto = MIDIDEV_OSENSTO; mididev_open(i); } mux_mdep_open(); mux_curpos = 0; mux_nextpos = 0; mux_reqphase = MUX_STOP; mux_phase = MUX_STOP; mux_wallclock = 0; log_sync = 1; } /* * release all structures and close midi devices */ void mux_close(void) { struct mididev *i; log_sync = 1; norm_stop(); mixout_stop(); mux_flush(); for (i = mididev_list; i != NULL; i = i->next) { if (i->isysex) { cons_err("lost incomplete sysex"); sysex_del(i->isysex); } mididev_close(i); } mux_mdep_close(); mux_isopen = 0; statelist_done(&mux_ostate); statelist_done(&mux_istate); timo_done(); } #ifdef MUX_DEBUG void mux_logphase(unsigned phase) { switch(phase) { case MUX_STARTWAIT: log_puts("STARTWAIT"); break; case MUX_START: log_puts("START"); break; case MUX_FIRST: log_puts("FIRST"); break; case MUX_NEXT: log_puts("NEXT"); break; case MUX_STOP: log_puts("STOP"); break; default: log_puts("unknown"); break; } } #endif /* * change the current phase */ void mux_chgphase(unsigned phase) { #ifdef MUX_DEBUG log_puts("mux_phase: "); mux_logphase(mux_phase); log_puts(" -> "); mux_logphase(phase); log_puts("\n"); #endif mux_phase = phase; } /* * send a TIC to all devices that transmit real-time events. The tic * is only sent if the device tic_per_unit permits it. */ void mux_sendtic(void) { struct mididev *i; for (i = mididev_list; i != NULL; i = i->next) { if (i->sendclk && i != mididev_clksrc) { while (i->ticdelta >= mux_ticrate) { mididev_puttic(i); i->ticdelta -= mux_ticrate; } i->ticdelta += i->ticrate; } } } /* * similar to sendtic, but sends a START event */ void mux_sendstart(void) { struct mididev *i; for (i = mididev_list; i != NULL; i = i->next) { if (i->sendclk && i != mididev_clksrc) { i->ticdelta = i->ticrate; /* * send a spurious tick just before the start * event in order to notify that we are the * master */ mididev_puttic(i); mididev_putstart(i); } } } /* * similar to sendtic, but send a STOP event */ void mux_sendstop(void) { struct mididev *i; for (i = mididev_list; i != NULL; i = i->next) { if (i->sendclk && i != mididev_clksrc) { mididev_putstop(i); } } } /* * send the given voice event to the appropriate device, no * other routines should be used to send events */ void mux_putev(struct ev *ev) { unsigned unit; struct mididev *dev; struct ev rev[CONV_NUMREV]; unsigned i, nev; #ifdef MUX_DEBUG if (mux_debug) { log_puts("mux_putev: "); ev_log(ev); log_puts("\n"); } #endif if (!EV_ISVOICE(ev) && !EV_ISSX(ev)) { log_puts("mux_putev: "); ev_log(ev); log_puts(": only voice events allowed\n"); panic(); } unit = ev->dev; if (unit >= DEFAULT_MAXNDEVS) { log_puts("mux_putev: "); ev_log(ev); log_puts(": bogus unit number\n"); panic(); } dev = mididev_byunit[unit]; if (dev != NULL) { nev = conv_unpackev(&mux_ostate, dev->oxctlset, dev->oevset, ev, rev); for (i = 0; i < nev; i++) { mididev_putev(dev, &rev[i]); } } } /* * send bytes to the given device, typically used to send * sysex messages */ void mux_sendraw(unsigned unit, unsigned char *buf, unsigned len) { struct mididev *dev; if (unit >= DEFAULT_MAXNDEVS) { return; } if (len == 0) { return; } dev = mididev_byunit[unit]; if (dev == NULL) { return; } mididev_sendraw(dev, buf, len); } /* * called when MTC timer starts (full frame message). */ void mux_mtcstart(unsigned mtcpos) { /* * if already started, trigger a MTC stop to enter * a state in which we can start */ if (mux_phase >= MUX_START && mux_phase <= MUX_NEXT) { if (mux_debug) log_puts("mux_mtcstart: triggered stop\n"); mux_mtcstop(); } /* * check if we're trying to start, if not just return */ if (mux_phase == MUX_STOP) { if (mux_debug) log_puts("mux_mtcstart: ignored mtc start (stopped)\n"); return; } /* * ignore position change if we're not using MTC because * it's already set (e.g., internally generated MTC start) */ if (mididev_mtcsrc) { mux_curpos = song_gotocb(usong, LOC_MTC, mtcpos); mux_nextpos = mux_ticlength; if (mux_curpos >= mux_nextpos) { log_puts("mux_mtcstart: offset larger than 1 tick\n"); panic(); } } /* * generate clock start */ if (mux_debug) log_puts("mux_mtcstart: generated clk start\n"); mux_startcb(); } /* * called periodically by the MTC timer */ void mux_mtctick(unsigned delta) { mux_curpos += delta; while (mux_curpos >= mux_nextpos) { mux_curpos -= mux_nextpos; mux_nextpos = mux_ticlength; /* * if in manual mode, dont trigger the 0-th tick (ie * the start signal). */ if (!mux_manualstart || mux_phase != MUX_START) mux_ticcb(); } } /* * called when the MTC timer stops */ void mux_mtcstop(void) { /* * if using external clock, ignore MTC */ if (mididev_clksrc) return; if (mux_phase >= MUX_START) { if (mux_debug) log_puts("mux_mtcstop: generated stop\n"); mux_stopcb(); } } /* * call-back called every time the clock changes, the argument * contains the number of 24th of seconds elapsed since the last call */ void mux_timercb(unsigned long delta) { struct mididev *dev; /* * update wall clock */ mux_wallclock += delta; /* * run expired timeouts */ timo_update(delta); /* * handle timeouts not using the timo.c interface * XXX: convert this to timo_xxx() routines */ for (dev = mididev_list; dev != NULL; dev = dev->next) { if (dev->isensto) { if (dev->isensto <= delta) { dev->isensto = 0; cons_erru(dev->unit, "sensing timeout, disabled"); } else { dev->isensto -= delta; } } if (dev->osensto) { if (dev->osensto <= delta) { mididev_putack(dev); mididev_flush(dev); dev->osensto = MIDIDEV_OSENSTO; } else { dev->osensto -= delta; } } if (dev->imtc.timo) { if (dev->imtc.timo <= delta) { dev->imtc.timo = 0; mtc_timo(&dev->imtc); } else { dev->imtc.timo -= delta; } } } /* * if there's no ext MTC source, then generate one internally * using the current sequencer state as hints */ if (!mididev_mtcsrc && !mididev_clksrc) { switch (mux_phase) { case MUX_STARTWAIT: if (!mux_manualstart) { log_puts("mux_timercb: startwait: bad state\n"); panic(); } break; case MUX_START: mux_curpos += delta; if (mux_curpos >= mux_nextpos) { mux_curpos = 0; mux_nextpos = 0; mux_mtctick(0); } break; case MUX_FIRST: case MUX_NEXT: mux_mtctick(delta); break; } } } /* * called when a MIDI TICK is received */ void mux_ticcb(void) { for (;;) { if (mididev_clksrc != NULL && mididev_clksrc->ticdelta < mididev_clksrc->ticrate) { mididev_clksrc->ticdelta += mux_ticrate; break; } if (mux_phase == MUX_FIRST) { mux_chgphase(MUX_NEXT); } else if (mux_phase == MUX_START) { mux_curpos = 0; mux_nextpos = mux_ticlength; mux_chgphase(MUX_FIRST); } if (mux_phase == MUX_NEXT) { mux_curtic++; mux_sendtic(); song_movecb(usong); } else if (mux_phase == MUX_FIRST) { mux_curtic = 0; mux_sendtic(); song_startcb(usong); } if (mididev_clksrc == NULL) break; mididev_clksrc->ticdelta -= mididev_clksrc->ticrate; } } /* * called when a MIDI START event is received from an external device */ void mux_startcb(void) { if (mux_debug) log_puts("mux_startcb: got start event\n"); if (mux_phase != MUX_STARTWAIT) { log_puts("mux_startcb: ignored MIDI start (not ready)\n"); return; } /* * if the MIDI START event comes from a device * move to the beginning (we don't support SPP yet) */ if (mididev_clksrc) { mux_curpos = 0; mux_nextpos = mux_ticlength; song_gotocb(usong, LOC_MTC, 0); } mux_chgphase(MUX_START); mux_sendstart(); mux_flush(); } /* * called when a MIDI STOP event is received from an external device */ void mux_stopcb(void) { if (mux_debug) log_puts("mux_stopcb: got stop\n"); if (mux_phase >= MUX_START && mux_phase <= MUX_NEXT) mux_sendstop(); mux_chgphase(mux_reqphase); song_stopcb(usong); mux_flush(); } /* * called when a MIDI Active-sensing is received from an external device */ void mux_ackcb(unsigned unit) { struct mididev *dev = mididev_byunit[unit]; if (dev->isensto == 0) { cons_erru(dev->unit, "sensing enabled"); dev->isensto = MIDIDEV_ISENSTO; } } /* * called when a MIDI voice event is received from an external device */ void mux_evcb(unsigned unit, struct ev *ev) { struct ev rev; struct mididev *dev = mididev_byunit[ev->dev]; #ifdef MUX_DEBUG if (mux_debug) { log_puts("mux_evcb: "); ev_log(ev); log_puts("\n"); } #endif if (conv_packev(&mux_istate, dev->ixctlset, dev->ievset, ev, &rev)) { norm_evcb(&rev); } } /* * called if an error is detected. currently we send an all note off * and all ctls reset */ void mux_errorcb(unsigned unit) { /* * XXX: should stop only failed unit, not all devices */ norm_shut(); mux_flush(); } /* * called when an sysex has been received from an external device */ void mux_sysexcb(unsigned unit, struct sysex *sysex) { unsigned char *p, *q, *data; struct ev ev; unsigned cmd; if (sysex->first != NULL && sysex->first->next == NULL) { data = sysex->first->data; /* * discard real-time messages, that should not be * recorded */ if (sysex->first->used >= 6 && data[0] == 0xf0 && data[1] == 0x7f && data[3] == 1) { sysex_del(sysex); return; } /* * handle custom events */ for (cmd = EV_PAT0; cmd < EV_PAT0 + EV_NPAT; cmd++) { if (evinfo[cmd].ev == NULL) continue; ev.v0 = ev.v1 = 0; p = evinfo[cmd].pattern; q = data; for (;; p++, q++) { switch (*p) { case EV_PATV0_HI: ev.v0 |= *q << 7; continue; case EV_PATV0_LO: ev.v0 |= *q; continue; case EV_PATV1_HI: ev.v1 |= *q << 7; continue; case EV_PATV1_LO: ev.v1 |= *q; continue; } if (*p != *q) break; if (*p == 0xf7) { ev.cmd = cmd; ev.dev = unit; norm_evcb(&ev); sysex_del(sysex); return; } } } } song_sysexcb(usong, sysex); } /* * flush all devices */ void mux_flush(void) { struct mididev *dev; for (dev = mididev_list; dev != NULL; dev = dev->next) { mididev_flush(dev); } } /* * return the current phase */ unsigned mux_getphase(void) { return mux_phase; } /* * change the tempo, the argument is tic length in 24th of * microseconds */ void mux_chgtempo(unsigned long ticlength) { if (mux_phase == MUX_FIRST || mux_phase == MUX_NEXT) { mux_nextpos += ticlength; mux_nextpos -= mux_ticlength; } mux_ticlength = ticlength; } /* * change the number of ticks per unit note; that's used to know for * instance that 1 of "our"ticks equals 2 ticks on that device... */ void mux_chgticrate(unsigned tpu) { mux_ticrate = tpu; } /* * start waiting for a MIDI START event (or generate one if * we're the clock master). */ void mux_startreq(int manualstart) { struct mididev *dev; static unsigned char mmc_start[] = { 0xf0, 0x7f, 0x7f, 0x06, 0x02, 0xf7 }; mux_manualstart = manualstart; mux_reqphase = MUX_STARTWAIT; if (mux_phase != MUX_STOP) { log_puts("bad state to call mux_startreq()\n"); panic(); } mux_chgphase(MUX_STARTWAIT); if (!mididev_clksrc && !mididev_mtcsrc) { if (mux_debug) log_puts("mux_startreq: generated mtc start\n"); mux_curpos = 0; mux_nextpos = MUX_START_DELAY; mux_mtcstart(0xdeadbeef); } else { mux_curpos = 0; mux_nextpos = mux_ticlength; } for (dev = mididev_list; dev != NULL; dev = dev->next) { if (dev->sendmmc) mididev_sendraw(dev, mmc_start, sizeof(mmc_start)); } } /* * stop the MIDI clock */ void mux_stopreq(void) { struct mididev *dev; static unsigned char mmc_stop[] = { 0xf0, 0x7f, 0x7f, 0x06, 0x01, 0xf7 }; mux_reqphase = MUX_STOP; if (mux_phase < MUX_STOP) mux_stopcb(); for (dev = mididev_list; dev != NULL; dev = dev->next) { if (dev->sendmmc) mididev_sendraw(dev, mmc_stop, sizeof(mmc_stop)); } } /* * relocate MIDI clock to given position */ void mux_gotoreq(unsigned mmcpos) { #if DEFAULT_FPS == 25 #define FPS_ID (1 << 5) #endif struct mididev *dev; unsigned char mmc_reloc[13]; mmc_reloc[0] = 0xf0; mmc_reloc[1] = 0x7f; mmc_reloc[2] = 0x7f; mmc_reloc[3] = 0x06; mmc_reloc[4] = 0x44; mmc_reloc[5] = 0x06; mmc_reloc[6] = 0x01; mmc_reloc[7] = (mmcpos / (3600 * MTC_SEC)) % 24 | FPS_ID; mmc_reloc[8] = (mmcpos / (60 * MTC_SEC)) % 60; mmc_reloc[9] = (mmcpos / MTC_SEC) % 60; mmc_reloc[10] = (mmcpos / (MTC_SEC / DEFAULT_FPS)) % DEFAULT_FPS; mmc_reloc[11] = 0; mmc_reloc[12] = 0xf7; for (dev = mididev_list; dev != NULL; dev = dev->next) { if (dev->sendmmc) mididev_sendraw(dev, mmc_reloc, sizeof(mmc_reloc)); } } midish-1.4.0/mux.h010066400017510001751000000060041501104363400124610ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_MUX_H #define MIDISH_MUX_H #define MUX_STARTWAIT 0 /* waiting for a start event */ #define MUX_START 1 /* just got a start */ #define MUX_FIRST 2 /* just got the first tic */ #define MUX_NEXT 3 /* just got the next tic */ #define MUX_STOP 4 /* nothing to do */ #define MUX_LINESIZE 1024 struct ev; struct sysex; /* * modules are chained as follows: mux -> norm -> filt -> song -> output * each module calls call-backs of the next module of the chain. In * theory we should use function pointers to "connect" modules to * each other... * * But since connection between various modules are hardcoded and not * user configurable, we don't use function pointers and other * over-engineered stuff. We just call the following call-backs. We * save 3 layers of indirection */ struct song; extern struct song *usong; extern unsigned mux_isopen; extern unsigned mux_manualstart; extern unsigned long mux_wallclock; void song_startcb(struct song *); void song_stopcb(struct song *); void song_movecb(struct song *); void song_evcb(struct song *, struct ev *); void song_sysexcb(struct song *, struct sysex *); unsigned song_gotocb(struct song *, int, unsigned); struct norm; void norm_evcb(struct ev *); struct filt; void filt_evcb(struct filt *, struct ev *); /* * public functions usable in the rest of the code to send/receive * events and to manipulate the clock */ void mux_open(void); void mux_close(void); void mux_run(void); void mux_sleep(unsigned); void mux_flush(void); void mux_shut(void); void mux_putev(struct ev *); void mux_sendraw(unsigned, unsigned char *, unsigned); unsigned mux_getphase(void); struct sysex *mux_getsysex(void); void mux_chgtempo(unsigned long); void mux_chgticrate(unsigned); void mux_startreq(int); void mux_stopreq(void); void mux_gotoreq(unsigned); int mux_mdep_wait(int); /* XXX: hide this prototype */ /* * call-backs called by midi device drivers */ void mux_timercb(unsigned long); void mux_startcb(void); void mux_stopcb(void); void mux_ticcb(void); void mux_ackcb(unsigned); void mux_evcb(unsigned, struct ev *); void mux_sysexcb(unsigned, struct sysex *); void mux_errorcb(unsigned); void mux_mtcstart(unsigned); void mux_mtctick(unsigned); void mux_mtcstop(void); #endif /* MIDISH_MUX_H */ midish-1.4.0/name.c010066400017510001751000000054501501104363400125670ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * name is a singly-linked list of strings */ #include "utils.h" #include "name.h" void name_init(struct name *o, char *name) { o->str = str_new(name); } void name_done(struct name *o) { str_delete(o->str); } struct name * name_new(char *name) { struct name *o; o = xmalloc(sizeof(struct name), "name"); name_init(o, name); return o; } struct name * name_newarg(char *name, struct name *next) { struct name *o; o = name_new(name); o->next = next; return o; } void name_delete(struct name *o) { name_done(o); xfree(o); } void name_log(struct name *o) { for (; o != NULL; o = o->next) { str_log(o->str); if (o->next) { log_puts("."); } } } void name_insert(struct name **first, struct name *i) { i->next = *first; *first = i; } void name_add(struct name **first, struct name *v) { struct name **i; i = first; while (*i != NULL) { i = &(*i)->next; } v->next = NULL; *i = v; } void name_remove(struct name **first, struct name *v) { struct name **i; i = first; while (*i != NULL) { if (*i == v) { *i = v->next; v->next = NULL; return; } i = &(*i)->next; } log_puts("name_remove: not found\n"); panic(); } void name_empty(struct name **first) { struct name *i, *inext; for (i = *first; i != NULL; i = inext) { inext = i->next; name_delete(i); } *first = NULL; } void name_cat(struct name **dst, struct name **src) { while (*dst != NULL) { dst = &(*dst)->next; } while (*src != NULL) { *dst = name_new((*src)->str); dst = &(*dst)->next; src = &(*src)->next; } } unsigned name_eq(struct name **first1, struct name **first2) { struct name *n1 = *first1, *n2 = *first2; for (;;) { if (n1 == NULL && n2 == NULL) { return 1; } else if (n1 == NULL || n2 == NULL || !str_eq(n1->str, n2->str)) { return 0; } n1 = n1->next; n2 = n2->next; } } struct name * name_lookup(struct name **first, char *str) { struct name *i; for (i = *first; i != NULL; i = i->next) { if (i->str == NULL) continue; if (str_eq(i->str, str)) return i; } return 0; } midish-1.4.0/name.h010066400017510001751000000032351501104363400125730ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_NAME_H #define MIDISH_NAME_H #include "str.h" /* * a name is an entry in a simple list of strings the string buffer is * owned by the name, so it need not to be allocated if name_xxx * routines are used */ struct name { char *str; struct name *next; }; void name_init(struct name *, char *); void name_done(struct name *); struct name *name_new(char *); struct name *name_newarg(char *, struct name *); void name_log(struct name *); void name_delete(struct name *); void name_insert(struct name **, struct name *); void name_add(struct name **, struct name *); void name_remove(struct name **, struct name *); void name_empty(struct name **); void name_cat(struct name **, struct name **); unsigned name_eq(struct name **, struct name **); struct name *name_lookup(struct name **, char *); #endif /* MIDISH_NAME_H */ midish-1.4.0/node.c010066400017510001751000000363741501104363400126050ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * this module implements the tree containing interpreter code. Each * node of the tree represents one instruction. */ #include "utils.h" #include "str.h" #include "data.h" #include "node.h" #include "exec.h" #include "cons.h" #include "user.h" #include "textio.h" struct node * node_new(struct node_vmt *vmt, struct data *data) { struct node *o; o = xmalloc(sizeof(struct node), "node"); o->vmt = vmt; o->data = data; o->list = o->next = NULL; return o; } void node_delete(struct node *o) { struct node *i, *inext; if (o == NULL) { return; } for (i = o->list; i != NULL; i = inext) { inext = i->next; node_delete(i); } if (o->data) { data_delete(o->data); } xfree(o); } void node_log(struct node *o, unsigned depth) { #define NODE_MAXDEPTH 30 static char str[2 * NODE_MAXDEPTH + 1] = ""; struct node *i; log_puts(str); log_puts(o != NULL && o->next != NULL ? "+-" : "\\-"); if (o == NULL) { log_puts("\n"); return; } log_puts(o->vmt->name); if (o->data) { log_puts("("); data_log(o->data); log_puts(")"); } log_puts(depth >= NODE_MAXDEPTH && o->list ? "[...]\n" : "\n"); if (depth < NODE_MAXDEPTH) { str[2 * depth] = o->next ? '|' : ' '; str[2 * depth + 1] = ' '; str[2 * depth + 2] = '\0'; for (i = o->list; i != NULL; i = i->next) { node_log(i, depth + 1); } str[2 * depth] = '\0'; } #undef NODE_MAXDEPTH } void node_insert(struct node **n, struct node *e) { e->next = *n; *n = e; } void node_replace(struct node **n, struct node *e) { if (e->list != NULL) { log_puts("node_replace: e->list != NULL\n"); panic(); } e->list = *n; e->next = (*n)->next; (*n)->next = NULL; *n = e; } /* * run a node. * the following rule must be respected * 1) node_exec must be called always with *r == NULL * 2) in statements (slist,...) *r != NULL if and only if RETURN * 3) in expressions (add, ...) *r == NULL if and only if ERROR */ unsigned node_exec(struct node *o, struct exec *x, struct data **r) { unsigned result; if (x->depth == EXEC_MAXDEPTH) { cons_err("too many nested operations"); return RESULT_ERR; } *r = NULL; x->depth++; result = o->vmt->exec(o, x, r); x->depth--; if (result == RESULT_ERR && *r) { data_delete(*r); *r = NULL; } return result; } /* * execute an unary operator ( '-', '!', '~') */ unsigned node_exec_unary(struct node *o, struct exec *x, struct data **r, unsigned (*func)(struct data *)) { if (node_exec(o->list, x, r) == RESULT_ERR) { return RESULT_ERR; } if (!func(*r)) { return RESULT_ERR; } return RESULT_OK; } /* * execute a binary operator */ unsigned node_exec_binary(struct node *o, struct exec *x, struct data **r, unsigned (*func)(struct data *, struct data *)) { struct data *lhs; if (node_exec(o->list, x, r) == RESULT_ERR) { return RESULT_ERR; } lhs = *r; *r = NULL; if (node_exec(o->list->next, x, r) == RESULT_ERR) { data_delete(lhs); return RESULT_ERR; } if (!func(lhs, *r)) { data_delete(lhs); return RESULT_ERR; } data_delete(*r); *r = lhs; return RESULT_OK; } /* * execute a procedure definition: just check arguments * and move the tree into a proc structure */ unsigned node_exec_proc(struct node *o, struct exec *x, struct data **r) { struct proc *p; struct data *a; struct name *args; args = NULL; for (a = o->data->val.list->next; a != NULL; a = a->next) { if (name_lookup(&args, a->val.ref)) { cons_err("duplicate arguments in proc definition"); name_empty(&args); return RESULT_ERR; } name_add(&args, name_new(a->val.ref)); } p = exec_proclookup(x, o->data->val.list->val.ref); if (p != NULL) { name_empty(&p->args); node_delete(p->code); } else { p = proc_new(o->data->val.list->val.ref); name_insert((struct name **)&x->procs, (struct name *)p); } p->args = args; p->code = o->list; o->list = NULL; return RESULT_OK; } /* * execute a list of statements */ unsigned node_exec_slist(struct node *o, struct exec *x, struct data **r) { struct node *i; unsigned result; for (i = o->list; i != NULL; i = i->next) { result = node_exec(i, x, r); if (result != RESULT_OK) { /* stop on ERR, BREAK, CONTINUE, RETURN, EXIT */ return result; } } return RESULT_OK; } /* * execute a builtin function * if the function didn't set 'r', then set it to 'nil' */ unsigned node_exec_builtin(struct node *o, struct exec *x, struct data **r) { if (!((unsigned (*)(struct exec *, struct data **)) o->data->val.user)(x, r)) { return RESULT_ERR; } if (!*r) { *r = data_newnil(); } return RESULT_OK; } /* * return a constant */ unsigned node_exec_cst(struct node *o, struct exec *x, struct data **r) { *r = data_newnil(); data_assign(*r, o->data); return RESULT_OK; } /* * return the value of the variable (in the node) */ unsigned node_exec_var(struct node *o, struct exec *x, struct data **r) { struct var *v; v = exec_varlookup(x, o->data->val.ref); if (v == NULL) { cons_errss(x->procname, o->data->val.ref, "no such variable"); return RESULT_ERR; } *r = data_newnil(); data_assign(*r, v->data); return RESULT_OK; } /* * ignore the result of the expression * (used to ignore return values of calls) */ unsigned node_exec_ignore(struct node *o, struct exec *x, struct data **r) { unsigned result; result = node_exec(o->list, x, r); if (result == RESULT_ERR || result == RESULT_EXIT) { return result; } if (*r != NULL && (*r)->type != DATA_NIL) { data_print(*r); textout_putstr(tout, "\n"); } data_delete(*r); *r = NULL; return RESULT_OK; } /* * call a procedure */ unsigned node_exec_call(struct node *o, struct exec *x, struct data **r) { struct proc *p; struct name **oldlocals, *newlocals; struct name *argn; struct node *argv; struct var *valist; char *procname_save; unsigned result; newlocals = NULL; result = RESULT_ERR; p = exec_proclookup(x, o->data->val.ref); if (p == NULL) { cons_errs(o->data->val.ref, "no such proc"); goto finish; } valist = NULL; argv = o->list; for (argn = p->args; argn != NULL; argn = argn->next) { if (str_eq(argn->str, "...")) { valist = var_new(&newlocals, "...", data_newlist(NULL)); break; } if (argv == NULL) { cons_errs(o->data->val.ref, "to few arguments"); goto finish; } if (node_exec(argv, x, r) == RESULT_ERR) { goto finish; } var_new(&newlocals, argn->str, *r); argv = argv->next; *r = NULL; } if (valist == NULL && argv != NULL) { cons_errs(o->data->val.ref, "to many arguments"); goto finish; } while (argv != NULL) { if (node_exec(argv, x, r) == RESULT_ERR) goto finish; data_listadd(valist->data, *r); argv = argv->next; *r = NULL; } oldlocals = x->locals; x->locals = &newlocals; procname_save = x->procname; x->procname = p->name.str; result = node_exec(p->code, x, r); if (result != RESULT_ERR) { if (*r == NULL) { /* we always return something */ *r = data_newnil(); } if (result != RESULT_EXIT) { result = RESULT_OK; } } x->locals = oldlocals; x->procname = procname_save; finish: var_empty(&newlocals); return result; } unsigned node_exec_if(struct node *o, struct exec *x, struct data **r) { unsigned cond, result; if (node_exec(o->list, x, r) == RESULT_ERR) { return RESULT_ERR; } cond = data_eval(*r); data_delete(*r); *r = NULL; if (cond) { result = node_exec(o->list->next, x, r); if (result != RESULT_OK) { return result; } } else if (o->list->next->next) { result = node_exec(o->list->next->next, x, r); if (result != RESULT_OK) { return result; } } return RESULT_OK; } unsigned node_exec_for(struct node *o, struct exec *x, struct data **r) { unsigned result; struct data *list, *i; struct var *v; if (node_exec(o->list, x, &list) == RESULT_ERR) { return RESULT_ERR; } if (list->type != DATA_LIST && list->type != DATA_RANGE) { cons_errs(x->procname, "argument to 'for' must be a list or range"); return RESULT_ERR; } v = exec_varlookup(x, o->data->val.ref); if (v == NULL) { v = var_new(x->locals, o->data->val.ref, data_newnil()); } result = RESULT_OK; if (list->type == DATA_LIST) { for (i = list->val.list; i != NULL; i = i->next) { data_assign(v->data, i); result = node_exec(o->list->next, x, r); if (result == RESULT_CONTINUE) { continue; } else if (result == RESULT_BREAK) { break; } else if (result != RESULT_OK) { break; } } } else { if (list->val.range.min <= list->val.range.max) { i = data_newlong(list->val.range.min); while (1) { data_assign(v->data, i); result = node_exec(o->list->next, x, r); if (result == RESULT_CONTINUE) { continue; } else if (result == RESULT_BREAK) { break; } else if (result != RESULT_OK) { break; } if (i->val.num == list->val.range.max) break; i->val.num++; } data_delete(i); } } data_delete(list); return result; } unsigned node_exec_return(struct node *o, struct exec *x, struct data **r) { if (node_exec(o->list, x, r) == RESULT_ERR) { return RESULT_ERR; } return RESULT_RETURN; } unsigned node_exec_exit(struct node *o, struct exec *x, struct data **r) { return RESULT_EXIT; } unsigned node_exec_assign(struct node *o, struct exec *x, struct data **r) { struct var *v; struct data *expr; if (node_exec(o->list, x, &expr) == RESULT_ERR) { return RESULT_ERR; } v = exec_varlookup(x, o->data->val.ref); if (v == NULL) { v = var_new(x->locals, o->data->val.ref, expr); } else { data_delete(v->data); v->data = expr; } return RESULT_OK; } /* * do nothing */ unsigned node_exec_nop(struct node *o, struct exec *x, struct data **r) { return RESULT_OK; } /* * built a list from the expression list */ unsigned node_exec_list(struct node *o, struct exec *x, struct data **r) { struct node *arg; struct data *d; *r = data_newlist(NULL); for (arg = o->list; arg != NULL; arg = arg->next) { if (node_exec(arg, x, &d) == RESULT_ERR) { data_delete(*r); *r = NULL; return RESULT_ERR; } data_listadd(*r, d); } return RESULT_OK; } /* * built a range from two integers */ unsigned node_exec_range(struct node *o, struct exec *x, struct data **r) { struct data *min, *max; if (!node_exec(o->list, x, &min)) return RESULT_ERR; if (!node_exec(o->list->next, x, &max)) return RESULT_ERR; if (min->type != DATA_LONG || max->type != DATA_LONG) { cons_err("cannot create a range with non integers"); return RESULT_ERR; } if (min->val.num > max->val.num) { cons_err("max > min, cant create a valid range"); return RESULT_ERR; } *r = data_newrange(min->val.num, max->val.num); return RESULT_OK; } unsigned node_exec_eq(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_eq); } unsigned node_exec_neq(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_neq); } unsigned node_exec_le(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_le); } unsigned node_exec_lt(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_lt); } unsigned node_exec_ge(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_ge); } unsigned node_exec_gt(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_gt); } unsigned node_exec_and(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_and); } unsigned node_exec_or(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_or); } unsigned node_exec_not(struct node *o, struct exec *x, struct data **r) { return node_exec_unary(o, x, r, data_not); } unsigned node_exec_add(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_add); } unsigned node_exec_sub(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_sub); } unsigned node_exec_mul(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_mul); } unsigned node_exec_div(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_div); } unsigned node_exec_mod(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_mod); } unsigned node_exec_neg(struct node *o, struct exec *x, struct data **r) { return node_exec_unary(o, x, r, data_neg); } unsigned node_exec_lshift(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_lshift); } unsigned node_exec_rshift(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_rshift); } unsigned node_exec_bitand(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_bitand); } unsigned node_exec_bitor(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_bitor); } unsigned node_exec_bitxor(struct node *o, struct exec *x, struct data **r) { return node_exec_binary(o, x, r, data_bitxor); } unsigned node_exec_bitnot(struct node *o, struct exec *x, struct data **r) { return node_exec_unary(o, x, r, data_bitnot); } struct node_vmt node_vmt_proc = { "proc", node_exec_proc }, node_vmt_slist = { "slist", node_exec_slist }, node_vmt_cst = { "cst", node_exec_cst }, node_vmt_var = { "var", node_exec_var }, node_vmt_call = { "call", node_exec_call }, node_vmt_ignore = { "ignore", node_exec_ignore }, node_vmt_builtin = { "builtin", node_exec_builtin }, node_vmt_if = { "if", node_exec_if }, node_vmt_for = { "for", node_exec_for }, node_vmt_return = { "return", node_exec_return }, node_vmt_exit = { "exit", node_exec_exit }, node_vmt_assign = { "assign", node_exec_assign }, node_vmt_nop = { "nop", node_exec_nop }, node_vmt_list = { "list", node_exec_list}, node_vmt_range = { "range", node_exec_range}, node_vmt_eq = { "eq", node_exec_eq }, node_vmt_neq = { "neq", node_exec_neq }, node_vmt_le = { "le", node_exec_le }, node_vmt_lt = { "lt", node_exec_lt }, node_vmt_ge = { "ge", node_exec_ge }, node_vmt_gt = { "gt", node_exec_gt }, node_vmt_and = { "and", node_exec_and }, node_vmt_or = { "or", node_exec_or }, node_vmt_not = { "not", node_exec_not }, node_vmt_add = { "add", node_exec_add }, node_vmt_sub = { "sub", node_exec_sub }, node_vmt_mul = { "mul", node_exec_mul }, node_vmt_div = { "div", node_exec_div }, node_vmt_mod = { "mod", node_exec_mod }, node_vmt_neg = { "neg", node_exec_neg }, node_vmt_lshift = { "lshift", node_exec_lshift }, node_vmt_rshift = { "rshift", node_exec_rshift }, node_vmt_bitand = { "bitand", node_exec_bitand }, node_vmt_bitor = { "bitor", node_exec_bitor }, node_vmt_bitxor = { "bitxor", node_exec_bitxor }, node_vmt_bitnot = { "bitnot", node_exec_bitnot }; midish-1.4.0/node.h010066400017510001751000000037331501104363400126030ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_NODE_H #define MIDISH_NODE_H struct node; struct node_vmt; struct exec; struct node { struct node_vmt *vmt; struct data *data; struct node *next, *list; }; struct node_vmt { char *name; unsigned (*exec)(struct node *, struct exec *, struct data **); }; struct node *node_new(struct node_vmt *, struct data *); void node_delete(struct node *); void node_log(struct node *, unsigned); void node_insert(struct node **, struct node *); void node_replace(struct node **, struct node *); unsigned node_exec(struct node *, struct exec *, struct data **); extern struct node_vmt node_vmt_proc, node_vmt_slist, node_vmt_call, node_vmt_elist, node_vmt_builtin, node_vmt_cst, node_vmt_var, node_vmt_list, node_vmt_range, node_vmt_eq, node_vmt_neq, node_vmt_le, node_vmt_lt, node_vmt_ge, node_vmt_gt, node_vmt_ignore, node_vmt_if, node_vmt_for, node_vmt_return, node_vmt_exit, node_vmt_assign, node_vmt_nop, node_vmt_and, node_vmt_or, node_vmt_not, node_vmt_neg, node_vmt_add, node_vmt_sub, node_vmt_mul, node_vmt_div, node_vmt_mod, node_vmt_lshift, node_vmt_rshift, node_vmt_bitand, node_vmt_bitor, node_vmt_bitxor, node_vmt_bitnot; #endif /* MIDISH_NODE_H */ midish-1.4.0/norm.c010066400017510001751000000120631501104363400126200ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * norm.c * * a stateful midi normalizer. It's used to normalize/sanitize midi * input * */ #include "utils.h" #include "ev.h" #include "norm.h" #include "pool.h" #include "mux.h" #include "filt.h" #include "mixout.h" struct song; #define TAG_PASS 1 #define TAG_PENDING 2 /* * timeout for throtteling: 1 tick at 60 bpm */ #define NORM_TIMO TEMPO_TO_USEC24(120,24) unsigned norm_debug = 0; struct statelist norm_slist; /* state of the normilizer */ struct timo norm_timo; /* for throtteling */ /* --------------------------------------------------------------------- */ void norm_timocb(void *); /* * inject an event */ void norm_putev(struct ev *ev) { if (!EV_ISVOICE(ev) && !EV_ISSX(ev)) return; song_evcb(usong, ev); mux_flush(); } /* * configure the normalizer so that output events are passed to the * given callback */ void norm_start(void) { statelist_init(&norm_slist); timo_set(&norm_timo, norm_timocb, NULL); timo_add(&norm_timo, NORM_TIMO); if (norm_debug) { log_puts("norm_start()\n"); } } /* * unconfigure the normalizer */ void norm_stop(void) { struct state *s, *snext; struct ev ca; if (norm_debug) { log_puts("norm_stop()\n"); } for (s = norm_slist.first; s != NULL; s = snext) { snext = s->next; if (state_cancel(s, &ca)) { if (norm_debug) { log_puts("norm_stop: "); ev_log(&s->ev); log_puts(": cancelled by: "); ev_log(&ca); log_puts("\n"); } s = statelist_update(&norm_slist, &ca); norm_putev(&s->ev); } } timo_del(&norm_timo); statelist_done(&norm_slist); } /* * shuts all notes and restores the default values * of the modified controllers, the bender etc... */ void norm_shut(void) { struct state *s, *snext; struct ev ca; for (s = norm_slist.first; s != NULL; s = snext) { snext = s->next; if (!(s->tag & TAG_PASS)) continue; if (state_cancel(s, &ca)) { if (norm_debug) { log_puts("norm_shut: "); ev_log(&s->ev); log_puts(": cancelled by: "); ev_log(&ca); log_puts("\n"); } s = statelist_update(&norm_slist, &ca); norm_putev(&s->ev); } s->tag &= ~TAG_PASS; } } /* * kill all tagged frames matching the given event (because a bogus * event was received) */ void norm_kill(struct ev *ev) { struct state *st, *stnext; struct ev ca; for (st = norm_slist.first; st != NULL; st = stnext) { stnext = st->next; if (!state_match(st, ev) || !(st->tag & TAG_PASS) || st->phase & EV_PHASE_LAST) { continue; } /* * cancel/untag the frame and change the phase to * EV_PHASE_LAST, so the state can be deleted if * necessary */ if (state_cancel(st, &ca)) { st = statelist_update(&norm_slist, &ca); norm_putev(&st->ev); } st->tag &= ~TAG_PASS; log_puts("norm_kill: "); ev_log(&st->ev); log_puts(": killed\n"); } } /* * give an event to the normalizer for processing */ void norm_evcb(struct ev *ev) { struct state *st; if (norm_debug) { log_puts("norm_run: "); ev_log(ev); log_puts("\n"); } #ifdef NORM_DEBUG if (!EV_ISVOICE(ev) && !EV_ISSX(ev)) { log_puts("norm_evcb: only voice events allowed\n"); panic(); } #endif /* * create/update state for this event */ st = statelist_update(&norm_slist, ev); if (st->phase & EV_PHASE_FIRST) { if (st->flags & STATE_NEW) st->nevents = 0; if (st->flags & (STATE_BOGUS | STATE_NESTED)) { st->tag = 0; if (norm_debug) { log_puts("norm_evcb: "); ev_log(ev); log_puts(": bogus/nested frame\n"); } norm_kill(ev); } else st->tag = TAG_PASS; } /* * nothing to do with silent (not selected) frames */ if (!(st->tag & TAG_PASS)) return; /* * throttling: if we played more than MAXEV * events skip this event only if it doesnt change the * phase of the frame */ if (st->nevents > NORM_MAXEV && (st->phase == EV_PHASE_NEXT || st->phase == (EV_PHASE_FIRST | EV_PHASE_LAST))) { st->tag |= TAG_PENDING; return; } norm_putev(&st->ev); st->nevents++; } /* * timeout: outdate all events, so throttling will enable * output again. */ void norm_timocb(void *addr) { struct state *i; statelist_outdate(&norm_slist); for (i = norm_slist.first; i != NULL; i = i->next) { i->nevents = 0; if (i->tag & TAG_PENDING) { i->tag &= ~TAG_PENDING; norm_putev(&i->ev); i->nevents++; } } timo_add(&norm_timo, NORM_TIMO); } midish-1.4.0/norm.h010066400017510001751000000021561501104363400126270ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_NORM_H #define MIDISH_NORM_H #define NORM_MAXEV 1 /* max events per time slice */ struct filt; struct ev; void norm_start(void); void norm_shut(void); void norm_stop(void); void norm_putev(struct ev *); void norm_evcb(struct ev *); void norm_timercb(void); extern unsigned norm_debug; #endif /* MIDISH_NORM_H */ midish-1.4.0/parse.c010066400017510001751000000630421501104363400127620ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * A simple LL(1)-like parser for the followingi grammar: * * endl: "\n" * ";" * * cst: num * string * ident * nil * "$" ident * "[" call "]" * "(" expr ")" * "{" [ expr expr ... expr ] "}" * * unary: "-" unary * "~" unary * "!" unary * cst * * muldiv: unary "*" unary * unary "/" unary * unary "%" unary * * addsub: muldiv "+" muldiv * muldiv "-" muldiv * * shift: addsub "<<" addsub * addsub ">>" addsub * * compare: shift "<" shift * shift ">" shift * shift "<=" shift * shift ">=" shift * * equal: compare "==" compare * compare "!=" compare * * bitand: equal "&" equal * * bitxor: bitand "^" bitand * * bitor: bitxor "|" bitxor * * and: bitor "&&" bitor * * or: and "||" and * * range: or ".." or * * expr: or * * call: ident [ expr expr ... expr ] * * stmt: "let" ident "=" expr endl * "for" ident "in" expr slist * "if" expr slist [ else slist ] * "for" ident "in" expr slist * "return" expr * call endl * endl * * slist: "{" [ stmt stmt ... stmt ] "}" * * proc: "proc" ident [ ident ident ... ident ] slist * * line: proc * stmt * * prog: [ line line ... line ] EOF */ #include #include #include #include "data.h" #include "parse.h" #include "node.h" #include "utils.h" #include "exec.h" #include "cons.h" /* * tokinizer states */ enum LEX_STATE { LEX_ANY, LEX_ERROR, LEX_COMMENT, LEX_BREAK, LEX_BASE, LEX_NUM, LEX_IDENT, LEX_STRING, LEX_OP }; /* * parser states */ enum { PARSE_RANGE, PARSE_RANGE_1, PARSE_OR, PARSE_OR_1, PARSE_AND, PARSE_AND_1, PARSE_BITOR, PARSE_BITOR_1, PARSE_BITXOR, PARSE_BITXOR_1, PARSE_BITAND, PARSE_BITAND_1, PARSE_EQ, PARSE_EQ_1, PARSE_CMP, PARSE_CMP_1, PARSE_SHIFT, PARSE_SHIFT_1, PARSE_SUB, PARSE_SUB_1, PARSE_DIV, PARSE_DIV_1, PARSE_UN, PARSE_CST, PARSE_LIST_1, PARSE_LIST_2, PARSE_PAR_1, PARSE_PAR_2, PARSE_VAR_1, PARSE_FUNC_1, PARSE_FUNC_2, PARSE_CALL, PARSE_CALL_1, PARSE_CALL_2, PARSE_STMT, PARSE_IF_1, PARSE_IF_2, PARSE_IF_3, PARSE_IF_4, PARSE_FOR_1, PARSE_FOR_2, PARSE_FOR_3, PARSE_FOR_4, PARSE_RET_1, PARSE_LET_1, PARSE_LET_2, PARSE_ENDL, PARSE_SLIST, PARSE_SLIST_1, PARSE_SLIST_2, PARSE_PROC, PARSE_PROC_1, PARSE_PROC_2, PARSE_PROC_3, PARSE_PROG, PARSE_PROG_1, PARSE_ERROR }; unsigned parse_debug = 0; struct tokname { unsigned id; /* token id */ char *str; /* corresponding string */ } lex_kw[] = { { TOK_IF, "if" }, { TOK_ELSE, "else" }, { TOK_PROC, "proc" }, { TOK_LET, "let" }, { TOK_RETURN, "return" }, { TOK_FOR, "for" }, { TOK_IN, "in" }, { TOK_EXIT, "exit" }, { TOK_NIL, "nil" } }, lex_op[] = { { TOK_ELLIPSIS, "..." }, { TOK_RANGE, ".." }, { TOK_DOT, "." }, { TOK_EQ, "==" }, { TOK_ASSIGN, "=" }, { TOK_NEQ, "!=" }, { TOK_EXCLAM, "!" }, { TOK_GE, ">=" }, { TOK_RSHIFT, ">>" }, { TOK_GT, ">" }, { TOK_LE, "<=" }, { TOK_LSHIFT, "<<" }, { TOK_LT, "<" }, { TOK_AND, "&&" }, { TOK_BITAND, "&" }, { TOK_OR, "||" }, { TOK_BITOR, "|" }, { TOK_PLUS, "+" }, { TOK_MINUS, "-" }, { TOK_STAR, "*" }, { TOK_SLASH, "/" }, { TOK_PCT, "%" }, { TOK_LPAR, "(" }, { TOK_RPAR, ")" }, { TOK_LBRACE, "{" }, { TOK_RBRACE, "}" }, { TOK_LBRACKET, "[" }, { TOK_RBRACKET, "]" }, { TOK_COMMA, "," }, { TOK_SEMICOLON, ";" }, { TOK_COLON, ":" }, { TOK_BITXOR, "^" }, { TOK_TILDE, "~" }, { TOK_AT, "@" }, { TOK_DOLLAR, "$" }, { TOK_ENDLINE, "\n" } }; #define LEX_NKW (sizeof(lex_kw) / sizeof(lex_kw[0])) #define LEX_NOP (sizeof(lex_op) / sizeof(lex_op[0])) /* * names of parser pstates (debug only) */ char *parse_pstates[] = { "PARSE_RANGE", "PARSE_RANGE_1", "PARSE_OR", "PARSE_OR_1", "PARSE_AND", "PARSE_AND_1", "PARSE_BITOR", "PARSE_BITOR_1", "PARSE_BITXOR", "PARSE_BITXOR_1", "PARSE_BITAND", "PARSE_BITAND_1", "PARSE_EQ", "PARSE_EQ_1", "PARSE_CMP", "PARSE_CMP_1", "PARSE_SHIFT", "PARSE_SHIFT_1", "PARSE_SUB", "PARSE_SUB_1", "PARSE_DIV", "PARSE_DIV_1", "PARSE_UN", "PARSE_CST", "PARSE_LIST_1", "PARSE_LIST_2", "PARSE_PAR_1", "PARSE_PAR_2", "PARSE_VAR_1", "PARSE_FUNC_1", "PARSE_FUNC_2", "PARSE_CALL", "PARSE_CALL_1", "PARSE_CALL_2", "PARSE_STMT", "PARSE_IF_1", "PARSE_IF_2", "PARSE_IF_3", "PARSE_IF_4", "PARSE_FOR_1", "PARSE_FOR_2", "PARSE_FOR_3", "PARSE_FOR_4", "PARSE_RET_1", "PARSE_LET_1", "PARSE_LET_2", "PARSE_ENDL", "PARSE_SLIST", "PARSE_SLIST_1", "PARSE_SLIST_2", "PARSE_PROC", "PARSE_PROC_1", "PARSE_PROC_2", "PARSE_PROC_3", "PARSE_PROG", "PARSE_PROG_1", "PARSE_ERROR" }; /* * set of tokens that may start an expression */ unsigned first_expr[] = { TOK_MINUS, TOK_EXCLAM, TOK_TILDE, TOK_LPAR, TOK_DOLLAR, TOK_LBRACE, TOK_LBRACKET, TOK_IDENT, TOK_NUM, TOK_STRING, TOK_NIL, TOK_ELLIPSIS, 0 }; /* * set of tokens that may start a pstatement */ unsigned first_stmt[] = { TOK_SEMICOLON, TOK_ENDLINE, TOK_IF, TOK_FOR, TOK_IDENT, TOK_LET, TOK_RETURN, TOK_EXIT, 0 }; void lex_init(struct parse *l, char *filename, void (*tokcb)(void *, unsigned, unsigned long), void *tokarg) { l->lstate = LEX_ANY; l->tokcb = tokcb; l->tokarg = tokarg; l->filename = filename; l->line = 1; } void lex_done(struct parse *l) { if (l->lstate != LEX_ANY) log_puts("lex_done: unterminated token\n"); } void lex_toklog(unsigned id, unsigned long val) { unsigned i; if (id == TOK_ENDLINE) { log_puts("\\n"); return; } for (i = 0; i < LEX_NKW; i++) { if (lex_kw[i].id == id) { log_puts(lex_kw[i].str); return; } } for (i = 0; i < LEX_NOP; i++) { if (lex_op[i].id == id) { log_puts(lex_op[i].str); return; } } switch (id) { case TOK_EOF: log_puts("EOF"); break; case TOK_ERR: log_puts("ERR"); break; case TOK_IDENT: log_puts("@"); log_puts((char *)val); break; case TOK_NUM: log_putu(val); break; case TOK_STRING: log_puts("\""); log_puts((char *)val); log_puts("\""); break; default: log_puts("UNKNOWN"); break; } } void lex_err(struct parse *l, char *msg) { cons_errsu(l->filename, l->line, msg); l->lstate = LEX_ERROR; l->tokcb(l->tokarg, TOK_ERR, 0); } void lex_found(struct parse *l, unsigned id, unsigned long val) { l->lstate = LEX_ANY; l->tokcb(l->tokarg, id, val); } /* * convert string to number in the appropriate base */ int lex_atol(struct parse *l) { char *p; unsigned digit; unsigned long hi, lo; unsigned long val; #define BITS (8 * sizeof(unsigned long) / 2) #define LOWORD(a) ((a) & ((1L << BITS) - 1)) #define HIWORD(a) ((a) >> BITS) val = 0; for (p = l->buf; *p != '\0'; p++) { if (*p >= 'a' && *p <= 'z') { digit = 10 + *p - 'a'; } else if (*p >= 'A' && *p <= 'Z') { digit = 10 + *p - 'A'; } else { digit = *p - '0'; } if (digit >= l->base) { lex_err(l, "not allowed digit in numeric constant"); return 0; } lo = digit + l->base * LOWORD(val); hi = l->base * HIWORD(val); if (HIWORD(hi + HIWORD(lo)) != 0) { lex_err(l, "overflow in numeric constant"); return 0; } val = (hi << BITS) + lo; } #undef BITS #undef LOWORD #undef HIWORD lex_found(l, TOK_NUM, val); return 1; } void lex_handle(struct parse *l, int c) { unsigned i; if (c == '\n') l->line++; for (;;) { switch (l->lstate) { case LEX_ANY: if (c < 0) { lex_found(l, TOK_EOF, 0); return; } if (IS_SPACE(c)) return; if (c == '#') { l->lstate = LEX_COMMENT; return; } if (c == '\\') { l->lstate = LEX_BREAK; return; } if (IS_DIGIT(c)) { l->used = 0; if (c == '0') { l->lstate = LEX_BASE; return; } l->lstate = LEX_NUM; l->base = 10; break; } if (IS_IDFIRST(c)) { l->lstate = LEX_IDENT; l->buf[0] = c; l->used = 1; return; } if (IS_QUOTE(c)) { l->lstate = LEX_STRING; l->used = 0; return; } for (i = 0; ; i++) { if (i == LEX_NOP) { lex_err(l, "character doesn't start any token"); break; } if (lex_op[i].str[0] == c) { l->lstate = LEX_OP; l->opindex = i; l->used = 0; break; } } break; case LEX_ERROR: if (c < 0 || c == '\n') { l->lstate = LEX_ANY; break; } return; case LEX_COMMENT: if (c < 0 || c == '\n') { l->lstate = LEX_ANY; break; } return; case LEX_BREAK: if (IS_SPACE(c)) return; if (c != '\n') { lex_err(l, "newline expected after '\\'"); break; } l->lstate = LEX_ANY; return; case LEX_BASE: l->lstate = LEX_NUM; if (c == 'x' || c == 'X') { l->base = 16; return; } l->base = 8; break; case LEX_NUM: if (IS_DIGIT(c) || IS_ALPHA(c)) { if (l->used == STRING_MAXSZ - 1) { lex_err(l, "constant too long"); break; } l->buf[l->used++] = c; return; } l->buf[l->used] = 0; lex_atol(l); break; case LEX_IDENT: if (IS_IDNEXT(c)) { if (l->used == IDENT_MAXSZ - 1) { lex_err(l, "identifier too long"); break; } l->buf[l->used++] = c; return; } l->buf[l->used] = '\0'; for (i = 0;; i++) { if (i == LEX_NKW) { lex_found(l, TOK_IDENT, (unsigned long)l->buf); break; } if (strcmp(lex_kw[i].str, l->buf) == 0) { lex_found(l, lex_kw[i].id, 0); break; } } break; case LEX_STRING: if (IS_QUOTE(c)) { l->buf[l->used] = '\0'; lex_found(l, TOK_STRING, (unsigned long)l->buf); return; } if (!IS_PRINTABLE(c)) { lex_err(l, "non printable char in string"); break; } if (l->used == STRING_MAXSZ - 1) { lex_err(l, "string too long"); break; } l->buf[l->used++] = c; return; case LEX_OP: if (lex_op[l->opindex].str[l->used] == c) { l->used++; if (lex_op[l->opindex].str[l->used] == '\0') lex_found(l, lex_op[l->opindex].id, 0); return; } else if (lex_op[l->opindex].str[l->used] == '\0') { lex_found(l, lex_op[l->opindex].id, 0); break; } l->opindex++; if (l->opindex == LEX_NOP) { bad_op: lex_err(l, "bad operator"); break; } for (i = 0; i < l->used; i++) { if (lex_op[i].str[i] != lex_op[i].str[i]) goto bad_op; } break; default: log_puts("lex_handle: bad state\n"); panic(); } } } /* * return true if the given token is in the given set */ unsigned tok_is(unsigned id, unsigned *set) { while (*set != 0) { if (id == *set) return 1; set++; } return 0; } /* * initialize the parser */ void parse_init(struct parse *p, struct exec *e, void (*cb)(struct exec *, struct node *)) { p->root = NULL; p->sp = p->stack; p->sp->pstate = PARSE_PROG; p->sp->pnode = (void *)0xdeadbeef; p->exec = e; p->cb = cb; } void parse_done(struct parse *p) { if (p->sp->pstate != PARSE_PROG) log_puts("parse_done: unterminated rule\n"); if (p->sp - p->stack > 0) log_puts("parse_done: stack not empty\n"); } /* * push the current pstate in the stack and * switch to the given pstate */ void parse_begin(struct parse *p, unsigned newpstate, struct node **newpnode) { p->sp++; p->sp->pstate = newpstate; p->sp->pnode = newpnode; } /* * pop and restore a pstate */ void parse_end(struct parse *p) { p->sp--; } /* * display a syntax error and start recovering */ void parse_err(struct parse *p, char *msg) { if (msg) cons_errsu(p->filename, p->line, msg); node_delete(p->root); p->root = NULL; p->sp = p->stack; p->sp->pstate = PARSE_ERROR; p->sp->pnode = (void *)0xdeadbeef; p->cb(p->exec, NULL); } void parse_found(struct parse *p) { if (parse_debug) node_log(p->root, 0); p->cb(p->exec, p->root); node_delete(p->root); p->root = NULL; } /* * process the given token */ void parse_cb(void *arg, unsigned id, unsigned long val) { struct parse *p = (struct parse *)arg; struct node_vmt *vmt; struct data *args; struct pst *sp; if (parse_debug) { lex_toklog(id, val); log_puts("\n"); } for (;;) { if (parse_debug) { for (sp = p->stack; sp != p->sp; sp++) log_puts(". "); log_puts(parse_pstates[sp->pstate]); log_puts("\n"); } if (id == TOK_ERR) { parse_err(p, NULL); id = 0; } sp = p->sp; switch (sp->pstate) { case PARSE_RANGE: sp->pstate = PARSE_RANGE_1; parse_begin(p, PARSE_OR, sp->pnode); break; case PARSE_RANGE_1: if (id == 0) return; if (id == TOK_RANGE) { vmt = &node_vmt_range; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_OR, &(*sp->pnode)->list->next); break; case PARSE_OR: sp->pstate = PARSE_OR_1; parse_begin(p, PARSE_AND, sp->pnode); break; case PARSE_OR_1: if (id == 0) return; if (id == TOK_OR) { vmt = &node_vmt_or; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_AND, &(*sp->pnode)->list->next); break; case PARSE_AND: sp->pstate = PARSE_AND_1; parse_begin(p, PARSE_BITOR, sp->pnode); break; case PARSE_AND_1: if (id == 0) return; if (id == TOK_AND) { vmt = &node_vmt_and; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_BITOR, &(*sp->pnode)->list->next); break; case PARSE_BITOR: sp->pstate = PARSE_BITOR_1; parse_begin(p, PARSE_BITXOR, sp->pnode); break; case PARSE_BITOR_1: if (id == 0) return; if (id == TOK_BITOR) { vmt = &node_vmt_bitor; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_BITXOR, &(*sp->pnode)->list->next); break; case PARSE_BITXOR: sp->pstate = PARSE_BITXOR_1; parse_begin(p, PARSE_BITAND, sp->pnode); break; case PARSE_BITXOR_1: if (id == 0) return; if (id == TOK_BITXOR) { vmt = &node_vmt_bitxor; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_BITAND, &(*sp->pnode)->list->next); break; case PARSE_BITAND: sp->pstate = PARSE_BITAND_1; parse_begin(p, PARSE_EQ, sp->pnode); break; case PARSE_BITAND_1: if (id == 0) return; if (id == TOK_BITAND) { vmt = &node_vmt_bitand; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_EQ, &(*sp->pnode)->list->next); break; case PARSE_EQ: sp->pstate = PARSE_EQ_1; parse_begin(p, PARSE_CMP, sp->pnode); break; case PARSE_EQ_1: if (id == 0) return; if (id == TOK_EQ) { vmt = &node_vmt_eq; } else if (id == TOK_NEQ) { vmt = &node_vmt_neq; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_CMP, &(*sp->pnode)->list->next); break; case PARSE_CMP: sp->pstate = PARSE_CMP_1; parse_begin(p, PARSE_SHIFT, sp->pnode); break; case PARSE_CMP_1: if (id == 0) return; if (id == TOK_LT) { vmt = &node_vmt_lt; } else if (id == TOK_LE) { vmt = &node_vmt_le; } else if (id == TOK_GT) { vmt = &node_vmt_gt; } else if (id == TOK_GE) { vmt = &node_vmt_ge; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_SHIFT, &(*sp->pnode)->list->next); break; case PARSE_SHIFT: sp->pstate = PARSE_SHIFT_1; parse_begin(p, PARSE_SUB, sp->pnode); break; case PARSE_SHIFT_1: if (id == 0) return; if (id == TOK_LSHIFT) { vmt = &node_vmt_lshift; } else if (id == TOK_RSHIFT) { vmt = &node_vmt_rshift; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_SUB, &(*sp->pnode)->list->next); break; case PARSE_SUB: sp->pstate = PARSE_SUB_1; parse_begin(p, PARSE_DIV, sp->pnode); break; case PARSE_SUB_1: if (id == 0) return; if (id == TOK_MINUS) { vmt = &node_vmt_sub; } else if (id == TOK_PLUS) { vmt = &node_vmt_add; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_DIV, &(*sp->pnode)->list->next); break; case PARSE_DIV: sp->pstate = PARSE_DIV_1; parse_begin(p, PARSE_UN, sp->pnode); break; case PARSE_DIV_1: if (id == 0) return; if (id == TOK_SLASH) { vmt = &node_vmt_div; } else if (id == TOK_STAR) { vmt = &node_vmt_mul; } else if (id == TOK_PCT) { vmt = &node_vmt_mod; } else { parse_end(p); break; } id = 0; node_replace(sp->pnode, node_new(vmt, NULL)); parse_begin(p, PARSE_UN, &(*sp->pnode)->list->next); break; case PARSE_UN: if (id == 0) return; if (id == TOK_MINUS) { vmt = &node_vmt_neg; } else if (id == TOK_EXCLAM) { vmt = &node_vmt_not; } else if (id == TOK_TILDE) { vmt =&node_vmt_bitnot; } else { sp->pstate = PARSE_CST; break; } id = 0; *sp->pnode = node_new(vmt, NULL); sp->pnode = &(*sp->pnode)->list; break; case PARSE_CST: if (id == 0) return; if (id == TOK_NUM) { *sp->pnode = node_new(&node_vmt_cst, data_newlong(val)); parse_end(p); } else if (id == TOK_STRING) { *sp->pnode = node_new(&node_vmt_cst, data_newstring((char *)val)); parse_end(p); } else if (id == TOK_IDENT) { *sp->pnode = node_new(&node_vmt_cst, data_newref((char *)val)); parse_end(p); } else if (id == TOK_ELLIPSIS) { *sp->pnode = node_new(&node_vmt_var, data_newref((char *)"...")); parse_end(p); } else if (id == TOK_NIL) { *sp->pnode = node_new(&node_vmt_cst, data_newnil()); parse_end(p); } else if (id == TOK_LPAR) { sp->pstate = PARSE_PAR_1; } else if (id == TOK_DOLLAR) { sp->pstate = PARSE_VAR_1; } else if (id == TOK_LBRACKET) { sp->pstate = PARSE_FUNC_1; } else if (id == TOK_LBRACE) { *sp->pnode = node_new(&node_vmt_list, NULL); sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_LIST_1; } else { parse_err(p, "value or ``('' expected"); break; } id = 0; break; case PARSE_LIST_1: if (id == 0) return; if (id == TOK_RBRACE) { parse_end(p); } else if (tok_is(id, first_expr)) { sp->pstate = PARSE_LIST_2; parse_begin(p, PARSE_RANGE, sp->pnode); break; } else { parse_err(p, "expr or ``}'' expected"); break; } id = 0; break; case PARSE_LIST_2: sp->pnode = &(*sp->pnode)->next; sp->pstate = PARSE_LIST_1; break; case PARSE_PAR_1: sp->pstate = PARSE_PAR_2; parse_begin(p, PARSE_RANGE, sp->pnode); break; case PARSE_PAR_2: if (id == 0) return; if (id != TOK_RPAR) { parse_err(p, "')' expected\n"); break; } id = 0; parse_end(p); break; case PARSE_VAR_1: if (id == 0) return; if (id != TOK_IDENT) { parse_err(p, "identifier expected"); break; } id = 0; *sp->pnode = node_new(&node_vmt_var, data_newref((char *)val)); parse_end(p); break; case PARSE_FUNC_1: sp->pstate = PARSE_FUNC_2; parse_begin(p, PARSE_CALL, sp->pnode); break; case PARSE_FUNC_2: if (id == 0) return; if (id != TOK_RBRACKET) { parse_err(p, "']' expected"); break; } id = 0; parse_end(p); break; case PARSE_CALL: if (id == 0) return; if (id != TOK_IDENT) { parse_err(p, "proc identifier expected"); break; } id = 0; *sp->pnode = node_new(&node_vmt_call, data_newref((char *)val)); sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_CALL_1; break; case PARSE_CALL_1: if (id == 0) return; if (!tok_is(id, first_expr)) { parse_end(p); break; } sp->pstate = PARSE_CALL_2; parse_begin(p, PARSE_RANGE, sp->pnode); break; case PARSE_CALL_2: sp->pnode = &(*sp->pnode)->next; sp->pstate = PARSE_CALL_1; break; case PARSE_STMT: if (id == 0) return; if (id == TOK_IDENT) { sp->pstate = PARSE_ENDL; parse_begin(p, PARSE_CALL, sp->pnode); break; } else if (id == TOK_EXIT) { *sp->pnode = node_new(&node_vmt_exit, NULL); sp->pstate = PARSE_ENDL; } else if (id == TOK_IF) { sp->pstate = PARSE_IF_1; } else if (id == TOK_FOR) { sp->pstate = PARSE_FOR_1; } else if (id == TOK_RETURN) { sp->pstate = PARSE_RET_1; } else if (id == TOK_LET) { sp->pstate = PARSE_LET_1; } else if (id == TOK_ENDLINE || id == TOK_SEMICOLON) { *sp->pnode = node_new(&node_vmt_nop, NULL); parse_end(p); } else { parse_err(p, "pstatement expected"); break; } id = 0; break; case PARSE_IF_1: if (id == 0) return; if (!tok_is(id, first_expr)) { parse_err(p, "expr expected after ``if''"); break; } *sp->pnode = node_new(&node_vmt_if, NULL); sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_IF_2; parse_begin(p, PARSE_RANGE, sp->pnode); break; case PARSE_IF_2: sp->pnode = &(*sp->pnode)->next; sp->pstate = PARSE_IF_3; parse_begin(p, PARSE_SLIST, sp->pnode); break; case PARSE_IF_3: if (id == 0) return; if (id != TOK_ELSE) { parse_end(p); break; } id = 0; sp->pnode = &(*sp->pnode)->next; sp->pstate = PARSE_IF_4; parse_begin(p, PARSE_SLIST, sp->pnode); break; case PARSE_IF_4: parse_end(p); break; case PARSE_FOR_1: if (id == 0) return; if (id != TOK_IDENT) { parse_err(p, "ident expected after ``for''"); break; } id = 0; *sp->pnode = node_new(&node_vmt_for, data_newref((char *)val)); sp->pstate = PARSE_FOR_2; break; case PARSE_FOR_2: if (id == 0) return; if (id != TOK_IN) { parse_err(p, "``in'' expected"); break; } id = 0; sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_FOR_3; parse_begin(p, PARSE_RANGE, sp->pnode); break; case PARSE_FOR_3: sp->pnode = &(*sp->pnode)->next; sp->pstate = PARSE_FOR_4; parse_begin(p, PARSE_SLIST, sp->pnode); break; case PARSE_FOR_4: parse_end(p); break; case PARSE_RET_1: *sp->pnode = node_new(&node_vmt_return, NULL); sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_ENDL; parse_begin(p, PARSE_RANGE, sp->pnode); break; case PARSE_LET_1: if (id == 0) return; if (id != TOK_IDENT) { parse_err(p, "ref expected after ``let''"); break; } id = 0; *sp->pnode = node_new(&node_vmt_assign, data_newref((char *)val)); sp->pstate = PARSE_LET_2; break; case PARSE_LET_2: if (id == 0) return; if (id != TOK_ASSIGN) { parse_err(p, "``='' expected"); break; } id = 0; sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_ENDL; parse_begin(p, PARSE_RANGE, sp->pnode); break; case PARSE_ENDL: if (id == 0) return; if (id == TOK_SEMICOLON || id == TOK_ENDLINE) { id = 0; parse_end(p); break; } parse_err(p, "``;'' or new line expected"); break; case PARSE_SLIST: if (id == 0) return; if (id != TOK_LBRACE) { parse_err(p, "``{'' expected"); break; } id = 0; *sp->pnode = node_new(&node_vmt_slist, NULL); sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_SLIST_1; break; case PARSE_SLIST_1: if (id == 0) return; if (id == TOK_RBRACE) { id = 0; parse_end(p); break; } sp->pstate = PARSE_SLIST_2; parse_begin(p, PARSE_STMT, sp->pnode); break; case PARSE_SLIST_2: sp->pnode = &(*sp->pnode)->next; sp->pstate = PARSE_SLIST_1; break; case PARSE_PROC_1: if (id == 0) return; if (id != TOK_IDENT) { parse_err(p, "ref expected after ``proc''"); break; } id = 0; args = data_newlist(NULL); data_listadd(args, data_newref((char *)val)); *sp->pnode = node_new(&node_vmt_proc, args); sp->pstate = PARSE_PROC_2; break; case PARSE_PROC_2: if (id == 0) return; if (id == TOK_IDENT) { id = 0; args = (*sp->pnode)->data; data_listadd(args, data_newref((char *)val)); break; } if (id == TOK_ELLIPSIS) { id = 0; args = (*sp->pnode)->data; data_listadd(args, data_newref("...")); sp->pstate = PARSE_PROC_3; break; } if (id == TOK_LBRACE) { sp->pstate = PARSE_PROC_3; break; } parse_err(p, "arg name or block expected\n"); break; case PARSE_PROC_3: if (id == 0) return; if (id != TOK_LBRACE) { parse_err(p, "'{' expected\n"); break; } sp->pnode = &(*sp->pnode)->list; sp->pstate = PARSE_SLIST; break; case PARSE_PROG: if (id == 0) return; if (id == TOK_PROC) { id = 0; sp->pstate = PARSE_PROG_1; parse_begin(p, PARSE_PROC_1, &p->root); break; } if (tok_is(id, first_stmt)) { sp->pstate = PARSE_PROG_1; parse_begin(p, PARSE_STMT, &p->root); break; } if (id == TOK_EOF) return; parse_err(p, "stmt or proc def expected"); break; case PARSE_PROG_1: parse_found(p); sp->pstate = PARSE_PROG; break; case PARSE_ERROR: if (id == 0) return; if (id == TOK_EOF) { sp->pstate = PARSE_PROG; break; } if (id == TOK_ENDLINE || id == TOK_RBRACE) sp->pstate = PARSE_PROG; id = 0; break; default: log_puts("parse_handle: bad state\n"); panic(); } } } midish-1.4.0/parse.h010066400017510001751000000055141501104363400127670ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_PARSE_H #define MIDISH_PARSE_H enum TOK_ID { TOK_EOF = 1, TOK_ERR, /* data */ TOK_IDENT, TOK_NUM, TOK_STRING, /* operators */ TOK_ASSIGN, TOK_PLUS, TOK_MINUS, TOK_STAR, TOK_SLASH, TOK_PCT, TOK_LSHIFT, TOK_RSHIFT, TOK_BITAND, TOK_BITOR, TOK_BITXOR, TOK_TILDE, TOK_EQ, TOK_NEQ, TOK_GE, TOK_GT, TOK_LE, TOK_LT, TOK_EXCLAM, TOK_AND, TOK_OR, TOK_LPAR, TOK_RPAR, TOK_LBRACE, TOK_RBRACE, TOK_LBRACKET, TOK_RBRACKET, TOK_COMMA, TOK_DOT, TOK_SEMICOLON, TOK_COLON, TOK_RANGE, TOK_ELLIPSIS, TOK_AT, TOK_DOLLAR, TOK_ENDLINE, /* keywords */ TOK_IF, TOK_ELSE, TOK_WHILE, TOK_DO, TOK_FOR, TOK_IN, TOK_PROC, TOK_LET, TOK_RETURN, TOK_EXIT, TOK_NIL }; #define IDENT_MAXSZ 32 #define STRING_MAXSZ 1024 #define IS_SPACE(c) ((c) == ' ' || (c) == '\r' || (c) == '\t') #define IS_PRINTABLE(c) ((c) >= ' ' && (c) != 0x7f) #define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') #define IS_ALPHA(c) (((c) >= 'A' && (c) <= 'Z') || \ ((c) >= 'a' && (c) <= 'z')) #define IS_IDFIRST(c) (IS_ALPHA(c) || (c) == '_') #define IS_IDNEXT(c) (IS_IDFIRST(c) || IS_DIGIT(c)) #define IS_QUOTE(c) ((c) == '"') struct node; struct exec; struct pst { unsigned pstate; /* backup of pstate */ struct node **pnode; /* backup of node */ }; struct parse { /* * lexical analyser */ unsigned lstate; unsigned base; unsigned opindex; unsigned used; void (*tokcb)(void *, unsigned, unsigned long); void *tokarg; char buf[STRING_MAXSZ]; unsigned line, col; char *filename; /* * parser */ #define PARSE_STACKLEN 64 struct pst stack[PARSE_STACKLEN]; struct pst *sp; struct node *root; /* root of the tree */ struct exec *exec; void (*cb)(struct exec *, struct node *); }; void lex_init(struct parse *, char *, void (*)(void *, unsigned, unsigned long), void *); void lex_done(struct parse *); void lex_handle(struct parse *, int); void lex_toklog(unsigned, unsigned long); void parse_init(struct parse *, struct exec *, void (*)(struct exec *, struct node *)); void parse_done(struct parse *); void parse_cb(void *, unsigned, unsigned long); #endif /* !defined(MIDISH_PARSE_H) */ midish-1.4.0/pool.c010066400017510001751000000075711501104363400126260ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * a pool is a large memory block (the pool) that is split into * small blocks of equal size (pools entries). Its used for * fast allocation of pool entries. Free enties are on a singly * linked list */ #include "utils.h" #include "pool.h" unsigned pool_debug = 0; /* * initialises a pool of "itemnum" elements of size "itemsize" */ void pool_init(struct pool *o, char *name, unsigned itemsize, unsigned itemnum) { unsigned i; unsigned char *p; /* * round item size to sizeof unsigned */ if (itemsize < sizeof(struct poolent)) { itemsize = sizeof(struct poolent); } itemsize += sizeof(unsigned) - 1; itemsize &= ~(sizeof(unsigned) - 1); o->data = xmalloc(itemsize * itemnum, "pool"); if (!o->data) { log_puts("pool_init("); log_puts(name); log_puts("): out of memory\n"); panic(); } o->first = NULL; o->itemsize = itemsize; o->itemnum = itemnum; o->name = name; #ifdef POOL_DEBUG o->maxused = 0; o->used = 0; o->newcnt = 0; #endif /* * create a linked list of all entries */ p = o->data; for (i = itemnum; i != 0; i--) { ((struct poolent *)p)->next = o->first; o->first = (struct poolent *)p; p += itemsize; o->itemnum++; } } /* * free the given pool */ void pool_done(struct pool *o) { xfree(o->data); #ifdef POOL_DEBUG if (o->used != 0) { log_puts("pool_done("); log_puts(o->name); log_puts("): WARNING "); log_putu(o->used); log_puts(" items still allocated\n"); } if (pool_debug) { log_puts("pool_done("); log_puts(o->name); log_puts("): using "); log_putu((1023 + o->itemnum * o->itemsize) / 1024); log_puts("kB maxused = "); log_putu(100 * o->maxused / o->itemnum); log_puts("% allocs = "); log_putu(100 * o->newcnt / o->itemnum); log_puts("%\n"); } #endif } /* * allocate an entry from the pool: just unlink * it from the free list and return the pointer */ void * pool_new(struct pool *o) { #ifdef POOL_DEBUG unsigned i; unsigned char *buf; #endif struct poolent *e; if (!o->first) { log_puts("pool_new("); log_puts(o->name); log_puts("): pool is empty\n"); panic(); } /* * unlink from the free list */ e = o->first; o->first = e->next; #ifdef POOL_DEBUG o->newcnt++; o->used++; if (o->used > o->maxused) o->maxused = o->used; /* * overwrite the entry with garbage so any attempt to use * uninitialized memory will probably segfault */ buf = (unsigned char *)e; for (i = o->itemsize; i > 0; i--) *(buf++) = 0xd0; #endif return e; } /* * free an entry: just link it again on the free list */ void pool_del(struct pool *o, void *p) { struct poolent *e = (struct poolent *)p; #ifdef POOL_DEBUG unsigned i; unsigned char *buf; /* * check if we aren't trying to free more * entries than the poll size */ if (o->used == 0) { log_puts("pool_del("); log_puts(o->name); log_puts("): pool is full\n"); panic(); } o->used--; /* * overwrite the entry with garbage so any attempt to use a * free entry will probably segfault */ buf = (unsigned char *)e; for (i = o->itemsize; i > 0; i--) *(buf++) = 0xdf; #endif /* * link on the free list */ e->next = o->first; o->first = e; } midish-1.4.0/pool.h010066400017510001751000000034001501104363400126160ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_POOL_H #define MIDISH_POOL_H /* * entry from the pool. Any real pool entry is cast to this structure * by the pool code. The actual size of a pool entry is in 'itemsize' * field of the pool structure */ struct poolent { struct poolent *next; }; /* * the pool is a linked list of 'itemnum' blocks of size * 'itemsize'. The pool name is for debugging prurposes only */ struct pool { unsigned char *data; /* memory block of the pool */ struct poolent *first; /* head of linked list */ #ifdef POOL_DEBUG unsigned maxused; /* max pool usage */ unsigned used; /* current pool usage */ unsigned newcnt; /* current items allocated */ #endif unsigned itemnum; /* total number of entries */ unsigned itemsize; /* size of a sigle entry */ char *name; /* name of the pool */ }; void pool_init(struct pool *, char *, unsigned, unsigned); void pool_done(struct pool *); void *pool_new(struct pool *); void pool_del(struct pool *, void *); #endif /* MIDISH_POOL_H */ midish-1.4.0/regress004077500017510001751000000000001501104363400130755ustar00alexalexmidish-1.4.0/regress/bad1.msh010066400017510001751000000003741501104363400144750ustar00alexalex{ songtrk t { track { 48 kat {0 0} 65 64 96 kat {0 0} 65 65 48 non {0 0} 65 66 48 kat {0 0} 65 67 48 kat {0 0} 65 68 48 non {0 0} 65 69 48 noff {0 0} 65 70 48 noff {0 0} 65 71 48 } } } midish-1.4.0/regress/bank.msh010066400017510001751000000001741501104363400145770ustar00alexalex{ songtrk t { track { 48 ctl {0 0} 0 65 48 48 ctl {0 0} 32 66 48 48 pc {0 0} 67 48 } } } midish-1.4.0/regress/bend1.msh010066400017510001751000000001631501104363400146530ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 48 } } } midish-1.4.0/regress/bend2.msh010066400017510001751000000001551501104363400146550ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_e1.msh010066400017510001751000000003061501104363400151560ustar00alexalex{ songtrk t2 { track { bend {0 0} 0 50 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_e2.msh010066400017510001751000000003141501104363400151560ustar00alexalex{ songtrk t2 { track { bend {0 0} 0 50 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_e3.msh010066400017510001751000000003221501104363400151560ustar00alexalex{ songtrk t2 { track { bend {0 0} 0 50 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_e4.msh010066400017510001751000000003301501104363400151560ustar00alexalex{ songtrk t2 { track { bend {0 0} 0 50 96 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_e5.msh010066400017510001751000000003361501104363400151650ustar00alexalex{ songtrk t2 { track { bend {0 0} 0 50 96 96 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_e6.msh010066400017510001751000000003441501104363400151650ustar00alexalex{ songtrk t2 { track { bend {0 0} 0 50 96 96 96 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_s1.msh010066400017510001751000000003521501104363400151750ustar00alexalex{ songtrk t2 { track { 96 bend {0 0} 0 50 96 96 96 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_s2.msh010066400017510001751000000003521501104363400151760ustar00alexalex{ songtrk t2 { track { 96 96 bend {0 0} 0 50 96 96 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_s3.msh010066400017510001751000000003521501104363400151770ustar00alexalex{ songtrk t2 { track { 96 96 96 bend {0 0} 0 50 96 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_s4.msh010066400017510001751000000003521501104363400152000ustar00alexalex{ songtrk t2 { track { 96 96 96 96 bend {0 0} 0 50 96 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_s5.msh010066400017510001751000000003521501104363400152010ustar00alexalex{ songtrk t2 { track { 96 96 96 96 96 bend {0 0} 0 50 96 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/bend_s6.msh010066400017510001751000000003521501104363400152020ustar00alexalex{ songtrk t2 { track { 96 96 96 96 96 96 bend {0 0} 0 50 96 bend {0 0} 0 64 } } songtrk t { track { 96 96 bend {0 0} 0 100 96 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_a0.cmd010066400017510001751000000001131501104363400153000ustar00alexalexload "bend1.msh" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_a0.res010066400017510001751000000001551501104363400153340ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_a1.cmd010066400017510001751000000001131501104363400153010ustar00alexalexload "bend1.msh" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_a1.res010066400017510001751000000002061501104363400153320ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 48 bend {0 0} 0 64 96 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_a2.cmd010066400017510001751000000001131501104363400153020ustar00alexalexload "bend1.msh" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_a2.res010066400017510001751000000001551501104363400153360ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_a3.cmd010066400017510001751000000001131501104363400153030ustar00alexalexload "bend1.msh" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_a3.res010066400017510001751000000001551501104363400153370ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_b0.cmd010066400017510001751000000001131501104363400153010ustar00alexalexload "bend2.msh" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_b0.res010066400017510001751000000001551501104363400153350ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_b1.cmd010066400017510001751000000001131501104363400153020ustar00alexalexload "bend2.msh" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_b1.res010066400017510001751000000000421501104363400153310ustar00alexalex{ songtrk t { track { } } } midish-1.4.0/regress/blank_b2.cmd010066400017510001751000000001131501104363400153030ustar00alexalexload "bend2.msh" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_b2.res010066400017510001751000000001551501104363400153370ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_b3.cmd010066400017510001751000000001131501104363400153040ustar00alexalexload "bend2.msh" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_b3.res010066400017510001751000000001551501104363400153400ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/blank_c0.cmd010066400017510001751000000001121501104363400153010ustar00alexalexload "note.msh" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_c0.res010066400017510001751000000000421501104363400153310ustar00alexalex{ songtrk t { track { } } } midish-1.4.0/regress/blank_c1.cmd010066400017510001751000000001121501104363400153020ustar00alexalexload "note.msh" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_c1.res010066400017510001751000000003641501104363400153410ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/blank_c2.cmd010066400017510001751000000001121501104363400153030ustar00alexalexload "note.msh" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_c2.res010066400017510001751000000003641501104363400153420ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/blank_c3.cmd010066400017510001751000000001121501104363400153040ustar00alexalexload "note.msh" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_c3.res010066400017510001751000000003641501104363400153430ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/blank_d0.cmd010066400017510001751000000001111501104363400153010ustar00alexalexload "ctl.msh" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_d0.res010066400017510001751000000000721501104363400153350ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/blank_d1.cmd010066400017510001751000000001111501104363400153020ustar00alexalexload "ctl.msh" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_d1.res010066400017510001751000000000421501104363400153330ustar00alexalex{ songtrk t { track { } } } midish-1.4.0/regress/blank_d2.cmd010066400017510001751000000001111501104363400153030ustar00alexalexload "ctl.msh" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_d2.res010066400017510001751000000000721501104363400153370ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/blank_d3.cmd010066400017510001751000000001111501104363400153040ustar00alexalexload "ctl.msh" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/blank_d3.res010066400017510001751000000000721501104363400153400ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/check.cmd010066400017510001751000000000771501104363400147170ustar00alexalexload "bad1.msh" ct t tcheck g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/check.res010066400017510001751000000002171501104363400147410ustar00alexalex{ songtrk t { track { 192 non {0 0} 65 66 48 kat {0 0} 65 67 48 kat {0 0} 65 68 144 noff {0 0} 65 71 48 } } } midish-1.4.0/regress/confev_0.cmd010066400017510001751000000003341501104363400153350ustar00alexalexonew c {1 9} oaddev {ctl c 70 1} oaddev {ctl c 71 2} oaddev {xpc c 255 64} oaddev {xctl c 7 1023} oaddev {nrpn c 1 10} oaddev {rpn c 2 20} oaddev {nrpn c 3 30} oaddev {nrpn c 4 40} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/confev_0.res010066400017510001751000000003071501104363400153630ustar00alexalex{ songchan c { chan {1 9} conf { ctl {1 9} 70 1 ctl {1 9} 71 2 xpc {1 9} 64 255 xctl {1 9} 7 1023 nrpn {1 9} 1 10 rpn {1 9} 2 20 nrpn {1 9} 3 30 nrpn {1 9} 4 40 } } } midish-1.4.0/regress/copy_a0.cmd010066400017510001751000000001751501104363400151730ustar00alexalexload "bend1.msh" tnew u ct t; g 0; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_a0.res010066400017510001751000000001241501104363400152130ustar00alexalex{ songtrk u { track { 48 bend {0 0} 0 65 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/copy_a1.cmd010066400017510001751000000001751501104363400151740ustar00alexalexload "bend1.msh" tnew u ct t; g 1; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_a1.res010066400017510001751000000001471501104363400152210ustar00alexalex{ songtrk u { track { bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/copy_a2.cmd010066400017510001751000000001751501104363400151750ustar00alexalexload "bend1.msh" tnew u ct t; g 2; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_a2.res010066400017510001751000000001161501104363400152160ustar00alexalex{ songtrk u { track { bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/copy_a3.cmd010066400017510001751000000001751501104363400151760ustar00alexalexload "bend1.msh" tnew u ct t; g 3; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_a3.res010066400017510001751000000000421501104363400152150ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_b0.cmd010066400017510001751000000001751501104363400151740ustar00alexalexload "bend2.msh" tnew u ct t; g 0; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_b0.res010066400017510001751000000000421501104363400152130ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_b1.cmd010066400017510001751000000001751501104363400151750ustar00alexalexload "bend2.msh" tnew u ct t; g 1; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_b1.res010066400017510001751000000001471501104363400152220ustar00alexalex{ songtrk u { track { bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/copy_b2.cmd010066400017510001751000000001751501104363400151760ustar00alexalexload "bend2.msh" tnew u ct t; g 2; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_b2.res010066400017510001751000000000421501104363400152150ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_b3.cmd010066400017510001751000000001751501104363400151770ustar00alexalexload "bend2.msh" tnew u ct t; g 3; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_b3.res010066400017510001751000000000421501104363400152160ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_c0.cmd010066400017510001751000000001741501104363400151740ustar00alexalexload "note.msh" tnew u ct t; g 0; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_c0.res010066400017510001751000000003641501104363400152230ustar00alexalex{ songtrk u { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/copy_c1.cmd010066400017510001751000000001741501104363400151750ustar00alexalexload "note.msh" tnew u ct t; g 1; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_c1.res010066400017510001751000000000421501104363400152150ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_c2.cmd010066400017510001751000000001741501104363400151760ustar00alexalexload "note.msh" tnew u ct t; g 2; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_c2.res010066400017510001751000000000421501104363400152160ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_c3.cmd010066400017510001751000000001741501104363400151770ustar00alexalexload "note.msh" tnew u ct t; g 3; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_c3.res010066400017510001751000000000421501104363400152170ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_d0.cmd010066400017510001751000000001731501104363400151740ustar00alexalexload "ctl.msh" tnew u ct t; g 0; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_d0.res010066400017510001751000000000421501104363400152150ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_d1.cmd010066400017510001751000000001731501104363400151750ustar00alexalexload "ctl.msh" tnew u ct t; g 1; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_d1.res010066400017510001751000000000641501104363400152220ustar00alexalex{ songtrk u { track { ctl {0 0} 7 65 } } } midish-1.4.0/regress/copy_d2.cmd010066400017510001751000000001731501104363400151760ustar00alexalexload "ctl.msh" tnew u ct t; g 2; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_d2.res010066400017510001751000000000421501104363400152170ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/copy_d3.cmd010066400017510001751000000001731501104363400151770ustar00alexalexload "ctl.msh" tnew u ct t; g 3; sel 1; tcopy; ct u; g 0; tpaste; g 0; sel 0 ct t; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/copy_d3.res010066400017510001751000000000421501104363400152200ustar00alexalex{ songtrk u { track { } } } midish-1.4.0/regress/ctl.msh010066400017510001751000000000721501104363400144430ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/ctl_e1.msh010066400017510001751000000002031501104363400150240ustar00alexalex{ songtrk t2 { track { 96 ctl {0 0} 7 50 } } songtrk t { track { ctl {0 0} 7 50 96 ctl {0 0} 7 64 } } } midish-1.4.0/regress/cut_a0.cmd010066400017510001751000000001131501104363400150040ustar00alexalexload "bend1.msh" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_a0.res010066400017510001751000000001471501104363400150410ustar00alexalex{ songtrk t { track { bend {0 0} 0 65 48 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/cut_a1.cmd010066400017510001751000000001131501104363400150050ustar00alexalexload "bend1.msh" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_a1.res010066400017510001751000000001551501104363400150410ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/cut_a2.cmd010066400017510001751000000001131501104363400150060ustar00alexalexload "bend1.msh" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_a2.res010066400017510001751000000001551501104363400150420ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/cut_a3.cmd010066400017510001751000000001131501104363400150070ustar00alexalexload "bend1.msh" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_a3.res010066400017510001751000000001551501104363400150430ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/cut_b0.cmd010066400017510001751000000001131501104363400150050ustar00alexalexload "bend2.msh" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_b0.res010066400017510001751000000001601501104363400150350ustar00alexalex{ songtrk t { mute 0 track { bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/cut_b1.cmd010066400017510001751000000001131501104363400150060ustar00alexalexload "bend2.msh" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_b1.res010066400017510001751000000000421501104363400150350ustar00alexalex{ songtrk t { track { } } } midish-1.4.0/regress/cut_b2.cmd010066400017510001751000000001131501104363400150070ustar00alexalexload "bend2.msh" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_b2.res010066400017510001751000000001551501104363400150430ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/cut_b3.cmd010066400017510001751000000001131501104363400150100ustar00alexalexload "bend2.msh" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_b3.res010066400017510001751000000001551501104363400150440ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/cut_c0.cmd010066400017510001751000000001121501104363400150050ustar00alexalexload "note.msh" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_c0.res010066400017510001751000000000421501104363400150350ustar00alexalex{ songtrk t { track { } } } midish-1.4.0/regress/cut_c1.cmd010066400017510001751000000001121501104363400150060ustar00alexalexload "note.msh" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_c1.res010066400017510001751000000003641501104363400150450ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/cut_c2.cmd010066400017510001751000000001121501104363400150070ustar00alexalexload "note.msh" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_c2.res010066400017510001751000000003641501104363400150460ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/cut_c3.cmd010066400017510001751000000001121501104363400150100ustar00alexalexload "note.msh" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_c3.res010066400017510001751000000003641501104363400150470ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/cut_d0.cmd010066400017510001751000000001111501104363400150050ustar00alexalexload "ctl.msh" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_d0.res010066400017510001751000000000641501104363400150420ustar00alexalex{ songtrk t { track { ctl {0 0} 7 65 } } } midish-1.4.0/regress/cut_d1.cmd010066400017510001751000000001111501104363400150060ustar00alexalexload "ctl.msh" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_d1.res010066400017510001751000000000421501104363400150370ustar00alexalex{ songtrk t { track { } } } midish-1.4.0/regress/cut_d2.cmd010066400017510001751000000001111501104363400150070ustar00alexalexload "ctl.msh" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_d2.res010066400017510001751000000000721501104363400150430ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/cut_d3.cmd010066400017510001751000000001111501104363400150100ustar00alexalexload "ctl.msh" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/cut_d3.res010066400017510001751000000000721501104363400150440ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/filt.msh010066400017510001751000000002411501104363400146150ustar00alexalex{ songfilt f { filt { evmap any {7 4..15} > any {3 0..11} evmap any {7 9} > any {3 8} evmap note {7 9} 0..65 > note {3 8} 10..75 } } curfilt f } midish-1.4.0/regress/fmap_a0.cmd010066400017510001751000000000551501104363400151410ustar00alexalexload "filt.msh" fmap {any {0 0}} {any {1 1}} midish-1.4.0/regress/fmap_a0.res010066400017510001751000000003071501104363400151670ustar00alexalex{ songfilt f { filt { evmap any {7 4..15} > any {3 0..11} evmap any {7 9} > any {3 8} evmap note {7 9} 0..65 > note {3 8} 10..75 evmap any {0 0} > any {1 1} } } curfilt f } midish-1.4.0/regress/fmap_a1.cmd010066400017510001751000000000551501104363400151420ustar00alexalexload "filt.msh" fmap {any {0 0}} {any {3 8}} midish-1.4.0/regress/fmap_a1.res010066400017510001751000000003001501104363400151610ustar00alexalex{ songfilt f { filt { evmap any {7 4..15} > any {3 0..11} evmap any {7 9} > any {3 8} evmap note {7 9} 0..65 > note {3 8} 10..75 evmap any {0 0} > any {3 8} } } curfilt f } midish-1.4.0/regress/fmap_a3.cmd010066400017510001751000000000551501104363400151440ustar00alexalexload "filt.msh" fmap {any {7 9}} {any {0 1}} midish-1.4.0/regress/fmap_a3.res010066400017510001751000000002221501104363400151660ustar00alexalex{ songfilt f { filt { evmap any {7 4..15} > any {3 0..11} evmap any {7 9} > any {3 8} evmap any {7 9} > any {0 1} } } curfilt f } midish-1.4.0/regress/fnew.cmd010066400017510001751000000001661501104363400146000ustar00alexalexfnew f fmap {any {7 4..15}} {any {3 0..11}} fmap {any {7 9}} {any {3 8}} fmap {note {7 9} 0..65} {note {3 8} 10..75} midish-1.4.0/regress/fnew.res010066400017510001751000000002411501104363400146200ustar00alexalex{ songfilt f { filt { evmap any {7 4..15} > any {3 0..11} evmap any {7 9} > any {3 8} evmap note {7 9} 0..65 > note {3 8} 10..75 } } curfilt f } midish-1.4.0/regress/help.cmd010066400017510001751000000000411501104363400145610ustar00alexalexfor i in [builtinlist] { h $i; } midish-1.4.0/regress/help.res010066400017510001751000000000031501104363400146050ustar00alexalex{} midish-1.4.0/regress/insert_a0.cmd010066400017510001751000000001061501104363400155170ustar00alexalexload "bend1.msh" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_a0.res010066400017510001751000000001561501104363400155520ustar00alexalex{ songtrk t { track { 144 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_a1.cmd010066400017510001751000000001061501104363400155200ustar00alexalexload "bend1.msh" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_a1.res010066400017510001751000000002371501104363400155530ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 48 bend {0 0} 0 64 96 bend {0 0} 0 65 48 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_a2.cmd010066400017510001751000000001061501104363400155210ustar00alexalexload "bend1.msh" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_a2.res010066400017510001751000000002371501104363400155540ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 48 bend {0 0} 0 64 96 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_a3.cmd010066400017510001751000000001061501104363400155220ustar00alexalexload "bend1.msh" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_a3.res010066400017510001751000000001551501104363400155540ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_b0.cmd010066400017510001751000000001061501104363400155200ustar00alexalexload "bend2.msh" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_b0.res010066400017510001751000000001561501104363400155530ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_b1.cmd010066400017510001751000000001061501104363400155210ustar00alexalexload "bend2.msh" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_b1.res010066400017510001751000000001601501104363400155470ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_b2.cmd010066400017510001751000000001061501104363400155220ustar00alexalexload "bend2.msh" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_b2.res010066400017510001751000000001551501104363400155540ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_b3.cmd010066400017510001751000000001061501104363400155230ustar00alexalexload "bend2.msh" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_b3.res010066400017510001751000000001551501104363400155550ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.4.0/regress/insert_c0.cmd010066400017510001751000000001051501104363400155200ustar00alexalexload "note.msh" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_c0.res010066400017510001751000000003651501104363400155560ustar00alexalex{ songtrk t { track { 144 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/insert_c1.cmd010066400017510001751000000001051501104363400155210ustar00alexalexload "note.msh" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_c1.res010066400017510001751000000003641501104363400155560ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/insert_c2.cmd010066400017510001751000000001051501104363400155220ustar00alexalexload "note.msh" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_c2.res010066400017510001751000000003641501104363400155570ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/insert_c3.cmd010066400017510001751000000001051501104363400155230ustar00alexalexload "note.msh" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_c3.res010066400017510001751000000002131501104363400155510ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 } } } midish-1.4.0/regress/insert_d0.cmd010066400017510001751000000001041501104363400155200ustar00alexalexload "ctl.msh" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_d0.res010066400017510001751000000000731501104363400155530ustar00alexalex{ songtrk t { track { 192 ctl {0 0} 7 65 } } } midish-1.4.0/regress/insert_d1.cmd010066400017510001751000000001041501104363400155210ustar00alexalexload "ctl.msh" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_d1.res010066400017510001751000000000731501104363400155540ustar00alexalex{ songtrk t { track { 192 ctl {0 0} 7 65 } } } midish-1.4.0/regress/insert_d2.cmd010066400017510001751000000001041501104363400155220ustar00alexalexload "ctl.msh" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_d2.res010066400017510001751000000000721501104363400155540ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/insert_d3.cmd010066400017510001751000000001041501104363400155230ustar00alexalexload "ctl.msh" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/insert_d3.res010066400017510001751000000000721501104363400155550ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.4.0/regress/mcut_a0.cmd010066400017510001751000000000541501104363400151650ustar00alexalexload "sign2.msh" g 0 sel 1 mcut g 0; sel 0 midish-1.4.0/regress/mcut_a0.res010066400017510001751000000001011501104363400152040ustar00alexalex{ meta { tempo 400000 timesig 2 24 96 timesig 4 24 } } midish-1.4.0/regress/mcut_a1.cmd010066400017510001751000000000541501104363400151660ustar00alexalexload "sign2.msh" g 1 sel 1 mcut g 0; sel 0 midish-1.4.0/regress/mcut_a1.res010066400017510001751000000001251501104363400152130ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 timesig 2 24 48 timesig 4 24 } } midish-1.4.0/regress/mcut_a2.cmd010066400017510001751000000000541501104363400151670ustar00alexalexload "sign2.msh" g 1 sel 2 mcut g 0; sel 0 midish-1.4.0/regress/mcut_a2.res010066400017510001751000000000551501104363400152160ustar00alexalex{ meta { tempo 400000 timesig 4 24 } } midish-1.4.0/regress/mdup_a0.cmd010066400017510001751000000000561501104363400151640ustar00alexalexload "sign3.msh" g 0 sel 1 mdup 6 g 0; sel 0 midish-1.4.0/regress/mdup_a0.res010066400017510001751000000002611501104363400152100ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 576 timesig 6 12 144 timesig 4 24 } } midish-1.4.0/regress/mdup_a1.cmd010066400017510001751000000000561501104363400151650ustar00alexalexload "sign3.msh" g 0 sel 1 mdup 7 g 0; sel 0 midish-1.4.0/regress/mdup_a1.res010066400017510001751000000002611501104363400152110ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 576 timesig 6 12 144 timesig 4 24 } } midish-1.4.0/regress/mdup_a2.cmd010066400017510001751000000000561501104363400151660ustar00alexalexload "sign3.msh" g 0 sel 1 mdup 8 g 0; sel 0 midish-1.4.0/regress/mdup_a2.res010066400017510001751000000003661501104363400152200ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 # 3:0:0 -> 1 + 5 meas. 4/4 timesig 6 12 72 timesig 4 24 96 timesig 6 12 72 timesig 4 24 } } midish-1.4.0/regress/mdup_a3.cmd010066400017510001751000000000561501104363400151670ustar00alexalexload "sign3.msh" g 0 sel 1 mdup 9 g 0; sel 0 midish-1.4.0/regress/mdup_a3.res010066400017510001751000000003471501104363400152200ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 # 3:0:0 -> 1 + 5 meas. 4/4 timesig 6 12 144 # 8:0:0 -> 2 meas 6/8 timesig 4 24 } } midish-1.4.0/regress/mdup_a4.cmd010066400017510001751000000000571501104363400151710ustar00alexalexload "sign3.msh" g 0 sel 1 mdup 10 g 0; sel 0 midish-1.4.0/regress/mdup_a4.res010066400017510001751000000003471501104363400152210ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 # 3:0:0 -> 1 + 5 meas. 4/4 timesig 6 12 144 # 8:0:0 -> 2 meas 6/8 timesig 4 24 } } midish-1.4.0/regress/mdup_b0.cmd010066400017510001751000000000561501104363400151650ustar00alexalexload "sign3.msh" g 0 sel 2 mdup 5 g 0; sel 0 midish-1.4.0/regress/mdup_b0.res010066400017510001751000000003311501104363400152070ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 timesig 2 24 48 timesig 4 24 96 timesig 6 12 144 timesig 4 24 } } midish-1.4.0/regress/mdup_b1.cmd010066400017510001751000000000561501104363400151660ustar00alexalexload "sign3.msh" g 0 sel 2 mdup 6 g 0; sel 0 midish-1.4.0/regress/mdup_b1.res010066400017510001751000000003351501104363400152140ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 576 timesig 2 24 48 timesig 6 12 144 # 8:0:0 -> 2 meas 6/8 timesig 4 24 } } midish-1.4.0/regress/mdup_b2.cmd010066400017510001751000000000561501104363400151670ustar00alexalexload "sign3.msh" g 0 sel 2 mdup 7 g 0; sel 0 midish-1.4.0/regress/mdup_b2.res010066400017510001751000000004121501104363400152110ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 # 3:0:0 -> 1 + 5 meas. 4/4 timesig 6 12 72 timesig 4 24 96 timesig 2 24 48 timesig 6 12 72 timesig 4 24 } } midish-1.4.0/regress/mdup_b3.cmd010066400017510001751000000000561501104363400151700ustar00alexalexload "sign3.msh" g 0 sel 2 mdup 8 g 0; sel 0 midish-1.4.0/regress/mdup_b3.res010066400017510001751000000004171501104363400152170ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 # 3:0:0 -> 1 + 5 meas. 4/4 timesig 6 12 144 # 8:0:0 -> 2 meas 6/8 timesig 4 24 96 timesig 2 24 48 timesig 4 24 } } midish-1.4.0/regress/mdup_b4.cmd010066400017510001751000000000561501104363400151710ustar00alexalexload "sign3.msh" g 0 sel 2 mdup 9 g 0; sel 0 midish-1.4.0/regress/mdup_b4.res010066400017510001751000000004201501104363400152120ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 # 3:0:0 -> 1 + 5 meas. 4/4 timesig 6 12 144 # 8:0:0 -> 2 meas 6/8 timesig 4 24 192 timesig 2 24 48 timesig 4 24 } } midish-1.4.0/regress/mergebend_e1.cmd010066400017510001751000000001221501104363400161460ustar00alexalexload "bend_e1.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_e1.res010066400017510001751000000002341501104363400162000ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 96 bend {0 0} 0 64 96 bend {0 0} 0 100 192 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_e2.cmd010066400017510001751000000001221501104363400161470ustar00alexalexload "bend_e2.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_e2.res010066400017510001751000000002041501104363400161760ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 192 bend {0 0} 0 100 192 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_e3.cmd010066400017510001751000000001221501104363400161500ustar00alexalexload "bend_e3.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_e3.res010066400017510001751000000002031501104363400161760ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 288 bend {0 0} 0 100 96 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_e4.cmd010066400017510001751000000001221501104363400161510ustar00alexalexload "bend_e4.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_e4.res010066400017510001751000000001511501104363400162010ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 384 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_e5.cmd010066400017510001751000000001221501104363400161520ustar00alexalexload "bend_e5.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_e5.res010066400017510001751000000001201501104363400161760ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 480 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_e6.cmd010066400017510001751000000001221501104363400161530ustar00alexalexload "bend_e6.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_e6.res010066400017510001751000000001171501104363400162050ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 576 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_s1.cmd010066400017510001751000000001221501104363400161640ustar00alexalexload "bend_s1.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_s1.res010066400017510001751000000001251501104363400162150ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 50 576 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_s2.cmd010066400017510001751000000001221501104363400161650ustar00alexalexload "bend_s2.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_s2.res010066400017510001751000000001261501104363400162170ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 50 480 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_s3.cmd010066400017510001751000000001221501104363400161660ustar00alexalexload "bend_s3.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_s3.res010066400017510001751000000001601501104363400162160ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 100 96 bend {0 0} 0 50 384 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_s4.cmd010066400017510001751000000001221501104363400161670ustar00alexalexload "bend_s4.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_s4.res010066400017510001751000000001611501104363400162200ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 100 192 bend {0 0} 0 50 288 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_s5.cmd010066400017510001751000000001221501104363400161700ustar00alexalexload "bend_s5.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_s5.res010066400017510001751000000002131501104363400162170ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 100 192 bend {0 0} 0 124 96 bend {0 0} 0 50 192 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergebend_s6.cmd010066400017510001751000000001221501104363400161710ustar00alexalexload "bend_s6.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergebend_s6.res010066400017510001751000000002431501104363400162230ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 100 192 bend {0 0} 0 124 96 bend {0 0} 0 64 96 bend {0 0} 0 50 96 bend {0 0} 0 64 } } } midish-1.4.0/regress/mergectl_e1.cmd010066400017510001751000000001211501104363400160170ustar00alexalexload "ctl_e1.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergectl_e1.res010066400017510001751000000000641501104363400160530ustar00alexalex{ songtrk t { track { ctl {0 0} 7 50 } } } midish-1.4.0/regress/mergenote_e1.cmd010066400017510001751000000001221501104363400162030ustar00alexalexload "note_e1.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_e1.res010066400017510001751000000002371501104363400162400ustar00alexalex{ songtrk t { track { non {0 0} 65 50 96 noff {0 0} 65 50 96 non {0 0} 65 100 192 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/mergenote_e2.cmd010066400017510001751000000001221501104363400162040ustar00alexalexload "note_e2.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_e2.res010066400017510001751000000001201501104363400162300ustar00alexalex{ songtrk t { track { non {0 0} 65 50 192 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_e3.cmd010066400017510001751000000001221501104363400162050ustar00alexalexload "note_e3.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_e3.res010066400017510001751000000001201501104363400162310ustar00alexalex{ songtrk t { track { non {0 0} 65 50 288 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_e4.cmd010066400017510001751000000001221501104363400162060ustar00alexalexload "note_e4.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_e4.res010066400017510001751000000001201501104363400162320ustar00alexalex{ songtrk t { track { non {0 0} 65 50 384 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_e5.cmd010066400017510001751000000001221501104363400162070ustar00alexalexload "note_e5.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_e5.res010066400017510001751000000001201501104363400162330ustar00alexalex{ songtrk t { track { non {0 0} 65 50 480 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_e6.cmd010066400017510001751000000001221501104363400162100ustar00alexalexload "note_e6.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_e6.res010066400017510001751000000001201501104363400162340ustar00alexalex{ songtrk t { track { non {0 0} 65 50 576 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_s1.cmd010066400017510001751000000001221501104363400162210ustar00alexalexload "note_s1.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_s1.res010066400017510001751000000001261501104363400162530ustar00alexalex{ songtrk t { track { 96 non {0 0} 65 50 576 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_s2.cmd010066400017510001751000000001221501104363400162220ustar00alexalexload "note_s2.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_s2.res010066400017510001751000000001271501104363400162550ustar00alexalex{ songtrk t { track { 192 non {0 0} 65 50 480 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_s3.cmd010066400017510001751000000001221501104363400162230ustar00alexalexload "note_s3.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_s3.res010066400017510001751000000001271501104363400162560ustar00alexalex{ songtrk t { track { 288 non {0 0} 65 50 384 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_s4.cmd010066400017510001751000000001221501104363400162240ustar00alexalexload "note_s4.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_s4.res010066400017510001751000000001271501104363400162570ustar00alexalex{ songtrk t { track { 384 non {0 0} 65 50 288 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_s5.cmd010066400017510001751000000001221501104363400162250ustar00alexalexload "note_s5.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_s5.res010066400017510001751000000002411501104363400162550ustar00alexalex{ songtrk t { track { 192 non {0 0} 65 100 192 kat {0 0} 65 124 96 noff {0 0} 65 100 non {0 0} 65 50 192 noff {0 0} 65 50 } } } midish-1.4.0/regress/mergenote_s6.cmd010066400017510001751000000001221501104363400162260ustar00alexalexload "note_s6.msh" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/mergenote_s6.res010066400017510001751000000002461501104363400162630ustar00alexalex{ songtrk t { track { 192 non {0 0} 65 100 192 kat {0 0} 65 124 96 noff {0 0} 65 100 96 non {0 0} 65 50 96 noff {0 0} 65 50 } } } midish-1.4.0/regress/mins_a0.cmd010066400017510001751000000000431501104363400151610ustar00alexalexload "sign2.msh" g 0 mins 1 {6 8} midish-1.4.0/regress/mins_a0.res010066400017510001751000000002451501104363400152130ustar00alexalex{ meta { timesig 6 12 tempo 400000 72 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 } curlen 1 } midish-1.4.0/regress/mins_a1.cmd010066400017510001751000000000471501104363400151660ustar00alexalexload "sign2.msh" g 1 mins 1 {6 8} g 0 midish-1.4.0/regress/mins_a1.res010066400017510001751000000001631501104363400152130ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 timesig 6 12 72 timesig 2 24 96 timesig 4 24 } curlen 1 } midish-1.4.0/regress/mins_a2.cmd010066400017510001751000000000471501104363400151670ustar00alexalexload "sign2.msh" g 2 mins 1 {6 8} g 0 midish-1.4.0/regress/mins_a2.res010066400017510001751000000002071501104363400152130ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 timesig 2 24 48 timesig 6 12 72 timesig 2 24 48 timesig 4 24 } curlen 1 } midish-1.4.0/regress/mins_a3.cmd010066400017510001751000000000471501104363400151700ustar00alexalexload "sign2.msh" g 3 mins 1 {6 8} g 0 midish-1.4.0/regress/mins_a3.res010066400017510001751000000002451501104363400152160ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 6 12 72 timesig 4 24 } curlen 1 } midish-1.4.0/regress/mins_a4.cmd010066400017510001751000000000471501104363400151710ustar00alexalexload "sign2.msh" g 4 mins 1 {6 8} g 0 midish-1.4.0/regress/mins_a4.res010066400017510001751000000002711501104363400152160ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 96 timesig 6 12 72 timesig 4 24 } curlen 1 } midish-1.4.0/regress/note.msh010066400017510001751000000002211501104363400146220ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 48 kat {0 0} 65 123 96 kat {0 0} 65 124 48 noff {0 0} 65 100 48 } } } midish-1.4.0/regress/note2.msh010066400017510001751000000001751501104363400147140ustar00alexalex{ songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_e1.msh010066400017510001751000000003111501104363400152070ustar00alexalex{ songtrk t2 { track { non {0 0} 65 50 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_e2.msh010066400017510001751000000003171501104363400152160ustar00alexalex{ songtrk t2 { track { non {0 0} 65 50 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_e3.msh010066400017510001751000000003251501104363400152160ustar00alexalex{ songtrk t2 { track { non {0 0} 65 50 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_e4.msh010066400017510001751000000003331501104363400152160ustar00alexalex{ songtrk t2 { track { non {0 0} 65 50 96 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_e5.msh010066400017510001751000000003411501104363400152160ustar00alexalex{ songtrk t2 { track { non {0 0} 65 50 96 96 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_e6.msh010066400017510001751000000003471501104363400152250ustar00alexalex{ songtrk t2 { track { non {0 0} 65 50 96 96 96 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_s1.msh010066400017510001751000000003551501104363400152350ustar00alexalex{ songtrk t2 { track { 96 non {0 0} 65 50 96 96 96 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_s2.msh010066400017510001751000000003551501104363400152360ustar00alexalex{ songtrk t2 { track { 96 96 non {0 0} 65 50 96 96 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_s3.msh010066400017510001751000000003551501104363400152370ustar00alexalex{ songtrk t2 { track { 96 96 96 non {0 0} 65 50 96 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_s4.msh010066400017510001751000000003551501104363400152400ustar00alexalex{ songtrk t2 { track { 96 96 96 96 non {0 0} 65 50 96 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_s5.msh010066400017510001751000000003551501104363400152410ustar00alexalex{ songtrk t2 { track { 96 96 96 96 96 non {0 0} 65 50 96 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/note_s6.msh010066400017510001751000000003551501104363400152420ustar00alexalex{ songtrk t2 { track { 96 96 96 96 96 96 non {0 0} 65 50 96 noff {0 0} 65 50 } } songtrk t { track { 96 96 non {0 0} 65 100 96 96 kat {0 0} 65 124 96 noff {0 0} 65 100 } } } midish-1.4.0/regress/quant.cmd010066400017510001751000000001471501104363400147700ustar00alexalexload "quant.msh" g 0 ct t sel 16 setq 24 tquanta 100 sel 0 setq nil g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/quant.msh010066400017510001751000000006371501104363400150200ustar00alexalex{ songtrk t { track { 48 non {0 0} 60 100 48 noff {0 0} 60 100 49 non {0 0} 61 100 47 noff {0 0} 61 100 50 non {0 0} 62 100 46 noff {0 0} 62 100 51 non {0 0} 63 100 45 noff {0 0} 63 100 47 non {0 0} 59 100 49 noff {0 0} 59 100 46 non {0 0} 58 100 50 noff {0 0} 58 100 45 non {0 0} 57 100 51 noff {0 0} 57 100 } } } midish-1.4.0/regress/quant.res010066400017510001751000000006251501104363400150170ustar00alexalex{ songtrk t { track { 48 non {0 0} 60 100 48 noff {0 0} 60 100 48 non {0 0} 61 100 48 noff {0 0} 61 100 52 non {0 0} 62 100 44 noff {0 0} 62 100 52 non {0 0} 63 100 44 noff {0 0} 63 100 48 non {0 0} 59 100 48 noff {0 0} 59 100 48 non {0 0} 58 100 48 noff {0 0} 58 100 44 non {0 0} 57 100 52 noff {0 0} 57 100 } } } midish-1.4.0/regress/quant_m.msh010066400017510001751000000003651501104363400153320ustar00alexalex{ songtrk t { track { 1 non {0 0} 60 100 94 non {0 0} 61 100 98 non {0 0} 62 100 94 non {0 0} 63 100 98 noff {0 0} 60 100 94 noff {0 0} 61 100 98 noff {0 0} 62 100 94 noff {0 0} 63 100 } } } midish-1.4.0/regress/quant_ovr.msh010066400017510001751000000002321501104363400156750ustar00alexalex{ songtrk t { track { 48 4 non {0 0} 60 100 40 noff {0 0} 60 100 4 4 non {0 0} 60 50 40 noff {0 0} 60 100 4 } } } midish-1.4.0/regress/quanta_ovr.cmd010066400017510001751000000001521501104363400160130ustar00alexalexload "quant_ovr.msh" g 0 ct t sel 16 setq 1 tquanta 100 sel 0 setq nil g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/quanta_ovr.res010066400017510001751000000001711501104363400160420ustar00alexalex{ songtrk t { track { 96 non {0 0} 60 100 noff {0 0} 60 100 non {0 0} 60 50 noff {0 0} 60 100 } } } midish-1.4.0/regress/quantf.cmd010066400017510001751000000001471501104363400151360ustar00alexalexload "quant.msh" g 0 ct t sel 16 setq 24 tquantf 100 sel 0 setq nil g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/quantf.res010066400017510001751000000006251501104363400151650ustar00alexalex{ songtrk t { track { 48 non {0 0} 60 100 48 noff {0 0} 60 100 48 non {0 0} 61 100 47 noff {0 0} 61 100 53 non {0 0} 62 100 46 noff {0 0} 62 100 50 non {0 0} 63 100 45 noff {0 0} 63 100 47 non {0 0} 59 100 49 noff {0 0} 59 100 47 non {0 0} 58 100 50 noff {0 0} 58 100 42 non {0 0} 57 100 51 noff {0 0} 57 100 } } } midish-1.4.0/regress/quantf_m.cmd010066400017510001751000000001471501104363400154520ustar00alexalexload "quant_m.msh" ct t g 2 sel 3 setq 4 tquantf 100 sel 0 setq nil g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/quantf_m.res010066400017510001751000000003651501104363400155020ustar00alexalex{ songtrk t { track { 1 non {0 0} 60 100 94 non {0 0} 61 100 97 non {0 0} 62 100 96 non {0 0} 63 100 97 noff {0 0} 60 100 94 noff {0 0} 61 100 97 noff {0 0} 62 100 96 noff {0 0} 63 100 } } } midish-1.4.0/regress/quantf_ovr.cmd010066400017510001751000000001521501104363400160200ustar00alexalexload "quant_ovr.msh" g 0 ct t sel 16 setq 1 tquantf 100 sel 0 setq nil g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/quantf_ovr.res010066400017510001751000000001271501104363400160500ustar00alexalex{ songtrk t { track { 96 non {0 0} 60 100 40 noff {0 0} 60 100 } } } midish-1.4.0/regress/run-test010077500017510001751000000017561501104363400146710ustar00alexalex#!/bin/sh # # For each command-line argument, run the corresponding test as follows: # # - Load the expected result $testname.res and save it in $testname.tmp1 # # - Run $testname.cmd and save the actual result in $testname.tmp2 # # - Check that there are no differences between the actual and the # expected results. If there are, the resulting $testname.diff and # and $testname.log files are kept. # if [ -z "$*" ]; then set -- *.cmd fi echo 1..$# # # loop over all tests # for i; do i=${i%.cmd} (echo load \"$i.res\"\; \ save \"$i.tmp1\"\; \ reset \; \ exec \"$i.cmd\"\; \ save \"$i.tmp2\"\; \ | ../midish -b >$i.log 2>&1 ) \ && \ diff -u $i.tmp1 $i.tmp2 >$i.diff 2>>$i.log if [ "$?" -eq 0 ]; then echo ok $i rm -- $i.tmp1 $i.tmp2 $i.diff $i.log else echo not ok $i failed="$failed $i" fi done # # print summary, and set exit code # echo >&2 if [ -n "$failed" ]; then echo Tests failed: $failed >&2 exit 1 else echo Tests passed exit 0 fi midish-1.4.0/regress/sign.msh010066400017510001751000000000671501104363400146250ustar00alexalex{ meta { 96 timesig 3 24 48 timesig 4 24 } } midish-1.4.0/regress/sign2.msh010066400017510001751000000002071501104363400147030ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 } } midish-1.4.0/regress/sign3.msh010066400017510001751000000003471501104363400147110ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 # 0:0:0 -> 1 meas. 4/4 timesig 2 24 96 # 1:0:0 -> 2 meas. 2/4 timesig 4 24 480 # 3:0:0 -> 1 + 5 meas. 4/4 timesig 6 12 144 # 8:0:0 -> 2 meas 6/8 timesig 4 24 } } midish-1.4.0/regress/tempo.cmd010066400017510001751000000001141501104363400147560ustar00alexalexg 0 t 240 g 1 t 120 g 2 t 240 g 1 t 240 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tempo.res010066400017510001751000000000631501104363400150070ustar00alexalex{ meta { timesig 4 24 tempo 250000 192 } } midish-1.4.0/regress/tevmap.msh010066400017510001751000000011611501104363400151550ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 ctl {0 0} 7 64 48 ctl {0 0} 7 65 48 ctl {0 1} 10 64 48 ctl {0 1} 10 65 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a0.cmd010066400017510001751000000001521501104363400155100ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {note {0 0}} {note {1 1}} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a0.res010066400017510001751000000011721501104363400155410ustar00alexalex{ songtrk t { track { 48 non {1 1} 65 100 96 kat {1 1} 65 123 48 noff {1 1} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a1.cmd010066400017510001751000000001641501104363400155140ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {note {} 66..106} {note {} 60..100} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a1.res010066400017510001751000000011721501104363400155420ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 60 100 96 kat {0 1} 60 123 48 noff {0 1} 60 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a2.cmd010066400017510001751000000001501501104363400155100ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {ctl {0 0}} {ctl {1 1}} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a2.res010066400017510001751000000011721501104363400155430ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {1 1} 7 8192 48 xctl {1 1} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a3.cmd010066400017510001751000000001561501104363400155170ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {ctl {} 7..8} {ctl {} 10..11} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a3.res010066400017510001751000000011741501104363400155460ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 10 8192 48 xctl {0 0} 10 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a4.cmd010066400017510001751000000001501501104363400155120ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {cat {0 0}} {cat {1 1}} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/time.msh010066400017510001751000000001361501104363400146200ustar00alexalex{ meta { timesig 5 12 600 timesig 5 24 1200 timesig 5 12 600 timesig 4 24 } } midish-1.4.0/regress/tevmap_a4.res010066400017510001751000000011721501104363400155450ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {1 1} 64 48 cat {1 1} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a5.cmd010066400017510001751000000001501501104363400155130ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {xpc {0 0}} {xpc {1 1}} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a5.res010066400017510001751000000011721501104363400155460ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {1 1} 1 64 48 xpc {1 1} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a6.cmd010066400017510001751000000001541501104363400155200ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {xpc {} 64 1} {xpc {} 64 5} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a6.res010066400017510001751000000011721501104363400155470ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 5 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a7.cmd010066400017510001751000000001641501104363400155220ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {xpc {} 64..74 1} {xpc {} 24..34 1} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a7.res010066400017510001751000000011721501104363400155500ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 24 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a8.cmd010066400017510001751000000001641501104363400155230ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {nrpn {0 0} 1 64} {nrpn {1 1} 2 34} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a8.res010066400017510001751000000011721501104363400155510ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {1 1} 2 34 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {0 0} 0 0 48 bend {0 0} 0 64 48 bend {0 1} 63 63 48 bend {0 1} 0 64 } } } midish-1.4.0/regress/tevmap_a9.cmd010066400017510001751000000001621501104363400155220ustar00alexalexload "tevmap.msh" ct t; g 0; sel 100; tevmap {bend {0 0..15}} {bend {1 0..15}} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tevmap_a9.res010066400017510001751000000011721501104363400155520ustar00alexalex{ songtrk t { track { 48 non {0 0} 65 100 96 kat {0 0} 65 123 48 noff {0 0} 65 100 48 non {0 1} 66 100 96 kat {0 1} 66 123 48 noff {0 1} 66 100 48 xctl {0 0} 7 8192 48 xctl {0 0} 7 8320 48 xctl {0 1} 10 8192 48 xctl {0 1} 10 8320 48 cat {0 0} 64 48 cat {0 0} 0 48 cat {0 1} 64 48 cat {0 1} 0 48 xpc {0 0} 1 64 48 xpc {0 0} 2 65 48 nrpn {0 0} 1 64 48 nrpn {0 0} 2 65 48 rpn {0 0} 3 66 48 rpn {0 0} 4 67 48 bend {1 0} 0 0 48 bend {1 0} 0 64 48 bend {1 1} 63 63 48 bend {1 1} 0 64 } } } midish-1.4.0/regress/timeins.cmd010066400017510001751000000001221501104363400153010ustar00alexalexg 0 mins 1 {8 8} mins 1 {2 2} g 1 mins 1 {4 4} g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/timeins.res010066400017510001751000000001511501104363400153310ustar00alexalex{ meta { timesig 2 48 tempo 500000 96 timesig 4 24 96 timesig 8 12 96 timesig 4 24 } } midish-1.4.0/regress/timerm.cmd010066400017510001751000000001051501104363400151270ustar00alexalexload "time.msh" g 10 sel 10; mcut g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/timerm.res010066400017510001751000000000641501104363400151610ustar00alexalex{ meta { timesig 5 12 1200 timesig 4 24 } } midish-1.4.0/regress/timesig.cmd010066400017510001751000000001211501104363400152710ustar00alexalexload "sign.msh" g 1 t 60 g 2 t 120 g 3 t 240 g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/timesig.res010066400017510001751000000001571501104363400153300ustar00alexalex{ meta { 96 timesig 3 24 tempo 1000000 48 timesig 4 24 24 tempo 500000 96 tempo 250000 } } midish-1.4.0/regress/tundo.msh010066400017510001751000000002021501104363400150050ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_d0.cmd010066400017510001751000000001161501104363400153500ustar00alexalexload "tundo.msh" ct t; g 0; sel 1; tclr; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_d0.res010066400017510001751000000002021501104363400153720ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_d1.cmd010066400017510001751000000001161501104363400153510ustar00alexalexload "tundo.msh" ct t; g 1; sel 1; tclr; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_d1.res010066400017510001751000000002021501104363400153730ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_d2.cmd010066400017510001751000000001161501104363400153520ustar00alexalexload "tundo.msh" ct t; g 3; sel 2; tcut; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_d2.res010066400017510001751000000002021501104363400153740ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_i0.cmd010066400017510001751000000001111501104363400153500ustar00alexalexload "tundo.msh" ct t; g 0; tins 1; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_i0.res010066400017510001751000000002021501104363400153770ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_i1.cmd010066400017510001751000000001341501104363400153560ustar00alexalexload "tundo.msh" ct t; g 2; sel 2; tcopy; g 5; tpaste; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_i1.res010066400017510001751000000002021501104363400154000ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_i2.cmd010066400017510001751000000001401501104363400153540ustar00alexalexload "tundo.msh" ct t; g 0; taddev 0 0 0 {xctl {0 0} 7 0}; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_i2.res010066400017510001751000000002021501104363400154010ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_i3.cmd010066400017510001751000000001401501104363400153550ustar00alexalexload "tundo.msh" ct t; g 0; taddev 0 1 0 {xctl {0 0} 7 0}; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_i3.res010066400017510001751000000002021501104363400154020ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_i4.cmd010066400017510001751000000001401501104363400153560ustar00alexalexload "tundo.msh" ct t; g 0; taddev 1 1 0 {xctl {0 0} 7 0}; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_i4.res010066400017510001751000000002021501104363400154030ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_i5.cmd010066400017510001751000000001401501104363400153570ustar00alexalexload "tundo.msh" ct t; g 0; taddev 5 0 0 {xctl {0 0} 7 0}; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_i5.res010066400017510001751000000002021501104363400154040ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/tundo_n.cmd010066400017510001751000000001111501104363400152750ustar00alexalexload "tundo.msh" ct t; g 0; tcheck; u g 0; sel 0; ct nil; ci nil; co nil midish-1.4.0/regress/tundo_n.res010066400017510001751000000002021501104363400153240ustar00alexalex{ songtrk t { track { 96 xctl {0 0} 7 1 96 xctl {0 0} 7 2 96 xctl {0 0} 7 3 96 xctl {0 0} 7 4 } } } midish-1.4.0/regress/update-test010077500017510001751000000003451501104363400153400ustar00alexalex#!/bin/sh # # patch .res files with diff files generated by failed tests # so that tests will no more fail # if [ -n "$*" ]; then list=$* else list=*.cmd fi for i in $list; do i=${i%.cmd} patch -F1 $i.res <$i.diff done midish-1.4.0/sample.msh010066400017510001751000001521111501104363400134720ustar00alexalex# # song file of midish version 0.3 # { tics_per_unit 96 tempo_factor 256 meta { timesig 4 24 tempo 400000 } songin kbd { chan {1 0} conf { } } songout piano { chan {0 0} conf { xpc {0 0} 0 nil } } songout drums { chan {0 9} conf { } } songout bass { chan {0 1} conf { xpc {0 1} 33 nil } } songout organ { chan {0 2} conf { xpc {0 2} 17 nil xctl {0 2} 7 7680 } } songout strings { chan {0 4} conf { xpc {0 4} 51 nil xctl {0 4} 7 6400 } } songfilt piano { filt { evmap any {1 0} > any {0 0} } } songfilt drums { filt { evmap any {1 0} > any {0 9} transp any {0..15 0..15} 104 } } songfilt bass { filt { evmap any {1 0} > any {0 1} transp any {0..15 0..15} 104 } } songfilt organ { filt { evmap any {1 0} > any {0 2} } } songfilt strings { filt { evmap any {1 0} > any {0 4} } } songtrk pi1 { curfilt piano mute 0 track { 864 non {0 0} 67 42 1 non {0 0} 64 46 non {0 0} 52 33 non {0 0} 59 36 2 noff {0 0} 64 100 2 noff {0 0} 67 100 noff {0 0} 59 100 36 non {0 0} 67 42 non {0 0} 59 45 non {0 0} 64 42 3 noff {0 0} 52 100 noff {0 0} 67 100 noff {0 0} 64 100 1 noff {0 0} 59 100 18 non {0 0} 64 63 non {0 0} 67 58 non {0 0} 59 55 4 noff {0 0} 64 100 1 noff {0 0} 67 100 noff {0 0} 59 100 43 non {0 0} 67 49 non {0 0} 59 51 non {0 0} 64 60 5 noff {0 0} 59 100 noff {0 0} 64 100 2 noff {0 0} 67 100 20 non {0 0} 52 32 8 non {0 0} 62 57 non {0 0} 66 57 non {0 0} 57 50 3 noff {0 0} 52 100 9 noff {0 0} 62 100 noff {0 0} 66 100 noff {0 0} 57 100 3 non {0 0} 52 34 6 noff {0 0} 52 100 3 non {0 0} 57 34 non {0 0} 62 46 non {0 0} 66 46 11 noff {0 0} 57 100 2 noff {0 0} 62 100 noff {0 0} 66 100 8 non {0 0} 67 51 1 non {0 0} 52 31 2 non {0 0} 64 55 non {0 0} 59 43 19 noff {0 0} 52 100 13 noff {0 0} 59 100 1 noff {0 0} 67 100 noff {0 0} 64 100 6 non {0 0} 67 45 non {0 0} 64 56 non {0 0} 59 54 4 noff {0 0} 64 100 1 noff {0 0} 67 100 noff {0 0} 59 100 66 non {0 0} 67 44 non {0 0} 59 40 non {0 0} 64 45 4 noff {0 0} 64 100 2 noff {0 0} 67 100 noff {0 0} 59 100 20 non {0 0} 52 26 7 non {0 0} 66 51 non {0 0} 62 51 1 non {0 0} 57 45 13 noff {0 0} 57 100 2 noff {0 0} 52 100 noff {0 0} 62 100 1 noff {0 0} 66 100 7 non {0 0} 66 44 non {0 0} 62 51 non {0 0} 57 56 11 noff {0 0} 57 100 2 noff {0 0} 66 100 noff {0 0} 62 100 11 non {0 0} 67 46 1 non {0 0} 52 45 non {0 0} 59 40 non {0 0} 64 47 33 noff {0 0} 67 100 1 noff {0 0} 59 100 noff {0 0} 64 100 5 non {0 0} 59 34 non {0 0} 67 40 non {0 0} 64 46 4 noff {0 0} 59 100 noff {0 0} 64 100 1 noff {0 0} 67 100 1 noff {0 0} 52 100 24 non {0 0} 67 52 non {0 0} 64 57 non {0 0} 59 47 3 noff {0 0} 64 100 1 noff {0 0} 67 100 noff {0 0} 59 100 36 non {0 0} 67 53 non {0 0} 59 56 1 non {0 0} 64 55 3 noff {0 0} 64 100 1 noff {0 0} 67 100 noff {0 0} 59 100 22 non {0 0} 52 38 8 noff {0 0} 52 100 non {0 0} 66 63 1 non {0 0} 62 58 non {0 0} 57 59 15 noff {0 0} 62 100 1 noff {0 0} 66 100 noff {0 0} 57 100 1 non {0 0} 52 22 4 noff {0 0} 52 100 1 non {0 0} 66 52 1 non {0 0} 62 56 non {0 0} 57 56 14 noff {0 0} 57 100 1 non {0 0} 52 38 1 noff {0 0} 66 100 noff {0 0} 62 100 6 noff {0 0} 52 100 2 non {0 0} 67 61 non {0 0} 64 83 1 non {0 0} 59 47 27 noff {0 0} 59 100 2 noff {0 0} 67 100 noff {0 0} 64 100 8 non {0 0} 67 46 non {0 0} 59 51 1 non {0 0} 64 66 2 noff {0 0} 64 100 1 noff {0 0} 67 100 noff {0 0} 59 100 28 non {0 0} 67 50 non {0 0} 64 65 1 non {0 0} 59 51 13 noff {0 0} 64 100 noff {0 0} 59 100 1 noff {0 0} 67 100 76 non {0 0} 67 60 non {0 0} 59 66 non {0 0} 64 71 4 noff {0 0} 67 100 noff {0 0} 59 100 noff {0 0} 64 100 28 non {0 0} 67 42 non {0 0} 64 69 non {0 0} 59 58 34 noff {0 0} 64 100 noff {0 0} 59 100 2 non {0 0} 64 83 1 non {0 0} 59 76 4 noff {0 0} 64 100 noff {0 0} 59 100 1 noff {0 0} 67 100 26 non {0 0} 59 71 1 non {0 0} 64 69 non {0 0} 67 71 82 noff {0 0} 59 100 1 noff {0 0} 64 100 1 noff {0 0} 67 100 4 non {0 0} 67 56 non {0 0} 59 58 non {0 0} 64 69 4 noff {0 0} 59 100 noff {0 0} 64 100 1 noff {0 0} 67 100 28 non {0 0} 60 54 non {0 0} 66 62 non {0 0} 57 46 35 noff {0 0} 57 100 1 noff {0 0} 60 100 noff {0 0} 66 100 3 non {0 0} 60 62 1 non {0 0} 57 50 non {0 0} 66 61 3 noff {0 0} 60 100 1 noff {0 0} 57 100 1 noff {0 0} 66 100 26 non {0 0} 66 53 non {0 0} 60 55 1 non {0 0} 57 52 12 noff {0 0} 66 100 noff {0 0} 60 100 noff {0 0} 57 100 25 non {0 0} 60 56 1 non {0 0} 66 55 non {0 0} 57 52 4 noff {0 0} 60 100 noff {0 0} 57 100 1 noff {0 0} 66 100 43 non {0 0} 60 63 non {0 0} 66 62 1 non {0 0} 57 55 25 noff {0 0} 60 100 1 noff {0 0} 57 100 1 noff {0 0} 66 100 5 non {0 0} 66 58 1 non {0 0} 63 66 non {0 0} 57 68 36 noff {0 0} 66 100 noff {0 0} 63 100 noff {0 0} 57 100 5 non {0 0} 63 65 non {0 0} 57 69 6 noff {0 0} 63 100 1 noff {0 0} 57 100 24 non {0 0} 63 92 2 non {0 0} 57 86 30 noff {0 0} 63 100 noff {0 0} 57 100 6 non {0 0} 66 61 1 non {0 0} 63 72 non {0 0} 57 74 64 noff {0 0} 63 100 4 noff {0 0} 66 100 noff {0 0} 57 100 11 non {0 0} 67 89 non {0 0} 62 95 non {0 0} 57 71 31 noff {0 0} 57 100 2 noff {0 0} 67 100 noff {0 0} 62 100 7 non {0 0} 62 99 non {0 0} 67 89 non {0 0} 59 76 11 noff {0 0} 62 100 1 noff {0 0} 67 100 2 noff {0 0} 59 100 21 non {0 0} 62 60 non {0 0} 67 62 non {0 0} 59 60 75 noff {0 0} 59 100 2 noff {0 0} 62 100 3 noff {0 0} 67 100 6 non {0 0} 64 68 1 non {0 0} 67 49 non {0 0} 59 78 27 noff {0 0} 64 100 1 noff {0 0} 59 100 1 noff {0 0} 67 100 4 non {0 0} 64 68 non {0 0} 67 58 non {0 0} 59 74 33 noff {0 0} 64 100 1 noff {0 0} 67 100 noff {0 0} 59 100 2 non {0 0} 67 58 non {0 0} 64 72 1 non {0 0} 59 53 3 noff {0 0} 64 100 noff {0 0} 59 100 1 noff {0 0} 67 100 46 non {0 0} 67 57 non {0 0} 59 54 non {0 0} 64 72 70 noff {0 0} 59 100 noff {0 0} 64 100 1 noff {0 0} 67 100 3 non {0 0} 64 74 non {0 0} 67 66 non {0 0} 59 74 11 noff {0 0} 64 100 noff {0 0} 59 100 2 noff {0 0} 67 100 19 non {0 0} 60 55 non {0 0} 66 61 1 non {0 0} 57 60 32 noff {0 0} 60 100 noff {0 0} 66 100 noff {0 0} 57 100 4 non {0 0} 60 53 non {0 0} 57 48 non {0 0} 66 56 3 noff {0 0} 60 100 1 noff {0 0} 57 100 1 noff {0 0} 66 100 27 non {0 0} 60 40 non {0 0} 66 56 non {0 0} 57 45 83 noff {0 0} 57 100 1 noff {0 0} 66 100 1 noff {0 0} 60 100 3 non {0 0} 60 48 non {0 0} 66 60 non {0 0} 57 57 4 noff {0 0} 60 100 1 noff {0 0} 57 100 1 noff {0 0} 66 100 28 non {0 0} 63 54 non {0 0} 57 63 non {0 0} 66 62 33 noff {0 0} 63 100 noff {0 0} 57 100 1 noff {0 0} 66 100 2 non {0 0} 63 76 2 non {0 0} 66 62 non {0 0} 57 60 4 noff {0 0} 63 100 1 noff {0 0} 66 100 noff {0 0} 57 100 29 non {0 0} 66 55 non {0 0} 57 53 non {0 0} 63 61 72 noff {0 0} 66 100 noff {0 0} 57 100 noff {0 0} 63 100 13 non {0 0} 57 55 non {0 0} 66 58 non {0 0} 63 68 25 noff {0 0} 57 100 1 noff {0 0} 66 100 noff {0 0} 63 100 7 non {0 0} 64 74 non {0 0} 67 58 1 non {0 0} 59 58 26 noff {0 0} 67 100 2 noff {0 0} 64 100 noff {0 0} 59 100 9 non {0 0} 62 92 1 non {0 0} 59 72 non {0 0} 67 81 29 noff {0 0} 62 100 1 noff {0 0} 59 100 1 noff {0 0} 67 100 5 non {0 0} 67 57 1 non {0 0} 64 62 non {0 0} 59 60 50 noff {0 0} 64 100 3 noff {0 0} 59 100 3 noff {0 0} 67 100 11 non {0 0} 66 63 non {0 0} 62 92 non {0 0} 57 76 33 noff {0 0} 57 100 2 noff {0 0} 66 100 1 noff {0 0} 62 100 13 non {0 0} 67 81 1 non {0 0} 64 74 non {0 0} 59 76 non {0 0} 52 68 54 noff {0 0} 59 100 2 noff {0 0} 64 100 3 noff {0 0} 67 100 3 noff {0 0} 52 100 } } songtrk dr2 { curfilt drums mute 0 track { 144 non {0 9} 37 95 4 noff {0 9} 37 100 36 non {0 9} 37 53 4 noff {0 9} 37 100 68 non {0 9} 36 76 7 noff {0 9} 36 100 1 non {0 9} 37 54 8 noff {0 9} 37 100 16 non {0 9} 36 49 4 noff {0 9} 36 100 12 non {0 9} 36 44 6 noff {0 9} 36 100 18 non {0 9} 36 49 8 noff {0 9} 36 100 non {0 9} 37 51 10 noff {0 9} 37 100 14 non {0 9} 36 89 5 noff {0 9} 36 100 35 non {0 9} 36 46 5 noff {0 9} 36 100 3 non {0 9} 36 45 9 noff {0 9} 36 100 31 non {0 9} 37 108 8 noff {0 9} 37 100 16 non {0 9} 36 46 5 noff {0 9} 36 100 3 non {0 9} 36 56 11 noff {0 9} 36 100 13 non {0 9} 36 68 6 noff {0 9} 36 100 18 non {0 9} 37 114 8 noff {0 9} 37 100 56 non {0 9} 37 99 6 noff {0 9} 37 100 2 non {0 9} 36 48 6 noff {0 9} 36 100 10 non {0 9} 36 53 8 non {0 9} 37 114 1 noff {0 9} 36 100 15 noff {0 9} 37 100 48 non {0 9} 37 108 4 noff {0 9} 37 100 20 non {0 9} 36 65 5 noff {0 9} 36 100 19 non {0 9} 36 44 5 noff {0 9} 36 100 3 non {0 9} 37 127 9 noff {0 9} 37 100 31 non {0 9} 37 99 3 noff {0 9} 37 100 21 non {0 9} 36 33 6 noff {0 9} 36 100 2 non {0 9} 37 103 7 noff {0 9} 37 100 9 non {0 9} 37 74 6 noff {0 9} 37 100 18 non {0 9} 37 43 8 non {0 9} 36 54 2 noff {0 9} 37 100 6 noff {0 9} 36 100 32 non {0 9} 36 31 8 noff {0 9} 36 100 non {0 9} 37 103 10 noff {0 9} 37 100 6 non {0 9} 36 56 6 noff {0 9} 36 100 2 non {0 9} 36 58 9 noff {0 9} 36 100 31 non {0 9} 36 47 4 noff {0 9} 36 100 4 non {0 9} 36 53 10 noff {0 9} 36 100 14 non {0 9} 37 108 10 noff {0 9} 37 100 30 non {0 9} 36 53 5 noff {0 9} 36 100 3 non {0 9} 36 48 9 noff {0 9} 36 100 15 non {0 9} 36 95 7 noff {0 9} 36 100 17 non {0 9} 37 108 5 noff {0 9} 37 100 19 non {0 9} 36 69 7 noff {0 9} 36 100 33 non {0 9} 36 92 3 noff {0 9} 36 100 5 non {0 9} 36 61 10 noff {0 9} 36 100 30 non {0 9} 37 114 7 noff {0 9} 37 100 17 non {0 9} 36 83 3 noff {0 9} 36 100 5 non {0 9} 36 60 9 noff {0 9} 36 100 15 non {0 9} 36 86 7 noff {0 9} 36 100 17 non {0 9} 37 114 7 noff {0 9} 37 100 17 non {0 9} 36 62 6 noff {0 9} 36 100 34 non {0 9} 36 48 6 noff {0 9} 36 100 2 non {0 9} 36 72 9 noff {0 9} 36 100 15 non {0 9} 37 120 8 noff {0 9} 37 100 40 non {0 9} 36 81 8 noff {0 9} 36 100 32 non {0 9} 36 99 4 noff {0 9} 36 100 4 non {0 9} 37 76 6 noff {0 9} 37 100 18 non {0 9} 36 99 7 noff {0 9} 36 100 33 non {0 9} 38 127 8 noff {0 9} 38 100 16 non {0 9} 36 26 7 noff {0 9} 36 100 1 non {0 9} 38 95 6 noff {0 9} 38 100 10 non {0 9} 38 108 9 noff {0 9} 38 100 15 non {0 9} 38 51 8 noff {0 9} 38 100 non {0 9} 36 81 8 noff {0 9} 36 100 32 non {0 9} 36 57 8 non {0 9} 38 127 1 noff {0 9} 36 100 10 noff {0 9} 38 100 5 non {0 9} 36 55 5 noff {0 9} 36 100 3 non {0 9} 36 60 13 noff {0 9} 36 100 27 non {0 9} 36 62 6 noff {0 9} 36 100 2 non {0 9} 36 69 14 noff {0 9} 36 100 10 non {0 9} 38 127 8 noff {0 9} 38 100 32 non {0 9} 36 60 5 noff {0 9} 36 100 3 non {0 9} 36 68 13 noff {0 9} 36 100 11 non {0 9} 36 74 10 noff {0 9} 36 100 14 non {0 9} 38 127 9 noff {0 9} 38 100 15 non {0 9} 36 60 6 noff {0 9} 36 100 34 non {0 9} 36 53 5 noff {0 9} 36 100 3 non {0 9} 36 65 10 noff {0 9} 36 100 14 non {0 9} 38 127 8 noff {0 9} 38 100 32 non {0 9} 36 47 4 noff {0 9} 36 100 4 non {0 9} 36 59 11 noff {0 9} 36 100 29 non {0 9} 36 51 8 noff {0 9} 36 100 non {0 9} 38 120 12 noff {0 9} 38 100 12 non {0 9} 36 54 6 noff {0 9} 36 100 34 non {0 9} 36 54 5 noff {0 9} 36 100 3 non {0 9} 36 66 6 noff {0 9} 36 100 18 non {0 9} 38 127 12 noff {0 9} 38 100 28 non {0 9} 38 58 8 non {0 9} 36 71 1 noff {0 9} 38 100 8 noff {0 9} 36 100 7 non {0 9} 36 83 8 noff {0 9} 36 100 16 non {0 9} 36 42 7 noff {0 9} 36 100 1 non {0 9} 38 99 16 noff {0 9} 38 100 8 non {0 9} 36 103 5 noff {0 9} 36 100 35 non {0 9} 36 39 5 noff {0 9} 36 100 3 non {0 9} 36 92 10 noff {0 9} 36 100 14 non {0 9} 38 120 24 noff {0 9} 38 100 16 non {0 9} 38 51 8 non {0 9} 36 76 3 noff {0 9} 38 100 11 noff {0 9} 36 100 26 non {0 9} 36 59 8 non {0 9} 38 120 4 noff {0 9} 36 100 11 noff {0 9} 38 100 9 non {0 9} 36 59 9 noff {0 9} 36 100 31 non {0 9} 36 63 5 noff {0 9} 36 100 3 non {0 9} 36 69 11 noff {0 9} 36 100 13 non {0 9} 38 127 8 noff {0 9} 38 100 32 non {0 9} 36 46 5 noff {0 9} 36 100 3 non {0 9} 36 72 10 noff {0 9} 36 100 14 non {0 9} 36 66 6 noff {0 9} 36 100 18 non {0 9} 38 120 6 noff {0 9} 38 100 18 non {0 9} 36 66 6 noff {0 9} 36 100 34 non {0 9} 36 59 6 noff {0 9} 36 100 2 non {0 9} 36 62 9 noff {0 9} 36 100 15 non {0 9} 38 127 11 noff {0 9} 38 100 29 non {0 9} 38 68 8 non {0 9} 36 71 3 noff {0 9} 38 100 5 noff {0 9} 36 100 32 non {0 9} 36 71 8 non {0 9} 38 114 3 noff {0 9} 36 100 12 noff {0 9} 38 100 1 non {0 9} 36 68 5 noff {0 9} 36 100 3 non {0 9} 36 58 11 noff {0 9} 36 100 29 non {0 9} 36 78 5 noff {0 9} 36 100 3 non {0 9} 36 62 9 noff {0 9} 36 100 15 non {0 9} 38 127 8 noff {0 9} 38 100 40 non {0 9} 38 127 7 noff {0 9} 38 100 9 non {0 9} 36 47 9 noff {0 9} 36 100 15 non {0 9} 36 78 8 noff {0 9} 36 100 16 non {0 9} 36 47 8 noff {0 9} 36 100 non {0 9} 38 127 10 noff {0 9} 38 100 30 non {0 9} 38 127 7 noff {0 9} 38 100 17 non {0 9} 36 52 8 noff {0 9} 36 100 non {0 9} 38 108 7 noff {0 9} 38 100 9 non {0 9} 38 103 9 noff {0 9} 38 100 15 non {0 9} 38 49 8 noff {0 9} 38 100 non {0 9} 36 103 10 noff {0 9} 36 100 } } songtrk dr3 { curfilt drums mute 0 track { 96 non {0 9} 56 103 6 noff {0 9} 56 100 10 non {0 9} 56 43 8 noff {0 9} 56 100 8 non {0 9} 56 35 7 noff {0 9} 56 100 9 non {0 9} 56 24 10 noff {0 9} 56 100 230 non {0 9} 56 59 8 noff {0 9} 56 100 8 non {0 9} 56 36 8 noff {0 9} 56 100 8 non {0 9} 56 34 8 noff {0 9} 56 100 8 non {0 9} 56 34 7 noff {0 9} 56 100 41 non {0 9} 56 99 10 noff {0 9} 56 100 6 non {0 9} 56 41 7 noff {0 9} 56 100 17 non {0 9} 56 24 6 noff {0 9} 56 100 242 non {0 9} 56 92 10 noff {0 9} 56 100 6 non {0 9} 56 39 7 noff {0 9} 56 100 9 non {0 9} 56 35 7 noff {0 9} 56 100 9 non {0 9} 56 22 8 noff {0 9} 56 100 8 non {0 9} 56 22 8 noff {0 9} 56 100 8 non {0 9} 56 21 6 noff {0 9} 56 100 202 non {0 9} 56 95 9 noff {0 9} 56 100 7 non {0 9} 56 32 8 noff {0 9} 56 100 8 non {0 9} 56 28 9 noff {0 9} 56 100 7 non {0 9} 56 25 10 noff {0 9} 56 100 230 non {0 9} 56 108 8 noff {0 9} 56 100 8 non {0 9} 56 45 7 noff {0 9} 56 100 9 non {0 9} 56 30 7 noff {0 9} 56 100 9 non {0 9} 56 39 8 noff {0 9} 56 100 232 non {0 9} 56 78 non {0 9} 49 95 9 noff {0 9} 56 100 1 noff {0 9} 49 100 38 non {0 9} 56 95 9 noff {0 9} 56 100 39 non {0 9} 56 89 9 noff {0 9} 56 100 39 non {0 9} 56 95 12 noff {0 9} 56 100 12 non {0 9} 56 44 12 noff {0 9} 56 100 12 non {0 9} 56 99 11 noff {0 9} 56 100 13 non {0 9} 56 20 8 noff {0 9} 56 100 16 non {0 9} 56 99 10 noff {0 9} 56 100 22 non {0 9} 56 34 3 noff {0 9} 56 100 21 non {0 9} 56 103 10 noff {0 9} 56 100 30 non {0 9} 56 76 9 noff {0 9} 56 100 15 non {0 9} 56 46 7 noff {0 9} 56 100 9 non {0 9} 56 65 8 non {0 9} 55 86 1 noff {0 9} 56 100 7 noff {0 9} 55 100 16 non {0 9} 56 27 10 noff {0 9} 56 100 14 non {0 9} 56 103 10 noff {0 9} 56 100 6 non {0 9} 56 27 16 noff {0 9} 56 100 16 non {0 9} 56 99 9 noff {0 9} 56 100 15 non {0 9} 56 63 8 noff {0 9} 56 100 8 non {0 9} 56 47 7 noff {0 9} 56 100 1 non {0 9} 56 99 13 noff {0 9} 56 100 35 non {0 9} 56 114 7 noff {0 9} 56 100 17 non {0 9} 56 48 8 noff {0 9} 56 100 16 non {0 9} 56 60 5 noff {0 9} 56 100 11 non {0 9} 56 86 9 noff {0 9} 56 100 23 non {0 9} 56 114 8 noff {0 9} 56 100 16 non {0 9} 56 20 10 noff {0 9} 56 100 14 non {0 9} 56 41 non {0 9} 57 66 8 noff {0 9} 57 100 5 noff {0 9} 56 100 19 non {0 9} 56 24 7 noff {0 9} 56 100 9 non {0 9} 56 108 non {0 9} 49 86 10 noff {0 9} 56 100 1 noff {0 9} 49 100 13 non {0 9} 56 41 8 noff {0 9} 56 100 8 non {0 9} 56 57 7 noff {0 9} 56 100 1 non {0 9} 56 108 12 noff {0 9} 56 100 12 non {0 9} 56 61 9 noff {0 9} 56 100 39 non {0 9} 56 57 9 noff {0 9} 56 100 15 non {0 9} 56 103 9 noff {0 9} 56 100 15 non {0 9} 56 72 8 noff {0 9} 56 100 8 non {0 9} 56 39 7 noff {0 9} 56 100 1 non {0 9} 56 99 9 noff {0 9} 56 100 15 non {0 9} 56 92 9 noff {0 9} 56 100 7 non {0 9} 56 24 8 noff {0 9} 56 100 non {0 9} 56 89 9 noff {0 9} 56 100 15 non {0 9} 56 108 9 noff {0 9} 56 100 15 non {0 9} 56 50 8 noff {0 9} 56 100 16 non {0 9} 56 103 7 noff {0 9} 56 100 9 non {0 9} 56 36 8 noff {0 9} 56 100 non {0 9} 56 95 10 noff {0 9} 56 100 14 non {0 9} 56 40 9 noff {0 9} 56 100 15 non {0 9} 56 99 10 noff {0 9} 56 100 14 non {0 9} 56 20 9 noff {0 9} 56 100 7 non {0 9} 56 46 8 noff {0 9} 56 100 non {0 9} 56 103 11 noff {0 9} 56 100 13 non {0 9} 56 95 9 noff {0 9} 56 100 15 non {0 9} 56 60 6 noff {0 9} 56 100 18 non {0 9} 56 42 8 noff {0 9} 56 100 16 non {0 9} 56 103 9 noff {0 9} 56 100 15 non {0 9} 56 43 5 noff {0 9} 56 100 11 non {0 9} 56 35 7 noff {0 9} 56 100 1 non {0 9} 55 69 non {0 9} 56 99 5 noff {0 9} 56 100 5 noff {0 9} 55 100 14 non {0 9} 56 53 8 noff {0 9} 56 100 16 non {0 9} 56 37 8 noff {0 9} 56 100 16 non {0 9} 56 99 non {0 9} 55 108 8 noff {0 9} 55 100 1 noff {0 9} 56 100 15 non {0 9} 56 51 6 noff {0 9} 56 100 10 non {0 9} 56 78 6 noff {0 9} 56 100 2 non {0 9} 56 83 9 noff {0 9} 56 100 15 non {0 9} 56 103 10 noff {0 9} 56 100 14 non {0 9} 56 28 9 noff {0 9} 56 100 7 non {0 9} 56 43 8 non {0 9} 49 103 1 noff {0 9} 56 100 7 noff {0 9} 49 100 } } songtrk dr1 { curfilt drums mute 0 track { 96 non {0 9} 42 103 6 noff {0 9} 42 100 10 non {0 9} 42 26 4 noff {0 9} 42 100 4 non {0 9} 42 71 7 noff {0 9} 42 100 9 non {0 9} 42 28 6 noff {0 9} 42 100 2 non {0 9} 42 50 8 noff {0 9} 42 100 8 non {0 9} 42 45 6 noff {0 9} 42 100 2 non {0 9} 42 78 9 noff {0 9} 42 100 7 non {0 9} 42 27 6 noff {0 9} 42 100 2 non {0 9} 42 58 7 noff {0 9} 42 100 9 non {0 9} 42 65 7 noff {0 9} 42 100 1 non {0 9} 42 108 10 noff {0 9} 42 100 6 non {0 9} 42 32 5 noff {0 9} 42 100 3 non {0 9} 44 76 11 noff {0 9} 44 100 5 non {0 9} 42 61 4 noff {0 9} 42 100 4 non {0 9} 42 108 7 noff {0 9} 42 100 1 non {0 9} 46 95 8 non {0 9} 44 89 2 noff {0 9} 46 100 6 non {0 9} 42 95 2 noff {0 9} 44 100 5 noff {0 9} 42 100 9 non {0 9} 42 45 4 noff {0 9} 42 100 4 non {0 9} 42 35 9 noff {0 9} 42 100 7 non {0 9} 42 40 6 noff {0 9} 42 100 2 non {0 9} 42 127 10 noff {0 9} 42 100 6 non {0 9} 42 14 5 noff {0 9} 42 100 3 non {0 9} 44 51 12 noff {0 9} 44 100 4 non {0 9} 42 60 6 noff {0 9} 42 100 2 non {0 9} 42 43 11 noff {0 9} 42 100 5 non {0 9} 42 65 7 noff {0 9} 42 100 1 non {0 9} 44 69 11 noff {0 9} 44 100 5 non {0 9} 42 21 4 noff {0 9} 42 100 4 non {0 9} 42 53 19 noff {0 9} 42 100 5 non {0 9} 44 89 10 noff {0 9} 44 100 6 non {0 9} 42 42 6 noff {0 9} 42 100 2 non {0 9} 42 99 12 noff {0 9} 42 100 4 non {0 9} 42 27 3 noff {0 9} 42 100 5 non {0 9} 42 49 10 noff {0 9} 42 100 6 non {0 9} 42 25 3 noff {0 9} 42 100 5 non {0 9} 44 95 9 noff {0 9} 44 100 7 non {0 9} 42 20 4 noff {0 9} 42 100 4 non {0 9} 42 108 9 noff {0 9} 42 100 7 non {0 9} 42 29 6 noff {0 9} 42 100 2 non {0 9} 42 59 10 noff {0 9} 42 100 6 non {0 9} 42 68 4 noff {0 9} 42 100 4 non {0 9} 42 108 9 noff {0 9} 42 100 7 non {0 9} 42 37 4 noff {0 9} 42 100 4 non {0 9} 44 68 10 noff {0 9} 44 100 6 non {0 9} 42 86 4 noff {0 9} 42 100 4 non {0 9} 42 108 8 noff {0 9} 42 100 non {0 9} 46 76 8 non {0 9} 44 66 3 noff {0 9} 46 100 5 non {0 9} 42 86 2 noff {0 9} 44 100 7 noff {0 9} 42 100 7 non {0 9} 42 34 3 noff {0 9} 42 100 5 non {0 9} 42 83 10 noff {0 9} 42 100 6 non {0 9} 42 71 5 noff {0 9} 42 100 3 non {0 9} 44 92 11 noff {0 9} 44 100 5 non {0 9} 42 40 6 noff {0 9} 42 100 2 non {0 9} 42 72 10 noff {0 9} 42 100 6 non {0 9} 42 27 7 noff {0 9} 42 100 1 non {0 9} 42 54 10 noff {0 9} 42 100 6 non {0 9} 42 41 5 noff {0 9} 42 100 3 non {0 9} 42 127 12 noff {0 9} 42 100 4 non {0 9} 42 31 5 noff {0 9} 42 100 3 non {0 9} 44 60 11 noff {0 9} 44 100 5 non {0 9} 42 34 6 noff {0 9} 42 100 2 non {0 9} 42 99 10 noff {0 9} 42 100 6 non {0 9} 42 34 5 noff {0 9} 42 100 3 non {0 9} 44 99 11 noff {0 9} 44 100 5 non {0 9} 42 35 4 noff {0 9} 42 100 4 non {0 9} 42 62 10 noff {0 9} 42 100 6 non {0 9} 42 35 6 noff {0 9} 42 100 2 non {0 9} 46 108 10 noff {0 9} 46 100 6 non {0 9} 42 33 8 non {0 9} 44 89 1 noff {0 9} 42 100 10 noff {0 9} 44 100 5 non {0 9} 42 60 4 noff {0 9} 42 100 4 non {0 9} 42 43 9 noff {0 9} 42 100 7 non {0 9} 42 51 4 noff {0 9} 42 100 4 non {0 9} 44 72 13 noff {0 9} 44 100 3 non {0 9} 42 65 5 noff {0 9} 42 100 3 non {0 9} 42 86 7 noff {0 9} 42 100 1 non {0 9} 42 60 4 noff {0 9} 42 100 4 non {0 9} 44 52 8 non {0 9} 42 26 1 noff {0 9} 44 100 7 noff {0 9} 42 100 8 non {0 9} 42 89 4 noff {0 9} 42 100 4 non {0 9} 42 127 9 noff {0 9} 42 100 7 non {0 9} 42 30 5 noff {0 9} 42 100 3 non {0 9} 42 58 12 noff {0 9} 42 100 4 non {0 9} 42 71 7 noff {0 9} 42 100 1 non {0 9} 44 81 9 noff {0 9} 44 100 7 non {0 9} 42 33 5 noff {0 9} 42 100 3 non {0 9} 42 66 13 noff {0 9} 42 100 3 non {0 9} 42 59 19 noff {0 9} 42 100 5 non {0 9} 42 16 4 noff {0 9} 42 100 4 non {0 9} 44 92 10 noff {0 9} 44 100 6 non {0 9} 42 36 6 noff {0 9} 42 100 2 non {0 9} 42 120 13 noff {0 9} 42 100 3 non {0 9} 42 92 5 noff {0 9} 42 100 3 non {0 9} 46 120 11 noff {0 9} 46 100 5 non {0 9} 42 19 8 noff {0 9} 42 100 non {0 9} 44 53 14 noff {0 9} 44 100 2 non {0 9} 42 66 4 noff {0 9} 42 100 4 non {0 9} 42 78 11 noff {0 9} 42 100 5 non {0 9} 42 35 7 noff {0 9} 42 100 1 non {0 9} 44 99 8 non {0 9} 42 92 1 noff {0 9} 44 100 3 noff {0 9} 42 100 4 non {0 9} 42 48 7 noff {0 9} 42 100 1 non {0 9} 42 74 5 noff {0 9} 42 100 11 non {0 9} 42 89 8 noff {0 9} 42 100 non {0 9} 42 71 7 noff {0 9} 42 100 9 non {0 9} 42 61 10 noff {0 9} 42 100 14 non {0 9} 44 81 8 non {0 9} 42 95 1 noff {0 9} 44 100 6 noff {0 9} 42 100 9 non {0 9} 42 35 17 noff {0 9} 42 100 7 non {0 9} 44 48 8 non {0 9} 42 103 1 noff {0 9} 44 100 5 noff {0 9} 42 100 10 non {0 9} 42 76 7 noff {0 9} 42 100 1 non {0 9} 42 66 6 noff {0 9} 42 100 10 non {0 9} 42 57 9 noff {0 9} 42 100 15 non {0 9} 44 92 7 noff {0 9} 44 100 1 non {0 9} 42 52 7 noff {0 9} 42 100 9 non {0 9} 42 42 9 noff {0 9} 42 100 15 non {0 9} 46 89 8 non {0 9} 42 108 1 noff {0 9} 46 100 5 noff {0 9} 42 100 2 non {0 9} 44 65 8 noff {0 9} 44 100 non {0 9} 42 99 6 noff {0 9} 42 100 2 non {0 9} 42 127 6 noff {0 9} 42 100 10 non {0 9} 46 127 8 non {0 9} 42 114 1 noff {0 9} 46 100 3 noff {0 9} 42 100 4 non {0 9} 46 76 8 noff {0 9} 46 100 non {0 9} 42 103 5 noff {0 9} 42 100 3 non {0 9} 44 69 8 non {0 9} 42 66 2 noff {0 9} 44 100 7 noff {0 9} 42 100 7 non {0 9} 42 103 6 noff {0 9} 42 100 10 non {0 9} 42 108 7 noff {0 9} 42 100 1 non {0 9} 42 103 6 noff {0 9} 42 100 10 non {0 9} 44 66 4 noff {0 9} 44 100 4 non {0 9} 42 114 5 noff {0 9} 42 100 11 non {0 9} 42 49 7 noff {0 9} 42 100 1 non {0 9} 42 81 6 noff {0 9} 42 100 10 non {0 9} 42 103 4 noff {0 9} 42 100 4 non {0 9} 42 86 10 noff {0 9} 42 100 6 non {0 9} 42 29 6 noff {0 9} 42 100 2 non {0 9} 44 108 10 noff {0 9} 44 100 6 non {0 9} 42 72 6 noff {0 9} 42 100 2 non {0 9} 42 89 12 noff {0 9} 42 100 4 non {0 9} 42 81 8 noff {0 9} 42 100 non {0 9} 46 99 12 noff {0 9} 46 100 4 non {0 9} 42 66 8 non {0 9} 44 95 2 noff {0 9} 42 100 8 noff {0 9} 44 100 6 non {0 9} 42 89 6 noff {0 9} 42 100 2 non {0 9} 42 120 10 noff {0 9} 42 100 6 non {0 9} 42 89 8 noff {0 9} 42 100 non {0 9} 44 95 10 noff {0 9} 44 100 6 non {0 9} 42 29 7 noff {0 9} 42 100 1 non {0 9} 42 57 11 noff {0 9} 42 100 5 non {0 9} 42 63 7 noff {0 9} 42 100 1 non {0 9} 42 127 10 noff {0 9} 42 100 6 non {0 9} 42 108 8 non {0 9} 44 114 1 noff {0 9} 42 100 9 noff {0 9} 44 100 6 non {0 9} 42 60 20 noff {0 9} 42 100 4 non {0 9} 42 92 7 noff {0 9} 42 100 1 non {0 9} 42 114 10 noff {0 9} 42 100 6 non {0 9} 42 65 6 noff {0 9} 42 100 2 non {0 9} 42 108 9 noff {0 9} 42 100 7 non {0 9} 42 72 8 noff {0 9} 42 100 non {0 9} 46 108 11 noff {0 9} 46 100 5 non {0 9} 42 83 8 non {0 9} 44 86 1 noff {0 9} 42 100 9 noff {0 9} 44 100 6 non {0 9} 42 74 6 noff {0 9} 42 100 2 non {0 9} 42 86 9 noff {0 9} 42 100 7 non {0 9} 42 66 7 noff {0 9} 42 100 1 non {0 9} 42 120 8 noff {0 9} 42 100 8 non {0 9} 42 42 7 noff {0 9} 42 100 1 non {0 9} 44 108 11 noff {0 9} 44 100 5 non {0 9} 42 55 7 noff {0 9} 42 100 1 non {0 9} 42 120 10 noff {0 9} 42 100 6 non {0 9} 42 81 7 noff {0 9} 42 100 1 non {0 9} 42 120 11 noff {0 9} 42 100 5 non {0 9} 42 89 7 noff {0 9} 42 100 1 non {0 9} 42 95 11 noff {0 9} 42 100 5 non {0 9} 42 74 6 noff {0 9} 42 100 2 non {0 9} 44 86 7 noff {0 9} 44 100 1 non {0 9} 42 89 5 noff {0 9} 42 100 3 non {0 9} 42 99 8 noff {0 9} 42 100 non {0 9} 42 89 6 noff {0 9} 42 100 10 non {0 9} 44 95 7 noff {0 9} 44 100 1 non {0 9} 42 81 6 noff {0 9} 42 100 10 non {0 9} 42 89 6 noff {0 9} 42 100 2 non {0 9} 42 89 7 noff {0 9} 42 100 9 non {0 9} 42 99 8 non {0 9} 46 108 2 noff {0 9} 42 100 9 noff {0 9} 46 100 5 non {0 9} 42 114 8 noff {0 9} 42 100 non {0 9} 46 92 15 noff {0 9} 46 100 1 non {0 9} 42 53 8 non {0 9} 44 89 1 noff {0 9} 42 100 8 noff {0 9} 44 100 7 non {0 9} 42 68 8 noff {0 9} 42 100 24 non {0 9} 42 34 3 noff {0 9} 42 100 5 non {0 9} 42 89 6 noff {0 9} 42 100 2 non {0 9} 42 99 4 noff {0 9} 42 100 4 non {0 9} 42 74 6 noff {0 9} 42 100 10 non {0 9} 42 40 5 noff {0 9} 42 100 3 non {0 9} 42 58 7 noff {0 9} 42 100 9 non {0 9} 42 78 3 noff {0 9} 42 100 5 non {0 9} 42 78 6 noff {0 9} 42 100 10 non {0 9} 42 57 6 noff {0 9} 42 100 2 non {0 9} 44 92 7 noff {0 9} 44 100 1 non {0 9} 42 89 5 noff {0 9} 42 100 3 non {0 9} 42 58 6 noff {0 9} 42 100 2 non {0 9} 42 40 8 noff {0 9} 42 100 8 non {0 9} 42 108 4 noff {0 9} 42 100 4 non {0 9} 42 83 6 noff {0 9} 42 100 10 non {0 9} 42 43 5 noff {0 9} 42 100 3 non {0 9} 42 57 6 noff {0 9} 42 100 10 non {0 9} 42 92 7 noff {0 9} 42 100 1 non {0 9} 42 99 4 noff {0 9} 42 100 4 non {0 9} 44 54 8 non {0 9} 42 95 1 noff {0 9} 44 100 4 noff {0 9} 42 100 3 non {0 9} 42 92 8 noff {0 9} 42 100 8 non {0 9} 42 45 5 noff {0 9} 42 100 3 non {0 9} 42 99 11 noff {0 9} 42 100 5 non {0 9} 42 51 7 noff {0 9} 42 100 1 non {0 9} 42 99 10 noff {0 9} 42 100 6 non {0 9} 42 29 5 noff {0 9} 42 100 3 non {0 9} 44 86 11 noff {0 9} 44 100 5 non {0 9} 42 27 3 noff {0 9} 42 100 5 non {0 9} 42 108 13 noff {0 9} 42 100 3 non {0 9} 46 120 8 non {0 9} 44 103 4 noff {0 9} 46 100 11 noff {0 9} 44 100 1 non {0 9} 42 54 6 noff {0 9} 42 100 2 non {0 9} 42 99 6 noff {0 9} 42 100 10 non {0 9} 42 45 3 noff {0 9} 42 100 5 non {0 9} 42 66 6 noff {0 9} 42 100 2 non {0 9} 42 95 4 noff {0 9} 42 100 4 non {0 9} 42 68 8 noff {0 9} 42 100 non {0 9} 42 37 5 noff {0 9} 42 100 11 non {0 9} 42 58 8 noff {0 9} 42 100 non {0 9} 42 81 6 noff {0 9} 42 100 10 non {0 9} 42 49 7 noff {0 9} 42 100 1 non {0 9} 42 72 7 noff {0 9} 42 100 9 non {0 9} 42 62 8 noff {0 9} 42 100 non {0 9} 42 95 7 noff {0 9} 42 100 9 non {0 9} 44 86 8 noff {0 9} 44 100 non {0 9} 42 66 7 noff {0 9} 42 100 9 non {0 9} 42 40 9 noff {0 9} 42 100 15 non {0 9} 46 92 8 non {0 9} 42 74 2 noff {0 9} 46 100 6 noff {0 9} 42 100 8 non {0 9} 44 39 8 non {0 9} 42 86 5 noff {0 9} 44 100 6 noff {0 9} 42 100 5 non {0 9} 42 24 6 noff {0 9} 42 100 2 non {0 9} 46 114 8 non {0 9} 42 81 2 noff {0 9} 46 100 10 noff {0 9} 42 100 4 non {0 9} 44 83 12 noff {0 9} 44 100 4 non {0 9} 42 83 5 noff {0 9} 42 100 3 non {0 9} 42 114 13 noff {0 9} 42 100 3 non {0 9} 42 81 8 noff {0 9} 42 100 non {0 9} 44 81 15 noff {0 9} 44 100 1 non {0 9} 42 68 9 noff {0 9} 42 100 15 non {0 9} 46 103 7 noff {0 9} 46 100 } } songtrk ba1 { curfilt bass mute 0 track { 97 non {0 1} 28 65 28 noff {0 1} 28 100 9 non {0 1} 28 49 5 noff {0 1} 28 100 67 non {0 1} 28 56 9 noff {0 1} 28 100 39 non {0 1} 28 47 8 non {0 1} 38 51 5 noff {0 1} 28 100 12 noff {0 1} 38 100 non {0 1} 40 35 8 non {0 1} 28 59 1 noff {0 1} 40 100 30 noff {0 1} 28 100 9 non {0 1} 28 99 8 noff {0 1} 28 100 146 non {0 1} 28 103 32 noff {0 1} 28 100 5 non {0 1} 28 68 6 noff {0 1} 28 100 91 non {0 1} 28 89 8 noff {0 1} 28 100 18 non {0 1} 28 53 7 non {0 1} 34 51 2 noff {0 1} 28 100 15 noff {0 1} 34 100 non {0 1} 35 46 9 noff {0 1} 35 100 non {0 1} 28 62 30 noff {0 1} 28 100 9 non {0 1} 28 83 6 noff {0 1} 28 100 92 non {0 1} 28 68 6 non {0 1} 34 95 3 noff {0 1} 28 100 10 noff {0 1} 34 100 non {0 1} 35 58 9 noff {0 1} 35 100 non {0 1} 38 83 16 noff {0 1} 38 100 1 non {0 1} 40 69 9 non {0 1} 28 60 1 noff {0 1} 40 100 29 noff {0 1} 28 100 9 non {0 1} 28 114 7 noff {0 1} 28 100 41 non {0 1} 28 81 6 noff {0 1} 28 100 64 non {0 1} 28 42 10 non {0 1} 38 120 2 noff {0 1} 28 100 13 noff {0 1} 38 100 2 non {0 1} 40 63 8 non {0 1} 28 56 1 noff {0 1} 40 100 29 noff {0 1} 28 100 9 non {0 1} 28 114 4 noff {0 1} 28 100 116 non {0 1} 28 46 8 non {0 1} 34 66 4 noff {0 1} 28 100 12 noff {0 1} 34 100 non {0 1} 35 37 10 noff {0 1} 35 100 non {0 1} 28 92 32 noff {0 1} 28 100 5 non {0 1} 28 71 7 noff {0 1} 28 100 41 non {0 1} 28 76 9 noff {0 1} 28 100 63 non {0 1} 28 53 8 non {0 1} 38 81 5 noff {0 1} 28 100 11 noff {0 1} 38 100 2 non {0 1} 40 28 6 noff {0 1} 40 100 2 non {0 1} 28 60 15 noff {0 1} 28 100 23 non {0 1} 28 71 9 noff {0 1} 28 100 13 non {0 1} 28 40 9 non {0 1} 38 83 4 noff {0 1} 28 100 12 noff {0 1} 38 100 1 non {0 1} 35 35 6 noff {0 1} 35 100 99 non {0 1} 36 99 12 noff {0 1} 36 100 13 non {0 1} 36 92 21 noff {0 1} 36 100 2 non {0 1} 33 39 10 noff {0 1} 33 100 9 non {0 1} 36 51 10 noff {0 1} 36 100 11 non {0 1} 36 108 33 noff {0 1} 36 100 1 non {0 1} 35 86 15 noff {0 1} 35 100 non {0 1} 36 89 6 noff {0 1} 36 100 28 non {0 1} 36 114 13 non {0 1} 33 54 2 noff {0 1} 36 100 10 noff {0 1} 33 100 21 non {0 1} 30 114 23 noff {0 1} 30 100 non {0 1} 42 76 15 non {0 1} 30 45 1 noff {0 1} 42 100 23 non {0 1} 40 103 2 noff {0 1} 30 100 23 noff {0 1} 40 100 2 non {0 1} 42 120 12 noff {0 1} 42 100 10 non {0 1} 30 95 56 noff {0 1} 30 100 2 non {0 1} 35 108 14 noff {0 1} 35 100 10 non {0 1} 35 74 non {0 1} 33 81 7 noff {0 1} 33 100 18 noff {0 1} 35 100 non {0 1} 47 76 12 non {0 1} 35 26 1 noff {0 1} 47 100 9 non {0 1} 45 89 9 noff {0 1} 35 100 7 noff {0 1} 45 100 non {0 1} 47 103 7 noff {0 1} 47 100 15 non {0 1} 35 72 23 non {0 1} 42 108 2 noff {0 1} 35 100 23 noff {0 1} 42 100 1 non {0 1} 43 49 26 noff {0 1} 43 100 6 non {0 1} 28 99 1 non {0 1} 30 83 2 noff {0 1} 30 100 9 noff {0 1} 28 100 12 non {0 1} 28 108 21 noff {0 1} 28 100 3 non {0 1} 38 95 14 non {0 1} 28 36 1 noff {0 1} 38 100 9 non {0 1} 38 74 5 noff {0 1} 28 100 11 non {0 1} 40 114 3 noff {0 1} 38 100 4 noff {0 1} 40 100 18 non {0 1} 28 103 24 non {0 1} 40 92 2 noff {0 1} 28 100 20 noff {0 1} 40 100 1 non {0 1} 28 58 9 non {0 1} 35 63 5 noff {0 1} 28 100 10 non {0 1} 38 103 1 noff {0 1} 35 100 9 non {0 1} 36 95 2 noff {0 1} 38 100 8 noff {0 1} 36 100 13 non {0 1} 36 108 25 noff {0 1} 36 100 1 non {0 1} 35 74 13 non {0 1} 36 68 1 noff {0 1} 35 100 7 noff {0 1} 36 100 16 non {0 1} 33 127 24 non {0 1} 36 99 1 noff {0 1} 33 100 22 noff {0 1} 36 100 1 non {0 1} 35 89 17 noff {0 1} 35 100 17 non {0 1} 36 103 12 noff {0 1} 36 100 2 non {0 1} 35 51 9 noff {0 1} 35 100 non {0 1} 36 89 9 noff {0 1} 36 100 15 non {0 1} 30 108 30 noff {0 1} 30 100 8 non {0 1} 30 114 9 noff {0 1} 30 100 15 non {0 1} 42 99 22 non {0 1} 30 54 2 noff {0 1} 42 100 8 noff {0 1} 30 100 17 non {0 1} 30 68 11 noff {0 1} 30 100 13 non {0 1} 30 74 9 non {0 1} 40 120 3 noff {0 1} 30 100 12 non {0 1} 42 55 1 noff {0 1} 40 100 10 noff {0 1} 42 100 non {0 1} 35 120 11 noff {0 1} 35 100 11 non {0 1} 35 99 24 noff {0 1} 35 100 1 non {0 1} 45 108 13 noff {0 1} 45 100 non {0 1} 35 62 10 non {0 1} 42 86 2 noff {0 1} 35 100 14 noff {0 1} 42 100 non {0 1} 43 108 6 noff {0 1} 43 100 18 non {0 1} 42 114 23 non {0 1} 40 81 2 noff {0 1} 42 100 21 noff {0 1} 40 100 1 non {0 1} 35 89 20 noff {0 1} 35 100 4 non {0 1} 34 38 6 noff {0 1} 34 100 1 non {0 1} 33 68 15 noff {0 1} 33 100 11 non {0 1} 33 99 13 noff {0 1} 33 100 10 non {0 1} 31 120 15 noff {0 1} 31 100 non {0 1} 30 56 6 noff {0 1} 30 100 17 non {0 1} 28 120 27 non {0 1} 40 108 2 noff {0 1} 28 100 9 noff {0 1} 40 100 13 non {0 1} 28 58 8 non {0 1} 35 54 6 noff {0 1} 28 100 7 noff {0 1} 35 100 non {0 1} 28 81 10 non {0 1} 38 120 3 noff {0 1} 28 100 12 non {0 1} 40 72 non {0 1} 42 61 noff {0 1} 42 100 1 noff {0 1} 38 100 7 noff {0 1} 40 100 non {0 1} 28 86 13 noff {0 1} 28 100 } } songtrk pi2 { curfilt piano mute 0 track { 831 non {0 0} 71 78 9 non {0 0} 74 108 3 noff {0 0} 71 100 11 noff {0 0} 74 100 4 non {0 0} 71 24 7 non {0 0} 74 95 1 noff {0 0} 71 100 11 noff {0 0} 74 100 4 non {0 0} 71 54 non {0 0} 76 53 3 noff {0 0} 71 100 noff {0 0} 76 100 21 non {0 0} 71 74 1 non {0 0} 76 78 17 noff {0 0} 71 100 8 non {0 0} 71 36 2 noff {0 0} 76 100 7 non {0 0} 74 41 2 noff {0 0} 71 100 6 noff {0 0} 74 100 6 non {0 0} 76 54 non {0 0} 71 49 3 noff {0 0} 71 100 1 noff {0 0} 76 100 45 non {0 0} 79 83 1 non {0 0} 71 86 35 noff {0 0} 71 100 11 non {0 0} 71 33 4 noff {0 0} 79 100 4 non {0 0} 74 37 1 noff {0 0} 71 100 13 noff {0 0} 74 100 1 non {0 0} 71 59 non {0 0} 76 61 4 noff {0 0} 71 100 2 noff {0 0} 76 100 17 non {0 0} 71 103 non {0 0} 76 103 29 noff {0 0} 71 100 4 non {0 0} 74 127 5 noff {0 0} 76 100 3 noff {0 0} 74 100 7 non {0 0} 71 39 3 noff {0 0} 71 100 5 non {0 0} 69 76 8 non {0 0} 70 32 1 noff {0 0} 69 100 5 noff {0 0} 70 100 4 non {0 0} 69 23 8 non {0 0} 67 95 1 noff {0 0} 69 100 11 noff {0 0} 67 100 11 non {0 0} 69 89 23 noff {0 0} 69 100 26 non {0 0} 71 92 non {0 0} 74 108 13 noff {0 0} 74 100 3 non {0 0} 76 51 5 noff {0 0} 71 100 noff {0 0} 76 100 17 non {0 0} 71 95 1 non {0 0} 76 114 17 noff {0 0} 71 100 8 noff {0 0} 76 100 2 non {0 0} 71 39 7 non {0 0} 74 78 4 noff {0 0} 71 100 5 noff {0 0} 74 100 5 non {0 0} 76 83 non {0 0} 71 65 9 noff {0 0} 76 100 1 noff {0 0} 71 100 95 non {0 0} 71 103 non {0 0} 74 127 10 noff {0 0} 71 100 3 noff {0 0} 74 100 1 non {0 0} 76 62 12 non {0 0} 74 53 3 noff {0 0} 76 100 7 noff {0 0} 74 100 3 non {0 0} 71 45 4 noff {0 0} 71 100 6 non {0 0} 69 89 6 non {0 0} 70 39 2 noff {0 0} 69 100 5 noff {0 0} 70 100 8 non {0 0} 67 76 12 noff {0 0} 67 100 3 non {0 0} 69 69 7 noff {0 0} 69 100 non {0 0} 70 48 8 noff {0 0} 70 100 1 non {0 0} 71 68 8 non {0 0} 74 76 4 noff {0 0} 71 100 2 noff {0 0} 74 100 3 non {0 0} 71 53 1 non {0 0} 76 103 2 noff {0 0} 71 100 5 noff {0 0} 76 100 9 non {0 0} 79 127 4 noff {0 0} 79 100 27 non {0 0} 78 127 6 noff {0 0} 78 100 2 non {0 0} 79 38 8 non {0 0} 78 26 1 noff {0 0} 79 100 4 noff {0 0} 78 100 3 non {0 0} 76 81 12 noff {0 0} 76 100 3 non {0 0} 77 45 7 noff {0 0} 77 100 non {0 0} 78 74 15 noff {0 0} 78 100 3 non {0 0} 74 103 7 noff {0 0} 74 100 3 non {0 0} 71 39 11 noff {0 0} 71 100 2 non {0 0} 74 44 5 noff {0 0} 74 100 19 non {0 0} 71 114 1 non {0 0} 76 120 31 noff {0 0} 76 100 17 non {0 0} 74 95 7 noff {0 0} 74 100 1 noff {0 0} 71 100 16 non {0 0} 76 114 6 noff {0 0} 76 100 17 non {0 0} 79 127 23 noff {0 0} 79 100 3 non {0 0} 76 32 8 non {0 0} 79 99 14 noff {0 0} 79 100 1 non {0 0} 81 89 5 noff {0 0} 81 100 19 non {0 0} 82 76 7 noff {0 0} 82 100 3 non {0 0} 83 65 15 noff {0 0} 83 100 non {0 0} 81 89 4 noff {0 0} 76 100 107 noff {0 0} 81 100 17 non {0 0} 86 127 11 noff {0 0} 86 100 5 non {0 0} 83 51 5 noff {0 0} 83 100 4 non {0 0} 81 89 7 non {0 0} 82 33 1 noff {0 0} 81 100 5 noff {0 0} 82 100 10 non {0 0} 79 95 10 noff {0 0} 79 100 14 non {0 0} 81 127 16 noff {0 0} 81 100 non {0 0} 79 34 11 non {0 0} 82 92 3 noff {0 0} 79 100 3 noff {0 0} 82 100 2 non {0 0} 83 74 37 noff {0 0} 83 100 non {0 0} 79 103 12 noff {0 0} 79 100 2 non {0 0} 76 30 8 non {0 0} 77 68 2 noff {0 0} 76 100 3 noff {0 0} 77 100 3 non {0 0} 78 74 8 non {0 0} 74 120 3 noff {0 0} 78 100 6 noff {0 0} 74 100 1 non {0 0} 71 45 15 non {0 0} 74 127 2 noff {0 0} 71 100 4 noff {0 0} 74 100 18 non {0 0} 71 103 non {0 0} 76 114 8 noff {0 0} 71 100 1 noff {0 0} 76 100 14 non {0 0} 76 120 1 non {0 0} 71 120 16 noff {0 0} 71 100 7 noff {0 0} 76 100 non {0 0} 71 32 9 non {0 0} 74 127 11 noff {0 0} 71 100 3 noff {0 0} 74 100 3 non {0 0} 76 92 1 non {0 0} 71 76 11 noff {0 0} 71 100 2 noff {0 0} 76 100 44 non {0 0} 86 127 57 noff {0 0} 86 100 2 non {0 0} 84 127 9 noff {0 0} 84 100 1 non {0 0} 83 43 10 noff {0 0} 83 100 4 non {0 0} 84 127 27 noff {0 0} 84 100 non {0 0} 83 53 6 noff {0 0} 83 100 2 non {0 0} 81 36 13 noff {0 0} 81 100 2 non {0 0} 82 76 4 noff {0 0} 82 100 2 non {0 0} 83 81 25 noff {0 0} 83 100 4 non {0 0} 79 95 12 noff {0 0} 79 100 2 non {0 0} 76 41 9 non {0 0} 79 108 8 noff {0 0} 76 100 2 noff {0 0} 79 100 5 non {0 0} 81 54 5 noff {0 0} 81 100 16 non {0 0} 76 99 1 non {0 0} 81 127 23 noff {0 0} 76 100 4 non {0 0} 76 39 2 noff {0 0} 81 100 7 non {0 0} 79 99 12 noff {0 0} 79 100 3 non {0 0} 81 103 7 noff {0 0} 81 100 2 noff {0 0} 76 100 10 non {0 0} 76 20 1 non {0 0} 82 99 5 noff {0 0} 82 100 non {0 0} 83 60 52 noff {0 0} 76 100 2 noff {0 0} 83 100 38 non {0 0} 90 108 10 non {0 0} 87 48 3 noff {0 0} 90 100 8 noff {0 0} 87 100 3 non {0 0} 83 83 6 noff {0 0} 83 100 4 non {0 0} 81 86 5 non {0 0} 82 56 3 noff {0 0} 81 100 4 noff {0 0} 82 100 11 non {0 0} 79 99 13 noff {0 0} 79 100 3 non {0 0} 76 38 9 non {0 0} 79 108 12 noff {0 0} 79 100 5 non {0 0} 81 108 3 noff {0 0} 76 100 noff {0 0} 81 100 20 non {0 0} 76 99 1 non {0 0} 81 120 13 noff {0 0} 76 100 7 noff {0 0} 81 100 3 non {0 0} 76 37 8 non {0 0} 79 108 12 noff {0 0} 79 100 2 non {0 0} 81 78 4 noff {0 0} 81 100 6 non {0 0} 82 114 7 non {0 0} 83 74 2 noff {0 0} 76 100 noff {0 0} 82 100 9 non {0 0} 79 92 3 noff {0 0} 83 100 6 non {0 0} 76 63 1 noff {0 0} 79 100 12 non {0 0} 79 127 5 noff {0 0} 79 100 4 noff {0 0} 76 100 non {0 0} 81 103 7 non {0 0} 79 95 3 noff {0 0} 81 100 4 noff {0 0} 79 100 1 non {0 0} 76 63 8 non {0 0} 77 78 3 noff {0 0} 76 100 3 noff {0 0} 77 100 1 non {0 0} 78 46 7 non {0 0} 74 99 2 noff {0 0} 78 100 9 noff {0 0} 74 100 non {0 0} 71 59 14 non {0 0} 74 127 3 noff {0 0} 71 100 4 noff {0 0} 74 100 18 non {0 0} 71 127 non {0 0} 76 114 31 noff {0 0} 76 100 17 noff {0 0} 71 100 } } songtrk or1 { curfilt organ mute 0 track { 120 non {0 2} 74 69 non {0 2} 67 92 4 noff {0 2} 74 100 noff {0 2} 67 100 44 non {0 2} 74 81 non {0 2} 67 92 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 74 114 non {0 2} 67 103 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 67 108 non {0 2} 74 103 5 noff {0 2} 67 100 noff {0 2} 74 100 43 non {0 2} 74 114 non {0 2} 67 95 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 74 114 non {0 2} 67 108 5 noff {0 2} 74 100 1 noff {0 2} 67 100 42 non {0 2} 67 95 non {0 2} 74 99 5 noff {0 2} 67 100 noff {0 2} 74 100 43 non {0 2} 67 103 non {0 2} 74 108 5 noff {0 2} 74 100 1 noff {0 2} 67 100 42 non {0 2} 67 95 non {0 2} 74 114 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 67 95 non {0 2} 74 103 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 67 103 non {0 2} 74 127 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 74 127 non {0 2} 67 95 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 67 103 non {0 2} 74 127 5 noff {0 2} 74 100 1 noff {0 2} 67 100 43 non {0 2} 67 114 non {0 2} 74 120 5 noff {0 2} 67 100 noff {0 2} 74 100 42 non {0 2} 67 99 1 non {0 2} 74 108 6 noff {0 2} 67 100 noff {0 2} 74 100 42 non {0 2} 67 108 non {0 2} 74 127 5 noff {0 2} 74 100 1 noff {0 2} 67 100 41 non {0 2} 67 99 non {0 2} 74 114 5 noff {0 2} 67 100 noff {0 2} 74 100 43 non {0 2} 67 99 non {0 2} 74 120 5 noff {0 2} 67 100 noff {0 2} 74 100 43 non {0 2} 67 108 non {0 2} 74 127 4 noff {0 2} 67 100 1 noff {0 2} 74 100 43 non {0 2} 67 99 non {0 2} 74 120 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 74 127 non {0 2} 67 103 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 74 108 non {0 2} 67 99 6 noff {0 2} 74 100 noff {0 2} 67 100 42 non {0 2} 67 108 non {0 2} 74 114 7 noff {0 2} 74 100 1 noff {0 2} 67 100 40 non {0 2} 67 108 non {0 2} 74 103 5 noff {0 2} 74 100 1 noff {0 2} 67 100 43 non {0 2} 67 103 non {0 2} 74 127 5 noff {0 2} 67 100 noff {0 2} 74 100 42 non {0 2} 67 99 non {0 2} 74 103 5 noff {0 2} 74 100 2 noff {0 2} 67 100 41 non {0 2} 67 95 non {0 2} 74 103 6 noff {0 2} 67 100 noff {0 2} 74 100 42 non {0 2} 74 108 non {0 2} 67 83 5 noff {0 2} 74 100 noff {0 2} 67 100 43 non {0 2} 67 95 non {0 2} 74 114 5 noff {0 2} 67 100 1 noff {0 2} 74 100 42 non {0 2} 67 95 non {0 2} 74 120 6 noff {0 2} 67 100 1 noff {0 2} 74 100 41 non {0 2} 74 127 non {0 2} 67 120 6 noff {0 2} 74 100 1 noff {0 2} 67 100 41 non {0 2} 67 103 1 non {0 2} 74 114 4 noff {0 2} 74 100 1 noff {0 2} 67 100 42 non {0 2} 71 103 non {0 2} 76 108 6 noff {0 2} 71 100 noff {0 2} 76 100 42 non {0 2} 71 103 1 non {0 2} 76 103 4 noff {0 2} 76 100 2 noff {0 2} 71 100 41 non {0 2} 71 99 non {0 2} 76 114 5 noff {0 2} 76 100 3 noff {0 2} 71 100 40 non {0 2} 76 114 non {0 2} 71 103 6 noff {0 2} 76 100 2 noff {0 2} 71 100 40 non {0 2} 76 92 non {0 2} 69 89 6 noff {0 2} 69 100 2 noff {0 2} 76 100 41 non {0 2} 76 108 non {0 2} 69 127 4 noff {0 2} 76 100 1 noff {0 2} 69 100 42 non {0 2} 76 92 non {0 2} 69 114 6 noff {0 2} 76 100 noff {0 2} 69 100 42 non {0 2} 76 103 non {0 2} 69 95 6 noff {0 2} 76 100 noff {0 2} 69 100 42 non {0 2} 69 99 non {0 2} 75 114 5 noff {0 2} 75 100 2 noff {0 2} 69 100 41 non {0 2} 69 99 non {0 2} 75 103 7 noff {0 2} 75 100 1 noff {0 2} 69 100 40 non {0 2} 69 95 non {0 2} 75 103 6 noff {0 2} 75 100 3 noff {0 2} 69 100 39 non {0 2} 69 103 non {0 2} 75 108 8 noff {0 2} 69 100 noff {0 2} 75 100 40 non {0 2} 74 127 non {0 2} 67 108 6 noff {0 2} 74 100 noff {0 2} 67 100 42 non {0 2} 74 127 non {0 2} 67 114 5 noff {0 2} 74 100 1 noff {0 2} 67 100 42 non {0 2} 67 103 non {0 2} 74 127 5 noff {0 2} 74 100 1 noff {0 2} 67 100 42 non {0 2} 67 95 non {0 2} 74 114 5 noff {0 2} 67 100 noff {0 2} 74 100 44 non {0 2} 71 99 non {0 2} 76 120 6 noff {0 2} 71 100 noff {0 2} 76 100 41 non {0 2} 71 86 non {0 2} 76 103 5 noff {0 2} 76 100 1 noff {0 2} 71 100 42 non {0 2} 71 99 non {0 2} 76 114 4 noff {0 2} 76 100 2 noff {0 2} 71 100 42 non {0 2} 71 103 non {0 2} 76 114 5 noff {0 2} 76 100 2 noff {0 2} 71 100 41 non {0 2} 76 108 1 non {0 2} 69 114 6 noff {0 2} 76 100 noff {0 2} 69 100 41 non {0 2} 76 114 1 non {0 2} 69 127 5 noff {0 2} 76 100 noff {0 2} 69 100 42 non {0 2} 76 103 non {0 2} 69 95 5 noff {0 2} 69 100 2 noff {0 2} 76 100 41 non {0 2} 76 103 non {0 2} 69 127 6 noff {0 2} 69 100 1 noff {0 2} 76 100 41 non {0 2} 69 103 non {0 2} 75 95 5 noff {0 2} 75 100 1 noff {0 2} 69 100 42 non {0 2} 69 120 non {0 2} 75 120 5 noff {0 2} 69 100 1 noff {0 2} 75 100 43 non {0 2} 69 92 non {0 2} 75 114 5 noff {0 2} 69 100 1 noff {0 2} 75 100 41 non {0 2} 69 120 non {0 2} 75 120 5 noff {0 2} 75 100 2 noff {0 2} 69 100 41 non {0 2} 74 103 non {0 2} 67 99 6 noff {0 2} 74 100 noff {0 2} 67 100 42 non {0 2} 67 89 non {0 2} 74 108 5 noff {0 2} 74 100 1 noff {0 2} 67 100 42 non {0 2} 67 103 non {0 2} 74 127 5 noff {0 2} 74 100 1 noff {0 2} 67 100 42 non {0 2} 67 99 non {0 2} 74 103 6 noff {0 2} 74 100 1 noff {0 2} 67 100 } } songtrk st1 { curfilt strings mute 0 track { 864 non {0 4} 71 41 1 non {0 4} 67 28 750 noff {0 4} 71 100 3 noff {0 4} 67 100 13 non {0 4} 67 37 1 non {0 4} 72 44 191 noff {0 4} 67 100 4 non {0 4} 69 39 non {0 4} 66 39 178 noff {0 4} 69 100 1 noff {0 4} 72 100 noff {0 4} 66 100 10 non {0 4} 71 40 non {0 4} 66 45 180 noff {0 4} 66 100 10 noff {0 4} 71 100 4 non {0 4} 67 49 non {0 4} 71 53 181 noff {0 4} 67 100 3 noff {0 4} 71 100 8 non {0 4} 67 44 non {0 4} 72 51 180 noff {0 4} 67 100 12 non {0 4} 66 48 184 noff {0 4} 72 100 5 non {0 4} 71 44 176 noff {0 4} 66 100 18 non {0 4} 67 33 189 noff {0 4} 71 100 noff {0 4} 67 100 } } songsx gm_on { sysex { unit 0 data 0xf0 0x7e 0x7f 0x09 0x01 0xf7 } } curtrk pi1 curfilt piano cursx gm_on curin kbd curout piano curpos 0 curlen 0 curquant 0 curev any {0..15 0..15} metro { mask rec lo non {0 9} 68 90 hi non {0 9} 67 127 } } midish-1.4.0/saveload.c010066400017510001751000001374111501104363400134500ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include "utils.h" #include "name.h" #include "mididev.h" #include "song.h" #include "filt.h" #include "textio.h" #include "saveload.h" #include "frame.h" #include "conv.h" #include "version.h" #include "cons.h" #define FORMAT_VERSION 1 void chan_output(unsigned dev, unsigned ch, struct textout *f) { textout_putstr(f, "{"); textout_putlong(f, dev); textout_putstr(f, " "); textout_putlong(f, ch % 16); textout_putstr(f, "}"); } void ev_output(struct ev *e, struct textout *f) { /* XXX: use ev_getstr() */ switch(e->cmd) { case EV_NOFF: textout_putstr(f, "noff"); goto two; case EV_NON: textout_putstr(f, "non"); goto two; case EV_CAT: textout_putstr(f, "cat"); goto one; case EV_XCTL: if (e->ctl_num < 32) { textout_putstr(f, "xctl"); goto two; } else { textout_putstr(f, "ctl"); textout_putstr(f, " "); chan_output(e->dev, e->ch, f); textout_putstr(f, " "); textout_putlong(f, e->ctl_num); textout_putstr(f, " "); textout_putlong(f, e->ctl_val >> 7); } break; case EV_XPC: textout_putstr(f, "xpc"); textout_putstr(f, " "); chan_output(e->dev, e->ch, f); textout_putstr(f, " "); if (e->pc_bank != EV_UNDEF) { textout_putlong(f, e->pc_bank); } else { textout_putstr(f, "nil"); } textout_putstr(f, " "); textout_putlong(f, e->pc_prog); return; case EV_RPN: textout_putstr(f, "rpn"); goto two; case EV_NRPN: textout_putstr(f, "nrpn"); goto two; case EV_KAT: textout_putstr(f, "kat"); goto two; case EV_BEND: textout_putstr(f, "bend"); textout_putstr(f, " "); chan_output(e->dev, e->ch, f); textout_putstr(f, " "); textout_putlong(f, e->bend_val & 0x7f); textout_putstr(f, " "); textout_putlong(f, e->bend_val >> 7); break; case EV_TEMPO: textout_putstr(f, "tempo"); textout_putstr(f, " "); textout_putlong(f, e->tempo_usec24); break; case EV_TIMESIG: textout_putstr(f, "timesig"); textout_putstr(f, " "); textout_putlong(f, e->timesig_beats); textout_putstr(f, " "); textout_putlong(f, e->timesig_tics); break; default: if (EV_ISSX(e)) { textout_putstr(f, evinfo[e->cmd].ev); textout_putstr(f, " "); textout_putlong(f, e->dev); if (evinfo[e->cmd].nparams >= 1) { textout_putstr(f, " "); textout_putlong(f, e->v0); } if (evinfo[e->cmd].nparams >= 2) { textout_putstr(f, " "); textout_putlong(f, e->v1); } } else { textout_putstr(f, "# ignored event\n"); log_puts("ignoring event: "); ev_log(e); log_puts("\n"); } break; } return; two: textout_putstr(f, " "); chan_output(e->dev, e->ch, f); textout_putstr(f, " "); textout_putlong(f, e->v0); textout_putstr(f, " "); textout_putlong(f, e->v1); if (e->cmd == EV_XCTL) { textout_putstr(f, " # "); textout_putlong(f, e->v1 >> 7); } return; one: textout_putstr(f, " "); chan_output(e->dev, e->ch, f); textout_putstr(f, " "); textout_putlong(f, e->v0); return; } void range_output(unsigned min, unsigned max, struct textout *f) { textout_putlong(f, min); if (min != max) { textout_putstr(f, ".."); textout_putlong(f, max); } } void evspec_output(struct evspec *o, struct textout *f) { textout_putstr(f, evinfo[o->cmd].spec); if ((evinfo[o->cmd].flags & EV_HAS_DEV) && (evinfo[o->cmd].flags & EV_HAS_CH)) { textout_putstr(f, " {"); range_output(o->dev_min, o->dev_max, f); textout_putstr(f, " "); range_output(o->ch_min, o->ch_max, f); textout_putstr(f, "}"); } else if (evinfo[o->cmd].flags & EV_HAS_DEV) { textout_putstr(f, " "); range_output(o->dev_min, o->dev_max, f); } if (evinfo[o->cmd].nranges >= 1) { textout_putstr(f, " "); if (o->v0_min == EV_UNDEF) { /* XPC is allowed to have v0 = UNDEF */ textout_putstr(f, "nil"); } else range_output(o->v0_min, o->v0_max, f); } if (evinfo[o->cmd].nranges >= 2) { textout_putstr(f, " "); range_output(o->v1_min, o->v1_max, f); } } void track_output(struct track *t, struct textout *f) { struct seqev *i; textout_putstr(f, "{\n"); textout_shiftright(f); for (i = t->first; i != NULL; i = i->next) { if (i->delta != 0) { textout_putlong(f, i->delta); textout_putstr(f, "\n"); } if (i->ev.cmd == EV_NULL) { break; } ev_output(&i->ev, f); textout_putstr(f, "\n"); } textout_shiftleft(f); textout_putstr(f, "}"); } void filt_output(struct filt *o, struct textout *f) { struct filtnode *s, *snext; struct filtnode *d; textout_putstr(f, "{\n"); textout_shiftright(f); snext = 0; for (;;) { if (snext == o->map) break; for (s = o->map; s->next != snext; s = s->next) { /* nothing */ } for (d = s->dstlist; d != NULL; d = d->next) { textout_putstr(f, "evmap "); evspec_output(&s->es, f); textout_putstr(f, " > "); evspec_output(&d->es, f); textout_putstr(f, "\n"); } snext = s; } for (d = o->transp; d != NULL; d = d->next) { textout_putstr(f, "transp "); evspec_output(&d->es, f); textout_putstr(f, " "); textout_putlong(f, d->u.transp.plus & 0x7f); textout_putstr(f, "\n"); } for (d = o->vcurve; d != NULL; d = d->next) { textout_putstr(f, "vcurve "); evspec_output(&d->es, f); textout_putstr(f, " "); textout_putlong(f, (64 - d->u.vel.nweight) & 0x7f); textout_putstr(f, "\n"); } textout_shiftleft(f); textout_putstr(f, "}"); } void sysex_output(struct sysex *o, struct textout *f) { struct chunk *c; unsigned i, col; textout_putstr(f, "{\n"); textout_shiftright(f); textout_putstr(f, "unit "); textout_putlong(f, o->unit); textout_putstr(f, "\n"); textout_putstr(f, "data\t"); textout_shiftright(f); col = 0; for (c = o->first; c != NULL; c = c->next) { for (i = 0; i < c->used; i++) { textout_putbyte(f, c->data[i]); if (i + 1 < c->used || c->next != NULL) { col++; if (col >= 8) { col = 0; textout_putstr(f, " \\\n"); } else { textout_putstr(f, " "); } } } } textout_putstr(f, "\n"); textout_shiftleft(f); textout_shiftleft(f); textout_putstr(f, "}"); } void songsx_output(struct songsx *o, struct textout *f) { struct sysex *i; textout_putstr(f, "{\n"); textout_shiftright(f); for (i = o->sx.first; i != NULL; i = i->next) { textout_putstr(f, "sysex "); sysex_output(i, f); textout_putstr(f, "\n"); } textout_shiftleft(f); textout_putstr(f, "}"); } void songtrk_output(struct songtrk *o, struct textout *f) { textout_putstr(f, "{\n"); textout_shiftright(f); if (o->curfilt) { textout_putstr(f, "curfilt "); textout_putstr(f, o->curfilt->name.str); textout_putstr(f, "\n"); } textout_putstr(f, "mute "); textout_putlong(f, o->mute); textout_putstr(f, "\n"); textout_putstr(f, "track "); track_output(&o->track, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_putstr(f, "}"); } void songchan_output(struct songchan *o, struct textout *f) { textout_putstr(f, "{\n"); textout_shiftright(f); textout_putstr(f, "chan "); chan_output(o->dev, o->ch, f); textout_putstr(f, "\n"); textout_putstr(f, "conf "); track_output(&o->conf, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_putstr(f, "}"); } void songfilt_output(struct songfilt *o, struct textout *f) { textout_putstr(f, "{\n"); textout_shiftright(f); textout_putstr(f, "filt "); filt_output(&o->filt, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_putstr(f, "}"); } void metro_output(struct metro *o, struct textout *f) { char *mstr; if (o->mask & (1 << SONG_PLAY)) { mstr = "on"; } else if (o->mask & (1 << SONG_REC)) { mstr = "rec"; } else { mstr = "off"; } textout_putstr(f, "{\n"); textout_shiftright(f); textout_putstr(f, "mask\t"); textout_putstr(f, mstr); textout_putstr(f, "\n"); textout_putstr(f, "lo\t"); ev_output(&o->lo, f); textout_putstr(f, "\n"); textout_putstr(f, "hi\t"); ev_output(&o->hi, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_putstr(f, "}"); } void evctltab_output(struct evctl *tab, struct textout *f) { unsigned i; struct evctl *ctl; textout_putstr(f, "ctltab {\n"); textout_shiftright(f); textout_putstr(f, "#\n"); textout_putstr(f, "# name\tnumber\tdefval\n"); textout_putstr(f, "#\n"); for (i = 0; i < EV_MAXCOARSE + 1; i++) { ctl = &tab[i]; if (ctl->name) { textout_putstr(f, ctl->name); textout_putstr(f, "\t"); textout_putlong(f, i); textout_putstr(f, "\t"); if (ctl->defval == EV_UNDEF) { textout_putstr(f, "nil"); } else { textout_putlong(f, ctl->defval); } textout_putstr(f, "\n"); } } textout_shiftleft(f); textout_putstr(f, "}\n"); } void evpat_output(struct textout *f) { unsigned char *p; unsigned i; for (i = 0; i < EV_NPAT; i++) { if (evinfo[EV_PAT0 + i].ev == NULL) continue; textout_putstr(f, "evpat "); textout_putstr(f, evinfo[EV_PAT0 + i].ev); textout_putstr(f, " {\n"); textout_shiftright(f); textout_putstr(f, "pattern"); p = evinfo[EV_PAT0 + i].pattern; for (;;) { textout_putstr(f, " "); switch (*p) { case EV_PATV0_HI: textout_putstr(f, "v0_hi"); break; case EV_PATV0_LO: textout_putstr(f, "v0_lo"); break; case EV_PATV1_HI: textout_putstr(f, "v1_hi"); break; case EV_PATV1_LO: textout_putstr(f, "v1_lo"); break; default: textout_putbyte(f, *p); if (*p == 0xf7) goto end; } p++; } end: textout_putstr(f, "\n"); textout_shiftleft(f); textout_putstr(f, "}\n"); } } void song_output(struct song *o, struct textout *f) { struct songtrk *t; struct songchan *i; struct songfilt *g; struct songsx *s; textout_putstr(f, "{\n"); textout_shiftright(f); textout_putstr(f, "format "); textout_putlong(f, FORMAT_VERSION); textout_putstr(f, "\n"); textout_putstr(f, "tics_per_unit "); textout_putlong(f, o->tics_per_unit); textout_putstr(f, "\n"); textout_putstr(f, "tempo_factor "); textout_putlong(f, o->tempo_factor); textout_putstr(f, "\n"); textout_putstr(f, "meta "); track_output(&o->meta, f); textout_putstr(f, "\n"); evpat_output(f); SONG_FOREACH_CHAN(o, i) { textout_putstr(f, i->isinput ? "songin " : "songout "); textout_putstr(f, i->name.str); textout_putstr(f, " "); songchan_output(i, f); textout_putstr(f, "\n"); } SONG_FOREACH_FILT(o, g) { textout_putstr(f, "songfilt "); textout_putstr(f, g->name.str); textout_putstr(f, " "); songfilt_output(g, f); textout_putstr(f, "\n"); } SONG_FOREACH_TRK(o, t) { textout_putstr(f, "songtrk "); textout_putstr(f, t->name.str); textout_putstr(f, " "); songtrk_output(t, f); textout_putstr(f, "\n"); } SONG_FOREACH_SX(o, s) { textout_putstr(f, "songsx "); textout_putstr(f, s->name.str); textout_putstr(f, " "); songsx_output(s, f); textout_putstr(f, "\n"); } if (o->curtrk) { textout_putstr(f, "curtrk "); textout_putstr(f, o->curtrk->name.str); textout_putstr(f, "\n"); } if (o->curfilt) { textout_putstr(f, "curfilt "); textout_putstr(f, o->curfilt->name.str); textout_putstr(f, "\n"); } if (o->cursx) { textout_putstr(f, "cursx "); textout_putstr(f, o->cursx->name.str); textout_putstr(f, "\n"); } if (o->curin) { textout_putstr(f, "curin "); textout_putstr(f, o->curin->name.str); textout_putstr(f, "\n"); } if (o->curout) { textout_putstr(f, "curout "); textout_putstr(f, o->curout->name.str); textout_putstr(f, "\n"); } textout_putstr(f, "curpos "); textout_putlong(f, o->curpos); textout_putstr(f, "\n"); textout_putstr(f, "curlen "); textout_putlong(f, o->curlen); textout_putstr(f, "\n"); textout_putstr(f, "curquant "); textout_putlong(f, o->curquant); textout_putstr(f, "\n"); textout_putstr(f, "curev "); evspec_output(&o->curev, f); textout_putstr(f, "\n"); textout_putstr(f, "metro "); metro_output(&o->metro, f); textout_putstr(f, "\n"); textout_putstr(f, "tap "); textout_putstr(f, song_tap_modestr[o->tap_mode]); textout_putstr(f, "\n"); textout_putstr(f, "tapev "); evspec_output(&o->tap_evspec, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_putstr(f, "}"); } /* ---------------------------------------------------------------------- */ enum SYM_ID { TOK_EOF = 0, TOK_LBRACE, TOK_RBRACE, TOK_LT, TOK_GT, TOK_NIL, TOK_RANGE, TOK_ENDLINE, TOK_WORD, TOK_NUM }; struct load { unsigned id; #define TOK_MAXLEN 31 char strval[TOK_MAXLEN + 1]; unsigned long longval; struct textin *in; /* input file */ int lookchar; /* used by ungetchar */ unsigned line, col; /* for error reporting */ unsigned lookavail; int format; }; unsigned load_getsym(struct load *); void load_ungetsym(struct load *); /* ----------------------------------------------------- tokdefs --- */ unsigned load_getchar(struct load *o, int *c) { if (o->lookchar < 0) { textin_getpos(o->in, &o->line, &o->col); if (!textin_getchar(o->in, c)) { return 0; } } else { *c = o->lookchar; o->lookchar = -1; } return 1; } void load_ungetchar(struct load *o, int c) { if (o->lookchar >= 0) { log_puts("load_ungetchar: lookchar already set\n"); panic(); } o->lookchar = c; } void load_err(struct load *o, char *msg) { cons_erruu(o->line + 1, o->col + 1, msg); } unsigned load_scan(struct load *o) { int c, cn; unsigned i, dig, base; unsigned long val, maxq, maxr; for (;;) { if (!load_getchar(o, &c)) return 0; if (c == CHAR_EOF) { o->id = TOK_EOF; return 1; } /* check if line continues */ if (c == '\\') { do { if (!load_getchar(o, &c)) return 0; } while (c == ' ' || c == '\t' || c == '\r'); if (c == '\n') continue; if (c == CHAR_EOF) { load_ungetchar(o, c); continue; } load_ungetchar(o, c); load_err(o, "newline exected after '\\'"); return 0; } /* skip comments */ if (c == '#') { do { if (!load_getchar(o, &c)) return 0; } while (c != '\n' && c != CHAR_EOF); load_ungetchar(o, c); continue; } if (c >= '0' && c <= '9') { base = 10; if (c == '0') { if (!load_getchar(o, &c)) return 0; if (c == 'x' || c == 'X') { base = 16; if (!load_getchar(o, &c)) return 0; if ((c < '0' || c > '9') && (c < 'A' || c > 'F') && (c < 'a' || c > 'f')) { load_ungetchar(o, c); load_err(o, "bad hex number"); return 0; } } } val = 0; maxq = ULONG_MAX / base; maxr = ULONG_MAX % base; for (;;) { if (c >= 'a' && c <= 'z') { dig = 10 + c - 'a'; } else if (c >= 'A' && c <= 'Z') { dig = 10 + c - 'A'; } else if (c >= '0' && c <= '9') { dig = c - '0'; } else { load_ungetchar(o, c); break; } if (dig >= base) { load_err(o, "bad number"); return 0; } if ((val > maxq) || (val == maxq && dig > maxr)) { load_err(o, "number too large"); return 0; } val = val * base + dig; if (!load_getchar(o, &c)) return 0; } o->longval = val; o->id = TOK_NUM; return 1; } if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_')) { i = 0; for (;;) { if (i >= TOK_MAXLEN) { load_err(o, "word too long"); return 0; } o->strval[i++] = c; if (!load_getchar(o, &c)) { return 0; } if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && (c != '_')) { o->strval[i++] = '\0'; load_ungetchar(o, c); break; } } if (str_eq(o->strval, "nil")) o->id = TOK_NIL; else o->id = TOK_WORD; return 1; } switch (c) { case ' ': case '\t': case '\r': continue; case '\n': o->id = TOK_ENDLINE; return 1; case '{': o->id = TOK_LBRACE; return 1; case '}': o->id = TOK_RBRACE; return 1; case '<': o->id = TOK_LT; return 1; case '>': o->id = TOK_GT; return 1; case '.': if (!load_getchar(o, &cn)) return 0; if (cn == '.') { o->id = TOK_RANGE; return 1; } load_ungetchar(o, cn); } load_err(o, "bad token"); return 0; } /* not reached */ } int load_init(struct load *o, char *filename) { o->lookchar = -1; o->in = textin_new(filename); if (!o->in) return 0; o->lookavail = 0; o->format = 0; return 1; } void load_done(struct load *o) { textin_delete(o->in); } unsigned load_getsym(struct load *o) { if (o->lookavail) { o->lookavail = 0; return 1; } return load_scan(o); } void load_ungetsym(struct load *o) { if (o->lookavail) { log_puts("load_ungetsym: looksym already set\n"); panic(); } o->lookavail = 1; } unsigned load_ukline(struct load *o); unsigned load_ukblock(struct load *o); unsigned load_delta(struct load *o, unsigned *delta); unsigned load_ev(struct load *o, struct ev *ev); unsigned load_track(struct load *o, struct track *t); unsigned load_songtrk(struct load *o, struct song *s, struct songtrk *t); unsigned load_song(struct load *o, struct song *s); unsigned load_ukline(struct load *o) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD && o->id != TOK_NUM && o->id != TOK_ENDLINE) { load_err(o, "ident, number, newline or '}' expected"); return 0; } load_ungetsym(o); for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_RBRACE || o->id == TOK_EOF) { load_ungetsym(o); break; } else if (o->id == TOK_ENDLINE) { break; } else if (o->id == TOK_LBRACE) { load_ungetsym(o); if (!load_ukblock(o)) return 0; } } return 1; } unsigned load_ukblock(struct load *o) { if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing block"); return 0; } for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_RBRACE) { break; } else { load_ungetsym(o); if (!load_ukline(o)) return 0; } } return 1; } unsigned load_nl(struct load *o) { if (!load_getsym(o)) return 0; if (o->id == TOK_RBRACE || o->id != TOK_EOF) { load_ungetsym(o); } else if (o->id != TOK_ENDLINE) { load_err(o, "new line expected"); return 0; } return 1; } unsigned load_empty(struct load *o) { for (;;) { if (!load_getsym(o)) return 0; if (o->id != TOK_ENDLINE) { load_ungetsym(o); break; } } return 1; } unsigned load_long(struct load *o, unsigned long min, unsigned long max, unsigned long *data) { if (!load_getsym(o)) return 0; if (o->id != TOK_NUM) { load_err(o, "number expected"); return 0; } if (o->longval < min || o->longval > max) { load_err(o, "number out of allowed range"); return 0; } *data = (unsigned)o->longval; return 1; } unsigned load_delta(struct load *o, unsigned *delta) { if (!load_getsym(o)) return 0; if (o->id != TOK_NUM) { load_err(o, "numeric constant expected in event offset"); return 0; } *delta = o->longval; if (!load_nl(o)) return 0; return 1; } unsigned load_chan(struct load *o, unsigned long *dev, unsigned long *ch) { if (!load_getsym(o)) return 0; if (o->id == TOK_LBRACE) { if (!load_long(o, 0, EV_MAXDEV, dev)) return 0; if (!load_long(o, 0, EV_MAXCH, ch)) return 0; if (!load_getsym(o)) return 0; if (o->id != TOK_RBRACE) { load_err(o, "'}' expected in channel spec"); return 0; } return 1; } else if (o->id == TOK_NUM) { *dev = o->longval / (EV_MAXCH + 1); *ch = o->longval % (EV_MAXCH + 1); if (*dev > EV_MAXDEV) { load_err(o, "dev/chan out of range"); return 0; } return 1; } else { load_err(o, "bad channel spec"); return 0; } } unsigned load_bank(struct load *o, unsigned long *rbank) { if (!load_getsym(o)) return 0; if (o->id == TOK_NIL) { *rbank = EV_UNDEF; } else { load_ungetsym(o); if (!load_long(o, 0, EV_MAXFINE, rbank)) return 0; } return 1; } unsigned load_ev(struct load *o, struct ev *ev) { unsigned long val, val2; struct evinfo *ei; if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "event name expected"); return 0; } if (!ev_str2cmd(ev, o->strval)) { ignore: load_ungetsym(o); if (!load_ukline(o)) { return 0; } load_err(o, "unknown event, ignored"); ev->cmd = EV_NULL; return 1; } if (EV_ISVOICE(ev)) { if (!load_chan(o, &val, &val2)) { return 0; } ev->dev = val; ev->ch = val2; } switch (ev->cmd) { case EV_TEMPO: if (!load_long(o, TEMPO_MIN, TEMPO_MAX, &val)) { return 0; } ev->tempo_usec24 = val; break; case EV_TIMESIG: if (!load_long(o, 1, TIMESIG_BEATS_MAX, &val)) { return 0; } ev->timesig_beats = val; if (!load_long(o, 1, TIMESIG_TICS_MAX, &val)) { return 0; } ev->timesig_tics = val; break; case EV_NRPN: case EV_RPN: if (!load_long(o, 0, EV_MAXFINE, &val)) { return 0; } ev->rpn_num = val; if (!load_long(o, 0, EV_MAXFINE, &val)) { return 0; } ev->rpn_val = val; break; case EV_XCTL: if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->ctl_num = val; if (!load_long(o, 0, EV_MAXFINE, &val)) { return 0; } ev->ctl_val = val; break; case EV_XPC: /* on older versions prog and bank are swapped */ if (o->format <= 0) { if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->pc_prog = val; if (!load_bank(o, &val)) { return 0; } ev->pc_bank = val; } else { if (!load_bank(o, &val)) { return 0; } ev->pc_bank = val; if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->pc_prog = val; } break; case EV_NON: case EV_NOFF: case EV_CTL: if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 = val; if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v1 = val; break; case EV_KAT: if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 = val; /* * XXX: midish version < 0.2.6 used to * generate bogus kat events (without the last * byte). As workaround, we ignore such * events, in order to allow user to load its * files. Remove this code when no more needed */ if (!load_getsym(o)) return 0; if (o->id != TOK_NUM) { load_ungetsym(o); if (!load_nl(o)) { return 0; } ev->cmd = EV_NULL; return 1; } load_ungetsym(o); if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v1 = val; break; case EV_PC: if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->cmd = EV_XPC; ev->v0 = EV_UNDEF; ev->v1 = val; break; case EV_CAT: if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 = val; break; case EV_BEND: if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 = val; if (!load_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 += (val << 7); break; default: if (EV_ISSX(ev) && evinfo[ev->cmd].ev != NULL) { ei = evinfo + ev->cmd; if (!load_long(o, 0, EV_MAXDEV, &val)) return 0; ev->dev = val; if (ei->nparams >= 1) { if (!load_long(o, 0, ei->v0_max, &val)) return 0; ev->v0 = val; } if (ei->nparams >= 2) { if (!load_long(o, 0, ei->v1_max, &val)) return 0; ev->v1 = val; } } else goto ignore; } if (!load_nl(o)) { return 0; } return 1; } unsigned load_track(struct load *o, struct track *t) { unsigned delta; struct seqev *pos, *se; struct statelist slist; struct ev ev, rev; struct mididev *dev; unsigned int xctlset, evset; if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing track"); return 0; } track_clear(t); statelist_init(&slist); pos = t->first; for (;;) { if (!load_getsym(o)) { statelist_done(&slist); return 0; } if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_NUM) { load_ungetsym(o); if (!load_delta(o, &delta)) { statelist_done(&slist); return 0; } pos->delta += delta; } else { load_ungetsym(o); if (!load_ev(o, &ev)) { statelist_done(&slist); return 0; } if (ev.cmd != EV_NULL) { /* * Pack events according to device setup. * * XXX: this needs to be done in * doevset/doxctl by converting the whole * project whenever events configuration * is changed. */ if ((evinfo[ev.cmd].flags & EV_HAS_DEV) && (dev = mididev_byunit[ev.dev]) != NULL) { xctlset = dev->oxctlset; evset = dev->oevset; } else { xctlset = 0; evset = CONV_XPC | CONV_NRPN | CONV_RPN; } if (conv_packev(&slist, xctlset, evset, &ev, &rev)) { se = seqev_new(); se->ev = rev; seqev_ins(pos, se); } } } } statelist_done(&slist); return 1; } unsigned load_range(struct load *o, unsigned min, unsigned max, unsigned *rmin, unsigned *rmax) { unsigned long tmin, tmax; if (!load_long(o, min, max, &tmin)) return 0; if (!load_getsym(o)) return 0; if (o->id != TOK_RANGE) { load_ungetsym(o); *rmin = *rmax = tmin; return 1; } if (!load_long(o, min, max, &tmax)) return 0; *rmin = tmin; *rmax = tmax; return 1; } unsigned load_evspec(struct load *o, struct evspec *es) { struct evinfo *info; if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "event spec name expected"); return 0; } if (!evspec_str2cmd(es, o->strval)) { load_err(o, "event spec name matches no event"); return 0; } info = &evinfo[es->cmd]; es->v0_min = evinfo[es->cmd].v0_min; es->v0_max = evinfo[es->cmd].v0_max; es->v1_min = evinfo[es->cmd].v1_min; es->v1_max = evinfo[es->cmd].v1_max; if ((info->flags & EV_HAS_DEV) && (info->flags & EV_HAS_CH)) { if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected"); return 0; } if (!load_range(o, 0, EV_MAXDEV, &es->dev_min, &es->dev_max) || !load_range(o, 0, EV_MAXCH, &es->ch_min, &es->ch_max)) return 0; if (!load_getsym(o)) return 0; if (o->id != TOK_RBRACE) { load_err(o, "'}' expected"); return 0; } } else if (info->flags & EV_HAS_DEV) { if (!load_range(o, 0, EV_MAXDEV, &es->dev_min, &es->dev_max)) return 0; es->ch_min = es->ch_max = 0; } if (info->nranges == 0) return 1; if (!load_getsym(o)) return 0; if (o->id == TOK_NIL) { es->v0_min = es->v0_max = EV_UNDEF; } else { load_ungetsym(o); if (!load_range(o, 0, info->v0_max, &es->v0_min, &es->v0_max)) return 0; } if (info->nranges == 1) return 1; if (!load_range(o, 0, info->v1_max, &es->v1_min, &es->v1_max)) return 0; return 1; } unsigned load_rule(struct load *o, struct filt *f) { unsigned long idev, ich, odev, och, ictl, octl, keylo, keyhi, ukeyplus; struct evspec from, to; int keyplus; if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "filter-type identifier expected"); return 0; } if (str_eq(o->strval, "keydrop")) { if (!load_chan(o, &idev, &ich)) return 0; if (!load_long(o, 0, EV_MAXCOARSE, &keylo)) return 0; if (!load_long(o, 0, EV_MAXCOARSE, &keyhi)) return 0; evspec_reset(&from); from.cmd = EVSPEC_NOTE; from.dev_min = from.dev_max = idev; from.ch_min = from.ch_max = ich; from.v0_min = keylo; from.v0_max = keyhi; to.cmd = EVSPEC_EMPTY; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "keymap")) { if (!load_chan(o, &idev, &ich)) { return 0; } if (!load_chan(o, &odev, &och)) { return 0; } if (!load_long(o, 0, EV_MAXCOARSE, &keylo)) { return 0; } if (!load_long(o, 0, EV_MAXCOARSE, &keyhi)) { return 0; } if (!load_long(o, 0, EV_MAXCOARSE, &ukeyplus)) { return 0; } if (ukeyplus > 63) { keyplus = ukeyplus - 128; } else { keyplus = ukeyplus; } if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "curve identifier expected"); return 0; } if ((int)keyhi + keyplus > EV_MAXCOARSE) keyhi = EV_MAXCOARSE - keyplus; if ((int)keylo + keyplus < 0) keylo = -keyplus; if (keylo >= keyhi) { load_err(o, "bad range in keymap rule"); return 0; } evspec_reset(&from); evspec_reset(&to); from.cmd = EVSPEC_NOTE; from.dev_min = from.dev_max = idev; from.ch_min = from.ch_max = ich; from.v0_min = keylo; from.v0_max = keyhi; to.cmd = EVSPEC_NOTE; to.dev_min = to.dev_max = odev; to.ch_min = to.ch_max = och; to.v0_min = keylo + keyplus; to.v0_max = keyhi + keyplus; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "ctldrop")) { if (!load_chan(o, &idev, &ich)) { return 0; } if (!load_long(o, 0, EV_MAXCOARSE, &ictl)) { return 0; } evspec_reset(&from); from.cmd = EVSPEC_XCTL; from.dev_min = from.dev_max = idev; from.ch_min = from.ch_max = ich; from.v0_min = from.v0_max = ictl; to.cmd = EVSPEC_EMPTY; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "ctlmap")) { if (!load_chan(o, &idev, &ich)) { return 0; } if (!load_chan(o, &odev, &och)) { return 0; } if (!load_long(o, 0, EV_MAXCOARSE, &ictl)) { return 0; } if (!load_long(o, 0, EV_MAXCOARSE, &octl)) { return 0; } if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "curve identifier expected"); return 0; } evspec_reset(&from); evspec_reset(&to); from.cmd = EVSPEC_CTL; from.dev_min = from.dev_max = idev; from.ch_min = from.ch_max = ich; from.v0_min = from.v0_max = ictl; to.cmd = EVSPEC_CTL; to.dev_min = to.dev_max = odev; to.ch_min = to.ch_max = och; to.v0_min = to.v0_max = octl; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "chandrop")) { if (!load_chan(o, &idev, &ich)) { return 0; } evspec_reset(&from); from.dev_min = from.dev_max = idev; from.ch_min = from.ch_max = ich; to.cmd = EVSPEC_EMPTY; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "chanmap")) { if (!load_chan(o, &idev, &ich)) { return 0; } if (!load_chan(o, &odev, &och)) { return 0; } evspec_reset(&from); evspec_reset(&to); from.dev_min = from.dev_max = idev; from.ch_min = from.ch_max = ich; to.dev_min = to.dev_max = odev; to.ch_min = to.ch_max = och; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "devdrop")) { if (!load_long(o, 0, EV_MAXDEV, &idev)) { return 0; } evspec_reset(&from); from.dev_min = from.dev_max = idev; to.cmd = EVSPEC_EMPTY; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "devmap")) { if (!load_long(o, 0, EV_MAXDEV, &idev)) { return 0; } if (!load_long(o, 0, EV_MAXDEV, &odev)) { return 0; } evspec_reset(&from); evspec_reset(&to); from.dev_min = from.dev_max = idev; to.dev_min = to.dev_max = odev; filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "evmap")) { if (!load_evspec(o, &from)) { return 0; } if (!load_getsym(o)) return 0; if (o->id != TOK_GT) { load_err(o, "'>' expected"); return 0; } if (!load_evspec(o, &to)) { return 0; } filt_mapnew(f, &from, &to); } else if (str_eq(o->strval, "transp")) { if (!load_evspec(o, &to)) { return 0; } if (!load_long(o, 0, EV_MAXCOARSE, &ukeyplus)) { return 0; } filt_transp(f, &to, ukeyplus); } else if (str_eq(o->strval, "vcurve")) { if (!load_evspec(o, &to)) { return 0; } if (!load_long(o, 1, EV_MAXCOARSE, &ukeyplus)) { return 0; } filt_vcurve(f, &to, ukeyplus); } else { load_ungetsym(o); if (!load_ukline(o)) { return 0; } load_err(o, "unknown filter rule, ignored"); return 1; } if (!load_nl(o)) { return 0; } return 1; } unsigned load_filt(struct load *o, struct filt *f) { if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing filt"); return 0; } for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else { load_ungetsym(o); if (!load_rule(o, f)) return 0; } } return 1; } unsigned load_sysex(struct load *o, struct sysex **res) { struct sysex *sx; unsigned long val; if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing sysex"); return 0; } sx = sysex_new(0); for (;;) { if (!load_getsym(o)) goto err1; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "data")) { for (;;) { if (!load_getsym(o)) goto err1; if (o->id == TOK_ENDLINE) { break; } load_ungetsym(o); if (!load_long(o, 0, 0xff, &val)) { goto err1; } sysex_add(sx, val); } } else if (str_eq(o->strval, "unit")) { if (!load_long(o, 0, EV_MAXDEV, &val)) { goto err1; } sx->unit = val; if (!load_nl(o)) { goto err1; } } else { goto unknown; } } else { unknown: load_ungetsym(o); if (!load_ukline(o)) { goto err1; } load_err(o, "unknown line format in sysex, ignored"); } } *res = sx; return 1; err1: sysex_del(sx); return 0; } unsigned load_evpat(struct load *o, char *ref) { unsigned long val; unsigned size = 0; unsigned char *pattern; char *name; unsigned cmd; /* * find a free slot */ if (evpat_lookup(ref, &cmd)) evpat_unconf(cmd); for (cmd = EV_PAT0;; cmd++) { if (cmd == EV_PAT0 + EV_NPAT) { load_err(o, "too many sysex patterns"); return 0; } if (evinfo[cmd].ev == NULL) break; } name = str_new(ref); pattern = xmalloc(EV_PATSIZE, "evpat"); if (!load_getsym(o)) goto err1; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing evpat"); goto err1; } for (;;) { if (!load_getsym(o)) goto err1; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "pattern")) { for (;;) { if (!load_getsym(o)) goto err1; if (o->id == TOK_ENDLINE) { break; } if (size == EV_PATSIZE) { load_err(o, "pattern too long"); goto err1; } if (o->id == TOK_NUM) { load_ungetsym(o); if (!load_long(o, 0, 0xff, &val)) { goto err1; } pattern[size++] = val; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "v0_hi")) val = EV_PATV0_HI; else if (str_eq(o->strval, "v0_lo")) val = EV_PATV0_LO; else if (str_eq(o->strval, "v1_hi")) val = EV_PATV1_HI; else if (str_eq(o->strval, "v1_lo")) val = EV_PATV1_LO; else { load_err(o, "unexpected atom in pattern"); return 0; } pattern[size++] = val; } } } else { goto unknown; } } else { unknown: load_ungetsym(o); if (!load_ukline(o)) { goto err1; } load_err(o, "unknown line format in sysex, ignored"); } } if (!evpat_set(cmd, name, pattern, size)) goto err1; return 1; err1: str_delete(name); xfree(pattern); return 0; } unsigned load_songchan(struct load *o, struct song *s, struct songchan *i) { unsigned long val, val2; if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing songchan"); return 0; } for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "conf")) { if (!load_track(o, &i->conf)) return 0; if (!load_nl(o)) return 0; track_setchan(&i->conf, i->dev, i->ch); } else if (str_eq(o->strval, "chan")) { if (!load_chan(o, &val, &val2)) return 0; i->dev = val; i->ch = val2; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "curinput")) { if (!load_chan(o, &val, &val2)) return 0; if (!load_nl(o)) return 0; load_err(o, "ignored obsolete 'curinput' line"); } else { goto unknown; } } else { unknown: load_ungetsym(o); if (!load_ukline(o)) { return 0; } load_err(o, "unknown line format, ignored"); } } return 1; } unsigned load_songtrk(struct load *o, struct song *s, struct songtrk *t) { struct songfilt *f; unsigned long val; if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing songtrk"); return 0; } for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "curfilt")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } f = song_filtlookup(s, o->strval); if (!f) { f = song_filtnew(s, o->strval); song_setcurfilt(s, NULL); } t->curfilt = f; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "track")) { if (!load_track(o, &t->track)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "mute")) { if (!load_long(o, 0, 1, &val)) return 0; t->mute = val; if (!load_nl(o)) return 0; } else goto unknown; } else { unknown: load_ungetsym(o); if (!load_ukline(o)) return 0; load_err(o, "unknown line format, ignored"); } } return 1; } unsigned load_songfilt(struct load *o, struct song *s, struct songfilt *g) { if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing songfilt"); return 0; } for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "curchan")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } if (!load_nl(o)) return 0; load_err(o, "ignored obsolete 'curchan' line"); } else if (str_eq(o->strval, "filt")) { if (!load_filt(o, &g->filt)) return 0; } else goto unknown; } else { unknown: load_ungetsym(o); if (!load_ukline(o)) return 0; load_err(o, "unknown line format, ignored"); } } return 1; } unsigned load_songsx(struct load *o, struct song *s, struct songsx *g) { struct sysex *sx; if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing songsx"); return 0; } for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "sysex")) { if (!load_sysex(o, &sx)) return 0; sysexlist_put(&g->sx, sx); } else { goto unknown; } } else { unknown: load_ungetsym(o); if (!load_ukline(o)) return 0; load_err(o, "unknown line format, ignored"); } } return 1; } unsigned load_metro(struct load *o, struct metro *m) { struct ev ev; unsigned mask; if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing metro"); return 0; } for (;;) { if (!load_getsym(o)) return 0; if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "enabled")) { if (!load_getsym(o)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "mask")) { if (!load_getsym(o)) return 0; if (!metro_str2mask(m, o->strval, &mask)) { load_err(o, "skipped unknown " "metronome mask"); } metro_setmask(m, mask); if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "lo")) { if (!load_ev(o, &ev)) return 0; if (ev.cmd != EV_NON) { load_err(o, "'lo' must be followed " "by a 'non' event"); } else { m->lo = ev; } } else if (str_eq(o->strval, "hi")) { if (!load_ev(o, &ev)) return 0; if (ev.cmd != EV_NON) { load_err(o, "'hi' must be followed " "by a 'non' event\n"); } else m->hi = ev; } else { goto unknown; } } else { unknown: load_ungetsym(o); if (!load_ukline(o)) return 0; load_err(o, "unknown line format, ignored"); } } return 1; } unsigned load_song(struct load *o, struct song *s) { struct songtrk *t; struct songchan *i; struct songfilt *g; struct songsx *l; struct evspec es; unsigned long num, num2; int input; if (!load_getsym(o)) return 0; if (o->id != TOK_LBRACE) { load_err(o, "'{' expected while parsing song"); return 0; } for (;;) { if (!load_getsym(o)) { return 0; } if (o->id == TOK_ENDLINE) { /* nothing */ } else if (o->id == TOK_RBRACE) { break; } else if (o->id == TOK_WORD) { if (str_eq(o->strval, "format")) { if (!load_getsym(o)) return 0; if (o->id != TOK_NUM) { load_err(o, "version number expected"); return 0; } if (o->longval > FORMAT_VERSION) { cons_err("Warning: midish version " "too old to read this file."); } o->format = o->longval; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "evpat")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } if (!load_evpat(o, o->strval)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "songtrk")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } t = song_trklookup(s, o->strval); if (t == NULL) { t = song_trknew(s, o->strval); song_setcurtrk(s, NULL); } if (!load_songtrk(o, s, t)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "songin")) { input = 1; goto chan_do; } else if (str_eq(o->strval, "songout") || str_eq(o->strval, "songchan")) { input = 0; chan_do: if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } i = song_chanlookup(s, o->strval, input); if (i == NULL) { i = song_channew(s, o->strval, 0, 0, input); song_setcurchan(s, NULL, input); if (i->filt) filt_reset(&i->filt->filt); } if (!load_songchan(o, s, i)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "songfilt")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } g = song_filtlookup(s, o->strval); if (!g) { g = song_filtnew(s, o->strval); song_setcurfilt(s, NULL); } if (!load_songfilt(o, s, g)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "songsx")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } l = song_sxlookup(s, o->strval); if (!l) { l = song_sxnew(s, o->strval); song_setcursx(s, NULL); } if (!load_songsx(o, s, l)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "meta")) { if (!load_track(o, &s->meta)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "tics_per_unit")) { if (!load_long(o, 0, ~1U, &num)) return 0; if ((num % 96) != 0) { load_err(o, "warning, rounding " "tic_per_unit to a multiple " "of 96"); num = 96 * (num / 96); if (num < 96) { num = 96; } } if (!load_nl(o)) return 0; s->tics_per_unit = num; } else if (str_eq(o->strval, "tempo_factor")) { if (!load_long(o, 0, ~1U, &num)) return 0; if (!load_nl(o)) return 0; if (num < 0x80 || num > 0x200) { load_err(o, "warning, tempo factor " "out of bounds\n"); } else s->tempo_factor = num; } else if (str_eq(o->strval, "curtrk")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } t = song_trklookup(s, o->strval); if (t) { s->curtrk = t; } else { load_err(o, "warning, can't set " "current track, not such track"); } if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "curfilt")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } g = song_filtlookup(s, o->strval); if (g) { s->curfilt = g; } else { load_err(o, "warning, can't set " "current filt, not such filt"); } if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "cursx")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } l = song_sxlookup(s, o->strval); if (l) { s->cursx = l; } else { load_err(o, "warning, cant set " "current sysex, not such sysex"); } if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "curin")) { input = 1; goto curchan_do; } else if (str_eq(o->strval, "curout") || str_eq(o->strval, "curchan")) { input = 0; curchan_do: if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "word expected"); return 0; } i = song_chanlookup(s, o->strval, input); if (i) { song_setcurchan(s, i, input); } else { load_err(o, "warning, cant set " "current chan, not such chan"); } if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "curpos")) { if (!load_long(o, 0, ~1U, &num)) return 0; if (!load_nl(o)) return 0; s->curpos = num; } else if (str_eq(o->strval, "curlen")) { if (!load_long(o, 0, ~1U, &num)) return 0; if (!load_nl(o)) return 0; s->curlen = num; } else if (str_eq(o->strval, "curquant")) { if (!load_long(o, 0, ~1U, &num)) return 0; if (num > s->tics_per_unit) { load_err(o, "warning, truncated " "curquant because it was " "too large"); num = s->tics_per_unit; } if (!load_nl(o)) return 0; s->curquant = num; } else if (str_eq(o->strval, "curev")) { if (!load_evspec(o, &es)) return 0; if (!load_nl(o)) return 0; s->curev = es; } else if (str_eq(o->strval, "curinput")) { if (!load_chan(o, &num, &num2)) return 0; i = song_chanlookup_bynum(s, num, num2, 1); if (i == NULL) { i = song_channew(s, "old_style_curin", num, num2, 1); song_setcurchan(s, NULL, 1); } else { i->dev = num; i->ch = num2; } if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "metro")) { if (!load_metro(o, &s->metro)) return 0; if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "tap")) { if (!load_getsym(o)) return 0; if (o->id != TOK_WORD) { load_err(o, "expected tap mode\n"); goto unknown; } if (str_eq(o->strval, "off")) s->tap_mode = SONG_TAP_OFF; else if (str_eq(o->strval, "start")) s->tap_mode = SONG_TAP_START; else if (str_eq(o->strval, "tempo")) s->tap_mode = SONG_TAP_TEMPO; else { load_err(o, "unknown tap mode\n"); goto unknown; } if (!load_nl(o)) return 0; } else if (str_eq(o->strval, "tapev")) { if (!load_evspec(o, &es)) return 0; if (!load_nl(o)) return 0; s->tap_evspec = es; } else goto unknown; } else { unknown: load_ungetsym(o); if (!load_ukline(o)) return 0; load_err(o, "unknown line format, ignored"); } } return 1; } void song_save(struct song *o, char *name) { struct textout *f; f = textout_new(name); if (f == NULL) { return; } textout_putstr(f, "#\n" "# " VERSION "\n" "#\n" ); song_output(o, f); textout_putstr(f, "\n"); textout_delete(f); } unsigned song_load(struct song *o, char *filename) { struct load p; unsigned res; if (!load_init(&p, filename)) return 0; res = load_empty(&p); if (res != 0) { res = load_song(&p, o); } load_done(&p); return res; } midish-1.4.0/saveload.h010066400017510001751000000034661501104363400134570ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_SAVELOAD_H #define MIDISH_SAVELOAD_H struct textout; struct ev; struct evctl; struct track; struct filt; struct rule; struct songchan; struct songtrk; struct songfilt; struct songsx; struct song; void ev_output(struct ev *, struct textout *); void evspec_output(struct evspec *, struct textout *); void evpat_output(struct textout *); void track_output(struct track *, struct textout *); void rule_output(struct rule *, struct textout *); void filt_output(struct filt *, struct textout *); void songtrk_output(struct songtrk *, struct textout *); void songchan_output(struct songchan *, struct textout *); void songfilt_output(struct songfilt *, struct textout *); void songsx_output(struct songsx *, struct textout *); void evctltab_output(struct evctl *, struct textout *); void song_output(struct song *, struct textout *); void track_save(struct track *, char *); unsigned track_load(struct track *, char *); void song_save(struct song *, char *); unsigned song_load(struct song *, char *); #endif /* MIDISH_SAVELOAD_H */ midish-1.4.0/smf.c010066400017510001751000000500011501104363400124240ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "utils.h" #include "mididev.h" #include "sysex.h" #include "track.h" #include "song.h" #include "smf.h" #include "cons.h" #include "frame.h" #include "conv.h" #define MAXTRACKNAME 100 char smftype_header[4] = { 'M', 'T', 'h', 'd' }; char smftype_track[4] = { 'M', 'T', 'r', 'k' }; unsigned smf_evlen[] = { 2, 2, 2, 2, 1, 1, 2, 0 }; #define SMF_EVLEN(status) (smf_evlen[((status) >> 4) & 7]) /* --------------------------------------------- chunk read/write --- */ struct smf { FILE *file; unsigned length, index; /* current chunk length/position */ }; /* * open a standard midi file and initialize * the smf structure */ unsigned smf_open(struct smf *o, char *path, char *mode) { o->file = fopen(path, mode); if (!o->file) { cons_errs(path, "failed to open file"); return 0; } o->length = 0; o->index = 0; return 1; } /* * close the file */ void smf_close(struct smf *o) { fclose(o->file); } /* * read a 32bit fixed-size number, return 0 on error */ unsigned smf_get32(struct smf *o, unsigned *val) { unsigned char buf[4]; if (o->index + 4 > o->length || fread(buf, 1, 4, o->file) != 4) { cons_err("failed to read 32bit number"); return 0; } *val = (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; o->index += 4; return 1; } /* * read a 24bit fixed-size number, return 0 on error */ unsigned smf_get24(struct smf *o, unsigned *val) { unsigned char buf[4]; if (o->index + 3 > o->length || fread(buf, 1, 3, o->file) != 3) { cons_err("failed to read 24bit number"); return 0; } *val = (buf[0] << 16) + (buf[1] << 8) + buf[2]; o->index += 3; return 1; } /* * read a 16bit fixed-size number, return 0 on error */ unsigned smf_get16(struct smf *o, unsigned *val) { unsigned char buf[4]; if (o->index + 2 > o->length || fread(buf, 1, 2, o->file) != 2) { cons_err("failed to read 16bit number"); return 0; } *val = (buf[0] << 8) + buf[1]; o->index += 2; return 1; } /* * read a 8bit fixed-size number, return 0 on error */ unsigned smf_getc(struct smf *o, unsigned *res) { int c; if (o->index + 1 > o->length || (c = fgetc(o->file)) < 0) { cons_err("failed to read one byte"); return 0; } o->index++; *res = c & 0xff; return 1; } /* * read a variable length number, return 0 on error */ unsigned smf_getvar(struct smf *o, unsigned *val) { int c; unsigned bits; *val = 0; bits = 0; for (;;) { if (o->index + 1 > o->length || (c = fgetc(o->file)) == EOF) { cons_err("failed to read varlength number"); return 0; } o->index++; *val += (c & 0x7f); if (!(c & 0x80)) { break; } *val <<= 7; bits += 7; /* * smf spec forbids more than 32bit per integer */ if (bits > 32) { cons_err("overflow while reading varlength number"); return 0; } } return 1; } /* * read a chunk header, compare it with ethe given 4-byte header and * initialize the smf structure so that other smf_getxxx can work. * return 0 on error */ unsigned smf_getheader(struct smf *o, char *hdr) { char buf[4]; unsigned len; if (o->index != o->length) { cons_err("chunk not finished"); return 0; } if (fread(buf, 1, 4, o->file) != 4) { cons_err("failed to read header"); return 0; } if (memcmp(buf, hdr, 4) != 0) { cons_err("header corrupted"); return 0; } o->index = 0; o->length = 4; if (!smf_get32(o, &len)) { return 0; } o->index = 0; o->length = len; return 1; } /* * in smf_putxxx routines there is a 'unsigned *used' argument if * (used == NULL) then data is not written in the smf structure, only * *user is incremented by the number of bytes that would be written * otherwise. This is used to determine chunk's length */ /* * put a fixed-size 32-bit number */ void smf_put32(struct smf *o, unsigned *used, unsigned val) { unsigned char buf[4]; if (used) { (*used) += 4; } else { if (o->index + 4 > o->length) { log_puts("smf_put32: bad chunk length\n"); panic(); } buf[0] = (val >> 24) & 0xff; buf[1] = (val >> 16) & 0xff; buf[2] = (val >> 8) & 0xff; buf[3] = val & 0xff; fwrite(buf, 1, 4, o->file); o->index += 4; } } /* * put a fixed-size 24-bit number */ void smf_put24(struct smf *o, unsigned *used, unsigned val) { unsigned char buf[4]; if (used) { (*used) += 3; } else { if (o->index + 3 > o->length) { log_puts("smf_put24: bad chunk length\n"); panic(); } buf[0] = (val >> 16) & 0xff; buf[1] = (val >> 8) & 0xff; buf[2] = val & 0xff; fwrite(buf, 1, 3, o->file); o->index += 3; } } /* * put a fixed-size 16-bit number */ void smf_put16(struct smf *o, unsigned *used, unsigned val) { unsigned char buf[4]; if (used) { (*used) += 2; } else { if (o->index + 2 > o->length) { log_puts("smf_put16: bad chunk length\n"); panic(); } buf[0] = (val >> 8) & 0xff; buf[1] = val & 0xff; fwrite(buf, 1, 2, o->file); o->index += 2; } } /* * put a fixed-size 8-bit number */ void smf_putc(struct smf *o, unsigned *used, unsigned val) { if (used) { (*used) ++; } else { if (o->index + 1 > o->length) { log_puts("smf_putc: bad chunk length\n"); panic(); } fputc((int)val & 0xff, o->file); o->index++; } } /* * put a variable length number */ void smf_putvar(struct smf *o, unsigned *used, unsigned val) { #define MAXBYTES 5 /* 32bit / 7bit = 4bytes + 4bit */ unsigned char buf[MAXBYTES]; unsigned index = 0, bits; for (bits = 7; bits < MAXBYTES * 7; bits += 7) { if (val < (1U << bits)) { bits -= 7; for (; bits != 0; bits -= 7) { buf[index++] = ((val >> bits) & 0x7f) | 0x80; } buf[index++] = val & 0x7f; if (used) { (*used) += index; } else { if (o->index + index > o->length) { log_puts("smf_putvar: bad chunk length\n"); panic(); } o->index += index; fwrite(buf, 1, index, o->file); } return; } } log_puts("smf_putvar: bogus value\n"); panic(); #undef MAXBYTES } /* * put the given chunk header with the given magic and size field */ void smf_putheader(struct smf *o, char *hdr, unsigned len) { fwrite(hdr, 1, 4, o->file); o->length = 4; o->index = 0; smf_put32(o, NULL, len); o->length = len; o->index = 0; } /* * store a track in the smf */ void smf_puttrack(struct smf *o, unsigned *used, struct song *s, struct track *t) { struct seqev *pos; unsigned status, newstatus, delta, chan, denom; struct ev rev[CONV_NUMREV]; struct statelist slist; unsigned i, nev; statelist_init(&slist); delta = 0; status = 0; for (pos = t->first; pos != NULL; pos = pos->next) { delta += pos->delta; if (pos->ev.cmd == EV_NULL) { break; } if (EV_ISVOICE(&pos->ev)) { nev = conv_unpackev(&slist, 0U, CONV_XPC | CONV_NRPN | CONV_RPN, &pos->ev, rev); for (i = 0; i < nev; i++) { smf_putvar(o, used, delta); delta = 0; chan = rev[i].ch; newstatus = (rev[i].cmd << 4) + (chan & 0x0f); if (newstatus != status) { status = newstatus; smf_putc(o, used, status); } if (rev[i].cmd == EV_BEND) { smf_putc(o, used, rev[i].bend_val & 0x7f); smf_putc(o, used, rev[i].bend_val >> 7); } else { smf_putc(o, used, rev[i].v0); if (SMF_EVLEN(status) == 2) { smf_putc(o, used, rev[i].v1); } } } } else if (pos->ev.cmd == EV_TEMPO) { smf_putvar(o, used, delta); delta = 0; smf_putc(o, used, 0xff); smf_putc(o, used, 0x51); smf_putc(o, used, 0x03); smf_put24(o, used, pos->ev.tempo_usec24 * s->tics_per_unit / 96); } else if (pos->ev.cmd == EV_TIMESIG) { denom = s->tics_per_unit / pos->ev.timesig_tics; switch(denom) { case 1: denom = 0; break; case 2: denom = 1; break; case 4: denom = 2; break; case 8: denom = 3; break; case 16: denom = 4; break; default: log_puts("smf_puttrack: bad time signature\n"); panic(); } smf_putvar(o, used, delta); delta = 0; smf_putc(o, used, 0xff); smf_putc(o, used, 0x58); smf_putc(o, used, 0x04); smf_putc(o, used, pos->ev.timesig_beats); smf_putc(o, used, denom); /* metronome tics per metro beat */ smf_putc(o, used, pos->ev.timesig_tics); /* metronome 1/32 notes per 24 tics */ smf_putc(o, used, 8 * s->tics_per_unit / 96); } } smf_putvar(o, used, delta); smf_putc(o, used, 0xff); smf_putc(o, used, 0x2f); smf_putc(o, used, 0x00); statelist_done(&slist); } /* * store a sysex in the smf */ void smf_putsysex(struct smf *o, unsigned *used, struct sysex *sx) { struct chunk *c; unsigned i, first; first = 1; for (c = sx->first; c != NULL; c = c->next) { for (i = 0; i < c->used; i++) { if (first) { first = 0; } else { smf_putc(o, used, c->data[i]); } } } } /* * store a sysex back in the smf */ void smf_putsx(struct smf *o, unsigned *used, struct song *s, struct songsx *songsx) { unsigned sysexused; struct sysex *sx; for (sx = songsx->sx.first; sx != NULL; sx = sx->next) { sysexused = 0; smf_putvar(o, used, 0); smf_putc(o, used, 0xf0); smf_putsysex(o, &sysexused, sx); smf_putvar(o, used, sysexused); smf_putsysex(o, used, sx); } smf_putvar(o, used, 0); smf_putc(o, used, 0xff); smf_putc(o, used, 0x2f); smf_putc(o, used, 0x00); } /* * open the smf and store the whole song */ unsigned song_exportsmf(struct song *o, char *filename) { struct smf f; struct songtrk *t; struct songchan *i; struct songsx *s; unsigned ntrks, nchan, nsx, used; if (!smf_open(&f, filename, "w")) { return 0; } ntrks = 0; SONG_FOREACH_TRK(o, t) { ntrks++; } nchan = 0; SONG_FOREACH_CHAN(o, i) { if (i->isinput) continue; nchan++; } nsx = 0; SONG_FOREACH_SX(o, s) { nsx++; } /* * write the header */ smf_putheader(&f, smftype_header, 6); smf_put16(&f, NULL, 1); /* format = 1 */ smf_put16(&f, NULL, nsx + ntrks + nchan + 1); /* +1 -> meta track */ smf_put16(&f, NULL, o->tics_per_unit / 4); /* tics per quarter */ /* * write the tempo track */ used = 0; smf_puttrack(&f, &used, o, &o->meta); smf_putheader(&f, smftype_track, used); smf_puttrack(&f, NULL, o, &o->meta); /* * write each sx */ SONG_FOREACH_SX(o, s) { used = 0; smf_putsx(&f, &used, o, s); smf_putheader(&f, smftype_track, used); smf_putsx(&f, NULL, o, s); } /* * write each chan */ SONG_FOREACH_CHAN(o, i) { if (i->isinput) continue; used = 0; smf_puttrack(&f, &used, o, &i->conf); smf_putheader(&f, smftype_track, used); smf_puttrack(&f, NULL, o, &i->conf); } /* * write each track */ SONG_FOREACH_TRK(o, t) { used = 0; smf_puttrack(&f, &used, o, &t->track); smf_putheader(&f, smftype_track, used); smf_puttrack(&f, NULL, o, &t->track); } smf_close(&f); return 1; } /* * parse '0xF0 varlen data data ... data 0xF7' */ unsigned smf_getsysex(struct smf *o, struct sysex *sx) { unsigned i, length, c; if (!smf_getvar(o, &length)) { return 0; } for (i = 0; i < length; i++) { if (!smf_getc(o, &c)) { return 0; } sysex_add(sx, c); } return 1; } /* * parse a track 'varlen event varlen event ... varlen event' */ unsigned smf_gettrack(struct smf *o, struct song *s, struct songtrk *t) { unsigned delta, i, status, type, length; unsigned tempo, num, den, dummy; struct statelist slist; struct songsx *songsx; struct seqev *pos, *se; struct sysex *sx; struct mididev *dev; struct ev ev, rev; unsigned xctlset, evset; unsigned c; if (!smf_getheader(o, smftype_track)) { return 0; } status = 0; track_clear(&t->track); pos = t->track.first; songsx = (struct songsx *)s->sxlist; /* first (and unique) sysex in song */ if (songsx == NULL) { songsx = song_sxnew(s, "smf"); } statelist_init(&slist); for (;;) { if (o->index >= o->length) { statelist_done(&slist); return 1; } if (!smf_getvar(o, &delta)) { goto err; } pos->delta += delta; if (!smf_getc(o, &c)) { goto err;; } if (c == 0xff) { status = 0; if (!smf_getc(o, &c)) { goto err; } type = c; if (!smf_getvar(o, &length)) { goto err; } if (type == 0x2f && length == 0) { /* end of track */ goto ignoremeta; } else if (type == 0x51 && length == 3) { /* tempo change */ if (!smf_get24(o, &tempo)) { goto err; } ev.cmd = EV_TEMPO; ev.tempo_usec24 = tempo * 96 / s->tics_per_unit; goto putev; } else if (type == 0x58 && length == 4) { /* time signature change */ if (!smf_getc(o, &num)) { goto err; } if (!smf_getc(o, &den)) { goto err; } ev.cmd = EV_TIMESIG; ev.timesig_beats = num; ev.timesig_tics = s->tics_per_unit / (1 << den); if (!smf_getc(o, &dummy)) { goto err; } /* log_puts("cc="); log_putu(dummy); log_puts(", dd="); */ if (!smf_getc(o, &dummy)) { goto err; } /* log_putu(dummy); log_puts("\n"); */ goto putev; } else { ignoremeta: for (i = 0; i < length; i++) { if (!smf_getc(o, &c)) { goto err; } } } } else if (c == 0xf7) { /* raw data */ cons_err("raw data (status = 0xF7) not implemented"); status = 0; if (!smf_getvar(o, &length)) { goto err; } for (i = 0; i < length; i++) { if (!smf_getc(o, &c)) { goto err; } } } else if (c == 0xf0) { /* sys ex */ status = 0; sx = sysex_new(0); sysex_add(sx, 0xf0); if (!smf_getsysex(o, sx)) { sysex_del(sx); goto err; } if (sysex_check(sx)) { sysexlist_put(&songsx->sx, sx); } else { cons_err("corrupted sysex message, ignored"); /* sysex_log(sx); log_puts("\n"); */ sysex_del(sx); } } else if (c >= 0x80 && c < 0xf0) { status = c; if (!smf_getc(o, &c)) { goto err; } runningstatus: ev.cmd = (status >> 4) & 0xf; ev.dev = 0; ev.ch = status & 0xf; ev.v0 = c & 0x7f; if (SMF_EVLEN(status) == 2) { if (!smf_getc(o, &c)) { goto err; } c &= 0x7f; if (ev.cmd == EV_BEND) { ev.v0 += c << 7; } else { ev.v1 = c; } } putev: if (ev.cmd == EV_NON && ev.note_vel == 0) { ev.cmd = EV_NOFF; ev.note_vel = EV_NOFF_DEFAULTVEL; } /* * Pack events according to device setup. * * XXX: this needs to be done in * doevset/doxctl by converting the whole * project whenever events configuration * is changed. */ if ((evinfo[ev.cmd].flags & EV_HAS_DEV) && (dev = mididev_byunit[ev.dev]) != NULL) { xctlset = dev->oxctlset; evset = dev->oevset; } else { xctlset = 0; evset = CONV_XPC | CONV_NRPN | CONV_RPN; } if (conv_packev(&slist, xctlset, evset, &ev, &rev)) { se = seqev_new(); se->ev = rev; seqev_ins(pos, se); } } else if (c < 0x80) { if (status == 0) { cons_err("bad status"); goto err; } goto runningstatus; } else { cons_err("bad event"); goto err; } } err: statelist_done(&slist); return 0; } /* * fix song imported from format 1 smf */ void song_fix1(struct song *o) { struct track copy; struct seqptr *tp, *cp; struct state *st; struct statelist slist; struct songtrk *t, *tnext; unsigned delta; SONG_FOREACH_TRK(o, t) { /* * move meta events into meta track */ delta = 0; track_init(©); cp = seqptr_new(©); tp = seqptr_new(&t->track); statelist_init(&slist); for (;;) { delta = seqptr_ticdel(tp, ~0U, &slist); seqptr_ticput(tp, delta); seqptr_ticput(cp, delta); st = seqptr_evdel(tp, &slist); if (st == NULL) { break; } if (st->phase & EV_PHASE_FIRST) { st->tag = EV_ISMETA(&st->ev) ? 1 : 0; } if (st->tag) { seqptr_evput(cp, &st->ev); } else { seqptr_evput(tp, &st->ev); } } statelist_done(&slist); seqptr_del(tp); seqptr_del(cp); track_merge(&o->meta, ©); track_done(©); } /* * remove the first track, if there are not events */ for (t = (struct songtrk *)o->trklist; t != NULL; t = tnext) { tnext = (struct songtrk *)t->name.next; if (t->track.first->ev.cmd == EV_NULL) { song_trkdel(o, t); } } } /* * fix song imported from format 0 SMFs: call fix1 to move meta events * to the meta-track then split it creating one track per channel */ void song_fix0(struct song *o) { char trackname[MAXTRACKNAME]; struct seqptr *tp, *cp; struct state *st; struct statelist slist; struct songtrk *t, *smf; unsigned delta; unsigned i; song_fix1(o); smf = (struct songtrk *)o->trklist; if (smf == NULL) { return; } for (i = 1; i < 16; i++) { snprintf(trackname, MAXTRACKNAME, "trk%02u", i); t = song_trknew(o, trackname); delta = 0; cp = seqptr_new(&t->track); tp = seqptr_new(&smf->track); statelist_init(&slist); for (;;) { delta = seqptr_ticdel(tp, ~0U, &slist); seqptr_ticput(tp, delta); seqptr_ticput(cp, delta); st = seqptr_evdel(tp, &slist); if (st == NULL) { break; } if (st->phase & EV_PHASE_FIRST) { if (EV_ISVOICE(&st->ev) && st->ev.ch == i) { st->tag = 1; } else { st->tag = 0; } } if (st->tag) { seqptr_evput(cp, &st->ev); } else { seqptr_evput(tp, &st->ev); } } statelist_done(&slist); seqptr_del(tp); seqptr_del(cp); } } /* * open the smf and load the whole file */ struct song * song_importsmf(char *filename) { struct song *o; struct songtrk *t; unsigned format, ntrks, timecode, i; char trackname[MAXTRACKNAME]; struct smf f; if (!smf_open(&f, filename, "r")) { goto bad1; } if (!smf_getheader(&f, smftype_header)) { goto bad2; } if (!smf_get16(&f, &format)) { goto bad2; } if (format != 1 && format != 0) { cons_err("only smf format 0 or 1 can be imported"); goto bad2; } if (!smf_get16(&f, &ntrks)) { goto bad2; } if (ntrks >= 256) { cons_err("too many tracks in midi file"); goto bad2; } if (!smf_get16(&f, &timecode)) { goto bad2; } if ((timecode & 0x8000) != 0) { cons_err("SMPTE timecode is not supported"); goto bad2; } o = song_new(); o->tics_per_unit = timecode * 4; /* timecode = tics per quarter */ for (i = 0; i < ntrks; i++) { snprintf(trackname, MAXTRACKNAME, "trk%02u", i); t = song_trknew(o, trackname); if (!smf_gettrack(&f, o, t)) { goto bad3; } track_check(&t->track); } smf_close(&f); if (format == 0) { song_fix0(o); } else if (format == 1) { song_fix1(o); } /* * TODO: move sysex messages into separate songsx */ return o; bad3: song_delete(o); bad2: smf_close(&f); bad1: return 0; } int syx_import(char *path, struct sysexlist *l, int unit) { FILE *f; struct sysex *sx = NULL; int c; f = fopen(path, "r"); if (f == NULL) { cons_errs(path, "failed to open file"); return 0; } sysexlist_clear(l); for (;;) { c = fgetc(f); if (c == EOF) break; if (c == 0xf0) sx = sysex_new(unit); if (sx == NULL) { cons_errs(path, "corrupted .syx file"); goto err; } sysex_add(sx, c); if (c == 0xf7) { if (sysex_check(sx)) { sysexlist_put(l, sx); sx = NULL; continue; } else { cons_err("corrupted sysex message, ignored"); sysex_del(sx); goto err; } } } fclose(f); return 1; err: sysexlist_clear(l); fclose(f); return 0; } int syx_export(char *path, struct sysexlist *l) { FILE *f; struct sysex *x; struct chunk *c; ssize_t n; f = fopen(path, "w"); if (f == NULL) { cons_errs(path, "failed to open file"); return 0; } for (x = l->first; x != NULL; x = x->next) { for (c = x->first; c != NULL; c = c->next) { n = fwrite(c->data, 1, c->used, f); if (n != c->used) { cons_errs(path, "write failed"); fclose(f); return 0; } } } fclose(f); return 1; } midish-1.4.0/smf.h010066400017510001751000000020461501104363400124370ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_SMF_H #define MIDISH_SMF_H struct song; unsigned song_exportsmf(struct song *, char *); struct song *song_importsmf(char *); int syx_import(char *, struct sysexlist *, int); int syx_export(char *, struct sysexlist *); #endif /* MIDISH_SMF_H */ midish-1.4.0/smfplay010077500017510001751000000013351501104363400131020ustar00alexalex#!/bin/sh usage() { echo "usage: smfplay [-mxy] [-d device] [-i device] midifile" exit 2 } extclock=0 sendrt=0 input="" device=$MIDIDEV metronome="off" tempo=0 pos=0 while getopts mxyd:i:g: optname; do case "$optname" in m) metronome="on";; x) extclock=1;; y) sendrt=1;; d) device=$OPTARG;; i) input=$OPTARG;; g) pos=$OPTARG;; esac done shift $(($OPTIND - 1)) if [ "$#" != "1" -o -z "$1" ]; then usage; fi exec midish -b < .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd August 22, 2005 .Dt SMFPLAY 1 .Os .Sh NAME .Nm smfplay .Nd play a standard MIDI file .Sh SYNOPSIS .Nm smfplay .Op Fl mxy .Op Fl g Ar measure .Op Fl d Ar devname .Op Fl i Ar devname midifile .Sh DESCRIPTION The .Nm utility plays a MIDI file. While playing, it can route events from one .Xr midi 4 device to another. To stop performance, send an interrupt signal to .Nm (for instance by pressing control ^C on the terminal). The options are as follows: .Bl -tag -width "-i devname " .It Fl m Use metronome. The metronome will follow tempo changes and time signature changes in the midi file. .It Fl x Synchronise to an external .Xr midi 4 device. If the .Fl i flag is used then playback will be synchronised to the input device else it will be synchronised to the default device. .It Fl y Send midi timing information to the default device. Useful if the output device is a slave MIDI sequencer. .It Fl g Ar measure Start playback at the given measure number. .It Fl d Ar filename Default .Xr midi 4 device on which to play the midi file. If not specified, the content of the .Ev MIDIDEV environment variable will be used instead. .It Fl i Ar filename Alternate input .Xr midi 4 device. Voice events (notes, controllers, etc) received on the input device will be sent as-is to the default device. Without this flag, the default device will be used for input. .El .Pp The .Nm utility is an interface to .Xr midish 1 . If more specific features are needed, the user may consider using .Xr midish 1 . .Sh EXAMPLES The following will play .Pa mysong.mid on device .Pa /dev/rmidi4 and will use metronome. .Pp .Dl $ smfplay -m -d /dev/rmidi4 mysong.mid .Sh SEE ALSO .Xr midiplay 1 , .Xr midish 1 , .Xr smfrec 1 , .Xr midi 4 midish-1.4.0/smfrec010077500017510001751000000014511501104363400127050ustar00alexalex#!/bin/sh usage() { echo "usage: smfrec [-amxy] [-d device] [-i device] midifile" exit 2 } extclock=0 sendrt=0 input="" device=$MIDIDEV metronome="off" tempo=0 append=0 pos=0 while getopts amxyd:i:g: optname; do case "$optname" in a) append=1;; m) metronome="on";; x) extclock=1;; y) sendrt=1;; d) device="$OPTARG";; i) input="$OPTARG";; g) pos=$OPTARG;; esac done shift $(($OPTIND - 1)) if [ "$#" != "1" -o -z "$1" ]; then usage; fi exec midish -b < .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd August 27, 2005 .Dt SMFREC 1 .Os .Sh NAME .Nm smfrec .Nd record a standard MIDI file .Sh SYNOPSIS .Nm smfrec .Op Fl amxy .Op Fl g Ar measure .Op Fl d Ar devname .Op Fl i Ar devname midifile .Sh DESCRIPTION The .Nm utility records a MIDI file. It can add recorded events on top of an existing midi file. To stop performance, send an interrupt signal to .Nm (for instance by pressing control ^C on the terminal). The options are as follows: .Bl -tag -width "-i devname " .It Fl a Append mode. Play the given midi file and append to it a new track containing recorded events. .It Fl m Use metronome. .It Fl x Synchronise to the default .Xr midi 4 device instead of using an internal clock source. .It Fl y Send midi timing information to the default device. Useful if it is a slave MIDI sequencer. .It Fl g Ar measure Start playback and recording at the given measure number. .It Fl d Ar filename Default .Xr midi 4 device from which to record and on which to send midi events. If not specified, the content of the .Ev MIDIDEV environment variable will be used instead. .It Fl i Ar filename Alternate input .Xr midi 4 device. Voice events (notes, controllers, etc) received on the input device will be recorded and sent as-is to the default device. Without this flag, the default device will be used for input. .El .Pp The .Nm utility is an interface to .Xr midish 1 . If more specific features are needed, the user may consider using .Xr midish 1 . .Sh EXAMPLES The following will play .Pa mysong.mid and append to it recorded events from device .Pa /dev/rmidi4 ; metronome will be used. .Pp .Dl $ smfrec -a -m -d /dev/rmidi4 mysong.mid .Sh SEE ALSO .Xr midish 1 , .Xr smfplay 1 , .Xr midi 4 midish-1.4.0/song.c010066400017510001751000001067701501104363400126240ustar00alexalex/* * Copyright (c) 2003-2019 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "utils.h" #include "mididev.h" #include "mux.h" #include "track.h" #include "frame.h" #include "filt.h" #include "song.h" #include "cons.h" #include "metro.h" #include "defs.h" #include "mixout.h" #include "norm.h" #include "undo.h" #define TAG_OFF 0 #define TAG_PLAY 1 #define TAG_REC 2 unsigned song_debug = 0; char *song_tap_modestr[3] = {"off", "start", "tempo"}; /* * allocate and initialize a song structure */ struct song * song_new(void) { struct song *o; o = xmalloc(sizeof(struct song), "song"); song_init(o); return o; } /* * free the given song structure */ void song_delete(struct song *o) { song_done(o); xfree(o); } /* * initialize the given song */ void song_init(struct song *o) { struct seqev *se; /* * song parameters */ o->mode = 0; o->trklist = NULL; o->chanlist = NULL; o->filtlist = NULL; o->sxlist = NULL; o->undo = NULL; o->undo_size = 0; o->tics_per_unit = DEFAULT_TPU; track_init(&o->meta); track_init(&o->clip); track_init(&o->rec); sysexlist_init(&o->recsx); /* * runtime play record parameters */ o->tpb = o->tics_per_unit / DEFAULT_BPM; o->bpm = DEFAULT_BPM; o->tempo = TEMPO_TO_USEC24(DEFAULT_TEMPO, o->tpb); o->tempo_factor = 0x100; /* * metronome */ metro_init(&o->metro); o->tic = o->beat = o->measure = 0; o->abspos = 0; /* * defaults */ o->curtrk = NULL; o->curfilt = NULL; o->cursx = NULL; o->curin = NULL; o->curout = NULL; o->curpos = 0; o->curlen = 0; o->curquant = 0; o->loop = 0; evspec_reset(&o->curev); evspec_reset(&o->tap_evspec); o->tap_evspec.cmd = EVSPEC_EMPTY; o->tap_mode = 0; /* * add default timesig/tempo so that setunit() works */ se = seqev_new(); se->ev.cmd = EV_TEMPO; se->ev.tempo_usec24 = TEMPO_TO_USEC24(DEFAULT_TEMPO, o->tpb); seqev_ins(o->meta.first, se); se = seqev_new(); se->ev.cmd = EV_TIMESIG; se->ev.timesig_beats = DEFAULT_BPM; se->ev.timesig_tics = o->tics_per_unit / DEFAULT_BPM; seqev_ins(o->meta.first, se); } /* * delete a song without freeing it */ void song_done(struct song *o) { if (mux_isopen) { song_stop(o); } undo_clear(o, &o->undo); while (o->trklist) { song_trkdel(o, (struct songtrk *)o->trklist); } while (o->chanlist) { song_chandel(o, (struct songchan *)o->chanlist); } while (o->filtlist) { song_filtdel(o, (struct songfilt *)o->filtlist); } while (o->sxlist) { song_sxdel(o, (struct songsx *)o->sxlist); } track_done(&o->meta); track_done(&o->clip); track_done(&o->rec); sysexlist_done(&o->recsx); metro_done(&o->metro); if (o->undo != NULL) { log_puts("undo data not freed\n"); panic(); } } /* * create a new track in the song */ struct songtrk * song_trknew(struct song *o, char *name) { struct songtrk *t; t = xmalloc(sizeof(struct songtrk), "songtrk"); name_init(&t->name, name); track_init(&t->track); t->curfilt = NULL; t->mute = 0; name_add(&o->trklist, (struct name *)t); song_getcurfilt(o, &t->curfilt); song_setcurtrk(o, t); return t; } /* * delete the current track from the song */ void song_trkdel(struct song *o, struct songtrk *t) { if (o->curtrk == t) { o->curtrk = NULL; } name_remove(&o->trklist, (struct name *)t); track_done(&t->track); name_done(&t->name); xfree(t); } /* * return the track with the given name */ struct songtrk * song_trklookup(struct song *o, char *name) { return (struct songtrk *)name_lookup(&o->trklist, name); } /* * add a new chan to the song */ struct songchan * song_channew(struct song *o, char *name, unsigned dev, unsigned ch, int input) { struct songfilt *f; struct songchan *c, *i; struct evspec src, dst; c = xmalloc(sizeof(struct songchan), "songchan"); name_init(&c->name, name); track_init(&c->conf); c->dev = dev; c->ch = ch; c->isinput = input; name_add(&o->chanlist, (struct name *)c); if (input) c->filt = NULL; else { f = song_filtlookup(o, name); if (f == NULL) f = song_filtnew(o, name); c->filt = f; evspec_reset(&src); evspec_reset(&dst); dst.dev_min = dst.dev_max = c->dev; dst.ch_min = dst.ch_max = c->ch; SONG_FOREACH_CHAN(o, i) { if (!i->isinput) continue; src.dev_min = src.dev_max = i->dev; src.ch_min = src.ch_max = i->ch; filt_mapnew(&c->filt->filt, &src, &dst); } } song_setcurchan(o, c, input); return c; } /* * delete the given chan from the song */ void song_chandel(struct song *o, struct songchan *c) { if (c->isinput) { if (o->curin == c) o->curin = NULL; } else { if (o->curout == c) o->curout = NULL; } name_remove(&o->chanlist, (struct name *)c); track_done(&c->conf); name_done(&c->name); if (c->filt != NULL) song_filtdel(o, c->filt); xfree(c); } /* * return the chan with the given name */ struct songchan * song_chanlookup(struct song *o, char *name, int input) { struct songchan *c; SONG_FOREACH_CHAN(o, c) { if (!!c->isinput == !!input && str_eq(c->name.str, name)) break; } return c; } /* * return the chan matching the given (dev, ch) pair */ struct songchan * song_chanlookup_bynum(struct song *o, unsigned dev, unsigned ch, int input) { struct songchan *c; SONG_FOREACH_CHAN(o, c) { if (!!c->isinput == !!input && c->dev == dev && c->ch == ch) break; } return c; } /* * add a new filt to the song */ struct songfilt * song_filtnew(struct song *o, char *name) { struct songfilt *f; f = xmalloc(sizeof(struct songfilt), "songfilt"); name_init(&f->name, name); filt_init(&f->filt); name_add(&o->filtlist, (struct name *)f); song_setcurfilt(o, f); return f; } /* * delete the given filter from the song */ void song_filtdel(struct song *o, struct songfilt *f) { struct songtrk *t; if (o->curfilt == f) { song_setcurfilt(o, NULL); } SONG_FOREACH_TRK(o, t) { if (t->curfilt == f) { t->curfilt = NULL; } } name_remove(&o->filtlist, (struct name *)f); filt_done(&f->filt); name_done(&f->name); xfree(f); } /* * return the filt conrresponding to the given name */ struct songfilt * song_filtlookup(struct song *o, char *name) { return (struct songfilt *)name_lookup(&o->filtlist, name); } /* * add a new sx to the song */ struct songsx * song_sxnew(struct song *o, char *name) { struct songsx *x; x = xmalloc(sizeof(struct songsx), "songsx"); name_init(&x->name, name); sysexlist_init(&x->sx); name_add(&o->sxlist, (struct name *)x); song_setcursx(o, x); return x; } /* * delete the given sysex bank from the song */ void song_sxdel(struct song *o, struct songsx *x) { if (o->cursx == x) { o->cursx = NULL; } name_remove(&o->sxlist, (struct name *)x); sysexlist_done(&x->sx); name_done(&x->name); xfree(x); } /* * return the sx with the given name */ struct songsx * song_sxlookup(struct song *o, char *name) { return (struct songsx *)name_lookup(&o->sxlist, name); } /* * the following routines are basically getters and setters for song * "current" parametters. They also deal with inheritancs of these * values */ void song_getcursx(struct song *o, struct songsx **r) { *r = o->cursx; } void song_setcursx(struct song *o, struct songsx *x) { o->cursx = x; } void song_getcurtrk(struct song *o, struct songtrk **r) { *r = o->curtrk; } void song_setcurtrk(struct song *o, struct songtrk *t) { o->curtrk = t; } void song_getcurfilt(struct song *o, struct songfilt **r) { *r = o->curfilt; } void song_setcurfilt(struct song *o, struct songfilt *f) { if (o->curfilt == f) return; if (mux_isopen) norm_shut(); o->curfilt = f; if (o->curout && o->curout->filt != f) o->curout = NULL; if (mux_isopen) mux_flush(); } void song_getcurchan(struct song *o, struct songchan **r, int input) { *r = (input) ? o->curin : o->curout; } void song_setcurchan(struct song *o, struct songchan *c, int input) { struct songchan **pc = input ? &o->curin : &o->curout; if (*pc == c) return; *pc = c; if (c != NULL && !input) song_setcurfilt(o, c->filt); } unsigned song_endpos(struct song *o) { struct seqptr *mp; struct songtrk *t; unsigned m, tpm, tpb, bpm, len, maxlen, delta; maxlen = 0; SONG_FOREACH_TRK(o, t) { len = track_numtic(&t->track); if (maxlen < len) maxlen = len; } m = 0; len = 0; mp = seqptr_new(&o->meta); while (len < maxlen) { while (seqptr_evget(mp)) { /* nothing */ } seqptr_getsign(mp, &bpm, &tpb); tpm = bpm * tpb; delta = seqptr_skip(mp, tpm); if (delta > 0) { m += (maxlen - len + tpm - 1) / tpm; break; } len += tpm; m++; } seqptr_del(mp); return m; } void song_playconfev(struct song *o, struct songchan *c, struct ev *in) { struct ev ev = *in; if (!EV_ISVOICE(&ev)) { log_puts("song_playconfev: "); log_puts(c->name.str); log_puts(": "); ev_log(&ev); log_puts(": not a voice event"); log_puts("\n"); return; } ev.dev = c->dev; ev.ch = c->ch; if (!c->isinput) { mixout_putev(&ev, PRIO_CHAN); } else { norm_evcb(&ev); } } /* * send to the output all events from all chans */ void song_playconf(struct song *o) { struct songchan *i; struct seqptr *cp; struct state *st; SONG_FOREACH_CHAN(o, i) { cp = seqptr_new(&i->conf); for (;;) { st = seqptr_evget(cp); if (st == NULL) break; song_playconfev(o, i, &st->ev); } seqptr_del(cp); } mux_flush(); mux_sleep(DEFAULT_CHANWAIT); } /* * send all sysex messages */ void song_playsysex(struct song *o) { struct songsx *l; struct sysex *s; struct chunk *c; SONG_FOREACH_SX(o, l) { for (s = l->sx.first; s != NULL; s = s->next) { for (c = s->first; c != NULL; c = c->next) { mux_sendraw(s->unit, c->data, c->used); mux_flush(); } mux_sleep(DEFAULT_SXWAIT); } } } /* * play a meta event */ void song_metaput(struct song *o, struct state *s) { switch(s->ev.cmd) { case EV_TIMESIG: if ((s->flags & STATE_CHANGED) && (o->beat != 0 || o->tic != 0)) { /* * found an incomplete measure, * skip to the beggining of the next one */ o->beat = 0; o->tic = 0; o->measure++; } o->bpm = s->ev.timesig_beats; o->tpb = s->ev.timesig_tics; break; case EV_TEMPO: o->tempo = s->ev.tempo_usec24; if (mux_isopen) mux_chgtempo(o->tempo_factor * o->tempo / 0x100); break; default: break; } } /* * save the state at the given start position, so that we can repeat * playback from there. */ void song_loop_init(struct song *o) { struct state *s, *snext; struct statelist *slist; struct songtrk *t; unsigned qstep; if (o->loop) { o->loop_mstart = o->curpos; o->loop_mend = o->curpos + o->curlen; } else o->loop_mstart = o->loop_mend = 0; if (o->loop_mstart == o->loop_mend) return; o->loop_tstart = track_findmeasure(&o->meta, o->loop_mstart); o->loop_tend = track_findmeasure(&o->meta, o->loop_mend); qstep = o->curquant / 2; if (o->loop_tstart > qstep) { o->loop_tstart -= qstep; o->loop_tend -= qstep; } o->loop_metaptr = seqptr_new(&o->meta); seqptr_skip(o->loop_metaptr, o->loop_tstart); SONG_FOREACH_TRK(o, t) { t->loop_trackptr = seqptr_new(&t->track); seqptr_skip(t->loop_trackptr, o->loop_tstart); /* * Drop notes, as we don't restore them */ slist = &t->loop_trackptr->statelist; for (s = slist->first; s != NULL; s = snext) { snext = s->next; if (EV_ISNOTE(&s->ev)) { statelist_rm(slist, s); state_del(s); } } /* * Drop terminated states */ statelist_outdate(slist); } } /* * free loop state */ void song_loop_done(struct song *o) { struct songtrk *t; if (o->loop_mstart == o->loop_mend) return; seqptr_del(o->loop_metaptr); SONG_FOREACH_TRK(o, t) { statelist_empty(&t->loop_trackptr->statelist); seqptr_del(t->loop_trackptr); } } /* * restore the given track (or meta-track if NULL) from its loop * state. */ void song_loop_track(struct song *o, struct songtrk *t) { struct seqptr *sp, *lp; struct statelist *dlist, *slist; struct state *s, *d, *dnext; struct ev re; if (t) { sp = t->trackptr; lp = t->loop_trackptr; } else { sp = o->metaptr; lp = o->loop_metaptr; } dlist = &sp->statelist; slist = &lp->statelist; /* * cancel states not present in loop state (this will cancel * all notes as their state is not saved) */ for (d = dlist->first; d != NULL; d = dnext) { dnext = d->next; s = statelist_lookup(slist, &d->ev); if (s != NULL) continue; if (!state_cancel(d, &re)) continue; statelist_update(dlist, &re); if (d->tag) { if (t != NULL) mixout_putev(&d->ev, PRIO_TRACK); else song_metaput(o, d); } } /* * restore states from loop start */ for (s = slist->first; s != NULL; s = s->next) { d = statelist_lookup(dlist, &s->ev); if (d != NULL && state_eq(d, &s->ev)) continue; if (!state_restore(s, &re)) continue; d = statelist_update(dlist, &re); if (d->phase & EV_PHASE_FIRST) { d->tag = (t != NULL) ? !t->mute : EV_ISMETA(&d->ev); } if (d->tag) { if (t != NULL) mixout_putev(&d->ev, PRIO_TRACK); else song_metaput(o, d); } } sp->pos = lp->pos; sp->delta = lp->delta; sp->tic = lp->tic; } void song_loop_rec(struct song *o) { if (o->playptr != NULL) return; o->playptr = seqptr_new(&o->rec); seqptr_link(o->playptr, o->recptr); if (seqptr_ticdel(o->playptr, o->loop_tstart, &o->rec_replay) != o->loop_tstart) { log_puts("song_loop_rec: events in the way\n"); panic(); } seqptr_ticput(o->playptr, o->loop_tstart); if (song_debug) log_puts("song_loop_rec: starting replay\n"); } /* * continue playback from the loop start position */ int song_loop_repeat(struct song *o) { struct songtrk *t; if (o->loop_mstart == o->loop_mend || o->abspos != o->loop_tend) return 0; o->abspos = o->loop_tstart; o->measure -= o->loop_mend - o->loop_mstart; SONG_FOREACH_TRK(o, t) { song_loop_track(o, t); } song_loop_track(o, NULL); if (o->mode >= SONG_REC) song_loop_rec(o); return 1; } /* * move all track pointers 1 tick forward. Return true if at least one * track moved forward and 0 if no track moved forward (ie end of the * song was track reached) * * Note that must be no events available on any track, in other words, * this routine must be called after song_ticplay() */ void song_ticskip(struct song *o) { struct ev ev; struct songtrk *i; struct state *s; unsigned neot; unsigned period; /* * tempo_track */ neot = seqptr_ticskip(o->metaptr, 1); o->tic++; if (o->tic >= o->tpb) { o->tic = 0; o->beat++; if (o->beat >= o->bpm) { o->beat = 0; o->measure++; } } o->abspos++; SONG_FOREACH_TRK(o, i) { neot |= seqptr_ticskip(i->trackptr, 1); } if (o->mode >= SONG_REC) { if (o->playptr) { seqptr_ticdel(o->playptr, 1, &o->rec_replay); seqptr_ticput(o->playptr, 1); } seqptr_ticput(o->recptr, 1); statelist_outdate(&o->rec_input); /* * increment length and terminate notes longer than * the loop period */ period = o->loop_tend - o->loop_tstart; for (s = o->rec_input.first; s != NULL; s = s->next) { if (s->tag != TAG_REC) continue; if (++s->tic != period) continue; if (state_cancel(s, &ev)) { seqptr_evmerge2(o->recptr, &o->rec_replay, &ev, NULL); mixout_putev(&ev, 0); } s->tag = TAG_OFF; } } if (song_loop_repeat(o)) return; if (neot == 0 && !o->complete) { cons_puttag("complete"); o->complete = 1; } } /* * play data corresponding to the current tick used by playcb and * recordcb. */ void song_ticplay(struct song *o) { struct songtrk *i; struct state *st, *sr; while ((st = seqptr_evget(o->metaptr))) song_metaput(o, st); if (o->tic == 0) { cons_putpos(o->measure, o->beat, o->tic); } metro_tic(&o->metro, o->beat, o->tic); SONG_FOREACH_TRK(o, i) { while ((st = seqptr_evget(i->trackptr))) { if (st->phase & EV_PHASE_FIRST) st->tag = i->mute ? 0 : 1; if (st->tag) mixout_putev(&st->ev, PRIO_TRACK); } } if (o->mode >= SONG_REC) { /* * remove all events from rec track and put them back * on it by merging them with the input states. The * played states table is updated so it always contain * the exact state of the original track. */ if (o->playptr != NULL) { for(;;) { st = seqptr_evdel(o->playptr, &o->rec_replay); if (st == NULL) break; st->tag = 1; sr = seqptr_evmerge1(o->recptr, st); if (sr != NULL) mixout_putev(&st->ev, 0); } } } } /* * restore all frames in the given statelist. First, restore events * that have context, then the rest. */ void song_confrestore(struct statelist *slist, int all, unsigned prio) { struct state *s; struct ev re; for (s = slist->first; s != NULL; s = s->next) { if (EV_ISNOTE(&s->ev)) continue; if (s->tag) { if (song_debug) { log_puts("song_strestore: "); ev_log(&s->ev); log_puts(": not restored (tagged)\n"); } continue; } if (!(s->phase & EV_PHASE_LAST) && !all) { if (song_debug) { log_puts("song_strestore: "); ev_log(&s->ev); log_puts(": not restored (unterminated)\n"); } continue; } if (state_restore(s, &re)) { if (song_debug) { log_puts("song_strestore: "); ev_log(&s->ev); log_puts(": restored -> "); ev_log(&re); log_puts("\n"); } mixout_putev(&re, prio); } s->tag = 1; } } /* * cancel all frames in the given state list */ void song_confcancel(struct statelist *slist, unsigned prio) { struct state *s; struct ev ca; for (s = slist->first; s != NULL; s = s->next) { if (s->tag) { if (state_cancel(s, &ca)) { if (song_debug) { log_puts("song_stcancel: "); ev_log(&s->ev); log_puts(": canceled -> "); ev_log(&ca); log_puts("\n"); } mixout_putev(&ca, prio); } s->tag = 0; } else { if (song_debug) { log_puts("song_stcancel: "); ev_log(&s->ev); log_puts(": not canceled (no tag)\n"); } } } } /* * mute the given track by canceling all states */ void song_trkmute(struct song *s, struct songtrk *t) { if (s->mode >= SONG_PLAY) song_confcancel(&t->trackptr->statelist, PRIO_TRACK); t->mute = 1; } /* * unmute the given track by restoring all states */ void song_trkunmute(struct song *s, struct songtrk *t) { if (s->mode >= SONG_PLAY) song_confrestore(&t->trackptr->statelist, 1, PRIO_TRACK); t->mute = 0; } /* * merge recorded track into current track */ void song_mergerec(struct song *o) { struct state *st; struct track loop; struct seqptr *lp; struct songtrk *t; struct songsx *x; struct sysex *e; struct state *s; struct ev ev; unsigned period, offset; /* * if there is no filter for recording there may be * unterminated frames, so finalize them. */ for (s = o->rec_input.first; s != NULL; s = s->next) { if (s->tag != TAG_REC && s->tag != TAG_PLAY) continue; if (state_cancel(s, &ev)) { if (s->tag == TAG_REC) { seqptr_evmerge2(o->recptr, &o->rec_replay, &ev, NULL); } mixout_putev(&ev, 0); } s->tag = TAG_OFF; } /* * cancel any remaining frames (replayed events in loop mode) */ for (s = o->recptr->statelist.first; s != NULL; s = s->next) { if (!state_cancel(s, &ev)) continue; mixout_putev(&ev, 0); } if (o->playptr != NULL) { period = o->loop_tend - o->loop_tstart; offset = (o->playptr->tic - o->loop_tstart) % period; /* * advance until loop end */ while (offset++ != period) { seqptr_ticdel(o->playptr, 1, &o->rec_replay); seqptr_ticput(o->playptr, 1); seqptr_ticput(o->recptr, 1); for(;;) { st = seqptr_evdel(o->playptr, &o->rec_replay); if (st == NULL) break; st->tag = 1; seqptr_evmerge1(o->recptr, st); } } track_init(&loop); lp = seqptr_new(&loop); seqptr_ticput(lp, o->loop_tstart); /* * unroll loop into a new 'loop' track */ while (o->rec.first->ev.cmd != EV_NULL) { seqptr_ticdel(o->playptr, 1, &o->rec_replay); seqptr_ticput(o->playptr, 1); seqptr_ticput(o->recptr, 1); seqptr_ticput(lp, 1); for(;;) { st = seqptr_evdel(o->playptr, &o->rec_replay); if (st == NULL) break; if (st->phase & EV_PHASE_FIRST) st->tag = 0; if (st->tag) seqptr_evmerge1(o->recptr, st); else seqptr_evput(lp, &st->ev); } } seqptr_del(lp); track_swap(&o->rec, &loop); track_done(&loop); } song_getcurtrk(o, &t); if (t) { undo_track_save(o, &t->track, "record", t->name.str); track_merge(&o->curtrk->track, &o->rec); undo_track_diff(o); } track_clear(&o->rec); song_getcursx(o, &x); if (x) { for (;;) { e = sysexlist_get(&o->recsx); if (e == NULL) break; sysexlist_put(&x->sx, e); } } sysexlist_clear(&o->recsx); } /* * call-back called when the first midi tick arrives */ void song_startcb(struct song *o) { if (song_debug) { log_puts("song_startcb:\n"); } if (o->mode >= SONG_PLAY) { song_ticplay(o); mux_flush(); } o->started = 1; } /* * call-back called when the midi clock stopped */ void song_stopcb(struct song *o) { struct songtrk *t; if (song_debug) log_puts("song_stopcb:\n"); /* * stop all sounding notes */ SONG_FOREACH_TRK(o, t) { song_confcancel(&t->trackptr->statelist, PRIO_TRACK); } } /* * call-back, called when the midi clock moved one tick forward */ void song_movecb(struct song *o) { if (o->mode >= SONG_PLAY) { (void)song_ticskip(o); song_ticplay(o); } mux_flush(); } /* * call-back called when a midi event arrives */ void song_evcb(struct song *o, struct ev *ev) { struct ev filtout[FILT_MAXNRULES], rev; struct state *s; unsigned i, nev; unsigned usec24; if (o->tap_mode != SONG_TAP_OFF && evspec_matchev(&o->tap_evspec, ev)) { if (!(ev_phase(ev) & EV_PHASE_FIRST)) return; if (o->started) return; if (o->tap_cnt == 0) { if (o->tap_mode == SONG_TAP_START) { log_puts("start triggered\n"); o->tap_cnt = -1; mux_ticcb(); } else { log_puts("measuring tempo...\n"); o->tap_time = mux_wallclock; } } else if (o->tap_mode == SONG_TAP_TEMPO && o->tap_cnt == 1) { usec24 = (long)(mux_wallclock - o->tap_time) / o->tpb; log_puts("start triggered, tempo = "); log_putu(60 * 24000000 / o->tpb / usec24); log_puts("\n"); if (usec24 < TEMPO_MIN || usec24 > TEMPO_MAX) { log_puts("tempo out of range, aborted\n"); o->tap_cnt = 0; return; } o->tempo = usec24; mux_chgtempo(o->tempo); mux_ticcb(); o->tap_cnt = -1; } o->tap_cnt++; return; } /* * apply filter, if any */ if (o->curfilt) { nev = filt_do(&o->curfilt->filt, ev, filtout); } else { filtout[0] = *ev; nev = 1; } /* * output and/or record resulting events */ ev = filtout; for (i = 0; i < nev; i++) { if (o->mode >= SONG_REC) { s = statelist_update(&o->rec_input, ev); if (s->phase & EV_PHASE_FIRST) { s->tic = 0; if (s->flags & (STATE_BOGUS | STATE_NESTED)) { s->tag = TAG_OFF; } else if ((mux_getphase() >= MUX_START) && (o->loop_mstart == o->loop_mend || o->abspos >= o->loop_tstart)) { s->tag = TAG_REC; } else s->tag = TAG_PLAY; } if (s->tag == TAG_REC) { if (seqptr_evmerge2(o->recptr, &o->rec_replay, ev, &rev)) mixout_putev(&rev, 0); } if (s->tag == TAG_REC || s->tag == TAG_PLAY) mixout_putev(ev, 0); } else mixout_putev(ev, 0); ev++; } } /* * call-back called when a sysex message arrives */ void song_sysexcb(struct song *o, struct sysex *sx) { if (song_debug) log_puts("song_sysexcb:\n"); if (sx == NULL) { log_puts("got null sx\n"); return; } if (o->mode >= SONG_REC) sysexlist_put(&o->recsx, sx); else sysex_del(sx); } unsigned song_mtcpos(struct song *o, unsigned where, unsigned offs) { struct seqptr *p; unsigned delta, bpm, tpb, meas, beat, tick; unsigned long long pos; unsigned long usec24; p = seqptr_new(&o->meta); pos = 0; meas = beat = tick = 0; for (;;) { seqptr_getsign(p, &bpm, &tpb); seqptr_gettempo(p, &usec24); delta = (where - meas) * bpm * tpb - beat * tpb - tick; if (delta <= offs) break; delta -= offs; if (!seqptr_eot(p)) { delta = seqptr_ticskip(p, delta); while (seqptr_evget(p)) ; /* nothing */ } tick += delta; beat += tick / tpb; tick = tick % tpb; meas += beat / bpm; beat = beat % bpm; pos += (unsigned long long)delta * usec24; } /* round to frame */ pos -= pos % (24000000ULL / DEFAULT_FPS); /* wrap every 24 hours */ pos = pos % (24000000ULL * 36000 * 24); seqptr_del(p); return pos / (24000000ULL / MTC_SEC); } /* * cancel the current state, and restore the state of * the new postition, must be in idle mode * * return the absolute position as an ``MTC position'', * suitable for a MMC relocate message. */ unsigned song_loc(struct song *o, unsigned how, unsigned where, unsigned offs) { struct state *s; struct songtrk *t; unsigned maxdelta, delta; unsigned bpm, tpb; unsigned long long pos, endpos; unsigned long usec24; seqptr_del(o->metaptr); o->metaptr = seqptr_new(&o->meta); /* please gcc */ endpos = 0xdeadbeef; maxdelta = 0xdeadbeef; /* * XXX: when not in LOC_MEAS and LOC_SPP modes, the MTC position * can overflow. Limit song_loc() to 24 hours */ switch (how) { case LOC_MEAS: break; case LOC_MTC: endpos = (unsigned long long)where * (24000000 / MTC_SEC); offs = 0; break; case LOC_SPP: where *= o->tics_per_unit / 16; offs = 0; break; default: log_puts("song_loc: bad argument\n"); panic(); } pos = 0; o->abspos = 0; o->measure = o->beat = o->tic = 0; for (;;) { seqptr_getsign(o->metaptr, &bpm, &tpb); seqptr_gettempo(o->metaptr, &usec24); switch (how) { case LOC_MEAS: maxdelta = (where - o->measure) * bpm * tpb - o->beat * tpb - o->tic; break; case LOC_MTC: maxdelta = (endpos - pos) / usec24; break; case LOC_SPP: maxdelta = where - o->abspos; break; } if (maxdelta <= offs) break; maxdelta -= offs; delta = seqptr_ticskip(o->metaptr, maxdelta); s = seqptr_evget(o->metaptr); if (s == NULL) { /* XXX: integrate this in ticskip() & co */ statelist_outdate(&o->metaptr->statelist); delta = maxdelta; } o->tic += delta; o->beat += o->tic / tpb; o->tic = o->tic % tpb; o->measure += o->beat / bpm; o->beat = o->beat % bpm; o->abspos += delta; pos += (unsigned long long)delta * usec24; } /* * process all meta events of the current tick, * so we get the time signature and the tempo of * the next tick */ while (seqptr_evget(o->metaptr)) ; /* nothing */ o->complete = !seqptr_eot(o->metaptr); /* * move all tracks to the current position */ SONG_FOREACH_TRK(o, t) { /* * cancel and free old states */ song_confcancel(&t->trackptr->statelist, PRIO_TRACK); statelist_empty(&t->trackptr->statelist); seqptr_del(t->trackptr); /* * allocate and restore new states */ t->trackptr = seqptr_new(&t->track); seqptr_skip(t->trackptr, o->abspos); for (s = t->trackptr->statelist.first; s != NULL; s = s->next) s->tag = 0; song_confrestore(&t->trackptr->statelist, o->mode >= SONG_PLAY, PRIO_TRACK); /* * check if we reached the end-of-track */ if (!seqptr_eot(t->trackptr)) o->complete = 0; } if (o->mode >= SONG_REC) track_clear(&o->rec); #ifdef SONG_DEBUG if (!track_isempty(&o->rec)) { log_puts("song_loc: rec track not empty\n"); panic(); } #endif /* * at this point track is cleared, song->rec_xxx lists are * empty */ if (o->playptr) seqptr_del(o->playptr); seqptr_del(o->recptr); o->recptr = seqptr_new(&o->rec); o->playptr = NULL; if (o->mode >= SONG_REC) { seqptr_seek(o->recptr, o->abspos); } statelist_empty(&o->rec_input); statelist_empty(&o->rec_replay); /* * we've the tempo to be set, as in the LOC_MTC case, the return * value is a franction of the tick length. The tempo is set, * either by mux_open() or by the song_metaput() calls. */ for (s = o->metaptr->statelist.first; s != NULL; s = s->next) { if (EV_ISMETA(&s->ev)) { if (song_debug) { log_puts("song_loc: "); ev_log(&s->ev); log_puts(": restoring meta-event\n"); } song_metaput(o, s); s->tag = 1; } else { if (song_debug) { log_puts("song_loc: "); ev_log(&s->ev); log_puts(": not restored (not tagged)\n"); } s->tag = 0; } } if (o->complete) cons_puttag("complete"); pos = endpos - pos; /* * display initial position */ cons_putpos(o->measure, o->beat, o->tic); if (song_debug) { log_puts("song_loc: "); log_putu(where); log_puts(" -> "); log_putu(o->measure); log_puts(":"); log_putu(o->beat); log_puts(":"); log_putu(o->tic); log_puts("("); log_putu(o->abspos); log_puts(") +"); log_putu(pos); log_puts("/"); log_putu(usec24); log_puts("\n"); } return pos; } /* * relocate requested from a device. Move the song to the * tick just before the given MTC position, and return * the time (24-th of us) between the requested position * and the current tick. This way the mux module will "skip" * this duration to ensure we're perfectly in sync. */ unsigned song_gotocb(struct song *o, int how, unsigned where) { return song_loc(o, how, where, 0); } /* * set the current mode */ void song_setmode(struct song *o, unsigned newmode) { struct songtrk *t; unsigned oldmode; oldmode = o->mode; o->mode = newmode; if (oldmode >= SONG_PLAY) { mux_stopreq(); } if (newmode < oldmode) metro_setmode(&o->metro, newmode); if (oldmode >= SONG_REC && newmode < SONG_REC) song_mergerec(o); if (oldmode >= SONG_PLAY && newmode < SONG_PLAY) song_loop_done(o); if (oldmode >= SONG_IDLE && newmode < SONG_IDLE) { /* * cancel and free states */ SONG_FOREACH_TRK(o, t) { song_confcancel(&t->trackptr->statelist, PRIO_TRACK); statelist_empty(&t->trackptr->statelist); seqptr_del(t->trackptr); } if (o->playptr) seqptr_del(o->playptr); statelist_empty(&o->rec_input); statelist_done(&o->rec_input); statelist_empty(&o->rec_replay); statelist_done(&o->rec_replay); seqptr_del(o->recptr); seqptr_del(o->metaptr); norm_shut(); mux_flush(); mux_close(); } if (oldmode < SONG_PLAY && newmode >= SONG_PLAY) { o->tap_cnt = 0; o->complete = 0; song_loop_init(o); } if (oldmode < SONG_IDLE && newmode >= SONG_IDLE) { o->abspos = 0; o->measure = 0; o->beat = 0; o->tic = 0; /* * get empty states */ SONG_FOREACH_TRK(o, t) { t->trackptr = seqptr_new(&t->track); } o->metaptr = seqptr_new(&o->meta); o->recptr = seqptr_new(&o->rec); o->playptr = NULL; statelist_init(&o->rec_replay); statelist_init(&o->rec_input); mux_open(); mux_chgticrate(o->tics_per_unit); /* * send sysex messages and channel config messages */ song_playsysex(o); song_playconf(o); mux_flush(); } if (newmode > oldmode) metro_setmode(&o->metro, newmode); } /* * cancel the current state, and restore the state of * the given position */ void song_goto(struct song *o, unsigned measure) { unsigned mmcpos, offs; if (o->mode >= SONG_IDLE) { /* * 1 measure of count-down for recording */ if (o->mode >= SONG_REC && measure > 0) measure--; o->started = 0; offs = (o->mode >= SONG_PLAY && !o->tap_mode) ? o->curquant / 2 : 0; /* * Move all tracks to given measure. * * If we're starting playback/recording and using MTC, * then just send the request, song_loc() will be called * back later, upon the initial MTC full-frame message. */ if (o->mode >= SONG_PLAY && mididev_mtcsrc != NULL) { mmcpos = song_mtcpos(o, measure, offs); mux_gotoreq(mmcpos); } else song_loc(o, LOC_MEAS, measure, offs); } else cons_putpos(measure, 0, 0); } /* * stop play/record: undo song_start and things done during the * play/record process. Must be called with the mux initialised */ void song_stop(struct song *o) { song_setmode(o, 0); cons_putpos(o->curpos, 0, 0); } /* * play the song initialize the midi/timer and start the event loop */ void song_play(struct song *o) { unsigned m; m = (o->mode >= SONG_IDLE) ? o->measure : o->curpos; song_setmode(o, SONG_PLAY); song_goto(o, m); mux_startreq(o->tap_mode != SONG_TAP_OFF); mux_flush(); if (song_debug) { log_puts("song_play: waiting for a start event...\n"); } } /* * record the current track: initialise the midi/timer and start the * event loop */ void song_record(struct song *o) { struct songtrk *t; unsigned m; song_getcurtrk(o, &t); if (!t || t->mute) { log_puts("song_record: no current track (or muted)\n"); } m = (o->mode >= SONG_IDLE) ? o->measure : o->curpos; song_setmode(o, SONG_REC); song_goto(o, m); mux_startreq(o->tap_mode != SONG_TAP_OFF); mux_flush(); if (song_debug) { log_puts("song_record: waiting for a start event...\n"); } } /* * move input events directly to the output */ void song_idle(struct song *o) { unsigned m; m = (o->mode >= SONG_IDLE) ? o->measure : o->curpos; song_setmode(o, SONG_IDLE); song_goto(o, m); mux_flush(); if (song_debug) { log_puts("song_idle: started loop...\n"); } } /* * the song_try_xxx() routines return 1 if we can have exclusive write * access to the corresponding object, 0 otherwise. * * builting functions that cannot handle properly concurent access to * the object, must call the corresponding song_try_xxx(), and fail if * they cannot get exclusive access to it. */ unsigned song_try_mode(struct song *o, unsigned reqmode) { if (o->mode > reqmode) { cons_err("song in use, use ``s'' to release it"); return 0; } return 1; } unsigned song_try_curev(struct song *o) { return 1; } unsigned song_try_curpos(struct song *o) { return 1; } unsigned song_try_curlen(struct song *o) { return 1; } unsigned song_try_curquant(struct song *o) { return 1; } unsigned song_try_curtrk(struct song *o) { return 1; } unsigned song_try_curchan(struct song *o, int input) { return 1; } unsigned song_try_curfilt(struct song *o) { return song_try_mode(o, 0); } unsigned song_try_cursx(struct song *o) { return song_try_mode(o, 0); } unsigned song_try_trk(struct song *o, struct songtrk *f) { if (o->mode >= SONG_PLAY) { cons_err("track in use, use ``s'' or ``i'' to release it"); return 0; } return 1; } unsigned song_try_chan(struct song *o, struct songchan *f, int input) { return song_try_mode(o, 0); } unsigned song_try_filt(struct song *o, struct songfilt *f) { return song_try_mode(o, 0); } unsigned song_try_sx(struct song *o, struct songsx *x) { if (o->mode >= SONG_REC && (o->cursx == x)) { cons_err("sysex in use, use ``s'' or ``i'' to stop recording"); return 0; } return 1; } unsigned song_try_meta(struct song *o) { if (o->mode >= SONG_PLAY) { cons_err("meta track in use, use ``s'' or ``i'' to release it"); return 0; } return 1; } unsigned song_try_ev(struct song *o, unsigned cmd) { struct songfilt *f; struct songchan *c; struct songtrk *t; SONG_FOREACH_TRK(o, t) { if (track_evcnt(&t->track, cmd)) { cons_errs(t->name.str, "event in use by track"); return 0; } } SONG_FOREACH_CHAN(o, c) { if (track_evcnt(&c->conf, cmd)) { cons_errs(c->name.str, "event in use by input"); return 0; } } SONG_FOREACH_FILT(o, f) { if (filt_evcnt(&f->filt, cmd)) { cons_errs(f->name.str, "event in use by filter"); return 0; } } return 1; } void song_confev(struct song *o, struct songchan *c, struct ev *ev) { track_confev(&c->conf, ev); if (mux_isopen) { song_playconfev(o, c, ev); mux_flush(); } } void song_unconfev(struct song *o, struct songchan *c, struct evspec *es) { track_unconfev(&c->conf, es); } midish-1.4.0/song.h010066400017510001751000000171741501104363400126300ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_SONG_H #define MIDISH_SONG_H #define SONG_DEFAULT_BPM 4 #define SONG_DEFAULT_TPB 24 #define SONG_DEFAULT_TEMPO 60 #include "name.h" #include "track.h" #include "frame.h" #include "filt.h" #include "sysex.h" #include "metro.h" struct songtrk; struct songchan; struct songfilt; struct songsx; struct undo; struct songtrk { struct name name; /* identifier + list entry */ struct track track; /* actual data */ struct seqptr *trackptr; /* track pointer for RT */ struct seqptr *loopstate; struct songfilt *curfilt; /* source and dest. channel */ struct seqptr *loop_trackptr; /* backup of trackptr */ unsigned mute; }; struct songchan { struct name name; /* identifier + list entry */ struct track conf; /* data to send on initialization */ unsigned dev, ch; /* dev/chan of the chan */ int isinput; struct songfilt *filt; /* default filter (output only) */ }; struct songfilt { struct name name; /* identifier + list entry */ struct filt filt; /* filter rules */ }; struct songsx { struct name name; /* identifier + list entry */ struct sysexlist sx; /* list of sysex messages */ }; struct song { /* * music-related fields that should be saved */ struct track meta; /* tempo track */ struct name *trklist; /* list of tracks */ struct name *chanlist; /* list of channels */ struct name *filtlist; /* list of fiters */ struct name *sxlist; /* list of system exclive banks */ struct undo *undo; /* list of operation to undo */ unsigned undo_size; /* size of all undo buffers */ unsigned tics_per_unit; /* number of tics in an unit note */ unsigned tempo_factor; /* tempo := tempo * factor / 256 */ struct songtrk *curtrk; /* default track */ struct songfilt *curfilt; /* default filter */ struct songchan *curout; /* default output channel */ struct songchan *curin; /* default input channel */ struct songsx *cursx; /* default sysex bank */ unsigned curpos; /* default position (in measures) */ unsigned curlen; /* selection length */ unsigned curquant; /* default quantization step */ struct evspec curev; /* evspec for track editing */ struct metro metro; /* metonome conf. */ struct evspec tap_evspec; /* event to trigger start manually */ unsigned long tap_time; /* timestamp of first tap */ #define SONG_TAP_OFF 0 #define SONG_TAP_START 1 #define SONG_TAP_TEMPO 2 int tap_mode; /* one of above */ int tap_cnt; /* number of taps, -1 means done */ /* * clipboard */ #define CLIP_OFFS (256 * 96) struct track clip; /* tmp track for copy & paste */ /* * temporary variables used in real-time operations */ struct seqptr *metaptr; /* cur. pos in meta track */ unsigned long tempo; /* cur tempo in 24th of usec per tic */ unsigned bpm, tpb; /* cur time signature */ struct track rec; /* track being recorded */ struct seqptr *recptr; /* record position in rec track */ struct seqptr *playptr; /* replay position in rec track */ struct statelist rec_input; /* events to be recorded */ struct statelist rec_replay; /* recorded events to be replayed */ struct sysexlist recsx; unsigned abspos; /* cur postion in ticks */ unsigned measure, beat, tic; /* cur position (for metronome) */ #define SONG_IDLE 1 /* filter running */ #define SONG_PLAY 2 /* above + playback */ #define SONG_REC 3 /* above + recording */ unsigned mode; /* real-time "mode" */ unsigned started; /* playback started */ unsigned complete; /* playback completed */ unsigned metro_mask; /* if enable = (mask | mode) */ unsigned loop; /* loop-mode enabled */ unsigned loop_mstart; /* loop start measure */ unsigned loop_mend; /* loop end measure */ unsigned loop_tstart; /* loop start tick */ unsigned loop_tend; /* loop end tick */ struct seqptr *loop_metaptr; /* backup of metaptr */ }; extern char *song_tap_modestr[3]; #define SONG_FOREACH_TRK(s, i) \ for (i = (struct songtrk *)(s)->trklist; \ i != NULL; \ i = (struct songtrk *)i->name.next) #define SONG_FOREACH_CHAN(s, i) \ for (i = (struct songchan *)(s)->chanlist; \ i != NULL; \ i = (struct songchan *)i->name.next) #define SONG_FOREACH_FILT(s, i) \ for (i = (struct songfilt *)(s)->filtlist; \ i != NULL; \ i = (struct songfilt *)i->name.next) #define SONG_FOREACH_SX(s, i) \ for (i = (struct songsx *)(s)->sxlist; \ i != NULL; \ i = (struct songsx *)i->name.next) struct song *song_new(void); void song_delete(struct song *); void song_init(struct song *); void song_done(struct song *); struct songtrk *song_trknew(struct song *, char *); struct songtrk *song_trklookup(struct song *, char *); void song_trkdel(struct song *, struct songtrk *); void song_trkmute(struct song *, struct songtrk *); void song_trkunmute(struct song *, struct songtrk *); struct songchan *song_channew(struct song *, char *, unsigned, unsigned, int); struct songchan *song_chanlookup(struct song *, char *, int); struct songchan *song_chanlookup_bynum(struct song *, unsigned, unsigned, int); void song_confev(struct song *, struct songchan *, struct ev *); void song_unconfev(struct song *, struct songchan *, struct evspec *); void song_chandel(struct song *, struct songchan *); struct songfilt *song_filtnew(struct song *, char *); struct songfilt *song_filtlookup(struct song *, char *); void song_filtdel(struct song *, struct songfilt *); struct songsx *song_sxnew(struct song *, char *); struct songsx *song_sxlookup(struct song *, char *); void song_sxdel(struct song *, struct songsx *); void song_getcursx(struct song *, struct songsx **); void song_setcursx(struct song *, struct songsx *); void song_getcurtrk(struct song *, struct songtrk **); void song_setcurtrk(struct song *, struct songtrk *); void song_getcurfilt(struct song *, struct songfilt **); void song_setcurfilt(struct song *, struct songfilt *); void song_getcurchan(struct song *, struct songchan **, int); void song_setcurchan(struct song *, struct songchan *, int); unsigned song_endpos(struct song *); void song_setmode(struct song *, unsigned); void song_goto(struct song *, unsigned); void song_record(struct song *); void song_play(struct song *); void song_idle(struct song *); void song_stop(struct song *); unsigned song_try_mode(struct song *, unsigned); unsigned song_try_curev(struct song *); unsigned song_try_curpos(struct song *); unsigned song_try_curlen(struct song *); unsigned song_try_curquant(struct song *); unsigned song_try_curtrk(struct song *); unsigned song_try_curchan(struct song *, int); unsigned song_try_curfilt(struct song *); unsigned song_try_cursx(struct song *); unsigned song_try_trk(struct song *, struct songtrk *); unsigned song_try_chan(struct song *, struct songchan *, int); unsigned song_try_filt(struct song *, struct songfilt *); unsigned song_try_sx(struct song *, struct songsx *); unsigned song_try_meta(struct song *); unsigned song_try_ev(struct song *, unsigned); extern unsigned song_debug; #endif /* MIDISH_SONG_H */ midish-1.4.0/state.c010066400017510001751000000270661501104363400127760ustar00alexalex/* * Copyright (c) 2003-2019 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * states are structures used to hold events like notes, last values * of controllers, the current value of the bender, etc... * * states are linked to a list (statelist structure), so that the list * contains the complete state of the MIDI stream (ie all sounding * notes, states of all controllers etc...) * * statelist structures are used in the real-time filter, so we use a * state pool. In a typical performace, the maximum state list length * is roughly equal to the maximum sounding notes; the mean list * length is between 2 and 3 states and the maximum is between 10 and * 20 states. Currently we use a singly linked list, but for * performance reasons we shoud use a hash table in the future. * */ #include "utils.h" #include "pool.h" #include "state.h" struct pool state_pool; unsigned state_serial; void state_pool_init(unsigned size) { state_serial = 0; pool_init(&state_pool, "state", sizeof(struct state), size); } void state_pool_done(void) { pool_done(&state_pool); } struct state * state_new(void) { return (struct state *)pool_new(&state_pool); } void state_del(struct state *s) { pool_del(&state_pool, s); } /* * dump the state to stderr */ void state_log(struct state *s) { ev_log(&s->ev); if (s->flags & STATE_NEW) { log_puts(" NEW"); } if (s->flags & STATE_CHANGED) { log_puts(" CHANGED"); } if (s->flags & STATE_BOGUS) { log_puts(" BOGUS"); } if (s->flags & STATE_NESTED) { log_puts(" NESTED"); } if (s->phase & EV_PHASE_FIRST) { log_puts(" FIRST"); } if (s->phase & EV_PHASE_NEXT) { log_puts(" NEXT"); } if (s->phase & EV_PHASE_LAST) { log_puts(" LAST"); } } /* * copy an event into a state. */ void state_copyev(struct state *st, struct ev *ev, unsigned ph) { st->ev = *ev; st->phase = ph; st->flags |= STATE_CHANGED; } /* * check if the given event matches the given frame (if so, this means * that, iether the event is part of the frame, either there is a * conflict between the frame and the event) */ unsigned state_match(struct state *st, struct ev *ev) { unsigned res; res = ev_match(&st->ev, ev); #ifdef STATE_DEBUG if (res) { log_puts("state_match: "); ev_log(&st->ev); log_puts(": ok\n"); } #endif return res; } /* * check if the given state belongs to the event spec */ unsigned state_inspec(struct state *st, struct evspec *spec) { struct evinfo *ei; if (spec == NULL) { return 1; } ei = evinfo + st->ev.cmd; switch(spec->cmd) { case EVSPEC_EMPTY: return 0; case EVSPEC_ANY: goto ch; case EVSPEC_NOTE: if (!EV_ISNOTE(&st->ev)) return 0; break; default: if (st->ev.cmd != spec->cmd) return 0; } if (ei->nparams >= 1) { if (st->ev.v0 < spec->v0_min || st->ev.v0 > spec->v0_max) return 0; } if (ei->nparams >= 2) { if (st->ev.v1 < spec->v1_min || st->ev.v1 > spec->v1_max) return 0; } ch: if (ei->flags & EV_HAS_DEV) { if (st->ev.dev < spec->dev_min || st->ev.dev > spec->dev_max) return 0; } if (ei->flags & EV_HAS_CH) { if (st->ev.ch < spec->ch_min || st->ev.ch > spec->ch_max) return 0; } return 1; } /* * compare a state to a matching event (ie one for which * state_match() returns 1) */ unsigned state_eq(struct state *st, struct ev *ev) { struct evinfo *ei; if (EV_ISVOICE(&st->ev)) { switch(st->ev.cmd) { case EV_CAT: case EV_BEND: if (st->ev.v0 != ev->v0) return 0; break; default: if (st->ev.cmd != ev->cmd || st->ev.v0 != ev->v0 || st->ev.v1 != ev->v1) return 0; break; } } else if (EV_ISSX(&st->ev)) { if (st->ev.cmd != ev->cmd) return 0; ei = evinfo + st->ev.cmd; if ((ei->nparams >= 1 && st->ev.v0 != ev->v0) || (ei->nparams >= 2 && st->ev.v1 != ev->v1)) return 0; } else if (st->ev.cmd == EV_TEMPO) { if (st->ev.tempo_usec24 != ev->tempo_usec24) { return 0; } } else if (st->ev.cmd == EV_TIMESIG) { if (st->ev.timesig_beats != ev->timesig_beats || st->ev.timesig_tics != ev->timesig_tics) { return 0; } } else { log_puts("state_eq: not defined\n"); panic(); } return 1; } /* * generate an array of events that can be played in order to cancel * the given state (ie restore all parameters related to the frame * state as the frame never existed). Return the number of generated * events * * note: if zero is returned, that doesn't mean that the frame * couldn't be canceled, that just means no events are needed (btw * currently this never happens...) */ unsigned state_cancel(struct state *st, struct ev *rev) { if (st->phase & EV_PHASE_LAST) return 0; switch(st->ev.cmd) { case EV_NON: case EV_KAT: rev->cmd = EV_NOFF; rev->note_num = st->ev.note_num; rev->note_vel = EV_NOFF_DEFAULTVEL; rev->dev = st->ev.dev; rev->ch = st->ev.ch; break; case EV_CAT: rev->cmd = EV_CAT; rev->cat_val = EV_CAT_DEFAULT; rev->dev = st->ev.dev; rev->ch = st->ev.ch; break; case EV_XCTL: rev->cmd = EV_XCTL; rev->ctl_num = st->ev.ctl_num; rev->ctl_val = EV_CTL_DEFVAL(&st->ev); rev->dev = st->ev.dev; rev->ch = st->ev.ch; break; case EV_BEND: rev->cmd = EV_BEND; rev->bend_val = EV_BEND_DEFAULT; rev->dev = st->ev.dev; rev->ch = st->ev.ch; break; default: /* * other events have their EV_PHASE_LAST bit set, so * we never come here */ log_puts("state_cancel: unknown event type\n"); panic(); } return 1; } /* * generate an array of events that will restore the given state * return the number of generated events. * * note: if zero is returned, that doesn't mean that the frame * couldn't be canceled, that just means no events are needed (btw * currently this never happens...) */ unsigned state_restore(struct state *st, struct ev *rev) { if (st->flags & STATE_BOGUS) return 0; if (EV_ISNOTE(&st->ev)) { /* * we never use this function for NOTE events, so * if we're here, there is problem somewhere... */ log_puts("state_restore: can't restore note events\n"); panic(); } /* * don't restore last event of terminated frames */ if ((st->phase & EV_PHASE_LAST) && !(st->phase & EV_PHASE_FIRST)) return 0; *rev = st->ev; return 1; } /* * initialize an empty state list */ void statelist_init(struct statelist *o) { o->first = NULL; o->changed = 0; o->serial = state_serial++; } /* * destroy a statelist. All states are deleted, but if there are * states corresponding to unterminated frames, then a warning is * issued, since this probably is due to track inconsistencies */ void statelist_done(struct statelist *o) { struct state *i, *inext; /* * free all states */ for (i = o->first; i != NULL; i = inext) { /* * check that we didn't forgot to cancel some states * the EV_CTL case is here for conv_xxx() functions */ if (!(i->phase & EV_PHASE_LAST) && i->ev.cmd != EV_CTL) { log_puts("statelist_done: "); ev_log(&i->ev); log_puts(": unterminated frame\n"); } inext = i->next; statelist_rm(o, i); state_del(i); } } void statelist_dump(struct statelist *o) { struct state *i; log_puts("statelist_dump:\n"); for (i = o->first; i != NULL; i = i->next) { ev_log(&i->ev); log_puts("\n"); } } /* * create a new statelist by duplicating another one */ void statelist_dup(struct statelist *o, struct statelist *src) { struct state *i, *n; statelist_init(o); for (i = src->first; i != NULL; i = i->next) { n = state_new(); n->ev = i->ev; n->phase = i->phase; n->flags = i->flags; statelist_add(o, n); } } /* * remove and free all states from the state list */ void statelist_empty(struct statelist *o) { struct state *i, *inext; for (i = o->first; i != NULL; i = inext) { inext = i->next; statelist_rm(o, i); state_del(i); } } /* * add a state to the state list */ void statelist_add(struct statelist *o, struct state *st) { st->next = o->first; st->prev = &o->first; if (o->first) o->first->prev = &st->next; o->first = st; } /* * remove a state from the state list, the state * isn't freed */ void statelist_rm(struct statelist *o, struct state *st) { *st->prev = st->next; if (st->next) st->next->prev = st->prev; } /* * find the first state that matches the given event * return NULL if not found */ struct state * statelist_lookup(struct statelist *o, struct ev *ev) { struct state *i; for (i = o->first; i != NULL; i = i->next) { if (state_match(i, ev)) { break; } } return i; } /* * update the state of a frame when a new event is received. If this * is the first event of the frame, then create a new state. * * we dont reuse existing states, but instead we purge them and we * allocate new ones, so that states that are often updated go to the * beginning of the list. */ struct state * statelist_update(struct statelist *statelist, struct ev *ev) { struct state *st, *stnext; unsigned phase; phase = ev_phase(ev); st = statelist->first; for (;;) { if (st == NULL) { st = state_new(); st->flags = STATE_NEW; statelist_add(statelist, st); break; } stnext = st->next; if (state_match(st, ev)) { if (!(st->phase == EV_PHASE_LAST) && !(st->flags & STATE_BOGUS)) { st->flags &= ~STATE_NEW; break; } statelist_rm(statelist, st); state_del(st); } st = stnext; } switch (phase) { case EV_PHASE_FIRST: if (st->flags != STATE_NEW) { st = state_new(); st->flags = STATE_NEW | STATE_NESTED; statelist_add(statelist, st); #ifdef STATE_DEBUG log_puts("statelist_update: "); ev_log(ev); log_puts(": nested events, stacked\n"); #endif } break; case EV_PHASE_NEXT: case EV_PHASE_LAST: if (st->flags == STATE_NEW) { st->flags |= STATE_BOGUS; phase |= EV_PHASE_FIRST; phase &= ~EV_PHASE_NEXT; #ifdef STATE_DEBUG log_puts("statelist_update: "); ev_log(ev); log_puts(": missing first event\n"); #endif } break; case EV_PHASE_FIRST | EV_PHASE_NEXT: phase &= (st->flags == STATE_NEW) ? ~EV_PHASE_NEXT : ~EV_PHASE_FIRST; break; case EV_PHASE_FIRST | EV_PHASE_LAST: /* nothing */ break; default: log_puts("statelist_update: bad phase\n"); panic(); } state_copyev(st, ev, phase); statelist->changed = 1; #ifdef STATE_DEBUG log_puts("statelist_update: updated: "); state_log(st); log_puts("\n"); #endif return st; } /* * mark all states as not changed. This routine is called at the * beginning of a tick (track editting) or after a timeout (real-time * filter). */ void statelist_outdate(struct statelist *o) { struct state *i, *inext; if (!o->changed) return; o->changed = 0; for (i = o->first; i != NULL; i = inext) { inext = i->next; /* * we purge states that are terminated, but we keep states * of unknown controllers, tempo changes etc... these * states have both FIRST and LAST bits set */ if (i->phase == EV_PHASE_LAST) { #ifdef STATE_DEBUG log_puts("statelist_outdate: "); state_log(i); log_puts(": removed\n"); #endif statelist_rm(o, i); state_del(i); } else { i->flags &= ~STATE_CHANGED; } } } midish-1.4.0/state.h010066400017510001751000000067701501104363400130020ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_STATE_H #define MIDISH_STATE_H #include "ev.h" #include "utils.h" struct seqev; struct statelist; struct state { struct state *next, **prev; /* for statelist */ struct ev ev; /* last event */ unsigned phase; /* current phase (of the 'ev' field) */ /* * the following flags are set by statelist_update() and * statelist_outdate() and can be read by other routines, * but shouldn't be changed */ #define STATE_NEW 1 /* just created, never updated */ #define STATE_CHANGED 2 /* updated within the current tick */ #define STATE_BOGUS 4 /* frame detected as bogus */ #define STATE_NESTED 8 /* nested frame */ unsigned flags; /* bitmap of above */ unsigned nevents; /* number of events before timeout */ /* * the following are general purpose fields that are ignored * by state_xxx() and statelist_xxx() routines. Other * subsystems (seqptr, filt, ...) use them privately for * various purposes. See specific modules to get their various * significances. */ unsigned tag; /* user-defined tag */ unsigned tic; /* absolute tic of the FIRST event */ struct seqev *pos; /* pointer to the FIRST event */ }; struct statelist { /* * instead of a simple list, we should use a hash table here, * but statistics on real-life cases seem to show that lookups * are very fast thanks to the state ordering (average lookup * time is around 1-2 iterations for a common MIDI file), so * we keep using a simple list */ struct state *first; /* head of the state list */ unsigned changed; /* if changed within this tick */ unsigned serial; /* unique ID */ #ifdef STATE_PROF struct prof prof; #endif }; void state_pool_init(unsigned); void state_pool_done(void); struct state *state_new(void); void state_del(struct state *); void state_log(struct state *); void state_copyev(struct state *, struct ev *, unsigned); unsigned state_match(struct state *, struct ev *); unsigned state_inspec(struct state *, struct evspec *); unsigned state_eq(struct state *, struct ev *); unsigned state_cancel(struct state *, struct ev *); unsigned state_restore(struct state *, struct ev *); void statelist_init(struct statelist *); void statelist_done(struct statelist *); void statelist_dump(struct statelist *); void statelist_dup(struct statelist *, struct statelist *); void statelist_empty(struct statelist *); void statelist_add(struct statelist *, struct state *); void statelist_rm(struct statelist *, struct state *); struct state *statelist_lookup(struct statelist *, struct ev *); struct state *statelist_update(struct statelist *, struct ev *); void statelist_outdate(struct statelist *); #endif /* MIDISH_STATE_H */ midish-1.4.0/str.c010066400017510001751000000051661501104363400124630ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * simple string manupulation functions. A NULL pointer is considered * as a "non-sense" string, and cannot be used with str_new() and * str_delete(). Non-sense strings cannot be compared and have no * length */ #include #include "utils.h" #include "str.h" /* * allocate a new string and copy the string from the given argument * into the allocated buffer the argument cannot be NULL. */ char * str_new(char *val) { unsigned cnt; char *s, *buf; if (val == NULL) { log_puts("str_new: NULL pointer argument\n"); panic(); } cnt = str_len(val) + 1; buf = xmalloc(cnt, "str"); for (s = buf; cnt > 0; cnt--) { *s++ = *val++; } return buf; } /* * free a string allocated with str_new() can't be used on NULL * pointer */ void str_delete(char *s) { if (s == NULL) { log_puts("str_delete: NULL pointer argument\n"); panic(); } xfree(s); } /* * print the string to stderr; can be safely used even if the string * is NULL */ void str_log(char *s) { if (s == NULL) { log_puts(""); } else { log_puts(s); } } /* * return 1 if the two strings are identical and 0 otherwise. Two * zero-length strings are considered identical. */ unsigned str_eq(char *s1, char *s2) { if (s1 == NULL || s2 == NULL) { log_puts("std_id: NULL pointer argument\n"); panic(); } for (;;) { if (*s1 == '\0' && *s2 == '\0') { return 1; } else if (*s1 == '\0' || *s2 == '\0' || *s1 != *s2) { return 0; } s1++; s2++; } } /* * return the length of the given string */ unsigned str_len(char *s) { unsigned n; for (n = 0; *s; s++) { n++; } return n; } /* * concatenate two strings. */ char * str_cat(char *s1, char *s2) { size_t n1, n2; char *buf; n1 = str_len(s1); n2 = str_len(s2); buf = xmalloc(n1 + n2 + 1, "str"); memcpy(buf, s1, n1); memcpy(buf + n1, s2, n2); buf[n1 + n2] = 0; return buf; } midish-1.4.0/str.h010066400017510001751000000020071501104363400124570ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_STR_H #define MIDISH_STR_H char *str_new(char *); void str_delete(char *); void str_log(char *); unsigned str_eq(char *, char *); unsigned str_len(char *); char *str_cat(char *, char *); #endif /* MIDISH_STR_H */ midish-1.4.0/sysex.c010066400017510001751000000132311501104363400130160ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * system exclusive (sysex) message management. * * A sysex message is a long byte string whose size is not know in * advance. So we preallocate a large pool of 256 byte chunks and we * represent a sysex message as a list of chunks. Since there may be * several sysex messages we use a pool for the sysex messages * themselves. * * the song contains a list of sysex message, so we group them in a * list. */ #include "utils.h" #include "sysex.h" #include "defs.h" #include "pool.h" /* ------------------------------------------ sysex pool routines --- */ struct pool chunk_pool; struct pool sysex_pool; void chunk_pool_init(unsigned size) { pool_init(&chunk_pool, "chunk", sizeof(struct chunk), size); } void chunk_pool_done(void) { pool_done(&chunk_pool); } struct chunk * chunk_new(void) { struct chunk *o; o = (struct chunk *)pool_new(&chunk_pool); o->next = NULL; o->used = 0; return o; } void chunk_del(struct chunk *o) { pool_del(&chunk_pool, o); } void sysex_pool_init(unsigned size) { pool_init(&sysex_pool, "sysex", sizeof(struct sysex), size); } void sysex_pool_done(void) { pool_done(&sysex_pool); } /* * create an empty sysex message */ struct sysex * sysex_new(unsigned unit) { struct sysex *o; o = (struct sysex *)pool_new(&sysex_pool); o->next = NULL; o->unit = unit; o->first = o->last = NULL; return o; } /* * free all chunks of a sysex message, and the message * itself */ void sysex_del(struct sysex *o) { struct chunk *i, *inext; for (i = o->first; i != NULL; i = inext) { inext = i->next; chunk_del(i); } pool_del(&sysex_pool, o); } /* * add a byte to the message */ void sysex_add(struct sysex *o, unsigned data) { struct chunk *ck; ck = o->last; if (!ck) { ck = o->first = o->last = chunk_new(); } if (ck->used >= CHUNK_SIZE) { ck->next = chunk_new(); ck = ck->next; o->last = ck; } ck->data[ck->used++] = data; } /* * dump the sysex message on stderr */ void sysex_log(struct sysex *o) { struct chunk *ck; unsigned i; log_puts("unit = "); log_putx(o->unit); log_puts(", data = { "); for (ck = o->first; ck != NULL; ck = ck->next) { for (i = 0; i < ck->used; i++) { log_putx(ck->data[i]); log_puts(" "); } } log_puts("}"); } /* * check that the sysex message (1) starts with 0xf0, (2) ends with * 0xf7 and (3) doesn't contain any status bytes. */ unsigned sysex_check(struct sysex *o) { unsigned status, data; struct chunk *ck; unsigned i; status = 0; for (ck = o->first; ck != NULL; ck = ck->next) { for (i = 0; i < ck->used; i++) { data = ck->data[i]; if (data == 0xf0) { /* sysex start */ if (status != 0) { return 0; } status = data; } else if (data == 0xf7) { if (status != 0xf0) { return 0; } status = data; } else if (data > 0x7f) { return 0; } else { if (status != 0xf0) { return 0; } } } } if (status != 0xf7) { return 0; } return 1; } /* * initialize a list of sysex messages */ void sysexlist_init(struct sysexlist *o) { o->first = NULL; o->lastptr = &o->first; } /* * destroy the list */ void sysexlist_done(struct sysexlist *o) { sysexlist_clear(o); o->first = (void *)0xdeadbeef; o->lastptr = (void *)0xdeadbeef; } /* * clear the sysex list */ void sysexlist_clear(struct sysexlist *o) { struct sysex *i, *inext; for (i = o->first; i != NULL; i = inext) { inext = i->next; sysex_del(i); } o->first = NULL; o->lastptr = &o->first; } /* * put a sysex message at the end of the list */ void sysexlist_put(struct sysexlist *o, struct sysex *e) { e->next = NULL; *o->lastptr = e; o->lastptr = &e->next; } /* * detach the first sysex message from the list */ struct sysex * sysexlist_get(struct sysexlist *o) { struct sysex *e; if (o->first) { e = o->first; o->first = e->next; if (e->next == NULL) { o->lastptr = &o->first; } return e; } return 0; } /* * put a sysex message at the given position */ void sysexlist_add(struct sysexlist *l, unsigned int pos, struct sysex *e) { struct sysex **pe; pe = &l->first; while (pos > 0) { pe = &(*pe)->next; pos--; } e->next = *pe; *pe = e; if (e->next == NULL) l->lastptr = &e->next; } /* * detach the sysex message at the given position from the list */ struct sysex * sysexlist_rm(struct sysexlist *l, unsigned int pos) { struct sysex *e, **pe; pe = &l->first; while (pos > 0) { pe = &(*pe)->next; pos--; } e = *pe; *pe = e->next; if (*pe == NULL) l->lastptr = pe; return e; } /* * dump a sysex list on stderr */ void sysexlist_log(struct sysexlist *o) { struct sysex *e; unsigned i; log_puts("sysex_log:\n"); for (e = o->first; e != NULL; e = e->next) { log_puts("unit = "); log_putx(e->unit); log_puts(", data = { "); if (e->first) { for (i = 0; i < e->first->used; i++) { if (i > 16) { log_puts("... "); break; } log_putx(e->first->data[i]); log_puts(" "); } } log_puts("}\n"); } } midish-1.4.0/sysex.h010066400017510001751000000043321501104363400130250ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_SYSEX_H #define MIDISH_SYSEX_H struct chunk { struct chunk *next; unsigned used; /* bytes used in 'data' */ #define CHUNK_SIZE 0x100 unsigned char data[CHUNK_SIZE]; }; struct sysex { struct sysex *next; unsigned unit; /* device number */ struct chunk *first, *last; }; struct sysexlist { struct sysex *first, **lastptr; }; struct sysex_data { unsigned char *data; unsigned int unit; unsigned int size; unsigned int pos; }; void chunk_pool_init(unsigned); void chunk_pool_done(void); struct chunk *chunk_new(void); void chunk_del(struct chunk *); void sysex_pool_init(unsigned); void sysex_pool_done(void); struct sysex *sysex_new(unsigned); void sysex_del(struct sysex *); void sysex_add(struct sysex *, unsigned); void sysex_log(struct sysex *); unsigned sysex_check(struct sysex *); void sysexlist_init(struct sysexlist *); void sysexlist_done(struct sysexlist *); void sysexlist_clear(struct sysexlist *); void sysexlist_put(struct sysexlist *, struct sysex *); struct sysex *sysexlist_get(struct sysexlist *); void sysexlist_log(struct sysexlist *); void sysexlist_add(struct sysexlist *, unsigned int, struct sysex *); struct sysex *sysexlist_rm(struct sysexlist *, unsigned int); struct sysex *sysex_undorestore(struct sysex_data *); unsigned int sysex_undosave(struct sysex *, struct sysex_data *); #endif /* MIDISH_SYSEX_H */ midish-1.4.0/textio.c010066400017510001751000000075711501104363400131710ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * textin implemets inputs from text files (or stdin) * (open/close, line numbering, etc...). Used by lex * * textout implements outputs into text files (or stdout) * (open/close, indentation...) * */ #include #include #include #include "utils.h" #include "textio.h" #include "cons.h" struct textin { FILE *file; unsigned line, col; }; struct textout { FILE *file; unsigned indent, isconsole, col; }; /* -------------------------------------------------------- input --- */ struct textin * textin_new(char *filename) { struct textin *o; o = xmalloc(sizeof(struct textin), "textin"); if (filename == NULL) { o->file = stdin; } else { o->file = fopen(filename, "r"); if (o->file == NULL) { cons_errs(filename, "failed to open input file"); xfree(o); return 0; } } o->line = o->col = 0; return o; } void textin_delete(struct textin *o) { fclose(o->file); xfree(o); } unsigned textin_getchar(struct textin *o, int *c) { *c = fgetc(o->file); if (*c == EOF) { *c = CHAR_EOF; if (ferror(o->file)) { log_perror("fgetc"); } return 1; } if (*c == '\n') { o->col = 0; o->line++; } else if (*c == '\t') { o->col += 8; } else { o->col++; } *c = *c & 0xff; return 1; } void textin_getpos(struct textin *o, unsigned *line, unsigned *col) { *line = o->line; *col = o->col; } /* ------------------------------------------------------- output --- */ struct textout * textout_new(char *filename) { struct textout *o; o = xmalloc(sizeof(struct textout), "textout"); if (filename != NULL) { o->file = fopen(filename, "w"); if (o->file == NULL) { cons_errs(filename, "failed to open output file"); xfree(o); return 0; } o->isconsole = 0; } else { o->file = stdout; o->isconsole = 1; } o->indent = 0; o->col = 0; return o; } void textout_delete(struct textout *o) { if (!o->isconsole) { fclose(o->file); } xfree(o); } void textout_shiftleft(struct textout *o) { o->indent--; } void textout_shiftright(struct textout *o) { o->indent++; } void textout_putstr(struct textout *o, char *str) { static char buf[1] = {'\t'}; char *p; unsigned int i; while (1) { if (str[0] == 0) break; if (o->col == 0) { for (i = 0; i < o->indent; i++) { if (o->isconsole) log_putc(buf, sizeof(buf)); else fwrite(buf, sizeof(buf), 1, o->file); o->col += 8; } } for (p = str; *p; p++) { if (*p == '\n') { p++; o->col = 0; break; } o->col++; } if (o->isconsole) log_putc(str, p - str); else fwrite(str, p - str, 1, o->file); str = p; } } void textout_putlong(struct textout *o, long val) { char buf[sizeof(val) * 3 + 1]; snprintf(buf, sizeof(buf), "%ld", val); textout_putstr(o, buf); } void textout_putbyte(struct textout *o, unsigned val) { char buf[sizeof(val) * 2 + 1]; snprintf(buf, sizeof(buf), "0x%02x", val & 0xff); textout_putstr(o, buf); } /* ------------------------------------------------------------------ */ struct textout *tout; void textio_init(void) { tout = textout_new(NULL); } void textio_done(void) { textout_delete(tout); tout = 0; } midish-1.4.0/textio.h010066400017510001751000000031001501104363400131560ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_TEXTIO_H #define MIDISH_TEXTIO_H #define CHAR_EOF (-1) struct textin; struct textout; struct textin *textin_new(char *); void textin_delete(struct textin *); unsigned textin_getchar(struct textin *, int *); void textin_getpos(struct textin *, unsigned *, unsigned *); struct textout *textout_new(char *); void textout_delete(struct textout *); void textout_shiftleft(struct textout *); void textout_shiftright(struct textout *); void textout_putstr(struct textout *, char *); void textout_putlong(struct textout *, long); void textout_putbyte(struct textout *, unsigned); /* ------------------------------------------------- stdin/stdout --- */ extern struct textout *tout; extern struct textin *tin; void textio_init(void); void textio_done(void); #endif /* MIDISH_TEXTIO_H */ midish-1.4.0/timo.c010066400017510001751000000067651501104363400126310ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * trivial timeouts implementation. * * A timeout is used to schedule the call of a routine (the callback) * there is a global list of timeouts that is processed inside the * event loop ie mux_run(). Timeouts work as follows: * * first the timo structure must be initialized with timo_set() * * then the timeout is scheduled (only once) with timo_add() * * if the timeout expires, the call-back is called; then it can * be scheduled again if needed. It's OK to reschedule it again * from the callback * * the timeout can be aborted with timo_del(), it is OK to try to * abort a timout that has expired * */ #include "utils.h" #include "timo.h" unsigned timo_debug = 0; struct timo *timo_queue; unsigned timo_abstime; /* * initialise a timeout structure, arguments are callback and argument * that will be passed to the callback */ void timo_set(struct timo *o, void (*cb)(void *), void *arg) { o->cb = cb; o->arg = arg; o->set = 0; } /* * schedule the callback in 'delta' 24-th of microseconds. The timeout * must not be already scheduled */ void timo_add(struct timo *o, unsigned delta) { struct timo **i; unsigned val; int diff; #ifdef TIMO_DEBUG if (o->set) { log_puts("timo_add: already set\n"); panic(); } if (delta == 0) { log_puts("timo_add: zero timeout is evil\n"); panic(); } #endif val = timo_abstime + delta; for (i = &timo_queue; *i != NULL; i = &(*i)->next) { diff = (*i)->val - val; if (diff > 0) { break; } } o->set = 1; o->val = val; o->next = *i; *i = o; } /* * abort a scheduled timeout */ void timo_del(struct timo *o) { struct timo **i; for (i = &timo_queue; *i != NULL; i = &(*i)->next) { if (*i == o) { *i = o->next; o->set = 0; return; } } if (timo_debug) log_puts("timo_del: not found\n"); } /* * routine to be called by the timer when 'delta' 24-th of microsecond * elapsed. This routine updates time referece used by timeouts and * calls expired timeouts */ void timo_update(unsigned delta) { struct timo *to; int diff; /* * update time reference */ timo_abstime += delta; /* * remove from the queue and run expired timeouts */ while (timo_queue != NULL) { /* * there is no overflow here because + and - are * modulo 2^32, they are the same for both signed and * unsigned integers */ diff = timo_queue->val - timo_abstime; if (diff > 0) break; to = timo_queue; timo_queue = to->next; to->set = 0; to->cb(to->arg); } } /* * initialize timeout queue */ void timo_init(void) { timo_queue = NULL; timo_abstime = 0; } /* * destroy timeout queue */ void timo_done(void) { if (timo_queue != NULL) { log_puts("timo_done: timo_queue not empty!\n"); panic(); } timo_queue = (struct timo *)0xdeadbeef; } midish-1.4.0/timo.h010066400017510001751000000025071501104363400126240ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_TIMO_H #define MIDISH_TIMO_H struct timo { struct timo *next; unsigned val; /* time to wait before the callback */ unsigned set; /* true if the timeout is set */ void (*cb)(void *arg); /* routine to call on expiration */ void *arg; /* argument to give to 'cb' */ }; void timo_set(struct timo *, void (*)(void *), void *); void timo_add(struct timo *, unsigned); void timo_del(struct timo *); void timo_update(unsigned); void timo_init(void); void timo_done(void); extern unsigned timo_abstime; #endif /* MIDISH_TIMO_H */ midish-1.4.0/track.c010066400017510001751000000134631501104363400127560ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * a track (struct track *o) is a linked list of * events. Each event (struct seqev) is made by * - a midi event (struct ev) * - the number of tics before the event is to be played * * Since a track can contain an amount of blank space * after the last event (if any), there is always an end-of-track event * in the list. * * - each clock tick marks the begining of a delta * - each event (struct ev) is played after delta ticks * */ #include "utils.h" #include "pool.h" #include "track.h" struct pool seqev_pool; void seqev_pool_init(unsigned size) { pool_init(&seqev_pool, "seqev", sizeof(struct seqev), size); } void seqev_pool_done(void) { pool_done(&seqev_pool); } struct seqev * seqev_new(void) { return (struct seqev *)pool_new(&seqev_pool); } void seqev_del(struct seqev *se) { pool_del(&seqev_pool, se); } void seqev_dump(struct seqev *i) { log_putu(i->delta); log_puts("\t"); ev_log(&i->ev); } /* * initialise the track */ void track_init(struct track *o) { o->eot.ev.cmd = EV_NULL; o->eot.delta = 0; o->eot.next = NULL; o->eot.prev = &o->first; o->first = &o->eot; } /* * free a track */ void track_done(struct track *o) { struct seqev *i, *inext; for (i = o->first; i != &o->eot; i = inext) { inext = i->next; seqev_del(i); } #ifdef TRACK_DEBUG o->first = (void *)0xdeadbeef; #endif } /* * dump the track on stderr, for debugging purposes */ void track_dump(struct track *o) { struct seqev *i; unsigned tic = 0, num = 0; for (i = o->first; i != NULL; i = i->next) { tic += i->delta; log_putu(num); log_puts("\t"); log_putu(tic); log_puts("\t+"); seqev_dump(i); log_puts("\n"); num++; } } /* * return true if the track is empty */ unsigned track_isempty(struct track *o) { return o->first->ev.cmd == EV_NULL && o->first->delta == 0; } /* * remove trailing blank space */ void track_chomp(struct track *o) { o->eot.delta = 0; } /* * shift the track origin forward */ void track_shift(struct track *o, unsigned ntics) { o->first->delta += ntics; } /* * swap contents of two tracks */ void track_swap(struct track *t1, struct track *t2) { struct seqev *se, eot; /* swap list of events */ se = t1->first; t1->first = t2->first; t2->first = se; t1->first->prev = &t1->first; t2->first->prev = &t2->first; /* swap eot events (as they are part of the track) */ eot = t1->eot; t1->eot = t2->eot; t2->eot = eot; /* fix references to eot events */ *t1->eot.prev = &t1->eot; *t2->eot.prev = &t2->eot; } /* * return true if an event is available on the track */ unsigned seqev_avail(struct seqev *pos) { return (pos->ev.cmd != EV_NULL); } /* * insert an event (stored in an already allocated seqev structure) * just before the event of the given position (the delta field of the * given event is ignored) */ void seqev_ins(struct seqev *pos, struct seqev *se) { se->delta = pos->delta; pos->delta = 0; /* link to the list */ se->next = pos; se->prev = pos->prev; *(se->prev) = se; pos->prev = &se->next; } /* * remove the event (but not blank space) on the given position */ void seqev_rm(struct seqev *pos) { #ifdef TRACK_DEBUG if (pos->ev.cmd == EV_NULL) { log_puts("seqev_rm: unexpected end of track\n"); panic(); } #endif pos->next->delta += pos->delta; pos->delta = 0; /* since se != &eot, next is never NULL */ *pos->prev = pos->next; pos->next->prev = pos->prev; } /* * return the number of events in the track */ unsigned track_numev(struct track *o) { unsigned n; struct seqev *i; n = 0; for (i = o->first; i != NULL; i = i->next) n++; return n; } /* * return the number of ticks in the track * ie its length (eot included, of course) */ unsigned track_numtic(struct track *o) { unsigned ntics; struct seqev *i; ntics = 0; for(i = o->first; i != NULL; i = i->next) ntics += i->delta; return ntics; } /* * remove all events from the track */ void track_clear(struct track *o) { struct seqev *i, *inext; for (i = o->first; i != &o->eot; i = inext) { inext = i->next; seqev_del(i); } o->eot.delta = 0; o->eot.prev = &o->first; o->first = &o->eot; } /* * set the chan (dev/midichan pair) of * all voice events */ void track_setchan(struct track *src, unsigned dev, unsigned ch) { struct seqev *i; for (i = src->first; i != NULL; i = i->next) { if (EV_ISVOICE(&i->ev)) { i->ev.dev = dev; i->ev.ch = ch; } } } /* * fill a map of used channels/devices */ void track_chanmap(struct track *o, char *map) { struct seqev *se; unsigned dev, ch, i; for (i = 0; i < DEFAULT_MAXNCHANS; i++) { map[i] = 0; } for (se = o->first; se != NULL; se = se->next) { if (EV_ISVOICE(&se->ev)) { dev = se->ev.dev; ch = se->ev.ch; if (dev >= DEFAULT_MAXNDEVS || ch >= 16) { log_puts("track_chanmap: bogus dev/ch pair, stopping\n"); break; } map[dev * 16 + ch] = 1; } } } /* * return the number of events of the given type */ unsigned track_evcnt(struct track *o, unsigned cmd) { struct seqev *se; unsigned cnt = 0; for (se = o->first; se != NULL; se = se->next) { if (se->ev.cmd == cmd) cnt++; } return cnt; } midish-1.4.0/track.h010066400017510001751000000044041501104363400127560ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_TRACK_H #define MIDISH_TRACK_H #include "ev.h" struct seqev { unsigned delta; struct ev ev; struct seqev *next, **prev; }; struct track { struct seqev eot; /* end-of-track event */ struct seqev *first; /* head of the event list */ }; struct track_data { struct seqev_data { unsigned delta; struct ev ev; } *evs; unsigned int pos, nrm, nins; }; void seqev_pool_init(unsigned); void seqev_pool_done(void); struct seqev *seqev_new(void); void seqev_del(struct seqev *); void seqev_dump(struct seqev *); void track_init(struct track *); void track_done(struct track *); void track_dump(struct track *); unsigned track_numev(struct track *); unsigned track_numtic(struct track *); void track_clear(struct track *); unsigned track_isempty(struct track *); void track_chomp(struct track *); void track_shift(struct track *, unsigned); void track_swap(struct track *, struct track *); unsigned seqev_avail(struct seqev *); void seqev_ins(struct seqev *, struct seqev *); void seqev_rm(struct seqev *); void track_setchan(struct track *, unsigned, unsigned); void track_chanmap(struct track *, char *); unsigned track_evcnt(struct track *, unsigned); unsigned track_undosave(struct track *, struct track_data *); unsigned track_undodiff(struct track *, struct track_data *); void track_undorestore(struct track *, struct track_data *); #endif /* MIDISH_TRACK_H */ midish-1.4.0/tty.c010066400017510001751000000543431501104363400124740ustar00alexalex/* * Copyright (c) 2015 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * see http://www.inwap.com/pdp10/ansicode.txt */ /* * TODO * * - we've the following pattern repeated many times, factor it * * min = el_curs; * max = el_used; * ... * if (max < el_used) * max = el_used; * el_refresh(min, max); * * - make inserting chars in completion mode search starting * from the current item * * - ^G during completion must revert to selection */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tty.h" #include "utils.h" #define EL_MODE_EDIT 0 #define EL_MODE_SEARCH 1 #define EL_MODE_COMPL 2 #define EL_LINEMAX 1024 #define EL_PROMPTMAX 64 #define EL_HISTMAX 200 struct textline { struct textline *next, *prev; char text[1]; }; struct textbuf { struct textline *head, *tail; unsigned int count; }; void el_draw(void *); void el_resize(void *, int); void el_onkey(void *, int); struct termios tty_tattr; /* backup of attributes */ int tty_tstate; /* one of TTY_TSTATE_xxx */ char tty_escbuf[4]; /* escape sequence */ int tty_escpar[TTY_ESC_NPAR]; /* escape seq. parameters */ int tty_nescpar; /* number of params found */ int tty_escval; /* temp var to parse params */ int tty_twidth; /* terminal width */ int tty_tcursx; /* terminal cursor x pos */ char tty_obuf[1024]; /* output buffer */ int tty_oused; /* bytes used in tty_obuf */ int tty_initialized; struct tty_ops *tty_ops; void *tty_arg; char el_prompt[EL_PROMPTMAX]; /* current prompt */ char el_buf[EL_LINEMAX]; /* line being editted */ int el_used = 0; /* chars used in the buffer */ int el_curs = 0; /* char pointed by cursor */ int el_offs = 0; /* most left char displayed */ int el_pos = 0; /* on screen text position */ int el_width = 0; /* on screen text width */ int el_start = 0; /* tty_read() position */ int el_mode; /* one of EL_MODE_xxx */ struct textline *el_curline; int el_selstart, el_selend; struct el_ops *el_ops; void *el_arg; struct textbuf el_hist, el_compl; struct tty_ops el_tty_ops = { el_draw, el_resize, el_onkey }; void textbuf_rm(struct textbuf *p, struct textline *l) { if (l->next) l->next->prev = l->prev; if (l->prev) l->prev->next = l->next; if (p->head == l) p->head = l->next; if (p->tail == l) p->tail = l->prev; p->count--; xfree(l); } void textbuf_ins(struct textbuf *p, struct textline *next, char *line, size_t len) { struct textline *l; l = xmalloc(offsetof(struct textline, text) + len + 1, "textline"); memcpy(l->text, line, len); l->text[len] = 0; l->next = next; if (next) { l->prev = next->prev; next->prev = l; } else { l->prev = p->tail; p->tail = l; } if (l->prev) l->prev->next = l; else p->head = l; p->count++; } int textbuf_search(struct textbuf *p, char *pat, size_t patlen, struct textline **rline, unsigned int *rpos) { struct textline *l; unsigned int pos, i; if (patlen == 0) return 0; l = *rline; while (1) { if (l == NULL) l = p->tail; else l = l->prev; if (l == NULL) break; pos = 0; while (1) { if (rpos == NULL || l->text[pos] == 0) break; i = 0; while (1) { if (i == patlen) { if (rpos) *rpos = pos + i; *rline = l; return 1; } if (pat[i] != l->text[pos + i]) break; i++; } pos++; } } return 0; } void textbuf_init(struct textbuf *p) { p->head = p->tail = NULL; p->count = 0; } void textbuf_done(struct textbuf *p) { while (p->head) textbuf_rm(p, p->head); } void textbuf_copy(struct textbuf *p, char *buf, size_t size, struct textline *l) { char *s; if (l == NULL) buf[0] = 0; else { s = l->text; while (1) { *buf = *s; if (*s == 0) break; buf++; s++; } } } int textbuf_findline(struct textbuf *p, char *pat, size_t patlen, struct textline **rline) { struct textline *l; unsigned int i; l = *rline; while (1) { if (l == NULL) return 0; i = 0; while (1) { if (i == patlen) { *rline = l; return 1; } if (pat[i] != l->text[i]) break; i++; } l = l->next; } } void el_enter(void) { char *p = el_buf; /* append new line */ el_buf[el_used++] = '\n'; /* invoke caller handler */ while (el_used-- > 0) el_ops->onchar(el_arg, *p++); } void el_replace(size_t start, size_t end, char *text, size_t textsize) { int i, off; if (el_used - (end - start) + textsize > EL_LINEMAX) { log_puts("text to paste too long (ignored)\n"); return; } /* * move text after selection */ off = end - start - textsize; if (off > 0) { i = end; while (i < el_used) { el_buf[i - off] = el_buf[i]; i++; } } else if (off < 0) { i = el_used; while (i > end) { i--; el_buf[i - off] = el_buf[i]; } } el_used -= off; /* * copy text into hole */ for (i = 0; i < textsize; i++) el_buf[start + i] = text[i]; end = start + textsize; } void el_refresh(int start, int end) { int x, w, todo; if (el_curs < el_offs) { /* * need to scroll left */ el_offs = el_curs; start = el_offs; end = el_offs + el_width; } else if (el_curs > el_offs + el_width - 1) { /* * need to scroll right */ el_offs = el_curs - el_width + 1; start = el_offs; end = el_offs + el_width; } else { /* * no need to scroll, clip to our width */ if (start < el_offs) start = el_offs; if (end > el_offs + el_width) end = el_offs + el_width; } if (end > start) { x = start - el_offs; w = end - start; if (end > el_used) todo = el_used - start; else todo = end - start; tty_tputs(el_pos + x, w, el_buf + start, todo); } tty_tsetcurs(el_pos + el_curs - el_offs); } char * el_getprompt(void) { #if 1 char *prompt; switch (el_mode) { case EL_MODE_EDIT: prompt = el_prompt; break; case EL_MODE_SEARCH: prompt = "(r-search) "; break; case EL_MODE_COMPL: prompt = "(complete) "; break; default: prompt = "unknown: "; } return prompt; #else return el_prompt; #endif } void el_compladd(char *s) { struct textline *l; int cmp; for (l = el_compl.head; l != NULL; l = l->next) { cmp = strcmp(l->text, s); if (cmp == 0) { log_puts(s); log_puts(": duplicate completion item\n"); panic(); } if (cmp > 0) break; } textbuf_ins(&el_compl, l, s, strlen(s)); } void compl_end(void) { while (el_compl.head) textbuf_rm(&el_compl, el_compl.head); el_curline = NULL; } void compl_next(void) { unsigned int max; if (textbuf_findline(&el_compl, el_buf + el_selstart, el_curs - el_selstart, &el_curline)) { max = el_used; el_replace(el_selstart, el_selend, el_curline->text, strlen(el_curline->text)); el_selend = el_selstart + strlen(el_curline->text); if (max < el_used) max = el_used; el_refresh(el_curs, max); } else el_curline = NULL; } void compl_start(void) { unsigned int min, max, off, i; struct textline *n; el_ops->oncompl(el_arg, el_buf, el_curs, el_used, &el_selstart, &el_selend); el_curline = el_compl.head; if (textbuf_findline(&el_compl, el_buf + el_selstart, el_curs - el_selstart, &el_curline)) { min = el_curs; max = el_used; el_replace(el_selstart, el_selend, el_curline->text, strlen(el_curline->text)); el_selend = el_selstart + strlen(el_curline->text); /* * complete ensuring uniqueness */ off = el_selend - el_selstart; n = el_curline; while (1) { next_line: n = n->next; if (n == NULL) break; i = 0; while (1) { if (i == off) goto next_line; if (n->text[i] != el_buf[el_selstart + i]) break; i++; } if (el_selstart + i < el_curs) break; off = i; } el_curs = el_selstart + off; if (max < el_used) max = el_used; el_refresh(min, max); } else el_curline = NULL; } void el_setmode(int mode) { if (el_mode != mode) { if (el_mode == EL_MODE_COMPL) compl_end(); el_mode = mode; el_resize(NULL, tty_twidth); } } void el_draw(void *arg) { tty_tclear(); if (el_pos > 0) tty_tputs(0, el_pos, el_getprompt(), el_pos); el_refresh(el_offs, el_offs + el_width); tty_tflush(); } void el_search(void) { unsigned int cpos; if (textbuf_search(&el_hist, el_buf + el_selstart, el_curs - el_selstart, &el_curline, &cpos)) { textbuf_copy(&el_hist, el_buf, EL_LINEMAX, el_curline); el_used = strlen(el_buf); el_curs = cpos; el_selstart = cpos - (el_selend - el_selstart); el_selend = cpos; } else { el_replace(el_selend, el_used, NULL, 0); el_replace(0, el_selstart, NULL, 0); el_curs = el_used; el_selstart = 0; el_selend = el_used; el_offs = 0; el_curline = NULL; } } void el_onkey(void *arg, int key) { char text[4]; int endpos; size_t max; if (key == 0) { el_ops->onchar(el_arg, -1); return; } if (key == (TTY_KEY_CTRL | 'C')) { tty_int(); return; } if (key < 0x100) { text[0] = key; if (el_mode == EL_MODE_COMPL) { el_selstart = el_curs; el_replace(el_selstart, el_selend, NULL, 0); el_selend = el_selstart; el_setmode(EL_MODE_EDIT); } if (el_mode == EL_MODE_EDIT) { if (el_used == EL_LINEMAX - 1) return; el_replace(el_curs, el_curs, text, 1); el_curs++; el_refresh(el_curs - 1, el_used); } else if (el_mode == EL_MODE_SEARCH) { if (el_used == EL_LINEMAX - 1) return; el_replace(el_curs, el_curs, text, 1); el_curs++; el_selend++; max = el_used; el_curline = NULL; el_search(); if (max < el_used) max = el_used; el_refresh(0, max); } } else if (key == TTY_KEY_DEL || key == (TTY_KEY_CTRL | 'D')) { el_setmode(EL_MODE_EDIT); if (el_curs == el_used) return; max = el_used; el_replace(el_curs, el_curs + 1, NULL, 0); el_refresh(el_curs, max); } else if (key == (TTY_KEY_CTRL | 'W')) { el_setmode(EL_MODE_EDIT); max = el_used; endpos = el_curs; while (el_curs > 0 && el_buf[el_curs - 1] == ' ') el_curs--; while (el_curs > 0 && el_buf[el_curs - 1] != ' ') el_curs--; el_replace(el_curs, endpos, NULL, 0); el_refresh(el_curs, max); } else if (key == TTY_KEY_BS || key == (TTY_KEY_CTRL | 'H')) { if (el_mode == EL_MODE_COMPL) { el_selstart = el_curs; el_replace(el_selstart, el_selend, NULL, 0); el_selend = el_selstart; el_setmode(EL_MODE_EDIT); } if (el_mode == EL_MODE_EDIT) { if (el_curs == 0) return; max = el_used; el_curs--; el_replace(el_curs, el_curs + 1, NULL, 0); el_refresh(el_curs, max); } else if (el_mode == EL_MODE_SEARCH) { max = el_used; if (el_curs == 0) { el_setmode(EL_MODE_EDIT); el_used = el_curs = 0; } else { el_selend--; el_curs--; el_replace(el_curs, el_curs + 1, NULL, 0); el_curline = NULL; el_search(); } if (max < el_used) max = el_used; el_refresh(0, max); } } else if (key == TTY_KEY_LEFT || key == (TTY_KEY_CTRL | 'B')) { el_setmode(EL_MODE_EDIT); if (el_curs == 0) return; el_curs--; el_refresh(el_curs, el_curs); } else if (key == TTY_KEY_RIGHT || key == (TTY_KEY_CTRL | 'F')) { el_setmode(EL_MODE_EDIT); if (el_curs == el_used) return; el_curs++; el_refresh(el_curs, el_curs); } else if (key == TTY_KEY_HOME || key == (TTY_KEY_CTRL | 'A')) { el_setmode(EL_MODE_EDIT); if (el_curs == 0) return; el_curs = 0; el_refresh(el_curs, el_curs); } else if (key == TTY_KEY_END || key == (TTY_KEY_CTRL | 'E')) { el_setmode(EL_MODE_EDIT); if (el_curs == el_used) return; el_curs = el_used; el_refresh(el_curs, el_curs); } else if (key == (TTY_KEY_CTRL | 'K')) { el_setmode(EL_MODE_EDIT); endpos = el_used; el_used = el_curs; el_refresh(el_curs, endpos); } else if (key == (TTY_KEY_CTRL | 'U')) { el_setmode(EL_MODE_EDIT); endpos = el_used; el_used = el_curs = 0; el_refresh(0, endpos); } else if (key == TTY_KEY_UP || key == (TTY_KEY_CTRL | 'P')) { el_setmode(EL_MODE_EDIT); if (el_curline != el_hist.head) { max = el_used; if (el_curline == NULL) el_curline = el_hist.tail; else el_curline = el_curline->prev; textbuf_copy(&el_hist, el_buf, EL_LINEMAX, el_curline); el_used = strlen(el_buf); el_offs = 0; el_curs = el_used; if (max < el_used) max = el_used; el_refresh(0, max); } } else if (key == TTY_KEY_DOWN || key == (TTY_KEY_CTRL | 'N')) { el_setmode(EL_MODE_EDIT); if (el_curline != NULL) { max = el_used; el_curline = el_curline->next; textbuf_copy(&el_hist, el_buf, EL_LINEMAX, el_curline); el_used = strlen(el_buf); el_offs = 0; el_curs = el_used; if (max < el_used) max = el_used; el_refresh(0, max); } } else if (key == TTY_KEY_ENTER) { if (el_mode == EL_MODE_COMPL) el_setmode(EL_MODE_EDIT); if (el_mode == EL_MODE_EDIT) { tty_tendl(); if (el_used > 0) { if (el_hist.count == EL_HISTMAX) textbuf_rm(&el_hist, el_hist.head); textbuf_ins(&el_hist, NULL, el_buf, el_used); el_curline = NULL; } el_enter(); el_curs = el_offs = el_used = 0; el_draw(arg); } else if (el_mode == EL_MODE_SEARCH) { el_setmode(EL_MODE_EDIT); } } else if (key == (TTY_KEY_CTRL | 'L')) { tty_tclrscr(); el_resize(NULL, tty_twidth); } else if (key == (TTY_KEY_CTRL | 'G')) { if (el_mode != EL_MODE_EDIT) el_setmode(EL_MODE_EDIT); } else if (key == (TTY_KEY_CTRL | 'R')) { max = el_used; if (el_mode != EL_MODE_SEARCH) { el_setmode(EL_MODE_SEARCH); el_curs = el_used = el_selstart = el_selend = 0; el_curline = NULL; } el_search(); if (max < el_used) max = el_used; el_refresh(0, max); } else if (key == TTY_KEY_TAB || key == (TTY_KEY_CTRL | 'I')) { if (el_mode == EL_MODE_EDIT) { compl_start(); if (el_curline && el_selstart < el_selend) el_setmode(EL_MODE_COMPL); else compl_end(); } else if (el_mode == EL_MODE_COMPL) { el_curline = el_curline->next; compl_next(); if (el_curline == NULL && el_compl.head) { el_curline = el_compl.head; compl_next(); } } } } void el_resize(void *arg, int w) { int end; el_pos = strlen(el_getprompt()); if (el_pos >= w) el_pos = 0; el_width = w - el_pos; if (w == 0) return; end = el_offs + el_width - 1; if (el_curs > end) el_curs = end; el_draw(arg); } void el_setprompt(char *str) { size_t len, dif; len = strlen(str); if (len > EL_PROMPTMAX - 1) { dif = len - (EL_PROMPTMAX - 1); len -= dif; str += dif; } memcpy(el_prompt, str, len); el_prompt[len] = 0; el_resize(NULL, tty_twidth); } void el_init(struct el_ops *ops, void *arg) { el_ops = ops; el_arg = arg; el_width = el_curs = el_used = el_offs = el_pos = 0; el_prompt[0] = '\0'; #ifdef DEBUG memset(el_buf, '.', sizeof(el_buf)); #endif textbuf_init(&el_hist); textbuf_init(&el_compl); el_curline = NULL; el_mode = EL_MODE_EDIT; tty_ops = &el_tty_ops; tty_arg = NULL; } void el_done(void) { textbuf_done(&el_compl); textbuf_done(&el_hist); } int tty_init(void) { if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) return 0; if (tcgetattr(STDIN_FILENO, &tty_tattr) < 0) { log_perror("can't get tty attributes: tcgetattr"); return 0; } tty_ops = NULL; tty_arg = NULL; tty_initialized = 1; return 1; } void tty_done(void) { if (tty_initialized) { tty_tclear(); tty_tflush(); if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty_tattr) < 0) log_perror("can't flush tty: tcsetattr"); tty_initialized = 0; } } /* * convert escape sequence to TTY_KEY_xxx */ void tty_onesc(void) { int c, key; switch (tty_escbuf[1]) { case '[': switch (tty_escbuf[2]) { case 'A': key = TTY_KEY_UP; break; case 'B': key = TTY_KEY_DOWN; break; case 'C': key = TTY_KEY_RIGHT; break; case 'D': key = TTY_KEY_LEFT; break; case 'F': key = TTY_KEY_END; break; case 'H': key = TTY_KEY_HOME; break; case '~': if (tty_nescpar == 0) return; switch (tty_escpar[0]) { case 1: key = TTY_KEY_HOME; break; case 2: key = TTY_KEY_INSERT; break; case 3: key = TTY_KEY_DEL; break; case 4: key = TTY_KEY_END; break; case 5: key = TTY_KEY_PGUP; break; case 6: key = TTY_KEY_PGDOWN; break; default: return; } break; default: return; } break; case '0': switch (tty_escbuf[2]) { case 'A': key = TTY_KEY_UP; break; case 'B': key = TTY_KEY_DOWN; break; case 'C': key = TTY_KEY_RIGHT; break; case 'D': key = TTY_KEY_LEFT; break; case 'M': key = TTY_KEY_ENTER; break; case 'j': key = '*'; break; case 'k': key = '+'; break; case 'l': key = ','; break; case 'm': key = '-'; break; case 'n': key = '.'; break; case 'o': key = '/'; break; default: c = tty_escbuf[2]; if (c >= 'p' && c <= 'p' + 10) key = '0' + c - 'p'; else return; } default: return; } tty_ops->onkey(tty_arg, key); } void tty_oninput(unsigned int c) { int key; if (c >= 0x80) return; for (;;) { switch (tty_tstate) { case TTY_TSTATE_ANY: if (c <= 0x1f) { switch (c) { case 0x09: key = TTY_KEY_TAB; break; case 0x0a: key = TTY_KEY_ENTER; break; case 0x08: key = TTY_KEY_BS; break; case 0x1b: tty_tstate = TTY_TSTATE_ESC; return; default: key = ('@' + c) | TTY_KEY_CTRL; } } else { switch (c) { case 0x7f: key = TTY_KEY_BS; break; default: key = c; } } tty_ops->onkey(tty_arg, key); return; case TTY_TSTATE_ESC: if (c <= 0x1f) { tty_tstate = TTY_TSTATE_ANY; continue; } else if (c >= 0x20 && c <= 0x2f) { /* 2-char esc sequence, we ignore it */ tty_tstate = TTY_TSTATE_INT; } else if (c >= 0x30 && c <= 0x3f) { /* 2-char esc sequence, we ignore it */ tty_tstate = TTY_TSTATE_ANY; return; } else { switch (c) { case '[': tty_tstate = TTY_TSTATE_CSI; tty_escbuf[0] = 0x1b; tty_escbuf[1] = c; tty_nescpar = 0; break; default: if (c == '@' || (c >= 'A' && c <= 'Z')) { alt_key: key = c | TTY_KEY_ALT; tty_ops->onkey(tty_arg, key); tty_tstate = TTY_TSTATE_ANY; return; } if (c >= 'a' && c <= 'z') { c -= 'a' - 'A'; goto alt_key; } tty_tstate = TTY_TSTATE_ERROR; } } return; case TTY_TSTATE_INT: if (c >= 0x30 && c <= 0x7e) tty_tstate = TTY_TSTATE_ANY; return; case TTY_TSTATE_CSI: if (c >= '0' && c <= '9') { tty_tstate = TTY_TSTATE_CSI_PAR; tty_escval = 0; continue; } else if (c >= 0x40 && c <= 0x7e) { tty_escbuf[2] = c; tty_tstate = TTY_TSTATE_ANY; tty_onesc(); } else { tty_tstate = TTY_TSTATE_ERROR; continue; } return; case TTY_TSTATE_CSI_PAR: if (c >= '0' && c <= '9') { tty_escval = tty_escval * 10 + c - '0'; if (tty_escval > 255) { tty_tstate = TTY_TSTATE_ERROR; continue; } return; } else { if (tty_nescpar == TTY_ESC_NPAR) { tty_tstate = TTY_TSTATE_ERROR; continue; } tty_escpar[tty_nescpar++] = tty_escval; tty_tstate = TTY_TSTATE_CSI; if (c == ';') return; continue; } return; case TTY_TSTATE_ERROR: if (c >= 0x40 && c <= 0x7e) tty_tstate = TTY_TSTATE_ANY; return; } } } void tty_winch(void) { struct winsize ws; if (!tty_initialized) return; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) { tty_twidth = ws.ws_col; if (tty_twidth <= 0) tty_twidth = 80; } else { log_perror("TIOCGWINSZ"); tty_twidth = 80; } tty_ops->resize(tty_arg, tty_twidth); } void tty_int(void) { tty_tclear(); tty_ops->draw(tty_arg); } void tty_reset(void) { struct termios tio; if (!tty_initialized) return; tio = tty_tattr; tio.c_lflag &= ~(ICANON | IXON | IXOFF /* | ISIG*/); tio.c_lflag &= ~ECHO; tio.c_cc[VMIN] = 1; tio.c_cc[VTIME] = 0; if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio) < 0) log_perror("tcsetattr"); tty_winch(); } void tty_tflush(void) { ssize_t n; if (tty_oused > 0) { n = write(STDOUT_FILENO, tty_obuf, tty_oused); if (n < 0) { log_perror("stdout"); return; } tty_oused = 0; } } void tty_toutput(char *buf, int len) { int n; while (len > 0) { n = sizeof(tty_obuf) - tty_oused; if (n == 0) { tty_tflush(); continue; } if (n > len) n = len; memcpy(tty_obuf + tty_oused, buf, n); tty_oused += n; buf += n; len -= n; } } void tty_tesc1(int cmd, int par0) { char buf[32]; int len; len = snprintf(buf, sizeof(buf), "\x1b[%u%c", par0, cmd); tty_toutput(buf, len); } void tty_tsetcurs(int x) { if (x < tty_tcursx) { tty_toutput("\r", 1); tty_tcursx = 0; } if (x > tty_tcursx) tty_tesc1('C', x - tty_tcursx); tty_tcursx = x; } void tty_tputs(int x, int w, char *buf, int len) { tty_tsetcurs(x); if (len > 0) { if (len > w) len = w; tty_toutput(buf, len); tty_tcursx += len; } if (len < w) tty_tesc1('X', w - len); } void tty_tendl(void) { tty_toutput("\n\r", 2); tty_tcursx = 0; } void tty_tclear(void) { tty_toutput("\r\x1b[K", 4); tty_tcursx = 0; } void tty_tclrscr(void) { tty_toutput("\x1b[H\x1b[J", 6); tty_tcursx = 0; } int tty_pollfd(struct pollfd *pfds) { pfds->fd = STDIN_FILENO; pfds->events = POLLIN; pfds->revents = 0; return 1; } int tty_revents(struct pollfd *pfds) { char buf[1024]; ssize_t i, n; if (pfds[0].revents & POLLIN) { n = read(STDIN_FILENO, buf, sizeof(buf)); if (n < 0) { log_perror("stdin"); tty_ops->onkey(tty_arg, 0); return POLLHUP; } if (n == 0) { tty_ops->onkey(tty_arg, 0); return POLLHUP; } for (i = 0; i < n; i++) tty_oninput(buf[i]); tty_tflush(); } return 0; } void tty_write(void *buf, size_t len) { if (!tty_initialized) { write(STDERR_FILENO, buf, len); return; } tty_tclear(); tty_tflush(); write(STDOUT_FILENO, buf, len); tty_ops->draw(tty_arg); } midish-1.4.0/tty.h010066400017510001751000000062411501104363400124730ustar00alexalex/* * Copyright (c) 2015 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef TTY_H #define TTY_H #include #define TTY_KEY_SHIFT (1 << 31) #define TTY_KEY_CTRL (1 << 30) #define TTY_KEY_ALT (1 << 29) enum TTY_KEY { /* * we put special "control" keys in the "private" unicode plane, * allowing to encode any key as a simple int * * certain function keys (tab, enter, delete... ) have unicode * character, but since we don't use them as "characters" we * put them in the private plane as well, shifted by 0x100000 */ TTY_KEY_BS = 0x100008, TTY_KEY_TAB = 0x100009, TTY_KEY_ENTER = 0x10000d, TTY_KEY_ESC = 0x10001b, TTY_KEY_DEL = 0x10007f, TTY_KEY_UP = 0x100100, TTY_KEY_DOWN, TTY_KEY_LEFT, TTY_KEY_RIGHT, TTY_KEY_HOME, TTY_KEY_END, TTY_KEY_PGUP, TTY_KEY_PGDOWN, TTY_KEY_INSERT }; #define TTY_TSTATE_ANY 0 /* expect any char */ #define TTY_TSTATE_ESC 1 /* got ESC */ #define TTY_TSTATE_CSI 2 /* got CSI, parsing it */ #define TTY_TSTATE_CSI_PAR 3 /* parsing CSI param (number) */ #define TTY_TSTATE_ERROR 4 /* found error, skipping */ #define TTY_TSTATE_INT 5 /* got ESC */ #define TTY_ESC_NPAR 8 /* max params in CSI */ struct pollfd; struct tty_ops { void (*draw)(void *); void (*resize)(void *, int); void (*onkey)(void *, int); }; int tty_init(void); void tty_done(void); int tty_pollfd(struct pollfd *); int tty_revents(struct pollfd *); void tty_winch(void); void tty_int(void); void tty_reset(void); void tty_write(void *, size_t); void tty_tflush(void); void tty_toutput(char *, int); void tty_tsetcurs(int); void tty_tputs(int, int, char *, int); void tty_tclear(void); void tty_tclrscr(void); void tty_tendl(void); extern struct tty_ops *tty_ops; extern void *tty_arg; struct el_ops { /* * Called for every character when a new line is entered, * including for \n and EOF. */ void (*onchar)(void *, int); /* * Called when a completion list must be built (when the TAB * key is hit the first time). The function gets a pointer to * the text, its size and the current cursor position. It * must parse the text, build the appropriate completion list * (by calling el_compladd() for each item) and return in the * extents of the sub-string being completed (for instance the * last component of the path). */ void (*oncompl)(void *, char *, int, int, int *, int *); }; void el_init(struct el_ops *, void *); void el_done(void); void el_setprompt(char *); void el_compladd(char *); extern struct tty_ops el_tty_ops; #endif midish-1.4.0/undo.c010066400017510001751000000350661501104363400126220ustar00alexalex/* * Copyright (c) 2018 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "utils.h" #include "mididev.h" #include "mux.h" #include "track.h" #include "frame.h" #include "filt.h" #include "song.h" #include "cons.h" #include "metro.h" #include "defs.h" #include "mixout.h" #include "norm.h" #include "undo.h" struct undo * undo_new(struct song *s, int type, char *func, char *name) { struct undo *u; u = xmalloc(sizeof(struct undo), "undo"); u->type = type; u->func = func; u->name = name; u->size = 0; return u; } void undo_pop(struct song *s) { struct songtrk *t; struct undo_fdel_trk *p; struct sysex *x; struct undo *u; int done = 0; while (!done) { u = s->undo; if (u == NULL) return; s->undo = u->next; if (u->func) { log_puts("undo:"); log_puts(" "); log_puts(u->func); if (u->name) { log_puts(" "); log_puts(u->name); } log_puts("\n"); done = 1; } switch (u->type) { case UNDO_EMPTY: break; case UNDO_STR: str_delete(*u->u.ren.ptr); *u->u.ren.ptr = u->u.ren.val; break; case UNDO_UINT: *u->u.uint.ptr = u->u.uint.val; break; case UNDO_TRACK: track_undorestore(u->u.track.track, &u->u.track.data); break; case UNDO_TDEL: name_add(&s->trklist, &u->u.tdel.trk->name); if (s->curtrk == NULL) s->curtrk = u->u.tdel.trk; break; case UNDO_TNEW: song_trkdel(s, u->u.tdel.trk); break; case UNDO_FILT: filt_reset(u->u.filt.filt); *u->u.filt.filt = u->u.filt.data; break; case UNDO_FDEL: name_add(&s->filtlist, &u->u.fdel.filt->name); if (s->curfilt == NULL) s->curfilt = u->u.fdel.filt; while ((p = u->u.fdel.trks) != NULL) { p->trk->curfilt = u->u.fdel.filt; u->u.fdel.trks = p->next; xfree(p); } break; case UNDO_FNEW: song_filtdel(s, u->u.fdel.filt); break; case UNDO_CDEL: name_add(&s->chanlist, &u->u.cdel.chan->name); if (u->u.cdel.chan->isinput) { if (s->curin == NULL) s->curin = u->u.cdel.chan; } else { if (s->curout == NULL) s->curout = u->u.cdel.chan; } break; case UNDO_CNEW: song_chandel(s, u->u.cdel.chan); break; case UNDO_XADD: x = sysexlist_rm(u->u.sysex.list, u->u.sysex.data.pos); sysex_del(x); break; case UNDO_XRM: x = sysex_undorestore(&u->u.sysex.data); sysexlist_add(u->u.sysex.list, u->u.sysex.data.pos, x); break; case UNDO_XDEL: name_add(&s->sxlist, &u->u.xdel.sx->name); if (s->cursx == NULL) s->cursx = u->u.xdel.sx; break; case UNDO_XNEW: song_sxdel(s, u->u.xdel.sx); break; case UNDO_SCALE: track_scale(&s->meta, u->u.scale.newunit, u->u.scale.oldunit); SONG_FOREACH_TRK(s, t) { track_scale(&t->track, u->u.scale.newunit, u->u.scale.oldunit); } break; default: log_puts("undo_pop: bad type\n"); panic(); } s->undo_size -= u->size; #ifdef SONG_DEBUG log_puts("undo: size -> "); log_puti(s->undo_size); log_puts("\n"); #endif xfree(u); } } void undo_clear(struct song *s, struct undo **pos) { struct undo *u; struct undo_fdel_trk *p; while ((u = *pos) != NULL) { *pos = u->next; switch (u->type) { case UNDO_EMPTY: break; case UNDO_STR: str_delete(u->u.ren.val); break; case UNDO_UINT: break; case UNDO_TRACK: xfree(u->u.track.data.evs); break; case UNDO_TDEL: track_done(&u->u.tdel.trk->track); name_done(&u->u.tdel.trk->name); xfree(u->u.tdel.trk); break; case UNDO_TNEW: break; case UNDO_FILT: filt_reset(&u->u.filt.data); break; case UNDO_FDEL: filt_reset(&u->u.fdel.filt->filt); name_done(&u->u.fdel.filt->name); while ((p = u->u.fdel.trks) != NULL) { u->u.fdel.trks = p->next; xfree(p); } xfree(u->u.fdel.filt); break; case UNDO_FNEW: break; case UNDO_CDEL: track_done(&u->u.cdel.chan->conf); name_done(&u->u.cdel.chan->name); xfree(u->u.cdel.chan); break; case UNDO_CNEW: break; case UNDO_XADD: break; case UNDO_XRM: xfree(u->u.sysex.data.data); break; case UNDO_XDEL: name_done(&u->u.xdel.sx->name); xfree(u->u.xdel.sx); break; case UNDO_XNEW: break; case UNDO_SCALE: break; default: log_puts("undo_clear: bad type\n"); panic(); } s->undo_size -= u->size; #ifdef SONG_DEBUG log_puts("undo: freed "); log_puts(u->func); log_puts("\n"); #endif xfree(u); } } void undo_push(struct song *s, struct undo *u) { struct undo **pu; size_t size; u->next = s->undo; s->undo = u; s->undo_size += u->size; #ifdef SONG_DEBUG log_puts("undo: "); log_puts(u->func); log_puts(", size -> "); log_puti(s->undo_size); log_puts("\n"); #endif /* * free old entries exceeding memory usage limit */ size = 0; pu = &s->undo; while (1) { u = *pu; if (u == NULL) return; size += u->size; if (size > UNDO_MAXSIZE) break; pu = &u->next; } undo_clear(s, pu); } void undo_start(struct song *s, char *func, char *tag) { struct undo *u; u = undo_new(s, UNDO_EMPTY, func, tag); undo_push(s, u); } void undo_setstr(struct song *s, char *func, char **ptr, char *val) { struct undo *u; u = undo_new(s, UNDO_STR, func, *ptr); u->u.ren.ptr = ptr; u->u.ren.val = *ptr; *ptr = str_new(val); undo_push(s, u); } void undo_setuint(struct song *s, char *func, char *tag, unsigned int *ptr, unsigned int val) { struct undo *u; u = undo_new(s, UNDO_UINT, func, tag); u->u.uint.ptr = ptr; u->u.uint.val = *ptr; *ptr = val; undo_push(s, u); } void undo_scale(struct song *s, char *func, char *tag, unsigned int oldunit, unsigned int newunit) { struct undo *u; struct songtrk *t; u = undo_new(s, UNDO_SCALE, func, tag); u->u.scale.oldunit = oldunit; u->u.scale.newunit = newunit; track_scale(&s->meta, oldunit, newunit); SONG_FOREACH_TRK(s, t) { track_scale(&t->track, oldunit, newunit); } undo_push(s, u); } unsigned track_undosave(struct track *t, struct track_data *u) { struct seqev *i; struct seqev_data *e; unsigned size; u->nins = track_numev(t); size = sizeof(struct seqev_data) * u->nins; u->evs = xmalloc(size, "track_data"); e = u->evs; for (i = t->first; i != NULL; i = i->next) { e->delta = i->delta; e->ev = i->ev; e++; } u->pos = 0; u->nrm = 0; return size; } void track_diff(struct track_data *u1, struct track_data *u2, unsigned *pos, unsigned *nrm, unsigned *nins) { unsigned start, end1, end2; start = 0; while (1) { if (start == u1->nins || start == u2->nins) break; if (u1->evs[start].delta != u2->evs[start].delta) break; if (!ev_eq(&u1->evs[start].ev, &u2->evs[start].ev)) break; start++; } end1 = u1->nins; end2 = u2->nins; while (1) { if (end1 == start || end2 == start) break; if (u1->evs[end1 - 1].delta != u2->evs[end2 - 1].delta) break; if (!ev_eq(&u1->evs[end1 - 1].ev, &u2->evs[end2 - 1].ev)) break; end1--; end2--; } *pos = start; *nrm = end1 - start; *nins = end2 - start; } unsigned track_undodiff(struct track *t, struct track_data *orig) { struct track_data mod; unsigned int i, pos, nrm, nins; struct seqev_data *evs; track_undosave(t, &mod); track_diff(orig, &mod, &pos, &nrm, &nins); evs = xmalloc(sizeof(struct seqev_data) * nrm, "track_diff"); for (i = 0; i < nrm; i++) evs[i] = orig->evs[pos + i]; xfree(mod.evs); xfree(orig->evs); orig->evs = evs; orig->nins = nins; orig->nrm = nrm; orig->pos = pos; return sizeof(struct seqev_data) * nrm; } void track_undorestore(struct track *t, struct track_data *u) { unsigned n; struct seqev *pos, *se; struct seqev_data *e; /* go to pos */ pos = t->first; for (n = u->pos; n > 0; n--) pos = pos->next; /* remove events that were inserted */ for (n = u->nins; n > 0; n--) { if (pos->ev.cmd == EV_NULL) { if (n != 1) { log_puts("can't remove eot event\n"); panic(); } pos->delta = 0; break; } se = pos; pos = se->next; /* remove seqev */ *se->prev = pos; pos->prev = se->prev; seqev_del(se); } /* insert events that were removed */ e = u->evs; for (n = u->nrm; n > 0; n--) { if (e->ev.cmd == EV_NULL) { if (n != 1) { log_puts("can't insert eot event\n"); panic(); } t->eot.delta = e->delta; break; } se = seqev_new(); se->ev = e->ev; se->delta = e->delta; e++; /* insert seqev */ se->next = pos; se->prev = pos->prev; *(se->prev) = se; pos->prev = &se->next; } xfree(u->evs); } void undo_track_save(struct song *s, struct track *t, char *func, char *name) { struct undo *u; u = undo_new(s, UNDO_TRACK, func, name); u->u.track.track = t; u->size = track_undosave(t, &u->u.track.data); undo_push(s, u); } void undo_track_diff(struct song *s) { struct undo *u = s->undo; unsigned size; if (u == NULL || u->type != UNDO_TRACK) { log_puts("undo_track_diff: no data to diff\n"); return; } size = track_undodiff(u->u.track.track, &u->u.track.data); s->undo_size += size - u->size; u->size = size; } void undo_tdel_do(struct song *s, struct songtrk *t, char *func) { struct undo *u; if (s->curtrk == t) s->curtrk = NULL; undo_track_save(s, &t->track, func, t->name.str); track_clear(&t->track); undo_track_diff(s); u = undo_new(s, UNDO_TDEL, NULL, NULL); u->u.tdel.trk = t; name_remove(&s->trklist, &t->name); undo_push(s, u); } struct songtrk * undo_tnew_do(struct song *s, char *func, char *name) { struct undo *u; struct songtrk *t; t = song_trknew(s, name); u = undo_new(s, UNDO_TNEW, func, t->name.str); u->u.tdel.trk = t; undo_push(s, u); return t; } int filtnode_size(struct filtnode **sloc) { struct filtnode *s; int size = 0; for (s = *sloc; s != NULL; s = s->next) size += sizeof(struct filtnode) + filtnode_size(&s->dstlist); return size; } void filtnode_dup(struct filtnode **dloc, struct filtnode **sloc) { struct filtnode *s, *d; s = *sloc; while (s != NULL) { d = filtnode_new(&s->es, dloc); filtnode_dup(&d->dstlist, &s->dstlist); dloc = &d->next; s = s->next; } } int filt_size(struct filt *f) { return filtnode_size(&f->map) + filtnode_size(&f->vcurve) + filtnode_size(&f->transp); } void filt_undosave(struct filt *f, struct filt *data) { filt_init(data); filtnode_dup(&data->map, &f->map); filtnode_dup(&data->vcurve, &f->vcurve); filtnode_dup(&data->transp, &f->transp); } void undo_filt_save(struct song *s, struct filt *f, char *func, char *name) { struct undo *u; u = undo_new(s, UNDO_FILT, func, name); u->u.filt.filt = f; filt_undosave(f, &u->u.filt.data); u->size += filt_size(&u->u.filt.data); undo_push(s, u); } void undo_fdel_do(struct song *s, struct songfilt *f, char *func) { struct undo *u; struct songtrk *t; struct undo_fdel_trk *p; u = undo_new(s, UNDO_FDEL, func, f->name.str); u->u.fdel.filt = f; u->u.fdel.trks = NULL; SONG_FOREACH_TRK(s, t) { if (t->curfilt != f) continue; p = xmalloc(sizeof(struct undo_fdel_trk), "fdel_trk"); u->size += sizeof(struct undo_fdel_trk); p->trk = t; p->next = u->u.fdel.trks; u->u.fdel.trks = p; t->curfilt = NULL; } if (s->curfilt == f) song_setcurfilt(s, NULL); name_remove(&s->filtlist, &f->name); undo_push(s, u); } struct songfilt * undo_fnew_do(struct song *s, char *func, char *name) { struct undo *u; struct songfilt *t; t = song_filtnew(s, name); u = undo_new(s, UNDO_FNEW, func, t->name.str); u->u.fdel.filt = t; u->u.fdel.trks = NULL; undo_push(s, u); return t; } struct songchan * undo_cnew_do(struct song *s, unsigned int dev, unsigned int ch, int input, char *func, char *name) { struct undo *u; struct songchan *c; c = song_channew(s, name, dev, ch, input); u = undo_new(s, UNDO_CNEW, func, c->name.str); u->u.cdel.chan = c; undo_push(s, u); return c; } void undo_cdel_do(struct song *s, struct songchan *c, char *func) { struct undo *u; if (c->isinput) { if (s->curin == c) s->curin = NULL; } else { if (s->curout == c) s->curout = NULL; } undo_track_save(s, &c->conf, func, c->name.str); track_clear(&c->conf); undo_track_diff(s); u = undo_new(s, UNDO_CDEL, NULL, NULL); u->u.cdel.chan = c; name_remove(&s->chanlist, &c->name); undo_push(s, u); if (c->filt) undo_fdel_do(s, c->filt, NULL); } unsigned int sysex_undosave(struct sysex *x, struct sysex_data *data) { struct chunk *ck; unsigned char *p; unsigned int i; data->unit = x->unit; data->size = 0; for (ck = x->first; ck != NULL; ck = ck->next) data->size += ck->used; data->data = xmalloc(data->size, "undo_sysex"); p = data->data; for (ck = x->first; ck != NULL; ck = ck->next) { for (i = 0; i < ck->used; i++) *p++ = ck->data[i]; } return data->size; } struct sysex * sysex_undorestore(struct sysex_data *data) { struct sysex *x; unsigned int i; x = sysex_new(data->unit); for (i = 0; i < data->size; i++) sysex_add(x, data->data[i]); xfree(data->data); return x; } void undo_xadd_do(struct song *s, char *func, struct songsx *sx, struct sysex *x) { struct undo *u; struct sysex *xi; unsigned int pos; pos = 0; for (xi = sx->sx.first; xi != NULL; xi = xi->next) pos++; u = undo_new(s, UNDO_XADD, func, sx->name.str); u->u.sysex.list = &sx->sx; u->u.sysex.data.unit = 0; u->u.sysex.data.data = NULL; u->u.sysex.data.pos = pos; u->size = 0; undo_push(s, u); sysexlist_put(&sx->sx, x); } void undo_xrm_do(struct song *s, char *func, struct songsx *sx, unsigned int pos) { struct undo *u; struct sysex *x; x = sysexlist_rm(&sx->sx, pos); u = undo_new(s, UNDO_XRM, func, sx->name.str); u->u.sysex.list = &sx->sx; u->u.sysex.data.pos = pos; u->size = sysex_undosave(x, &u->u.sysex.data); undo_push(s, u); sysex_del(x); } void undo_xdel_do(struct song *s, char *func, struct songsx *sx) { struct undo *u; if (s->cursx == sx) s->cursx = NULL; u = undo_new(s, UNDO_XDEL, func, sx->name.str); u->u.xdel.sx = sx; undo_push(s, u); while (sx->sx.first) undo_xrm_do(s, NULL, sx, 0); name_remove(&s->sxlist, &sx->name); } struct songsx * undo_xnew_do(struct song *s, char *func, char *name) { struct undo *u; struct songsx *sx; sx = song_sxnew(s, name); u = undo_new(s, UNDO_XNEW, func, sx->name.str); u->u.xdel.sx = sx; undo_push(s, u); return sx; } midish-1.4.0/undo.h010066400017510001751000000063541501104363400126250ustar00alexalex/* * Copyright (c) 2018 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_UNDO_H #define MIDISH_UNDO_H #include "track.h" #include "sysex.h" struct songtrk; struct songchan; struct songfilt; struct songsx; enum { UNDO_EMPTY, UNDO_STR, UNDO_UINT, UNDO_TRACK, UNDO_TDEL, UNDO_TNEW, UNDO_FILT, UNDO_FDEL, UNDO_FNEW, UNDO_CDEL, UNDO_CNEW, UNDO_XADD, UNDO_XRM, UNDO_XDEL, UNDO_XNEW, UNDO_SCALE }; struct undo { struct undo *next; int type; char *func; char *name; unsigned size; union { struct undo_setstr { char **ptr, *val; } ren; struct undo_setuint { unsigned int *ptr, val; } uint; struct undo_track { struct track *track; struct track_data data; } track; struct undo_tdel { struct songtrk *trk; struct track_data data; } tdel; struct undo_filt { struct filt *filt; struct filt data; } filt; struct undo_fdel { struct songfilt *filt; struct undo_fdel_trk { struct undo_fdel_trk *next; struct songtrk *trk; } *trks; } fdel; struct undo_cdel { struct songchan *chan; struct track_data data; } cdel; struct undo_sysex { struct sysexlist *list; struct sysex_data data; } sysex; struct undo_xdel { struct songsx *sx; } xdel; struct undo_scale { unsigned int oldunit, newunit; } scale; } u; }; void undo_pop(struct song *); void undo_push(struct song *, struct undo *); void undo_clear(struct song *, struct undo **); void undo_start(struct song *, char *, char *); void undo_setstr(struct song *, char *, char **, char *); void undo_setuint(struct song *, char *, char *, unsigned int *, unsigned int); void undo_scale(struct song *, char *, char *, unsigned int, unsigned int); void undo_track_save(struct song *, struct track *, char *, char *); void undo_track_diff(struct song *); void undo_tdel_do(struct song *, struct songtrk *, char *); struct songtrk *undo_tnew_do(struct song *, char *, char *); void undo_filt_save(struct song *, struct filt *, char *, char *); void undo_fdel_do(struct song *, struct songfilt *, char *); struct songfilt *undo_fnew_do(struct song *, char *, char *); struct songchan *undo_cnew_do(struct song *, unsigned int, unsigned int, int, char *, char *); void undo_cdel_do(struct song *, struct songchan *, char *); void undo_xadd_do(struct song *, char *, struct songsx *, struct sysex *); void undo_xrm_do(struct song *, char *, struct songsx *, unsigned int); void undo_xdel_do(struct song *, char *, struct songsx *); struct songsx *undo_xnew_do(struct song *, char *, char *); #endif /* MIDISH_UNDO_H */ midish-1.4.0/user.c010066400017510001751000000736441501104363400126370ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * implements misc built-in functions available through the * interpreter * * each function is described in the manual.html file */ #include "utils.h" #include "defs.h" #include "node.h" #include "exec.h" #include "data.h" #include "cons.h" #include "textio.h" #include "parse.h" #include "mux.h" #include "mididev.h" #include "track.h" #include "song.h" #include "user.h" #include "builtin.h" #include "smf.h" #include "saveload.h" struct song *usong; unsigned user_flag_batch = 0; unsigned user_flag_verb = 0; void exec_cb(struct exec *e, struct node *root) { struct data *data; if (root == NULL) { log_puts("syntax error\n"); return; } if (e->result == RESULT_EXIT) { log_puts("exitting, skiped\n"); return; } e->result = node_exec(root, e, &data); if (data != NULL) { if (data->type != DATA_NIL) { data_print(data); textout_putstr(tout, "\n"); } data_delete(data); } } /* * execute the script in the given file inside the 'exec' environment. * the script has acces to all global variables, but not to the local * variables of the calling proc. Thus it can be safely used from a * procedure */ unsigned exec_runfile(struct exec *exec, char *filename) { struct parse parse; struct textin *in; struct name **locals; int c; in = textin_new(filename); if (in == NULL) return 0; locals = exec->locals; exec->locals = &exec->globals; parse_init(&parse, exec, exec_cb); lex_init(&parse, filename, parse_cb, &parse); for (;;) { if (!textin_getchar(in, &c)) break; lex_handle(&parse, c); if (c < 0) break; } exec->locals = locals; textin_delete(in); lex_done(&parse); parse_done(&parse); return exec->result == RESULT_OK || exec->result == RESULT_EXIT; } /* * find the pointer to the songtrk contained in 'var' ('var' must be a * reference) */ unsigned exec_lookuptrack(struct exec *o, char *var, struct songtrk **res) { char *name; struct songtrk *t; if (!exec_lookupname(o, var, &name)) { return 0; } t = song_trklookup(usong, name); if (t == NULL) { cons_errs(name, "no such track"); return 0; } *res = t; return 1; } /* * find the (dev, ch) couple for the channel referenced by 'var' * 'var' can be * - a reference to a songchan * - a list of two integers (like '{dev chan}') */ unsigned exec_lookupchan_getnum(struct exec *o, char *var, unsigned *dev, unsigned *ch, int input) { struct var *arg; arg = exec_varlookup(o, var); if (!arg) { log_puts("exec_lookupchan_getnum: no such var\n"); panic(); } if (!data_getchan(arg->data, dev, ch, input)) { return 0; } return 1; } /* * find the pointer to an existing songchan that is referenced by * 'var'. ('var' must be a reference) */ unsigned exec_lookupchan_getref(struct exec *o, char *var, struct songchan **res, int input) { struct var *arg; struct songchan *i; arg = exec_varlookup(o, var); if (!arg) { log_puts("exec_lookupchan: no such var\n"); panic(); } if (arg->data->type == DATA_REF) { i = song_chanlookup(usong, arg->data->val.ref, input); } else { cons_err("bad channel name"); return 0; } if (i == NULL) { cons_errs(arg->data->val.ref, "no such chan"); return 0; } *res = i; return 1; } /* * find the pointer to the songfilt contained in 'var' ('var' must be * a reference) */ unsigned exec_lookupfilt(struct exec *o, char *var, struct songfilt **res) { char *name; struct songfilt *f; if (!exec_lookupname(o, var, &name)) { return 0; } f = song_filtlookup(usong, name); if (f == NULL) { cons_errs(name, "no such filt"); return 0; } *res = f; return 1; } /* * find the pointer to the songsx contained in 'var' ('var' must be a * reference) */ unsigned exec_lookupsx(struct exec *o, char *var, struct songsx **res) { char *name; struct songsx *t; if (!exec_lookupname(o, var, &name)) { return 0; } t = song_sxlookup(usong, name); if (t == NULL) { cons_errs(name, "no such sysex"); return 0; } *res = t; return 1; } /* * fill the event with the one referenced by 'var' * 'var' can be: * - { noff chan xxx yyy } * - { non chan xxx yyy } * - { ctl chan xxx yyy } * - { kat chan xxx yyy } * - { cat chan xxx } * - { pc chan xxx } * - { bend chan xxx } * - { xctl chan xxx yyy } * - { xpc chan xxx yyy } * - { rpn chan xxx yyy } * - { nrpn chan xxx yyy } * where 'chan' is in the same as in lookupchan_getnum * and 'xxx' and 'yyy' are integers */ unsigned exec_lookupev(struct exec *o, char *name, struct ev *ev, int input) { struct var *arg; struct data *d; unsigned dev, ch, num; arg = exec_varlookup(o, name); if (!arg) { log_puts("exec_lookupev: no such var\n"); panic(); } d = arg->data; if (d->type != DATA_LIST) { cons_err("event spec must be a list"); return 0; } d = d->val.list; if (!d || d->type != DATA_REF || !ev_str2cmd(ev, d->val.ref) || (!EV_ISVOICE(ev) && !EV_ISSX(ev))) { cons_err("bad status in event spec"); return 0; } d = d->next; if (!d) { cons_err("channel and/or device missing in event spec"); return 0; } if (evinfo[ev->cmd].flags & EV_HAS_CH) { if (!data_getchan(d, &dev, &ch, input)) { return 0; } ev->dev = dev; ev->ch = ch; } else { if (d->type != DATA_LONG) { cons_err("device number expected in event spec"); return 0; } if (d->val.num < 0 || d->val.num >= DEFAULT_MAXNDEVS) { cons_err("device number out of range in event spec"); return 0; } ev->dev = d->val.num; ev->ch = 0; } d = d->next; if (ev->cmd == EV_XCTL || ev->cmd == EV_CTL) { if (!d || !data_getctl(d, &num)) { return 0; } ev->ctl_num = num; } else { if (ev->cmd == EV_XPC && d != NULL && d->type == DATA_NIL) { ev->v0 = EV_UNDEF; } else if (d == NULL || d->type != DATA_LONG || d->val.num < 0 || d->val.num > evinfo[ev->cmd].v0_max) { cons_err("bad v0 in event spec"); return 0; } else ev->v0 = d->val.num; } d = d->next; if (evinfo[ev->cmd].nparams < 2) { if (d != NULL) { cons_err("extra data in event spec"); return 0; } } else { if (d == NULL || d->type != DATA_LONG || d->val.num < 0 || d->val.num > evinfo[ev->cmd].v1_max) { cons_err("bad v1 in event spec"); return 0; } ev->v1 = d->val.num; } /* * convert all CTL -> XCTL and PC -> XPC */ if (ev->cmd == EV_CTL) { ev->cmd = EV_XCTL; ev->ctl_val <<= 7; } if (ev->cmd == EV_PC) { ev->cmd = EV_XPC; ev->pc_prog = ev->v0; ev->pc_bank = EV_UNDEF; } return 1; } /* * fill the evspec with the one referenced by var. * var is of this form * - { [type [chanrange [xxxrange [yyyrange]]]] } * (brackets mean argument is optionnal) */ unsigned exec_lookupevspec(struct exec *o, char *name, struct evspec *e, int input) { struct var *arg; struct data *d; struct songchan *i; unsigned lo, hi, min, max; arg = exec_varlookup(o, name); if (!arg) { log_puts("exec_lookupev: no such var\n"); panic(); } d = arg->data; if (d->type != DATA_LIST) { cons_err("list expected in event range spec"); return 0; } evspec_reset(e); /* * find the event type */ d = d->val.list; if (!d) { goto done; } if (d->type != DATA_REF || !evspec_str2cmd(e, d->val.ref)) { cons_err("bad status in event spec"); return 0; } if (!(evinfo[e->cmd].flags & EV_HAS_DEV)) e->dev_max = e->dev_min; if (!(evinfo[e->cmd].flags & EV_HAS_CH)) e->ch_max = e->ch_min; e->v0_min = evinfo[e->cmd].v0_min; e->v0_max = evinfo[e->cmd].v0_max; e->v1_min = evinfo[e->cmd].v1_min; e->v1_max = evinfo[e->cmd].v1_max; /* * find the {device channel} pair */ d = d->next; if (!d) { goto done; } if ((evinfo[e->cmd].flags & (EV_HAS_DEV | EV_HAS_CH)) == 0) { goto toomany; } if (d->type == DATA_REF) { i = song_chanlookup(usong, d->val.ref, input); if (i == NULL) { cons_err("no such chan name"); return 0; } e->dev_min = e->dev_max = i->dev; e->ch_min = e->ch_max = i->ch; } else if (d->type == DATA_LIST) { if (!d->val.list) { /* empty list = any chan/dev */ } else if (d->val.list && d->val.list->next && !d->val.list->next->next) { if (!data_getrange(d->val.list, 0, EV_MAXDEV, &lo, &hi)) { return 0; } e->dev_min = lo; e->dev_max = hi; if (!data_getrange(d->val.list->next, 0, EV_MAXCH, &lo, &hi)) { return 0; } e->ch_min = lo; e->ch_max = hi; } else { cons_err("bad channel range spec"); return 0; } } else if (d->type == DATA_LONG) { if (d->val.num < 0 || d->val.num > EV_MAXDEV) { cons_err("bad device number"); return 0; } e->dev_min = e->dev_max = d->val.num; e->ch_min = 0; e->ch_max = (evinfo[e->cmd].flags & EV_HAS_CH) ? EV_MAXCH : e->ch_min; } else { cons_err("list or ref expected as channel range spec"); return 0; } /* * find the first parameter range */ d = d->next; if (!d) { goto done; } if (evinfo[e->cmd].nranges < 1) { goto toomany; } if ((e->cmd == EVSPEC_CTL || e->cmd == EV_XCTL) && d->type == DATA_REF) { if (!data_getctl(d, &hi)) { return 0; } e->v0_min = e->v0_max = hi; } else if (e->cmd == EV_XPC && d->type == DATA_NIL) { e->v0_min = e->v0_max = EV_UNDEF; } else { if (!data_getrange(d, e->v0_min, e->v0_max, &min, &max)) { return 0; } e->v0_min = min; e->v0_max = max; } /* * find the second parameter range; we desallow CTL/XCTL values * in order not to interefere with states */ d = d->next; if (!d) { goto done; } if (evinfo[e->cmd].nranges < 2) { goto toomany; } if (!data_getrange(d, e->v1_min, e->v1_max, &min, &max)) { return 0; } e->v1_min = min; e->v1_max = max; d = d->next; if (d) { goto toomany; } done: /* * convert PC->XPC and CTL->XCTL */ if (e->cmd == EVSPEC_PC) { e->cmd = EVSPEC_XPC; e->v1_min = e->v0_min; e->v1_max = e->v0_max; e->v0_min = e->v0_max = EV_UNDEF; } else if (e->cmd == EVSPEC_CTL) { e->cmd = EVSPEC_XCTL; e->v1_min = (e->v1_min << 7) & 0x3fff; e->v1_max = ((e->v1_max << 7) & 0x3fff) | 0x7f; } #if 0 log_puts("lookupevspec: "); evspec_log(e); log_puts("\n"); #endif return 1; toomany: cons_err("too many ranges/values in event spec"); return 0; } /* * find the (hi lo) couple for the 14bit controller * number: * - an integer for 7bit controller * - a list of two integers (like '{hi lo}') */ unsigned exec_lookupctl(struct exec *o, char *var, unsigned *num) { struct var *arg; arg = exec_varlookup(o, var); if (!arg) { log_puts("exec_lookupctl: no such var\n"); panic(); } return data_getctl(arg->data, num); } /* * print a struct data to the console */ void data_print(struct data *d) { struct data *i; switch(d->type) { case DATA_NIL: textout_putstr(tout, "nil"); break; case DATA_LONG: if (d->val.num < 0) { textout_putstr(tout, "-"); textout_putlong(tout, -d->val.num); } else { textout_putlong(tout, d->val.num); } break; case DATA_STRING: textout_putstr(tout, "\""); textout_putstr(tout, d->val.str); textout_putstr(tout, "\""); break; case DATA_REF: textout_putstr(tout, d->val.ref); break; case DATA_LIST: textout_putstr(tout, "{"); for (i = d->val.list; i != NULL; i = i->next) { data_print(i); if (i->next) { textout_putstr(tout, " "); } } textout_putstr(tout, "}"); break; case DATA_RANGE: textout_putlong(tout, d->val.range.min); textout_putstr(tout, ":"); textout_putlong(tout, d->val.range.max); break; default: log_puts("data_print: unknown type\n"); break; } } /* * lookup the value of the given controller */ unsigned exec_lookupval(struct exec *o, char *n, unsigned isfine, unsigned *r) { struct var *arg; unsigned max; arg = exec_varlookup(o, n); if (!arg) { log_puts("exec_lookupval: no such var\n"); panic(); } if (arg->data->type == DATA_NIL) { *r = EV_UNDEF; return 1; } else if (arg->data->type == DATA_LONG) { max = isfine ? EV_MAXFINE : EV_MAXCOARSE; if (arg->data->val.num < 0 || arg->data->val.num > max) { cons_err("controller value out of range"); return 0; } *r = arg->data->val.num; return 1; } else { cons_err("bad type of controller value"); return 0; } } /* * convert lists to channels, 'data' can be * - a reference to an existing songchan * - a pair of integers '{ dev midichan }' */ unsigned data_getchan(struct data *o, unsigned *res_dev, unsigned *res_ch, int input) { struct songchan *i; long dev, ch; if (o->type == DATA_LIST) { o = o->val.list; if (o == NULL || o->next == NULL || o->next->next != NULL || o->type != DATA_LONG || o->next->type != DATA_LONG) { cons_err("bad {dev midichan} pair"); return 0; } dev = o->val.num; ch = o->next->val.num; if (ch < 0 || ch > EV_MAXCH || dev < 0 || dev > EV_MAXDEV) { cons_err("bad dev/midichan ranges"); return 0; } *res_dev = dev; *res_ch = ch; return 1; } else if (o->type == DATA_REF) { i = song_chanlookup(usong, o->val.ref, input); if (i == NULL) { cons_errs(o->val.ref, "no such chan name"); return 0; } *res_dev = i->dev; *res_ch = i->ch; return 1; } else { cons_err("bad channel specification"); return 0; } } /* * convert a struct data to a pair of integers * data can be: * - a liste of 2 integers * - a single integer (then min = max) */ unsigned data_getrange(struct data *d, unsigned min, unsigned max, unsigned *rlo, unsigned *rhi) { long lo, hi; if (d->type == DATA_LONG) { lo = hi = d->val.num; } else if (d->type == DATA_LIST) { d = d->val.list; if (!d) { *rlo = min; *rhi = max; return 1; } if (!d->next || d->next->next || d->type != DATA_LONG || d->next->type != DATA_LONG) { cons_err("exactly 0 or 2 numbers expected in range spec"); return 0; } lo = d->val.num; hi = d->next->val.num; } else if (d->type == DATA_RANGE) { lo = d->val.range.min; hi = d->val.range.max; } else { cons_err("range or number expected in range spec"); return 0; } if (lo < min || lo > max || hi < min || hi > max || lo > hi) { cons_err("range values out of bounds"); return 0; } *rlo = lo; *rhi = hi; return 1; } /* * convert a list to bitmap of continuous controllers */ unsigned data_getctlset(struct data *d, unsigned *res) { unsigned ctlset; ctlset = 0; while (d) { if (d->type != DATA_LONG) { cons_err("not-a-number in controller set"); return 0; } if (d->val.num < 0 || d->val.num >= 32) { cons_err("controller number out of range 0..31"); return 0; } ctlset |= (1 << d->val.num); d = d->next; } *res = ctlset; return 1; } /* * convert a list to bitmap of CONV_xxx constants */ unsigned data_getxev(struct data *d, unsigned *res) { static unsigned cmds[] = {EV_XPC, EV_NRPN, EV_RPN}; unsigned i, conv, cmd; conv = 0; while (d) { if (d->type != DATA_REF) { err: cons_err("xpc, rpn, or nrpn expected as flag"); return 0; } i = 0; for (;;) { if (i == sizeof(cmds) / sizeof(int)) goto err; cmd = cmds[i]; if (str_eq(d->val.ref, evinfo[cmd].ev)) break; i++; } conv |= (1 << cmd); d = d->next; } *res = conv; return 1; } /* * check if the pattern in data (list of integers) * match the beggining of the given sysex */ unsigned data_matchsysex(struct data *d, struct sysex *sx, unsigned *res) { unsigned i; struct chunk *ck; i = 0; ck = sx->first; while (d) { if (d->type != DATA_LONG) { cons_err("not-a-number in sysex pattern"); return 0; } for (;;) { if (!ck) { *res = 0; return 1; } if (i < ck->used) { break; } ck = ck->next; i = 0; } if (d->val.num != ck->data[i++]) { *res = 0; return 1; } d = d->next; } *res = 1; return 1; } /* * convert a 'struct data' to a controller number */ unsigned data_getctl(struct data *d, unsigned *num) { if (d->type == DATA_LONG) { if (d->val.num < 0 || d->val.num > EV_MAXCOARSE) { cons_err("7bit ctl number out of bounds"); return 0; } *num = d->val.num; } else if (d->type == DATA_REF) { if (!evctl_lookup(d->val.ref, num)) { cons_errs(d->val.ref, "no such controller"); return 0; } } else { cons_err("number or identifier expected in ctl spec"); return 0; } return 1; } static struct parse parse; static struct exec *exec; static unsigned exitcode; int done = 0; void user_onchar(void *arg, int c) { if (done) return; lex_handle(&parse, c); if (c < 0) { /* end-of-file (user quit) */ exitcode = 1; done = 1; } if (exec->result == RESULT_EXIT) { exitcode = 1; done = 1; } if (exec->result == RESULT_ERR && user_flag_batch) { exitcode = 0; done = 1; } if (c == '\n') cons_ready(); } void user_oncompl(void *arg, char *text, int curs, int used, int *rstart, int *rend) { struct name *n; int start, end; int c, quote; quote = 0; start = curs; while (1) { if (start-- == 0) break; if (text[start] == '"') quote ^= 1; } if (quote) { /* * quoted test is file-name, find directory components * and scan directory */ end = curs; while (1) { if (end == used || text[end] == '"') break; end++; } start = curs; while (1) { if (text[start - 1] == '"') break; start--; } user_oncompl_path(text, &start, &end); } else { /* * unquoted text is a proc name; complete * if it's either at the beginning or after '[' */ start = curs; while (1) { if (start == 0) break; c = text[start - 1]; if (!IS_IDNEXT(c)) { if (c == '[') break; return; } start--; } end = curs; while (1) { if (end == used || !IS_IDNEXT(text[end])) break; end++; } for (n = exec->procs; n != NULL; n = n->next) el_compladd(n->str); } *rstart = start; *rend = end; } struct el_ops user_el_ops = { user_onchar, user_oncompl }; unsigned user_mainloop(void) { cons_init(&user_el_ops, NULL); textio_init(); evctl_init(); seqev_pool_init(DEFAULT_MAXNSEQEVS); state_pool_init(DEFAULT_MAXNSTATES); chunk_pool_init(DEFAULT_MAXNCHUNKS); sysex_pool_init(DEFAULT_MAXNSYSEXS); seqptr_pool_init(DEFAULT_MAXNSEQPTRS); /* * create the project (ie the song) and * the execution environment of the interpreter */ mididev_listinit(); usong = song_new(); exec = exec_new(); /* * register built-in functions */ exec_newbuiltin(exec, "print", blt_print, name_newarg("...", NULL)); exec_newbuiltin(exec, "err", blt_err, name_newarg("message", NULL)); exec_newbuiltin(exec, "h", blt_h, name_newarg("...", NULL)); exec_newbuiltin(exec, "exec", blt_exec, name_newarg("filename", NULL)); exec_newbuiltin(exec, "debug", blt_debug, name_newarg("flag", name_newarg("value", NULL))); exec_newbuiltin(exec, "version", blt_version, NULL); exec_newbuiltin(exec, "panic", blt_panic, NULL); exec_newbuiltin(exec, "info", blt_info, NULL); exec_newbuiltin(exec, "getunit", blt_getunit, NULL); exec_newbuiltin(exec, "setunit", blt_setunit, name_newarg("tics_per_unit", NULL)); exec_newbuiltin(exec, "getfac", blt_getfac, NULL); exec_newbuiltin(exec, "fac", blt_fac, name_newarg("tempo_factor", NULL)); exec_newbuiltin(exec, "getpos", blt_getpos, NULL); exec_newbuiltin(exec, "g", blt_goto, name_newarg("measure", NULL)); exec_newbuiltin(exec, "getlen", blt_getlen, NULL); exec_newbuiltin(exec, "sel", blt_sel, name_newarg("length", NULL)); exec_newbuiltin(exec, "loop", blt_loop, NULL); exec_newbuiltin(exec, "noloop", blt_noloop, NULL); exec_newbuiltin(exec, "getq", blt_getq, NULL); exec_newbuiltin(exec, "setq", blt_setq, name_newarg("step", NULL)); exec_newbuiltin(exec, "ev", blt_ev, name_newarg("evspec", NULL)); exec_newbuiltin(exec, "gett", blt_gett, NULL); exec_newbuiltin(exec, "ct", blt_ct, name_newarg("trackname", NULL)); exec_newbuiltin(exec, "getf", blt_getf, NULL); exec_newbuiltin(exec, "cf", blt_cf, name_newarg("filtname", NULL)); exec_newbuiltin(exec, "getx", blt_getx, NULL); exec_newbuiltin(exec, "cx", blt_cx, name_newarg("sysexname", NULL)); exec_newbuiltin(exec, "geti", blt_geti, NULL); exec_newbuiltin(exec, "ci", blt_ci, name_newarg("channame", NULL)); exec_newbuiltin(exec, "geto", blt_geto, NULL); exec_newbuiltin(exec, "co", blt_co, name_newarg("channame", NULL)); exec_newbuiltin(exec, "mute", blt_mute, name_newarg("trackname", NULL)); exec_newbuiltin(exec, "unmute", blt_unmute, name_newarg("trackname", NULL)); exec_newbuiltin(exec, "getmute", blt_getmute, name_newarg("trackname", NULL)); exec_newbuiltin(exec, "ls", blt_ls, NULL); exec_newbuiltin(exec, "save", blt_save, name_newarg("filename", NULL)); exec_newbuiltin(exec, "load", blt_load, name_newarg("filename", NULL)); exec_newbuiltin(exec, "reset", blt_reset, NULL); exec_newbuiltin(exec, "export", blt_export, name_newarg("filename", NULL)); exec_newbuiltin(exec, "import", blt_import, name_newarg("filename", NULL)); exec_newbuiltin(exec, "i", blt_idle, NULL); exec_newbuiltin(exec, "p", blt_play, NULL); exec_newbuiltin(exec, "r", blt_rec, NULL); exec_newbuiltin(exec, "s", blt_stop, NULL); exec_newbuiltin(exec, "t", blt_tempo, name_newarg("beats_per_minute", NULL)); exec_newbuiltin(exec, "mins", blt_mins, name_newarg("amount", name_newarg("sig", NULL))); exec_newbuiltin(exec, "mcut", blt_mcut, NULL); exec_newbuiltin(exec, "mdup", blt_mdup, name_newarg("where", NULL)); exec_newbuiltin(exec, "minfo", blt_minfo, NULL); exec_newbuiltin(exec, "mtempo", blt_mtempo, NULL); exec_newbuiltin(exec, "msig", blt_msig, NULL); exec_newbuiltin(exec, "mend", blt_mend, NULL); exec_newbuiltin(exec, "ctlconf", blt_ctlconf, name_newarg("name", name_newarg("ctl", name_newarg("defval", NULL)))); exec_newbuiltin(exec, "ctlconfx", blt_ctlconfx, name_newarg("name", name_newarg("ctl", name_newarg("defval", NULL)))); exec_newbuiltin(exec, "ctlunconf", blt_ctlunconf, name_newarg("name", NULL)); exec_newbuiltin(exec, "ctlinfo", blt_ctlinfo, NULL); exec_newbuiltin(exec, "evpat", blt_evpat, name_newarg("name", name_newarg("pattern", NULL))); exec_newbuiltin(exec, "evinfo", blt_evinfo, NULL); exec_newbuiltin(exec, "m", blt_metro, name_newarg("onoff", NULL)); exec_newbuiltin(exec, "metrocf", blt_metrocf, name_newarg("eventhi", name_newarg("eventlo", NULL))); exec_newbuiltin(exec, "tap", blt_tap, name_newarg("mode", NULL)); exec_newbuiltin(exec, "tapev", blt_tapev, name_newarg("evspec", NULL)); exec_newbuiltin(exec, "u", blt_undo, NULL); exec_newbuiltin(exec, "ul", blt_undolist, NULL); exec_newbuiltin(exec, "tlist", blt_tlist, NULL); exec_newbuiltin(exec, "tnew", blt_tnew, name_newarg("trackname", NULL)); exec_newbuiltin(exec, "tdel", blt_tdel, NULL); exec_newbuiltin(exec, "tren", blt_tren, name_newarg("newname", NULL)); exec_newbuiltin(exec, "texists", blt_texists, name_newarg("trackname", NULL)); exec_newbuiltin(exec, "taddev", blt_taddev, name_newarg("measure", name_newarg("beat", name_newarg("tic", name_newarg("event", NULL))))); exec_newbuiltin(exec, "tsetf", blt_tsetf, name_newarg("filtname", NULL)); exec_newbuiltin(exec, "tgetf", blt_tgetf, NULL); exec_newbuiltin(exec, "tcheck", blt_tcheck, NULL); exec_newbuiltin(exec, "trewrite", blt_trewrite, NULL); exec_newbuiltin(exec, "tcut", blt_tcut, NULL); exec_newbuiltin(exec, "tclr", blt_tclr, NULL); exec_newbuiltin(exec, "tpaste", blt_tpaste, NULL); exec_newbuiltin(exec, "tcopy", blt_tcopy, NULL); exec_newbuiltin(exec, "tins", blt_tins, name_newarg("amount", NULL)); exec_newbuiltin(exec, "tmerge", blt_tmerge, name_newarg("source", NULL)); exec_newbuiltin(exec, "tquanta", blt_tquanta, name_newarg("rate", NULL)); exec_newbuiltin(exec, "tquantf", blt_tquantf, name_newarg("rate", NULL)); exec_newbuiltin(exec, "ttransp", blt_ttransp, name_newarg("halftones", NULL)); exec_newbuiltin(exec, "tvcurve", blt_tvcurve, name_newarg("weight", NULL)); exec_newbuiltin(exec, "tevmap", blt_tevmap, name_newarg("from", name_newarg("to", NULL))); exec_newbuiltin(exec, "tclist", blt_tclist, NULL); exec_newbuiltin(exec, "tinfo", blt_tinfo, NULL); exec_newbuiltin(exec, "tdump", blt_tdump, NULL); exec_newbuiltin(exec, "ilist", blt_ilist, NULL); exec_newbuiltin(exec, "iexists", blt_iexists, name_newarg("channame", NULL)); exec_newbuiltin(exec, "iset", blt_iset, name_newarg("channum", NULL)); exec_newbuiltin(exec, "inew", blt_inew, name_newarg("channame", name_newarg("channum", NULL))); exec_newbuiltin(exec, "idel", blt_idel, NULL); exec_newbuiltin(exec, "iren", blt_iren, name_newarg("newname", NULL)); exec_newbuiltin(exec, "iinfo", blt_iinfo, NULL); exec_newbuiltin(exec, "igetc", blt_igetc, NULL); exec_newbuiltin(exec, "igetd", blt_igetd, NULL); exec_newbuiltin(exec, "iaddev", blt_iaddev, name_newarg("event", NULL)); exec_newbuiltin(exec, "irmev", blt_irmev, name_newarg("evspec", NULL)); exec_newbuiltin(exec, "olist", blt_olist, NULL); exec_newbuiltin(exec, "oexists", blt_oexists, name_newarg("channame", NULL)); exec_newbuiltin(exec, "oset", blt_oset, name_newarg("channum", NULL)); exec_newbuiltin(exec, "onew", blt_onew, name_newarg("channame", name_newarg("channum", NULL))); exec_newbuiltin(exec, "odel", blt_odel, NULL); exec_newbuiltin(exec, "oren", blt_oren, name_newarg("newname", NULL)); exec_newbuiltin(exec, "oinfo", blt_oinfo, NULL); exec_newbuiltin(exec, "ogetc", blt_ogetc, NULL); exec_newbuiltin(exec, "ogetd", blt_ogetd, NULL); exec_newbuiltin(exec, "oaddev", blt_oaddev, name_newarg("event", NULL)); exec_newbuiltin(exec, "ormev", blt_ormev, name_newarg("evspec", NULL)); exec_newbuiltin(exec, "flist", blt_flist, NULL); exec_newbuiltin(exec, "fexists", blt_fexists, name_newarg("filtname", NULL)); exec_newbuiltin(exec, "fnew", blt_fnew, name_newarg("filtname", NULL)); exec_newbuiltin(exec, "fdel", blt_fdel, NULL); exec_newbuiltin(exec, "fren", blt_fren, name_newarg("newname", NULL)); exec_newbuiltin(exec, "finfo", blt_finfo, NULL); exec_newbuiltin(exec, "freset", blt_freset, NULL); exec_newbuiltin(exec, "fmap", blt_fmap, name_newarg("from", name_newarg("to", NULL))); exec_newbuiltin(exec, "funmap", blt_funmap, name_newarg("from", name_newarg("to", NULL))); exec_newbuiltin(exec, "ftransp", blt_ftransp, name_newarg("evspec", name_newarg("plus", NULL))); exec_newbuiltin(exec, "fvcurve", blt_fvcurve, name_newarg("evspec", name_newarg("weight", NULL))); exec_newbuiltin(exec, "fchgin", blt_fchgin, name_newarg("from", name_newarg("to", NULL))); exec_newbuiltin(exec, "fchgout", blt_fchgout, name_newarg("from", name_newarg("to", NULL))); exec_newbuiltin(exec, "fswapin", blt_fswapin, name_newarg("from", name_newarg("to", NULL))); exec_newbuiltin(exec, "fswapout", blt_fswapout, name_newarg("from", name_newarg("to", NULL))); exec_newbuiltin(exec, "xlist", blt_xlist, NULL); exec_newbuiltin(exec, "xexists", blt_xexists, name_newarg("sysexname", NULL)); exec_newbuiltin(exec, "xnew", blt_xnew, name_newarg("sysexname", NULL)); exec_newbuiltin(exec, "xdel", blt_xdel, NULL); exec_newbuiltin(exec, "xren", blt_xren, name_newarg("newname", NULL)); exec_newbuiltin(exec, "xinfo", blt_xinfo, NULL); exec_newbuiltin(exec, "xrm", blt_xrm, name_newarg("data", NULL)); exec_newbuiltin(exec, "xsetd", blt_xsetd, name_newarg("devnum", name_newarg("data", NULL))); exec_newbuiltin(exec, "xadd", blt_xadd, name_newarg("devnum", name_newarg("data", NULL))); exec_newbuiltin(exec, "ximport", blt_ximport, name_newarg("devnum", name_newarg("path", NULL))); exec_newbuiltin(exec, "xexport", blt_xexport, name_newarg("path", NULL)); exec_newbuiltin(exec, "shut", blt_shut, NULL); exec_newbuiltin(exec, "proclist", blt_proclist, NULL); exec_newbuiltin(exec, "builtinlist", blt_builtinlist, NULL); exec_newbuiltin(exec, "dnew", blt_dnew, name_newarg("devnum", name_newarg("path", name_newarg("mode", NULL)))); exec_newbuiltin(exec, "ddel", blt_ddel, name_newarg("devnum", NULL)); exec_newbuiltin(exec, "dlist", blt_dlist, NULL); exec_newbuiltin(exec, "dmtcrx", blt_dmtcrx, name_newarg("devnum", NULL)); exec_newbuiltin(exec, "dmmctx", blt_dmmctx, name_newarg("devlist", NULL)); exec_newbuiltin(exec, "dclktx", blt_dclktx, name_newarg("devlist", NULL)); exec_newbuiltin(exec, "dclkrx", blt_dclkrx, name_newarg("devnum", NULL)); exec_newbuiltin(exec, "dclkrate", blt_dclkrate, name_newarg("devnum", name_newarg("tics_per_unit", NULL))); exec_newbuiltin(exec, "dinfo", blt_dinfo, name_newarg("devnum", NULL)); exec_newbuiltin(exec, "dixctl", blt_dixctl, name_newarg("devnum", name_newarg("ctlset", NULL))); exec_newbuiltin(exec, "doxctl", blt_doxctl, name_newarg("devnum", name_newarg("ctlset", NULL))); exec_newbuiltin(exec, "diev", blt_diev, name_newarg("devnum", name_newarg("flags", NULL))); exec_newbuiltin(exec, "doev", blt_doev, name_newarg("devnum", name_newarg("flags", NULL))); /* * run the user startup script: $HOME/.midishrc or /etc/midishrc */ if (!user_flag_batch) { exec_runrcfile(exec); if (mididev_list == NULL) cons_err("Warning, no MIDI devices configured."); } /* * create the parser and start parsing standard input */ parse_init(&parse, exec, exec_cb); lex_init(&parse, "stdin", parse_cb, &parse); cons_ready(); cons_putpos(usong->curpos, 0, 0); done = 0; while (!done && mux_mdep_wait(1)) ; /* nothing */ lex_done(&parse); parse_done(&parse); exec_delete(exec); song_delete(usong); usong = NULL; mididev_listdone(); seqptr_pool_done(); sysex_pool_done(); chunk_pool_done(); state_pool_done(); seqev_pool_done(); evctl_done(); textio_done(); cons_done(); return exitcode; } midish-1.4.0/user.h010066400017510001751000000266761501104363400126470ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef MIDISH_USER_H #define MIDISH_USER_H struct song; struct songtrk; struct songchan; struct songfilt; struct songsx; struct exec; struct data; struct ev; struct evspec; struct sysex; extern struct song *usong; extern unsigned user_flag_batch; extern unsigned user_flag_verb; unsigned user_mainloop(void); void user_printstr(char *); void user_printlong(long); void user_error(char *); unsigned user_getopts(int *, char ***); void user_onchar(void *, int); void user_oncompl(void *, char *, int, int, int *, int *); void user_oncompl_path(char *, int *, int *); /* useful conversion functions */ unsigned exec_runfile(struct exec *, char *); unsigned exec_runrcfile(struct exec *); unsigned exec_lookuptrack(struct exec *, char *, struct songtrk **); unsigned exec_lookupchan_getnum(struct exec *, char *, unsigned *, unsigned *, int); unsigned exec_lookupchan_getref(struct exec *, char *, struct songchan **, int); unsigned exec_lookupfilt(struct exec *, char *, struct songfilt **); unsigned exec_lookupsx(struct exec *, char *, struct songsx **); unsigned exec_lookupev(struct exec *, char *, struct ev *, int); unsigned exec_lookupevspec(struct exec *, char *, struct evspec *, int); unsigned exec_lookupctl(struct exec *, char *, unsigned *); unsigned exec_lookupval(struct exec *, char *, unsigned, unsigned *); void data_print(struct data *); unsigned data_num2chan(struct data *, unsigned *, unsigned *); unsigned data_getchan(struct data *, unsigned *, unsigned *, int); unsigned data_getrange(struct data *, unsigned, unsigned, unsigned *, unsigned *); unsigned data_matchsysex(struct data *, struct sysex *, unsigned *); unsigned data_list2ctl(struct data *, unsigned *); unsigned data_getctlset(struct data *, unsigned *); unsigned data_getxev(struct data *, unsigned *); unsigned data_getctl(struct data *, unsigned *); /* track functions */ unsigned user_func_tracklist(struct exec *, struct data **); unsigned user_func_tracknew(struct exec *, struct data **); unsigned user_func_trackdelete(struct exec *, struct data **); unsigned user_func_trackrename(struct exec *, struct data **); unsigned user_func_trackexists(struct exec *, struct data **); unsigned user_func_trackinfo(struct exec *, struct data **); unsigned user_func_trackaddev(struct exec *, struct data **); unsigned user_func_tracksetcurfilt(struct exec *, struct data **); unsigned user_func_trackgetcurfilt(struct exec *, struct data **); unsigned user_func_trackcheck(struct exec *, struct data **); unsigned user_func_trackcut(struct exec *, struct data **); unsigned user_func_trackblank(struct exec *, struct data **); unsigned user_func_trackcopy(struct exec *, struct data **); unsigned user_func_trackinsert(struct exec *, struct data **); unsigned user_func_trackmerge(struct exec *, struct data **); unsigned user_func_trackquant(struct exec *, struct data **); unsigned user_func_tracktransp(struct exec *, struct data **); unsigned user_func_tracksetmute(struct exec *, struct data **); unsigned user_func_trackgetmute(struct exec *, struct data **); unsigned user_func_trackchanlist(struct exec *, struct data **); /* chan functions */ unsigned user_func_chanlist(struct exec *, struct data **); unsigned user_func_channew(struct exec *, struct data **); unsigned user_func_chanset(struct exec *, struct data **); unsigned user_func_chandelete(struct exec *, struct data **); unsigned user_func_chanrename(struct exec *, struct data **); unsigned user_func_chanexists(struct exec *, struct data **); unsigned user_func_changetch(struct exec *, struct data **); unsigned user_func_changetdev(struct exec *, struct data **); unsigned user_func_chanconfev(struct exec *, struct data **); unsigned user_func_chanunconfev(struct exec *, struct data **); unsigned user_func_chaninfo(struct exec *, struct data **); unsigned user_func_changetcurinput(struct exec *, struct data **); unsigned user_func_chansetcurinput(struct exec *, struct data **); /* sysex */ unsigned user_func_sysexlist(struct exec *, struct data **); unsigned user_func_sysexnew(struct exec *, struct data **); unsigned user_func_sysexdelete(struct exec *, struct data **); unsigned user_func_sysexrename(struct exec *, struct data **); unsigned user_func_sysexexists(struct exec *, struct data **); unsigned user_func_sysexinfo(struct exec *, struct data **); unsigned user_func_sysexclear(struct exec *, struct data **); unsigned user_func_sysexsetunit(struct exec *, struct data **); unsigned user_func_sysexadd(struct exec *, struct data **); /* filt functions */ unsigned user_func_filtlist(struct exec *, struct data **); unsigned user_func_filtnew(struct exec *, struct data **); unsigned user_func_filtdelete(struct exec *, struct data **); unsigned user_func_filtrename(struct exec *, struct data **); unsigned user_func_filtexists(struct exec *, struct data **); unsigned user_func_filtinfo(struct exec *, struct data **); unsigned user_func_filtdevdrop(struct exec *, struct data **); unsigned user_func_filtnodevdrop(struct exec *, struct data **); unsigned user_func_filtdevmap(struct exec *, struct data **); unsigned user_func_filtnodevmap(struct exec *, struct data **); unsigned user_func_filtchandrop(struct exec *, struct data **); unsigned user_func_filtnochandrop(struct exec *, struct data **); unsigned user_func_filtchanmap(struct exec *, struct data **); unsigned user_func_filtnochanmap(struct exec *, struct data **); unsigned user_func_filtctldrop(struct exec *, struct data **); unsigned user_func_filtnoctldrop(struct exec *, struct data **); unsigned user_func_filtctlmap(struct exec *, struct data **); unsigned user_func_filtnoctlmap(struct exec *, struct data **); unsigned user_func_filtkeydrop(struct exec *, struct data **); unsigned user_func_filtnokeydrop(struct exec *, struct data **); unsigned user_func_filtkeymap(struct exec *, struct data **); unsigned user_func_filtnokeymap(struct exec *, struct data **); unsigned user_func_filtreset(struct exec *, struct data **); unsigned user_func_filtchgich(struct exec *, struct data **); unsigned user_func_filtchgidev(struct exec *, struct data **); unsigned user_func_filtswapich(struct exec *, struct data **); unsigned user_func_filtswapidev(struct exec *, struct data **); unsigned user_func_filtchgoch(struct exec *, struct data **); unsigned user_func_filtchgodev(struct exec *, struct data **); unsigned user_func_filtswapoch(struct exec *, struct data **); unsigned user_func_filtswapodev(struct exec *, struct data **); unsigned user_func_filtevmap(struct exec *, struct data **); unsigned user_func_filtevunmap(struct exec *, struct data **); unsigned user_func_filtchgin(struct exec *, struct data **); unsigned user_func_filtchgout(struct exec *, struct data **); unsigned user_func_filtswapgin(struct exec *, struct data **); unsigned user_func_filtswapout(struct exec *, struct data **); unsigned user_func_filtsetcurchan(struct exec *, struct data **); unsigned user_func_filtgetcurchan(struct exec *, struct data **); /* song functions */ unsigned user_func_songsetunit(struct exec *, struct data **); unsigned user_func_songgetunit(struct exec *, struct data **); unsigned user_func_songsetfactor(struct exec *, struct data **); unsigned user_func_songgetfactor(struct exec *, struct data **); unsigned user_func_songsetcurpos(struct exec *, struct data **); unsigned user_func_songgetcurpos(struct exec *, struct data **); unsigned user_func_songsetcurlen(struct exec *, struct data **); unsigned user_func_songgetcurlen(struct exec *, struct data **); unsigned user_func_songsetcurquant(struct exec *, struct data **); unsigned user_func_songgetcurquant(struct exec *, struct data **); unsigned user_func_songsetcurtrack(struct exec *, struct data **); unsigned user_func_songgetcurtrack(struct exec *, struct data **); unsigned user_func_songsetcurfilt(struct exec *, struct data **); unsigned user_func_songgetcurfilt(struct exec *, struct data **); unsigned user_func_songinfo(struct exec *, struct data **); unsigned user_func_songsave(struct exec *, struct data **); unsigned user_func_songload(struct exec *, struct data **); unsigned user_func_songreset(struct exec *, struct data **); unsigned user_func_songexportsmf(struct exec *, struct data **); unsigned user_func_songimportsmf(struct exec *, struct data **); unsigned user_func_songidle(struct exec *, struct data **); unsigned user_func_songplay(struct exec *, struct data **); unsigned user_func_songrecord(struct exec *, struct data **); unsigned user_func_songstop(struct exec *, struct data **); unsigned user_func_songsettempo(struct exec *, struct data **); unsigned user_func_songtimeins(struct exec *, struct data **); unsigned user_func_songtimerm(struct exec *, struct data **); unsigned user_func_songtimeinfo(struct exec *, struct data **); unsigned user_func_songgettempo(struct exec *, struct data **); unsigned user_func_songgetsign(struct exec *, struct data **); unsigned user_func_songsetcursysex(struct exec *, struct data **); unsigned user_func_songgetcursysex(struct exec *, struct data **); unsigned user_func_songsetcurchan(struct exec *, struct data **); unsigned user_func_songgetcurchan(struct exec *, struct data **); unsigned user_func_songsetcurinput(struct exec *, struct data **); unsigned user_func_songgetcurinput(struct exec *, struct data **); /* device functions */ unsigned user_func_devlist(struct exec *, struct data **); unsigned user_func_devattach(struct exec *, struct data **); unsigned user_func_devdetach(struct exec *, struct data **); unsigned user_func_devsetmaster(struct exec *, struct data **); unsigned user_func_devgetmaster(struct exec *, struct data **); unsigned user_func_devsendrt(struct exec *, struct data **); unsigned user_func_devticrate(struct exec *, struct data **); unsigned user_func_devinfo(struct exec *, struct data **); unsigned user_func_devixctl(struct exec *, struct data **); unsigned user_func_devoxctl(struct exec *, struct data **); /* misc */ unsigned user_func_metroswitch(struct exec *, struct data **); unsigned user_func_metroconf(struct exec *, struct data **); unsigned user_func_shut(struct exec *, struct data **); unsigned user_func_sendraw(struct exec *, struct data **); unsigned user_func_panic(struct exec *, struct data **); unsigned user_func_debug(struct exec *, struct data **); unsigned user_func_exec(struct exec *, struct data **); unsigned user_func_print(struct exec *, struct data **); unsigned user_func_info(struct exec *, struct data **); unsigned user_func_proclist(struct exec *, struct data **); unsigned user_func_builtinlist(struct exec *, struct data **); unsigned user_func_ctlconf(struct exec *, struct data **); unsigned user_func_ctlconfx(struct exec *, struct data **); unsigned user_func_ctlunconf(struct exec *, struct data **); unsigned user_func_ctlinfo(struct exec *, struct data **); #endif /* MIDISH_USER_H */ midish-1.4.0/utils.c010066400017510001751000000076551501104363400130200ustar00alexalex/* * Copyright (c) 2003-2012 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * log_xxx() routines are used to quickly store traces into a trace buffer. * This allows traces to be collected during time sensitive operations without * disturbing them. The buffer can be flushed on standard error later, when * slow syscalls are no longer disruptive, e.g. at the end of the poll() loop. */ #include #include #include #include #include #include #include "utils.h" #include "tty.h" /* * log buffer size */ #define LOG_BUFSZ 8192 /* * store a character in the log */ #define LOG_PUTC(c) do { \ if (log_used < LOG_BUFSZ) \ log_buf[log_used++] = (c); \ } while (0) char log_buf[LOG_BUFSZ]; /* buffer where traces are stored */ unsigned int log_used = 0; /* bytes used in the buffer */ unsigned int log_sync = 1; /* if true, flush after each '\n' */ /* * write the log buffer on stderr */ void log_flush(void) { if (log_used == 0) return; tty_write(log_buf, log_used); log_used = 0; } /* * store a string in the log */ void log_putc(char *data, size_t count) { int c; while (count > 0) { c = *data++; LOG_PUTC(c); if (log_sync && c == '\n') log_flush(); count--; } } /* * store a string in the log */ void log_puts(char *msg) { char *p = msg; int c; while ((c = *p++) != '\0') { LOG_PUTC(c); if (log_sync && c == '\n') log_flush(); } } /* * store a hex in the log */ void log_putx(unsigned long num) { char dig[sizeof(num) * 2], *p = dig, c; unsigned int ndig; if (num != 0) { for (ndig = 0; num != 0; ndig++) { *p++ = num & 0xf; num >>= 4; } for (; ndig != 0; ndig--) { c = *(--p); c += (c < 10) ? '0' : 'a' - 10; LOG_PUTC(c); } } else LOG_PUTC('0'); } /* * store an unsigned decimal in the log */ void log_putu(unsigned long num) { char dig[sizeof(num) * 3], *p = dig; unsigned int ndig; if (num != 0) { for (ndig = 0; num != 0; ndig++) { *p++ = num % 10; num /= 10; } for (; ndig != 0; ndig--) LOG_PUTC(*(--p) + '0'); } else LOG_PUTC('0'); } /* * store a signed decimal in the log */ void log_puti(long num) { if (num < 0) { LOG_PUTC('-'); num = -num; } log_putu(num); } /* * same as perror() but messages goes to the log buffer */ void log_perror(char *str) { int n = errno; log_puts(str); log_puts(": "); log_puts(strerror(n)); log_puts("\n"); } /* * abort program execution after a fatal error */ void panic(void) { log_flush(); (void)kill(getpid(), SIGABRT); _exit(1); } /* * allocate 'size' bytes of memory (with size > 0). This functions never * fails (and never returns NULL), if there isn't enough memory then * abort the program. */ void * xmalloc(size_t size, char *tag) { void *p; p = malloc(size); if (p == NULL) { log_puts("failed to allocate "); log_putx(size); log_puts(" bytes\n"); panic(); } return p; } /* * free memory allocated with xmalloc() */ void xfree(void *p) { #ifdef DEBUG if (p == NULL) { log_puts("xfree with NULL arg\n"); panic(); } #endif free(p); } /* * xmalloc-style strdup(3) */ char * xstrdup(char *s, char *tag) { size_t size; void *p; size = strlen(s) + 1; p = xmalloc(size, tag); memcpy(p, s, size); return p; } midish-1.4.0/utils.h010066400017510001751000000023751501104363400130170ustar00alexalex/* * Copyright (c) 2003-2012 Alexandre Ratchov * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef UTILS_H #define UTILS_H #include #ifdef __cplusplus extern "C" { #endif void log_putc(char *, size_t); void log_puts(char *); void log_putx(unsigned long); void log_putu(unsigned long); void log_puti(long); void log_perror(char *); void log_putp(void *); void panic(void); void log_flush(void); void *xmalloc(size_t, char *); char *xstrdup(char *, char *); void xfree(void *); #ifdef __cplusplus } #endif extern unsigned log_sync; #endif /* UTILS_H */ midish-1.4.0/version.h010066400017510001751000000000371501104452700133370ustar00alexalex#define VERSION "midish 1.4.0"