/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: April 2020 */ /* This file contains code for writing a MIDI file */ #include "pmwhdr.h" typedef struct midi_event { int time; short int seq; uschar data[8]; } midi_event; enum { HR_NONE, HR_REPEATED, HR_PLAYON }; /************************************************* * Local variables * *************************************************/ static int file_count = 0; static midi_event *events = NULL; static midi_event *next_event; static int next_event_seq; static int last_written_time; static int running_status; static uschar midi_channel[MAX_STAVE+1]; static uschar midi_channel_volume[MIDI_MAXCHANNEL]; static FILE *midi_file; static uschar midi_note[MAX_STAVE+1]; static int play_bar; static int play_bar_moff; static movtstr *play_movt; static int play_nextbar; static int play_nextbar_moff; static BOOL play_onebar_only = FALSE; static usint play_staves[STAVE_BITVEC_SIZE] = { ~0, ~0 }; static int play_tempo; static int play_volume = 127; static int repeat_bar; static int repeat_bar_moff; static int repeat_count; static int repeat_endbar; static uschar stavetie[MAX_STAVE+1]; static signed char midi_transpose[MAX_STAVE+1]; static uschar stave_volume[MAX_STAVE+1]; /************************************************* * Comparison function for sorting events * *************************************************/ /* This function is passed to qsort(). Similar events at the same time should preserve their order. To do this, we give each event a sequence number that is compared if the times are equal. This function should never return zero in practice. Arguments: a pointer to an event structure b pointer to an event structure Returns: -1, 0, or +1 */ static int cf(const void *a, const void *b) { const midi_event *ma = (const midi_event *)a; const midi_event *mb = (const midi_event *)b; if (ma->time < mb->time) return -1; if (ma->time > mb->time) return +1; if (ma->seq < mb->seq) return -1; if (ma->seq > mb->seq) return +1; return 0; } /************************************************* * Find length of bar * *************************************************/ /* Scan the staves selected for playing until one with some notes in it is found. If there are none, return zero. If the bar contains only a centred rest, carry on looking for another stave in case this bar is a nocheck whole-bar rest, which might be of different length to the remaining staves' bars. Arguments: none; the required movement/bar are in play_movt and play_bar Returns: length of the bar, or zero */ static int find_barlength(void) { int stave; int yield = 0; for (stave = 1; stave <= play_movt->laststave; stave++) { BOOL notjustrest = FALSE; int length = 0; int gracecount = 0; if (mac_teststave(play_staves, stave)) { bstr *p = ((play_movt->stavetable)[stave])->barindex[play_bar]; if (p != NULL) { int moff = 0; int type = p->type; while (type != b_End) { switch(type) { case b_Jump: p = (bstr *)(((b_Jumpstr *)p)->next); break; case b_reset: if (moff > length) length = moff; moff = 0; break; case b_note: { b_notestr *note = (b_notestr *)p; moff += note->length; if (note->length == 0) gracecount++; else gracecount = 0; if (note->spitch != 0 || (note->flags & nf_centre) == 0) notjustrest = TRUE; } break; } p = (bstr *)((uschar *)p + length_table[type]); type = p->type; } /* At bar end check for longest length in case there were resets */ if (moff > length) length = moff; /* If there were grace notes at the end of the bar, increase its length by 1/10 second for each one. */ length += (gracecount*len_crotchet*play_tempo)/(60*10); /* If we have found a bar with notes in it other than a whole bar rest, we are done. Otherwise carry on, but leave length so far in yield in case there are no staves with notes. */ if (length > yield) yield = length; if (yield > 0 && notjustrest) break; } } } return yield; } /************************************************* * Find second time bar * *************************************************/ /* Starting from the bar after play_bar in play_movt, look along the stave for the second time bar. Argument: the stave to search Returns: the bar number, or 0 if there are no more bars */ static int find_second_time(int stave) { int yield = play_bar + 1; for (;;) { int type; bstr *p = ((play_movt->stavetable)[stave])->barindex[yield]; if (p == NULL) return 0; type = p->type; while (type != b_End) { switch(type) { case b_Jump: p = (bstr *)(((b_Jumpstr *)p)->next); break; case b_nbar: return yield; } p = (bstr *)((uschar *)p + length_table[type]); type = p->type; } yield++; } } /************************************************* * Write 32-bit number * *************************************************/ /* Write the most significant byte first. Argument: the number Returns: nothing */ static void write32(int n) { fputc((n>>24)&255, midi_file); fputc((n>>16)&255, midi_file); fputc((n>>8)&255, midi_file); fputc(n&255, midi_file); file_count += 4; } /************************************************* * Write 16-bit number * *************************************************/ /* Write the most significant byte first. Argument: the number Returns: nothing */ static void write16(int n) { fputc((n>>8)&255, midi_file); fputc(n&255, midi_file); file_count += 2; } /************************************************* * Write variable length number * *************************************************/ /* The number is chopped up into 7-bit chunks, and then written with the most significant chunk first. All but the last chunk have the top bit set. This copes with numbers up to 28-bits long. That's all that MIDI needs. Argument: the number Returns: nothing */ static void writevar(int n) { if (n < 0x80) { fputc(n, midi_file); file_count++; } else if (n < 0x4000) { fputc(((n>>7)&127)|0x80, midi_file); fputc(n&127, midi_file); file_count += 2; } else if (n < 0x200000) { fputc(((n>>14)&127)|0x80, midi_file); fputc(((n>>7)&127)|0x80, midi_file); fputc(n&127, midi_file); file_count += 3; } else { fputc(((n>>21)&127)|0x80, midi_file); fputc(((n>>14)&127)|0x80, midi_file); fputc(((n>>7)&127)|0x80, midi_file); fputc(n&127, midi_file); file_count += 4; } } /************************************************* * Write one byte * *************************************************/ static void writebyte(int n) { fputc(n & 255, midi_file); file_count++; } /************************************************* * Write one bar * *************************************************/ /* The bar number is in play_bar. Argument: TRUE if this is the final bar to be written Returns: nothing */ static void writebar(BOOL is_lastbar) { BOOL oknbar = TRUE; int hadrepeat = HR_NONE; int maxmoff = 0; int stave; int *ptc = play_movt->play_tempo_changes; int this_barlength = find_barlength(); midi_event *eptr, *neptr; DEBUG(("writebar %d\n", play_bar)); /* Find the tempo for this bar */ if (ptc != NULL && play_bar >= *ptc) { while (play_bar >= *ptc) ptc += 2; if (ptc[-1] != play_tempo) { usint temp; play_tempo = ptc[-1]; temp = 60000000/play_tempo; /* Microseconds per crotchet */ next_event->time = 0; next_event->seq = next_event_seq++; next_event->data[0] = 6; next_event->data[1] = 0xff; next_event->data[2] = 0x51; next_event->data[3] = 0x03; next_event->data[4] = (uschar)((temp >> 16) & 0xffu); next_event->data[5] = (uschar)((temp >> 8) & 0xffu); next_event->data[6] = (uschar)(temp & 0xffu); next_event++; } } /* Now scan the staves. When [notes off] appears in the input, a control is placed at the start of each bar into which it continues, so we do not have to keep track between bars. */ for (stave = 1; stave <= play_movt->laststave; stave++) { int moff = 0; if (mac_teststave(play_staves, stave)) { bstr *p = ((play_movt->stavetable)[stave])->barindex[play_bar]; if (p != NULL) { int midi_stave_status, midi_stave_pitch, midi_stave_velocity; int playtranspose = midi_transpose[stave]; int adjustlength = 0; int type = p->type; int tremolo = -1; BOOL noteson = TRUE; /* See above comment */ /* Set up midi parameters */ midi_stave_status = 0x90 + midi_channel[stave] - 1; midi_stave_pitch = midi_note[stave]; midi_stave_velocity = ((play_volume * stave_volume[stave] * midi_channel_volume[midi_channel[stave]-1])/225); /* Scan the bar's data */ while (type != b_End) { switch(type) { case b_Jump: p = (bstr *)(((b_Jumpstr *)p)->next); break; case b_reset: moff = 0; break; /* If a previous stave saw a repeat, hadrepeat is set to indicate what has been done. */ case b_rrepeat: if (play_repeats) { if (!play_onebar_only) { switch (hadrepeat) { case HR_PLAYON: break; case HR_REPEATED: goto NEXT_STAVE; default: case HR_NONE: if (repeat_count == 1) { hadrepeat = HR_REPEATED; play_nextbar = repeat_bar; play_nextbar_moff = repeat_bar_moff; repeat_endbar = play_bar; repeat_count++; goto NEXT_STAVE; /* Skip rest of bar */ } else { hadrepeat = HR_PLAYON; if (play_bar == repeat_endbar) repeat_count = 1; } break; } } } break; case b_lrepeat: repeat_bar = play_bar; repeat_bar_moff = moff; break; case b_nbar: if (moff == 0 && !play_onebar_only && oknbar) { b_nbarstr *b = (b_nbarstr *)p; if (b->n == 1 && repeat_count > 1) { int second = find_second_time(stave); if (second > 0) { play_bar = second; play_bar_moff = 0; play_nextbar = play_bar + 1; play_nextbar_moff = 0; repeat_bar = play_bar; repeat_bar_moff = 0; repeat_count = 1; writebar(is_lastbar); } return; } else oknbar = FALSE; } break; case b_notes: noteson = ((b_notesstr *)p)->value; break; case b_playchange: { b_playchangestr *change = (b_playchangestr *)p; playtranspose += change->transpose; midi_transpose[stave] = playtranspose; /* If the relative volume parameter occurs with a change of channel, it is a channel volume change. Otherwise it is a stave volume change. */ if (change->volume < 128 && change->channel == 128) { stave_volume[stave] = change->volume; midi_stave_velocity = ((play_volume * stave_volume[stave] * midi_channel_volume[midi_channel[stave]-1])/225); } /* Other changes */ if (change->channel < 128) { midi_channel[stave] = change->channel; midi_stave_status = 0x90 + midi_channel[stave] - 1; if (change->volume < 128) midi_channel_volume[change->channel - 1] = change->volume; midi_stave_velocity = ((play_volume * stave_volume[stave] * midi_channel_volume[midi_channel[stave]-1])/225); } if (change->note < 128) midi_stave_pitch = midi_note[stave] = change->note; /* A voice change must be scheduled to occur in the correct sequence with the notes. */ if (change->voice < 128) { next_event->time = moff; next_event->seq = next_event_seq++; next_event->data[0] = 2; next_event->data[1] = 0xC0 + midi_channel[stave] - 1; next_event->data[2] = change->voice; next_event++; } } break; case b_ornament: { b_ornamentstr *orn = (b_ornamentstr *)p; if (orn->ornament == or_trem1 || orn->ornament == or_trem2) tremolo = orn->ornament; } break; case b_note: { b_notestr *note = (b_notestr *)p; BOOL thisnotetied = FALSE; int length = note->length; int nstart = 0; int scrub = 1; int scrubcount; int tiebarcount = 1; int pitchcount = 0; int pitchlist[20]; int pitchlen[20]; int pitchstart[20]; oknbar = FALSE; if (length == 0) { length = (len_crotchet*play_tempo)/(60*10); /* 1/10 sec */ adjustlength += length; } else { length -= adjustlength; adjustlength = 0; } /* nf_noplay is set when a note has already been played, because of a previous tie, which might have been in a previous bar. */ if ((noteson || midi_for_notes_off) && moff >= play_bar_moff && note->spitch != 0 && (note->flags & nf_noplay) == 0) { /* Get a list of pitches in a chord, and leave the general pointer p at the final note. */ do { pitchlist[pitchcount] = note->truepitch; pitchlen[pitchcount] = length; pitchstart[pitchcount++] = nstart; p = (bstr *)note; mac_advancechord(note); } while (note->type == b_chord); /* Advance to start of following note */ nstart += length; /* If the note is followed by a tie, find the next note or chord on the stave. If any of its notes have the same pitch as any of those in the list, extend their playing times. If there are any new notes, add them to the list, with a later starting time. We have to do this because all the notes we are accumulating will be output at the end of this bar. Set the noplay flag in the next notes, to stop them playing again later. Continue for multiple ties. */ while (note->type == b_tie) { int i, nlength; note = misc_nextnote(note, NULL); if (note == NULL && play_bar + tiebarcount <= play_movt->barcount) { note = (b_notestr *)((play_movt->stavetable)[stave])-> barindex[play_bar + tiebarcount++]; if (note != NULL && note->type != b_note) note = misc_nextnote(note, NULL); } if (note == NULL) break; nlength = note->length; do { for (i = 0; i < pitchcount; i++) { if (pitchlist[i] == note->truepitch) { pitchlen[i] += note->length; thisnotetied = TRUE; note->flags |= nf_noplay; break; } } if (i >= pitchcount) { pitchlist[pitchcount] = note->truepitch; pitchlen[pitchcount] = nlength; note->flags |= nf_noplay; pitchstart[pitchcount++] = nstart; } mac_advancechord(note); } while (note->type == b_chord); nstart += nlength; } /* Handle some common scrubbing */ if (tremolo > 0 && !thisnotetied) { int ttype = (tremolo == or_trem1)? 1 : 2; switch (length) { case len_crotchet: scrub = 2*ttype; break; case (len_crotchet*3)/2: scrub = 3*ttype; break; case len_minim: scrub = 4*ttype; break; case (len_minim*3)/2: scrub = 6*ttype; break; } } for (scrubcount = 0; scrubcount < scrub; scrubcount++) { int pc = pitchcount; /* For each required pitch, set up the events to make a sound. The lengths may be different because of tied/non-tied notes in chords, but these can only happen when not scrubbing. */ while (--pc >= 0) { int pitch = pitchlist[pc] + playtranspose; int start = moff - play_bar_moff + pitchstart[pc] + scrubcount * (pitchlen[pc]/scrub); /* We have to schedule a note on and a note off event. Use note on with zero velocity for note off, as that means running status can be used. MIDI middle C is 60; PMW uses 48, so first adjust the pitch. */ pitch = midi_stave_pitch? midi_stave_pitch : (pitch + 12); next_event->time = start; next_event->seq = next_event_seq++; next_event->data[0] = 3; next_event->data[1] = midi_stave_status; next_event->data[2] = pitch; next_event->data[3] = midi_stave_velocity; next_event++; next_event->time = start + (pitchlen[pc]/scrub); next_event->seq = next_event_seq++; next_event->data[0] = 3; next_event->data[1] = midi_stave_status; next_event->data[2] = pitch; next_event->data[3] = 0; next_event++; } } } stavetie[stave] = thisnotetied; moff += length; } tremolo = -1; break; } p = (bstr *)((uschar *)p + length_table[type]); type = p->type; } } } NEXT_STAVE: if (moff > maxmoff) maxmoff = moff; } /* Sort and output the items we've created, along with any events left over from the previous bar (ending tied notes). We relativize the times, and make use of running status. Stop when we hit either the end, or an event that is past the end of the bar, unless this is the last bar being played. */ qsort(events, next_event - events, sizeof(midi_event), cf); for (eptr = events; eptr < next_event; eptr++) { if (!is_lastbar && eptr->time > this_barlength) break; writevar(mac_muldiv(eptr->time - last_written_time, 24, len_crotchet)); last_written_time = eptr->time; if ((eptr->data[1] & 0xf0) == 0x90) { if (eptr->data[1] != running_status) { writebyte(eptr->data[1]); running_status = eptr->data[1]; } writebyte(eptr->data[2]); writebyte(eptr->data[3]); } else { int i; running_status = 0; for (i = 1; i <= eptr->data[0]; i++) writebyte(eptr->data[i]); } } /* If we haven't written all the items (some notes are tied over the barline), shift down the remaining events, and re-relativize them. */ neptr = events; next_event_seq = 0; for (; eptr < next_event; eptr++, neptr++) { *neptr = *eptr; neptr->time -= this_barlength; next_event_seq = neptr->seq + 1; } next_event = neptr; /* Set time for start of next bar */ last_written_time -= (maxmoff - play_bar_moff); } /************************************************* * Write MIDI file * *************************************************/ /* This is the only external entry to this set of functions. The data is all in memory and global variables. Arguments: none Returns: nothing */ void midi_write(void) { int start_bar; int end_bar; int i, stave; int temp; midi_file = Ufopen(midi_filename, "w"); if (midi_file == NULL) error_moan(ERR4, midi_filename, strerror(errno)); /* Hard error */ DEBUG(("midi_write()\n")); /* Initialize things */ if (play_movt_number <= 0) play_movt_number = 1; play_movt = movement[play_movt_number]; play_tempo = play_movt->play_tempo; start_bar = (play_startbar <= 0)? 1 : play_startbar; end_bar = (play_endbar <= 0)? play_movt->barcount : play_endbar; play_onebar_only = (start_bar == end_bar); play_volume = 127; /* PRO TEM */ /* Stave selection is the main stave selection */ for (i = 0; i < STAVE_BITVEC_SIZE; i++) play_staves[i] = play_movt->staves[i] & main_staves[i]; /* Initialize the tie information */ for (stave = 1; stave <= play_movt->laststave; stave++) stavetie[stave] = FALSE; /* Miscellaneous stuff */ last_written_time = 0; running_status = 0; /* Get store in which to hold a bar's events before sorting. For the first bar, it is empty at the start. */ events = malloc(sizeof(midi_event) * 1000); next_event = events; next_event_seq = 0; /* Set up the initial per-stave vectors */ memcpy(midi_channel, play_movt->midi_channel, sizeof(midi_channel)); memcpy(midi_channel_volume, play_movt->midi_volume, sizeof(midi_channel_volume)); memcpy(midi_note, play_movt->midi_note, sizeof(midi_note)); /* Write header chunk */ fprintf(midi_file, "MThd"); write32(6); /* length */ write16(0); /* format */ write16(1); /* number of tracks */ write16(24); /* ticks per crotchet (MIDI standard) */ /* Now write the track, leaving space for the length */ fprintf(midi_file, "MTrk"); write32(0); file_count = 0; /* For computing the length */ /* Output any user-supplied initialization. The user's data is a plain MIDI stream, without any time deltas. Ensure that each event is set to occur at the beginning (time zero). */ if (curmovt->midi_start != NULL) { for (i = 1; i <= play_movt->midi_start[0]; i++) { if ((play_movt->midi_start[i] & 0x80) != 0) writebyte(0); writebyte(play_movt->midi_start[i]); } } /* Default tempo - can change for specific bars */ writebyte(0); writebyte(0xff); writebyte(0x51); writebyte(0x03); temp = 60000000/play_tempo; /* Microseconds per crotchet */ writebyte(temp>>16); writebyte(temp>>8); writebyte(temp); /* Assign MIDI voices to MIDI channels if required. */ for (i = 1; i <= MIDI_MAXCHANNEL; i++) { if (play_movt->midi_voice[i-1] < 128) { writebyte(0); /* delta time */ writebyte(0xC0 + i - 1); writebyte(play_movt->midi_voice[i-1]); } } /* Initialize the per-stave relative volume & transpose vectors */ memcpy(stave_volume, play_movt->play_volume, sizeof(stave_volume)); memcpy(midi_transpose, play_movt->playtranspose, sizeof(midi_transpose)); /* Unless starting at the beginning, scan through the playing changes blocks that were set up by a heading directive, and update the data if the change happened before this bar. The chain is for all staves, so we must always scan to the end. */ if (start_bar > 1) { b_playchangestr *change = play_movt->play_changes; while (change != NULL) { if (change->barno < play_bar) { stave = change->stave; /* If change volume occurs with midi channel change, it is a channel volume change, not a stave volume change. */ if (change->volume < 128 && change->channel == 128) stave_volume[stave] = change->volume; if (change->transpose) midi_transpose[stave] += change->transpose; if (change->channel < 128) { midi_channel[stave] = change->channel; if (change->volume < 128) midi_channel_volume[change->channel-1] = change->volume; } if (change->note < 128) midi_note[stave] = change->note; if (change->voice < 128) { writebyte(0); /* delta time */ writebyte(0xC0 + midi_channel[stave] - 1); writebyte(change->voice); } } change = change->next; } } /* Also, if not starting at the beginning, we must scan through the stave data for all preceding bars, in order to pick up any in-line MIDI changes. */ for (play_bar = 1; play_bar < start_bar; play_bar++) { for (stave = 1; stave <= play_movt->laststave; stave++) { bstr *p = ((play_movt->stavetable)[stave])->barindex[play_bar]; if (p != NULL && mac_teststave(play_staves, stave)) { int type = p->type; while (type != b_End) { switch(type) { case b_Jump: p = (bstr *)(((b_Jumpstr *)p)->next); break; case b_playchange: { b_playchangestr *change = (b_playchangestr *)p; midi_transpose[stave] += change->transpose; /* If the relative volume parameter occurs with a change of channel, it is a channel volume change. Otherwise it is a stave volume change. */ if (change->volume < 128 && change->channel == 128) stave_volume[stave] = change->volume; /* Other changes */ if (change->channel < 128) { midi_channel[stave] = change->channel; if (change->volume < 128) midi_channel_volume[change->channel - 1] = change->volume; } if (change->note < 128) midi_note[stave] = change->note; if (change->voice < 128) { writebyte(0); /* delta time */ writebyte(0xC0 + midi_channel[stave] - 1); writebyte(change->voice); } } break; default: break; } p = (bstr *)((uschar *)p + length_table[type]); type = p->type; } } } } /* Now write the bars */ repeat_bar = play_bar; repeat_bar_moff = 0; repeat_endbar = -1; repeat_count = 1; for (play_bar = start_bar; play_bar <= end_bar;) { play_nextbar = play_bar + 1; play_nextbar_moff = 0; writebar(play_bar == end_bar); play_bar = play_nextbar; play_bar_moff = play_nextbar_moff; } /* Mark the end of the track, and fill in its length before closing the file */ writebyte(0); writebyte(0xff); writebyte(0x2f); writebyte(0); fseek(midi_file, 18, SEEK_SET); write32(file_count); fclose(midi_file); } /* End of midi.c */