/* genmidi.c * This is the code for generating MIDI output from the stored music held * in arrays feature, num, denom, pitch. The top-level routine is * writetrack(). This file is part of abc2midi. * * 14th January 1999 * James Allwright * */ #include "abc.h" #include "midifile.h" #include /* global variables grouped roughly by function */ FILE *fp; extern int lineno; int lastlineno; extern char** atext; /* Named guitar chords */ extern int chordnotes[MAXCHORDNAMES][6]; extern int chordlen[MAXCHORDNAMES]; /* general purpose storage structure */ extern int *pitch, *num, *denom; extern featuretype *feature; extern int notes; extern int verbose; extern int sf, mi; extern int gchordvoice, wordvoice; /* Part handling */ char part[MAXPARTS]; int parts, partno, partlabel; int part_start[26], part_count[26]; long introlen, lastlen, partlen[26]; int partrepno; int err_num, err_denom; extern int voicesused; /* Tempo handling (Q: field) */ extern long tempo; extern int time_num, time_denom; int div_factor; int division = DIV; long delta_time; long tracklen, tracklen1; /* output file generation */ extern int ntracks; /* bar length checking */ int bar_num, bar_denom, barno, barsize; int b_num, b_denom; extern int barchecking; /* generating MIDI output */ int beat; int loudnote, mednote, softnote; int channel, program; #define MAXCHANS 16 int channels[MAXCHANS + 3]; int transpose, global_transpose; /* karaoke handling */ extern int karaoke, wcount; int kspace; char* wordlineptr; extern char** words; int wordson, noteson, gchordson; int thismline, thiswline, windex, thiswfeature; int wordlineplace; int nowordline; int waitforbar; int wlineno, syllcount; int lyricsyllables, musicsyllables; /* Generating accompaniment */ int gchords, g_started; int basepitch, chordnum; struct notetype { int base; int chan; int vel; }; struct notetype gchord, fun; int g_num, g_denom; int g_next; char gchord_seq[40]; int gchord_len[40]; int g_ptr; set_gchords(s) /* set up a string which indicates how to generate accompaniment from */ /* guitar chords (i.e. "A", "G" in abc). */ /* called from dodeferred(), startfile() and setbeat() */ char* s; { int seq_len; char* p; int j; p = s; j = 0; seq_len = 0; while (((*p == 'z') || (*p == 'c') || (*p == 'f')) && (j<39)) { gchord_seq[j] = *p; p = p + 1; if ((*p >= '0') && (*p <= '9')) { gchord_len[j] = readnump(&p); } else { gchord_len[j] = 1; }; seq_len = seq_len + gchord_len[j]; j = j + 1; }; if (seq_len == 0) { event_error("Bad gchord"); gchord_seq[0] = 'z'; gchord_len[0] = 1; seq_len = 1; }; gchord_seq[j] = '\0'; if (j == 39) { event_error("Sequence string too long"); }; /* work out unit delay in 1/4 notes*/ g_num = time_num * 4; g_denom = time_denom * seq_len; reduce(&g_num, &g_denom); } dodeferred(s) /* handle package-specific command which has been held over to be */ /* interpreted as MIDI is being generated */ char* s; { char* p; char command[40]; int done; p = s; skipspace(&p); readstr(command, &p); skipspace(&p); done = 0; if (strcmp(command, "program") == 0) { int chan, prog; skipspace(&p); prog = readnump(&p); chan = channel; skipspace(&p); if ((*p >= '0') && (*p <= '9')) { chan = prog - 1; prog = readnump(&p); }; write_program(prog, chan); done = 1; }; if (strcmp(command, "gchord") == 0) { set_gchords(p); done = 1; }; if ((strcmp(command, "chordprog") == 0) && (gchordvoice > 0)) { int prog; prog = readnump(&p); write_program(prog, gchord.chan); done = 1; }; if ((strcmp(command, "bassprog") == 0) && (gchordvoice> 0)) { int prog; prog = readnump(&p); write_program(prog, fun.chan); done = 1; }; if (strcmp(command, "chordvol") == 0) { gchord.vel = readnump(&p); done = 1; }; if (strcmp(command, "bassvol") == 0) { fun.vel = readnump(&p); done = 1; }; if (strcmp(command, "beat") == 0) { skipspace(&p); loudnote = readnump(&p); skipspace(&p); mednote = readnump(&p); skipspace(&p); softnote = readnump(&p); skipspace(&p); beat = readnump(&p); if (beat == 0) { beat = barsize; }; done = 1; }; if (strcmp(command, "control") == 0) { int chan, n, datum; char data[20]; char sel[40]; skipspace(&p); chan = channel; if (isalpha(*p)) { readstr(sel, &p); skipspace(&p); if (strcmp(sel, "bass") == 0) { chan = fun.chan; }; if (strcmp(sel, "chord") == 0) { chan = gchord.chan; }; }; n = 0; while ((n<20) && (*p >= '0') && (*p <= '9')) { datum = readnump(&p); if (datum > 127) { event_error("data must be in the range 0 - 127"); datum = 0; }; data[n] = (char) datum; n = n + 1; skipspace(&p); }; write_control(chan, data, n); done = 1; }; if (done == 0) { event_error("Command not recognized"); }; } softcheckbar(pass) /* allows repeats to be in mid-bar */ int pass; { if (barchecking) { if ((bar_num-barsize*(bar_denom) >= 0) || (barno <= 0)) { checkbar(pass); }; }; } checkbar(pass) /* check to see we have the right number of notes in the bar */ int pass; { char msg[80]; if (barchecking) { /* only generate these errors once */ if (noteson && (partrepno == 0)) { /* allow zero length bars for typesetting purposes */ if ((bar_num-barsize*(bar_denom) != 0) && (bar_num != 0) && ((pass == 2) || (barno != 0))) { sprintf(msg, "Bar %d has %d", barno, bar_num); if (bar_denom != 1) { sprintf(msg+strlen(msg), "/%d", bar_denom); }; sprintf(msg+strlen(msg), " units instead of %d", barsize); if (pass == 2) { strcpy(msg+strlen(msg), " in repeat"); }; event_warning(msg); }; }; }; if (bar_num > 0) { barno = barno + 1; }; bar_num = 0; bar_denom = 1; /* zero place in gchord sequence */ if (gchordson) { g_ptr = 0; addtoQ(0, g_denom, -1, g_ptr, 0); }; } addunits(a, b) /* add a/b to the count of units in the bar */ int a, b; { bar_num = bar_num*(b*b_denom) + (a*b_num)*bar_denom; bar_denom = bar_denom * (b*b_denom); reduce(&bar_num, &bar_denom); } reduce(a, b) /* elimate common factors in fraction a/b */ int *a, *b; { int sign; int t, n, m; if (*a < 0) { sign = -1; *a = -*a; } else { sign = 1; }; /* find HCF using Euclid's algorithm */ if (*a > *b) { n = *a; m = *b; } else { n = *b; m = *a; }; while (m != 0) { t = n % m; n = m; m = t; }; *a = (*a/n)*sign; *b = *b/n; } save_state(vec, a, b, c, d, e) /* save status when we go into a repeat */ int vec[5]; int a, b, c, d, e; { vec[0] = a; vec[1] = b; vec[2] = c; vec[3] = d; vec[4] = e; } restore_state(vec, a, b, c, d, e) /* restore status when we loop back to do second repeat */ int vec[5]; int *a, *b, *c, *d, *e; { *a = vec[0]; *b = vec[1]; *c = vec[2]; *d = vec[3]; *e = vec[4]; } int findchannel() /* work out next available channel */ { int j; j = 0; while ((j= MAXCHANS + 3) { event_error("Too many channels required"); j = 0; }; return (j); } fillvoice(partno, xtrack, voice) /* check length of this voice at the end of a part */ /* if it is zero, extend it to the correct length */ int partno, xtrack, voice; { char msg[100]; long now; now = tracklen + delta_time; if (partlabel == -1) { if (xtrack == 1) { introlen = now; } else { if (now == 0) { delta_time = delta_time + introlen; now = introlen; } else { if (now != introlen) { sprintf(msg, "Time 0-%ld voice %d, has length %ld", introlen, voice, now); event_error(msg); }; }; }; } else { if (xtrack == 1) { partlen[partlabel] = now - lastlen; } else { if (now - lastlen == 0) { delta_time = delta_time + partlen[partlabel]; now = now + partlen[partlabel]; } else { if (now - lastlen != partlen[partlabel]) { sprintf(msg, "Time %ld-%ld voice %d, part %c has length %ld", lastlen, lastlen+partlen[partlabel], voice, (char) (partlabel + (int) 'A'), now-lastlen); event_error(msg); }; }; }; }; lastlen = now; } int findpart(j) int j; /* find out where next part starts and update partno */ { int place; place = j; partno = partno + 1; if (partno < parts) { partlabel = (int)part[partno] - (int)'A'; } while ((partno < parts) && (part_start[partlabel] == -1)) { event_error("Part not defined"); partno = partno + 1; if (partno < parts) { partlabel = (int)part[partno] - (int)'A'; } }; if (partno >= parts) { place = notes; } else { partrepno = part_count[partlabel]; part_count[partlabel]++; place = part_start[partlabel]; }; if (verbose) { if (partno < parts) { printf("Doing part %c number %d of %d\n", part[partno], partno, parts); }; }; return(place); } int partbreak(xtrack, voice, place) /* come to part label in note data - check part length, then advance to */ /* next part if there was a P: field in the header */ int xtrack, voice, place; { int newplace; newplace = place; if (xtrack > 0) { fillvoice(partno, xtrack, voice); }; if (parts != -1) { /* go to next part label */ newplace = findpart(newplace); }; partlabel = (int) pitch[newplace] - (int)'A'; return(newplace); } int findvoice(initplace, voice, xtrack) /* find where next occurence of correct voice is */ int initplace; int voice, xtrack; { int foundvoice; int j; foundvoice = 0; j = initplace; while ((j < notes) && (foundvoice == 0)) { if (feature[j] == PART) { j = partbreak(xtrack, voice, j); if (voice == 1) { foundvoice = 1; } else { j = j + 1; }; } else { if ((feature[j] == VOICE) && (pitch[j] == voice)) { foundvoice = 1; } else { j = j + 1; }; }; }; return(j); } karaokestarttrack() /* header information for karaoke track based on w: fields */ { int j; text_data("@LENGL"); j = 0; while ((j < notes) && (feature[j] != TITLE)) j = j+1; if (feature[j] == TITLE) { char atitle[200]; strcpy(atitle, "@T"); strcpy(atitle+2, atext[pitch[j]]); text_data(atitle); }; kspace = 0; } int findwline(startline) int startline; /* Find next line of lyrics at or after startline. */ { int place; int done; int newwordline; int inwline, extending; int versecount, target; /* printf("findwline called with %d\n", startline); */ done = 0; inwline = 0; nowordline = 0; newwordline = -1; target = partrepno; if (startline == thismline) { versecount = 0; extending = 0; } else { versecount = target; extending = 1; }; if (thismline == -1) { event_error("First lyrics line must come after first music line"); } else { place = startline + 1; /* search for corresponding word line */ while ((place < notes) && (!done)) { switch (feature[place]) { case WORDLINE: inwline = 1; /* wait for words for this pass */ if (versecount == target) { thiswfeature = place; newwordline = place; windex = pitch[place]; wordlineplace = 0; done = 1; }; break; case WORDSTOP: if (inwline) { versecount = versecount + 1; }; inwline = 0; /* stop if we are part-way through a lyric set */ if (extending) { done = 1; }; break; case PART: done = 1; break; case VOICE: done = 1; break; case MUSICLINE: done = 1; break; default: break; }; place = place + 1; if (done && (newwordline == -1) && (versecount > 0) && (!extending)) { target = partrepno % versecount ; versecount = 0; place = startline+1; done = 0; inwline = 0; }; }; if (newwordline == -1) { /* remember that we couldn't find lyrics */ nowordline = 1; if (lyricsyllables == 0) { event_warning("Line of music without lyrics"); }; }; }; return(newwordline); } int getword(place, w) /* picks up next syllable out of w: field */ int* place; int w; { char syllable[200]; char c; int i; int syllcount; enum {empty, inword, postword, foundnext, failed} syllstatus; i = 0; syllcount = 0; if (w >= wcount) { syllable[i] = '\0'; return ('\0'); }; if (*place == 0) { if ((w % 2) == 0) { syllable[i] = '/'; } else { syllable[i] = '\\'; }; i = i + 1; }; if (kspace) { syllable[i] = ' '; i = i + 1; }; syllstatus = empty; c = *(words[w]+(*place)); while ((syllstatus != postword) && (syllstatus != failed)) { syllable[i] = c; switch(c) { case '\0': if (syllstatus == empty) { syllstatus = failed; } else { syllstatus = postword; kspace = 1; }; break; case '~': syllable[i] = ' '; syllstatus = inword; *place = *place + 1; i = i + 1; break; case '\\': if (*(words[w]+(*place+1)) == '-') { syllable[i] = '-'; syllstatus = inword; *place = *place + 2; i = i + 1; } else { /* treat like plain text */ *place = *place + 1; if (i>0) { syllstatus = inword; i = i + 1; }; }; break; case ' ': if (syllstatus == empty) { *place = *place + 1; } else { syllstatus = postword; *place = *place + 1; kspace = 1; }; break; case '-': if (syllstatus == inword) { syllstatus = postword; *place = *place + 1; kspace = 0; } else { *place = *place + 1; }; break; case '*': if (syllstatus == empty) { syllstatus = postword; *place = *place + 1; } else { syllstatus = postword; }; break; case '_': if (syllstatus == empty) { syllstatus = postword; *place = *place + 1; } else { syllstatus = postword; }; break; case '|': if (syllstatus == empty) { syllstatus = failed; *place = *place + 1; } else { syllstatus = postword; *place = *place + 1; kspace = 1; }; waitforbar = 1; break; default: /* copying plain text character across */ /* first character must be alphabetic */ if ((i>0) || isalpha(syllable[0])) { syllstatus = inword; i = i + 1; }; *place = *place + 1; break; }; c = *(words[w]+(*place)); }; syllable[i] = '\0'; if (syllstatus == failed) { syllcount = 0; } else { syllcount = 1; if (strlen(syllable) > 0) { text_data(syllable); }; }; /* now deal with anything after the syllable */ while ((syllstatus != failed) && (syllstatus != foundnext)) { c = *(words[w]+(*place)); switch (c) { case ' ': *place = *place + 1; break; case '-': *place = *place + 1; kspace = 0; break; case '\t': *place = *place + 1; break; case '_': *place = *place + 1; syllcount = syllcount + 1; break; case '|': if (waitforbar == 0) { *place = *place + 1; waitforbar = 1; } else { syllstatus = failed; }; break; default: syllstatus = foundnext; break; }; }; return(syllcount); } write_syllable(place) int place; /* Write out a syllable. This routine must check that it has a line of */ /* lyrics and find one if it doesn't have one. */ { musicsyllables = musicsyllables + 1; if (waitforbar) { lyricsyllables = lyricsyllables + 1; return; }; if ((!nowordline) && (!waitforbar)) { if (thiswline == -1) { thiswline = findwline(thismline); }; if (!nowordline) { int done; done = 0; while (!done) { if (syllcount == 0) { /* try to get fresh word */ syllcount = getword(&wordlineplace, windex); if (waitforbar) { done = 1; if (syllcount == 0) { lyricsyllables = lyricsyllables + 1; }; } else { if (syllcount == 0) { thiswline = findwline(thiswline); if (thiswline == -1) { done = 1; }; }; }; }; if (syllcount > 0) { /* still finishing off a multi-syllable item */ syllcount = syllcount - 1; lyricsyllables = lyricsyllables + 1; done = 1; }; }; }; }; } checksyllables() /* check line of lyrics matches line of music */ { int done; int syllcount; char msg[80]; /* first make sure all lyric syllables are read */ done = 0; while (!done) { syllcount = getword(&wordlineplace, windex); if (syllcount > 0) { lyricsyllables = lyricsyllables + syllcount; } else { thiswline = findwline(thiswline); if (thiswline == -1) { done = 1; } else { windex = pitch[thiswline]; }; }; }; if (lyricsyllables != musicsyllables) { sprintf(msg, "Verse %d mismatch; %d syllables in music %d in lyrics", partrepno+1, musicsyllables, lyricsyllables); event_error(msg); }; lyricsyllables = 0; musicsyllables = 0; } starttrack() /* called at the start of each MIDI track. Sets up all necessary default */ /* and initial values */ { int i; loudnote = 105; mednote = 95; softnote = 80; transpose = global_transpose; set_meter(time_num, time_denom); div_factor = division; gchords = 1; partno = -1; partlabel = -1; g_started = 0; g_ptr = 0; Qinit(); channel = findchannel(); if (gchordson) { addtoQ(0, g_denom, -1, g_ptr, 0); fun.base = 36; fun.vel = 80; gchord.base = 48; gchord.vel = 75; channels[channel] = 1; fun.chan = findchannel(); channels[fun.chan] = 1; gchord.chan = findchannel(); channels[fun.chan] = 0; channels[channel] = 0; }; g_next = 0; partrepno = 0; thismline = -1; thiswline = -1; nowordline = 0; waitforbar = 0; musicsyllables = 0; lyricsyllables = 0; for (i=0; i<26; i++) { part_count[i] = 0; }; } long writetrack(xtrack) /* this routine writes a MIDI track */ int xtrack; { int trackvoice; int inchord; int j, pass; int expect_repeat; int slurring; int state[5]; void mf_write_tempo(); /* default is a normal track */ tracklen = 0L; delta_time = 0L; trackvoice = xtrack; wordson = 0; noteson = 1; gchordson = 0; if (karaoke) { /* is this karaoke track ? */ if (xtrack == 2) { karaokestarttrack(); noteson = 0; wordson = 1; gchordson = 0; trackvoice = wordvoice; } else { if (trackvoice > 2) trackvoice = xtrack - 1; }; }; /* is this accompaniment track ? */ if ((xtrack == ntracks-1) && (gchordvoice != 0)) { noteson = 0; gchordson = 1; trackvoice = gchordvoice; }; if (verbose) { printf("track %d, voice %d\n", xtrack, trackvoice); }; if (xtrack == 0) { if (karaoke) { text_data("@KMIDI KARAOKE FILE"); }; mf_write_tempo(tempo); /* write key */ write_keysig(sf, mi); /* write timesig */ write_meter(time_num, time_denom); if (ntracks > 1) { /* type 1 files have no notes in first track */ return(0L); }; }; starttrack(); inchord = 0; /* write notes */ j = 0; if ((voicesused) && (trackvoice != 1)) { j = findvoice(j, trackvoice, xtrack); }; barno = 0; bar_num = 0; bar_denom = 1; err_num = 0; err_denom = 1; pass = 1; save_state(state, j, barno, div_factor, transpose, channel); slurring = 0; expect_repeat = 0; while (j < notes) { switch(feature[j]) { case NOTE: if (wordson) { write_syllable(j); }; if (noteson) { noteon(j); /* set up note off */ addtoQ(num[j], denom[j], pitch[j] + transpose, channel, -1); }; if (!inchord) { delay(num[j], denom[j], 0); addunits(num[j], denom[j]); }; break; case TNOTE: if (noteson) { noteon(j); /* set up note off */ addtoQ(num[j], denom[j], pitch[j] + transpose, channel, -1); }; break; case REST: if (!inchord) { delay(num[j], denom[j], 0); addunits(num[j], denom[j]); }; break; case CHORDON: inchord = 1; break; case CHORDOFF: if (wordson) { write_syllable(j); }; inchord = 0; delay(num[j], denom[j], 0); addunits(num[j], denom[j]); break; case LINENUM: /* get correct line number for diagnostics */ lineno = pitch[j]; break; case MUSICLINE: if (wordson) { thismline = j; nowordline = 0; }; break; case MUSICSTOP: if (wordson) { checksyllables(); }; break; case PART: j = partbreak(xtrack, trackvoice, j); if (parts == -1) { char msg[1]; msg[0] = (char) pitch[j]; mf_write_meta_event(0L, marker, msg, 1); }; break; case VOICE: /* search on for next occurence of voice */ j = findvoice(j, trackvoice, xtrack); break; case TEXT: mf_write_meta_event(0L, text_event, atext[pitch[j]], strlen(atext[pitch[j]])); break; case TITLE: mf_write_meta_event(0L, sequence_name, atext[pitch[j]], strlen(atext[pitch[j]])); break; case SINGLE_BAR: waitforbar = 0; checkbar(pass); break; case DOUBLE_BAR: waitforbar = 0; checkbar(pass); break; case BAR_REP: waitforbar = 0; softcheckbar(pass); if (expect_repeat) { event_error("Expected end repeat not found at |:"); }; save_state(state, j, barno, div_factor, transpose, channel); expect_repeat = 1; pass = 1; break; case REP_BAR: waitforbar = 0; softcheckbar(pass); if (pass == 1) { if (!expect_repeat) { event_error("Found unexpected :|"); } else { pass = 2; restore_state(state, &j, &barno, &div_factor, &transpose, &channel); slurring = 0; }; } else { pass = 1; expect_repeat = 0; }; break; case BAR1: waitforbar = 0; checkbar(pass); if (pass == 2) { while ((j 0) { div_factor = pitch[j]; }; break; case CHANNEL: channel = pitch[j]; break; case TRANSPOSE: transpose = pitch[j]; break; case RTRANSPOSE: transpose = transpose + pitch[j]; break; case SLUR_ON: if (slurring) { event_error("Unexpected start of slur found"); }; slurring = 1; break; case SLUR_OFF: if (!slurring) { event_error("Unexpected end of slur found"); }; slurring = 0; break; default: break; }; j = j + 1; }; if (expect_repeat) { event_error("Missing :| at end of tune"); }; clearQ(); tracklen = tracklen + delta_time; if (xtrack == 1) { tracklen1 = tracklen; } else { if ((xtrack != 0) && (tracklen != tracklen1)) { char msg[100]; sprintf(msg, "Track %d is %ld units long not %ld", xtrack, tracklen, tracklen1); event_warning(msg); }; }; return (delta_time); } set_meter(n, m) /* set up variables associated with meter */ int n, m; { /* set up barsize */ barsize = n; if (barsize % 3 == 0) { beat = 3; } else { if (barsize % 2 == 0) { beat = 2; } else { beat = barsize; }; }; /* correction factor to make sure we count in the right units */ if (m > 4) { b_num = m/4; b_denom = 1; } else { b_num = 1; b_denom = 4/m; }; } write_meter(n, m) /* write meter to MIDI file */ int n, m; { int t, dd; char data[4]; set_meter(n, m); dd = 0; t = m; while (t > 1) { dd = dd + 1; t = t/2; }; data[0] = (char)n; data[1] = (char)dd; if (n%2 == 0) { data[2] = (char)(24*2*n/m); } else { data[2] = (char)(24*n/m); }; data[3] = 8; mf_write_meta_event(0L, time_signature, data, 4); } write_keysig(sf, mi) /* Write key signature to MIDI file */ int sf, mi; { char data[2]; data[0] = (char) (0xff & sf); data[1] = (char) mi; mf_write_meta_event(0L, key_signature, data, 2); } noteon(n) /* compute note data and call noteon_data to write MIDI note event */ int n; { int vel; /* set velocity */ if (bar_num == 0) { vel = loudnote; } else { if ((bar_denom == 1) && ((bar_num % beat) == 0)) { vel = mednote; } else { vel = softnote; }; }; noteon_data(pitch[n] + transpose, channel, vel); } text_data(s) /* write text event to MIDI file */ char* s; { mf_write_meta_event(delta_time, text_event, s, strlen(s)); tracklen = tracklen + delta_time; delta_time = 0L; } noteon_data(pitch, channel, vel) /* write note to MIDI file and adjust delta_time */ int pitch, channel, vel; { midi_noteon(delta_time, pitch, channel, vel); tracklen = tracklen + delta_time; delta_time = 0L; } write_program(p, channel) /* write 'change program' (new instrument) command to MIDI file */ int p, channel; { char data[1]; data[0] = p; if (channel >= MAXCHANS) { event_error("Channel limit exceeded\n"); } else { mf_write_midi_event(delta_time, program_chng, channel, data, 1); }; tracklen = tracklen + delta_time; delta_time = 0L; } write_control(channel, data, n) /* write MIDI control event */ int channel, n; char data[]; { if (channel >= MAXCHANS) { event_error("Channel limit exceeded\n"); } else { mf_write_midi_event(delta_time, control_change, channel, data, n); }; } delay(a, b, c) /* wait for time a/b */ int a, b, c; { int dt; dt = (div_factor*a)/b + c; err_num = err_num * b + ((div_factor*a)%b)*err_denom; err_denom = err_denom * b; reduce(&err_num, &err_denom); dt = dt + (err_num/err_denom); err_num = err_num%err_denom; timestep(dt, 0); } midi_noteon(delta_time, pitch, chan, vel) /* write note on event to MIDI file */ long delta_time; int pitch, chan, vel; { char data[2]; #ifdef NOFTELL extern int nullpass; #endif data[0] = (char) pitch; data[1] = (char) vel; if (channel >= MAXCHANS) { event_error("Channel limit exceeded\n"); } else { mf_write_midi_event(delta_time, note_on, chan, data, 2); #ifdef NOFTELL if (nullpass != 1) { channels[chan] = 1; }; #else channels[chan] = 1; #endif }; } midi_noteoff(delta_time, pitch, chan) /* write note off event to MIDI file */ long delta_time; int pitch, chan; { char data[2]; data[0] = (char) pitch; data[1] = (char) 0; if (channel >= MAXCHANS) { event_error("Channel limit exceeded\n"); } else { mf_write_midi_event(delta_time, note_off, chan, data, 2); }; } save_note(num, denom, pitch, chan, vel) /* output 'note on' queue up 'note off' for later */ int num, denom; int pitch, chan, vel; { noteon_data(pitch + transpose, chan, vel); addtoQ(num, denom, pitch + transpose, chan, -1); } dogchords(i) /* generate accompaniment notes */ int i; { if ((i == g_ptr) && (g_ptr < strlen(gchord_seq))) { int len; char action; action = gchord_seq[g_ptr]; len = gchord_len[g_ptr]; if ((chordnum == -1) && (action == 'c')) { action = 'f'; }; switch (action) { case 'z': break; case 'c': /* do chord */ if (g_started && gchords) { int i; for (i=0; i