diff options
Diffstat (limited to 'cmt/record.c')
-rw-r--r-- | cmt/record.c | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/cmt/record.c b/cmt/record.c new file mode 100644 index 0000000..44f2373 --- /dev/null +++ b/cmt/record.c @@ -0,0 +1,638 @@ +/* record.c -- keyboard to adagio recorder + * Copyright 1989 Carnegie Mellon University + * + * the interface consists of three routines: + * rec_init() -- initialization + * rec_event(byte *data) -- called to insert (record) midi data, + * -- returns FALSE if no more space + * rec_final() -- called to finish up + */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 27-Feb-86 | Created changelog +* | Use pedal information when computing durations (code taken +* | from transcribe.c) +* 23-Mar-86 | Determine size of transcription when rec_init is called. +* 21-May-86 | Major rewrite to use continuous controls (code taken +* | from transcribe.c) +* 1-Aug-87 | F.H. Changed rec_init() to new memory handling. +* 17-Oct-88 | JCD : portable version. +* 31-Jan-90 | GWL : cleaned up for LATTICE +* 30-Jun-90 | RBD : further changes +* 2-Apr-91 | JDW : further changes +* 28-Apr-03 | DM : changed for portability; true->TRUE, false->FALSE +*****************************************************************************/ + +#include "switches.h" + +#include <stdio.h> +#include <stdlib.h> + +#include "cext.h" +#include "midifns.h" +#include "userio.h" +#include "midicode.h" +#include "record.h" +#include "cmdline.h" + +extern long space; /* how much space is left? */ + +int debug_rec = FALSE; /* verbose debug flag for this module */ + +long max_notes = -1L; /* -1 is flag that space must be allocated */ + +time_type previous_time; + +/**************************************************************** +* data structure notes: the midi stream is stored as an array +* of 4-byte records, each of which is either a time or midi +* data. Midi data always begins with a control byte (high +* order bit set), and it is assumed times are positive (high +* order bit clear), so the two are easy to distinguish +* IF THE COMPILER PUTS THESE BITS IN THE SAME PLACE. It looks +* like the high order byte of the time lines up with the last +* byte of a 4 byte array, so we will always set the high order +* bit of the last array byte when the first 3 bytes are filled +* with MIDI data. This is refered to as the "tag" bit. +* WARNING: Lattice C longs are UNSIGNED, therefore always +* positive. Test the high order bit with a mask. +****************************************************************/ + +#define MIDI_CMD_BIT 0x80 +#define HIGH_BIT 0x80000000 +#define istime(note) (!(((note)->when) & HIGH_BIT)) + +typedef union note_struct { + byte n[4]; + long when; +} +*note_type, note_node; + +private note_type event_buff; /* pointer to allocated buffer */ +private note_type next; /* pointer to next entry in buffer */ +private note_type last; /* pointer to last entry in buffer */ +private int pile_ups; /* inner loop iteration count */ +private int max_pile_up; /* maximum of pile_ups */ +private boolean fixed_octave; /* used to avoid many error messages */ + +/**************************************************************************** +* Routines local to this module +****************************************************************************/ +private void bend_filter(); +private void byteorder(); +private void ctrl_filter(); +private int event_bend(); +private void filter(); +private long getdur(); +private long getnext(); +private char map_ctrl(); +private void output(); + +/**************************************************************************** +* bend_filter +* Inputs: +* note_type note: the current note +* note_type last: the last recorded event +* long now: the current time +* Effect: +* remove pitch bend events in same 0.01 sec time slot +* Implementation: +* If the current event is a pitch bend that bends again +* in the same time slot, make it a no-op by replacing it with +* the time. +****************************************************************************/ + +private void bend_filter(note, last, now) +note_type note; /* current note */ +note_type last; /* the last recorded event */ +long now; /* the current time */ +{ + /* first see if there is another bend in this time + * slot. + */ + note_type note2 = note + 1; + while (note2 < last) { + if (istime(note2) && (note2->when > now)) { + break; /* new time slot */ + } + else if (note->n[0] == note2->n[0]) { + note->when = now; + return; /* found another bend */ + } + note2++; + } +} + +/**************************************************************************** +* byteorder +* Effect: +* check out assumptions about byte order and placement +****************************************************************************/ + +private void byteorder() +{ + note_node test_event; + if ((sizeof(test_event) != 4) || + (sizeof(test_event.when) != 4) || + (sizeof(test_event.n[0]) != 1)) { + gprintf(ERROR, "implementation error: size problem\n"); + EXIT(1); + } + test_event.n[0] = 0x12; + test_event.n[1] = 0x34; + test_event.n[2] = 0x56; + test_event.n[3] = 0x78; + if ((test_event.when != 0x78563412) && + (test_event.when != 0x12345678)) { + gprintf(ERROR, "implementation error: layout problem\n"); + EXIT(1); + } +} + +/**************************************************************************** +* ctrl_filter +* Inputs: +* note_type note: the current note +* note_type last: the last recorded event +* long now: the current time +* Effect: +* remove ctrl change events in same 0.01 sec time slot +* Implementation: +* If the current event is a control change that changes again +* in the same time slot, make it a no-op by replacing it with +* the time. +****************************************************************************/ + +private void ctrl_filter(note, last, now) +note_type note; /* the current note */ +note_type last; /* the last recorded event */ +long now; /* the current time */ +{ + /* see if there is another control change in this time + * slot. + */ + note_type note2 = note+1; + while (note2 < last) { + if (istime(note2) && (note2->when > now)) { + break; /* new time slot */ + } + else if ((note->n[0] == note2->n[0]) && + (note->n[1] == note2->n[1])) { + note->when = now; + return; /* found another change */ + } + note2++; + } +} + +/**************************************************************************** +* event_bend +* Inputs: +* note_type note: pointer to a pitch bend event +* Outputs: +* returns int: an 8 bit pitch bend number +****************************************************************************/ + +private int event_bend(note) +note_type note; +{ + return((int) (((note->n[1]) >> 6) + ((note->n[2]) << 1))); +} + +/**************************************************************************** +* filter +* Inputs: +* note_type last: the last note recorded +* Effect: allow only one control change per time slot (0.01 sec) +* Implementation: +* call ctrl_filter and bend_filter to overwrite control changes with +* noop data (the current time is used as a noop) +****************************************************************************/ + +private void filter(last) +note_type last; +{ + note_type note; /* loop control variable */ + long now=0; /* last time seen */ + int command; /* command pointed to by note */ + + for (note = event_buff; note <= last; note++) { + if (istime(note)) { + now = note->when; + } + else { + command = note->n[0] & MIDI_CODE_MASK; + + if (command == MIDI_CTRL && + note->n[1] == SUSTAIN) { + /* do nothing */; + } + else if (command == MIDI_CTRL) { + ctrl_filter(note, last, now); + } + else if (command == MIDI_TOUCH) { + bend_filter(note, last, now); /* bend and touch use the */ + } + else if (command == MIDI_BEND) { /* same filter routines */ + bend_filter(note, last, now); + } + } + } +} + + +/**************************************************************************** +* getdur +* Inputs: +* int i: index of the note +* note_type last: pointer to the last event recorded +* int ped: TRUE if pedal is down at event i +* long now: the time at event i +* Outputs: +* returns long: the duration of note i +* Assumes: +* assumes i is a note +* Implementation: +* This is tricky because of pedal messages. The note is kept on by +* either the key or the pedal. Keep 2 flags, key and ped. Key is +* turned off when a key is released, ped goes off and on with pedal. +* Note ends when (1) both key and ped are FALSE, (2) key is +* pressed (this event will also start another note). +****************************************************************************/ + +private long getdur(i, last, ped, now) +int i; +note_type last; +int ped; +long now; +{ + int key = TRUE; /* flag that says if note is on */ + long start = now; + int chan = event_buff[i].n[0] & MIDI_CHN_MASK; + int pitch = event_buff[i].n[1]; + note_type note = &(event_buff[i+1]); + int noteon; /* TRUE if a noteon message received on chan */ + int keyon; /* TRUE if noteon message had non-zero velocity */ + + /* search from the next event (i+1) to the end of the buffer: + */ + for (; note < last; note++) { + if (istime(note)) { + now = note->when; + } + else { + noteon = keyon = FALSE; + if ((note->n[0] & MIDI_CHN_MASK) == chan) { + noteon = ((note->n[0] & MIDI_CODE_MASK) == MIDI_ON_NOTE) && + (note->n[1] == pitch); + keyon = noteon && (note->n[2] != 0); + if ((noteon && (note->n[2] == 0)) || + (((note->n[0] & MIDI_CODE_MASK) == MIDI_OFF_NOTE) && + (note->n[1] == pitch))) key = FALSE; + if (((note->n[0] & MIDI_CODE_MASK) == MIDI_CTRL) && + note->n[1] == SUSTAIN && note->n[2] == 127) ped = TRUE; + if (((note->n[0] & MIDI_CODE_MASK) == MIDI_CTRL) && + note->n[1] == SUSTAIN && note->n[2] == 0) ped = FALSE; + + if ((!key && !ped) || keyon) + return(now - start); + } + } + } + return(last->when - start); +} + +/**************************************************************************** +* getnext +* Inputs: +* int i: the index of the current note +* note_type last: pointer to last valid data +* long now: the current time +* Outputs: +* returns long: the time of the next note, program, or control change +* (returns time of last event if nothing else is found) +****************************************************************************/ + +private long getnext(i, last, now) +int i; /* the index of the current note */ +note_type last; /* pointer to last valid data */ +long now; /* the current time */ +{ + i++; /* advance to next item */ + for (; event_buff + i < last; i++) { + note_type note = &(event_buff[i]); + int cmd = note->n[0] & MIDI_CODE_MASK; + + if (istime(note)) { + now = note->when; + } + else if (((cmd == MIDI_ON_NOTE) && + (note->n[2] != 0)) /* note on */ || + (cmd == MIDI_CH_PROGRAM) /* program change */ || + ((cmd == MIDI_CTRL) && + (note->n[1] != SUSTAIN) /* control change */ ) || + (cmd == MIDI_TOUCH) || + (cmd == MIDI_BEND)) { + return(now); + } + } + return(last->when); +} + +/**************************************************************************** +* map_ctrl +* Inputs: +* int control: a midi control number +* Outputs: +* returns char: an adagio control change command letter, EOS if +* control change is not one of PORTARATE, PORTASWITCH, +* MODWHEEL, FOOT +****************************************************************************/ + +private char map_ctrl(control) +int control; +{ + switch (control) { +/* 'J' is no longer code for PORTARATE + case PORTARATE: + return 'J'; */ + case PORTASWITCH: + return 'K'; + case MODWHEEL: + return 'M'; + case VOLUME: + return 'X'; + default: + return EOS; + } +#ifdef LATTICE322 + return EOS; /* make Lattice C type checker happy */ +#endif +} + +/**************************************************************************** +* output +* Inputs: +* FILE *fp: an opened file pointer +* note_type last: the last data in the buffer +* boolean absflag: set to TRUE if first line of the adagio score should +* include the absolute time +* Effect: +* write adagio file using data in event_buff +* Implementation: +* NOTE: put all program changes in rests +* use N(ext) notation for all timing +* output no more than one continuous parameter change per +* clock tick for each continuous change parameter +****************************************************************************/ + +private void output(fp, last, absflag) +FILE *fp; +note_type last; +boolean absflag; +{ + int i; /* loop counter */ + int command; /* the current command */ + int voice; /* the midi channel of the current event */ + int last_velocity = -1; /* used to filter repeated Lnn attributes */ + int last_voice = 0; /* the default adagio channel (1) */ + int ped = FALSE; /* flag maintains state of pedal */ + int how_many = last - event_buff; + long now=0; /* the time of the next event */ + + if (fp == NULL) { + gprintf(ERROR, "internal error: output called with NULL file.\n"); + EXIT(1); + } + + if (debug_rec) + gprintf(GDEBUG,"hint: if file is not being closed, decrease MAXSPACE\n"); + + + fprintf(fp, "!MSEC\n"); /* times will be in milliseconds */ + /* set the initial absolute time, all other times are relative */ + + if (absflag) { + now = event_buff[0].when; + if (now < 0) { + fprintf(fp, "* First event took place at Adagio time %d,\n", + (int)now); + fprintf(fp, "* but Adagio cannot represent negative times,\n"); + fprintf(fp, "* so this entire score will be %d ms late\n", + (int)-now); + gprintf(TRANS, "First event took place at Adagio time %d!\n", + (int)now); + gprintf(TRANS, "All events times will be %d ms late\n", + (int)-now); + now = 0L; + } + fprintf(fp, "T%ld ", now); + } + + for (i = 0; i < how_many; i++) { + if (debug_rec) { + gprintf(GDEBUG,"ev %d: %x %x %x (%ld)\n", i, event_buff[i].n[0], + event_buff[i].n[1], event_buff[i].n[2], event_buff[i].when); + } + + if (istime(event_buff+i)) { + now = event_buff[i].when; + if (debug_rec) gprintf(GDEBUG,"i = %d, now = %ld\n", i, now); + } else { + boolean needs_voice = TRUE; + command = event_buff[i].n[0] & MIDI_CODE_MASK; + voice = event_buff[i].n[0] & MIDI_CHN_MASK; + + if (command == MIDI_ON_NOTE && event_buff[i].n[2] != 0) { + int velocity = event_buff[i].n[2]; + write_pitch(fp, event_buff[i].n[1]); + fprintf(fp, " U%ld", getdur(i, last, ped, now)); + if (last_velocity != velocity) { + fprintf(fp, " L%d", velocity); + last_velocity = velocity; + } + } else if (command == MIDI_CH_PROGRAM) { + fprintf(fp, "Z%d", event_buff[i].n[1] + 1); + } else if (command == MIDI_CTRL && + event_buff[i].n[1] == SUSTAIN) { + ped = (event_buff[i].n[2] != 0); + needs_voice = FALSE; + } else if (command == MIDI_CTRL) { + char c = map_ctrl(event_buff[i].n[1]); + if (c != EOS) fprintf(fp, "%c%d", c, event_buff[i].n[2]); + else fprintf(fp, "~%d(%d)", event_buff[i].n[1], event_buff[i].n[2]); + } else if (command == MIDI_TOUCH) { + fprintf(fp, "O%d", event_buff[i].n[1]); + } else if (command == MIDI_BEND) { + fprintf(fp, "Y%d", event_bend(&event_buff[i])); + } else if (command == MIDI_ON_NOTE || command == MIDI_OFF_NOTE) { + needs_voice = FALSE; /* ignore note-offs */ + } else { + gprintf(ERROR, "Command 0x%x ignored\n", command); + needs_voice = FALSE; + } + if (needs_voice) { + if (last_voice != voice) { + fprintf(fp, " V%d", voice + 1); + last_voice = voice; + } + fprintf(fp, " N%d", (int)(getnext(i, last, now) - now)); + fprintf(fp, "\n"); + } + } + } +} + + +/**************************************************************************** +* write_pitch +* Inputs: +* FILE *fp: an open file +* int p: a pitch number +* Effect: write out the pitch name for a given number +****************************************************************************/ + +void write_pitch(FILE *fp, int p) +{ + static char *ptos[] = { + "C", "CS", "D", "EF", "E", "F", "FS", "G", + "GS", "A", "BF", "B" }; + /* avoid negative numbers: adagio can't express lowest octave: */ + while (p < 12) { + if (!fixed_octave) { + gprintf(ERROR, "%s%s%s", + "A low note was transposed up an octave\n", + "(Adagio cannot express the lowest MIDI octave).\n", + "This message will appear only once.\n"); + fixed_octave = TRUE; + } + p += 12; + } + fprintf(fp, "%s%d", ptos[p % 12], (p / 12) - 1); +} + +/********************************************************************** +* rec_final +* Inputs: +* boolean absflag: output absolute time of first note if TRUE +* Effect: +* Write recorded data to a file +**********************************************************************/ + +void rec_final(FILE *fp, boolean absflag) +{ + next->when = gettime(); + last = next; + if (debug_rec) gprintf(GDEBUG,"max_pile_up = %d, ", max_pile_up); + gprintf(TRANS,"%ld times and events recorded.\n", + (long) (last - event_buff)); + filter(last); + output(fp, last, absflag); + fclose(fp); + FREE(event_buff); + max_notes = -1; +} + +/**************************************************************************** +* rec_init +* Inputs: +* char *file: pointer to file name from command line (if any) +* boolean bender: TRUE if pitch bend should be enabled +* Outputs: +* return TRUE if initialization succeeds +* Effect: +* prepares module to record midi input +****************************************************************************/ + +/* ENOUGH_ROOM says if we have room for 10000 events + 10000 timestamps = + * 20000 note_struct's, then that's "enough room" for recording a sequence. + * If more ram is available, it won't be used. If less is available, we'll + * use as much as we can get, minus "SPACE_FOR_PLAY", which leaves a little + * bit of spare ram in case Moxc or stdio need to allocate some space. + * For DOS, we limit recording space to 64K. + */ +#ifdef DOS +#define ENOUGH_ROOM 64000L +#else +#define ENOUGH_ROOM (20000L * sizeof(union note_struct)) +#endif + + +boolean rec_init(boolean bender) +{ + size_t biggestChunk, spaceForRecord; + + debug_rec = cl_switch("debug"); + byteorder(); + pile_ups = 0; + max_pile_up = 0; + previous_time = (unsigned) -1L; /* this will force putting in initial timestamp */ + fixed_octave = FALSE; + + if (max_notes == -1) { /* allocate space 1st time rec_init called */ + biggestChunk = AVAILMEM; + if (biggestChunk <= SPACE_FOR_PLAY) { + /* not enough memory; give up */ + return(FALSE); + } + else { + spaceForRecord = + MIN((biggestChunk - SPACE_FOR_PLAY), ENOUGH_ROOM); + /* leave SPACE_FOR_PLAY contiguous bytes of memory */ + } + max_notes = spaceForRecord / sizeof(note_node); + /* gprintf(GDEBUG,"max_notes = %d\n", max_notes);*/ + event_buff = (note_type) MALLOC(spaceForRecord); + if (event_buff == NULL) { + /* should never happen */ + gprintf(FATAL, "Implementation error (record.c): getting memory."); + return FALSE; + } + } + next = event_buff; + last = event_buff + max_notes - 2; /* it is critical that last not point + * to the very last storage loc */ + midi_cont(bender); + return((boolean)(max_notes > 10)); + /* it would be silly to record with only room enough for 10 notes! */ +} + + +/**************************************************************************** +* rec_event +* Inputs: +* long time: the current time +* long data: midi data to record +* Outputs: +* returns FALSE if there is no more memory +* Effect: reads and stores any input +* Implementation: +* time stamps and midi events share the same buffer of 4-byte events +* save time at most once per call to rec_poll +* save time only if it changes +****************************************************************************/ + +boolean rec_event(long *data, time_type time) +{ + /* can't allow negative time because sign bit distinguishes + * data from time: */ + if (time < 0) time = 0; + + if (previous_time != time) { + next++->when = previous_time = time; + if (next >= last) goto overflow; + } + + next->when = *data; + next++->n[3] = MIDI_CMD_BIT; /* set tag bit */ + if (next >= last) goto overflow; + return TRUE; + +overflow: + next = last; /* last doesn't really point to last storage */ + gprintf(ERROR, "No more memory.\n"); + return FALSE; +} |