home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Sound Sensations!
/
sound_sensations.iso
/
midifile
/
cmtcmu
/
record.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-06-28
|
19KB
|
562 lines
/* 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;
}