💾 Archived View for runjimmyrunrunyoufuckerrun.com › src › foreign › pmw › src › midi.c captured on 2021-12-17 at 13:26:06.
View Raw
More Information
-=-=-=-=-=-=-
/*************************************************
- 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 */