summaryrefslogtreecommitdiff
path: root/cmt/record.c
diff options
context:
space:
mode:
Diffstat (limited to 'cmt/record.c')
-rw-r--r--cmt/record.c638
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;
+}