/* created by DMH (damonhorowitz): write seq_type as standard midifile */ /*************************************************************************** * Change Log * Date | Change *-----------+--------------------------------------------------------------- * 11-Mar-94 | Created Change Log * 11-Mar-94 | PLu : Added private to function defs. * 28-Apr-03 | DM : Change #include's for portability ****************************************************************************/ #include "switches.h" #include #include "cext.h" #include "userio.h" #include "midicode.h" #include "mfmidi.h" #include "midifns.h" #include "timebase.h" #include "moxc.h" #include "seq.h" #include "seqread.h" /* to get scale() */ #include "seqmwrite.h" long chunk_size_marker; int seti_counter; extern time_type a_start_time; long last_event; /*time from last_clock_event to the last event*/ time_type last_clock_event; time_type last_tick_size; /* millisec per tick shifted 16 bits */ struct smf_write_seq { seq_type seq; int track; FILE *outfile; } smfw_seq; extern seq_type sequence; /* this is a global to be accessed by * routines called from the sequence */ /* clock state: */ extern time_type clock_ticksize; /* millisec per tick shifted 16 bits */ extern boolean clock_running; /* TRUE if clock is running */ extern boolean use_midi_clock; private void smfw_bend(); private void smfw_cause_noteoff(); private void smfw_ctrl(); private void smfw_deltatime(); private void smfw_dotrack(); private void smfw_exclusive(); private void smfw_noteoff(); private void smfw_noteon(); private void smfw_process_event(); private void smfw_ramp_event(seq_type seq, event_type event, unsigned int value, unsigned int to_value, int increment, time_type step, int n); private void smfw_send_macro(); private void smfw_touch(seq_type seq, int voice, int value); private void writevarlen(); /* smfw_bend -- write a pitch bend to a midi file */ /**/ private void smfw_bend(seq_type seq, int voice, int value) { if(debug) gprintf(TRANS, "smfw_bend %d\n", value); smfw_deltatime(); putc(MIDI_BEND | (voice - 1), smfw_seq.outfile); putc(0xFF & ((value & 0x1) << 6) , smfw_seq.outfile); putc(0xFF & (value >> 1), smfw_seq.outfile); } /* smfw_cause_noteoff -- schedule a noteoff for midi file */ /* * NOTE: this is called by smfw_process_event when it handles a note * event node in a seq_type's event list. The ordinary moxc scheduler * is used to "schedule" the noteoff in the future. In reality, the * output is done as fast as possible (by attempting an infinite rate), * so no real timing delays occur. The effect is to sort events by their * specified time. */ private void smfw_cause_noteoff(seq, delay, voice, pitch) seq_type seq; time_type delay; int voice; int pitch; { if(debug) gprintf(TRANS, "cause noteoff at %ld...", virttime + delay); pitch += seq->transpose; while (pitch <= 0) pitch += 12; while (pitch >= 127) pitch -= 12; seq->noteoff_count++; causepri((delay_type) delay, 10, seq->noteoff_fn, seq, voice, pitch); } private void smfw_clock_event(old_ticksize, new_ticksize) time_type old_ticksize, new_ticksize; { time_type temp_ticksize = new_ticksize; new_ticksize = scale(new_ticksize, 375L, 1024L); /* (new_ticksize >> 16) * 24000 ms/clock becomes us/midiquarter */ if(debug) gprintf(TRANS, "smfw_clock: write %ld (time:%ld) ->->->tempo %ld\n", new_ticksize, virttime, 2500L / (new_ticksize / 24000)); /*use old ticksize to write the delta for the clock event*/ last_tick_size = old_ticksize; smfw_deltatime(); last_tick_size = temp_ticksize;/* reset to = new_tick_size */ putc(0xFF, smfw_seq.outfile); putc(0x51, smfw_seq.outfile); putc(0x03, smfw_seq.outfile); putc((int) ((new_ticksize >> 16) & 0xFF), smfw_seq.outfile); putc((int) ((new_ticksize >> 8) & 0xFF), smfw_seq.outfile); putc((int) (new_ticksize & 0xFF), smfw_seq.outfile); last_clock_event = virttime; last_event = 0L; /*no time expired between last clockevent and last event(they are the same).*/ /*next clock event will be exactly the next this_event from last_clock_event*/ } /* smfw_ctrl -- write a control change to a midi file */ /**/ private void smfw_ctrl(seq_type seq, int voice, int ctrl_name, int value) { if(debug) gprintf(TRANS, "smfw_ctrl %d: %d\n", ctrl_name, value); smfw_deltatime(); putc(MIDI_CTRL | (voice - 1), smfw_seq.outfile); putc(ctrl_name, smfw_seq.outfile); putc(value, smfw_seq.outfile); } /* smfw_deltatime -- write the time difference between now an previous event */ /**/ private void smfw_deltatime() { /* if last_ and clock_ are different, use last_ for clock deltatime*/ time_type use_ticksize = (clock_ticksize != last_tick_size) ? last_tick_size : clock_ticksize; time_type this_event = virttime - last_clock_event; if(debug) gprintf(TRANS, "delta! ticksize: %lu Lastev: %ld ThisevScaled: %lu Thisev: %lu ", clock_ticksize, last_event, (this_event * ((2500L << 16) / use_ticksize)) / 100, this_event); this_event = ((virttime - last_clock_event) * ((2500L << 16) / use_ticksize)) / 100; if(debug) gprintf(TRANS, "--- deltatime: %lu\n", this_event - last_event); writevarlen((long) (this_event - last_event)); last_event = this_event; } /* smfw_dotrack -- write the remainder of a track */ private void smfw_dotrack(seq) seq_type seq; { long end_marker; timebase_type old_timebase = timebase; unsigned long chunk_size; if (seq->runflag) { if ((seq->timebase->virt_base == 0) && (seq->timebase->rate == STOPRATE)) /*we just set these last time through... do nothing*/; seq_stop(seq); } timebase_use(seq->timebase); set_rate(seq->timebase, STOPRATE); set_virttime(seq->timebase, 0L); seq->current = seq_events(seq); seq->noteoff_count = 0L; seq->runflag = TRUE; seq->paused = TRUE; last_clock_event = 0L; last_event = 0L; if(debug) gprintf(TRANS, "dotrack (reset) %d %ld (%lu) \n", smfw_seq.track, last_event, virttime); if (seq->current) cause((delay_type)(seq->current->ntime - virttime), smfw_process_event, seq); set_virttime(timebase, MAXTIME); catchup(); putc(0x00, smfw_seq.outfile); putc(0xFF, smfw_seq.outfile);/*end of track chunk*/ putc(0x2F, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); end_marker = ftell(smfw_seq.outfile); fseek(smfw_seq.outfile, chunk_size_marker, 0);/*go back to enter chunksize*/ chunk_size = (end_marker - chunk_size_marker) - 4;/* - 4 for 4 size bytes*/ if(debug) gprintf(TRANS, "bytes written in previous track: %ld \n\n", chunk_size); putc((int) ((0xFF & (chunk_size >> 24))), smfw_seq.outfile); putc((int) ((0xFF & (chunk_size >> 16))), smfw_seq.outfile); putc((int) ((0xFF & (chunk_size >> 8))), smfw_seq.outfile); putc((int) ((0xFF & chunk_size)), smfw_seq.outfile); fseek(smfw_seq.outfile, end_marker, 0);/*return file pointer to end of track*/ timebase_use(old_timebase); } /* smfw_exclusive -- write a system excl. msg to midi file */ private void smfw_exclusive(length, msg) int length; unsigned char *msg; { int length_count = 0; if(debug) gprintf(TRANS, "SYSEX (time:%ld)\n", virttime); smfw_deltatime(); while (length > length_count){ /* *(msg-1) != MIDI_EOX) { */ putc(*msg++, smfw_seq.outfile); length_count++; } if(*(--msg) != MIDI_EOX) gprintf(TRANS, "ERROR: no end of sysex\n"); } private void smfw_msg_write(n,c1,c2,c3) int n; unsigned char c1,c2,c3; { if(debug) gprintf(TRANS, "MSGWRITE %d bytes (time:%ld)\n", n, virttime); smfw_deltatime(); switch(n) { case 1: putc(c1, smfw_seq.outfile); break; case 2: putc(c1, smfw_seq.outfile); putc(c2, smfw_seq.outfile); break; case 3: putc(c1, smfw_seq.outfile); putc(c2, smfw_seq.outfile); putc(c3, smfw_seq.outfile); break; } } /* smfw_noteoff -- write noteoff to midi file */ /**/ private void smfw_noteoff(seq_type seq, int voice, int pitch) { if(debug) gprintf(TRANS, "smfw_noteoff %d: %d (time:%ld)\n", voice, pitch, virttime); smfw_deltatime(); putc(NOTEOFF | (voice - 1), smfw_seq.outfile); putc(pitch, smfw_seq.outfile); putc(0x40, smfw_seq.outfile); } /* smfw_noteon -- write noteon to midi file */ /* * NOTE: the seq parameter is not used here, but is passed in by the * seq_noteon macro, so we have to have a placeholder for it. */ private void smfw_noteon(seq, voice, pitch, vel) seq_type seq; int voice, pitch, vel; { if(debug) gprintf(TRANS, "smfw_noteon %d: %d %d(time:%ld)\n", voice, pitch, vel, virttime); smfw_deltatime(); putc(NOTEON | (voice - 1), smfw_seq.outfile); putc(pitch, smfw_seq.outfile); putc(vel, smfw_seq.outfile); } /* smfw_process_event -- write a seq event to a midi file */ /**/ private void smfw_process_event(seq) seq_type seq; { register event_type event; if (!seq->runflag) return; while ((event = seq->current) && (event->ntime <= virttime)) { unsigned int voice; if ((vc_voice(event->nvoice) == smfw_seq.track) || /*if on current track*/ (((vc_voice(event->nvoice) - 16) == smfw_seq.track) && (smfw_seq.track > 0)) || /* acknowledge clock change on all tracks*/ (event->value == CLOCK_VALUE && vc_ctrl(event->nvoice) == ESC_CTRL)) { /* process all current (and earlier) events */ if (is_note(event)) { /*** play a note or rest ***/ /* if this note is not a rest, play it and schedule an off event */ if (event->value != NO_PITCH && (seq_channel_mask(seq) & (1 << ((voice = vc_voice(event->nvoice)) - 1)))) { seq_noteon(seq, voice, (0xFF & event->value), (int) (event->u.note.ndur & 0xFF)); seq_cause_noteoff(seq, (event->u.note.ndur) >> 8, voice, (0xFF & event->value)); } } else { /*** send a control command ***/ int n; time_type step; int delta; int increment; int voice = vc_voice(event->nvoice); ulong enabled = seq_channel_mask(seq) & (1 << (voice - 1)); switch (vc_ctrl(event->nvoice)) { case PSWITCH_CTRL: if (!enabled) break; if(debug) gprintf(TRANS, "porta %d (time:%ld)... ", event->value, virttime); seq_midi_ctrl(seq, voice, PORTASWITCH, 0xFF & event->value); break; case MODWHEEL_CTRL: if (!enabled) break; if(debug) gprintf(TRANS, "modw %d (time:%ld)...", event->value, virttime); seq_midi_ctrl(seq, voice, MODWHEEL, 0xFF & event->value); break; case TOUCH_CTRL: if (!enabled) break; if(debug) gprintf(TRANS, "touch %d (time:%ld)... ", event->value, virttime); seq_midi_touch(seq, voice, 0xFF & event->value); break; case VOLUME_CTRL: if (!enabled) break; if(debug) gprintf(TRANS, "ftvol %d (time:%ld)...", event->value, virttime); seq_midi_ctrl(seq, voice, VOLUME, 0xFF & event->value); break; case BEND_CTRL: if (!enabled) break; if(debug) gprintf(TRANS, "bend %d (time:%ld)... ", event->value, virttime); seq_midi_bend(seq, voice, event->value); break; case PROGRAM_CTRL: if (!enabled) break; if(debug) gprintf(TRANS, "prog %d (time:%ld)\n", event->value, virttime); smfw_deltatime(); putc(MIDI_CH_PROGRAM | (voice - 1), smfw_seq.outfile); putc(0xFF & event->value, smfw_seq.outfile); break; case ESC_CTRL: switch (event->value) { time_type this_event; case CALL_VALUE: /*called routine will write to midifile in execution */ sequence = seq; (*(event->u.call.routine))(event->u.call.args); break; case CLOCK_VALUE: clock_ticksize = event->u.clock.ticksize; if(debug) gprintf(TRANS, "clockevent! ticksize: %lu (time:%ld)\n", clock_ticksize, virttime); if (virttime > 0) { /* any clock before this is already recorded in the header */ if (smfw_seq.track == 0) { /* record clock event on tempo track = 0 */ /* cause clock write in half a newtick, because it was written .5 tick early*/ cause((delay_type) (clock_ticksize >> 17), smfw_clock_event, last_tick_size, clock_ticksize); last_tick_size = clock_ticksize; /*set new ticksize*/ } else { /*not on tempo track*/ this_event = ((virttime - last_clock_event) * ((2500L << 16) / last_tick_size)) / 100; if(debug) gprintf(TRANS, "track != 0: Lastev: %ld Thisev: %ld NewLast: %ld\n", last_event, this_event, this_event - last_event); last_event = 0L - (this_event - last_event); last_clock_event = virttime; /*last_event is negative, so will be ADDED to next this_event*/ last_tick_size = clock_ticksize; } } else if (debug) gprintf(TRANS, "IGNORED\n");/* if virttime <= 0 */ break; case MACCTRL_VALUE: if (!enabled) break; if (debug) gprintf(TRANS, "MACCTRL %d: %d (time:%ld)\n", event->u.macctrl.ctrl_number, event->u.macctrl.value, virttime); smfw_deltatime(); putc(MIDI_CTRL | (voice - 1), smfw_seq.outfile); putc(0xFF & event->u.macctrl.ctrl_number, smfw_seq.outfile); putc(0xFF & event->u.macctrl.value, smfw_seq.outfile); break; case MACRO_VALUE: if (!enabled) break; if (debug) gprintf(TRANS, "MACRO sent to...\n"); smfw_send_macro(event->u.macro.definition, voice, event->u.macro.parameter, -1, 0); break; case CTRLRAMP_VALUE: case DEFRAMP_VALUE: { int from, to; if (!enabled) break; step = event->u.ramp.step; if (event->value == CTRLRAMP_VALUE) { if(debug) gprintf(TRANS, "CTRLRAMP (time:%ld)...", virttime); from = event->u.ramp.u.ctrl.from_value; to = event->u.ramp.u.ctrl.to_value; } else { if (debug) gprintf(TRANS, "DEFRAMP (time:%ld)...", virttime); from = event->u.ramp.u.def.parameter[ event->u.ramp.u.def.parm_num]; to = event->u.ramp.u.def.to_value; } delta = to - from; increment = delta; if (delta < 0) delta = -delta; /* RBD - Note: Step is always non-zero */ n = event->u.ramp.dur / step; increment = (increment << 8) / n; smfw_ramp_event(seq, event, from << 8, to << 8, increment, step, n); seq->noteoff_count++; break; } case SETI_VALUE: seti_counter++; /*will be printed after writing is completed*/ *(event->u.seti.int_to_set) = event->u.seti.value; break; default: gprintf(TRANS, "unexpected ESC_CTRL value\n"); break; } break; default: gprintf(TRANS, "unexpected seq data\n"); break; } } } seq->current = event->next; } if (seq->current) { /* if there is an event: delay, and then process again */ cause((delay_type)(event->ntime - virttime), smfw_process_event, seq); } } /* smfw_ramp_event -- generate a ramp to write*/ private void smfw_ramp_event(seq, event, value, to_value, increment, step, n) seq_type seq; register event_type event; unsigned int value; unsigned int to_value; int increment; time_type step; int n; { if(debug) gprintf(TRANS, "ramp of %d: %d to %d\n", event->u.ramp.ctrl, value >> 8, to_value >> 8); if (seq->runflag) { int voice = vc_voice(event->nvoice); if (n == 0) value = to_value; else cause((delay_type)step, smfw_ramp_event, seq, event, value + increment, to_value, increment, step, n - 1); if (event->value == CTRLRAMP_VALUE) { int ctrl = event->u.ramp.ctrl; if (ctrl == -TOUCH_CTRL) smfw_touch(seq, voice, value >> 8); else if (ctrl == -BEND_CTRL) smfw_bend(seq, voice, value >> 8); else smfw_ctrl(seq, voice, ctrl, value >> 8); } else { /* must be DEFRAMP_VALUE */ smfw_send_macro(event->u.ramp.u.def.definition, vc_voice(event->nvoice), event->u.ramp.u.def.parameter, event->u.ramp.u.def.parm_num, value >> 8); } if (n == 0) seq_end_event(seq); } } /* smfw_send_macro -- write msg to midi file from a seq "macro" event */ /**/ private void smfw_send_macro(ptr, voice, parameter, parm_num, value) register unsigned char *ptr; int voice; short parameter[]; int parm_num; int value; { register unsigned char code, *loc; while ((code = *ptr++)) { loc = ptr + *ptr; ptr++; if (code <= nmacroparms) { code--; *loc = (code == parm_num ? value : parameter[code]) & 0x7f; } else if (code == nmacroparms + 1) { *loc = ((voice - 1) & 0xF) | *loc; } else { code -= (nmacroparms + 2); *loc = ((code == parm_num ? value : parameter[code]) >> 7) & 0x7F; } } if (ptr[1] == MIDI_SYSEX) smfw_exclusive(*ptr, ptr + 1); else smfw_msg_write(*ptr, ptr[1], ptr[2], ptr[3]); } /* smfw_touch -- write aftertouch msg to midi file */ /**/ private void smfw_touch(seq_type seq, int voice, int value) { if(debug) gprintf(TRANS, "smfw_touch %d\n", value); smfw_deltatime(); putc(MIDI_TOUCH | (voice - 1), smfw_seq.outfile); putc(value, smfw_seq.outfile); } void seq_write_smf(seq, outfile) seq_type seq; FILE *outfile; { time_type put_tick_size; int i; /* ticksize is milliseconds << 16, and tickrate = 24*tempo 60000ms/min / (24 * tempo) = 2500/tempo = 25 25 << 16 = 1638400 */ time_type starting_ticksize = 1638400L; /*default midifile tempo 100*/ int track_count = 0; long track_count_marker; register event_type event; seti_counter = 0; /*initialize the smfw_seq struct*/ smfw_seq.outfile = outfile; smfw_seq.seq = seq_copy(seq); smfw_seq.seq->cause_noteoff_fn = smfw_cause_noteoff; smfw_seq.seq->midi_bend_fn = smfw_bend; smfw_seq.seq->midi_ctrl_fn = smfw_ctrl; smfw_seq.seq->midi_touch_fn = smfw_touch; smfw_seq.seq->noteoff_fn = smfw_noteoff; smfw_seq.seq->noteon_fn = smfw_noteon; event = seq_events(smfw_seq.seq); /*search for clock events up till start of score*/ while(event->ntime <= 0){ if(debug) gprintf(TRANS, "event (time:%ld)\n", event->ntime); if(vc_ctrl(event->nvoice) == ESC_CTRL && event->value == CLOCK_VALUE) { if(debug) gprintf(TRANS, "clock %lu at 0\n", event->u.clock.ticksize); starting_ticksize = event->u.clock.ticksize; break; } event = event->next; } putc(0x4D, smfw_seq.outfile); /*header: MThd*/ putc(0x54, smfw_seq.outfile); putc(0x68, smfw_seq.outfile); putc(0x64, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); putc(0x06, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); putc(0x01, smfw_seq.outfile); /*format 1 */ putc(0x00, smfw_seq.outfile); track_count_marker = ftell(smfw_seq.outfile); /*number of tracks will be written later*/ putc(0x00, smfw_seq.outfile); /*will be filled by track_count_marker*/ putc(0x02, smfw_seq.outfile);/*division resolution of 600*/ putc(0x58, smfw_seq.outfile); for(i = 0; i < 17; i++){/*for each track..*/ if(((seq_used_mask(smfw_seq.seq) >> (i - 1)) & 0x1) || (i == 0)){ if(debug) gprintf(TRANS, "write track %d \n", i); track_count++; clock_ticksize = starting_ticksize; last_tick_size = starting_ticksize; putc(0x4D, smfw_seq.outfile);/*track header: MTrk*/ putc(0x54, smfw_seq.outfile); putc(0x72, smfw_seq.outfile); putc(0x6B, smfw_seq.outfile); chunk_size_marker = ftell(smfw_seq.outfile);/*size of chunk will be written later*/ putc(0x00, smfw_seq.outfile); /*will be filled by chunk_size_marker*/ putc(0x00, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); if(i == 0) { /*tempo and time signature track*/ putc(0x00, smfw_seq.outfile);/* default time sig stuff*/ putc(0xFF, smfw_seq.outfile); putc(0x58, smfw_seq.outfile); putc(0x04, smfw_seq.outfile); putc(0x04, smfw_seq.outfile); putc(0x02, smfw_seq.outfile); putc(0x18, smfw_seq.outfile); putc(0x08, smfw_seq.outfile); putc(0x00, smfw_seq.outfile); putc(0xFF, smfw_seq.outfile);/*TEMPO: inserted here in case default is used*/ putc(0x51, smfw_seq.outfile); putc(0x03, smfw_seq.outfile); /* ticksize is in ms<<16, so to get milliseconds per tick, it's ticksize / 65536. To get beat durations, multiply by 24 to get ticksize * 24 / 65536. To get microseconds, multiply by 1000: ticksize * 24000 / 65536. Divide both constants by 64 to get ticksize * 375 / 1024 = microseconds per quarter note. */ put_tick_size = scale(clock_ticksize, 375L, 1024L); putc((int) ((put_tick_size >> 16) & 0xFF), smfw_seq.outfile); putc((int) ((put_tick_size >> 8) & 0xFF), smfw_seq.outfile); putc((int) (put_tick_size & 0xFF), smfw_seq.outfile); } smfw_seq.track = i; smfw_dotrack(smfw_seq.seq); } } if(seti_counter) gprintf(TRANS, "%d SETI events IGNORED!\n", seti_counter); seq_stop(smfw_seq.seq); fseek(smfw_seq.outfile, track_count_marker, 0);/*go back and insert number of tracks*/ putc(0xFF & track_count, smfw_seq.outfile); fclose(smfw_seq.outfile); } /* writevarlen -- write a variable length integer to midi file */ /**/ private void writevarlen(value) register long value; { register ulong buffer; if(debug) gprintf(TRANS, "variable length quantity..."); buffer = value & 0x7f; while((value >>= 7) > 0) { buffer <<= 8; buffer |= 0x80; buffer += (value & 0x7f); } for(;;) { if(debug) gprintf(TRANS, " byte "); putc((int) (buffer & 0xFF), smfw_seq.outfile); if (buffer & 0x80) buffer >>= 8; else break; } if(debug) gprintf(TRANS, "written!\n"); }