From 53cbbeee1473c97d3fa33d0d2b636b6b9bfe8cdb Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Sun, 24 Jan 1999 12:00:39 +0000 Subject: [PATCH] Added playmidi1... --- test/Makefile.am | 6 +- test/midifile.3 | 336 +++++++++++++ test/midifile.c | 1173 ++++++++++++++++++++++++++++++++++++++++++++++ test/midifile.h | 132 ++++++ test/playmidi1.c | 711 ++++++++++++++++++++++++++++ 5 files changed, 2356 insertions(+), 2 deletions(-) create mode 100644 test/midifile.3 create mode 100644 test/midifile.c create mode 100644 test/midifile.h create mode 100644 test/playmidi1.c diff --git a/test/Makefile.am b/test/Makefile.am index e4a851ad..3fde7b1d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,4 +1,4 @@ -check_PROGRAMS=control mixer switches pause pcm latency seq +check_PROGRAMS=control mixer switches pause pcm latency seq playmidi1 control_LDADD=../src/libasound.la mixer_LDADD=../src/libasound.la @@ -7,8 +7,10 @@ pause_LDADD=../src/libasound.la pcm_LDADD=../src/libasound.la latency_LDADD=../src/libasound.la seq_LDADD=../src/libasound.la +playmidi1_LDADD=../src/libasound.la INCLUDES=-I$(top_srcdir)/include CFLAGS=-static -Wall -pipe -g -EXTRA_DIST=seq-decoder.c seq-sender.c +EXTRA_DIST=seq-decoder.c seq-sender.c midifile.h midifile.c midifile.3 + diff --git a/test/midifile.3 b/test/midifile.3 new file mode 100644 index 00000000..3428c441 --- /dev/null +++ b/test/midifile.3 @@ -0,0 +1,336 @@ +.TH MIDIFILE 3 +.SH NAME +mfread,mfwrite - read and write a standard MIDI file +.SH SYNOPSIS +\fC#include "mfread.h" + +mfread () + +.nf +int (*Mf_getc) (); +int (*Mf_putc) (); +int (*Mf_error) (char *msg); +int (*Mf_header) (int format, int ntrks, int division); +int (*Mf_trackstart) (); +int (*Mf_trackend) (); +int (*Mf_noteon) (int chan, int pitch, int vol); +int (*Mf_noteoff) (int chan, int pitch, int vol); +int (*Mf_pressure) (int chan, int pitch, int pressure); +int (*Mf_parameter) (int chan, int control, int value); +int (*Mf_pitchbend) (int chan, int msb, int lsb); +int (*Mf_program) (int chan, int program); +int (*Mf_chanpressure) (int chan, int pressure); +int (*Mf_sysex) (int leng, char *msg); +int (*Mf_metamisc) (int type, int leng, int msg); +int (*Mf_seqspecific) (int type, int leng, int msg); +int (*Mf_seqnum) (int num); +int (*Mf_text) (int type, int leng, int msg); +int (*Mf_eot) (); +int (*Mf_timesig) (int numer, int denom, int clocks, int qnotes); +int (*Mf_smpte) (int hour, int min, int sec, int frame, int fract); +int (*Mf_tempo) (int microsecs); +int (*Mf_keysig) (int sharpflat, int minor); +int (*Mf_arbitrary) (int leng, int msg); +int Mf_nomerge; +long Mf_currtime; +.fi +.sp 1 +mfwrite(int format, int ntracks, int division, FILE *fp) +.sp 1 +.nf +int (*Mf_writetrack)(int track); +int (*Mf_writetempotrack)(); + +void mf_write_midi_event(delta, type, chan, data, size) +unsigned long delta; +unsigned int type,chan,size; +char *data; + +void mf_write_meta_event(delta, type, data, size) +unsigned long delta; +unsigned int type,chan,size; +char *data; + +void mf_write_tempo(tempo) +unsigned long tempo; + +unsigned long mf_sec2ticks(float seconds, int division, int tempo) +float seconds; +int division; +unsigned int tempo; + +float mf_ticks2sec(ticks, division, tempo) +unsigned long ticks; +int division; +unsigned int tempo; +.fi + +.SH DESCRIPTION +The \fCmfread\fR function reads and inteprets a standard MIDI file. +To use it you need to understand the general form of a +MIDI file and the type of information it contains, but you don't +need to know much, if anything, about the detailed format of the file +and the mechanics of reading it reliably and portably. + +The \fCmfwrite\fR function writes a standard MIDI file making +use of user-defined functions that access the program's +data structure. To use it you need to define your own Mf_writetrack +routine and then make use of the write_* family of routines to +write out the MIDI data. The \fCmfwrite\fR routine takes +care of the file format and writing the file and track chunk headers. + +.SH READING STANDARD MIDI FILES +A single call to \fCmfread\fR will read an entire MIDI file. +The interface to \fCmfread\fR is a set of external variables +named \fCMf_*\fR, most of which are function pointers to be called +from within \fCmfread\fR during the process of parsing the MIDI file. +Before calling \fCmfread\fR, the only +requirement is that you assign a value +to \fCMf_getc\fR - a pointer to a function that will return +characters from the MIDI file, using -1 to indicate EOF. +All the rest of the function +pointers are initialized to NULL, and the default action for each +is to do nothing. The following is a complete program using \fCmfread\fR +that could serve as a 'syntax checker' for MIDI files: + +.in +1i +.ft C +.nf +#include +#include "midifile.h" + +mygetc() +{ + /* use standard input */ + return(getchar()); +} + +main() +{ + Mf_getc = mygetc; + mfread(); + exit(0); +} +.fi +.ft R +.in -1i + +This takes advantage of the default action when an error is detected, which +is to exit silently with a return code of 1. An error function of your +own can be used by giving a value to \fCMf_error\fR; the function will be +called with the error message as an argument. +The other \fCMf_* variables can similarly be used to call arbitrary +functions while parsing the MIDI file. The descriptions below +of the information passed to these functions is sparse; refer to +the MIDI file standard for the complete descriptions. + +\fCMf_header\fR is the first function to be called, and its arguments +contain information from the MIDI file's header; the format (0,1, or 2), +the number of tracks, and the division of a quarter-note that defines +the times units. +\fCMf_trackstart\fR and +\fCMf_trackend\fR are called at the beginning and end of each track. + +Once inside a track, each separate message causes a function to be called. +For example, each note-on message causes \fCMf_noteon\fR to be called +with the channel, pitch, and volume as arguments. The time at which +the message occurred is stored in \fCMf_currtime\fR - one of the few +external variables that isn't a function pointer. The other channel messages +are handled in a similar and obvious fashion - +\fCMf_noteoff\fR, +\fCMf_pressure\fR, +\fCMf_parameter\fR, +\fCMf_pitchbend\fR, +\fCMf_program\fR, +and \fCMf_chanpressure\fR. See the declarations above for the arguments +that are passed to each. + +System exclusive messages are handled by calling \fCMf_sysex\fR, passing +as arguments the message length and a pointer to a static buffer containing +the entire message. +The buffer is expanded when necessary; memory availability is the only limit +to its size. Normally, 'continued' system exclusives are automatically +merged, and \fCMf_sysex\fR is only called once. It you want to disable this +you can set \fCMf_nomerge\fR to 1, causing \fCMf_sysex\fR to be called +once for each part of the message. + +\fCMf_seqnum\fR is called by the \fImeta\fR message that provides +a sequence number, +which if present must appear at the beginning of a track. +The tempo \fImeta\fR message causes \fCMf_tempo\fR to be called; its +argument is the number of microseconds per MIDI quarter-note (24 MIDI clocks). +The end-of-track \fImeta\fR message causes \fCMf_eot\fR to be called. +The key signature \fImeta\fR message causes \fCMf_keysig\fR to be called; +the first argument conveys the number of sharps or flats, the second +argument is 1 if the key is minor. + +The \fCMf_timesig\fR and \fCMf_smpte\fR functions are called when the +corresponding \fImeta\fR messages are seen. See the MIDI file standard +for a description of their arguments. + +The \fItext\fR messages in the MIDI file standard are of the following +types: + +.in +1i +.nf +0x01 Text Event +0x02 Copyright +0x03 Sequence/Track Name +0x04 Instrument +0x05 Lyric +0x06 Marker +0x07 Cue Point +0x08-0x0F Reserverd but Undefined +.fi +.in -1i + +\fCMf_text\fR is called for each of these; the arguments are +the type number, the message length, and a pointer to the message buffer. + +Misceallaneous \fImeta\fR messages are handled by \fCMf_metamisc\fR, +sequencer-specific messages are handled by \fCMf_seqspecific\fR, and +arbitrary "escape" messages (started with 0xF7) are handled by +\fCMf_arbitrary\fR. +.SH READING EXAMPLE +The following is a \fCstrings\fR-like program for MIDI files: + +.in +1i +.ft C +.nf +#include +#include +#include "midifile.h" + +FILE *F; + +mygetc() { return(getc(F)); } + +mytext(type,leng,msg) +char *msg; +{ + char *p; + char *ep = msg + leng; + + for ( p=msg; p 1 ) + F = fopen(argv[1],"r"); + else + F = stdin; + + Mf_getc = mygetc; + Mf_text = mytext; + + mfread(); + + exit(0); +} +.fi +.ft R +.in -1i +.sp +.SH WRITING STANDARD MIDI FILES +A single call to \fCmfwrite\fR will write an entire MIDI file. Before +calling \fCmfwrite\fR, you must assign values to function pointers +\fCMf_writetrack\fR and \fCMf_putc\fR. The first is a routine to +access your MIDI data structure, which can make use of other library +routines to write the actual MIDI data. The routine +\fCMf_writetrack\fR will be passed a single parameter which is the +number of the track to be written. The pointer \fCMf_putc\fR should be +set to point to a routine that accepts a charcter as input, writes that +character to a file, and returns the value that was written. In the +case of a format 1 file, a routine has to be written to write a tempo +map, and assigned to the function pointer \fCMf_writetempotrack\fR. +This is because format 1 files assume the first track written is a +tempo track. + +\fCmf_write_midi_event\fR and \fCmf_write_meta_event\fR are routines +that should be called from your \fCMf_writetrack\fR routine to write +out MIDI events. The delta time param is the number of ticks since the +last event. The int "type" is the type of MIDI message. The int "chan" +is the MIDI channel, which can be between 1 and 16. The char pointer +"data" points to an array containing the data bytes, if any exist. The +int "size" is the number of data bytes. + +\fCmf_sec2ticks\fR and \fCmf_ticks2sec\fR are utility routines +to help you convert between the MIDI file parameter of ticks +and the more standard seconds. The int "division" is the same +division parameter from the file header, and tempo is expressed +in microseconds per MIDI quarter-note, or "24ths of a microsecond +per MIDI clock". The division has two meanings, depending on +whether bit 15 is set or not. If bit 15 of division is zero, +bits 14 through 0 represent the number of delta-time "ticks" +which make up a quarter note. If bit 15 of division is a one, +delta-times in a file correspond to subdivisions of a second +similiar to SMPTE and MIDI time code. In this format bits +14 through 8 contain one of four values - 24, -25, -29, or -30, +corresponding to the four standard SMPTE and MIDI time code +frame per second formats, where -29 represents 30 drop frame. +The second byte consisting of bits 7 through 0 corresponds +the the resolution within a frame. Refer the Standard MIDI Files +1.0 spec for more details. + +.SH WRITING EXAMPLE +The following is a simple program to demonstrate writing MIDI files. +The track would consist of a series of quarter notes from lowest to +highest in pitch at constant velocity, each separted by a quarter-note +rest. +.sp +.in +1i +.ft C +.nf +#include +#include +#include "midifile.h" + +FILE *fp; +myputc(c) { return(putc(c,fp));} + +int mywritetrack(track) +int track; +{ + int i; + char data[2]; + + /* 120 beats/per/second */ + mf_write_tempo((long)500000); + + for(i = 1 ; i < 128; i++){ + data[0] = i; /* note number */ + data[1] = 64; /* velocity */ + if(!mf_write_midi_event(480,note_on,1,data,2)) + return(-1); + if(!mf_write_midi_event(480,note_off,1,data,2)) + return(-1); + } + + return(1); +} /* end of write_track() */ + +main(argc,argv) +char **argv; +{ + if((fp = fopen(argv[1],"w")) == 0L) + exit(1); + + Mf_putc = myputc; + Mf_writetrack = mywritetrack; + + /* write a single track */ + mfwrite(0,1,480,fp); +} +.sp +.fi +.ft R +.in -1i +.sp +.SH AUTHOR +Tim Thompson (att!twitch!glimmer!tjt) +.SH CONTRIBUTORS +Michael Czeiszperger (mike@pan.com) diff --git a/test/midifile.c b/test/midifile.c new file mode 100644 index 00000000..4b959b45 --- /dev/null +++ b/test/midifile.c @@ -0,0 +1,1173 @@ +/* + * midifile 1.11 + * + * Read and write a MIDI file. Externally-assigned function pointers are + * called upon recognizing things in the file. + * + * Original release ? + * June 1989 - Added writing capability, M. Czeiszperger. + * + * The file format implemented here is called + * Standard MIDI Files, and is part of the Musical + * instrument Digital Interface specification. + * The spec is avaiable from: + * + * International MIDI Association + * 5316 West 57th Street + * Los Angeles, CA 90056 + * + * An in-depth description of the spec can also be found + * in the article "Introducing Standard MIDI Files", published + * in Electronic Musician magazine, April, 1989. + * + * February 1993 - Minor adjustments, Greg Lee: + * (1) can now set the global variable Mf_interactive to 1 to prevent the + * reading functions from looking for file and track headers + * (2) can now write system exclusive data with + * mf_write_midi_event(delta_time, system_exlusive, 0, data, size) + * (3) changed definition of 'sequencer_specific' in midifile.h to 0x7f + * (4) changed mf_write_tempo to take additional delta_time as first argument + * (since delta need not be zero) + * (5) added function mf_write_seqnum(unsigned long delta_time, unsigned seqnum) + * (6) changed mf_write_midi_event to use running status + * (7) removed the code to write an end of track meta event automatically + * -- this must now be done by the user of the library (I changed + * it because I need to be able to control the time delta of this + * meta event) + * (8) added global variables Mf_division, Mf_currtempo, Mf_realtime, which + * are updated by the reading functions. Mf_realtime is useful, + * because Mf_currtime does not really measure time at all, since + * its units change value at every tempo change. Mf_realtime is + * the midi-time elapsed in units of 1/16 of a centisecond (but it + * does not handle smpte times) + * (9) maintains a history of tempo settings to update Mf_currtempo, + * to handle tempo tracks. + * (10) if there is an Mf_error function, the error routine no longer + * exits, leaving it to the application to do this. + * (11) chanmessage skips over invalid c1 command bytes > 127 and + * adjusts invalid c2 argument byte > 127 to 127. + * (12) readmt returns EOF when it encounters a 0 or 0x1a byte instead of an expected + * header string (some midi files have padding at end). + */ +#define NO_LC_DEFINES +#include "midifile.h" +#ifdef NO_LC_DEFINES +#define system_exclusive 0xf0 +#define meta_event 0xFF +#define set_tempo 0x51 +#define lowerbyte(x) ((unsigned char)(x & 0xff)) +#define upperbyte(x) ((unsigned char)((x & 0xff00)>>8)) +#endif + +#define NULLFUNC 0 +#if 0 +#define NULL 0 +#endif + +#define THINK + +#ifdef THINK +#include +#endif + +#include +#include + +char *strcpy (), *strcat (); +/*void exit(), free();*/ + +/* public stuff */ + +/* Functions to be called while processing the MIDI file. */ +int (*Mf_getc) () = NULLFUNC; +void (*Mf_error) () = NULLFUNC; +void (*Mf_header) () = NULLFUNC; +void (*Mf_trackstart) () = NULLFUNC; +void (*Mf_trackend) () = NULLFUNC; +void (*Mf_noteon) () = NULLFUNC; +void (*Mf_noteoff) () = NULLFUNC; +void (*Mf_pressure) () = NULLFUNC; +void (*Mf_parameter) () = NULLFUNC; +void (*Mf_pitchbend) () = NULLFUNC; +void (*Mf_program) () = NULLFUNC; +void (*Mf_chanpressure) () = NULLFUNC; +void (*Mf_sysex) () = NULLFUNC; +void (*Mf_arbitrary) () = NULLFUNC; +void (*Mf_metamisc) () = NULLFUNC; +void (*Mf_seqnum) () = NULLFUNC; +void (*Mf_eot) () = NULLFUNC; +void (*Mf_smpte) () = NULLFUNC; +void (*Mf_tempo) () = NULLFUNC; +void (*Mf_timesig) () = NULLFUNC; +void (*Mf_keysig) () = NULLFUNC; +void (*Mf_seqspecific) () = NULLFUNC; +void (*Mf_text) () = NULLFUNC; + +/* Functions to implement in order to write a MIDI file */ +int (*Mf_putc) () = NULLFUNC; +int (*Mf_writetrack) () = NULLFUNC; +int (*Mf_writetempotrack) () = NULLFUNC; + +int Mf_nomerge = 0; /* 1 => continue'ed system exclusives are */ + /* not collapsed. */ +int Mf_interactive = 0; /* 1 => file and track headers are not required */ +unsigned long Mf_currtime = 0L; /* current time in delta-time units */ +unsigned long Mf_realtime = 0L; /* current time in 1/16 centisecond-time units */ +static double Mf_f_realtime = 0;/* as above, floating */ +static double old_f_realtime = 0; +int Mf_division = 96; +unsigned long Mf_currtempo = 500000; +static unsigned long old_currtempo = 500000; +static unsigned long old_realtime = 0; +static unsigned long old_currtime = 0; +static unsigned long revised_time = 0; +static unsigned long tempo_change_time = 0; + +#define MAX_HISTORY 512 +static unsigned long tempo_history[MAX_HISTORY]; +static unsigned long tempo_history_time[MAX_HISTORY]; +static int tempo_history_count = 0; + +/* private stuff */ +static long Mf_toberead = 0L; +static long Mf_numbyteswritten = 0L; + +static long readvarinum (); +static long read32bit (); +static long to32bit (); +static int read16bit (); +static int to16bit (); +static char *msg (); +static void readheader (); +static int readtrack (); +static void badbyte (); +static void metaevent (); +static void sysex (); +static void chanmessage (); +static void msginit (); +static int msgleng (); +static void msgadd (); +static void biggermsg (); +static int eputc (unsigned char c); + +double mf_ticks2sec (unsigned long ticks, int division, unsigned long tempo); +int mf_write_meta_event (); +void mf_write_tempo (); +void mf_write_seqnum (); +void WriteVarLen (); + +#ifdef READ_MODS +#include "mp_mod.c" +static int mod_file_flag = 0; +#endif /* READ_MODS */ +static int force_exit; + +void +mfread () +{ + force_exit = 0; + if (Mf_getc == NULLFUNC) + mferror ("mfread() called without setting Mf_getc"); + + readheader (); +#ifdef READ_MODS + if (mod_file_flag) + do_module(); + else +#endif + while (readtrack () && !force_exit) + ; +} + +/* for backward compatibility with the original lib */ +void +midifile () +{ + mfread (); +} + +static +int +readmt (s) /* read through the "MThd" or "MTrk" header string */ + char *s; +{ + int n = 0; + char *p = s; + int c; + + while (n++ < 4 && (c = (*Mf_getc) ()) != EOF) + { + if (c != *p++) + { + char buff[32]; + if (!c) return(EOF); + if (c == 0x1a) return(EOF); + (void) strcpy (buff, "expecting "); + (void) strcat (buff, s); + mferror (buff); + break; + } + } + return (c); +} + +static +int +egetc () /* read a single character and abort on EOF */ +{ + int c = (*Mf_getc) (); + + if (c == EOF) { + mferror ("premature EOF"); + force_exit = 1; + } + Mf_toberead--; + return (c); +} + +static +void +readheader () /* read a header chunk */ +{ + int format, ntrks, division; + + + Mf_division = 96; + Mf_currtempo = 500000; + old_currtempo = 500000; + tempo_history_count = 0; + tempo_history[tempo_history_count] = Mf_currtempo; + tempo_history_time[tempo_history_count] = 0; + + if (Mf_interactive) + { + Mf_toberead = 0; + format = 0; + ntrks = 1; + division = 96; + } + else +#ifdef READ_MODS + if (!strncmp(Mf_file_contents, "MThd", 4)) +#endif + { + if (readmt ("MThd") == EOF) + return; + + Mf_toberead = read32bit (); + format = read16bit (); + ntrks = read16bit (); + Mf_division = division = read16bit (); + } +#ifdef READ_MODS + else + { + format = 0; + ntrks = 1; + division = Mf_division; + Mf_toberead = 0; + mod_file_flag = 1; + } +#endif + + if (Mf_header) + (*Mf_header) (format, ntrks, division); + + /* flush any extra stuff, in case the length of header is not 6 */ + while (Mf_toberead > 0 && !force_exit) + (void) egetc (); +} + + +/*#define DEBUG_TIMES*/ +static +unsigned long +find_tempo() +{ + int i; + unsigned long old_tempo = Mf_currtempo; + unsigned long new_tempo = Mf_currtempo; + + for (i = 0; i <= tempo_history_count; i++) { + if (tempo_history_time[i] <= Mf_currtime) old_tempo = tempo_history[i]; + new_tempo = tempo_history[i]; + if (tempo_history_time[i] > revised_time) break; + } + if (i > tempo_history_count || tempo_history_time[i] > Mf_currtime) { +#ifdef DEBUG_TIMES +printf("[past %d, old_tempo %d]\n", tempo_history_time[i], old_tempo); +#endif + revised_time = Mf_currtime; + return(old_tempo); + } + tempo_change_time = revised_time = tempo_history_time[i]; +#ifdef DEBUG_TIMES +printf("[revised_time %d, new_tempo %d]\n", revised_time, new_tempo); +#endif + return(new_tempo); +} + +static +int +readtrack () /* read a track chunk */ +{ + /* This array is indexed by the high half of a status byte. It's */ + /* value is either the number of bytes needed (1 or 2) for a channel */ + /* message, or 0 (meaning it's not a channel message). */ + static int chantype[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */ + 2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */ + }; + long lookfor; + int c, c1, type; + int sysexcontinue = 0; /* 1 if last message was an unfinished sysex */ + int running = 0; /* 1 when running status used */ + int status = 0; /* status value (e.g. 0x90==note-on) */ + int needed; + + if (Mf_interactive) + { + Mf_toberead = MAXINT; + } + else + { + if (readmt ("MTrk") == EOF) + return (0); + + Mf_toberead = read32bit (); + } + Mf_currtime = Mf_realtime = 0; + Mf_f_realtime = old_f_realtime = 0; + old_currtime = old_realtime = 0; + Mf_currtempo = find_tempo(); + + if (Mf_trackstart) + (*Mf_trackstart) (); + + while (!force_exit && (Mf_interactive || Mf_toberead > 0)) + { + + if (Mf_interactive) + Mf_currtime += 1; + else + { + double delta_secs; + unsigned long delta_ticks = readvarinum (); + revised_time = Mf_currtime; + Mf_currtime += delta_ticks; /* delta time */ + +/* + * Step through each tempo change from old_currtime up to now, + * revising Mf_realtime after each change. + */ + + while (revised_time < Mf_currtime) { + unsigned long save_time = revised_time; + unsigned long save_tempo = Mf_currtempo; + Mf_currtempo = find_tempo(); + + if (Mf_currtempo != old_currtempo) { + old_currtempo = Mf_currtempo; + old_realtime = Mf_realtime; + if (revised_time != tempo_change_time) { + old_f_realtime = Mf_f_realtime; + old_currtime = save_time; + } + delta_secs = mf_ticks2sec (revised_time-old_currtime, Mf_division, save_tempo); +#ifdef DEBUG_TIMES +printf("d(rev %d - old %d, div %d, tempo %d) = %.3f\n", +revised_time, old_currtime, Mf_division, save_tempo, delta_secs * 1600.0); +#endif + Mf_f_realtime = old_f_realtime + delta_secs * 1600.0; + Mf_realtime = (unsigned long)(0.5 + Mf_f_realtime); +#ifdef DEBUG_TIMES +printf("\tt=%d ticks ( = %d csec/16 < old %.2f + %.2f)\n", Mf_currtime, Mf_realtime, +old_f_realtime, delta_secs * 1600.0); +#endif + if (revised_time == tempo_change_time) { + old_currtime = revised_time; + old_f_realtime = Mf_f_realtime; + } + } + else { + delta_secs = mf_ticks2sec (revised_time-old_currtime, Mf_division, Mf_currtempo); +#ifdef DEBUG_TIMES +printf("d(rev %d - old %d, div %d, tempo %d) = %.3f\n", +revised_time, old_currtime, Mf_division, Mf_currtempo, delta_secs * 1600.0); +#endif + Mf_f_realtime = old_f_realtime + delta_secs * 1600.0; + Mf_realtime = (unsigned long)(0.5 + Mf_f_realtime); +#ifdef DEBUG_TIMES +printf("\tt=%d ticks ( = %d csec/16 < old %.2f + %.2f)\n", Mf_currtime, Mf_realtime, +old_f_realtime, delta_secs * 1600.0); +#endif + } + + + } + } + + c = egetc (); + + if (sysexcontinue && c != 0xf7) + mferror ("didn't find expected continuation of a sysex"); + + if ((c & 0x80) == 0) + { /* running status? */ + if (status == 0) + mferror ("unexpected running status"); + running = 1; + } + else + { + status = c; + running = 0; + } + + needed = chantype[(status >> 4) & 0xf]; + + if (needed) + { /* ie. is it a channel message? */ + + if (running) + c1 = c; + else + c1 = egetc (); + chanmessage (status, c1, (needed > 1) ? egetc () : 0); + continue;; + } + + switch (c) + { + + case 0xff: /* meta event */ + + type = egetc (); + lookfor = Mf_toberead - readvarinum (); + msginit (); + + while (Mf_toberead > lookfor) + msgadd (egetc ()); + + metaevent (type); + break; + + case 0xf0: /* start of system exclusive */ + + lookfor = Mf_toberead - readvarinum (); + msginit (); + msgadd (0xf0); + + while (Mf_toberead > lookfor) + msgadd (c = egetc ()); + + if (c == 0xf7 || Mf_nomerge == 0) + sysex (); + else + sysexcontinue = 1; /* merge into next msg */ + break; + + case 0xf7: /* sysex continuation or arbitrary stuff */ + + lookfor = Mf_toberead - readvarinum (); + + if (!sysexcontinue) + msginit (); + + while (Mf_toberead > lookfor) + msgadd (c = egetc ()); + + if (!sysexcontinue) + { + if (Mf_arbitrary) + (*Mf_arbitrary) (msgleng (), msg ()); + } + else if (c == 0xf7) + { + sysex (); + sysexcontinue = 0; + } + break; + default: + badbyte (c); + break; + } + } + if (Mf_trackend) + (*Mf_trackend) (); + return (1); +} + +static +void +badbyte (c) + int c; +{ + char buff[32]; + + (void) sprintf (buff, "unexpected byte: 0x%02x", c); + mferror (buff); +} + +static +void +metaevent (int type) +{ + int leng = msgleng (); + char *m = msg (); + + switch (type) + { + case 0x00: + if (Mf_seqnum) + (*Mf_seqnum) (to16bit (m[0], m[1])); + break; + case 0x01: /* Text event */ + case 0x02: /* Copyright notice */ + case 0x03: /* Sequence/Track name */ + case 0x04: /* Instrument name */ + case 0x05: /* Lyric */ + case 0x06: /* Marker */ + case 0x07: /* Cue point */ + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + /* These are all text events */ + if (Mf_text) + (*Mf_text) (type, leng, m); + break; + case 0x2f: /* End of Track */ + if (Mf_eot) + (*Mf_eot) (); + break; + case 0x51: /* Set tempo */ + if (Mf_tempo) + (*Mf_tempo) (Mf_currtempo = to32bit (0, m[0], m[1], m[2])); + if (tempo_history[tempo_history_count] == Mf_currtempo) break; + if (tempo_history_time[tempo_history_count] > Mf_currtime) break; + if (tempo_history_count < MAX_HISTORY - 1) tempo_history_count++; + tempo_history[tempo_history_count] = Mf_currtempo; + tempo_history_time[tempo_history_count] = Mf_currtime; + break; + case 0x54: + if (Mf_smpte) + (*Mf_smpte) (m[0], m[1], m[2], m[3], m[4]); + break; + case 0x58: + if (Mf_timesig) + (*Mf_timesig) (m[0], m[1], m[2], m[3]); + break; + case 0x59: + if (Mf_keysig) + (*Mf_keysig) (m[0], m[1]); + break; + case 0x7f: + if (Mf_seqspecific) + (*Mf_seqspecific) (leng, m); + break; + default: + if (Mf_metamisc) + (*Mf_metamisc) (type, leng, m); + } +} + +static +void +sysex () +{ + if (Mf_sysex) + (*Mf_sysex) (msgleng (), msg ()); +} + +static +void +chanmessage (status, c1, c2) + int status; + int c1, c2; +{ + int chan = status & 0xf; + + /* I found a midi file with Mod Wheel values 128. --gl */ + + if (c1 > 127) /*mferror("chanmessage: bad c1") ??*/ return; + if (c2 > 127) c2 = 127; + + switch (status & 0xf0) + { + case 0x80: + if (Mf_noteoff) + (*Mf_noteoff) (chan, c1, c2); + break; + case 0x90: + if (Mf_noteon) + (*Mf_noteon) (chan, c1, c2); + break; + case 0xa0: + if (Mf_pressure) + (*Mf_pressure) (chan, c1, c2); + break; + case 0xb0: + if (Mf_parameter) + (*Mf_parameter) (chan, c1, c2); + break; + case 0xe0: + if (Mf_pitchbend) + (*Mf_pitchbend) (chan, c1, c2); + break; + case 0xc0: + if (Mf_program) + (*Mf_program) (chan, c1); + break; + case 0xd0: + if (Mf_chanpressure) + (*Mf_chanpressure) (chan, c1); + break; + } +} + +/* readvarinum - read a varying-length number, and return the */ +/* number of characters it took. */ + +static long +readvarinum () +{ + long value; + int c; + + c = egetc (); + value = c; + if (c & 0x80) + { + value &= 0x7f; + do + { + c = egetc (); + value = (value << 7) + (c & 0x7f); + } + while (c & 0x80); + } + return (value); +} + +static long +to32bit (int c1, int c2, int c3, int c4) +{ + long value = 0L; + + value = (c1 & 0xff); + value = (value << 8) + (c2 & 0xff); + value = (value << 8) + (c3 & 0xff); + value = (value << 8) + (c4 & 0xff); + return (value); +} + +static int +to16bit (c1, c2) + int c1, c2; +{ + return ((c1 & 0xff) << 8) + (c2 & 0xff); +} + +static long +read32bit () +{ + int c1, c2, c3, c4; + + c1 = egetc (); + c2 = egetc (); + c3 = egetc (); + c4 = egetc (); + return to32bit (c1, c2, c3, c4); +} + +static int +read16bit () +{ + int c1, c2; + c1 = egetc (); + c2 = egetc (); + return to16bit (c1, c2); +} + +/* static */ +void +mferror (s) + char *s; +{ + if (Mf_error) + (*Mf_error) (s); + else exit (1); +} + +/* The code below allows collection of a system exclusive message of */ +/* arbitrary length. The Msgbuff is expanded as necessary. The only */ +/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */ + +#define MSGINCREMENT 128 +static char *Msgbuff = NULL; /* message buffer */ +static int Msgsize = 0; /* Size of currently allocated Msg */ +static int Msgindex = 0; /* index of next available location in Msg */ + +static +void +msginit () +{ + Msgindex = 0; +} + +static char * +msg () +{ + return (Msgbuff); +} + +static +int +msgleng () +{ + return (Msgindex); +} + +static +void +msgadd (c) + int c; +{ + /* If necessary, allocate larger message buffer. */ + if (Msgindex >= Msgsize) + biggermsg (); + Msgbuff[Msgindex++] = c; +} + +static +void +biggermsg () +{ +/* char *malloc(); */ + char *newmess; + char *oldmess = Msgbuff; + int oldleng = Msgsize; + + Msgsize += MSGINCREMENT; + newmess = (char *) malloc ((unsigned) (sizeof (char) * Msgsize)); + + if (newmess == NULL) + mferror ("malloc error!"); + + /* copy old message into larger new one */ + if (oldmess != NULL) + { + register char *p = newmess; + register char *q = oldmess; + register char *endq = &oldmess[oldleng]; + + for (; q != endq; p++, q++) + *p = *q; + free (oldmess); + } + Msgbuff = newmess; +} + +static int laststatus = 0; + +/* + * mfwrite() - The only fuction you'll need to call to write out + * a midi file. + * + * format 0 - Single multi-channel track + * 1 - Multiple simultaneous tracks + * 2 - One or more sequentially independent + * single track patterns + * ntracks The number of tracks in the file. + * division This is kind of tricky, it can represent two + * things, depending on whether it is positive or negative + * (bit 15 set or not). If bit 15 of division is zero, + * bits 14 through 0 represent the number of delta-time + * "ticks" which make up a quarter note. If bit 15 of + * division is a one, delta-times in a file correspond to + * subdivisions of a second similiar to SMPTE and MIDI + * time code. In this format bits 14 through 8 contain + * one of four values - 24, -25, -29, or -30, + * corresponding to the four standard SMPTE and MIDI + * time code frame per second formats, where -29 + * represents 30 drop frame. The second byte + * consisting of bits 7 through 0 corresponds the the + * resolution within a frame. Refer the Standard MIDI + * Files 1.0 spec for more details. + * fp This should be the open file pointer to the file you + * want to write. It will have be a global in order + * to work with Mf_putc. + */ +void +mfwrite (format, ntracks, division, fp) + int format, ntracks, division; + FILE *fp; +{ + int i; + void mf_write_track_chunk (), mf_write_header_chunk (); + + if (Mf_putc == NULLFUNC) + mferror ("mfmf_write() called without setting Mf_putc"); + + if (Mf_writetrack == NULLFUNC) + mferror ("mfmf_write() called without setting Mf_mf_writetrack"); + + laststatus = 0; + + /* every MIDI file starts with a header */ + mf_write_header_chunk (format, ntracks, division); + + laststatus = 0; + + /* In format 1 files, the first track is a tempo map */ + if (format == 1 && (Mf_writetempotrack)) + { + (*Mf_writetempotrack) (); + } + + /* The rest of the file is a series of tracks */ + for (i = 0; i < ntracks; i++) + mf_write_track_chunk (i, fp); +} + +void +mf_write_track_chunk (which_track, fp) + int which_track; + FILE *fp; +{ + unsigned long trkhdr, trklength; + long offset, place_marker; + void write16bit (), write32bit (); + + + laststatus = 0; + + trkhdr = MTrk; + trklength = 0; + + /* Remember where the length was written, because we don't + know how long it will be until we've finished writing */ + offset = ftell (fp); + +#ifdef DEBUG + printf ("offset = %d\n", (int) offset); +#endif + + /* Write the track chunk header */ + write32bit (trkhdr); + write32bit (trklength); + + Mf_numbyteswritten = 0L; /* the header's length doesn't count */ + + if (Mf_writetrack) + { + (*Mf_writetrack) (which_track); + } + + /* mf_write End of track meta event */ +/* but this does not necessarily have a delta of 0, so + * I don't want to do it -- leave it up to the user of the + * library functions to do + * --gl + eputc(0); + eputc(laststatus = meta_event); + eputc(end_of_track); + + eputc(0); + */ + + /* It's impossible to know how long the track chunk will be beforehand, + so the position of the track length data is kept so that it can + be written after the chunk has been generated */ + place_marker = ftell (fp); + + /* This method turned out not to be portable because the + parameter returned from ftell is not guaranteed to be + in bytes on every machine */ + /* track.length = place_marker - offset - (long) sizeof(track); */ + +#ifdef DEBUG + printf ("length = %d\n", (int) trklength); +#endif + + if (fseek (fp, offset, 0) < 0) + mferror ("error seeking during final stage of write"); + + trklength = Mf_numbyteswritten; + + /* Re-mf_write the track chunk header with right length */ + write32bit (trkhdr); + write32bit (trklength); + + fseek (fp, place_marker, 0); +} /* End gen_track_chunk() */ + + +void +mf_write_header_chunk (format, ntracks, division) + int format, ntracks, division; +{ + unsigned long ident, length; + void write16bit (), write32bit (); + + ident = MThd; /* Head chunk identifier */ + length = 6; /* Chunk length */ + + /* individual bytes of the header must be written separately + to preserve byte order across cpu types :-( */ + write32bit (ident); + write32bit (length); + write16bit (format); + write16bit (ntracks); + write16bit (division); +} /* end gen_header_chunk() */ + + +/* + * mf_write_midi_event() + * + * Library routine to mf_write a single MIDI track event in the standard MIDI + * file format. The format is: + * + * + * + * In this case, event can be any multi-byte midi message, such as + * "note on", "note off", etc. + * + * delta_time - the time in ticks since the last event. + * type - the type of meta event. + * chan - The midi channel. + * data - A pointer to a block of chars containing the META EVENT, + * data. + * size - The length of the meta-event data. + */ +int +mf_write_midi_event (delta_time, type, chan, data, size) + unsigned long delta_time; + int chan, type; + unsigned long size; + char *data; +{ + int i; + unsigned char c; + + WriteVarLen (delta_time); + + /* all MIDI events start with the type in the first four bits, + and the channel in the lower four bits */ + if (type == system_exclusive || type == 0xf7) + { + c = type; + laststatus = 0; + } + else + c = type | chan; + + if (chan > 15) + perror ("error: MIDI channel greater than 16\n"); + + if (laststatus != c) + eputc (laststatus = c); + + if (type == system_exclusive || type == 0xf7) + WriteVarLen (size); + + /* write out the data bytes */ + for (i = 0; i < size; i++) + eputc (data[i]); + + return (size); +} /* end mf_write MIDI event */ + +/* + * mf_write_meta_event() + * + * Library routine to mf_write a single meta event in the standard MIDI + * file format. The format of a meta event is: + * + * + * + * delta_time - the time in ticks since the last event. + * type - the type of meta event. + * data - A pointer to a block of chars containing the META EVENT, + * data. + * size - The length of the meta-event data. + */ +int +mf_write_meta_event (delta_time, type, data, size) + unsigned long delta_time; + unsigned char *data, type; + unsigned long size; +{ + int i; + + WriteVarLen (delta_time); + + /* This marks the fact we're writing a meta-event */ + eputc (laststatus = meta_event); + + /* The type of meta event */ + eputc (type); + + /* The length of the data bytes to follow */ + WriteVarLen (size); + + for (i = 0; i < size; i++) + { + if (eputc (data[i]) != data[i]) + return (-1); + } + return (size); +} /* end mf_write_meta_event */ + +void +mf_write_tempo (delta_time, tempo) + unsigned long delta_time; + unsigned long tempo; +{ + /* Write tempo */ + /* all tempos are written as 120 beats/minute, */ + /* expressed in microseconds/quarter note */ + + WriteVarLen (delta_time); + eputc (laststatus = meta_event); + eputc (set_tempo); + + eputc (3); + eputc ((unsigned) (0xff & (tempo >> 16))); + eputc ((unsigned) (0xff & (tempo >> 8))); + eputc ((unsigned) (0xff & tempo)); +} + +void +mf_write_seqnum (delta_time, seqnum) + unsigned long delta_time; + unsigned seqnum; +{ + + WriteVarLen (delta_time); + eputc (laststatus = meta_event); + eputc (0); + + eputc ((unsigned) (0xff & (seqnum >> 8))); + eputc ((unsigned) (0xff & seqnum)); +} + +unsigned long +mf_sec2ticks (secs, division, tempo) + int division; + unsigned long tempo; + double secs; +{ + return (unsigned long) (((secs * 1000.0) / 4.0 * division) / tempo); +} + +/* + * Write multi-length bytes to MIDI format files + */ +void +WriteVarLen (value) + unsigned long value; +{ + unsigned long buffer; + + buffer = value & 0x7f; + while ((value >>= 7) > 0) + { + buffer <<= 8; + buffer |= 0x80; + buffer += (value & 0x7f); + } + while (1) + { + eputc ((unsigned) (buffer & 0xff)); + + if (buffer & 0x80) + buffer >>= 8; + else + return; + } +} /* end of WriteVarLen */ + +/* + * This routine converts delta times in ticks into seconds. The + * else statement is needed because the formula is different for tracks + * based on notes and tracks based on SMPTE times. + * + */ +double +mf_ticks2sec (ticks, division, tempo) + int division; + unsigned long tempo; + unsigned long ticks; +{ + double smpte_format, smpte_resolution; + + if (division > 0) + return ((double) (((double) (ticks) * (double) (tempo)) / ((double) (division) * 1000000.0))); + else + { + smpte_format = upperbyte (division); + smpte_resolution = lowerbyte (division); + return (double) ((double) ticks / (smpte_format * smpte_resolution * 1000000.0)); + } +} /* end of ticks2sec() */ + + +/* + * write32bit() + * write16bit() + * + * These routines are used to make sure that the byte order of + * the various data types remains constant between machines. This + * helps make sure that the code will be portable from one system + * to the next. It is slightly dangerous that it assumes that longs + * have at least 32 bits and ints have at least 16 bits, but this + * has been true at least on PCs, UNIX machines, and Macintosh's. + * + */ +void +write32bit (data) + unsigned long data; +{ + eputc ((unsigned) ((data >> 24) & 0xff)); + eputc ((unsigned) ((data >> 16) & 0xff)); + eputc ((unsigned) ((data >> 8) & 0xff)); + eputc ((unsigned) (data & 0xff)); +} + +void +write16bit (data) + int data; +{ + eputc ((unsigned) ((data & 0xff00) >> 8)); + eputc ((unsigned) (data & 0xff)); +} + +/* write a single character and abort on error */ +static int +eputc (c) + unsigned char c; +{ + int return_val; + + if ((Mf_putc) == NULLFUNC) + { + mferror ("Mf_putc undefined"); + return (-1); + } + + return_val = (*Mf_putc) (c); + + if (return_val == EOF) + mferror ("error writing"); + + Mf_numbyteswritten++; + return (return_val); +} diff --git a/test/midifile.h b/test/midifile.h new file mode 100644 index 00000000..189bd82b --- /dev/null +++ b/test/midifile.h @@ -0,0 +1,132 @@ +/* definitions for MIDI file parsing code */ +extern int (*Mf_getc)(); +extern void (*Mf_header)(); +extern void (*Mf_trackstart)(); +extern void (*Mf_trackend)(); +extern void (*Mf_noteon)(); +extern void (*Mf_noteoff)(); +extern void (*Mf_pressure)(); +extern void (*Mf_parameter)(); +extern void (*Mf_pitchbend)(); +extern void (*Mf_program)(); +extern void (*Mf_chanpressure)(); +extern void (*Mf_sysex)(); +extern void (*Mf_metamisc)(); +extern void (*Mf_seqspecific)(); +extern void (*Mf_seqnum)(); +extern void (*Mf_text)(); +extern void (*Mf_eot)(); +extern void (*Mf_timesig)(); +extern void (*Mf_smpte)(); +extern void (*Mf_tempo)(); +extern void (*Mf_keysig)(); +extern void (*Mf_arbitrary)(); +extern void (*Mf_error)(); +extern unsigned long Mf_currtime; +extern unsigned long Mf_realtime; +extern unsigned long Mf_currtempo; +extern int Mf_division; +extern int Mf_nomerge; +#ifdef READ_MODS +extern unsigned char *Mf_file_contents; +extern int Mf_file_size; +#endif + +/* definitions for MIDI file writing code */ +extern int (*Mf_putc)(); +extern int (*Mf_writetrack)(); +extern int (*Mf_writetempotrack)(); + +extern void midifile(); +extern unsigned long mf_sec2ticks(); +extern void mfwrite(); +extern int mf_write_meta_event(); +extern int mf_write_midi_event(unsigned long delta_time, int type, + int chan, char *data, unsigned long size); +extern double mf_ticks2sec(unsigned long ticks,int division,unsigned long tempo); +extern void mf_write_tempo(); +extern void mf_write_seqnum(); +extern void mfread(); +extern void mferror(char *s); + +#ifndef NO_LC_DEFINES +/* MIDI status commands most significant bit is 1 */ +#define note_off 0x80 +#define note_on 0x90 +#define poly_aftertouch 0xa0 +#define control_change 0xb0 +#define program_chng 0xc0 +#define channel_aftertouch 0xd0 +#define pitch_wheel 0xe0 +#define system_exclusive 0xf0 +#define delay_packet (1111) + +/* 7 bit controllers */ +#define damper_pedal 0x40 +#define portamento 0x41 +#define sostenuto 0x42 +#define soft_pedal 0x43 +#define general_4 0x44 +#define hold_2 0x45 +#define general_5 0x50 +#define general_6 0x51 +#define general_7 0x52 +#define general_8 0x53 +#ifndef PLAYMIDI +#define tremolo_depth 0x5c +#define ctrl_chorus_depth 0x5d +#define detune 0x5e +#define phaser_depth 0x5f +#endif + +/* parameter values */ +#define data_inc 0x60 +#define data_dec 0x61 + +/* parameter selection */ +#define non_reg_lsb 0x62 +#define non_reg_msb 0x63 +#define reg_lsb 0x64 +#define reg_msb 0x65 + +/* Standard MIDI Files meta event definitions */ +#define meta_event 0xFF +#define sequence_number 0x00 +#define text_event 0x01 +#define copyright_notice 0x02 +#define sequence_name 0x03 +#define instrument_name 0x04 +#define lyric 0x05 +#define marker 0x06 +#define cue_point 0x07 +#define channel_prefix 0x20 +#define end_of_track 0x2f +#define set_tempo 0x51 +#define smpte_offset 0x54 +#define time_signature 0x58 +#define key_signature 0x59 +#define sequencer_specific 0x74 + +/* Manufacturer's ID number */ +#define Seq_Circuits (0x01) /* Sequential Circuits Inc. */ +#define Big_Briar (0x02) /* Big Briar Inc. */ +#define Octave (0x03) /* Octave/Plateau */ +#define Moog (0x04) /* Moog Music */ +#define Passport (0x05) /* Passport Designs */ +#define Lexicon (0x06) /* Lexicon */ +#define Tempi (0x20) /* Bon Tempi */ +#define Siel (0x21) /* S.I.E.L. */ +#define Kawai (0x41) +#define Roland (0x42) +#define Korg (0x42) +#define Yamaha (0x43) +#endif + +/* miscellaneous definitions */ +#define MThd 0x4d546864 +#define MTrk 0x4d54726b + +#ifndef NO_LC_DEFINES +#define lowerbyte(x) ((unsigned char)(x & 0xff)) +#define upperbyte(x) ((unsigned char)((x & 0xff00)>>8)) +#endif diff --git a/test/playmidi1.c b/test/playmidi1.c new file mode 100644 index 00000000..82463016 --- /dev/null +++ b/test/playmidi1.c @@ -0,0 +1,711 @@ +/* + * MIDI file player for ALSA sequencer + * (type 0 only!, the library that is used doesn't support merging of tracks) + * + * Copyright (c) 1998 by Frank van de Pol + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* define this if you want to send real-time time stamps instead of midi ticks to the ALSA sequencer */ +/*#define USE_REALTIME */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "midifile.h" /* SMF library header */ +#include "midifile.c" /* SMF library code */ + +#include "sys/asoundlib.h" + +//#define DEST_QUEUE_NUMBER 0 +#define DEST_QUEUE_NUMBER 7 +#define DEST_CLIENT_NUMBER 64 +//#define DEST_CLIENT_NUMBER 72 +//#define DEST_CLIENT_NUMBER 128 +//#define DEST_CLIENT_NUMBER 255 +//#define DEST_CLIENT_NUMBER SND_SEQ_ADDRESS_BROADCAST + +FILE *F; +void* seq_handle = NULL; +int ppq = 96; + +double local_secs = 0; +int local_ticks = 0; +int local_tempo = 500000; + +static int dest_queue = DEST_QUEUE_NUMBER; +static int dest_client = DEST_CLIENT_NUMBER; +static int dest_port = 0; +//static int dest_port = 1; +static int source_channel = 0; +static int source_port = 0; + + +extern void alsa_start_timer(void); +extern void alsa_stop_timer(void); + + +static inline double tick2time_dbl(int tick) +{ + return local_secs + ((double) (tick - local_ticks) * (double) local_tempo * 1.0E-9 / (double) ppq); +} + +#ifdef USE_REALTIME +static void tick2time(snd_seq_real_time_t * tm, int tick) +{ + double secs = tick2time_dbl(tick); + + //double secs = ((double) tick * (double) local_tempo * 1.0E-6 / (double) ppq); + + tm->tv_sec = secs; + tm->tv_nsec = (secs - tm->tv_sec) * 1.0E9; + + //printf("secs = %lf = %d.%09d\n", secs, tm->tv_sec, tm->tv_nsec); +} + +#endif + +/* sleep until sequencer has reached specified timestamp, to guard that we play too much events ahead */ +void sleep_seq(int tick) +{ +#if 0 + snd_seq_queue_info_t queue_info; + const int COUNT_MAX = 500; + const int COUNT_MIN = 50; + static int count = 0; + count++; + if (count >= COUNT_MAX) + { + while (snd_seq_flush_output(seq_handle) > COUNT_MIN) + sched_yield (); + count = 0; + } +#endif +} + + +/* write event to ALSA sequencer */ +void write_ev_im(snd_seq_event_t * ev) +{ + int written; + + ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SND_SEQ_EVENT_LENGTH_FIXED; + + + written = -ENOMEM; + while (written<0) { + written = snd_seq_event_output (seq_handle, ev); + if (written<0) { + printf("written = %i (%s)\n", written, strerror(-written)); + sleep(1); + } + } +} + +/* write event to ALSA sequencer */ +void write_ev(snd_seq_event_t * ev) +{ + sleep_seq(ev->time.tick-ppq); + write_ev_im(ev); +} + +/* write variable length event to ALSA sequencer */ +void write_ev_var(snd_seq_event_t * ev, int len, void *ptr) +{ + int bytes; + int written; + unsigned char *buf; + + sleep_seq(ev->time.tick+ppq); + + ev->flags &= ~SND_SEQ_EVENT_LENGTH_MASK; + ev->flags |= SND_SEQ_EVENT_LENGTH_VARIABLE; + ev->data.ext.len = len; + ev->data.ext.ptr = ptr; + + written = 0; + while (!written) { + written = snd_seq_event_output (seq_handle, ev); + if (!written) + sleep(1); + } +} + + +int mygetc(void) +{ + return (getc(F)); +} + +void mytext(int type, int leng, char *msg) +{ + char *p; + char *ep = msg + leng; + + for (p = msg; p < ep; p++) + putchar(isprint(*p) ? *p : '?'); + putchar('\n'); +} + +void do_header(int format, int ntracks, int division) +{ + printf("smf format %d, %d tracks, %d ppq\n", format, ntracks, division); + ppq = division; + + if ((format != 0) || (ntracks != 1)) { + printf("This player does not support merging of tracks.\n"); + alsa_stop_timer(); + exit(1); + } + /* set ppq */ + { + snd_seq_queue_info_t queue_info; + + queue_info.queue = 1; /* queue we're using */ + queue_info.ppq = ppq; + //queue_info.tempo = -1; /* don't change */ + queue_info.tempo = 500000; /* don't change */ + if (snd_seq_set_queue_info (seq_handle, dest_queue, &queue_info) < 0) { + perror("ioctl"); + exit(1); + } + printf("ALSA Timer updated, PPQ = %d\n", queue_info.ppq); + } + + /* start playing... */ + alsa_start_timer(); +} + +void do_tempo(int us) +{ + double bpm; + snd_seq_event_t ev; + + bpm = 60.0E6 / (double) us; + + printf("tempo = %d us/beat\n", us); + printf("tempo = %.2f bpm\n", bpm); + + /* store new tempo and timestamp of tempo change */ + local_secs = tick2time_dbl(Mf_currtime); + local_ticks = Mf_currtime; + local_tempo = us; + + + /* and send tempo change event to the sequencer.... */ + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + //ev.dest.client = dest_client; /* broadcast */ + ev.dest.client = 255; /* broadcast */ + ev.dest.port = 0; + ev.dest.channel = 0; /* don't care */ + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_TEMPO; + ev.data.control.value = us; + + write_ev_im(&ev); + +} + +void do_noteon(int chan, int pitch, int vol) +{ + snd_seq_event_t ev; + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = chan; + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_NOTEON; + ev.data.note.note = pitch; + ev.data.note.velocity = vol; + + write_ev(&ev); + +} + + +void do_noteoff(int chan, int pitch, int vol) +{ + snd_seq_event_t ev; + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = chan; + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_NOTEOFF; + ev.data.note.note = pitch; + ev.data.note.velocity = vol; + + write_ev(&ev); +} + + +void do_program(int chan, int program) +{ + snd_seq_event_t ev; + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = chan; + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_PGMCHANGE; + ev.data.control.value = program; + + write_ev_im(&ev); +} + + +void do_parameter(int chan, int control, int value) +{ + snd_seq_event_t ev; + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = chan; + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_CONTROLLER; + ev.data.control.param = control; + ev.data.control.value = value; + + write_ev(&ev); +} + + +void do_pitchbend(int chan, int lsb, int msb) +{ /* !@#$% lsb & msb are in wrong order in docs */ + snd_seq_event_t ev; + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = chan; + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_PITCHBEND; + ev.data.control.value = (lsb + (msb << 7)) - 8192; + + write_ev(&ev); +} + +void do_pressure(int chan, int pitch, int pressure) +{ + snd_seq_event_t ev; + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = chan; + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_KEYPRESS; + ev.data.control.param = pitch; + ev.data.control.value = pressure; + + write_ev(&ev); +} + +void do_chanpressure(int chan, int pressure) +{ + snd_seq_event_t ev; + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = chan; + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_CHANPRESS; + ev.data.control.value = pressure; + + write_ev(&ev); +} + +void do_sysex(int len, char *msg) +{ + snd_seq_event_t ev; + +#if 0 + int c; + + printf("Sysex, len=%d\n", len); + for (c = 0; c < len; c++) { + printf(" %3d : %02x\n", c, (unsigned char) msg[c]); + } +#endif + + + ev.source.port = dest_port; + ev.source.channel = source_channel; + + ev.dest.queue = dest_queue; + ev.dest.client = dest_client; + ev.dest.port = dest_port; + ev.dest.channel = 0; /* don't care */ + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_SYSEX; + + write_ev_var(&ev, len, msg); +} + +/**/ +void alsa_sync () +{ + //dump buffer + int left = snd_seq_flush_output (seq_handle); + printf ("alsa_sync syncing\n"); + while (left > 0) + { + sched_yield (); + left = snd_seq_flush_output (seq_handle); + if (left < 0) + { + printf ("alsa_sync error!:%s\n", snd_strerror (left)); + return; + } + } + + + for (;;) + { + snd_seq_queue_info_t queue_info; + int tmp = snd_seq_get_queue_info (seq_handle, dest_queue, &queue_info); + if (tmp < 0) + { + printf ("AlsaClient::sync snd_seq_get_queue_info:%s\n", + snd_strerror (tmp)); + break; + } + + if (Mf_currtime < queue_info.tick) + break; + sched_yield (); + } +} + + +/* start timer */ +void alsa_start_timer(void) +{ + snd_seq_event_t ev; + + ev.source.port = SND_SEQ_PORT_SYSTEM_TIMER; + ev.source.channel = 0; + + ev.dest.queue = dest_queue; + //ev.dest.client = 0; /* system */ + ev.dest.client = SND_SEQ_CLIENT_SYSTEM; /* system */ + //ev.dest.client = dest_client; /* broadcast */ + ev.dest.client = 255; /* broadcast */ + ev.dest.port = 0; /* timer */ + ev.dest.channel = 0; /* don't care */ + + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL; + //ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_REL; + ev.time.real.tv_sec = 0; + ev.time.real.tv_nsec = 0; + + ev.type = SND_SEQ_EVENT_START; + + write_ev_im(&ev); + usleep(0.1E6); +} + +/* stop timer */ +void alsa_stop_timer(void) +{ + + snd_seq_event_t ev; + + ev.source.port = 0; + ev.source.channel = 0; + + ev.dest.queue = dest_queue; + ev.dest.client = 0; /* system */ + ev.dest.client = dest_client; /* broadcast */ + ev.dest.port = 0; /* timer */ + ev.dest.channel = 0; /* don't care */ + +#ifdef USE_REALTIME + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_ABS; + tick2time(&ev.time.real, Mf_currtime); +#else + ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_ABS; + ev.time.tick = Mf_currtime; +#endif + + ev.type = SND_SEQ_EVENT_STOP; + + write_ev_im(&ev); +} + +int main(int argc, char *argv[]) +{ + char *name; + snd_seq_client_info_t inf; + snd_seq_port_info_t src_port_info; + snd_seq_queue_client_t queue_info; + snd_seq_port_subscribe_t subscribe; + int tmp; + +#ifdef USE_REALTIME + printf("ALSA MIDI Player, feeding events to real-time queue\n"); +#else + printf("ALSA MIDI Player, feeding events to song queue\n"); +#endif + + /* open sequencer device */ + //tmp = snd_seq_open (&seq_handle, SND_SEQ_OPEN_OUT); + tmp = snd_seq_open (&seq_handle, SND_SEQ_OPEN); + if (tmp < 0) { + perror("open /dev/snd/seq"); + exit(1); + } + + tmp = snd_seq_block_mode (seq_handle, 0); + if (tmp < 0) + { + perror ("block_mode"); + exit (1); + } + + + /* set name */ + memset(&inf, 0, sizeof(snd_seq_client_info_t)); + strcpy(inf.name, "MIDI file player"); + if (snd_seq_set_client_info (seq_handle, &inf) < 0) { + perror("ioctl"); + exit(1); + } + + //create port + memset (&src_port_info, 0, sizeof (snd_seq_port_info_t)); + src_port_info.capability = SND_SEQ_PORT_CAP_OUT | SND_SEQ_PORT_CAP_SUBSCRIPTION; + src_port_info.type = SND_SEQ_PORT_TYPE_MIDI_GENERIC; + src_port_info.midi_channels = 16; + src_port_info.synth_voices = 0; + src_port_info.use = 0; + src_port_info.kernel = NULL; + tmp = snd_seq_create_port (seq_handle, &src_port_info); + if (tmp < 0) + { + perror ("creat port"); + exit (1); + } + + //setup queue + queue_info.used = 1; + //queue_info.low = 100;//??? + //queue_info.low = SND_SEQ_MAX_EVENTS-1;//??? + //queue_info.low = 500-1;//??? + //queue_info.low = 1;//??? + queue_info.low = 0; + //queue_info.low = 0x7ffffff;//??? + //queue_info.high = 0x7ffffff;//??? + queue_info.high = 500-100;//??? + //queue_info.high = 50;//??? + //queue_info.high = 0;//??? + //queue_info.high = 1;//??? + tmp = snd_seq_set_queue_client (seq_handle, dest_queue, + &queue_info); + if (tmp < 0) + { + perror ("queue_client"); + exit (1); + } +#if (DEST_CLIENT_NUMBER == 64) || (DEST_CLIENT_NUMBER == 72) + //setup subscriber + printf ("debug subscribe src_port_info.client=%d\n", + src_port_info.client); + subscribe.sender.client = snd_seq_client_id (seq_handle); + subscribe.sender.queue = dest_queue; + subscribe.sender.port = src_port_info.port; + subscribe.dest.client = dest_client; + subscribe.dest.port = dest_port; + subscribe.dest.queue = dest_queue; + subscribe.realtime = 1; + subscribe.exclusive = 0; + tmp = snd_seq_subscribe_port (seq_handle, &subscribe); + if (tmp < 0) + { + perror ("subscribe"); + exit (1); + } +#endif + + if (argc > 1) + F = fopen(argv[1], "r"); + else + F = stdin; + + Mf_header = do_header; + Mf_tempo = do_tempo; + Mf_getc = mygetc; + Mf_text = mytext; + + Mf_noteon = do_noteon; + Mf_noteoff = do_noteoff; + Mf_program = do_program; + Mf_parameter = do_parameter; + Mf_pitchbend = do_pitchbend; + Mf_pressure = do_pressure; + Mf_chanpressure = do_chanpressure; + Mf_sysex = do_sysex; + + + /* stop timer in case it was left running by a previous client */ + { + snd_seq_event_t ev; + + ev.source.port = 0; + ev.source.channel = 0; + + ev.dest.queue = dest_queue; + ev.dest.client = 0; /* system */ + //ev.dest.client = dest_client; /* broadcast */ + ev.dest.client = 255; /* broadcast */ + ev.dest.port = 0; /* timer */ + ev.dest.channel = 0; /* don't care */ + + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL; + //ev.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_REL; + ev.time.real.tv_sec = 0; + ev.time.real.tv_nsec = 0; + + //ev.type = SND_SEQ_EVENT_STOP; + ev.type = SND_SEQ_EVENT_START; + + write_ev(&ev); + } + alsa_start_timer (); + + /* go.. go.. go.. */ + mfread(); + + alsa_sync (); + alsa_stop_timer(); + + snd_seq_close (seq_handle); + + printf("Stopping at %lf s, tick %d\n", tick2time_dbl(Mf_currtime + 1), Mf_currtime + 1); + + exit(0); +} -- 2.47.1