midish-1.0.4004077500017510001751000000000001146135305200114265ustar00alexalexmidish-1.0.4/smfplay010077500017510001751000000013351104462352600131100ustar00alexalex#!/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 < * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "dbg.h" #include "defs.h" #include "node.h" #include "exec.h" #include "data.h" #include "cons.h" #include "frame.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" unsigned blt_panic(struct exec *o, struct data **r) { dbg_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, mem_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 if (str_eq(flag, "mem")) { mem_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; arg = exec_varlookup(o, "value"); if (!arg) { dbg_puts("blt_print: 'value': no such param\n"); return 0; } data_print(arg->data); 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 proc *proc; struct name *arg; if (!exec_lookupname(o, "function", &name)) { return 0; } proc = exec_proclookup(o, name); if (proc == NULL) { cons_errss(o->procname, name, "no such proc"); return 0; } textout_putstr(tout, name); for (arg = proc->args; arg != NULL; arg = arg->next) { textout_putstr(tout, " "); textout_putstr(tout, arg->str); } textout_putstr(tout, "\n"); 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) { dbg_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) { dbg_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; 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; } song_setunit(usong, 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); 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_setq(struct exec *o, struct data **r) { long step; struct var *arg; arg = exec_varlookup(o, "step"); if (!arg) { dbg_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) { dbg_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) { dbg_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_indent(tout); textout_putstr(tout, "# chan_name, {devicenum, midichan}\n"); SONG_FOREACH_OUT(usong, c) { textout_indent(tout); 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_indent(tout); textout_putstr(tout, "# chan_name, {devicenum, midichan}\n"); SONG_FOREACH_IN(usong, c) { textout_indent(tout); 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_indent(tout); textout_putstr(tout, "# filter_name\n"); SONG_FOREACH_FILT(usong, f) { textout_indent(tout); 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_indent(tout); textout_putstr(tout, "# track_name, default_filter, used_channels, flags\n"); SONG_FOREACH_TRK(usong, t) { textout_indent(tout); 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_indent(tout); textout_putstr(tout, "# sysex_name, number_messages\n"); SONG_FOREACH_SX(usong, s) { textout_indent(tout); 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"); return 1; } unsigned blt_save(struct exec *o, struct data **r) { char *filename; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupstring(o, "filename", &filename)) { return 0; } song_save(usong, filename); return 1; } unsigned blt_load(struct exec *o, struct data **r) { char *filename; unsigned res; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupstring(o, "filename", &filename)) { return 0; } song_done(usong); song_init(usong); res = song_load(usong, filename); cons_putpos(usong->curpos, 0, 0); return res; } unsigned blt_reset(struct exec *o, struct data **r) { if (!song_try_mode(usong, 0)) { return 0; } song_done(usong); song_init(usong); cons_putpos(usong->curpos, 0, 0); return 1; } unsigned blt_export(struct exec *o, struct data **r) { char *filename; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupstring(o, "filename", &filename)) { return 0; } return song_exportsmf(usong, filename); } unsigned blt_import(struct exec *o, struct data **r) { char *filename; struct song *sng; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupstring(o, "filename", &filename)) { return 0; } 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()) ; /* nothing */ cons_err("idling stopped"); song_stop(usong); } return 1; } unsigned blt_play(struct exec *o, struct data **r) { song_play(usong); if (user_flag_batch) { cons_err("press ^C to stop playback"); while (mux_mdep_wait() && !usong->complete) ; /* nothing */ cons_err("playback stopped"); song_stop(usong); } return 1; } unsigned blt_rec(struct exec *o, struct data **r) { song_record(usong); if (user_flag_batch) { cons_err("press ^C to stop recording"); while (mux_mdep_wait()) ; /* 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; } track_settempo(&usong->meta, usong->curpos, tempo); 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); track_ins(&usong->meta, tic, len); track_merge(&usong->meta, &tn); track_done(&tn); qstep = usong->curquant / 2; if (tic > qstep) { tic -= qstep; } SONG_FOREACH_TRK(usong, t) { track_ins(&t->track, tic, len); } 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 */ track_ins(&usong->meta, wtic, etic - stic); track_merge(&usong->meta, &paste); 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); track_ins(&t->track, wtic, etic - stic); track_merge(&t->track, &paste); 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); 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); } track_done(&t1); track_done(&t2); qstep = usong->curquant / 2; if (stic > qstep) { stic -= qstep; etic -= qstep; } SONG_FOREACH_TRK(usong, t) { track_cut(&t->track, stic, etic - stic); } 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_indent(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_indent(tout); 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_indent(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 <<= 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_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_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 *trkname; struct songtrk *t; if (!song_try_mode(usong, 0)) { return 0; } if (!exec_lookupname(o, "trackname", &trkname)) { return 0; } t = song_trklookup(usong, trkname); if (t != NULL) { cons_errs(o->procname, "track already exists"); return 0; } t = song_trknew(usong, trkname); 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; } song_trkdel(usong, t); 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; } str_delete(t->name.str); t->name.str = str_new(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; } pos += beat * tpb + tic; tp = seqptr_new(&t->track); seqptr_seek(tp, pos); seqptr_evput(tp, &ev); seqptr_del(tp); 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) { dbg_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 0; } 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; } track_check(&t->track); 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; } track_cut(&t->track, tic, len); usong->curlen = 0; 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; } track_ins(&t->track, tic, len); usong->curlen += amount; 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; } track_move(&t->track, tic, len, &usong->curev, NULL, 0, 1); 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; track_merge(&t->track, ©); } 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; } track_merge(&src->track, &dst->track); return 1; } unsigned blt_tquant(struct exec *o, struct data **r) { 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; } track_quantize(&t->track, tic, len, offset, 2 * qstep, rate); return 1; } 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; } track_transpose(&t->track, tic, len, &usong->curev, halftones); 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, 1) || !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; } track_evmap(&t->track, tic, len, &usong->curev, &from, &to); 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; song_getcurtrk(usong, &t); if (t == NULL) { cons_errs(o->procname, "no current track"); return 0; } textout_putstr(tout, "{\n"); textout_shiftright(tout); textout_indent(tout); 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++; } } } textout_putlong(tout, count); textout_putstr(tout, " "); if (len > 0) { if (len < usong->curquant / 2) { textout_putlong(tout, count_next); textout_putstr(tout, " "); } break; } (void)seqptr_skip(mp, bpm * tpb); } seqptr_del(mp); seqptr_del(tp); textout_putstr(tout, "\n"); textout_shiftleft(tout); textout_indent(tout); textout_putstr(tout, "}\n"); 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, input ? usong->inlist : usong->outlist) { 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 *i, *c; struct evspec src, dst; 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; } c = song_channew(usong, name, dev, ch, input); if (c->link) { evspec_reset(&src); evspec_reset(&dst); dst.dev_min = dst.dev_max = c->dev; dst.ch_min = dst.ch_max = c->ch; SONG_FOREACH_IN(usong, i) { src.dev_min = src.dev_max = i->dev; src.ch_min = src.ch_max = i->ch; filt_mapnew(&c->link->filt, &src, &dst); } } 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; } song_chandel(usong, c, input); 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->link && song_filtlookup(usong, name)) { cons_errss(o->procname, name, "filt name already in use"); return 0; } str_delete(c->name.str); c->name.str = str_new(name); if (c->link) { str_delete(c->link->name.str); c->link->name.str = str_new(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 songchan *c, *i; 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; } c->dev = dev; c->ch = ch; track_setchan(&c->conf, dev, ch); 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.ch != c->ch || ev.dev != c->dev) { cons_errs(o->procname, "dev/chan mismatch in event spec"); return 0; } if (ev_phase(&ev) != (EV_PHASE_FIRST | EV_PHASE_LAST)) { cons_errs(o->procname, "event must be stateless"); return 0; } song_confev(usong, c, input, &ev); 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; } song_unconfev(usong, c, input, &es); 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; } i = song_filtnew(usong, 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; } song_filtdel(usong, f); return 1; } unsigned blt_fren(struct exec *o, struct data **r) { struct songfilt *f; 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; } if (f->link && song_chanlookup(usong, name, 0)) { cons_errss(o->procname, name, "chan name already in use"); return 0; } str_delete(f->name.str); f->name.str = str_new(name); if (f->link) { str_delete(f->link->name.str); f->link->name.str = str_new(name); } return 1; } unsigned blt_fexists(struct exec *o, struct data **r) { char *name; struct songfilt *f; song_getcurfilt(usong, &f); if (f == NULL) { cons_errs(o->procname, "no current filt"); return 0; } 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(); 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(); 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(); 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(); 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(); 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(); 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 = song_sxnew(usong, 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; } song_sxdel(usong, 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; } str_delete(c->name.str); c->name.str = str_new(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_indent(tout); 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_indent(tout); textout_putstr(tout, "}\n"); return 1; } unsigned blt_xrm(struct exec *o, struct data **r) { struct songsx *c; struct sysex *x, **px; struct data *d; unsigned 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; } px = &c->sx.first; for (;;) { if (!*px) { break; } if (!data_matchsysex(d, *px, &match)) { return 0; } if (match) { x = *px; *px = x->next; if (*px == NULL) { c->sx.lastptr = px; } sysex_del(x); } else { px = &(*px)->next; } } return 1; } unsigned blt_xsetd(struct exec *o, struct data **r) { struct songsx *c; struct sysex *x; struct data *d; unsigned 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; } for (x = c->sx.first; x != NULL; x = x->next) { if (!x) { break; } if (!data_matchsysex(d, x, &match)) { return 0; } if (match) { 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) { dbg_puts("exec_lookupev: no such var\n"); dbg_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) { sysexlist_put(&c->sx, x); } else { sysex_del(x); } 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) { dbg_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) { dbg_puts("blt_dmtcrx: no such var\n"); dbg_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) { dbg_puts("blt_dclkrx: no such var\n"); dbg_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) { long unit; 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; } textout_putstr(tout, "{\n"); textout_shiftright(tout); textout_indent(tout); textout_putstr(tout, "devnum "); textout_putlong(tout, unit); textout_putstr(tout, "\n"); if (mididev_mtcsrc == mididev_byunit[unit]) { textout_indent(tout); textout_putstr(tout, "mtcrx\t\t\t# master MTC source\n"); } if (mididev_byunit[unit]->sendmmc) { textout_indent(tout); textout_putstr(tout, "mmctx\t\t\t# sends MMC messages\n"); } if (mididev_clksrc == mididev_byunit[unit]) { textout_indent(tout); textout_putstr(tout, "clkrx\t\t\t# master clock source\n"); } if (mididev_byunit[unit]->sendclk) { textout_indent(tout); textout_putstr(tout, "clktx\t\t\t# sends clock ticks\n"); } textout_indent(tout); 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_list2ctlset(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_list2ctlset(list, &ctlset)) { return 0; } mididev_byunit[unit]->oxctlset = ctlset; return 1; } midish-1.0.4/builtin.h010066400017510001751000000177311143320305600133270ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_BUILTIN_H #define MIDISH_BUILTIN_H 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_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_metro(struct exec *, struct data **); unsigned blt_metrocf(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_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_ttransp(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_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_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 **); #endif /* MIDISH_BUILTIN_H */ midish-1.0.4/configure010077500017510001751000000100661136440646100134210ustar00alexalex#!/bin/sh # # defaults # prefix=/usr/local # where to install midish readline=no # do we have readline alsa=no # do we want alsa support ? sndio=no # do we want sndio support ? rmidish=yes # do we want to build rmidish ? progs=midish # programs to build unset vars # variables definitions passed as-is unset cflags # compilation flags unset ldflags # linking flags unset bindir # path where to install binaries unset datadir # path where to install doc and examples unset mandir # path where to install man pages unset defs # no extra #defines unset ldadd # no extra libraries (-l options) unset readline_lib # path to readline library unset readline_include # path to readline header files # # guess if we want alsa devices support # if [ `uname` = Linux ]; then alsa=yes fi # # guess if we want sndio devices support # if [ `uname` = OpenBSD ]; then sndio=yes fi # # guess readline(3) parameters # if [ -e /usr/include/readline/readline.h ]; then readline=yes elif [ -e /usr/local/include/readline/readline.h ]; then readline=yes readline_include=/usr/local/include readline_lib=/usr/local/lib fi # # 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] --readline-lib=DIR path to readline library --readline-include=DIR path to readline header files --enable-alsa enable alsa sequencer backend [$alsa] --disable-alsa disable alsa sequencer backend --enable-sndio enable libsndio backend [$sndio] --disable-sndio disable libsndio backend --enable-rmidish build rmidish binary [$rmidish] --disable-rmidish don't build rmidish 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;; --readline-lib=*) readline=yes readline_lib="${i#--readline-lib=}" shift;; --readline-include=*) readline=yes readline_include="${i#--readline-include=}" shift;; --enable-alsa) alsa=yes shift;; --disable-alsa) alsa=no shift;; --enable-sndio) sndio=yes shift;; --disable-sndio) sndio=no shift;; --enable-rmidish) rmidish=yes shift;; --disable-rmidish) rmidish=no shift;; CC=*|CFLAGS=*|LDFLAGS=*) vars="${vars+$vars$nl}$i" shift;; *) help exit 1 ;; esac done bindir="${bindir:-$prefix/bin}" datadir="${datadir:-$prefix/share}" mandir="${mandir:-$prefix/man}" # # add parameters for ALSA support # if [ $alsa = yes ]; then defs="$defs -DUSE_ALSA" ldadd="$ldadd -lasound" fi # # add parameters for libsndio support # if [ $sndio = yes ]; then defs="$defs -DUSE_SNDIO" ldadd="$ldadd -lsndio" fi # # add rmidish front-end if readline is present # if [ $rmidish = yes ]; then if [ $readline != yes ]; then echo "Can't build rmidish without readline library" exit 1 fi progs="$progs rmidish" fi # # if variables defined, then insert a comment # vars=${vars+"\\ # \\ # the following were passed as arguments to configure script\\ # \\ $vars\\ "} echo "configure: creating Makefile" sed \ -e "s:@bindir@:$bindir:" \ -e "s:@datadir@:$datadir:" \ -e "s:@mandir@:$mandir:" \ -e "s:@readline_lib@:${readline_lib+-L}$readline_lib:" \ -e "s:@readline_include@:${readline_include+-I}$readline_include:" \ -e "s:@defs@:$defs:" \ -e "s:@ldadd@:$ldadd:" \ -e "s:@progs@:$progs:" \ -e "s:@vars@:${vars}:" \ < Makefile.in >Makefile echo echo "bindir................... $bindir" echo "datadir.................. $datadir" echo "mandir................... $mandir" echo "alsa..................... $alsa" echo "sndio.................... $sndio" echo "rmidish.................. $rmidish" echo "readline-lib............. ${readline_lib-default}" echo "readline-include......... ${readline_include-default}" echo echo "Do \"make && make install\" to compile and install midish" echo midish-1.0.4/cons.c010066400017510001751000000056251143320305600126150ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "dbg.h" #include "textio.h" #include "cons.h" #include "user.h" unsigned cons_ready; void cons_init(void) { cons_ready = 1; cons_mdep_init(); } void cons_done(void) { cons_mdep_done(); } /* * Same as fgetc(stdin), but if midish is started with the verb flag, * print "+ready\n" to stdout and flush it. This is useful to * front-ends that open midish in a pair of pipes */ int cons_getc(void) { int c; if (cons_ready) { if (user_flag_verb) { fprintf(stdout, "+ready\n"); fflush(stdout); } cons_ready = 0; } c = cons_mdep_getc(); if (c == '\n') { cons_ready = 1; } return c; } /* * print song position */ void cons_putpos(unsigned measure, unsigned beat, unsigned tic) { if (user_flag_verb) { fprintf(stdout, "+pos %u %u %u\n", measure, beat, tic); fflush(stdout); } } /* * follows routines that report user non-fatal errors please use them * instead of dbg_xxx (the later are only for debugging) */ void cons_err(char *mesg) { fprintf(stderr, "%s\n", mesg); } void cons_errs(char *s, char *mesg) { fprintf(stderr, "%s: %s\n", s, mesg); } void cons_erru(unsigned long u, char *mesg) { fprintf(stderr, "%lu: %s\n", u, mesg); } void cons_errss(char *s0, char *s1, char *mesg) { fprintf(stderr, "%s: %s: %s\n", s0, s1, mesg); } void cons_errsu(char *s, unsigned long u, char *mesg) { fprintf(stderr, "%s: %lu: %s\n", s, u, mesg); } void cons_erruu(unsigned long u0, unsigned long u1, char *mesg) { fprintf(stderr, "%lu.%lu: %s\n", u0, u1, mesg); } midish-1.0.4/cons.h010066400017510001751000000036361143320305600126220ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_CONS_H #define MIDISH_CONS_H void cons_init(void); void cons_done(void); void cons_mdep_init(void); void cons_mdep_done(void); int cons_mdep_getc(void); int cons_getc(void); unsigned cons_break(void); void cons_putpos(unsigned, unsigned, unsigned); 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 *); #endif /* MIDISH_CONS_H */ midish-1.0.4/conv.c010066400017510001751000000230201143320305600126050ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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, 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->pc_prog; rev->pc_bank = conv_getctx(l, ev, BANK_HI, BANK_LO); return 1; } else if (ev->cmd == EV_CTL) { switch (ev->ctl_num) { case BANK_HI: conv_rmctl(l, ev, BANK_LO); conv_setctl(l, ev); break; case RPN_HI: conv_rmctl(l, ev, NRPN_LO); conv_rmctl(l, ev, RPN_LO); conv_setctl(l, ev); break; case NRPN_HI: conv_rmctl(l, ev, RPN_LO); conv_rmctl(l, ev, NRPN_LO); conv_setctl(l, ev); break; case DATAENT_HI: conv_rmctl(l, ev, DATAENT_LO); conv_setctl(l, ev); break; case BANK_LO: conv_setctl(l, ev); break; case NRPN_LO: conv_rmctl(l, ev, RPN_LO); conv_setctl(l, ev); break; case RPN_LO: conv_rmctl(l, ev, NRPN_LO); conv_setctl(l, ev); break; case DATAENT_LO: 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) { break; } rev->cmd = EV_RPN; } val = conv_getctl(l, ev, DATAENT_HI); if (val == EV_UNDEF) { break; } rev->dev = ev->dev; rev->ch = ev->ch; rev->ctl_num = num; rev->ctl_val = ev->ctl_val + (val << 7); return 1; default: if (ev->ctl_num < 32) { if (EVCTL_ISFINE(xctlset, ev->ctl_num)) { conv_setctl(l, ev); break; } rev->ctl_num = ev->ctl_num; rev->ctl_val = ev->ctl_val << 7; } else if (ev->ctl_num < 64) { num = ev->ctl_num - 32; if (!EVCTL_ISFINE(xctlset, num)) { break; } val = conv_getctl(l, ev, num); if (val == EV_UNDEF) { break; } rev->ctl_num = num; rev->ctl_val = ev->ctl_val + (val << 7); } else { rev->ctl_num = ev->ctl_num; rev->ctl_val = ev->ctl_val << 7; } rev->cmd = EV_XCTL; rev->dev = ev->dev; rev->ch = ev->ch; return 1; } return 0; } 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, struct ev *ev, struct ev *rev) { unsigned val, hi; unsigned nev = 0; if (ev->cmd == EV_XCTL) { 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) { 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->pc_prog = ev->pc_prog; rev++; nev++; return nev; } else if (ev->cmd == EV_NRPN) { 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) { 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.0.4/conv.h010066400017510001751000000032541143320305600126210ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_CONV_H #define MIDISH_CONV_H #define CONV_NUMREV 4 struct ev; struct statelist; unsigned conv_packev(struct statelist *, unsigned, struct ev *, struct ev *); unsigned conv_unpackev(struct statelist *, unsigned, struct ev *, struct ev *); #endif /* MIDISH_CONV_H */ midish-1.0.4/data.c010066400017510001751000000340211143320305600125540ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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 *)mem_alloc(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(unsigned min, unsigned 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; } dbg_puts("data_listremove: not found\n"); dbg_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: dbg_puts("data_clear: unknown type\n"); dbg_panic(); break; } o->type = DATA_NIL; } void data_delete(struct data *o) { data_clear(o); mem_free(o); } void data_dbg(struct data *o) { struct data *i; switch(o->type) { case DATA_NIL: dbg_puts("(nil)"); break; case DATA_USER: dbg_puts("(user)"); break; case DATA_LONG: if (o->val.num < 0) { dbg_puts("-"); dbg_putu((unsigned) - o->val.num ); } else { dbg_putu((unsigned) o->val.num); } break; case DATA_STRING: dbg_puts("\""); dbg_puts(o->val.str); dbg_puts("\""); break; case DATA_REF: dbg_puts("@"); str_dbg(o->val.ref); break; case DATA_LIST: dbg_puts("{"); for (i = o->val.list; i != NULL; i = i->next) { data_dbg(i); if (i->next) { dbg_puts(" "); } } dbg_puts("}"); break; case DATA_RANGE: dbg_putu(o->val.range.min); dbg_puts(":"); dbg_putu(o->val.range.max); break; default: dbg_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) { dbg_puts("data_assign: src and dst are the same\n"); dbg_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: dbg_puts("data_assign: bad data type\n"); dbg_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: dbg_puts("data_id: bad data types\n"); dbg_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: dbg_puts("data_eval: bad data type\n"); dbg_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; 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; } 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.0.4/data.h010066400017510001751000000073411143320305600125660ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 */ unsigned min; unsigned 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(unsigned, unsigned); void data_delete(struct data *); void data_setfield(struct data *, char *); void data_dbg(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.0.4/dbg.c010066400017510001751000000175361143320305600124130ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * dbg_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. * * prof_xxx() routines record (small) integers and provide simple statistical * properties like: minimum, maximum, average and variance. * * mem_xxx() routines are simple wrappers around malloc() overwriting memory * blocks with random data upon allocation and release. */ #include #include #include #include "dbg.h" /* * size of the buffer where traces are stored */ #define DBG_BUFSZ 8192 /* * store a character in the trace buffer */ #define DBG_PUTC(c) do { \ if (dbg_used < DBG_BUFSZ) \ dbg_buf[dbg_used++] = (c); \ } while (0) char dbg_buf[DBG_BUFSZ]; /* buffer where traces are stored */ unsigned dbg_used = 0; /* bytes used in the buffer */ unsigned dbg_sync = 1; /* if true, flush after each '\n' */ /* * write debug info buffer on stderr */ void dbg_flush(void) { if (dbg_used == 0) return; write(STDERR_FILENO, dbg_buf, dbg_used); dbg_used = 0; } /* * store a string in the debug buffer */ void dbg_puts(char *msg) { char *p = msg; int c; while ((c = *p++) != '\0') { DBG_PUTC(c); if (dbg_sync && c == '\n') dbg_flush(); } } /* * store a hex in the debug buffer */ void dbg_putx(unsigned long num) { char dig[sizeof(num) * 2], *p = dig, c; unsigned 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; DBG_PUTC(c); } } else DBG_PUTC('0'); } /* * store a decimal in the debug buffer */ void dbg_putu(unsigned long num) { char dig[sizeof(num) * 3], *p = dig; unsigned ndig; if (num != 0) { for (ndig = 0; num != 0; ndig++) { *p++ = num % 10; num /= 10; } for (; ndig != 0; ndig--) DBG_PUTC(*(--p) + '0'); } else DBG_PUTC('0'); } /* * store a signed integer in the trace buffer */ void dbg_puti(long num) { if (num < 0) { DBG_PUTC('-'); num = -num; } dbg_putu(num); } /* * store a percent in the debug buffer */ void dbg_putpct(unsigned long n) { dbg_putu(n / 100); DBG_PUTC('.'); n %= 100; DBG_PUTC('0' + n / 10); DBG_PUTC('0' + n % 10); } /* * abort program execution after a fatal error, we should * put code here to backup user data */ void dbg_panic(void) { dbg_flush(); abort(); } /* * header of a memory block */ struct mem_hdr { char *owner; /* who allocaed the block ? */ unsigned words; /* data chunk size expressed in sizeof(int) */ #define MAGIC_FREE 0xa55f9811 /* a (hopefully) ``rare'' number */ unsigned magic; /* random number, but not MAGIC_FREE */ }; unsigned mem_nalloc = 0, mem_nfree = 0, mem_debug = 0; /* * return a random number, will be used to randomize memory bocks */ unsigned mem_rnd(void) { static unsigned seed = 1989123; seed = (seed * 1664525) + 1013904223; return seed; } /* * allocate 'n' bytes of memory (with n > 0). This functions never * fails (and never returns NULL), if there isn't enough memory then * we abord the program. The memory block is randomized to break code * that doesn't initialize the block. We also add a footer and a * trailer to detect writes outside the block boundaries. */ void * mem_alloc(unsigned bytes, char *owner) { unsigned words, i, *p; struct mem_hdr *hdr; if (bytes == 0) { dbg_puts("mem_alloc: nbytes = 0\n"); dbg_panic(); } /* * calculates the number of ints corresponding to ``bytes'' */ words = (bytes + sizeof(int) - 1) / sizeof(int); /* * allocate the header, the data chunk and the trailer */ hdr = malloc(sizeof(struct mem_hdr) + (words + 1) * sizeof(int)); if (hdr == NULL) { dbg_puts("mem_alloc: failed to allocate "); dbg_putx(words); dbg_puts(" words\n"); dbg_panic(); } /* * find a random magic, but not MAGIC_FREE */ do { hdr->magic = mem_rnd(); } while (hdr->magic == MAGIC_FREE); /* * randomize data chunk */ p = (unsigned *)(hdr + 1); for (i = words; i > 0; i--) *p++ = mem_rnd(); /* * trailer is equal to the magic */ *p = hdr->magic; hdr->owner = owner; hdr->words = words; mem_nalloc++; return hdr + 1; } /* * free a memory block. Also check that the header and the trailer * werent changed and randomise the block, so that the block is not * usable once freed */ void mem_free(void *mem) { struct mem_hdr *hdr; unsigned i, *p; hdr = (struct mem_hdr *)mem - 1; p = (unsigned *)mem; if (hdr->magic == MAGIC_FREE) { dbg_puts("mem_free: block seems already freed\n"); dbg_panic(); } if (hdr->magic != p[hdr->words]) { dbg_puts("mem_free: block corrupted\n"); dbg_panic(); } /* * randomize block, so it's not usable */ for (i = hdr->words; i > 0; i--) *p++ = mem_rnd(); hdr->magic = MAGIC_FREE; mem_nfree++; free(hdr); } void mem_stats(void) { if (mem_debug) { dbg_puts("mem_stats: used="); dbg_putu(mem_nalloc - mem_nfree); dbg_puts(", alloc="); dbg_putu(mem_nalloc); dbg_puts("\n"); } } unsigned prof_sqrt(unsigned op) { unsigned t, res = 0; unsigned one = 1 << (8 * sizeof(unsigned) / 2); while (one > op) one >>= 2; while (one != 0) { t = res + one; if (op >= t) { op -= t; res += one << 1; } res >>= 1; one >>= 2; } return res; } void prof_reset(struct prof *p, char *name) { p->n = 0; p->min = ~0U; p->max = 0; p->sum = 0; p->sumsqr = 0; p->name = name; p->err = 0; } void prof_val(struct prof *p, unsigned val) { #define MAXVAL ((1 << sizeof(unsigned) * 4) - 1) unsigned sumsqr; if (p->err) { p->err++; return; } if (val > MAXVAL) { dbg_puts("prof_val: "); dbg_putu(val); dbg_puts(": too large\n"); p->err++; return; } sumsqr = p->sumsqr + val * val; if (sumsqr < p->sumsqr) { dbg_puts("prof_val: overflow\n"); p->err++; return; } if (p->max < val) p->max = val; if (p->min > val) p->min = val; p->sumsqr = sumsqr; p->sum += val; p->n++; #undef MAXVAL } void prof_dbg(struct prof *p) { unsigned mean, delta; dbg_puts(p->name); dbg_puts(": err="); dbg_putu(p->err); dbg_puts(", n="); dbg_putu(p->n); if (p->n != 0) { dbg_puts(", min="); dbg_putpct(p->min); dbg_puts(", max="); dbg_putpct(p->max); mean = p->sum / p->n; dbg_puts(", mean="); dbg_putpct(mean); delta = prof_sqrt(p->sumsqr / p->n - mean * mean); dbg_puts(", delta="); dbg_putpct(delta); } dbg_puts("\n"); } midish-1.0.4/dbg.h010066400017510001751000000040271143320305600124070ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_DBG_H #define MIDISH_DBG_H #ifndef NULL #define NULL (0) #endif void dbg_puts(char *); void dbg_putx(unsigned long); void dbg_putu(unsigned long); void dbg_puti(long); void dbg_panic(void); void dbg_flush(void); void *mem_alloc(unsigned, char *); void mem_free(void *); void mem_stats(void); unsigned mem_rnd(void); struct prof { char *name; unsigned n; unsigned min; unsigned max; unsigned sum; unsigned sumsqr; unsigned err; }; void prof_reset(struct prof *, char *); void prof_val(struct prof *, unsigned); void prof_dbg(struct prof *); extern unsigned mem_debug; extern unsigned dbg_sync; #endif /* MIDISH_DBG_H */ midish-1.0.4/defs.h010066400017510001751000000077561143320305600126100ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_DEFAULT_H #define MIDISH_DEFAULT_H /* * version string for newly created files */ #define VERSION "midish version 1.0" /* * 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 /* * 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 100000 /* * 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) /* * 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 #endif /* MIDISH_DEFAULT_H */ midish-1.0.4/ev.c010066400017510001751000000364651143320305600122730ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * ev is an "extended" midi event */ #include "dbg.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, "any", EV_HAS_DEV | EV_HAS_CH, 0, 0, 0, 0, 0, 0 }, { "tempo", NULL, 0, 1, 0xdeadbeef, TEMPO_MIN, TEMPO_MAX, 0, 0 }, { "timesig", NULL, 0, 2, 0xdeadbeef, 1, 16, 1, 32 }, { "nrpn", "nrpn", EV_HAS_DEV | EV_HAS_CH, 2, 2, 0, EV_MAXFINE, 0, EV_MAXFINE }, { "rpn", "rpn", EV_HAS_DEV | EV_HAS_CH, 2, 2, 0, EV_MAXFINE, 0, EV_MAXFINE }, { "xctl", "xctl", EV_HAS_DEV | EV_HAS_CH, 2, 1, 0, EV_MAXCOARSE, 0, EV_MAXFINE }, { "xpc", "xpc", EV_HAS_DEV | EV_HAS_CH, 2, 2, 0, EV_MAXCOARSE, 0, EV_MAXFINE }, { "noff", NULL, EV_HAS_DEV | EV_HAS_CH, 2, 0xdeadbeef, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE }, { "non", "note", EV_HAS_DEV | EV_HAS_CH, 2, 1, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE }, { "kat", NULL, EV_HAS_DEV | EV_HAS_CH, 2, 0xdeadbeef, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE }, { "ctl", "ctl", EV_HAS_DEV | EV_HAS_CH, 2, 1, 0, EV_MAXCOARSE, 0, EV_MAXCOARSE }, { "pc", "pc", EV_HAS_DEV | EV_HAS_CH, 1, 1, 0, EV_MAXCOARSE, 0, 0 }, { "cat", "cat", EV_HAS_DEV | EV_HAS_CH, 1, 0, 0, EV_MAXCOARSE, 0, 0 }, { "bend", "bend", EV_HAS_DEV | EV_HAS_CH, 1, 0, 0, EV_MAXFINE, 0, 0 } }; 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; /* dbg_puts("ev_getstr: invalid cmd\n"); dbg_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; } /* * dump the event structure on stderr, for debug purposes */ void ev_dbg(struct ev *ev) { char *cmdstr; cmdstr = ev_getstr(ev); if (cmdstr == NULL) { dbg_puts("unkw("); dbg_putu(ev->cmd); dbg_puts(")"); } else { dbg_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: dbg_puts(" {"); dbg_putx(ev->dev); dbg_puts(" "); dbg_putx(ev->ch); dbg_puts("} "); dbg_putx(ev->v0); dbg_puts(" "); dbg_putx(ev->v1); break; case EV_BEND: case EV_CAT: case EV_PC: dbg_puts(" {"); dbg_putx(ev->dev); dbg_puts(" "); dbg_putx(ev->ch); dbg_puts("} "); dbg_putx(ev->v0); break; case EV_TEMPO: dbg_puts(" "); dbg_putu((unsigned)ev->tempo_usec24); break; case EV_TIMESIG: dbg_puts(" "); dbg_putx(ev->timesig_beats); dbg_puts(" "); dbg_putx(ev->timesig_tics); break; } } } /* * 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) { *out = *in; if (to->cmd != EVSPEC_ANY && to->cmd != EVSPEC_NOTE) out->cmd = to->cmd; if ((evinfo[from->cmd].flags & EV_HAS_DEV) && (evinfo[in->cmd].flags & EV_HAS_DEV)) { out->dev += to->dev_min - from->dev_min; } if ((evinfo[from->cmd].flags & EV_HAS_CH) && (evinfo[in->cmd].flags & EV_HAS_CH)) { out->ch += to->ch_min - from->ch_min; } if (evinfo[from->cmd].nranges > 0 && evinfo[in->cmd].nranges > 0) { out->v0 += to->v0_min - from->v0_min; } if (evinfo[from->cmd].nranges > 1 && evinfo[in->cmd].nranges > 1) { out->v1 += to->v1_min - from->v1_min; } } /* * 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 = 0xdeadbeef; o->v0_max = 0xdeadbeef; o->v1_min = 0xdeadbeef; o->v1_max = 0xdeadbeef; } /* * dump the event structure on stderr (debug purposes) */ void evspec_dbg(struct evspec *o) { unsigned i; i = 0; for (;;) { if (i == EV_NUMCMD) { dbg_puts("unk("); dbg_putu(o->cmd); dbg_puts(")"); break; } if (o->cmd == i) { if (evinfo[i].spec == NULL) { dbg_puts("bad("); dbg_putu(o->cmd); dbg_puts(")"); } else { dbg_puts(evinfo[i].spec); } break; } i++; } if (evinfo[o->cmd].flags & EV_HAS_DEV) { dbg_puts(" "); dbg_putu(o->dev_min); dbg_puts(":"); dbg_putu(o->dev_max); } if (evinfo[o->cmd].flags & EV_HAS_CH) { dbg_puts(" "); dbg_putu(o->ch_min); dbg_puts(":"); dbg_putu(o->ch_max); } if (evinfo[o->cmd].nranges >= 1) { dbg_puts(" "); dbg_putu(o->v0_min); dbg_puts(":"); dbg_putu(o->v0_max); } if (evinfo[o->cmd].nranges >= 2) { dbg_puts(" "); dbg_putu(o->v1_min); dbg_puts(":"); dbg_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].nranges > 0 && evinfo[ev->cmd].nranges > 0) { if (ev->v0 < es->v0_min || ev->v0 > es->v0_max) return 0; } if (evinfo[es->cmd].nranges > 1 && evinfo[ev->cmd].nranges > 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].nranges > 0) { if (es1->v0_min != es2->v0_min || es1->v0_max != es2->v0_max) { return 0; } } if (evinfo[es1->cmd].nranges > 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].nranges > 0 && evinfo[es2->cmd].nranges > 0) { if (es1->v0_min > es2->v0_max || es1->v0_max < es2->v0_min) { return 0; } } if (evinfo[es1->cmd].nranges > 1 && evinfo[es2->cmd].nranges > 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].nranges > 0 && evinfo[es2->cmd].nranges > 0) { if (es1->v0_min < es2->v0_min || es1->v0_max > es2->v0_max) { return 0; } } if (evinfo[es1->cmd].nranges > 1 && evinfo[es2->cmd].nranges > 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 != to->cmd) { cons_err("use the same cmd for 'from' and 'to' 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; } if (evinfo[from->cmd].nranges >= 1 && from->v0_max - from->v0_min != to->v0_max - to->v0_min) { cons_err("v0 ranges must have the same size"); return 0; } if (evinfo[from->cmd].nranges >= 2 && from->v1_max - from->v1_min != to->v1_max - to->v1_min) { cons_err("v1 ranges must have the same size"); return 0; } 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) { *out = *in; if (to->cmd != EVSPEC_ANY && to->cmd != EVSPEC_NOTE) out->cmd = to->cmd; if ((evinfo[from->cmd].flags & EV_HAS_DEV) && (evinfo[in->cmd].flags & EV_HAS_DEV)) { out->dev_min += to->dev_min - from->dev_min; out->dev_max += to->dev_min - from->dev_min; } if ((evinfo[from->cmd].flags & EV_HAS_CH) && (evinfo[in->cmd].flags & EV_HAS_CH)) { out->ch_min += to->ch_min - from->ch_min; out->ch_max += to->ch_min - from->ch_min; } if (evinfo[from->cmd].nranges > 0 && evinfo[in->cmd].nranges > 0) { out->v0_min += to->v0_min - from->v0_min; out->v0_max += to->v0_min - from->v0_min; } if (evinfo[from->cmd].nranges > 1 && evinfo[in->cmd].nranges > 1) { out->v1_min += to->v1_min - from->v1_min; out->v1_max += to->v1_min - from->v1_min; } } /* * 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(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); } } } /* * return 1 if the controller is reserved */ unsigned evctl_isreserved(unsigned num) { if (num == BANK_HI || num == DATAENT_HI || (num >= 32 && num < 64) || num == RPN_HI || num == RPN_LO || num == NRPN_HI || num == NRPN_LO) { return 1; } else { return 0; } } midish-1.0.4/ev.h010066400017510001751000000150671143320305600122730ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_EV_H #define MIDISH_EV_H #include "defs.h" #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_NUMCMD 0xf #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) /* * 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 v0 #define pc_bank v1 #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 */ }; extern struct evinfo evinfo[EV_NUMCMD]; void ev_dbg(struct ev *); unsigned ev_prio(struct ev *); unsigned ev_str2cmd(struct ev *, char *); unsigned ev_phase(struct ev *); void ev_map(struct ev *, struct evspec *, struct evspec *, struct ev *); unsigned evspec_str2cmd(struct evspec *, char *); void evspec_dbg(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); #endif /* MIDISH_EV_H */ midish-1.0.4/exec.c010066400017510001751000000201511143320305600125660ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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 *)mem_alloc(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); mem_free(o); } /* * print "name = value" on stderr */ void var_dbg(struct var *o) { str_dbg(o->name.str); dbg_puts(" = "); if (o->data != NULL) data_dbg(o->data); else dbg_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 *)mem_alloc(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); mem_free(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_dbg(struct proc *o) { struct name *i; str_dbg(o->name.str); dbg_puts("("); if (o->args) { i = o->args; for (;;) { dbg_puts(i->str); i = i->next; if (!i) { break; } dbg_puts(", "); } } dbg_puts(")\n"); node_dbg(o->code, 0); } /* * create a new empty execution environment */ struct exec * exec_new(void) { struct exec *o; o = (struct exec *)mem_alloc(sizeof(struct exec), "exec"); o->procs = NULL; o->globals = NULL; o->locals = &o->globals; o->procname = "top-level"; o->depth = 0; return o; } /* * delete the given execution environment. Must not be * called inside a procedure */ void exec_delete(struct exec *o) { if (o->depth != 0) { dbg_puts("exec_done: depth != 0\n"); dbg_panic(); } var_empty(&o->globals); proc_empty(&o->procs); mem_free(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) { dbg_puts(p->name.str); dbg_puts("("); for (n = p->args; n != NULL; n = n->next) { dbg_puts(n->str); if (n->next) { dbg_puts(", "); } } dbg_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) { dbg_puts(v->name.str); dbg_puts(" = "); data_dbg(v->data); dbg_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, "no such 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, "no such 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, "no such long"); 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, "no such 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, "no such bool"); return 0; } res = data_eval(var->data); *val = res; return 1; } midish-1.0.4/exec.h010066400017510001751000000071561143320305600126050ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_EXEC_H #define MIDISH_EXEC_H #include "name.h" enum RESULT_ID { 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 */ }; struct var *var_new(struct name **, char *, struct data *); void var_delete(struct name **, struct var *); void var_dbg(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_dbg(struct proc *); #endif /* MIDISH_EXEC_H */ midish-1.0.4/filt.c010066400017510001751000000231631143320305600126060ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * filt.c * * a simple midi filter. Rewrites input events according a set * of user-configurable rules. * */ #include "dbg.h" #include "ev.h" #include "filt.h" #include "pool.h" #include "mux.h" #include "cons.h" unsigned filt_debug = 0; void rule_dbg(struct evspec *from, struct evspec *to) { evspec_dbg(from); dbg_puts(" > "); evspec_dbg(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 = mem_alloc(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; mem_free(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) { dbg_puts("filtnode_mksrc: "); evspec_dbg(&s->es); dbg_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) dbg_puts("filtnode_mksrc: exact match\n"); return s; } if (evspec_in(from, &s->es)) { if (filt_debug) { dbg_puts("filtnode_mksrc: "); evspec_dbg(from); dbg_puts(" in "); evspec_dbg(&s->es); dbg_puts("\n"); } break; } if (filt_debug) { dbg_puts("filtnode_mksrc: "); evspec_dbg(from); dbg_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) { dbg_puts("filtnode_mkdst: "); rule_dbg(&s->es, &d->es); dbg_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 (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) { dbg_puts("filt_do: ("); rule_dbg(&s->es, &d->es); dbg_puts("): "); ev_dbg(in); dbg_puts(" -> "); ev_dbg(&out[nev]); dbg_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) { dbg_puts("filt_mapdel: "); rule_dbg(&s->es, &d->es); dbg_puts(": removed\n"); } filtnode_del(pd); continue; } pd = &d->next; } } if (s->dstlist == NULL) { if (filt_debug) { dbg_puts("filt_mapdel: "); evspec_dbg(&s->es); dbg_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) { dbg_puts("filt_mapnew: adding "); rule_dbg(from, to); dbg_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); } /* XXX: make this a filtnode_detach() */ struct filtnode * filt_movelist(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_movelist(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) { dbg_puts("filt_chgin: "); evspec_dbg(&s->es); dbg_puts(" -> "); evspec_dbg(&newspec); dbg_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_movelist(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) { dbg_puts("filt_chgout: "); evspec_dbg(&d->es); dbg_puts(" -> "); evspec_dbg(&newspec); dbg_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) { dbg_puts("filt_transp: set must contain notes\n"); return; } if (from->cmd == EVSPEC_NOTE && (from->v0_min != 0 || from->v0_max != EV_MAXCOARSE)) { dbg_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) { dbg_puts("filt_vcurve: set must contain notes\n"); return; } s = filtnode_mksrc(&f->vcurve, from); s->u.vel.nweight = (64 - weight) & 0x7f; } midish-1.0.4/filt.h010066400017510001751000000103101143320305600126010ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 */ }; 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); extern unsigned filt_debug; void filt_conf_devdrop(struct filt *, unsigned); void filt_conf_nodevdrop(struct filt *, unsigned); void filt_conf_devmap(struct filt *, unsigned, unsigned); void filt_conf_nodevmap(struct filt *, unsigned); void filt_conf_chandrop(struct filt *, unsigned, unsigned); void filt_conf_nochandrop(struct filt *, unsigned, unsigned); void filt_conf_chanmap(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_nochanmap(struct filt *, unsigned, unsigned ); void filt_conf_ctldrop(struct filt *, unsigned, unsigned, unsigned); void filt_conf_noctldrop(struct filt *, unsigned, unsigned, unsigned); void filt_conf_ctlmap(struct filt *, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned); void filt_conf_noctlmap(struct filt *, unsigned, unsigned, unsigned); void filt_conf_keydrop(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_nokeydrop(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_keymap(struct filt *, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned, int); void filt_conf_nokeymap(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_chgich(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_chgidev(struct filt *, unsigned, unsigned); void filt_conf_swapich(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_swapidev(struct filt *, unsigned, unsigned); void filt_conf_chgoch(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_chgodev(struct filt *, unsigned, unsigned); void filt_conf_swapoch(struct filt *, unsigned, unsigned, unsigned, unsigned); void filt_conf_swapodev(struct filt *, unsigned, unsigned); #endif /* MIDISH_FILT_H */ midish-1.0.4/frame.c010066400017510001751000001074501146135223400127500ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.h" #include "track.h" #include "defs.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->pos = t->first; sp->delta = 0; sp->tic = 0; return sp; } /* * release the seqptr structure, free statelist etc... */ void seqptr_del(struct seqptr *sp) { statelist_done(&sp->statelist); pool_del(&seqptr_pool, sp); } /* * 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->phase & EV_PHASE_FIRST) { 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 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; /* 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) { 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); } return ntics; } /* * insert blank space at the current position */ void seqptr_ticput(struct seqptr *sp, unsigned ntics) { if (ntics > 0) { sp->pos->delta += ntics; sp->delta += ntics; sp->tic += ntics; statelist_outdate(&sp->statelist); } } /* * 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); } /* * 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. */ struct state * seqptr_rmlast(struct seqptr *sp, struct state *st) { struct seqev *i, *prev, *cur, *next; #ifdef FRAME_DEBUG dbg_puts("seqptr_rmlast: "); ev_dbg(&st->ev); dbg_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); return NULL; } else { st->ev = prev->ev; st->phase = st->pos == prev ? EV_PHASE_FIRST : EV_PHASE_NEXT; } return st; } /* * 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(). */ struct state * seqptr_rmprev(struct seqptr *sp, struct state *st) { struct seqev *i, *next; #ifdef FRAME_DEBUG dbg_puts("seqptr_rmprev: "); ev_dbg(&st->ev); dbg_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); return NULL; } /* * merge "low priority" event: check that the event of state "s1" * doen't conflict with event in thate "s2". If so, then it is put in * the track. Else "s1" is tagged as silent so a next call to this * routine will just skip it. This routine must be called before * seqptr_evmerge2() within the same tic. */ void seqptr_evmerge1(struct seqptr *pd, struct state *s1, struct state *s2) { struct state *sd; /* * ignore bogus events */ if (s1->flags & (STATE_BOGUS | STATE_NESTED)) return; if (s2 != NULL && s2->flags & (STATE_BOGUS | STATE_NESTED)) s2 = NULL; if (s1->phase & EV_PHASE_FIRST) { s1->tag = (!s2 || (s2->phase & EV_PHASE_LAST)) ? 1 : 0; #ifdef FRAME_DEBUG if (!s1->tag) { dbg_puts("seqptr_evmerge1: "); ev_dbg(&s1->ev); dbg_puts(" started in silent state\n"); } #endif } if (s1->tag) { sd = statelist_lookup(&pd->statelist, &s1->ev); if (!sd || !state_eq(sd, &s1->ev)) (void)seqptr_evput(pd, &s1->ev); } } /* * merge "high priority" event: check that the event of state "s2" * doesn't conflict with events of state "s1". If so, then put it on * the track. If there is conflict then discard events related to "s1" * and put "s2". This routine must not be called before * seqptr_evmerge1() within the same tick. */ void seqptr_evmerge2(struct seqptr *pd, struct state *s1, struct state *s2) { struct state *sd; /* * ignore bogus events */ if (s2->flags & (STATE_BOGUS | STATE_NESTED)) return; if (s1 != NULL && s1->flags & (STATE_BOGUS | STATE_NESTED)) s1 = NULL; /* * tag/untag frames depending of if there are conflicts */ sd = statelist_lookup(&pd->statelist, &s2->ev); if (s2->phase & EV_PHASE_FIRST) { if (s1 && s1->tag) { if (sd == NULL) { dbg_puts("seqptr_merge2: "); ev_dbg(&s1->ev); dbg_puts(": no conflict\n"); dbg_panic(); } if (EV_ISNOTE(&s2->ev)) { if (!(s1->phase & EV_PHASE_LAST)) sd = seqptr_rmprev(pd, sd); } else { if (s1->flags & STATE_CHANGED) sd = seqptr_rmlast(pd, sd); } s1->tag = 0; } s2->tag = 1; } else if (s2->phase & EV_PHASE_NEXT) { /* * nothing to do, conflicts already handled */ } else if (s2->phase & EV_PHASE_LAST) { if (s1 && !EV_ISNOTE(&s1->ev)) { s2->tag = 0; if (sd == NULL || !state_eq(sd, &s1->ev)) { sd = seqptr_evput(pd, &s1->ev); } s1->tag = 1; } } /* * store the event, if the frame is tagged */ if (s2->tag && (sd == NULL || !state_eq(sd, &s2->ev))) { (void)seqptr_evput(pd, &s2->ev); } } /* * 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 *s1, *s2; struct seqptr *p2, *pd; struct statelist orglist; unsigned delta1, delta2, deltad; 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(;;) { s1 = seqptr_evdel(pd, &orglist); if (s1 == NULL) break; s2 = statelist_lookup(&p2->statelist, &s1->ev); seqptr_evmerge1(pd, s1, s2); } /* * move all events from 'src' to 'dst' by merging them * with the original state of 'dst'. */ for (;;) { s2 = seqptr_evget(p2); if (s2 == NULL) break; s1 = statelist_lookup(&orglist, &s2->ev); seqptr_evmerge2(pd, s1, s2); } /* * 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 (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 (!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, 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) { dbg_puts("track_quantize: delta < 0\n"); dbg_panic(); } #endif seqptr_ticput(qp, delta); qtic += delta; if (st->phase & EV_PHASE_FIRST) { if (EV_ISNOTE(&st->ev)) { st->tag = 1; 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) { dbg_puts("track_quantize: fluct = "); dbg_putu(fluct); dbg_puts(", notes = "); dbg_putu(notes); dbg_puts(", avg = "); dbg_putu(100 * fluct / notes); dbg_puts("% of a tick\n"); } } /* * time-scale the given track in such a way that * 'oldu' tics will correspond to 'newu' */ void track_scale(struct track *t, unsigned oldunit, unsigned newunit) { struct seqptr *sp; struct statelist slist; struct state *st; struct ev ev; unsigned delta, err; err = 0; sp = seqptr_new(t); statelist_init(&slist); for (;;) { delta = newunit * seqptr_ticdel(sp, ~0U, &slist) + err; seqptr_ticput(sp, delta / oldunit); err = delta % oldunit; 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); } /* * 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); } /* * 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) { dbg_puts("track_check: "); ev_dbg(&st->ev); dbg_puts(": bogus\n"); st->tag = 0; } else if (st->flags & STATE_NESTED) { dbg_puts("track_check: "); ev_dbg(&st->ev); dbg_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 { dbg_puts("track_check: "); ev_dbg(&st->ev); dbg_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)) { dbg_puts("track_check: "); ev_dbg(&st->ev); dbg_puts(": unterminated\n"); (void)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 dbg_puts("track_findmeasure: "); dbg_putu(m); dbg_puts(" -> "); dbg_putu(tic); dbg_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)) { dbg_puts("track_confev: "); ev_dbg(ev); dbg_puts(": bad phase, ignored"); dbg_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.0.4/frame.h010066400017510001751000000067521143320305600127540ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_FRAME_H #define MIDISH_FRAME_H #include "state.h" struct seqptr { struct statelist statelist; 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 *); 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); 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 *, unsigned, unsigned, unsigned, 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_check(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.0.4/lex.c010066400017510001751000000214111143320305700124330ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * simple lexical analyser: just converts a string into a stream of * tokens. */ #include "dbg.h" #include "lex.h" #include "str.h" #include "textio.h" #include "cons.h" /* for cons_errxxx */ #define IS_SPACE(c) ((c) == ' ' || (c) == '\r' || (c) == '\t') #define IS_PRINTABLE(c) ((c) >= ' ' && (c) <= 0xff) #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_ALPHA(c) || (c) == '_' || IS_DIGIT(c)) #define IS_QUOTE(c) ((c) == '"') /* ----------------------------------------------------- tokdefs --- */ struct tokdef lex_op[] = { { TOK_EQ, "==" }, { TOK_NEQ, "!=" }, { TOK_GE, ">=" }, { TOK_LE, "<=" }, { TOK_LSHIFT, "<<" }, { TOK_RSHIFT, ">>" }, { TOK_AND, "&&" }, { TOK_OR, "||" }, { TOK_RANGE, ".." }, { TOK_ASSIGN, "=" }, { 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_BITAND, "&" }, { TOK_BITOR, "|" }, { TOK_BITXOR, "^" }, { TOK_TILDE, "~" }, { TOK_EXCLAM, "!" }, { TOK_GT, ">" }, { TOK_LT, "<" }, { TOK_AT, "@" }, { TOK_DOLLAR, "$" }, { TOK_DOT, "." }, { 0, NULL } }; struct tokdef lex_kw[] = { { TOK_IF, "if" }, { TOK_ELSE, "else" }, { TOK_PROC, "proc" }, { TOK_LET, "let" }, { TOK_RETURN, "return" }, { TOK_EXIT, "exit" }, { TOK_FOR, "for" }, { TOK_IN, "in" }, { TOK_NIL, "nil" }, { 0, NULL } }; unsigned lex_init(struct lex *o, char *filename) { o->lookchar = -1; o->in = textin_new(filename); if (!o->in) { return 0; } return 1; } void lex_done(struct lex *o) { textin_delete(o->in); } unsigned lex_getchar(struct lex *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 lex_ungetchar(struct lex *o, int c) { if (o->lookchar >= 0) { dbg_puts("lex_ungetchar: lookchar already set\n"); dbg_panic(); } o->lookchar = c; } void lex_err(struct lex *o, char *msg) { cons_erruu(o->line + 1, o->col + 1, msg); } void lex_recover(struct lex *o, char *msg) { int c; lex_err(o, msg); for (;;) { if (!lex_getchar(o, &c)) { return; } if (c == '\n') { break; } if (c == CHAR_EOF) { lex_ungetchar(o, c); break; } } } /* * convert string to number in any base between 2 and 36 */ unsigned lex_str2long(struct lex *o, unsigned base) { char *p; unsigned digit; unsigned long hi, lo; #define BITS (8 * sizeof(unsigned long) / 2) #define LOWORD(a) ((a) & ((1L << BITS) - 1)) #define HIWORD(a) ((a) >> BITS) o->longval = 0; for (p = o->strval; *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 >= base) { lex_recover(o, "not allowed digit in numeric constant"); return 0; } lo = digit + base * LOWORD(o->longval); hi = base * HIWORD(o->longval); if (HIWORD(hi + HIWORD(lo)) != 0) { lex_recover(o, "overflow in numeric constant"); return 0; } o->longval = (hi << BITS) + lo; } #undef BITS #undef LOWORD #undef HIWORD return 1; } unsigned lex_scan(struct lex *o) { int c, cn; unsigned i, base; for (;;) { if (!lex_getchar(o, &c)) { return 0; } /* check for end of buf */ if (c == CHAR_EOF) { o->id = TOK_EOF; return 1; } /* strip spaces */ if (IS_SPACE(c)) { continue; } /* check if line continues */ if (c == '\\') { do { if (!lex_getchar(o, &c)) { return 0; } } while (IS_SPACE(c)); if (c == '\n') { continue; } else if (c == CHAR_EOF) { lex_ungetchar(o, c); continue; } lex_ungetchar(o, c); lex_recover(o, "newline exected after '\\'"); return 0; } /* check for newline */ if (c == '\n') { o->id = TOK_ENDLINE; return 1; } /* strip comments */ if (c == '#') { do { if (!lex_getchar(o, &c)) { return 0; } } while (c != '\n' && c != '\\' && c != CHAR_EOF); lex_ungetchar(o, c); continue; } /* check for string */ if (IS_QUOTE(c)) { i = 0; for (;;) { if (!lex_getchar(o, &c)) { return 0; } /* * dont strip \n embedded in strings in order * not to break error recovering code in the parsers */ if (!IS_PRINTABLE(c)) { lex_ungetchar(o, c); lex_recover(o, "non printable char in string constant"); return 0; } if (IS_QUOTE(c)) { o->strval[i++] = '\0'; break; } if (i >= STRING_MAXLEN) { lex_recover(o, "string constant too long"); return 0; } o->strval[i++] = c; } o->id = TOK_STRING; return 1; } /* check for numeric */ if (IS_DIGIT(c)) { base = 10; if (c == '0') { if (!lex_getchar(o, &c)) { return 0; } if (c == 'x' || c == 'X') { base = 16; if (!lex_getchar(o, &c)) { return 0; } if (!IS_DIGIT(c) && !IS_ALPHA(c)) { lex_ungetchar(o, c); lex_recover(o, "bad hex constant"); return 0; } } } i = 0; for (;;) { if (!IS_DIGIT(c) && !IS_ALPHA(c)) { o->strval[i++] = '\0'; lex_ungetchar(o, c); break; } o->strval[i++] = c; if (i >= TOK_MAXLEN) { lex_recover(o, "numeric constant too long"); return 0; } if (!lex_getchar(o, &c)) { return 0; } } if (!lex_str2long(o, base)) { return 0; } o->id = TOK_NUM; return 1; } /* check for identifier or keyword */ if (IS_IDFIRST(c)) { i = 0; for (;;) { if (i >= IDENT_MAXLEN) { lex_recover(o, "identifier too long"); return 0; } o->strval[i++] = c; if (!lex_getchar(o, &c)) { return 0; } if (!IS_IDNEXT(c)) { o->strval[i++] = '\0'; lex_ungetchar(o, c); break; } } for (i = 0; lex_kw[i].id != 0; i++) { if (str_eq(lex_kw[i].str, o->strval)) { o->id = lex_kw[i].id; return 1; } } o->id = TOK_IDENT; return 1; } /* check for operators */ if (!lex_getchar(o, &cn)) { return 0; } for (i = 0; lex_op[i].id != 0; i++) { if (lex_op[i].str[0] == c) { if (lex_op[i].str[1] == 0) { lex_ungetchar(o, cn); o->id = lex_op[i].id; return 1; } else if (lex_op[i].str[1] == cn) { o->id = lex_op[i].id; return 1; } } } lex_ungetchar(o, cn); lex_recover(o, "bad token"); return 0; } /* not reached */ } void lex_dbg(struct lex *o) { struct tokdef *t; if (o->id == 0) { dbg_puts("NULL"); return; } else if (o->id == TOK_ENDLINE) { dbg_puts("\\n"); return; } for (t = lex_op; t->id != 0; t++) { if (t->id == o->id) { dbg_puts(t->str); return; } } for (t = lex_kw; t->id != 0; t++) { if (t->id == o->id) { dbg_puts(t->str); return; } } switch(o->id) { case TOK_IDENT: dbg_puts("IDENT{"); dbg_puts(o->strval); dbg_puts("}"); break; case TOK_NUM: dbg_puts("NUM{"); dbg_putu(o->longval); dbg_puts("}"); break; case TOK_STRING: dbg_puts("STRING{\""); dbg_puts(o->strval); dbg_puts("\"}"); break; default: dbg_puts("UNKNOWN"); break; } } midish-1.0.4/lex.h010066400017510001751000000053561143320305700124520ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_LEX_H #define MIDISH_LEX_H #define IDENT_MAXLEN 30 #define STRING_MAXLEN 1024 #define TOK_MAXLEN STRING_MAXLEN enum SYM_ID { TOK_EOF = 0, 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_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, /* data */ TOK_IDENT, TOK_NUM, TOK_STRING }; struct tokdef { unsigned id; /* token id */ char *str; /* corresponding string */ }; struct lex { unsigned id; 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 lex_init(struct lex *, char *); void lex_done(struct lex *); unsigned lex_scan(struct lex *); void lex_dbg(struct lex *); unsigned lex_getchar(struct lex *, int *); void lex_ungetchar(struct lex *, int); void lex_err(struct lex *, char *); void lex_recover(struct lex *, char *); unsigned lex_str2long(struct lex *, unsigned); #endif /* MIDISH_LEX_H */ midish-1.0.4/main.c010066400017510001751000000050221143320305700125670ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "dbg.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; } cons_init(); 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); mididev_listinit(); exitcode = user_mainloop(); mididev_listdone(); seqptr_pool_done(); sysex_pool_done(); chunk_pool_done(); state_pool_done(); seqev_pool_done(); evctl_done(); textio_done(); cons_done(); mem_stats(); return exitcode ? 0 : 1; } midish-1.0.4/manual.html010066400017510001751000003224741136431051100136540ustar00alexalex Midish 1.0 - User's manual

Midish 1.0
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 2-clause 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" MIDI support. Works on OpenBSD (its development platform) and Linux.
  • A ``C'' complier (gcc), the ``make'' utility, and other common unix tools
  • The readline library (which depends on the ncurses library on most systems)
  • The ALSA library (on Linux only).
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 names are ``libreadline-dev'' and ``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'', this will build midish and rmidish, the readline(3) front-end to midish.
  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 in your shell:
    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 "aucat:0" rw
    
    see next section for details.

1.3 Invocation

Midish is a MIDI sequencer/filter driven by a command-line interpreter (like a shell). Once midish started, the interpreter prompts for commands. Then, 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. To start midish, just type:
rmidish
Then, commands are issued interactively on the midish prompt, example:
print "hello world"

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.
  • Input channels:
    they represent ``{device midi-channel}'' pair on which MIDI events arrive, like a MIDI keyboard, a MIDI control surface etc...
  • Output channels:
    they represent ``{device midi-channel}'' pair on which MIDI events can be sent, like synths. Output channel objects 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 channel and can contain events from any 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 channel 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 (input channel of the keyboard) to device 0, channel 5 (output channel of the sound module). 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"                     # 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.sng"         # 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.

3 Input channels

Since midish handles multiple input sources, instead of using MIDI channels, it uses device / MIDI channel pairs to represent inputs. So in this document input channel refers the ``{device MIDI-channel}'' pair on which MIDI events are incoming. Channels are handled by two-item lists, like this:
{1 0}                   # device 1, MIDI channel 0
Channels can also be named using the ``inew'' command, as follows:
inew keyboard {1 0}
this defines a named input channel ``keyboard'' that can be used instead of the ``{1 0}'' pair.

Note: input channels are used as default values by certain functions, so it's better to register inputs using the ``inew'' command. The easier is to add a ``inew'' statement in your ``$HOME/.midishrc'' file, to avoid typing it every time midish is started.

4 Output channels

As for input channels, midish uses device / MIDI channel pairs to represent outputs. So in this document output channel refers the ``{device MIDI-channel}'' pair to which MIDI events will be sent. Output channels are handled by two-item lists, like this:
{0 4}                   # device 0, MIDI channel 4
they can also be named using the ``onew'' command, as follows:
onew mybass {0 4}
this defines a named channel ``mybass'' that can be used instead of the ``{0 4}'' pair.

Output channels expose a built-in filter with the same name; it defines how input events are routed to the output channel. The filter comes with a default rule that routes all input channels to the given output channel. See filter section for further details.

4.1 Channel configuration

An output channel represents one musical instrument; midish allows basic channel configuration events (like program changes and controllers) to be attached to the channel definition. Such events are sent to the output when performance mode is entered, for instance just before playback is started. This approach avoids flooding MIDI devices with slow events (like program changes).

For instance, to select patch 34 on channel ``mybass'', attach a ``program change'' event:

oaddev mybass {pc mybass 32}
the list argument gives the event to attach to the channel. See the event section for more details about events.

To set the volume (controller 7) of this channel to 120:

oaddev mybass {ctl mybass 7 120}
If several events of the same type are attached then just the last one is kept. So, the following will change the volume to 125 by replacing the above event:
oaddev mybass {ctl mybass 7 125}

5 Events and event sets specification

5.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.
  • a channel
  • 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 off
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} 34 1234}
set controller number 7 to 99 on device/channel drums:
{ctl drums 7 99}

5.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 channel set. If a name is given, its interpreted as an input or output created with inew or onew. If a 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 can 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 channel "bass"
{ 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 channel "bass"
{ ctl bass 7 }          # match controller 7 on channel "bass"
{ bend {} }             # match bender
{ xctl {} 19 }          # match 14-bit controller number 19
{ xpc  {} 21 1234 }     # match patch 21 on bank 1234
{ nrpn {} 21 }          # match NRPN 21 change

6 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 a device/channel to another
  • map a controller to another
  • split the keyboard
  • drop unwanted events
  • etc...
Multiple filters can 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 can be added, modified and removed. The filtering rules can be listed with the finfo function. All rules can be removed with the freset function. See filtering functions section for details.

6.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 midi-channel 3 are sent to device 2, midi-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 channel ``{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 channel ``{0 0}'' the following rule might be used:

ctl {0 0} 7 -> none

Rules are created and delete 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
}

6.2 Rules hierarchy

In real life, the filter will probably have to 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 very useful because it allows 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 channel ``kbd'' (the input) to channel ``piano'' (the 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 fmap and funmap functions will always sort them.

That last point to retain is that 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.

6.3 Examples

6.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

6.3.2 Channel maps

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.

6.3.3 Controller maps

The following example add a new rule to the above filter that maps the modulation wheel (controller 1) of the source channel (i.e. device 1, midi channel 0) to the expression controller (number 11) of the destination channel (device 0, midi channel 9).
fmap {ctl {1 0} 1} {ctl {0 9} 11}
Rules of the filter can be listed with the finfo functions, which should display the list of rules as follows:
{
        evmap any {1 0} > any {0 9}
        evmap xctl {1 0} 1 > xctl {0 9} 11
}

6.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 channel of the synth). The second rule transposes anything the filter generated on channel ``{0 2}''. Note that the order of the rules is not important, the ``ftransp'' rule always applies to events outgoing from the filter.

6.3.5 Keyboard splits

In the same way it is possible to create a keyboard-split with two key-rules and two channel-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}

Defining filters seems quite tedious, however it's possible to define procedures that do the same in a very simpler way. See the interpreter language for more details.

7 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.

7.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.

7.2 Metronome

In order to "hear" time, a metronome can be used. It has three modes:
  • off - metronome is disabled
  • on - metronome is enabled
  • rec - metronome is enabled only for recording (default)
The ``m'' command can be used to change the mode of the metronome:
m on            # switch the metronome on
p               # start playback
s               # stop
The metronome has two kind of click-sound:
  • 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.

7.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.

7.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

8 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; a track can contain MIDI data from any device/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.

8.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).

8.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.

8.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.

8.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

8.5 Track quantization

A track can be quantized by rounding note-positions to the nearest exact position. The following will quantize the current selection of the current track by rounding notes to the nearest quarter note.
setq 4          # quarter note = 1/4-th of a whole
tquant 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 to regular especially on acoustic patches.

8.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.

9 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.

9.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.

9.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.

9.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
}

9.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.

10 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}

11 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 channel
oinfo           # list config events of current output channel
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 track list
print [ilist]   # print named input channel list
print [olist]   # print named output channel list
print [flist]   # print filter list
print [xlist]   # print sysex back 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 channel definition can 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.

12 Saving and loading songs

A song can be saved into a file as follows:
save "myfile"
In a similar way, the song can be load from a file as follows:
load "myfile"

All channel definitions, 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.

13 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 channel definition is exported as a track containing the channel 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"

14 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.

14.1 Global variables

Variables can be used to store numbers, strings and references to tracks, channels 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.

14.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 ("$").

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.

15 Changes

15.1 Changes from release 0.1 to release 0.2

  • added ``device'' parameter to ``sendraw''
  • removed ``filtchangein'' and added: filtchgich, filtchgidev, filtswapich, filtswapidev, filtchgoch, filtchgodev, filtswapoch, filtswapodev.
  • added new filter rules: filtdevdrop, filtchandrop, filtctldrop, filtkeydrop.
  • added functions that remove existing rules: filtnodevdrop, filtnodevmap, filtnochandrop, filtnochanmap, filtnoctldrop, filtnoctlmap, filtnokeydrop, filtnokeymap.
  • added support for external MIDI synchronization, added: devsetmaster, devgetmaster, devsendrt
  • added trackdelete, chandelete, filtdelete
  • added trackrename, chanrename, filtrename
  • split ``changetnum'' into ``changetdev'' and ``changetch''
  • added ``chanset''
  • added event ranges, and an event range argument to ``trackblank'' and ``trackcopy''
  • removed support for single number channels.
  • added ``tracksetmute'' and ``trackgetmute''
  • added support for system exclusive messages; added sysexlist, sysexnew, sysexdelete, sysexrename, sysexexists, sysexinfo, sysexclear, sysexsetunit, sysexadd, songgetcursysex, songsetcursysex.
  • removed ``tracksave'' and ``trackload''
  • removed support for gnu readline(3) library from midish, and created the ``rmidish'' utility, a front-end to midish using the readline(3) library.
  • added filters the ability to limit MIDI traffic (one controller, one bender, one aftertouch per tick)
  • make the filter drop duplicate controllers, pitch bends and aftertouches
  • added global and per filter current channel: songgetcurchan, songsetcurchan, filtgetcurchan, filtsetcurchan.
  • added global and per channel current input: songgetcurinput, songsetcurinput, changetcurinput, chansetcurinput.
  • added ``trackchanlist'' and improved ``songinfo''
  • added a MIDI file player and recorder: smfplay(1) and smfrec(1)

15.2 Changes from release 0.2 to release 0.3

  • added ``tracktransp'' to transpose tracks
  • added ``trackmerge'' to merge two tracks
  • make rmidish run midish from the same directory where rmidish is located.
  • now multiple bender events are gathered together. So track editing functions (``trackcopy'' & friends) always leave the bender in a neutral state.
  • improved ``trackcheck''
  • added ``exit'' keyword to exit midish
  • Added a ``mode'' argument to ``devattach'' function. Now devices can be used in read-only or write-only mode (allowing to use named pipes as MIDI devices).
  • added basic support for the MIDI "Active Sensing" feature
  • make metronome click audible with non-percussive patches
  • save metronome settings
  • added tempo factor: ``songsetfactor'' and ``songgetfactor''
  • changed all track functions to gracefully handle nested notes, overlapping controllers/benders.
  • when entering real-time mode, midish restores states of all controllers/benders corresponding to the current position
  • when leaving real-time mode midish release only notes that are sounding and reset only controllers that are touched instead of resetting all notes/controllers. This allows midish to better share MIDI devices with other sequencers.
  • added support for 14-bit controllers
  • added support for RPN/NRPN MIDI events.
  • split format 0 MIDI files into 16 tracks (one per MIDI channel)
  • made controller interpretation in editing functions configurable: ctlconf, ctlconfx, ctlunconf, ctlinfo.
  • added per-device controller precision selection (7-bit or 14-bit): devixctl, devoxctl.
  • added ``chanunconfev'' function to remove an event from a channel

15.3 Changes from release 0.3 to release 0.4

  • added output MIDI merger, to avoid conflicts between tracks and MIDI input
  • simplifications: removed current input and filter's current channel attributes.
  • continuous controllers are merged more intelligently in performance mode, avoiding clipping
  • changed all functions to use internal context instead of taking dozens of parameters, while we're at it give them shorter names. Old functions are deprecated, they are still available for compatibility. Below is the list of new functions:
    ci, co, geti, geto, cx, getx, setunit, getunit, goto, getpos, sel, getlen, setq, getq, fac, getfac, ct, gett, cf, getf, mute, unmute, getmute, ls, save, load, reset, export, import, idle, play, rec, stop, tempo, mins, mcut, mdup, minfo, mtempo, msig, mend, ctlconf, ctlconfx, ctlunconf, ctlinfo, metro, metrocf, tlist, tnew, tdel, tren, texists, taddev, tsetf, tgetf, tcheck, tcut, tins, tclr, tpaste, tcopy, tmerge, tquant, ttransp, tclist, tinfo, ilist, olist, iexists, oexists, inew, onew, idel, odel, iren, oren, iset, oset, igetc, ogetc, igetd, ogetd, iaddev, oaddev, irmev, ormev, iinfo, oinfo, flist, fnew, fdel, fren, fexists, finfo, freset, fmap, funmap, ftransp, fvcurve, fchgin, fchgout, fswapin, fswapout, xlist, xexists, xnew, xdel, xren, xinfo, xrm, xsetd, xadd, dnew, ddel, dclkrx, dclktx, dclkrate, dinfo, dixctl, doxctl
  • output channels are no more the only to be named and/or configured. Now input channels can be named too.
  • added ``msig'' and ``mtempo'' functions returning time signature and tempo respectively at the current position
  • added ``err'' function to print an error message and stop the interpreter execution flow.
  • added ``current event set'' in the context and associated ``ev'' function. All track editing functions use it.
  • greatly simplified the filter: more features with less code and concepts. The filter can map any event set to any other set. So all mapping can be created with a single fmap function.
  • added a new ``integer range'' data type, useful to define event sets.
  • added a new metronome mode ``rec'' to turn on the metronome only in record mode.
  • switched to multithreaded performance mode, meaning that the prompt is available during performance mode.
  • added new messages for front ends allowing to get asynchronously the current position. Updated rmidish to use it to display the current position in the prompt.
  • make tcopy copy selection into a hidden temporary track and add tpaste to paste it at the current position.
  • added basic readline completion of built-in functions to rmidish.
  • add ftransp command to transpose output easily.
  • add fvcurve command to adjust note velocity.
  • add mend function to return the end of the song (i.e. the first unused measure).
  • make mins and mcut modify all tracks, not only the time structure.
  • add mdup function to copy portions of the project preserving its time structure.
  • added built-in filter to output channels, so there's no more need to create filter for every channel.

15.4 Changes from release 0.4 to release 1.0

  • removed all legacy functions
  • added tevmap function to transform a set of events of a track into another set (eg. to change the device number).
  • allow midish to synchronize to a MTC source, meaning that it can be used with audio applications. Added dmtcrx function.
  • send MMC start, stop and relocate messages allowing MMC-capable audio applications to be controlled from midish. Added dmmctx function.

16 Project attributes

16.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

16.2 Channel attributes

The following table summarizes the channel attributes:

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

16.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

16.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.

16.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.

16.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 channel. 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 channel. 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

17 Language reference

17.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.

17.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
    

17.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}}

17.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
}

18 Function reference

18.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
tquant rate
quantize the current selection of the current track using the current quantization step (see setq function). Note 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.
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.
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 channels 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

18.2 Input functions

inew channelname {dev midichan}
create an new channel named ``channelname'' and assigned the given device and MIDI channel.
iset {dev midichan}
set the device/channel pair of the current channel.
idel
delete current channel.
iren newname
rename the current channel to ``newname''
iexists channelname
return 1 if ``channelname'' is a channel, 0 otherwise
igetc
return the MIDI channel number of the current channel
igetd channelname
return the device number of the current channel
iaddev event
add the event to the configuration of the current channel, it's not used yet.
irmev evspec
remove all events matching ``evspec'' (see event ranges) from the configuration of the current channel
iinfo
print all events on the config of the current channel.

18.3 Output functions

onew channelname {dev midichan}
create an new channel named ``channelname'' and assigned the given device and MIDI channel. Output channels contain a built-in filter having the same name; by defaut it maps all inputs to the newly created output channel.
oset {dev midichan}
set the device/channel pair of the current channel.
odel
delete current channel.
oren newname
rename the current channel to ``newname''
iexists channelname
return 1 if ``channelname'' is a channel, 0 otherwise
ogetc
return the MIDI channel number of the current channel
ogetd channelname
return the device number of the current channel
oaddev event
add the event to the configuration of the current channel, it's not used yet.
ormev evspec
remove all events matching ``evspec'' (see event ranges) from the configuration of the current channel
oinfo
print all events on the config of the current channel.

18.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

18.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.

18.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.

18.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 tquant function 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.
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 channame
set the current (named) input channel. All input channel editing functions will act on it.
geti
return the name of the current input channel or ``nil'' if none
co channame
set the current (named) output channel. All output channel editing functions will act on it.
geto
return the name of the current output channel 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, channels, 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.

18.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).

18.9 Controller 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

18.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
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
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.

19 Using midish in other programs

19.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.

19.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.

20 Sample midishrc-file

The sample midishrc file shipped in the source tar-balls contains a lot of examples of procedure definitions.

ctldrop ictl
make the current filter drop controller number ``ictl'' on the current input
ctlmap ictl octl
make the current filter route controller number ``ictl'' on the current input to controller ``octl'' on the current channel
solo
mute all tracks but current
nomute
unmute all tracks.
gmon devnum
create a new gmon sysex back and generate a ``general midi on'' system exclusive message for device number ``devnum'' and store it to the current sysex bank.
gmp patch
configures the current channel to use general MIDI patch number ``patch''. (this will send program change event when performance mode is entered).
vol value
set volume (controller number 7) of the current channel
reverb value
set reverb (controller number 91) of the current channel
chorus value
set chorus (controller number 93) of the current channel
rpn addr val
set registered parameter number (RPN) ``addr'' for the current channel to ``val''. Both ``addr'' and ``val'' are integers in the range 0..16383.
nrpn addr val
set not registered parameter number (NRPN) ``addr'' for the current channel to ``val''. Both ``addr'' and ``val'' are integers in the range 0..16383.

21 Example sessions

21.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"
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).

21.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 channel
[0000:00]> onew drums {0 9}                  # create drum channel
[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]> tquant 75                         # quantize to 75%
[0000:00]> p                                 # play
[0001:02]> s                                 # stop playing
[0000:00]> save "myrythm"                    # save to a file
[0000:00]>                                   # hit ^D to quit
first, we set the default input channel to ``{1 0}'' (default channel of the keyboard). Then, we define the ``drum'' channel on 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.

22 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-2010 Alexandre Ratchov
Last updated apr 19, 2010
midish-1.0.4/mdep.c010066400017510001751000000171271143320305700126010ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * machine and OS dependent code */ #include #include #include #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 "dbg.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 CONS_BUFSIZE 1024 #define MAXFDS (DEFAULT_MAXNDEVS + 1) struct timespec ts, ts_last; char cons_buf[CONS_BUFSIZE]; unsigned cons_index, cons_len, cons_eof, cons_quit; struct pollfd *cons_pfd; /* * handler for SIGALRM, invoked periodically */ void mdep_sigalrm(int i) { /* nothing to do, we only want poll() to return EINTR */ } /* * 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)) { perror("mux_mdep_open: sigprocmask"); exit(1); } if (clock_gettime(CLOCK_MONOTONIC, &ts_last) < 0) { 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) { 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) { 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) { 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(void) { int res, revents; nfds_t nfds; struct pollfd *pfd, pfds[MAXFDS]; struct mididev *dev; static unsigned char midibuf[MIDI_BUFSIZE]; long delta_nsec; nfds = 0; if (cons_index == cons_len && !cons_eof) { cons_pfd = &pfds[nfds]; cons_pfd->fd = STDIN_FILENO; cons_pfd->events = POLLIN | POLLHUP; nfds++; } else cons_pfd = NULL; 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; } if (cons_quit) { fprintf(stderr, "\n--interrupt--\n"); cons_quit = 0; return 0; } res = poll(pfds, nfds, -1); if (res < 0 && errno != EINTR) { 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) { perror("mux_mdep_wait: clock_gettime"); dbg_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 = 1000000000L * (ts.tv_sec - ts_last.tv_sec); delta_nsec += ts.tv_nsec - ts_last.tv_nsec; if (delta_nsec > 0) { ts_last = ts; /* * update the current position, * (time unit = 24th of microsecond */ mux_timercb(24 * delta_nsec / 1000); } } dbg_flush(); if (cons_pfd && (cons_pfd->revents & (POLLIN | POLLHUP))) { res = read(STDIN_FILENO, cons_buf, CONS_BUFSIZE); if (res < 0) { perror("stdin: read"); cons_eof = 1; return 1; } if (res == 0) { cons_eof = 1; } cons_len = res; cons_index = 0; } 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; struct timespec ts, remain; ts.tv_sec = millisecs / 1000; ts.tv_nsec = (millisecs % 1000) * 1000000; while (ts.tv_sec > 0 || ts.tv_nsec > 0) { res = nanosleep(&ts, &remain); if (res >= 0) break; if (errno != EINTR) { perror("mux_sleep: poll"); exit(1); } ts = remain; } if (clock_gettime(CLOCK_MONOTONIC, &ts_last) < 0) { perror("mux_sleep: clock_gettime"); exit(1); } } void cons_mdep_sigint(int s) { if (cons_quit) _exit(1); cons_quit = 1; } void cons_mdep_init(void) { struct sigaction sa; cons_index = 0; cons_len = 0; 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) { perror("cons_mdep_init: sigaction"); exit(1); } } void cons_mdep_done(void) { struct sigaction sa; sigfillset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; if (sigaction(SIGINT, &sa, NULL) < 0) { perror("cons_mdep_done: sigaction"); exit(1); } } int cons_mdep_getc(void) { int quit = 0; while (cons_index == cons_len && !cons_eof) { if (!mux_mdep_wait()) { quit = 1; break; } } return (cons_eof || quit) ? EOF : (cons_buf[cons_index++] & 0xff); } /* * 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; } midish-1.0.4/smf.c010066400017510001751000000456671143320306000124450ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "dbg.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) { dbg_puts("smf_put32: bad chunk length\n"); dbg_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) { dbg_puts("smf_put24: bad chunk length\n"); dbg_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) { dbg_puts("smf_put16: bad chunk length\n"); dbg_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) { dbg_puts("smf_putc: bad chunk length\n"); dbg_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) { dbg_puts("smf_putvar: bad chunk length\n"); dbg_panic(); } o->index += index; fwrite(buf, 1, index, o->file); } return; } } dbg_puts("smf_putvar: bogus value\n"); dbg_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, ~1U, &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: dbg_puts("smf_puttrack: bad time signature\n"); dbg_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_OUT(o, i) { 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_OUT(o, i) { 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, abspos; unsigned tempo, num, den, dummy; struct statelist slist; struct songsx *songsx; struct seqev *pos, *se; struct sysex *sx; struct ev ev, rev; unsigned c; if (!smf_getheader(o, smftype_track)) { return 0; } status = 0; abspos = 0; track_clear(&t->track); pos = t->track.first; songsx = (struct songsx *)s->sxlist; /* first (and unique) sysex in song */ statelist_init(&slist); for (;;) { if (o->index >= o->length) { statelist_done(&slist); return 1; } if (!smf_getvar(o, &delta)) { goto err; } abspos += delta; 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; } /* dbg_puts("cc="); dbg_putu(dummy); dbg_puts(", dd="); */ if (!smf_getc(o, &dummy)) { goto err; } /* dbg_putu(dummy); dbg_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_dbg(sx); dbg_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; } if (conv_packev(&slist, 0U, &ev, &rev)) { se = seqev_new(); se->ev = rev; seqev_ins(pos, se); } /* dbg_puts("ev: "); dbg_putu(abspos); dbg_puts(" "); ev_dbg(&ev); dbg_puts("\n"); */ } 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 songsx *sx; 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 (!smf_get16(&f, &timecode)) { goto bad2; } if ((timecode & 0x8000) != 0) { cons_err("SMPTE timecode is not supported"); goto bad2; } o = song_new(); song_setunit(o, timecode * 4); /* timecode = tics per quarter */ sx = song_sxnew(o, "sx"); 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; } midish-1.0.4/mdep_alsa.c010066400017510001751000000213001143320305700135650ustar00alexalex/* * Copyright (c) 2008 Willem van Engen * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef USE_ALSA #include #include #include #include #include #include #include #include #include #include "dbg.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 = mem_alloc(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); mem_free(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) { dbg_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) { dbg_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) { dbg_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; dbg_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; dbg_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) { dbg_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) { dbg_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) { dbg_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) { dbg_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) { dbg_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) { dbg_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) { dbg_puts("alsa_revents: no handle\n"); return 0; } if (snd_seq_poll_descriptors_revents(dev->seq_handle, pfd, dev->nfds, &revents) < 0) { dbg_puts("alsa_revents: snd_..._revents() failed\n"); return 0; } return revents; } #endif midish-1.0.4/mdep_raw.c010066400017510001751000000101451143320305700134430ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "dbg.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 = mem_alloc(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); mem_free(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 { dbg_puts("raw_open: not allowed mode\n"); dbg_panic(); mode = 0; } dev->fd = open(dev->path, mode, 0666); if (dev->fd < 0) { 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) { 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) { 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; return 1; } int raw_revents(struct mididev *addr, struct pollfd *pfd) { return pfd->revents; } midish-1.0.4/mdep_sndio.c010066400017510001751000000105101143320305700137620ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef USE_SNDIO #include #include #include "dbg.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 = mem_alloc(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); mem_free(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) { dbg_puts("sndio_open: "); dbg_puts(dev->path); dbg_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)) { dbg_puts("sndio_read: "); dbg_puts(dev->path); dbg_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) { dbg_puts("sndio_write: "); dbg_puts(dev->path); dbg_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.0.4/metro.c010066400017510001751000000076641143320305700130070ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "dbg.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) { dbg_puts("metro_tocb: no click sounding\n"); dbg_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) { dbg_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.0.4/metro.h010066400017510001751000000040761143320305700130060ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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.0.4/mididev.c010066400017510001751000000353031143320305700132710ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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" #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) { dbg_putu(id); dbg_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) dbg_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; if (mtc->state == MTC_STOP) return; if (data >> 4 != mtc->qfr) { if (mididev_debug) dbg_puts("mtc sync err\n"); return; } if (mtc->state == MTC_RUN) { mtc->pos += mtc->tps; 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->pos) { dbg_puts("mtc_tick: went off by "); dbg_puti((int)pos - (int)mtc->pos); dbg_puts(" ticks\n"); if (pos > mtc->pos && pos < mtc->pos + MTC_SEC / 6) { mux_mtctick(pos - mtc->pos); 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) { dbg_puts("mtc_full: start at "); dbg_putu(mtc->pos); dbg_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->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) { o->ops->close(o); if (o->oused) { /* * XXX: should we flush instead of printing error ? */ dbg_puts("mididev_close: device not flushed\n"); } 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) { dbg_puts("mididev_flush: "); dbg_putu(timo_abstime / 24); dbg_puts(": dev "); dbg_putu(o->unit); dbg_puts(":"); for (i = 0; i < o->oused; i++) { dbg_puts(" "); dbg_putx(o->obuf[i]); } dbg_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)) { dbg_puts("received data from output only device\n"); return; } if (mididev_debug) { dbg_puts("mididev_inputcb: "); dbg_putu(timo_abstime / 24); dbg_puts(": dev "); dbg_putu(o->unit); dbg_puts(":"); for (i = 0; i < count; i++) { dbg_puts(" "); dbg_putx(buf[i]); } dbg_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) { dbg_puts("mididev_inputcb: "); dbg_putx(data); dbg_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 */ dbg_puts("mididev_inputcb: "); dbg_putx(o->istatus); dbg_puts(": skipped aborted message\n"); } o->istatus = data; o->icount = 0; switch(data) { case MIDI_SYSEXSTART: if (o->isysex) { if (mididev_debug) dbg_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) dbg_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 s; 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); } } 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; } } dbg_puts("mididev_detach: the device is not in the list\n"); dbg_panic(); return 0; } midish-1.0.4/mididev.h010066400017510001751000000130551143320305700132760ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 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.0.4/midish.1010066400017510001751000000060431143320305700130420ustar00alexalex.\" .\" Copyright (c) 2003-2010 Alexandre Ratchov .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" .\" - Redistributions of source code must retain the above .\" copyright notice, this list of conditions and the .\" following disclaimer. .\" .\" - Redistributions in binary form must reproduce the above .\" copyright notice, this list of conditions and the .\" following disclaimer in the documentation and/or other .\" materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS .\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT .\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR .\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT .\" OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, .\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT .\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .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 (users wanting to use midish interactively may consider using the .Xr rmidish 1 utility). Here are some of its features: .Pp .Bl -bullet -compact .It Multiple MIDI devices handling .It Synchronisation to external MIDI devices .It Filtering/routing (controller mapping, keyboard splitting, ...) .It Track recording, editing, quatisation .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 "-b " .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 additionnal info before each line of input, useful to front-ends and for dubugging. .El .Pp Once midish started, the interpreter processes the .Pa "$HOME/.midishrc" file (or .Pa "/etc/midishrc" if the later dosn'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) .It Pa "/dev/rmidiN" .Xr midi 4 devices used by .Nm .El .Sh SEE ALSO .Xr rmidish 1 , .Xr smfplay 1 , .Xr midiplay 1 , .Xr midi 4 .br User's manual and tutorial .br http://caoua.org/midish/ midish-1.0.4/midishrc010066400017510001751000000033141117012327500132270ustar00alexalex# # midi devices config # #dnew 0 "/dev/rmidi4" rw #dnew 1 "/dev/rmidi3" rw # # # # some filter rules # proc ctldrop ctlno { for i in [ilist] { fmap {ctl $i $ctlno} {none} } } 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] } # # turn on general midi (send sysex) # proc gmon devnum { xnew gmon xadd $devnum { 0xF0 0x7E 0x7F 0x09 0x01 0xF7 } } # # configure an instrument for a given general midi patch # proc gmp patch { oaddev { pc [geto] ($patch - 1) } } # # XV-2020 specific macros # # configures a instrument with the given bank/patch or bank rhythm # bank 1,2,3,4 corresponds to preset A,B,C,D # patches/rhythmes are counted from 1 to 128 # proc xvp bank patch { oaddev { xpc [geto] ($patch - 1) (87 * 128 + (64 + $bank - 1)) } } proc xvr bank patch { oaddev { xpc [geto] ($patch - 1) (86 * 128 + (64 + $bank - 1)) } } # # 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 } } # # controllers config # proc vol val { oaddev {ctl [geto] 7 $val}; } proc bal val { oaddev {ctl [geto] 8 $val}; } proc pan val { oaddev {ctl [geto] 10 $val}; } proc reverb val { oaddev {ctl [geto] 91 $val}; } proc chorus val { oaddev {ctl [geto] 93 $val}; } # # set RPN/NRPN to the given value for the current chan # proc rpn addr val { oaddev {rpn [geto] $addr $val} } proc nrpn addr val { oaddev {nrpn [geto] $addr $val} } midish-1.0.4/mixout.c010066400017510001751000000100721143320305700131710ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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) { dbg_puts("mixout_start()\n"); } } void mixout_stop(void) { if (mixout_debug) { dbg_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; os = statelist_lookup(&mixout_slist, ev); if (os != NULL && os->tag != id) { if (os->tag < id) { if (mixout_debug) { dbg_puts("mixout_putev: ["); dbg_putu(id); dbg_puts("] "); ev_dbg(ev); dbg_puts(" ignored\n"); } return; } if (state_cancel(os, &ca)) { if (mixout_debug) { dbg_puts("mixout_putev: ["); dbg_putu(os->tag); dbg_puts("] "); ev_dbg(ev); dbg_puts(" kicked\n"); } os = statelist_update(&mixout_slist, &ca); mux_putev(&ca); } if (mixout_debug) { dbg_puts("mixout_putev: "); ev_dbg(ev); dbg_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); } 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) { dbg_puts("mixout_timo: "); state_dbg(i); dbg_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.0.4/mixout.h010066400017510001751000000031651143320305700132030ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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.0.4/mux.c010066400017510001751000000374101143320305700124620ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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; void *mux_addr; #ifdef MUX_PROF struct prof mux_prof; #endif struct statelist mux_istate, mux_ostate; void mux_sendstop(void); /* * the following are defined in mdep.c */ void mux_mdep_open(void); void mux_mdep_close(void); void mux_dbgphase(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; #ifdef MUX_PROF prof_reset(&mux_prof, "mux/ms"); #endif dbg_sync = 1; } /* * release all structures and close midi devices */ void mux_close(void) { struct mididev *i; dbg_sync = 1; #ifdef MUX_PROF prof_dbg(&mux_prof); #endif 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_dbgphase(unsigned phase) { switch(phase) { case MUX_STARTWAIT: dbg_puts("STARTWAIT"); break; case MUX_START: dbg_puts("START"); break; case MUX_FIRST: dbg_puts("FIRST"); break; case MUX_NEXT: dbg_puts("NEXT"); break; case MUX_STOP: dbg_puts("STOP"); break; default: dbg_puts("unknown"); break; } } #endif /* * change the current phase */ void mux_chgphase(unsigned phase) { #ifdef MUX_DEBUG dbg_puts("mux_phase: "); mux_dbgphase(mux_phase); dbg_puts(" -> "); mux_dbgphase(phase); dbg_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 spirious 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) { dbg_puts("mux_putev: "); ev_dbg(ev); dbg_puts("\n"); } #endif if (!EV_ISVOICE(ev)) { dbg_puts("mux_putev: "); ev_dbg(ev); dbg_puts(": only voice events allowed\n"); dbg_panic(); } unit = ev->dev; if (unit >= DEFAULT_MAXNDEVS) { dbg_puts("mux_putev: "); ev_dbg(ev); dbg_puts(": bogus unit number\n"); dbg_panic(); } dev = mididev_byunit[unit]; if (dev != NULL) { nev = conv_unpackev(&mux_ostate, dev->oxctlset, 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) { /* * not using MTC, do nothing */ if (mididev_clksrc) return; /* * 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) dbg_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) dbg_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_nextpos = mux_ticlength; mux_curpos = song_gotocb(usong, mtcpos); if (mux_curpos >= mux_ticlength) dbg_puts("mux_mtcstart: offset larger than 1 tick\n"); } /* * generate clock start */ if (mux_debug) dbg_puts("mux_mtcstart: generated clk start\n"); mux_sendstart(); mux_startcb(); mux_flush(); } /* * called periodically by the MTC timer */ void mux_mtctick(unsigned delta) { /* * if using external clock, ignore MTC */ if (mididev_clksrc) return; mux_curpos += delta; while (mux_curpos >= mux_nextpos) { mux_curpos -= mux_nextpos; mux_nextpos = mux_ticlength; mux_sendtic(); mux_ticcb(); mux_flush(); } } /* * 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) dbg_puts("mux_mtcstop: generated stop\n"); mux_sendstop(); mux_stopcb(); mux_flush(); } } /* * 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; #ifdef MUX_PROF prof_val(&mux_prof, delta / (24 * 10)); #endif /* * 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: dbg_puts("mux_timercb: startwait: bad state\n"); dbg_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_chgphase(MUX_FIRST); } if (mux_phase == MUX_NEXT) { mux_curtic++; song_movecb(usong); } else if (mux_phase == MUX_FIRST) { mux_curtic = 0; 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) dbg_puts("mux_startcb: got start event\n"); if (mux_phase != MUX_STARTWAIT) { dbg_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, 0); } mux_chgphase(MUX_START); } /* * called when a MIDI STOP event is received from an external device */ void mux_stopcb(void) { if (mux_debug) dbg_puts("mux_stopcb: got stop\n"); mux_chgphase(mux_reqphase); song_stopcb(usong); } /* * 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) { dbg_puts("mux_evcb: "); ev_dbg(ev); dbg_puts("\n"); } #endif if (conv_packev(&mux_istate, dev->ixctlset, 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 *data; /* * discard real-time messages, that should not be * recorded */ if (sysex->first != NULL && sysex->first->next == NULL && sysex->first->used >= 6) { data = sysex->first->data; if (data[0] == 0xf0 && data[1] == 0x7f && data[3] == 1) { 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(void) { struct mididev *dev; static unsigned char mmc_start[] = { 0xf0, 0x7f, 0x7f, 0x06, 0x02, 0xf7 }; mux_reqphase = MUX_STARTWAIT; if (mux_phase != MUX_STOP) { dbg_puts("bad state to call mux_startreq()\n"); dbg_panic(); } mux_chgphase(MUX_STARTWAIT); if (!mididev_clksrc && !mididev_mtcsrc) { if (mux_debug) dbg_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_START && mux_phase < MUX_STOP) mux_sendstop(); 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) { 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; mmc_reloc[8] = (mmcpos / (60 * MTC_SEC)) % 60; mmc_reloc[9] = (mmcpos / MTC_SEC) % 60; mmc_reloc[10] = (mmcpos / (MTC_SEC / 24)) % 24; mmc_reloc[11] = (mmcpos / (MTC_SEC / 24 / 100)) % 100; 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.0.4/mux.h010066400017510001751000000070451143320305700124700ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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; 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 *, 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(void); void mux_stopreq(void); void mux_gotoreq(unsigned); int mux_mdep_wait(void); /* 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.0.4/name.c010066400017510001751000000066241143320305700125740ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * name is a singly-linked list of strings */ #include "dbg.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 = mem_alloc(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); mem_free(o); } void name_dbg(struct name *o) { for (; o != NULL; o = o->next) { str_dbg(o->str); if (o->next) { dbg_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; } dbg_puts("name_remove: not found\n"); dbg_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.0.4/name.h010066400017510001751000000044061143320305700125750ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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_dbg(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.0.4/node.c010066400017510001751000000356101143320305700125760ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * this module implements the tree containing interpreter code. Each * node of the tree represents one instruction. */ #include "dbg.h" #include "str.h" #include "data.h" #include "node.h" #include "exec.h" #include "cons.h" struct node * node_new(struct node_vmt *vmt, struct data *data) { struct node *o; o = mem_alloc(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); } mem_free(o); } void node_dbg(struct node *o, unsigned depth) { #define NODE_MAXDEPTH 30 static char str[2 * NODE_MAXDEPTH + 1] = ""; struct node *i; dbg_puts(str); dbg_puts(o != NULL && o->next != NULL ? "+-" : "\\-"); if (o == NULL) { dbg_puts("\n"); return; } dbg_puts(o->vmt->name); if (o->data) { dbg_puts("("); data_dbg(o->data); dbg_puts(")"); } dbg_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_dbg(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) { dbg_puts("node_replace: e->list != NULL\n"); dbg_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; } 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; 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; } argv = o->list; for (argn = p->args; argn != NULL; argn = argn->next) { 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 (argv != NULL) { cons_errs(o->data->val.ref, "to many arguments"); goto finish; } 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) { cons_errs(x->procname, "argument to 'for' must be a list"); 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; 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; } } 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.0.4/node.h010066400017510001751000000051041143320305700125760ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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_dbg(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.0.4/norm.c010066400017510001751000000140431143320305700126210ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * norm.c * * a stateful midi normalizer. It's used to normalize/sanitize midi * input * */ #include "dbg.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 */ struct filt *norm_filt = NULL; /* --------------------------------------------------------------------- */ void norm_timocb(void *); /* * set the filter to send events to */ void norm_setfilt(struct filt *f) { norm_shut(); mux_flush(); norm_filt = f; } /* * inject an event */ void norm_putev(struct ev *ev) { struct ev filtout[FILT_MAXNRULES]; unsigned i, nev; if (!EV_ISVOICE(ev)) { return; } if (norm_filt) { nev = filt_do(norm_filt, ev, filtout); } else { filtout[0] = *ev; nev = 1; } for (i = 0; i < nev; i++) mixout_putev(&filtout[i], 0); for (i = 0; i < nev; i++) song_evcb(usong, &filtout[i]); 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) { dbg_puts("norm_start()\n"); } } /* * unconfigure the normalizer */ void norm_stop(void) { struct state *s, *snext; struct ev ca; if (norm_debug) { dbg_puts("norm_stop()\n"); } for (s = norm_slist.first; s != NULL; s = snext) { snext = s->next; if (state_cancel(s, &ca)) { if (norm_debug) { dbg_puts("norm_stop: "); ev_dbg(&s->ev); dbg_puts(": cancelled by: "); ev_dbg(&ca); dbg_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; struct ev ca; for (s = norm_slist.first; s != NULL; s = s->next) { if (!(s->tag & TAG_PASS)) continue; if (state_cancel(s, &ca)) { if (norm_debug) { dbg_puts("norm_shut: "); ev_dbg(&s->ev); dbg_puts(": cancelled by: "); ev_dbg(&ca); dbg_puts("\n"); } norm_putev(&ca); } 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; dbg_puts("norm_kill: "); ev_dbg(&st->ev); dbg_puts(": killed\n"); } } /* * give an event to the normalizer for processing */ void norm_evcb(struct ev *ev) { struct state *st; if (norm_debug) { dbg_puts("norm_run: "); ev_dbg(ev); dbg_puts("\n"); } #ifdef NORM_DEBUG if (o->cb == NULL) { dbg_puts("norm_evcb: cb = NULL, bad initialisation\n"); dbg_panic(); } if (!EV_ISVOICE(ev)) { dbg_puts("norm_evcb: only voice events allowed\n"); dbg_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; st->tag = TAG_PASS; if (st->flags & (STATE_BOGUS | STATE_NESTED)) { if (norm_debug) { dbg_puts("norm_evcb: "); ev_dbg(ev); dbg_puts(": bogus/nested frame\n"); } norm_kill(ev); } } /* * 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.0.4/norm.h010066400017510001751000000033711143320305700126300ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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_setfilt(struct filt *); void norm_putev(struct ev *); void norm_evcb(struct ev *); void norm_timercb(void); extern unsigned norm_debug; #endif /* MIDISH_NORM_H */ midish-1.0.4/parse.c010066400017510001751000000430561143320305700127660ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * * a simple LL(1)-like parser. Uses the following 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 * * expr: or * * call: ident [ expr expr ... expr ] * * stmt: "let" ident "=" expr endl * "for" ident "in" expr slist endl * "if" expr slist [ else slist ] endl * "for" ident "in" expr slist endl * "return" expr endl * call endl * endl * * slist: "{" [ stmt stmt ... stmt ] "}" * * proc: "proc" ident [ ident ident ... ident ] slist * * line: proc * stmt * * prog: [ line line ... line ] EOF */ #include "dbg.h" #include "textio.h" #include "lex.h" #include "data.h" #include "parse.h" #include "node.h" #include "cons.h" /* cons_errxxx */ /* ------------------------------------------------------------- */ void parse_recover(struct parse *o, char *msg) { lex_err(&o->lex, msg); for (;;) { if (o->lex.id == TOK_EOF) { parse_ungetsym(o); break; } if (o->lex.id == TOK_ENDLINE) { break; } if (!parse_getsym(o)) { break; } } } struct parse * parse_new(char *filename) { struct parse *o; o = mem_alloc(sizeof(struct parse), "parse"); if (!lex_init(&o->lex, filename)) { mem_free(o); return 0; } o->lookavail = 0; return o; } void parse_delete(struct parse *o) { lex_done(&o->lex); mem_free(o); } unsigned parse_getsym(struct parse *o) { if (o->lookavail) { o->lookavail = 0; return 1; } return lex_scan(&o->lex); } void parse_ungetsym(struct parse *o) { if (o->lookavail) { dbg_puts("parse_ungetsym: looksym already set\n"); dbg_panic(); } o->lookavail = 1; } unsigned parse_isfirst(struct parse *o, unsigned *first) { while (*first != 0) { if (*first == o->lex.id) { return 1; } first++; } return 0; } /* ------------------------------------------------------------- */ unsigned first_endl[] = { TOK_ENDLINE, TOK_SEMICOLON, 0 }; unsigned first_proc[] = { TOK_PROC, 0 }; unsigned first_assign[] = { TOK_LET, 0 }; unsigned first_call[] = { TOK_IDENT, 0 }; unsigned first_expr[] = { TOK_MINUS, TOK_EXCLAM, TOK_TILDE, TOK_LPAR, TOK_DOLLAR, TOK_LBRACE, TOK_IDENT, TOK_NIL, TOK_NUM, TOK_STRING, TOK_LBRACKET, 0 }; unsigned first_stmt[] = { TOK_SEMICOLON, TOK_ENDLINE, TOK_IF, TOK_FOR, TOK_IDENT, TOK_LET, TOK_RETURN, TOK_EXIT, 0 }; unsigned parse_endl(struct parse *o, struct node **n) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_SEMICOLON && o->lex.id != TOK_ENDLINE) { parse_recover(o, "';' or new line expected"); return 0; } return 1; } unsigned parse_cst(struct parse *o, struct node **n) { struct data *data; if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_LPAR) { if (!parse_getsym(o)) { return 0; } if (!parse_isfirst(o, first_expr)) { parse_recover(o, "expression expected afeter '('"); return 0; } parse_ungetsym(o); if (!parse_expr(o, n)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_RPAR) { parse_recover(o, "missing closing ')'"); return 0; } return 1; } else if (o->lex.id == TOK_NUM) { data = data_newlong((long)o->lex.longval); *n = node_new(&node_vmt_cst, data); return 1; } else if (o->lex.id == TOK_STRING) { data = data_newstring(o->lex.strval); *n = node_new(&node_vmt_cst, data); return 1; } else if (o->lex.id == TOK_IDENT) { data = data_newref(o->lex.strval); *n = node_new(&node_vmt_cst, data); return 1; } else if (o->lex.id == TOK_NIL) { data = data_newnil(); *n = node_new(&node_vmt_cst, data); return 1; } else if (o->lex.id == TOK_DOLLAR) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { parse_recover(o, "identifier expected after '$'"); return 0; } *n = node_new(&node_vmt_var, data_newstring(o->lex.strval)); return 1; } else if (o->lex.id == TOK_LBRACKET) { if (!parse_getsym(o)) { return 0; } if (!parse_isfirst(o, first_call)) { parse_recover(o, "proc call expected after '['"); return 0; } parse_ungetsym(o); if (!parse_call(o, n)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_RBRACKET) { parse_recover(o, "']' expected"); return 0; } return 1; } else if (o->lex.id == TOK_LBRACE) { *n = node_new(&node_vmt_list, NULL); n = &(*n)->list; for (;;) { if (!parse_getsym(o)) { return 0; } if (parse_isfirst(o, first_expr)) { parse_ungetsym(o); if (!parse_expr(o, n)) { return 0; } n = &(*n)->next; } else if (o->lex.id == TOK_RBRACE) { return 1; } else { parse_recover(o, "expression or '}' expected in list"); return 0; } } } parse_recover(o, "bad term in expression"); return 0; } unsigned parse_unary(struct parse *o, struct node **n) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_MINUS) { *n = node_new(&node_vmt_neg, NULL); if (!parse_unary(o, &(*n)->list)) { return 0; } return 1; } else if (o->lex.id == TOK_EXCLAM) { *n = node_new(&node_vmt_not, NULL); if (!parse_unary(o, &(*n)->list)) { return 0; } return 1; } else if (o->lex.id == TOK_TILDE) { *n = node_new(&node_vmt_bitnot, NULL); if (!parse_unary(o, &(*n)->list)) { return 0; } return 1; } else { parse_ungetsym(o); } return parse_cst(o, n); } unsigned parse_muldiv(struct parse *o, struct node **n) { if (!parse_unary(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_STAR) { node_replace(n, node_new(&node_vmt_mul, NULL)); if (!parse_unary(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_SLASH) { node_replace(n, node_new(&node_vmt_div, NULL)); if (!parse_unary(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_PCT) { node_replace(n, node_new(&node_vmt_mod, NULL)); if (!parse_unary(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_addsub(struct parse *o, struct node **n) { if (!parse_muldiv(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_PLUS) { node_replace(n, node_new(&node_vmt_add, NULL)); if (!parse_muldiv(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_MINUS) { node_replace(n, node_new(&node_vmt_sub, NULL)); if (!parse_muldiv(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_shift(struct parse *o, struct node **n) { if (!parse_addsub(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_LSHIFT) { node_replace(n, node_new(&node_vmt_lshift, NULL)); if (!parse_addsub(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_RSHIFT) { node_replace(n, node_new(&node_vmt_rshift, NULL)); if (!parse_addsub(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_compare(struct parse *o, struct node **n) { if (!parse_shift(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_LT) { node_replace(n, node_new(&node_vmt_lt, NULL)); if (!parse_shift(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_LE) { node_replace(n, node_new(&node_vmt_le, NULL)); if (!parse_shift(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_GT) { node_replace(n, node_new(&node_vmt_gt, NULL)); if (!parse_shift(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_GE) { node_replace(n, node_new(&node_vmt_ge, NULL)); if (!parse_shift(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_equal(struct parse *o, struct node **n) { if (!parse_compare(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_EQ) { node_replace(n, node_new(&node_vmt_eq, NULL)); if (!parse_compare(o, &(*n)->list->next)) { return 0; } } else if (o->lex.id == TOK_NEQ) { node_replace(n, node_new(&node_vmt_neq, NULL)); if (!parse_compare(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_bitand(struct parse *o, struct node **n) { if (!parse_equal(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_BITAND) { node_replace(n, node_new(&node_vmt_bitand, NULL)); if (!parse_equal(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_bitxor(struct parse *o, struct node **n) { if (!parse_bitand(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_BITXOR) { node_replace(n, node_new(&node_vmt_bitxor, NULL)); if (!parse_bitand(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_bitor(struct parse *o, struct node **n) { if (!parse_bitxor(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_BITOR) { node_replace(n, node_new(&node_vmt_bitor, NULL)); if (!parse_bitxor(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_and(struct parse *o, struct node **n) { if (!parse_bitor(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_AND) { node_replace(n, node_new(&node_vmt_and, NULL)); if (!parse_bitor(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_or(struct parse *o, struct node **n) { if (!parse_and(o, n)) { return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_OR) { node_replace(n, node_new(&node_vmt_or, NULL)); if (!parse_and(o, &(*n)->list->next)) { return 0; } } else { parse_ungetsym(o); break; } } return 1; } unsigned parse_exprange(struct parse *o, struct node **n) { if (!parse_or(o, n)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_RANGE) { node_replace(n, node_new(&node_vmt_range, NULL)); if (!parse_or(o, &(*n)->list->next)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_RANGE) { parse_recover(o, "too many '..' for range"); return 0; } } parse_ungetsym(o); return 1; } unsigned parse_expr(struct parse *o, struct node **n) { return parse_exprange(o, n); } unsigned parse_call(struct parse *o, struct node **n) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { return 0; } *n = node_new(&node_vmt_call, data_newref(o->lex.strval)); n = &(*n)->list; for (;;) { if (!parse_getsym(o)) { return 0; } if (!parse_isfirst(o, first_expr)) { parse_ungetsym(o); return 1; } parse_ungetsym(o); if (!parse_expr(o, n)) { return 0; } n = &(*n)->next; } /* not reached */ } unsigned parse_stmt(struct parse *o, struct node **n) { if (!parse_getsym(o)) { return 0; } if (parse_isfirst(o, first_call)) { parse_ungetsym(o); *n = node_new(&node_vmt_ignore, NULL); if (!parse_call(o, &(*n)->list)) { return 0; } if (!parse_endl(o, NULL)) { return 0; } return 1; } else if (o->lex.id == TOK_IF) { *n = node_new(&node_vmt_if, NULL); if (!parse_getsym(o)) { return 0; } if (!parse_isfirst(o, first_expr)) { parse_recover(o, "expression expected after 'if'"); return 0; } parse_ungetsym(o); if (!parse_expr(o, &(*n)->list)) { return 0; } if (!parse_slist(o, &(*n)->list->next)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ELSE) { if (!parse_slist(o, &(*n)->list->next->next)) { return 0; } } else { parse_ungetsym(o); } if (!parse_endl(o, NULL)) { return 0; } return 1; } else if (o->lex.id == TOK_FOR) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { parse_recover(o, "identifier expected in 'for' loop"); return 0; } *n = node_new(&node_vmt_for, data_newstring(o->lex.strval)); if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IN) { parse_recover(o, "'in' expected"); return 0; } if (!parse_expr(o, &(*n)->list)) { return 0; } if (!parse_slist(o, &(*n)->list->next)) { return 0; } if (!parse_endl(o, NULL)) { return 0; } return 1; } else if (o->lex.id == TOK_RETURN) { *n = node_new(&node_vmt_return, NULL); if (!parse_expr(o, &(*n)->list)) { return 0; } if (!parse_endl(o, NULL)) { return 0; } return 1; } else if (o->lex.id == TOK_LET) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { parse_recover(o, "identifier expected in the lhs of '='"); return 0; } *n = node_new(&node_vmt_assign, data_newstring(o->lex.strval)); if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_ASSIGN) { parse_recover(o, "'=' expected"); return 0; } if (!parse_getsym(o)) { return 0; } if (!parse_isfirst(o, first_expr)) { parse_recover(o, "expression expected after '='"); return 0; } parse_ungetsym(o); if (!parse_expr(o, &(*n)->list)) { return 0; } if (!parse_endl(o, NULL)) { return 0; } return 1; } else if (o->lex.id == TOK_EXIT) { *n = node_new(&node_vmt_exit, NULL); if (!parse_endl(o, NULL)) { return 0; } return 1; } else if (parse_isfirst(o, first_endl)) { parse_ungetsym(o); *n = node_new(&node_vmt_nop, NULL); if (!parse_endl(o, NULL)) { return 0; } return 1; } parse_recover(o, "bad statement"); return 0; } unsigned parse_slist(struct parse *o, struct node **n) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { parse_recover(o, "'{' expected"); return 0; } *n = node_new(&node_vmt_slist, NULL); n = &(*n)->list; for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_RBRACE) { break; } parse_ungetsym(o); if (!parse_stmt(o, n)) { return 0; } n = &(*n)->next; } return 1; } unsigned parse_proc(struct parse *o, struct node **n) { struct data *args; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_PROC) { parse_recover(o, "'proc' keyword expected"); return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { parse_recover(o, "proc name expected"); return 0; } args = data_newlist(NULL); data_listadd(args, data_newstring(o->lex.strval)); *n = node_new(&node_vmt_proc, args); for(;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_IDENT) { data_listadd(args, data_newstring(o->lex.strval)); } else if (o->lex.id == TOK_LBRACE) { parse_ungetsym(o); break; } else { parse_ungetsym(o); parse_recover(o, "argument name or block expected"); return 0; } } if (!parse_slist(o, &(*n)->list)) { return 0; } if (!parse_endl(o, NULL)) { return 0; } return 1; } unsigned parse_line(struct parse *o, struct node **n) { if (!parse_getsym(o)) { return 0; } if (parse_isfirst(o, first_proc)) { parse_ungetsym(o); if (!parse_proc(o, n)) { return 0; } } else if (parse_isfirst(o, first_stmt)) { parse_ungetsym(o); if (!parse_stmt(o, n)) { return 0; } } else { parse_recover(o, "statement or proc definition expected"); return 0; } return 1; } unsigned parse_prog(struct parse *o, struct node **n) { *n = node_new(&node_vmt_slist, NULL); n = &(*n)->list; for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_EOF) { return 1; } parse_ungetsym(o); if (!parse_line(o, n)) { return 0; } n = &(*n)->next; } /* not reached */ } midish-1.0.4/parse.h010066400017510001751000000047711143320305700127740ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_PARSE_H #define MIDISH_PARSE_H #include "lex.h" struct node; struct parse { struct lex lex; unsigned lookavail; }; struct parse *parse_new(char *); void parse_delete(struct parse *); void parse_error(struct parse *, char *); unsigned parse_getsym(struct parse *); void parse_ungetsym(struct parse *); unsigned parse_end(struct parse *, struct node **); unsigned parse_call(struct parse *, struct node **); unsigned parse_expr(struct parse *, struct node **); unsigned parse_addsub(struct parse *, struct node **); unsigned parse_muldiv(struct parse *, struct node **); unsigned parse_neg(struct parse *, struct node **); unsigned parse_const(struct parse *, struct node **); unsigned parse_assign(struct parse *, struct node **); unsigned parse_stmt(struct parse *, struct node **); unsigned parse_slist(struct parse *, struct node **); unsigned parse_proc(struct parse *, struct node **); unsigned parse_line(struct parse *, struct node **); unsigned parse_prog(struct parse *, struct node **); #endif /* MIDISH_PARSE_H */ midish-1.0.4/pool.c010066400017510001751000000104111143320305700126120ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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 = mem_alloc(itemsize * itemnum, "pool"); if (!o->data) { dbg_puts("pool_init("); dbg_puts(name); dbg_puts("): out of memory\n"); dbg_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) { mem_free(o->data); #ifdef POOL_DEBUG if (o->used != 0) { dbg_puts("pool_done("); dbg_puts(o->name); dbg_puts("): WARNING "); dbg_putu(o->used); dbg_puts(" items still allocated\n"); } if (pool_debug) { dbg_puts("pool_done("); dbg_puts(o->name); dbg_puts("): using "); dbg_putu((1023 + o->itemnum * o->itemsize) / 1024); dbg_puts("kB maxused = "); dbg_putu(100 * o->maxused / o->itemnum); dbg_puts("% allocs = "); dbg_putu(100 * o->newcnt / o->itemnum); dbg_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) { struct poolent *e; if (!o->first) { dbg_puts("pool_new("); dbg_puts(o->name); dbg_puts("): pool is empty\n"); dbg_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; #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, n; unsigned *buf; /* * check if we aren't trying to free more * entries than the poll size */ if (o->used == 0) { dbg_puts("pool_del("); dbg_puts(o->name); dbg_puts("): pool is full\n"); dbg_panic(); } o->used--; /* * overwrite the entry with garbage so any attempt to use a * free entry will probably segfault */ buf = (unsigned *)e; n = o->itemsize / sizeof(unsigned); for (i = 0; i < n; i++) *(buf++) = mem_rnd(); #endif /* * link on the free list */ e->next = o->first; o->first = e; } midish-1.0.4/pool.h010066400017510001751000000045511143320305700126270ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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.0.4/rmidish.1010066400017510001751000000042521143320305700132240ustar00alexalex.\" .\" Copyright (c) 2003-2010 Alexandre Ratchov .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" .\" - Redistributions of source code must retain the above .\" copyright notice, this list of conditions and the .\" following disclaimer. .\" .\" - Redistributions in binary form must reproduce the above .\" copyright notice, this list of conditions and the .\" following disclaimer in the documentation and/or other .\" materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS .\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT .\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR .\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT .\" OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, .\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT .\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd August 22, 2005 .Dt RMIDISH 1 .Os .Sh NAME .Nm rmidish .Nd front-end to .Xr midish 1 , using the .Xr readline 3 library .Sh SYNOPSIS .Nm rmidish .Sh DESCRIPTION The .Nm utility is a very simple front-end to .Xr midish 1 that uses the .Xr readline 3 library. It runs .Xr midish 1 in the background, issues an interactive prompt and passes user commands to .Xr midish 1 . The initial purpose of .Nm was to illustrate how to use the .Fl v option of .Xr midish 1 to create a front-end, but it can be run instead of .Xr midish 1 to make the later more user-friendly when used interactively. .Sh SEE ALSO .Xr midish 1 , .Xr readline 3 .br Midish user's manual and tutorial .br http://caoua.org/midish/ .Sh BUGS The .Xr midish 1 executable has to be in the same directoy as .Nm midish-1.0.4/rmidish.c010066400017510001751000000217471143320305700133160ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * This program gives an example on how to create a front-end to midish * * first we create a pair of pipes corresponding to the stdin/stdout of midish * and we start it. * * then, to send commands to midish we write to its standard input (though the * first pipe) and to receive the results we read its standart output (trough * the second pipe). */ #include #include #include #include #include #include #include #include #include #include #include #define MIDISH "midish" struct midish { int pid; /* pid of midish */ int sin; /* stdin of midish */ int sout; /* stdout of midish */ #define MIDISH_BUFLEN 1024 unsigned char buf[MIDISH_BUFLEN]; /* fifo for reading */ unsigned buf_start; /* start position of the buf */ unsigned buf_used; /* bytes available */ int eof; /* 1 if eof reached */ }; /* * start midish */ int midish_open(struct midish *p, char *path) { int ipipe[2], opipe[2]; /* * allocate pipes and fork */ if (pipe(ipipe) < 0 || pipe(opipe) < 0 ) { perror("pipe"); exit(1); } p->pid = fork(); if (p->pid < 0) { perror("fork"); return -1; } if (p->pid == 0) { close(ipipe[1]); close(opipe[0]); if (dup2(ipipe[0], STDIN_FILENO) < 0) { perror("dup2"); exit(1); } if (dup2(opipe[1], STDOUT_FILENO) < 0) { perror("dup2"); exit(1); } close(ipipe[0]); close(opipe[1]); /* * run midish */ if (execlp(path, path, "-v", (char *)NULL) < 0) { perror(path); } exit(1); } close(ipipe[0]); close(opipe[1]); p->sout = opipe[0]; p->sin = ipipe[1]; p->eof = 0; p->buf_start = 0; p->buf_used = 0; /* * lower scheduling priority */ if (setpriority(PRIO_PROCESS, getpid(), 15) < 0) perror("setpriority"); return 0; } /* * close connection to midish, and wait for it to terminate */ void midish_close(struct midish *p) { int status; close(p->sout); close(p->sin); if (waitpid(p->pid, &status, 0) < 0) { perror("waitpid"); } } /* * read a character from midish's stdout */ int midish_getc(struct midish *p) { unsigned end, avail, count; int n, c; if (p->eof) return -1; if (p->buf_used == 0) { avail = MIDISH_BUFLEN - p->buf_used; end = p->buf_start + p->buf_used; if (end >= MIDISH_BUFLEN) end -= MIDISH_BUFLEN; count = MIDISH_BUFLEN - end; if (count > avail) count = avail; n = read(p->sout, p->buf + end, count); if (n < 0) { perror("midish_getc: read"); return -1; } if (n == 0) { p->eof = 1; return -1; } p->buf_used += n; } c = p->buf[p->buf_start]; p->buf_start++; if (p->buf_start >= MIDISH_BUFLEN) p->buf_start -= MIDISH_BUFLEN; p->buf_used--; return c; } /* * send a line on midish's stdin */ void midish_puts(struct midish *p, char *str) { if (write(p->sin, str, strlen(str)) < 0) { perror("midish_puts: write"); } } /* * get a line from midish's stdout */ char * midish_gets(struct midish *p, char *str, size_t maxsize) { int c; size_t size = 0; maxsize--; for (;;) { c = midish_getc(p); if (c == -1) { return NULL; } if (size == maxsize) { fprintf(stderr, "line too long\n"); return NULL; } str[size++] = c; if (c == '\n') { str[size] = '\0'; break; } } return str; } /* ---------------------------------------------------------------- */ #define LINELENGTH 10000 #define PROMPTLENGTH 20 struct midish midish; char linebuf[LINELENGTH]; char prompt[PROMPTLENGTH] = "[----:--]>"; int quit = 0; /* * check if midish sent asynchronous position notification * and if so update the prompt */ void asyncline(void) { unsigned m, b, t; if (sscanf(linebuf, "+pos %u %u %u", &m, &b, &t) == 3) { snprintf(prompt, PROMPTLENGTH, "[%04u:%02u]> ", m, b); rl_set_prompt(prompt); } } /* * wait midish to become ready to accept another line */ void waitready(void) { for (;;) { if (!midish_gets(&midish, linebuf, LINELENGTH)) { exit(1); } if (linebuf[0] != '+') { fprintf(stdout, "%s", linebuf); continue; } if (strcmp(linebuf, "+ready\n") == 0) return; asyncline(); } } /* * call-back, called by the readline library once a line is ready */ void userline(char *rl) { if (rl == NULL) { fputs("\n", stdout); quit = 1; return; } if (rl[0] != '\0') { add_history(rl); } midish_puts(&midish, rl); midish_puts(&midish, "\n"); waitready(); free(rl); } char *builtins[] = { /* * generated with: * grep blt_ builtin.h |sed -es/^.*blt_/\"/ -es/\(.*$/\",/ |fmt */ "panic", "debug", "exec", "print", "err", "h", "ev", "ci", "geti", "co", "geto", "cx", "getx", "setunit", "getunit", "goto", "getpos", "sel", "getlen", "setq", "getq", "fac", "getfac", "ct", "gett", "cf", "getf", "mute", "unmute", "getmute", "ls", "save", "load", "reset", "export", "import", "idle", "play", "rec", "stop", "tempo", "mins", "mcut", "mdup", "minfo", "mtempo", "msig", "mend", "ctlconf", "ctlconfx", "ctlunconf", "ctlinfo", "metro", "metrocf", "tlist", "tnew", "tdel", "tren", "texists", "taddev", "tsetf", "tgetf", "tcheck", "tcut", "tins", "tclr", "tpaste", "tcopy", "tmerge", "tquant", "ttransp", "tevmap", "tclist", "tinfo", "ilist", "iexists", "inew", "idel", "iren", "iset", "igetc", "igetd", "iaddev", "irmev", "iinfo", "olist", "oexists", "onew", "odel", "oren", "oset", "ogetc", "ogetd", "oaddev", "ormev", "oinfo", "flist", "fnew", "fdel", "fren", "fexists", "finfo", "freset", "fmap", "funmap", "ftransp", "fvcurve", "fchgin", "fchgout", "fswapin", "fswapout", "xlist", "xexists", "xnew", "xdel", "xren", "xinfo", "xrm", "xsetd", "xadd", "dnew", "ddel", "dmtcrx", "dmmctx", "dclkrx", "dclktx", "dclkrate", "dinfo", "dixctl", "doxctl", NULL }; char * genbuiltin(const char *text, int state) { static char **pname; size_t len; if (state == 0) pname = &builtins[0]; len = strlen(text); while (*pname != NULL) { if (strncmp(*pname, text, len) == 0) return strdup(*pname++); pname++; } return NULL; } char ** complete(const char *text, int start, int end) { if (start != 0) return NULL; return rl_completion_matches(text, genbuiltin); } int main(int argc, char *argv[]) { char path[PATH_MAX]; unsigned dirlen, filelen; struct pollfd pfds[2]; int n; /* * determine the complete path of the midish executable */ if (argc > 0) { dirlen = strlen(argv[0]); while(dirlen > 0 && argv[0][dirlen - 1] != '/') dirlen--; memcpy(path, argv[0], dirlen); } else { dirlen = 0; } filelen = strlen(MIDISH); if (dirlen + filelen >= PATH_MAX) { fprintf(stderr, "midish file name too long\n"); exit(1); } memcpy(path + dirlen, MIDISH, filelen + 1); /* * if stdin or stdout is not a tty, then dont start the front end, * just execute midish */ if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) { if (execlp(path, path, (char *)NULL) < 0) { perror(path); } exit(1); } if (midish_open(&midish, path) < 0) { exit(1); } fprintf(stderr, "send EOF character (control-D) to quit\n"); pfds[0].fd = STDIN_FILENO; pfds[0].events = POLLIN; pfds[1].fd = midish.sout; pfds[1].events = POLLIN; waitready(); rl_attempted_completion_function = complete; rl_callback_handler_install(prompt, userline); while (!quit) { n = poll(pfds, 2, -1); if (pfds[0].revents & POLLIN) { /* * got characters on the console, * will call userline() */ rl_callback_read_char(); } if (pfds[1].revents & POLLIN) { /* * got characters from midish */ if (!midish_gets(&midish, linebuf, LINELENGTH)) { exit(1); } asyncline(); rl_redisplay(); } } rl_callback_handler_remove(); midish_close(&midish); fputs("\n", stdout); return 0; } midish-1.0.4/sample.sng010066400017510001751000001521111104640205700134740ustar00alexalex# # 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.0.4/saveload.c010066400017510001751000001206261143320305700134510ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "dbg.h" #include "name.h" #include "song.h" #include "filt.h" #include "parse.h" #include "textio.h" #include "saveload.h" #include "frame.h" #include "conv.h" /* ------------------------------------------------------------------- */ 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"); goto two; 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: textout_putstr(f, "# ignored event\n"); dbg_puts("ignoring event: "); ev_dbg(e); dbg_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, " "); if (e->v1 != EV_UNDEF) { textout_putlong(f, e->v1); } else { textout_putstr(f, "nil"); } 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, "}"); } if (evinfo[o->cmd].nranges >= 1) { textout_putstr(f, " "); 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_indent(f); textout_putlong(f, i->delta); textout_putstr(f, "\n"); } if (i->ev.cmd == EV_NULL) { break; } textout_indent(f); ev_output(&i->ev, f); textout_putstr(f, "\n"); } textout_shiftleft(f); textout_indent(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_indent(f); 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_indent(f); 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_indent(f); 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_indent(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_indent(f); textout_putstr(f, "unit "); textout_putlong(f, o->unit); textout_putstr(f, "\n"); textout_indent(f); 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"); textout_indent(f); } else { textout_putstr(f, " "); } } } } textout_putstr(f, "\n"); textout_shiftleft(f); textout_shiftleft(f); textout_indent(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_indent(f); textout_putstr(f, "sysex "); sysex_output(i, f); textout_putstr(f, "\n"); } textout_shiftleft(f); textout_indent(f); textout_putstr(f, "}"); } void songtrk_output(struct songtrk *o, struct textout *f) { textout_putstr(f, "{\n"); textout_shiftright(f); if (o->curfilt) { textout_indent(f); textout_putstr(f, "curfilt "); textout_putstr(f, o->curfilt->name.str); textout_putstr(f, "\n"); } textout_indent(f); textout_putstr(f, "mute "); textout_putlong(f, o->mute); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "track "); track_output(&o->track, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_indent(f); textout_putstr(f, "}"); } void songchan_output(struct songchan *o, struct textout *f) { textout_putstr(f, "{\n"); textout_shiftright(f); textout_indent(f); textout_putstr(f, "chan "); chan_output(o->dev, o->ch, f); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "conf "); track_output(&o->conf, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_indent(f); textout_putstr(f, "}"); } void songfilt_output(struct songfilt *o, struct textout *f) { textout_putstr(f, "{\n"); textout_shiftright(f); textout_indent(f); textout_putstr(f, "filt "); filt_output(&o->filt, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_indent(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_indent(f); textout_putstr(f, "mask\t"); textout_putstr(f, mstr); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "lo\t"); ev_output(&o->lo, f); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "hi\t"); ev_output(&o->hi, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_indent(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_indent(f); textout_putstr(f, "#\n"); textout_indent(f); textout_putstr(f, "# name\tnumber\tdefval\n"); textout_indent(f); textout_putstr(f, "#\n"); for (i = 0; i < EV_MAXCOARSE + 1; i++) { ctl = &tab[i]; if (ctl->name) { textout_indent(f); 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 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_indent(f); textout_putstr(f, "tics_per_unit "); textout_putlong(f, o->tics_per_unit); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "tempo_factor "); textout_putlong(f, o->tempo_factor); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "meta "); track_output(&o->meta, f); textout_putstr(f, "\n"); SONG_FOREACH_IN(o, i) { textout_indent(f); textout_putstr(f, "songin "); textout_putstr(f, i->name.str); textout_putstr(f, " "); songchan_output(i, f); textout_putstr(f, "\n"); } SONG_FOREACH_OUT(o, i) { textout_indent(f); textout_putstr(f, "songout "); textout_putstr(f, i->name.str); textout_putstr(f, " "); songchan_output(i, f); textout_putstr(f, "\n"); } SONG_FOREACH_FILT(o, g) { textout_indent(f); 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_indent(f); 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_indent(f); 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_indent(f); textout_putstr(f, "curtrk "); textout_putstr(f, o->curtrk->name.str); textout_putstr(f, "\n"); } if (o->curfilt) { textout_indent(f); textout_putstr(f, "curfilt "); textout_putstr(f, o->curfilt->name.str); textout_putstr(f, "\n"); } if (o->cursx) { textout_indent(f); textout_putstr(f, "cursx "); textout_putstr(f, o->cursx->name.str); textout_putstr(f, "\n"); } if (o->curin) { textout_indent(f); textout_putstr(f, "curin "); textout_putstr(f, o->curin->name.str); textout_putstr(f, "\n"); } if (o->curout) { textout_indent(f); textout_putstr(f, "curout "); textout_putstr(f, o->curout->name.str); textout_putstr(f, "\n"); } textout_indent(f); textout_putstr(f, "curpos "); textout_putlong(f, o->curpos); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "curlen "); textout_putlong(f, o->curlen); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "curquant "); textout_putlong(f, o->curquant); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "curev "); evspec_output(&o->curev, f); textout_putstr(f, "\n"); textout_indent(f); textout_putstr(f, "metro "); metro_output(&o->metro, f); textout_putstr(f, "\n"); textout_shiftleft(f); textout_indent(f); textout_putstr(f, "}"); } /* ---------------------------------------------------------------------- */ unsigned parse_ukline(struct parse *o); unsigned parse_ukblock(struct parse *o); unsigned parse_delta(struct parse *o, unsigned *delta); unsigned parse_ev(struct parse *o, struct ev *ev); unsigned parse_track(struct parse *o, struct track *t); unsigned parse_songtrk(struct parse *o, struct song *s, struct songtrk *t); unsigned parse_song(struct parse *o, struct song *s); unsigned parse_ukline(struct parse *o) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT && o->lex.id != TOK_NUM && o->lex.id != TOK_ENDLINE) { lex_err(&o->lex, "ident, number, newline or '}' expected"); return 0; } parse_ungetsym(o); for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_RBRACE || o->lex.id == TOK_EOF) { parse_ungetsym(o); break; } else if (o->lex.id == TOK_ENDLINE) { break; } else if (o->lex.id == TOK_LBRACE) { parse_ungetsym(o); if (!parse_ukblock(o)) { return 0; } } } return 1; } unsigned parse_ukblock(struct parse *o) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing block"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_RBRACE) { break; } else { parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } } } return 1; } unsigned parse_nl(struct parse *o) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_RBRACE || o->lex.id != TOK_EOF) { parse_ungetsym(o); } else if (o->lex.id != TOK_ENDLINE) { lex_err(&o->lex, "new line expected"); return 0; } return 1; } unsigned parse_empty(struct parse *o) { for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_ENDLINE) { parse_ungetsym(o); break; } } return 1; } unsigned parse_long(struct parse *o, unsigned long min, unsigned long max, unsigned long *data) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_NUM) { lex_err(&o->lex, "number expected"); return 0; } if (o->lex.longval < min || o->lex.longval > max) { lex_err(&o->lex, "number out of allowed range"); return 0; } *data = (unsigned)o->lex.longval; return 1; } unsigned parse_delta(struct parse *o, unsigned *delta) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_NUM) { lex_err(&o->lex, "numeric constant expected in event offset"); return 0; } *delta = o->lex.longval; if (!parse_nl(o)) { return 0; } return 1; } unsigned parse_chan(struct parse *o, unsigned long *dev, unsigned long *ch) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_LBRACE) { if (!parse_long(o, 0, EV_MAXDEV, dev)) { return 0; } if (!parse_long(o, 0, EV_MAXCH, ch)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_RBRACE) { lex_err(&o->lex, "'}' expected in channel spec"); return 0; } return 1; } else if (o->lex.id == TOK_NUM) { *dev = o->lex.longval / (EV_MAXCH + 1); *ch = o->lex.longval % (EV_MAXCH + 1); if (*dev > EV_MAXDEV) { lex_err(&o->lex, "dev/chan out of range"); return 0; } return 1; } else { lex_err(&o->lex, "bad channel spec"); return 0; } } unsigned parse_ev(struct parse *o, struct ev *ev) { unsigned long val, val2; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "event name expected"); return 0; } if (!ev_str2cmd(ev, o->lex.strval)) { ignore: parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown event, ignored"); ev->cmd = EV_NULL; return 1; } if (EV_ISVOICE(ev)) { if (!parse_chan(o, &val, &val2)) { return 0; } ev->dev = val; ev->ch = val2; } switch (ev->cmd) { case EV_TEMPO: if (!parse_long(o, TEMPO_MIN, TEMPO_MAX, &val)) { return 0; } ev->tempo_usec24 = val; break; case EV_TIMESIG: if (!parse_long(o, 1, TIMESIG_BEATS_MAX, &val)) { return 0; } ev->timesig_beats = val; if (!parse_long(o, 1, TIMESIG_TICS_MAX, &val)) { return 0; } ev->timesig_tics = val; break; case EV_NRPN: case EV_RPN: if (!parse_long(o, 0, EV_MAXFINE, &val)) { return 0; } ev->rpn_num = val; if (!parse_long(o, 0, EV_MAXFINE, &val)) { return 0; } ev->rpn_val = val; break; case EV_XCTL: if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->ctl_num = val; if (!parse_long(o, 0, EV_MAXFINE, &val)) { return 0; } ev->ctl_val = val; break; case EV_XPC: if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->pc_prog = val; if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_NIL) { ev->pc_bank = EV_UNDEF; break; } parse_ungetsym(o); if (!parse_long(o, 0, EV_MAXFINE, &val)) { return 0; } ev->pc_bank = val; break; case EV_NON: case EV_NOFF: case EV_CTL: if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 = val; if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v1 = val; break; case EV_KAT: if (!parse_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 (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_NUM) { parse_ungetsym(o); if (!parse_nl(o)) { return 0; } ev->cmd = EV_NULL; return 1; } parse_ungetsym(o); if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v1 = val; break; case EV_PC: case EV_CAT: if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 = val; break; case EV_BEND: if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 = val; if (!parse_long(o, 0, EV_MAXCOARSE, &val)) { return 0; } ev->v0 += (val << 7); break; default: goto ignore; } if (!parse_nl(o)) { return 0; } return 1; } unsigned parse_track(struct parse *o, struct track *t) { unsigned delta; struct seqev *pos, *se; struct statelist slist; struct ev ev, rev; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing track"); return 0; } track_clear(t); statelist_init(&slist); pos = t->first; for (;;) { if (!parse_getsym(o)) { statelist_done(&slist); return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_NUM) { parse_ungetsym(o); if (!parse_delta(o, &delta)) { statelist_done(&slist); return 0; } pos->delta += delta; } else { parse_ungetsym(o); if (!parse_ev(o, &ev)) { statelist_done(&slist); return 0; } if (ev.cmd != EV_NULL) { if (conv_packev(&slist, 0U, &ev, &rev)) { se = seqev_new(); se->ev = rev; seqev_ins(pos, se); } } } } statelist_done(&slist); return 1; } unsigned parse_range(struct parse *o, unsigned min, unsigned max, unsigned *rmin, unsigned *rmax) { unsigned long tmin, tmax; if (!parse_long(o, min, max, &tmin)) return 0; if (!parse_getsym(o)) return 0; if (o->lex.id != TOK_RANGE) { parse_ungetsym(o); *rmin = *rmax = tmin; return 1; } if (!parse_long(o, min, max, &tmax)) return 0; *rmin = tmin; *rmax = tmax; return 1; } unsigned parse_evspec(struct parse *o, struct evspec *es) { struct evinfo *info; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "event spec name expected"); return 0; } if (!evspec_str2cmd(es, o->lex.strval)) { return 0; } info = &evinfo[es->cmd]; if ((info->flags & EV_HAS_DEV) && (info->flags & EV_HAS_CH)) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected"); return 0; } if (!parse_range(o, 0, EV_MAXDEV, &es->dev_min, &es->dev_max) || !parse_range(o, 0, EV_MAXCH, &es->ch_min, &es->ch_max)) return 0; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_RBRACE) { lex_err(&o->lex, "'}' expected"); return 0; } } if (info->nranges == 0) { return 1; } if (!parse_range(o, 0, info->v0_max, &es->v0_min, &es->v0_max)) { return 0; } if (info->nranges == 1) { return 1; } if (!parse_range(o, 0, info->v1_max, &es->v1_min, &es->v1_max)) { return 0; } return 1; } unsigned parse_rule(struct parse *o, struct filt *f) { unsigned long idev, ich, odev, och, ictl, octl, keylo, keyhi, ukeyplus; struct evspec from, to; int keyplus; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "filter-type identifier expected"); return 0; } if (str_eq(o->lex.strval, "keydrop")) { if (!parse_chan(o, &idev, &ich)) { return 0; } if (!parse_long(o, 0, EV_MAXCOARSE, &keylo)) { return 0; } if (!parse_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->lex.strval, "keymap")) { if (!parse_chan(o, &idev, &ich)) { return 0; } if (!parse_chan(o, &odev, &och)) { return 0; } if (!parse_long(o, 0, EV_MAXCOARSE, &keylo)) { return 0; } if (!parse_long(o, 0, EV_MAXCOARSE, &keyhi)) { return 0; } if (!parse_long(o, 0, EV_MAXCOARSE, &ukeyplus)) { return 0; } if (ukeyplus > 63) { keyplus = ukeyplus - 128; } else { keyplus = ukeyplus; } if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "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) { lex_err(&o->lex, "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->lex.strval, "ctldrop")) { if (!parse_chan(o, &idev, &ich)) { return 0; } if (!parse_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->lex.strval, "ctlmap")) { if (!parse_chan(o, &idev, &ich)) { return 0; } if (!parse_chan(o, &odev, &och)) { return 0; } if (!parse_long(o, 0, EV_MAXCOARSE, &ictl)) { return 0; } if (!parse_long(o, 0, EV_MAXCOARSE, &octl)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "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->lex.strval, "chandrop")) { if (!parse_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->lex.strval, "chanmap")) { if (!parse_chan(o, &idev, &ich)) { return 0; } if (!parse_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->lex.strval, "devdrop")) { if (!parse_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->lex.strval, "devmap")) { if (!parse_long(o, 0, EV_MAXDEV, &idev)) { return 0; } if (!parse_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->lex.strval, "evmap")) { if (!parse_evspec(o, &from)) { return 0; } if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_GT) { lex_err(&o->lex, "'>' expected"); return 0; } if (!parse_evspec(o, &to)) { return 0; } filt_mapnew(f, &from, &to); } else if (str_eq(o->lex.strval, "transp")) { if (!parse_evspec(o, &to)) { return 0; } if (!parse_long(o, 0, EV_MAXCOARSE, &ukeyplus)) { return 0; } filt_transp(f, &to, ukeyplus); } else if (str_eq(o->lex.strval, "vcurve")) { if (!parse_evspec(o, &to)) { return 0; } if (!parse_long(o, 1, EV_MAXCOARSE, &ukeyplus)) { return 0; } filt_vcurve(f, &to, ukeyplus); } else { parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown filter rule, ignored"); return 1; } if (!parse_nl(o)) { return 0; } return 1; } unsigned parse_filt(struct parse *o, struct filt *f) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing filt"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else { parse_ungetsym(o); if (!parse_rule(o, f)) { return 0; } } } return 1; } unsigned parse_sysex(struct parse *o, struct sysex **res) { struct sysex *sx; unsigned long val; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing sysex"); return 0; } sx = sysex_new(0); for (;;) { if (!parse_getsym(o)) { goto err1; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_IDENT) { if (str_eq(o->lex.strval, "data")) { for (;;) { if (!parse_getsym(o)) { goto err1; } if (o->lex.id == TOK_ENDLINE) { break; } parse_ungetsym(o); if (!parse_long(o, 0, 0xff, &val)) { goto err1; } sysex_add(sx, val); } } else if (str_eq(o->lex.strval, "unit")) { if (!parse_long(o, 0, EV_MAXDEV, &val)) { goto err1; } sx->unit = val; if (!parse_nl(o)) { goto err1; } } else { goto unknown; } } else { unknown: parse_ungetsym(o); if (!parse_ukline(o)) { goto err1; } lex_err(&o->lex, "unknown line format in sysex, ignored"); } } *res = sx; return 1; err1: sysex_del(sx); return 0; } unsigned parse_songchan(struct parse *o, struct song *s, struct songchan *i) { unsigned long val, val2; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing songchan"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_IDENT) { if (str_eq(o->lex.strval, "conf")) { if (!parse_track(o, &i->conf)) { return 0; } if (!parse_nl(o)) { return 0; } track_setchan(&i->conf, i->dev, i->ch); } else if (str_eq(o->lex.strval, "chan")) { if (!parse_chan(o, &val, &val2)) { return 0; } i->dev = val; i->ch = val2; if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "curinput")) { if (!parse_chan(o, &val, &val2)) { return 0; } if (!parse_nl(o)) { return 0; } lex_err(&o->lex, "ignored obsolete 'curinput' line"); } else { goto unknown; } } else { unknown: parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown line format in songchan, ignored"); } } return 1; } unsigned parse_songtrk(struct parse *o, struct song *s, struct songtrk *t) { struct songfilt *f; unsigned long val; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing songtrk"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_IDENT) { if (str_eq(o->lex.strval, "curfilt")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected after 'curfilt' in songtrk"); return 0; } f = song_filtlookup(s, o->lex.strval); if (!f) { f = song_filtnew(s, o->lex.strval); song_setcurfilt(s, NULL); } t->curfilt = f; if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "track")) { if (!parse_track(o, &t->track)) { return 0; } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "mute")) { if (!parse_long(o, 0, 1, &val)) { return 0; } t->mute = val; if (!parse_nl(o)) { return 0; } } else { goto unknown; } } else { unknown: parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown line format in songtrk, ignored"); } } return 1; } unsigned parse_songfilt(struct parse *o, struct song *s, struct songfilt *g) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing songfilt"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_IDENT) { if (str_eq(o->lex.strval, "curchan")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected " "after 'curchan' in songfilt"); return 0; } if (!parse_nl(o)) { return 0; } lex_err(&o->lex, "ignored obsolete 'curchan' line"); } else if (str_eq(o->lex.strval, "filt")) { if (!parse_filt(o, &g->filt)) { return 0; } } else { goto unknown; } } else { unknown: parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown line format in songfilt, ignored"); } } return 1; } unsigned parse_songsx(struct parse *o, struct song *s, struct songsx *g) { struct sysex *sx; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing songsx"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_IDENT) { if (str_eq(o->lex.strval, "sysex")) { if (!parse_sysex(o, &sx)) { return 0; } sysexlist_put(&g->sx, sx); } else { goto unknown; } } else { unknown: parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown line format in songsx, ignored"); } } return 1; } unsigned parse_metro(struct parse *o, struct metro *m) { struct ev ev; unsigned mask; if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing metro"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_IDENT) { if (str_eq(o->lex.strval, "enabled")) { if (!parse_getsym(o)) { return 0; } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "mask")) { if (!parse_getsym(o)) { return 0; } if (!metro_str2mask(m, o->lex.strval, &mask)) { lex_err(&o->lex, "skipped unknown metronome mask"); } metro_setmask(m, mask); if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "lo")) { if (!parse_ev(o, &ev)) { return 0; } if (ev.cmd != EV_NON) { lex_err(&o->lex, "'lo' must be followed by a 'non' event\n"); } else { m->lo = ev; } } else if (str_eq(o->lex.strval, "hi")) { if (!parse_ev(o, &ev)) { return 0; } if (ev.cmd != EV_NON) { lex_err(&o->lex, "'hi' must be followed by a 'non' event\n"); } else { m->hi = ev; } } else { goto unknown; } } else { unknown: parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown line format in song, ignored"); } } return 1; } unsigned parse_song(struct parse *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 (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_LBRACE) { lex_err(&o->lex, "'{' expected while parsing song"); return 0; } for (;;) { if (!parse_getsym(o)) { return 0; } if (o->lex.id == TOK_ENDLINE) { /* nothing */ } else if (o->lex.id == TOK_RBRACE) { break; } else if (o->lex.id == TOK_IDENT) { if (str_eq(o->lex.strval, "songtrk")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected after 'songtrk' in song"); return 0; } t = song_trklookup(s, o->lex.strval); if (t == NULL) { t = song_trknew(s, o->lex.strval); song_setcurtrk(s, NULL); } if (!parse_songtrk(o, s, t)) { return 0; } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "songin")) { input = 1; goto chan_do; } else if (str_eq(o->lex.strval, "songout") || str_eq(o->lex.strval, "songchan")) { input = 0; chan_do: if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected after 'songchan' in song"); return 0; } i = song_chanlookup(s, o->lex.strval, input); if (i == NULL) { i = song_channew(s, o->lex.strval, 0, 0, input); song_setcurchan(s, NULL, input); } if (!parse_songchan(o, s, i)) { return 0; } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "songfilt")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected after 'songfilt' in song"); return 0; } g = song_filtlookup(s, o->lex.strval); if (!g) { g = song_filtnew(s, o->lex.strval); song_setcurfilt(s, NULL); } if (!parse_songfilt(o, s, g)) { return 0; } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "songsx")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected after 'songsx' in song"); return 0; } l = song_sxlookup(s, o->lex.strval); if (!l) { l = song_sxnew(s, o->lex.strval); song_setcursx(s, NULL); } if (!parse_songsx(o, s, l)) { return 0; } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "meta")) { if (!parse_track(o, &s->meta)) { return 0; } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "tics_per_unit")) { if (!parse_long(o, 0, ~1U, &num)) { return 0; } if ((num % 96) != 0) { lex_err(&o->lex, "warning, rounded tic_per_unit to a multiple of 96"); num = 96 * (num / 96); if (num < 96) { num = 96; } } if (!parse_nl(o)) { return 0; } s->tics_per_unit = num; } else if (str_eq(o->lex.strval, "tempo_factor")) { if (!parse_long(o, 0, ~1U, &num)) { return 0; } if (!parse_nl(o)) { return 0; } if (num < 0x80 || num > 0x200) { lex_err(&o->lex, "warning, tempo factor out of bounds\n"); } else { s->tempo_factor = num; } } else if (str_eq(o->lex.strval, "curtrk")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected afer 'curtrk' in song"); return 0; } t = song_trklookup(s, o->lex.strval); if (t) { s->curtrk = t; } else { lex_err(&o->lex, "warning, cant set current track, not such track"); } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "curfilt")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected afer 'curfilt' in song"); return 0; } g = song_filtlookup(s, o->lex.strval); if (g) { s->curfilt = g; } else { lex_err(&o->lex, "warning, cant set current filt, not such filt"); } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "cursx")) { if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected afer 'cursx' in song"); return 0; } l = song_sxlookup(s, o->lex.strval); if (l) { s->cursx = l; } else { lex_err(&o->lex, "warning, cant set current sysex, not such sysex"); } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "curin")) { input = 1; goto curchan_do; } else if (str_eq(o->lex.strval, "curout") || str_eq(o->lex.strval, "curchan")) { input = 0; curchan_do: if (!parse_getsym(o)) { return 0; } if (o->lex.id != TOK_IDENT) { lex_err(&o->lex, "identifier expected afer 'curchan' in song"); return 0; } i = song_chanlookup(s, o->lex.strval, input); if (i) { song_setcurchan(s, i, input); } else { lex_err(&o->lex, "warning, cant set current chan, not such chan"); } if (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "curpos")) { if (!parse_long(o, 0, ~1U, &num)) { return 0; } if (!parse_nl(o)) { return 0; } s->curpos = num; } else if (str_eq(o->lex.strval, "curlen")) { if (!parse_long(o, 0, ~1U, &num)) { return 0; } if (!parse_nl(o)) { return 0; } s->curlen = num; } else if (str_eq(o->lex.strval, "curquant")) { if (!parse_long(o, 0, ~1U, &num)) { return 0; } if (num > s->tics_per_unit) { lex_err(&o->lex, "warning, truncated curquant because it was too large"); num = s->tics_per_unit; } if (!parse_nl(o)) { return 0; } s->curquant = num; } else if (str_eq(o->lex.strval, "curev")) { if (!parse_evspec(o, &es)) { return 0; } if (!parse_nl(o)) { return 0; } s->curev = es; } else if (str_eq(o->lex.strval, "curinput")) { if (!parse_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 (!parse_nl(o)) { return 0; } } else if (str_eq(o->lex.strval, "metro")) { if (!parse_metro(o, &s->metro)) { return 0; } if (!parse_nl(o)) { return 0; } } else { goto unknown; } } else { unknown: parse_ungetsym(o); if (!parse_ukline(o)) { return 0; } lex_err(&o->lex, "unknown line format in song, 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" "# song file of " VERSION "\n" "#\n" ); song_output(o, f); textout_putstr(f, "\n"); textout_delete(f); } unsigned song_load(struct song *o, char *filename) { struct parse *parse; unsigned res; parse = parse_new(filename); if (!parse) { return 0; } res = parse_empty(parse); if (res != 0) { res = parse_song(parse, o); } parse_delete(parse); return res; } midish-1.0.4/saveload.h010066400017510001751000000045721143320305700134570ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 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.0.4/smf.h010066400017510001751000000030621143320306000124310ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_SMF_H #define MIDISH_SMF_H struct song; unsigned song_exportsmf(struct song *o, char *); struct song *song_importsmf(char *); #endif /* MIDISH_SMF_H */ midish-1.0.4/regress004077500017510001751000000000001146135305700131055ustar00alexalexmidish-1.0.4/regress/bad1.sng010066400017510001751000000003741063321335100144750ustar00alexalex{ 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.0.4/regress/bank.sng010066400017510001751000000001741063321335100145770ustar00alexalex{ 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.0.4/regress/bend1.sng010066400017510001751000000001631063321335100146530ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 48 } } } midish-1.0.4/regress/bend2.sng010066400017510001751000000001551063321335100146550ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/bend_e1.sng010066400017510001751000000003061063321335100151560ustar00alexalex{ 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.0.4/regress/bend_e2.sng010066400017510001751000000003141063321335100151560ustar00alexalex{ 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.0.4/regress/bend_e3.sng010066400017510001751000000003221063321335100151560ustar00alexalex{ 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.0.4/regress/bend_e4.sng010066400017510001751000000003301063321335100151560ustar00alexalex{ 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.0.4/regress/bend_e5.sng010066400017510001751000000003361063321335100151650ustar00alexalex{ 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.0.4/regress/bend_e6.sng010066400017510001751000000003441063321335100151650ustar00alexalex{ 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.0.4/regress/bend_s1.sng010066400017510001751000000003521063321335100151750ustar00alexalex{ 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.0.4/regress/bend_s2.sng010066400017510001751000000003521063321335100151760ustar00alexalex{ 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.0.4/regress/bend_s3.sng010066400017510001751000000003521063321335100151770ustar00alexalex{ 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.0.4/regress/bend_s4.sng010066400017510001751000000003521063321335100152000ustar00alexalex{ 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.0.4/regress/bend_s5.sng010066400017510001751000000003521063321335100152010ustar00alexalex{ 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.0.4/regress/bend_s6.sng010066400017510001751000000003521063321335100152020ustar00alexalex{ 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.0.4/regress/blank_a0.cmd010066400017510001751000000001131104167232600153050ustar00alexalexload "bend1.sng" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_a0.res010066400017510001751000000001551063321335100153340ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.0.4/regress/blank_a1.cmd010066400017510001751000000001131104167232600153060ustar00alexalexload "bend1.sng" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_a1.res010066400017510001751000000002061063321335100153320ustar00alexalex{ 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.0.4/regress/blank_a2.cmd010066400017510001751000000001131104167232600153070ustar00alexalexload "bend1.sng" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_a2.res010066400017510001751000000001551063321335100153360ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/blank_a3.cmd010066400017510001751000000001131104167232600153100ustar00alexalexload "bend1.sng" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_a3.res010066400017510001751000000001551063321335100153370ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.0.4/regress/blank_b0.cmd010066400017510001751000000001131104167232600153060ustar00alexalexload "bend2.sng" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_b0.res010066400017510001751000000001551063321335100153350ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/blank_b1.cmd010066400017510001751000000001131104167232600153070ustar00alexalexload "bend2.sng" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_b1.res010066400017510001751000000000421063321335100153310ustar00alexalex{ songtrk t { track { } } } midish-1.0.4/regress/blank_b2.cmd010066400017510001751000000001131104167232600153100ustar00alexalexload "bend2.sng" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_b2.res010066400017510001751000000001551063321335100153370ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/blank_b3.cmd010066400017510001751000000001131104167232600153110ustar00alexalexload "bend2.sng" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_b3.res010066400017510001751000000001551063321335100153400ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/blank_c0.cmd010066400017510001751000000001121104167232600153060ustar00alexalexload "note.sng" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_c0.res010066400017510001751000000000421063321335200153320ustar00alexalex{ songtrk t { track { } } } midish-1.0.4/regress/blank_c1.cmd010066400017510001751000000001121104167232600153070ustar00alexalexload "note.sng" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_c1.res010066400017510001751000000003641063321335200153420ustar00alexalex{ 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.0.4/regress/blank_c2.cmd010066400017510001751000000001121104167232600153100ustar00alexalexload "note.sng" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_c2.res010066400017510001751000000003641063321335200153430ustar00alexalex{ 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.0.4/regress/blank_c3.cmd010066400017510001751000000001121104167232600153110ustar00alexalexload "note.sng" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_c3.res010066400017510001751000000003641063321335200153440ustar00alexalex{ 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.0.4/regress/blank_d0.cmd010066400017510001751000000001111104167232600153060ustar00alexalexload "ctl.sng" ct t; g 0; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_d0.res010066400017510001751000000000721063321335200153360ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/blank_d1.cmd010066400017510001751000000001111104167232600153070ustar00alexalexload "ctl.sng" ct t; g 1; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_d1.res010066400017510001751000000000421063321335200153340ustar00alexalex{ songtrk t { track { } } } midish-1.0.4/regress/blank_d2.cmd010066400017510001751000000001111104167232600153100ustar00alexalexload "ctl.sng" ct t; g 2; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_d2.res010066400017510001751000000000721063321335200153400ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/blank_d3.cmd010066400017510001751000000001111104167232600153110ustar00alexalexload "ctl.sng" ct t; g 3; sel 1; tclr g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/blank_d3.res010066400017510001751000000000721063321335200153410ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/check.cmd010066400017510001751000000000771104167232600147240ustar00alexalexload "bad1.sng" ct t tcheck g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/check.res010066400017510001751000000002171063321335200147420ustar00alexalex{ 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.0.4/regress/confev_0.cmd010066400017510001751000000003341104167232600153420ustar00alexalexonew c {1 9} oaddev {ctl c 70 1} oaddev {ctl c 71 2} oaddev {xpc c 64 255} 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.0.4/regress/confev_0.res010066400017510001751000000003071063321335200153640ustar00alexalex{ 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.0.4/regress/copy_a0.cmd010066400017510001751000000001751104167232600152000ustar00alexalexload "bend1.sng" 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.0.4/regress/copy_a0.res010066400017510001751000000001241063321335200152140ustar00alexalex{ songtrk u { track { 48 bend {0 0} 0 65 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/copy_a1.cmd010066400017510001751000000001751104167232600152010ustar00alexalexload "bend1.sng" 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.0.4/regress/copy_a1.res010066400017510001751000000001471063321335200152220ustar00alexalex{ songtrk u { track { bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/copy_a2.cmd010066400017510001751000000001751104167232600152020ustar00alexalexload "bend1.sng" 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.0.4/regress/copy_a2.res010066400017510001751000000001161063321335200152170ustar00alexalex{ songtrk u { track { bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/copy_a3.cmd010066400017510001751000000001751104167232600152030ustar00alexalexload "bend1.sng" 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.0.4/regress/copy_a3.res010066400017510001751000000000421063321335200152160ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_b0.cmd010066400017510001751000000001751104167232600152010ustar00alexalexload "bend2.sng" 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.0.4/regress/copy_b0.res010066400017510001751000000000421063321335200152140ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_b1.cmd010066400017510001751000000001751104167232600152020ustar00alexalexload "bend2.sng" 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.0.4/regress/copy_b1.res010066400017510001751000000001471063321335200152230ustar00alexalex{ songtrk u { track { bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/copy_b2.cmd010066400017510001751000000001751104167232600152030ustar00alexalexload "bend2.sng" 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.0.4/regress/copy_b2.res010066400017510001751000000000421063321335200152160ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_b3.cmd010066400017510001751000000001751104167232600152040ustar00alexalexload "bend2.sng" 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.0.4/regress/copy_b3.res010066400017510001751000000000421063321335200152170ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_c0.cmd010066400017510001751000000001741104167232600152010ustar00alexalexload "note.sng" 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.0.4/regress/copy_c0.res010066400017510001751000000003641063321335200152240ustar00alexalex{ 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.0.4/regress/copy_c1.cmd010066400017510001751000000001741104167232600152020ustar00alexalexload "note.sng" 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.0.4/regress/copy_c1.res010066400017510001751000000000421063321335200152160ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_c2.cmd010066400017510001751000000001741104167232600152030ustar00alexalexload "note.sng" 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.0.4/regress/copy_c2.res010066400017510001751000000000421063321335200152170ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_c3.cmd010066400017510001751000000001741104167232600152040ustar00alexalexload "note.sng" 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.0.4/regress/copy_c3.res010066400017510001751000000000421063321335200152200ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_d0.cmd010066400017510001751000000001731104167232700152020ustar00alexalexload "ctl.sng" 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.0.4/regress/copy_d0.res010066400017510001751000000000421063321335200152160ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_d1.cmd010066400017510001751000000001731104167232700152030ustar00alexalexload "ctl.sng" 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.0.4/regress/copy_d1.res010066400017510001751000000000641063321335200152230ustar00alexalex{ songtrk u { track { ctl {0 0} 7 65 } } } midish-1.0.4/regress/copy_d2.cmd010066400017510001751000000001731104167232700152040ustar00alexalexload "ctl.sng" 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.0.4/regress/copy_d2.res010066400017510001751000000000421063321335200152200ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/copy_d3.cmd010066400017510001751000000001731104167232700152050ustar00alexalexload "ctl.sng" 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.0.4/regress/copy_d3.res010066400017510001751000000000421063321335200152210ustar00alexalex{ songtrk u { track { } } } midish-1.0.4/regress/ctl.sng010066400017510001751000000000721063321335200144440ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/cut_a0.cmd010066400017510001751000000001131104167232700150120ustar00alexalexload "bend1.sng" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_a0.res010066400017510001751000000001471063321335200150420ustar00alexalex{ songtrk t { track { bend {0 0} 0 65 48 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.0.4/regress/cut_a1.cmd010066400017510001751000000001131104167232700150130ustar00alexalexload "bend1.sng" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_a1.res010066400017510001751000000001551063321335200150420ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/cut_a2.cmd010066400017510001751000000001131104167232700150140ustar00alexalexload "bend1.sng" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_a2.res010066400017510001751000000001551063321335200150430ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/cut_a3.cmd010066400017510001751000000001131104167232700150150ustar00alexalexload "bend1.sng" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_a3.res010066400017510001751000000001551063321335200150440ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.0.4/regress/cut_b0.cmd010066400017510001751000000001131104167232700150130ustar00alexalexload "bend2.sng" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_b0.res010066400017510001751000000001601063321335200150360ustar00alexalex{ songtrk t { mute 0 track { bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/cut_b1.cmd010066400017510001751000000001131104167232700150140ustar00alexalexload "bend2.sng" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_b1.res010066400017510001751000000000421063321335200150360ustar00alexalex{ songtrk t { track { } } } midish-1.0.4/regress/cut_b2.cmd010066400017510001751000000001131104167232700150150ustar00alexalexload "bend2.sng" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_b2.res010066400017510001751000000001551063321335200150440ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/cut_b3.cmd010066400017510001751000000001131104167232700150160ustar00alexalexload "bend2.sng" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_b3.res010066400017510001751000000001551063321335200150450ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/cut_c0.cmd010066400017510001751000000001121104167232700150130ustar00alexalexload "note.sng" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_c0.res010066400017510001751000000000421063321335200150360ustar00alexalex{ songtrk t { track { } } } midish-1.0.4/regress/cut_c1.cmd010066400017510001751000000001121104167232700150140ustar00alexalexload "note.sng" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_c1.res010066400017510001751000000003641063321335200150460ustar00alexalex{ 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.0.4/regress/cut_c2.cmd010066400017510001751000000001121104167232700150150ustar00alexalexload "note.sng" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_c2.res010066400017510001751000000003641063321335200150470ustar00alexalex{ 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.0.4/regress/cut_c3.cmd010066400017510001751000000001121104167232700150160ustar00alexalexload "note.sng" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_c3.res010066400017510001751000000003641063321335300150510ustar00alexalex{ 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.0.4/regress/cut_d0.cmd010066400017510001751000000001111104167232700150130ustar00alexalexload "ctl.sng" ct t; g 0; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_d0.res010066400017510001751000000000641063321335300150440ustar00alexalex{ songtrk t { track { ctl {0 0} 7 65 } } } midish-1.0.4/regress/cut_d1.cmd010066400017510001751000000001111104167232700150140ustar00alexalexload "ctl.sng" ct t; g 1; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_d1.res010066400017510001751000000000421063321335300150410ustar00alexalex{ songtrk t { track { } } } midish-1.0.4/regress/cut_d2.cmd010066400017510001751000000001111104167232700150150ustar00alexalexload "ctl.sng" ct t; g 2; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_d2.res010066400017510001751000000000721063321335300150450ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/cut_d3.cmd010066400017510001751000000001111104167232700150160ustar00alexalexload "ctl.sng" ct t; g 3; sel 1; tcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/cut_d3.res010066400017510001751000000000721063321335300150460ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/filt.sng010066400017510001751000000002411117021240200146060ustar00alexalex{ 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.0.4/regress/fmap_a0.cmd010066400017510001751000000000551117021240200151320ustar00alexalexload "filt.sng" fmap {any {0 0}} {any {1 1}} midish-1.0.4/regress/fmap_a0.res010066400017510001751000000003071117021240200151600ustar00alexalex{ 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.0.4/regress/fmap_a1.cmd010066400017510001751000000000551117021240200151330ustar00alexalexload "filt.sng" fmap {any {0 0}} {any {3 8}} midish-1.0.4/regress/fmap_a1.res010066400017510001751000000003001117021240200151520ustar00alexalex{ 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.0.4/regress/fmap_a3.cmd010066400017510001751000000000551117021240200151350ustar00alexalexload "filt.sng" fmap {any {7 9}} {any {0 1}} midish-1.0.4/regress/fmap_a3.res010066400017510001751000000002221117021240200151570ustar00alexalex{ 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.0.4/regress/fnew.cmd010066400017510001751000000001661117021240200145710ustar00alexalexfnew 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.0.4/regress/fnew.res010066400017510001751000000002411117021240200146110ustar00alexalex{ 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.0.4/regress/insert_a0.cmd010066400017510001751000000001061104167232700155250ustar00alexalexload "bend1.sng" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_a0.res010066400017510001751000000001561063321335300155540ustar00alexalex{ songtrk t { track { 144 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.0.4/regress/insert_a1.cmd010066400017510001751000000001061104167232700155260ustar00alexalexload "bend1.sng" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_a1.res010066400017510001751000000002371063321335300155550ustar00alexalex{ 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.0.4/regress/insert_a2.cmd010066400017510001751000000001061104167232700155270ustar00alexalexload "bend1.sng" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_a2.res010066400017510001751000000002371063321335300155560ustar00alexalex{ 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.0.4/regress/insert_a3.cmd010066400017510001751000000001061104167232700155300ustar00alexalexload "bend1.sng" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_a3.res010066400017510001751000000001551063321335300155560ustar00alexalex{ songtrk t { track { 48 bend {0 0} 0 65 96 bend {0 0} 0 66 96 bend {0 0} 0 64 } } } midish-1.0.4/regress/insert_b0.cmd010066400017510001751000000001061104167232700155260ustar00alexalexload "bend2.sng" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_b0.res010066400017510001751000000001561063321335300155550ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/insert_b1.cmd010066400017510001751000000001061104167232700155270ustar00alexalexload "bend2.sng" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_b1.res010066400017510001751000000001601063321335300155510ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/insert_b2.cmd010066400017510001751000000001061104167232700155300ustar00alexalexload "bend2.sng" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mcut_a0.cmd010066400017510001751000000000541117010555200151650ustar00alexalexload "sign2.sng" g 0 sel 1 mcut g 0; sel 0 midish-1.0.4/regress/insert_b2.res010066400017510001751000000001551063321335300155560ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/insert_b3.cmd010066400017510001751000000001061104167232700155310ustar00alexalexload "bend2.sng" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_b3.res010066400017510001751000000001551063321335300155570ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 65 48 bend {0 0} 0 66 48 bend {0 0} 0 64 } } } midish-1.0.4/regress/insert_c0.cmd010066400017510001751000000001051104167232700155260ustar00alexalexload "note.sng" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_c0.res010066400017510001751000000003651063321335300155600ustar00alexalex{ 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.0.4/regress/insert_c1.cmd010066400017510001751000000001051104167232700155270ustar00alexalexload "note.sng" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_c1.res010066400017510001751000000003641063321335300155600ustar00alexalex{ 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.0.4/regress/insert_c2.cmd010066400017510001751000000001051104167232700155300ustar00alexalexload "note.sng" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_c2.res010066400017510001751000000003641063321335300155610ustar00alexalex{ 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.0.4/regress/insert_c3.cmd010066400017510001751000000001051104167232700155310ustar00alexalexload "note.sng" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_c3.res010066400017510001751000000002131063321335300155530ustar00alexalex{ 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.0.4/regress/insert_d0.cmd010066400017510001751000000001041104167232700155260ustar00alexalexload "ctl.sng" ct t; g 0; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_d0.res010066400017510001751000000000731063321335300155550ustar00alexalex{ songtrk t { track { 192 ctl {0 0} 7 65 } } } midish-1.0.4/regress/insert_d1.cmd010066400017510001751000000001041104167232700155270ustar00alexalexload "ctl.sng" ct t; g 1; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_d1.res010066400017510001751000000000731063321335300155560ustar00alexalex{ songtrk t { track { 192 ctl {0 0} 7 65 } } } midish-1.0.4/regress/insert_d2.cmd010066400017510001751000000001041104167232700155300ustar00alexalexload "ctl.sng" ct t; g 2; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_d2.res010066400017510001751000000000721063321335300155560ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/insert_d3.cmd010066400017510001751000000001041104167232700155310ustar00alexalexload "ctl.sng" ct t; g 3; tins 1 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/insert_d3.res010066400017510001751000000000721063321335300155570ustar00alexalex{ songtrk t { track { 96 ctl {0 0} 7 65 } } } midish-1.0.4/regress/mcut_a0.res010066400017510001751000000001011117010555200152040ustar00alexalex{ meta { tempo 400000 timesig 2 24 96 timesig 4 24 } } midish-1.0.4/regress/mcut_a1.cmd010066400017510001751000000000541117010555200151660ustar00alexalexload "sign2.sng" g 1 sel 1 mcut g 0; sel 0 midish-1.0.4/regress/mcut_a1.res010066400017510001751000000001251117010555200152130ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 timesig 2 24 48 timesig 4 24 } } midish-1.0.4/regress/mcut_a2.cmd010066400017510001751000000000541117010555200151670ustar00alexalexload "sign2.sng" g 1 sel 2 mcut g 0; sel 0 midish-1.0.4/regress/mcut_a2.res010066400017510001751000000000551117010555200152160ustar00alexalex{ meta { tempo 400000 timesig 4 24 } } midish-1.0.4/regress/mdup_a0.cmd010066400017510001751000000000561117010757400151720ustar00alexalexload "sign3.sng" g 0 sel 1 mdup 6 g 0; sel 0 midish-1.0.4/regress/mdup_a0.res010066400017510001751000000002611117010757400152160ustar00alexalex{ 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.0.4/regress/mdup_a1.cmd010066400017510001751000000000561117010757400151730ustar00alexalexload "sign3.sng" g 0 sel 1 mdup 7 g 0; sel 0 midish-1.0.4/regress/mdup_a1.res010066400017510001751000000002611117010757400152170ustar00alexalex{ 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.0.4/regress/mdup_a2.cmd010066400017510001751000000000561117010757400151740ustar00alexalexload "sign3.sng" g 0 sel 1 mdup 8 g 0; sel 0 midish-1.0.4/regress/mdup_a2.res010066400017510001751000000003661117010757400152260ustar00alexalex{ 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.0.4/regress/mdup_a3.cmd010066400017510001751000000000561117010757400151750ustar00alexalexload "sign3.sng" g 0 sel 1 mdup 9 g 0; sel 0 midish-1.0.4/regress/mdup_a3.res010066400017510001751000000003471117010757400152260ustar00alexalex{ 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.0.4/regress/mdup_a4.cmd010066400017510001751000000000571117010757400151770ustar00alexalexload "sign3.sng" g 0 sel 1 mdup 10 g 0; sel 0 midish-1.0.4/regress/mdup_a4.res010066400017510001751000000003471117010757400152270ustar00alexalex{ 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.0.4/regress/mdup_b0.cmd010066400017510001751000000000561117010757400151730ustar00alexalexload "sign3.sng" g 0 sel 2 mdup 5 g 0; sel 0 midish-1.0.4/regress/mdup_b0.res010066400017510001751000000003311117010757400152150ustar00alexalex{ 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.0.4/regress/mdup_b1.cmd010066400017510001751000000000561117010757400151740ustar00alexalexload "sign3.sng" g 0 sel 2 mdup 6 g 0; sel 0 midish-1.0.4/regress/mdup_b1.res010066400017510001751000000003351117010757400152220ustar00alexalex{ 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.0.4/regress/mdup_b2.cmd010066400017510001751000000000561117010757400151750ustar00alexalexload "sign3.sng" g 0 sel 2 mdup 7 g 0; sel 0 midish-1.0.4/regress/mdup_b2.res010066400017510001751000000004121117010757400152170ustar00alexalex{ 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.0.4/regress/mdup_b3.cmd010066400017510001751000000000561117010757400151760ustar00alexalexload "sign3.sng" g 0 sel 2 mdup 8 g 0; sel 0 midish-1.0.4/regress/mdup_b3.res010066400017510001751000000004171117010757400152250ustar00alexalex{ 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.0.4/regress/mdup_b4.cmd010066400017510001751000000000561117010757400151770ustar00alexalexload "sign3.sng" g 0 sel 2 mdup 9 g 0; sel 0 midish-1.0.4/regress/mdup_b4.res010066400017510001751000000004201117010757400152200ustar00alexalex{ 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.0.4/regress/mergebend_e1.cmd010066400017510001751000000001221104167232700161540ustar00alexalexload "bend_e1.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mins_a0.cmd010066400017510001751000000000431117010555200151610ustar00alexalexload "sign2.sng" g 0 mins 1 {6 8} midish-1.0.4/regress/mergebend_e1.res010066400017510001751000000002341063321335300162020ustar00alexalex{ 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.0.4/regress/mergebend_e2.cmd010066400017510001751000000001221104167232700161550ustar00alexalexload "bend_e2.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_e2.res010066400017510001751000000002041063321335300162000ustar00alexalex{ 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.0.4/regress/mergebend_e3.cmd010066400017510001751000000001221104167232700161560ustar00alexalexload "bend_e3.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_e3.res010066400017510001751000000002031063321335300162000ustar00alexalex{ 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.0.4/regress/mergebend_e4.cmd010066400017510001751000000001221104167232700161570ustar00alexalexload "bend_e4.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_e4.res010066400017510001751000000001511063321335300162030ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 384 bend {0 0} 0 124 96 bend {0 0} 0 64 } } } midish-1.0.4/regress/mergebend_e5.cmd010066400017510001751000000001221104167232700161600ustar00alexalexload "bend_e5.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_e5.res010066400017510001751000000001201063321335300162000ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 480 bend {0 0} 0 64 } } } midish-1.0.4/regress/mergebend_e6.cmd010066400017510001751000000001221104167232700161610ustar00alexalexload "bend_e6.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_e6.res010066400017510001751000000001171063321335300162070ustar00alexalex{ songtrk t { track { bend {0 0} 0 50 576 bend {0 0} 0 64 } } } midish-1.0.4/regress/mergebend_s1.cmd010066400017510001751000000001221104167232700161720ustar00alexalexload "bend_s1.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_s1.res010066400017510001751000000001251063321335300162170ustar00alexalex{ songtrk t { track { 96 bend {0 0} 0 50 576 bend {0 0} 0 64 } } } midish-1.0.4/regress/mergebend_s2.cmd010066400017510001751000000001221104167232700161730ustar00alexalexload "bend_s2.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_s2.res010066400017510001751000000001261063321335300162210ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 50 480 bend {0 0} 0 64 } } } midish-1.0.4/regress/mergebend_s3.cmd010066400017510001751000000001221104167232700161740ustar00alexalexload "bend_s3.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_s3.res010066400017510001751000000001601063321335300162200ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 100 96 bend {0 0} 0 50 384 bend {0 0} 0 64 } } } midish-1.0.4/regress/mergebend_s4.cmd010066400017510001751000000001221104167232700161750ustar00alexalexload "bend_s4.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_s4.res010066400017510001751000000001611063321335300162220ustar00alexalex{ songtrk t { track { 192 bend {0 0} 0 100 192 bend {0 0} 0 50 288 bend {0 0} 0 64 } } } midish-1.0.4/regress/mergebend_s5.cmd010066400017510001751000000001221104167232700161760ustar00alexalexload "bend_s5.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_s5.res010066400017510001751000000002131063321335300162210ustar00alexalex{ 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.0.4/regress/mergebend_s6.cmd010066400017510001751000000001221104167232700161770ustar00alexalexload "bend_s6.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergebend_s6.res010066400017510001751000000002431063321335300162250ustar00alexalex{ 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.0.4/regress/mergenote_e1.cmd010066400017510001751000000001221104167232700162110ustar00alexalexload "note_e1.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_e1.res010066400017510001751000000002371063321335300162420ustar00alexalex{ 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.0.4/regress/mergenote_e2.cmd010066400017510001751000000001221104167232700162120ustar00alexalexload "note_e2.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_e2.res010066400017510001751000000001201063321335300162320ustar00alexalex{ songtrk t { track { non {0 0} 65 50 192 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_e3.cmd010066400017510001751000000001221104167232700162130ustar00alexalexload "note_e3.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_e3.res010066400017510001751000000001201063321335300162330ustar00alexalex{ songtrk t { track { non {0 0} 65 50 288 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_e4.cmd010066400017510001751000000001221104167232700162140ustar00alexalexload "note_e4.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_e4.res010066400017510001751000000001201063321335300162340ustar00alexalex{ songtrk t { track { non {0 0} 65 50 384 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_e5.cmd010066400017510001751000000001221104167232700162150ustar00alexalexload "note_e5.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_e5.res010066400017510001751000000001201063321335300162350ustar00alexalex{ songtrk t { track { non {0 0} 65 50 480 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_e6.cmd010066400017510001751000000001221104167232700162160ustar00alexalexload "note_e6.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_e6.res010066400017510001751000000001201063321335300162360ustar00alexalex{ songtrk t { track { non {0 0} 65 50 576 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_s1.cmd010066400017510001751000000001221104167232700162270ustar00alexalexload "note_s1.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_s1.res010066400017510001751000000001261063321335300162550ustar00alexalex{ songtrk t { track { 96 non {0 0} 65 50 576 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_s2.cmd010066400017510001751000000001221104167232700162300ustar00alexalexload "note_s2.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_s2.res010066400017510001751000000001271063321335300162570ustar00alexalex{ songtrk t { track { 192 non {0 0} 65 50 480 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_s3.cmd010066400017510001751000000001221104167232700162310ustar00alexalexload "note_s3.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_s3.res010066400017510001751000000001271063321335400162610ustar00alexalex{ songtrk t { track { 288 non {0 0} 65 50 384 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_s4.cmd010066400017510001751000000001221104167232700162320ustar00alexalexload "note_s4.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_s4.res010066400017510001751000000001271063321335400162620ustar00alexalex{ songtrk t { track { 384 non {0 0} 65 50 288 noff {0 0} 65 50 } } } midish-1.0.4/regress/mergenote_s5.cmd010066400017510001751000000001221104167232700162330ustar00alexalexload "note_s5.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_s5.res010066400017510001751000000002411063321335400162600ustar00alexalex{ 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.0.4/regress/mergenote_s6.cmd010066400017510001751000000001221104167232700162340ustar00alexalexload "note_s6.sng" ct t2; tmerge t ct t2; tdel g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/mergenote_s6.res010066400017510001751000000002461063321335400162660ustar00alexalex{ 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.0.4/regress/mins_a0.res010066400017510001751000000002451117010555200152130ustar00alexalex{ 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.0.4/regress/mins_a1.cmd010066400017510001751000000000471117010555200151660ustar00alexalexload "sign2.sng" g 1 mins 1 {6 8} g 0 midish-1.0.4/regress/mins_a1.res010066400017510001751000000001631117010555200152130ustar00alexalex{ meta { tempo 400000 timesig 4 24 96 timesig 6 12 72 timesig 2 24 96 timesig 4 24 } curlen 1 } midish-1.0.4/regress/mins_a2.cmd010066400017510001751000000000471117010555200151670ustar00alexalexload "sign2.sng" g 2 mins 1 {6 8} g 0 midish-1.0.4/regress/mins_a2.res010066400017510001751000000002071117010555200152130ustar00alexalex{ 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.0.4/regress/mins_a3.cmd010066400017510001751000000000471117010555200151700ustar00alexalexload "sign2.sng" g 3 mins 1 {6 8} g 0 midish-1.0.4/regress/mins_a3.res010066400017510001751000000002451117010555200152160ustar00alexalex{ 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.0.4/regress/mins_a4.cmd010066400017510001751000000000471117010555200151710ustar00alexalexload "sign2.sng" g 4 mins 1 {6 8} g 0 midish-1.0.4/regress/mins_a4.res010066400017510001751000000002711117010555200152160ustar00alexalex{ 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.0.4/regress/note.sng010066400017510001751000000002211063321335400146250ustar00alexalex{ 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.0.4/regress/note2.sng010066400017510001751000000001751063321335400147170ustar00alexalex{ 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.0.4/regress/note_e1.sng010066400017510001751000000003111063321335400152120ustar00alexalex{ 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.0.4/regress/note_e2.sng010066400017510001751000000003171063321335400152210ustar00alexalex{ 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.0.4/regress/note_e3.sng010066400017510001751000000003251063321335400152210ustar00alexalex{ 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.0.4/regress/note_e4.sng010066400017510001751000000003331063321335400152210ustar00alexalex{ 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.0.4/regress/note_e5.sng010066400017510001751000000003411063321335400152210ustar00alexalex{ 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.0.4/regress/note_e6.sng010066400017510001751000000003471063321335400152300ustar00alexalex{ 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.0.4/regress/note_s1.sng010066400017510001751000000003551063321335400152400ustar00alexalex{ 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.0.4/regress/note_s2.sng010066400017510001751000000003551063321335400152410ustar00alexalex{ 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.0.4/regress/note_s3.sng010066400017510001751000000003551063321335400152420ustar00alexalex{ 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.0.4/regress/note_s4.sng010066400017510001751000000003551063321335400152430ustar00alexalex{ 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.0.4/regress/note_s5.sng010066400017510001751000000003551063321335400152440ustar00alexalex{ 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.0.4/regress/note_s6.sng010066400017510001751000000003551063321335400152450ustar00alexalex{ 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.0.4/regress/quant.cmd010066400017510001751000000001461104167232700147750ustar00alexalexload "quant.sng" g 0 ct t sel 16 setq 24 tquant 100 sel 0 setq nil g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/quant.res010066400017510001751000000006251063321335400150220ustar00alexalex{ 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.0.4/regress/quant.sng010066400017510001751000000006371063321335400150230ustar00alexalex{ 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.0.4/regress/run-test010077500017510001751000000020321104156455100146620ustar00alexalex#!/bin/sh # # for each command line argument, run the assocated 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 is no difference between # actual and expected results. If there is difference # the test is failed and $testname.diff and # $testname.log files are kept. # #set -x if [ -n "$*" ]; then list=$* else list=*.cmd fi # # loop over all tests # for i in $list; 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 $i: passed rm -- $i.tmp1 $i.tmp2 $i.diff $i.log else echo $i: FAILED 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.0.4/regress/sign.sng010066400017510001751000000000671063321335400146300ustar00alexalex{ meta { 96 timesig 3 24 48 timesig 4 24 } } midish-1.0.4/regress/sign2.sng010066400017510001751000000002071117011007200146740ustar00alexalex{ 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.0.4/regress/sign3.sng010066400017510001751000000003471117011007200147020ustar00alexalex{ 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.0.4/regress/tempo.cmd010066400017510001751000000001141104167232700147640ustar00alexalexg 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.0.4/regress/tempo.res010066400017510001751000000000631063321335400150120ustar00alexalex{ meta { timesig 4 24 tempo 250000 192 } } midish-1.0.4/regress/time.sng010066400017510001751000000001361063321335400146230ustar00alexalex{ meta { timesig 5 12 600 timesig 5 24 1200 timesig 5 12 600 timesig 4 24 } } midish-1.0.4/regress/timeins.cmd010066400017510001751000000001221104167232700153070ustar00alexalexg 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.0.4/regress/timeins.res010066400017510001751000000001511063321335400153340ustar00alexalex{ meta { timesig 2 48 tempo 500000 96 timesig 4 24 96 timesig 8 12 96 timesig 4 24 } } midish-1.0.4/regress/timerm.cmd010066400017510001751000000001051116566254000151400ustar00alexalexload "time.sng" g 10 sel 10; mcut g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/timerm.res010066400017510001751000000000641063321335400151640ustar00alexalex{ meta { timesig 5 12 1200 timesig 4 24 } } midish-1.0.4/regress/timesig.cmd010066400017510001751000000001211104167232700152770ustar00alexalexload "sign.sng" g 1 t 60 g 2 t 120 g 3 t 240 g 0; sel 0; ct nil; ci nil; co nil midish-1.0.4/regress/timesig.res010066400017510001751000000001571063321335400153330ustar00alexalex{ meta { 96 timesig 3 24 tempo 1000000 48 timesig 4 24 24 tempo 500000 96 tempo 250000 } } midish-1.0.4/regress/update-test010077500017510001751000000003451063321335400153430ustar00alexalex#!/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.0.4/smfplay.1010066400017510001751000000061231143320306000132310ustar00alexalex.\" .\" Copyright (c) 2003-2010 Alexandre Ratchov .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" .\" - Redistributions of source code must retain the above .\" copyright notice, this list of conditions and the .\" following disclaimer. .\" .\" - Redistributions in binary form must reproduce the above .\" copyright notice, this list of conditions and the .\" following disclaimer in the documentation and/or other .\" materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS .\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT .\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR .\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT .\" OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, .\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT .\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd August 22, 2005 .Dt SMFPLAY 1 .Os .Sh NAME .Nm smfplay .Nd play a standard MIDI file .Sh SYNOPSIS .Nm smfplay .Op -mxy .Op -g Ar measure .Op -d Ar devname .Op -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 .Pa 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 smfrec 1 , .Xr midish 1 , .Xr midiplay 1 , .Xr midi 4 midish-1.0.4/smfrec010077500017510001751000000014511104462352600127130ustar00alexalex#!/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 < .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" .\" - Redistributions of source code must retain the above .\" copyright notice, this list of conditions and the .\" following disclaimer. .\" .\" - Redistributions in binary form must reproduce the above .\" copyright notice, this list of conditions and the .\" following disclaimer in the documentation and/or other .\" materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS .\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT .\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR .\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT .\" OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, .\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT .\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, .\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" .Dd August 27, 2005 .Dt SMFREC 1 .Os .Sh NAME .Nm smfrec .Nd record a standard MIDI file .Sh SYNOPSIS .Nm smfrec .Op -amxy .Op -g Ar measure .Op -d Ar devname .Op -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 .Pa 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 smfplay 1 , .Xr midish 1 , .Xr midi 4 midish-1.0.4/song.c010066400017510001751000000626471143320306000126230ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "dbg.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" unsigned song_debug = 0; /* * allocate and initialize a song structure */ struct song * song_new(void) { struct song *o; o = mem_alloc(sizeof(struct song), "song"); song_init(o); return o; } /* * free the given song structure */ void song_delete(struct song *o) { song_done(o); mem_free(o); } /* * initialize the given song */ void song_init(struct song *o) { struct seqev *se; /* * song parameters */ o->mode = 0; o->trklist = NULL; o->outlist = NULL; o->inlist = NULL; o->filtlist = NULL; o->sxlist = NULL; o->tics_per_unit = DEFAULT_TPU; track_init(&o->meta); track_init(&o->clip); track_init(&o->rec); /* * 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; /* * 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; evspec_reset(&o->curev); /* * 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); } while (o->trklist) { song_trkdel(o, (struct songtrk *)o->trklist); } while (o->outlist) { song_chandel(o, (struct songchan *)o->outlist, 0); } while (o->inlist) { song_chandel(o, (struct songchan *)o->inlist, 1); } 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); metro_done(&o->metro); } /* * create a new track in the song */ struct songtrk * song_trknew(struct song *o, char *name) { struct songtrk *t; t = mem_alloc(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); mem_free(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; struct name **list = input ? &o->inlist : &o->outlist; c = mem_alloc(sizeof(struct songchan), "songchan"); name_init(&c->name, name); track_init(&c->conf); c->link = NULL; c->dev = dev; c->ch = ch; name_add(list, (struct name *)c); if (!input) { f = song_filtlookup(o, name); if (f == NULL) f = song_filtnew(o, name); f->link = c; c->link = f; } song_setcurchan(o, c, input); return c; } /* * delete the given chan from the song */ void song_chandel(struct song *o, struct songchan *c, int input) { struct songfilt *f; struct name **list = input ? &o->inlist : &o->outlist; if (input) { if (o->curin == c) o->curin = NULL; } else { if (o->curout == c) o->curout = NULL; } name_remove(list, (struct name *)c); track_done(&c->conf); name_done(&c->name); f = c->link; if (f != NULL) { f->link = NULL; c->link = NULL; song_filtdel(o, f); } mem_free(c); } /* * return the chan with the given name */ struct songchan * song_chanlookup(struct song *o, char *name, int input) { struct name **list = input ? &o->inlist : &o->outlist; return (struct songchan *)name_lookup(list, name); } /* * 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 *i; struct name *list = input ? o->inlist : o->outlist; SONG_FOREACH_CHAN(o, i, list) { if (i->dev == dev && i->ch == ch) { return i; } } return 0; } /* * add a new filt to the song */ struct songfilt * song_filtnew(struct song *o, char *name) { struct songfilt *f; f = mem_alloc(sizeof(struct songfilt), "songfilt"); name_init(&f->name, name); filt_init(&f->filt); f->link = NULL; 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 songchan *c; 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); c = f->link; if (c != NULL) { c->link = NULL; f->link = NULL; song_chandel(o, c, 0); } mem_free(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 = mem_alloc(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); mem_free(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; o->curfilt = f; if (f != NULL) song_setcurchan(o, f->link, 0); if (mux_isopen) { norm_setfilt(f != NULL ? &f->filt : NULL); } } 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->link); } void song_setunit(struct song *o, unsigned tpu) { struct songtrk *t; unsigned mult; mult = (tpu + DEFAULT_TPU - 1) / DEFAULT_TPU; if (mult == 0) mult = 1; tpu = mult * DEFAULT_TPU; SONG_FOREACH_TRK(o, t) { track_scale(&t->track, o->tics_per_unit, tpu); } track_scale(&o->meta, o->tics_per_unit, tpu); o->curquant = o->curquant * tpu / o->tics_per_unit; o->tics_per_unit = tpu; } 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, int input, struct ev *in) { struct ev ev = *in; if (!EV_ISVOICE(&ev)) { dbg_puts("song_playconfev: "); dbg_puts(c->name.str); dbg_puts(": "); ev_dbg(&ev); dbg_puts(": not a voice event"); dbg_puts("\n"); return; } ev.dev = c->dev; ev.ch = c->ch; if (!input) { mixout_putev(&ev, 0); } else { norm_putev(&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_IN(o, i) { cp = seqptr_new(&i->conf); for (;;) { st = seqptr_evget(cp); if (st == NULL) break; song_playconfev(o, i, 1, &st->ev); } seqptr_del(cp); } SONG_FOREACH_OUT(o, i) { cp = seqptr_new(&i->conf); for (;;) { st = seqptr_evget(cp); if (st == NULL) break; song_playconfev(o, i, 0, &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; } } /* * 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 songtrk *i; unsigned neot; /* * 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++; } } #if 0 if (song_debug) { dbg_puts("song_ticskip: "); dbg_putu(o->measure); dbg_puts(":"); dbg_putu(o->beat); dbg_puts(":"); dbg_putu(o->tic); dbg_puts("\n"); } #endif SONG_FOREACH_TRK(o, i) { neot |= seqptr_ticskip(i->trackptr, 1); } if (neot == 0) 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; unsigned id; 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) { id = i->trackptr->statelist.serial; 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, id); } } } /* * restore all frames in the given statelist. First, restore events * that have context, then the rest. */ void song_confrestore(struct statelist *slist) { struct state *s; struct ev re; for (s = slist->first; s != NULL; s = s->next) { if (!s->tag && !EV_ISNOTE(&s->ev)) { if (state_restore(s, &re)) { if (song_debug) { dbg_puts("song_strestore: "); ev_dbg(&s->ev); dbg_puts(": restored -> "); ev_dbg(&re); dbg_puts("\n"); } mixout_putev(&re, slist->serial); } s->tag = 1; } else { if (song_debug) { dbg_puts("song_strestore: "); ev_dbg(&s->ev); dbg_puts(": not restored (not tagged)\n"); } } } } /* * cancel all frames in the given state list */ void song_confcancel(struct statelist *slist) { 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) { dbg_puts("song_stcancel: "); ev_dbg(&s->ev); dbg_puts(": canceled -> "); ev_dbg(&ca); dbg_puts("\n"); } mixout_putev(&ca, slist->serial); } s->tag = 0; } else { if (song_debug) { dbg_puts("song_stcancel: "); ev_dbg(&s->ev); dbg_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); 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); t->mute = 0; } /* * merge recorded track into current track */ void song_mergerec(struct song *o) { struct songtrk *t; struct state *s; struct ev ev; /* * if there is no filter for recording there may be * unterminated frames, so finalize them. */ for (s = o->recptr->statelist.first; s != NULL; s = s->next) { if (!(s->phase & EV_PHASE_LAST) && state_cancel(s, &ev)) { seqptr_evput(o->recptr, &ev); } } song_getcurtrk(o, &t); if (t) { track_merge(&o->curtrk->track, &o->rec); } track_clear(&o->rec); } /* * call-back called when the first midi tick arrives */ void song_startcb(struct song *o) { if (song_debug) { dbg_puts("song_startcb:\n"); } if (o->mode >= SONG_PLAY) { song_ticplay(o); mux_flush(); } } /* * call-back called when the midi clock stopped */ void song_stopcb(struct song *o) { struct songtrk *t; if (song_debug) dbg_puts("song_stopcb:\n"); /* * stop all sounding notes */ SONG_FOREACH_TRK(o, t) { song_confcancel(&t->trackptr->statelist); } } /* * call-back, called when the midi clock moved one tick forward */ void song_movecb(struct song *o) { unsigned delta; if (o->mode >= SONG_REC) { while (seqptr_evget(o->recptr)) ; /* nothing */ delta = seqptr_ticskip(o->recptr, 1); if (delta == 0) seqptr_ticput(o->recptr, 1); } 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) { if (o->mode >= SONG_REC) { if (mux_getphase() >= MUX_START) (void)seqptr_evput(o->recptr, ev); } } /* * call-back called when a sysex message arrives */ void song_sysexcb(struct song *o, struct sysex *sx) { if (song_debug) dbg_puts("song_sysexcb:\n"); if (sx == NULL) { dbg_puts("got null sx\n"); return; } if (o->mode >= SONG_REC && o->cursx) sysexlist_put(&o->cursx->sx, sx); else sysex_del(sx); } /* * 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 where, unsigned how) { struct state *s; struct songtrk *t; unsigned maxdelta, delta, tic; unsigned bpm, tpb, offs; unsigned long long pos, endpos; unsigned long usec24; seqptr_del(o->metaptr); o->metaptr = seqptr_new(&o->meta); /* please gcc */ endpos = 0xdeadbeef; offs = 0xdeadbeef; maxdelta = 0xdeadbeef; /* * XXX: when not in LOC_MTC mode, the MTC position * can overflow. Limit song_loc() to 24 hours */ switch (how) { case SONG_LOC_MEAS: offs = (o->mode >= SONG_PLAY) ? o->curquant / 2 : 0; break; case SONG_LOC_MTC: endpos = (unsigned long long)where * (24000000 / MTC_SEC); offs = 0; break; case SONG_LOC_SPP: where *= o->tics_per_unit / 16; offs = 0; break; default: dbg_puts("song_loc: bad argument\n"); dbg_panic(); } tic = 0; pos = 0; o->measure = o->beat = o->tic = 0; for (;;) { seqptr_getsign(o->metaptr, &bpm, &tpb); seqptr_gettempo(o->metaptr, &usec24); switch (how) { case SONG_LOC_MEAS: maxdelta = (where - o->measure) * bpm * tpb - o->beat * tpb - o->tic; break; case SONG_LOC_MTC: maxdelta = (endpos - pos) / usec24; break; case SONG_LOC_SPP: maxdelta = where - tic; 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; pos += (unsigned long long)delta * usec24; tic += delta; } /* * move all tracks to the current position */ SONG_FOREACH_TRK(o, t) { /* * cancel and free old states */ song_confcancel(&t->trackptr->statelist); statelist_empty(&t->trackptr->statelist); seqptr_del(t->trackptr); /* * allocate and restore new states */ t->trackptr = seqptr_new(&t->track); seqptr_skip(t->trackptr, tic); song_confrestore(&t->trackptr->statelist); } if (o->mode >= SONG_REC) track_clear(&o->rec); #ifdef SONG_DEBUG if (!track_isempty(&o->rec)) { dbg_puts("song_loc: rec track not empty\n"); dbg_panic(); } #endif seqptr_del(o->recptr); o->recptr = seqptr_new(&o->rec); if (o->mode >= SONG_REC) { seqptr_seek(o->recptr, tic); } for (s = o->metaptr->statelist.first; s != NULL; s = s->next) { if (EV_ISMETA(&s->ev)) { if (song_debug) { dbg_puts("song_loc: "); ev_dbg(&s->ev); dbg_puts(": restoring meta-event\n"); } song_metaput(o, s); s->tag = 1; } else { if (song_debug) { dbg_puts("song_loc: "); ev_dbg(&s->ev); dbg_puts(": not restored (not tagged)\n"); } s->tag = 0; } } pos /= 24000000 / MTC_SEC; if (song_debug) { dbg_puts("song_loc: "); dbg_putu(where); dbg_puts(" -> "); dbg_putu(o->measure); dbg_puts(":"); dbg_putu(o->beat); dbg_puts(":"); dbg_putu(o->tic); dbg_puts("/"); dbg_putu(tic); dbg_puts(", mtc = "); dbg_putu(pos); dbg_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, unsigned mtcpos) { unsigned newpos; newpos = song_loc(o, mtcpos, SONG_LOC_MTC); cons_putpos(o->measure, o->beat, o->tic); if (newpos > mtcpos) { dbg_puts("song_gotocb: negative offset\n"); dbg_panic(); } return (mtcpos - newpos) * (24000000 / MTC_SEC); } /* * set the current mode */ void song_setmode(struct song *o, unsigned newmode) { struct songfilt *f; 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_IDLE && newmode < SONG_IDLE) { /* * cancel and free states */ SONG_FOREACH_TRK(o, t) { song_confcancel(&t->trackptr->statelist); statelist_empty(&t->trackptr->statelist); seqptr_del(t->trackptr); } seqptr_del(o->recptr); seqptr_del(o->metaptr); norm_setfilt(NULL); mux_flush(); mux_close(); } if (oldmode < SONG_PLAY && newmode >= SONG_PLAY) { o->complete = 0; } if (oldmode < SONG_IDLE && newmode >= SONG_IDLE) { 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); mux_open(); /* * check for the current filter */ song_getcurfilt(o, &f); norm_setfilt(f != NULL ? &f->filt : NULL); /* * 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; if (o->mode >= SONG_IDLE) { /* * 1 measure of count-down for recording */ if (o->mode >= SONG_REC && measure > 0) measure--; /* * move all tracks to given measure */ mmcpos = song_loc(o, measure, SONG_LOC_MEAS); mux_gotoreq(mmcpos); /* * display initial position */ cons_putpos(o->measure, o->beat, o->tic); } 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(); if (song_debug) { dbg_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) { dbg_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(); if (song_debug) { dbg_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); if (song_debug) { dbg_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; } void song_confev(struct song *o, struct songchan *c, int input, struct ev *ev) { track_confev(&c->conf, ev); if (mux_isopen) { song_playconfev(o, c, input, ev); mux_flush(); } } void song_unconfev(struct song *o, struct songchan *c, int input, struct evspec *es) { track_unconfev(&c->conf, es); } midish-1.0.4/song.h010066400017510001751000000175361143320306000126250ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 songtrk { struct name name; /* identifier + list entry */ struct track track; /* actual data */ struct seqptr *trackptr; /* track pointer for RT */ struct songfilt *curfilt; /* source and dest. channel */ 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 */ struct songfilt *link; /* default filter (output only) */ }; struct songfilt { struct name name; /* identifier + list entry */ struct filt filt; /* filter rules */ struct songchan *link; /* default output channel */ }; 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 *outlist; /* list of output channels */ struct name *inlist; /* list of input channels */ struct name *filtlist; /* list of fiters */ struct name *sxlist; /* list of system exclive banks */ 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. */ /* * 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; /* cur position in 'rec' track */ 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 complete; /* playback completed */ unsigned metro_mask; /* if enable = (mask | mode) */ }; #define SONG_FOREACH_TRK(s, i) \ for (i = (struct songtrk *)(s)->trklist; \ i != NULL; \ i = (struct songtrk *)i->name.next) #define SONG_FOREACH_OUT(s, i) \ for (i = (struct songchan *)(s)->outlist; \ i != NULL; \ i = (struct songchan *)i->name.next) #define SONG_FOREACH_IN(s, i) \ for (i = (struct songchan *)(s)->inlist; \ i != NULL; \ i = (struct songchan *)i->name.next) #define SONG_FOREACH_CHAN(s, i, list) \ for (i = (struct songchan *)(list); \ 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) /* * how to relocate, used by song_loc() & friends */ #define SONG_LOC_MEAS 0 /* measure number */ #define SONG_LOC_MTC 1 /* MTC/MMC absolute time */ #define SONG_LOC_SPP 2 /* MIDI song position pointer */ 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 *, int, struct ev *); void song_unconfev(struct song *, struct songchan *, int, struct evspec *); void song_chandel(struct song *, struct songchan *, int); 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); void song_setunit(struct song *, unsigned); unsigned song_endpos(struct song *); void song_playconf(struct song *); unsigned song_loc(struct song *, unsigned, unsigned); 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 *); extern unsigned song_debug; #endif /* MIDISH_SONG_H */ midish-1.0.4/state.c010066400017510001751000000344611143320306000127660ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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_dbg(struct state *s) { ev_dbg(&s->ev); if (s->flags & STATE_NEW) { dbg_puts(" NEW"); } if (s->flags & STATE_CHANGED) { dbg_puts(" CHANGED"); } if (s->flags & STATE_BOGUS) { dbg_puts(" BOGUS"); } if (s->flags & STATE_NESTED) { dbg_puts(" NESTED"); } if (s->phase & EV_PHASE_FIRST) { dbg_puts(" FIRST"); } if (s->phase & EV_PHASE_NEXT) { dbg_puts(" NEXT"); } if (s->phase & EV_PHASE_LAST) { dbg_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) { switch (st->ev.cmd) { case EV_NON: case EV_NOFF: case EV_KAT: if (!EV_ISNOTE(ev) || st->ev.note_num != ev->note_num || st->ev.ch != ev->ch || st->ev.dev != ev->dev) { return 0; } break; case EV_XCTL: case EV_NRPN: case EV_RPN: if (st->ev.cmd != ev->cmd || st->ev.dev != ev->dev || st->ev.ch != ev->ch || st->ev.v0 != ev->v0) { return 0; } break; case EV_BEND: case EV_CAT: case EV_XPC: if (st->ev.cmd != ev->cmd || st->ev.dev != ev->dev || st->ev.ch != ev->ch) { return 0; } break; case EV_TEMPO: case EV_TIMESIG: if (st->ev.cmd != ev->cmd) { return 0; } break; default: dbg_puts("state_match: "); state_dbg(st); dbg_puts(": bad event type\n"); dbg_panic(); break; } #ifdef STATE_DEBUG dbg_puts("state_match: "); ev_dbg(&st->ev); dbg_puts(": ok\n"); #endif return 1; } /* * check if the given state belongs to the event spec */ unsigned state_inspec(struct state *st, struct evspec *spec) { if (spec == NULL) { return 1; } switch(spec->cmd) { case EVSPEC_EMPTY: return 0; case EVSPEC_ANY: goto ch; case EVSPEC_NOTE: if (!(st->phase & EV_PHASE_FIRST)) { dbg_puts("state_inspec: not first\n"); dbg_panic(); } if (st->ev.cmd == EV_NON) { goto v0; } break; case EVSPEC_CAT: if (st->ev.cmd == EV_CAT) { goto ch; } break; case EVSPEC_BEND: if (st->ev.cmd == EV_BEND) { goto ch; } break; case EVSPEC_XPC: if (st->ev.cmd == EV_XPC) { goto v1; } break; case EVSPEC_NRPN: if (st->ev.cmd == EV_NRPN) { goto v1; } break; case EVSPEC_RPN: if (st->ev.cmd == EV_RPN) { goto v1; } break; case EVSPEC_XCTL: if (st->ev.cmd == EV_XCTL) { goto v0; } break; default: break; } return 0; v1: if (st->ev.v1 < spec->v1_min || st->ev.v1 > spec->v1_max) { return 0; } v0: if (st->ev.v0 < spec->v0_min || st->ev.v0 > spec->v0_max) { return 0; } ch: if (st->ev.dev < spec->dev_min || st->ev.dev > spec->dev_max || 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) { 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 (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 { dbg_puts("state_eq: not defined\n"); dbg_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 */ dbg_puts("state_cancel: unknown event type\n"); dbg_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... */ dbg_puts("state_restore: can't restore note events\n"); dbg_panic(); } if ((st->phase & EV_PHASE_LAST) && !(st->phase & EV_PHASE_FIRST)) { dbg_puts("state_restore: "); state_dbg(st); dbg_puts(": called for last event!\n"); 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++; #ifdef STATE_PROF prof_reset(&o->prof, "statlist/iter"); #endif } /* * 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) { dbg_puts("statelist_done: "); ev_dbg(&i->ev); dbg_puts(": unterminated frame\n"); } inext = i->next; statelist_rm(o, i); state_del(i); } #ifdef STATE_PROF prof_dbg(&o->prof); #endif } void statelist_dump(struct statelist *o) { struct state *i; dbg_puts("statelist_dump:\n"); for (i = o->first; i != NULL; i = i->next) { ev_dbg(&i->ev); dbg_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; #ifdef STATE_PROF unsigned time = 0; #endif for (i = o->first; i != NULL; i = i->next) { #ifdef STATE_PROF time++; #endif if (state_match(i, ev)) { break; } } #ifdef STATE_PROF prof_val(&o->prof, time * 100); #endif 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; #ifdef STATE_PROF unsigned time = 0; #endif /* * we scan for a matching state, if it exists but is * terminated (phase = EV_PHASE_LAST) we purge it in order to * reuse the list entry. We cant just use statelist_lookup(), * because this will not work with nested frames (eg. if the * "top" state is purged but not the other one). So here we * inline a kind of 'lookup_for_write()' routine: */ st = statelist->first; for (;;) { #ifdef STATE_PROF time++; #endif if (st == NULL) { st = state_new(); statelist_add(statelist, st); st->flags = STATE_NEW; break; } stnext = st->next; if (state_match(st, ev)) { /* * found a matching state */ if (st->phase & EV_PHASE_LAST) { /* * if the event is not tagged as * nested, we reached the deepest * state, so stop iterating here * else continue purging states */ if (!(st->flags & STATE_NESTED)) { st->flags = 0; break; } else { statelist_rm(statelist, st); state_del(st); } } else { st->flags &= ~STATE_NEW; break; } } st = stnext; } #ifdef STATE_PROF prof_val(&statelist->prof, time * 100); #endif /* * if one of the following are true * - there is no state because this is the first * event of a new frame, or this is a bogus next event * (the beginning is missing) * - there is a state, but this is for sure the * first event of a new frame (thus this is * de beginning of a nested frame) * then create a new state. */ phase = ev_phase(ev); if (st->flags & STATE_NEW || st->phase & EV_PHASE_LAST) { /* * this is new state or a terminated frame that we are * reusing */ if (!(phase & EV_PHASE_FIRST)) { phase = EV_PHASE_FIRST | EV_PHASE_LAST; st->flags |= STATE_BOGUS; #ifdef STATE_DEBUG dbg_puts("statelist_update: "); ev_dbg(ev); dbg_puts(": not first and no state\n"); #endif } else { phase &= ~EV_PHASE_NEXT; } } else if (phase == EV_PHASE_FIRST) { /* * this frame is not yet terminated. the incoming * event starts a new one that's conflicting */ #ifdef STATE_DEBUG dbg_puts("statelist_update: "); ev_dbg(ev); dbg_puts(": nested events, stacked\n"); #endif st = state_new(); statelist_add(statelist, st); st->flags = STATE_NESTED | STATE_NEW; } else { /* * this frame is not yet terminated, the incoming * event belongs to it */ phase &= ~EV_PHASE_FIRST; } state_copyev(st, ev, phase); statelist->changed = 1; #ifdef STATE_DEBUG dbg_puts("statelist_update: updated: "); state_dbg(st); dbg_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) { statelist_rm(o, i); state_del(i); } else { i->flags &= ~STATE_CHANGED; } } } midish-1.0.4/state.h010066400017510001751000000101371143320306000127650ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_STATE_H #define MIDISH_STATE_H #include "ev.h" #include "dbg.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_dbg(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.0.4/str.c010066400017510001751000000057241143320306000124560ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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) { dbg_puts("str_new: NULL pointer argument\n"); dbg_panic(); } cnt = str_len(val) + 1; buf = mem_alloc(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) { dbg_puts("str_delete: NULL pointer argument\n"); dbg_panic(); } mem_free(s); } /* * print the string to stderr; can be safely used even if the string * is NULL */ void str_dbg(char *s) { if (s == NULL) { dbg_puts(""); } else { dbg_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) { dbg_puts("std_id: NULL pointer argument\n"); dbg_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; } midish-1.0.4/str.h010066400017510001751000000031211143320306000124500ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_STR_H #define MIDISH_STR_H char *str_new(char *); void str_delete(char *); void str_dbg(char *); unsigned str_eq(char *, char *); unsigned str_len(char *); #endif /* MIDISH_STR_H */ midish-1.0.4/sysex.c010066400017510001751000000127701143320306000130200ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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_dbg(struct sysex *o) { struct chunk *ck; unsigned i; dbg_puts("unit = "); dbg_putx(o->unit); dbg_puts(", data = { "); for (ck = o->first; ck != NULL; ck = ck->next) { for (i = 0; i < ck->used; i++) { dbg_putx(ck->data[i]); dbg_puts(" "); } } dbg_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) { 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 on 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; } /* * dump a sysex list on stderr */ void sysexlist_dbg(struct sysexlist *o) { struct sysex *e; unsigned i; dbg_puts("sysex_dbg:\n"); for (e = o->first; e != NULL; e = e->next) { dbg_puts("unit = "); dbg_putx(e->unit); dbg_puts(", data = { "); if (e->first) { for (i = 0; i < e->first->used; i++) { if (i > 16) { dbg_puts("... "); break; } dbg_putx(e->first->data[i]); dbg_puts(" "); } } dbg_puts("}\n"); } } midish-1.0.4/sysex.h010066400017510001751000000046451143320306000130270ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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; }; 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_dbg(struct sysex *); unsigned sysex_check(struct sysex *); void sysexlist_init(struct sysexlist *); void sysexlist_done(struct sysexlist *); void sysexlist_put(struct sysexlist *, struct sysex *); struct sysex *sysexlist_get(struct sysexlist *); void sysexlist_dbg(struct sysexlist *); #endif /* MIDISH_SYSEX_H */ midish-1.0.4/textio.c010066400017510001751000000103171143320306000131540ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.h" #include "textio.h" #include "cons.h" struct textin { FILE *file; unsigned isconsole; unsigned line, col; }; struct textout { FILE *file; unsigned indent, isconsole; }; /* -------------------------------------------------------- input --- */ struct textin * textin_new(char *filename) { struct textin *o; o = mem_alloc(sizeof(struct textin), "textin"); if (filename == NULL) { o->isconsole = 1; o->file = stdin; } else { o->isconsole = 0; o->file = fopen(filename, "r"); if (o->file == NULL) { cons_errs(filename, "failed to open input file"); mem_free(o); return 0; } } o->line = o->col = 0; return o; } void textin_delete(struct textin *o) { if (!o->isconsole) { fclose(o->file); } mem_free(o); } unsigned textin_getchar(struct textin *o, int *c) { if (o->isconsole) { *c = cons_getc(); /* because it handles ^C */ } else { *c = fgetc(o->file); } if (*c < 0) { *c = CHAR_EOF; if (ferror(o->file)) { 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 = mem_alloc(sizeof(struct textout), "textout"); if (filename != NULL) { o->file = fopen(filename, "w"); if (o->file == NULL) { cons_errs(filename, "failed to open output file"); mem_free(o); return 0; } o->isconsole = 0; } else { o->file = stdout; o->isconsole = 1; } o->indent = 0; return o; } void textout_delete(struct textout *o) { if (!o->isconsole) { fclose(o->file); } mem_free(o); } void textout_indent(struct textout *o) { unsigned i; for (i = 0; i < o->indent; i++) { fputc('\t', o->file); } } void textout_shiftleft(struct textout *o) { o->indent--; } void textout_shiftright(struct textout *o) { o->indent++; } void textout_putstr(struct textout *o, char *str) { fputs(str, o->file); } void textout_putlong(struct textout *o, unsigned long val) { fprintf(o->file, "%lu", val); } void textout_putbyte(struct textout *o, unsigned val) { fprintf(o->file, "0x%02x", val & 0xff); } /* ------------------------------------------------------------------ */ struct textout *tout; struct textin *tin; void textio_init(void) { tout = textout_new(NULL); tin = textin_new(NULL); } void textio_done(void) { textout_delete(tout); tout = 0; textin_delete(tin); tin = 0; } midish-1.0.4/textio.h010066400017510001751000000043311143320306000131600ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef MIDISH_TEXTIO_H #define MIDISH_TEXTIO_H #define CHAR_EOF (~0) 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_indent(struct textout *); void textout_shiftleft(struct textout *); void textout_shiftright(struct textout *); void textout_putstr(struct textout *, char *); void textout_putlong(struct textout *, unsigned 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.0.4/timo.c010066400017510001751000000101501143320306000126030ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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) { dbg_puts("timo_add: already set\n"); dbg_panic(); } if (delta == 0) { dbg_puts("timo_add: zero timeout is evil\n"); dbg_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) dbg_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) { dbg_puts("timo_done: timo_queue not empty!\n"); dbg_panic(); } timo_queue = (struct timo *)0xdeadbeef; } midish-1.0.4/timo.h010066400017510001751000000036601143320306000126200ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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.0.4/track.c010066400017510001751000000133171143320306000127470ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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 "dbg.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) { dbg_putu(i->delta); dbg_puts("\t"); ev_dbg(&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; dbg_putu(num); dbg_puts("\t"); dbg_putu(tic); dbg_puts("\t+"); seqev_dump(i); dbg_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; } /* * 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) { dbg_puts("seqev_rm: unexpected end of track\n"); dbg_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 != &o->eot; 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) { dbg_puts("track_chanmap: bogus dev/ch pair, stopping\n"); break; } map[dev * 16 + ch] = 1; } } } midish-1.0.4/track.h010066400017510001751000000047211143320306000127530ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 */ }; 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); 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 *); #endif /* MIDISH_TRACK_H */ midish-1.0.4/user.c010066400017510001751000000710461143320310600126250ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * implements misc built-in functions available through the * interpreter * * each function is described in the manual.html file */ #include "dbg.h" #include "defs.h" #include "node.h" #include "exec.h" #include "data.h" #include "cons.h" #include "textio.h" #include "lex.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; /* -------------------------------------------------- some tools --- */ /* * 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 name **locals; struct node *root; struct data *data; unsigned res; res = 0; root = NULL; data = NULL; parse = parse_new(filename); if (!parse) { return 0; } locals = exec->locals; exec->locals = &exec->globals; if (parse_prog(parse, &root)) { res = node_exec(root, exec, &data); } exec->locals = locals; parse_delete(parse); node_delete(root); return res; } /* * 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) { dbg_puts("exec_lookupchan_getnum: no such var\n"); dbg_panic(); } if (!data_list2chan(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) { dbg_puts("exec_lookupchan: no such var\n"); dbg_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, max, num; arg = exec_varlookup(o, name); if (!arg) { dbg_puts("exec_lookupev: no such var\n"); dbg_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)) { cons_err("bad status in event spec"); return 0; } d = d->next; if (!d) { cons_err("no channel in event spec"); return 0; } if (!data_list2chan(d, &dev, &ch, input)) { return 0; } ev->dev = dev; ev->ch = ch; 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_BEND || ev->cmd == EV_NRPN || ev->cmd == EV_RPN) { max = EV_MAXFINE; } else { max = EV_MAXCOARSE; } if (!d || d->type != DATA_LONG || d->val.num < 0 || d->val.num > max) { cons_err("bad byte0 in event spec"); return 0; } ev->v0 = d->val.num; } d = d->next; if (ev->cmd == EV_PC || ev->cmd == EV_CAT || ev->cmd == EV_BEND) { if (d) { cons_err("extra data in event spec"); return 0; } } else { if (ev->cmd == EV_XPC || ev->cmd == EV_XCTL || ev->cmd == EV_NRPN || ev->cmd == EV_RPN) { max = EV_MAXFINE; } else { max = EV_MAXCOARSE; } if (!d || d->type != DATA_LONG || d->val.num < 0 || d->val.num > max) { cons_err("bad byte1 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_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) { dbg_puts("exec_lookupev: no such var\n"); dbg_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; } 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 */ /* nothing */ } else if (d->val.list && d->val.list->next && !d->val.list->next->next) { if (!data_list2range(d->val.list, 0, EV_MAXDEV, &lo, &hi)) { return 0; } e->dev_min = lo; e->dev_max = hi; if (!data_list2range(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 = EV_MAXCH; } 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 (!data_list2range(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_list2range(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 = evinfo[e->cmd].v1_min; e->v1_max = evinfo[e->cmd].v1_max; } 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 dbg_puts("lookupevspec: "); evspec_dbg(e); dbg_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) { dbg_puts("exec_lookupctl: no such var\n"); dbg_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: dbg_puts("data_print: unknown type\n"); break; } } /* * convert 2 integer lists to channels */ unsigned data_num2chan(struct data *o, unsigned *res_dev, unsigned *res_ch) { long dev, ch; if (o == NULL || o->next == NULL || o->next->next != NULL || o->type != DATA_LONG || o->next->type != DATA_LONG) { cons_err("bad {dev midichan} in spec"); 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; } /* * 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) { dbg_puts("exec_lookupval: no such var\n"); dbg_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_list2chan(struct data *o, unsigned *res_dev, unsigned *res_ch, int input) { struct songchan *i; if (o->type == DATA_LIST) { return data_num2chan(o->val.list, res_dev, res_ch); } 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_list2range(struct data *d, unsigned min, unsigned max, unsigned *lo, unsigned *hi) { if (d->type == DATA_LONG) { *lo = *hi = d->val.num; } else if (d->type == DATA_LIST) { d = d->val.list; if (!d) { *lo = min; *hi = 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; } return 1; } /* * convert a list to bitmap of continuous controllers */ unsigned data_list2ctlset(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; } if (evctl_isreserved(d->val.num)) { cons_erru(d->val.num, "controller number reserved"); return 0; } ctlset |= (1 << d->val.num); d = d->next; } *res = ctlset; 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; } if (evctl_isreserved(d->val.num)) { cons_err("controller is reserved for bank, rpn, nrpn"); 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; } /* ---------------------------------------- interpreter functions --- */ unsigned user_func_info(struct exec *o, struct data **r) { exec_dumpprocs(o); exec_dumpvars(o); return 1; } unsigned user_func_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 user_func_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 user_func_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 user_mainloop(void) { struct parse *parse; struct exec *exec; struct node *root; struct data *data; unsigned result, exitcode; /* * create the project (ie the song) and * the execution environment of the interpreter */ usong = song_new(); exec = exec_new(); /* * register built-in functions */ exec_newbuiltin(exec, "print", blt_print, name_newarg("value", NULL)); exec_newbuiltin(exec, "err", blt_err, name_newarg("message", NULL)); exec_newbuiltin(exec, "h", blt_h, name_newarg("function", 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, "panic", blt_panic, NULL); exec_newbuiltin(exec, "info", user_func_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, "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, "m", blt_metro, name_newarg("onoff", NULL)); exec_newbuiltin(exec, "metrocf", blt_metrocf, name_newarg("eventhi", name_newarg("eventlo", 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, "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, "tquant", blt_tquant, name_newarg("rate", NULL)); exec_newbuiltin(exec, "ttransp", blt_ttransp, name_newarg("halftones", 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, "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, "shut", user_func_shut, NULL); exec_newbuiltin(exec, "proclist", user_func_proclist, NULL); exec_newbuiltin(exec, "builtinlist", user_func_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, "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))); /* * 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 = parse_new(NULL); if (parse == NULL) { return 0; } cons_putpos(usong->curpos, 0, 0); root = NULL; data = NULL; for (;;) { /* * print mem_alloc() and mem_free() stats, useful to * track memory leaks */ mem_stats(); /* * parse a block */ if (!parse_getsym(parse)) { goto err; } if (parse->lex.id == TOK_EOF) { /* end-of-file (user quit) */ exitcode = 1; break; } parse_ungetsym(parse); if (!parse_line(parse, &root)) { node_delete(root); root = NULL; goto err; } /* * at this stage no parse error, execute the tree */ result = node_exec(root, exec, &data); node_delete(root); root = NULL; if (result == RESULT_OK) { continue; } if (result == RESULT_EXIT) { exitcode = 1; /* 1 means success */ break; } err: /* * in batch mode stop on the first error */ if (user_flag_batch) { exitcode = 0; /* 0 means failure */ break; } } parse_delete(parse); exec_delete(exec); song_delete(usong); usong = NULL; mididev_listdone(); return exitcode; } midish-1.0.4/user.h010066400017510001751000000275631143320306000126360ustar00alexalex/* * Copyright (c) 2003-2010 Alexandre Ratchov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #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 ***); /* 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_list2chan(struct data *, unsigned *, unsigned *, int); unsigned data_list2range(struct data *, unsigned, unsigned, unsigned *, unsigned *); unsigned data_matchsysex(struct data *, struct sysex *, unsigned *); unsigned data_list2ctl(struct data *, unsigned *); unsigned data_list2ctlset(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 */