/* record.c -- keyboard to adagio recorder * * the interface consists of three routines: * rec_init() -- initialization * int rec_poll(long time) -- called during recording, returns true if * recording space is exhausted * 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) *****************************************************************************/ #include "cext.h" #include "stdio.h" #include "malloc.h" #include "mpu.h" #include "userio.h" #include "midicode.h" #include "record.h" extern long space; /* how much space is left? */ int debug_rec = false; /* verbose debug flag for this module */ int max_notes = -1; /* -1 is flag that space must be allocated */ /**************************************************************** * 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)) #define ndsw 2 private char *dsw[ndsw] = { "-d", "-debug" }; typedef union note_struct { byte n[4]; long when; } *note_type, note_node; private note_type event_buff; /* pointer to allocated buffer */ private FILE *fp; private char file_name[100]; 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; /* maximum of pile_ups */ /**************************************************************************** * 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(); private void put_pitch(); /**************************************************************************** * 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() { if ((sizeof(event_buff[0]) != 4) || (sizeof(event_buff[0].when) != 4) || (sizeof(event_buff[0].n[0]) != 1)) { fprintf(stderr, "implementation error: size problem\n"); exit(1); } event_buff[0].n[0] = 0x12; event_buff[0].n[1] = 0x34; event_buff[0].n[2] = 0x56; event_buff[0].n[3] = 0x78; if ((event_buff[0].when != 0x78563412) && (event_buff[0].when != 0x12345678)) { fprintf(stderr, "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; /* last time seen */ int command; /* command pointed to by note */ int chan; /* channel 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; chan = note->n[0] & MIDI_CHN_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, NULL if * control change is not one of PORTARATE, PORTASWITCH, * MODWHEEL, FOOT ****************************************************************************/ private char map_ctrl(control) int control; { switch (control) { case PORTARATE: return 'J'; case PORTASWITCH: return 'K'; case MODWHEEL: return 'M'; case FOOT: return 'X'; default: return 0; } return NULL; /* make Lattice C type cheker happy */ } /**************************************************************************** * 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 chan; /* the midi channel of the current event */ int lastchan = 0; /* the default adagio channel (1) */ int ped = false; /* flag maintains state of pedal */ int how_many = last - event_buff; long now; /* the time of the next event */ boolean bad_ctrl_flag = false; /* set to true if unknown ctrl read */ if (fp == NULL) { fprintf(stderr, "internal error: output called with NULL file.\n"); exit(1); } if (debug_rec) printf("hint: if file is not being closed, decrease MAXSPACE\n"); /* set the initial absolute time, all other times are relative */ if (absflag) fprintf(fp, "t%ld ", event_buff[0].when); for (i = 0; i < how_many; i++) { if (debug_rec) { printf("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) printf("i = %d, now = %ld\n", i, now); } else { command = event_buff[i].n[0] & MIDI_CODE_MASK; chan = event_buff[i].n[0] & MIDI_CHN_MASK; if (command == MIDI_ON_NOTE && event_buff[i].n[2] != 0) { put_pitch(fp, event_buff[i].n[1] - 12); fprintf(fp, " u%ld l%d n%ld", getdur(i, last, ped, now), event_buff[i].n[2], getnext(i, last, now) - now); if (lastchan != chan) { fprintf(fp, " v%d\n", chan + 1); lastchan = chan; } else fprintf(fp, "\n"); } else if (command == MIDI_CH_PROGRAM) { fprintf(fp, "r z%d n%ld", event_buff[i].n[1] + 1, getnext(i, last, now) - now); if (lastchan != chan) { fprintf(fp, " v%d\n", chan + 1); lastchan = chan; } else fprintf(fp, "\n"); } else if (command == MIDI_CTRL && event_buff[i].n[1] == SUSTAIN) { ped = (event_buff[i].n[2] != 0); } else if (command == MIDI_CTRL) { char c = map_ctrl(event_buff[i].n[1]); if (c != 0) { fprintf(fp, "%c%d n%d\n", c, event_buff[i].n[2], getnext(i, last, now) - now); } else bad_ctrl_flag = true; } else if (command == MIDI_TOUCH) { fprintf(fp, "O%d n%d\n", event_buff[i].n[1], getnext(i, last, now) - now); } else if (command == MIDI_BEND) { fprintf(fp, "Y%d n%d\n", event_bend(&event_buff[i]), getnext(i, last, now) - now); } else if (command != MIDI_ON_NOTE) { fprintf(stderr, "Command 0x%x ignored\n", command); } } } if (bad_ctrl_flag) fprintf(stderr, "Some unrecognized control changes were omitted from file.\n"); } /**************************************************************************** * put_pitch * Inputs: * FILE *fp: an open file * int p: a pitch number * Effect: write out the pitch name for a given number ****************************************************************************/ private void put_pitch(fp, p) FILE *fp; int p; { static char *ptos[] = {"c", "cs", "d", "ef", "e", "f", "fs", "g", "gs", "a", "bf", "b"}; fprintf(fp, "%s%d", ptos[p % 12], p / 12); } /********************************************************************** * rec_final * Inputs: * boolean absflag: output absolute time of first note if true * Effect: * Write recorded data to a file **********************************************************************/ void rec_final(absflag) boolean absflag; { next->when = gettime(); last = next; if (debug_rec) printf("max_pile_up = %d, ", max_pile); printf("%d times and events recorded.\n", last - event_buff); filter(last); output(fp, last, absflag); fclose(fp); } /**************************************************************************** * rec_init * Inputs: * char *file: pointer to file name from command line (if any) * boolean bender: true if pitch bend should be enabled * Outputs: * returns true if initialization succeeds * Effect: * prepares module to record midi input ****************************************************************************/ boolean rec_init(file, bender) char *file; boolean bender; { char *malloc(); /* memory allocation */ debug_rec = (cl_nswitch(dsw, ndsw) != NULL); pile_ups = 0; max_pile = 0; fp = fileopen(file, "gio", "w", "Name of output file"); if (max_notes == -1) { /* allocate space 1st time rec_init called */ max_notes = space/sizeof(note_node); event_buff = (note_type) malloc(sizeof(note_node) * max_notes); if (event_buff == NULL) { fprintf(stderr, "Internal error allocating record space."); musicterm(); exit(1); } byteorder(); printf("Space for %d events has been allocated.\n", max_notes); } next = event_buff; last = event_buff + max_notes - 2; while (getkey(false) != -1) ; /* flush old midi events */ midi_cont(bender); return max_notes > 10; } /**************************************************************************** * rec_poll * Inputs: * long time: the current time * Outputs: * returns true if there is no more memory * Effect: reads and stores any input * Assumes: rec_poll must be called frequently to get accurate results * 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 midi data is present ****************************************************************************/ boolean rec_poll(time) long time; { next->when = time; /* this will overwrite an earlier time unless data */ /* was recorded */ if (getbuf(false, (next+1)->n)) { /* buffer nonempty? */ next++; next->n[3] = MIDI_CMD_BIT; /* set tag bit */ pile_ups = 1; while (getbuf(false, (++next)->n)) { next->n[3] = MIDI_CMD_BIT; /* set tag bit */ pile_ups++; if (next >= last) { break; } } } if (pile_ups > max_pile) max_pile = pile_ups; if (next >= last) { fprintf(stderr, "No more memory.\n"); return true; } /* else */ return false; }