/* * File: mp_intp.c * SGoldthorpe 20-Jul-91 */ /* * mp_intp - the midi file interpreter for midiplay * This software is (C) 1991 Stephen Goldthorpe but it's FREE! Usual * disclaimers and notices about this software not being sold for profit. * But you may take all you want from the code though! If you have any * suggestions/bug fixes please get in contact with me. I don't want to * maintain code i've never even seen before (life's hard enough without all * of that)! * -Steve Goldthorpe * Phone (DAYTIME UK): +44 707 382350 * Internet E-Mail: SGoldthorpe.wgc-e@rx.xerox.com * goldthor@arisia.xerox.com * * Version 0.5 by Piet van Oostrum * November 1991. * I made the following changes: * 1. Files > 32767 wouldn't play. I have changed a couple of ints to * LONG. Also malloc'ed the buffer with a variable rather than a fixed size. * 2. replaced array referenced with a[i] rather than *(a+i). I find this * more readable but it has the same meaning. * 3. Midifiles of more than about 3 minutes didn't play right. I think * this is a bug in ltod (long to double) in the floating point lib. I * changed the timing from using floating point arithmetic to LONG * arithmetic. * Anyway the timing was not robust in the presence of tempo changes * because the new time per beat would be applied to the time from the * beginning of the piece rather than from the time of the tempo change. * 4. I introduced the -T option to send timing commands. This can be * used to trigger a drum computer or an arranger. * * NOTE: I distribute this version with the consent of Steve * Goldthorpe. I think he should not be bothered with bugs in my version! */ #include #include #include #include /* and for the atari OS stuff */ #include /* #include "midiplay.h" included by mp_gbls.h */ #include "mp_gbls.h" /* GLOBAL VARIABLES */ /* The track_delta's are in units of 1/division clockticks */ /* time_per_beat is the length of a quarter note in clock ticks */ static long track_delta[MAX_TRACKS], division, time_per_beat; static BYTE *track_pos[MAX_TRACKS]; static long track_left[MAX_TRACKS]; static int track_finished[MAX_TRACKS]; static clock_t clock_orig; static char *gFile; static WORD format, tracks; static int finished_tracks; static int trnr; static long clock_delta, clock_time; /* FUNCTION DECLS */ BOOL interp(); static void truncated(), all_notes_off(), parse_error(); /* MACRO FUNCTIONS */ /* the error checking may be a bit OTT but I'm gonna do it anyway (helps catch those naughty bugs - and bad files) */ #define GET32BITS(dw,p,l) dw=(((LONG)(*p)<<24)+ \ ((LONG)(*(p+1))<<16)+ \ (((LONG)*(p+2))<<8)+ \ (LONG)(*(p+3))); \ if(l<4) \ { truncated(); \ return(FALSE); \ }; \ p += 4; l -= 4 #define GET16BITS(w,p,l) w=(((WORD)(*p)<<8)+(WORD)*(p+1)); \ if(l<2) \ { truncated(); \ return(FALSE); \ } \ p += 2; l -= 2 #define GET8BITS(b,p,l) b = *(p)++; \ if(--l<0) \ { truncated(); \ return(FALSE); \ } #define GETVARLEN(dw,p,l) for(dw=(LONG)(*p)&0x7f;(*(p)++)&0x80;) \ { if(--l<0) \ { truncated(); \ return(FALSE); \ }; \ dw <<=7; \ dw |= (LONG)(*p)&0x7f; \ }; \ if(--l<0) \ { truncated(); \ return(FALSE); \ } #define CHECKLEFT(l,v) if(l MAX_TRACKS) { (void)fprintf(stderr,"%s: %s has too many tracks (%d allowed).\n", app_name,file,tracks,MAX_TRACKS); return(FALSE); }; /* get division - this is the division of a quarter note or if negative is frame based. */ GET16BITS(w,pos,left); division=(LONG)w; /* I don't suport the frame stuff yet! */ if(division < 0) { (void)fprintf(stderr, "%s: file %s - don't support framed based files yet!", app_name, file); return(FALSE); }; #ifdef DEBUG (void)printf("division = %f\n",division); #endif time_per_beat = (LONG)(CLK_TCK/2); /* default 120bpm = 2bps */ #ifdef DEBUG (void)printf("time per beat (1/%d sec) %ld\n", CLK_TCK, time_per_beat); #endif /* do some initialisation, track finding etc */ finished_tracks = 0; for(trnr=0;trnr= clock_time) { SEND(0xF8); clock_time += (time_per_beat * division / 24); } for(trnr=0;trnr= track_delta[trnr])) { GET8BITS(event,track_pos[trnr],track_left[trnr]); /* parse event */ switch (event) { /* meta-events */ case 0xff: { if(!meta_event()) return(FALSE); break; }; /* sysex events */ case 0xf0: case 0xf7: { if(!sysex_event(event)) return(FALSE); break; }; case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: { if(!system_common(event)) return(FALSE); break; }; case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: { if(!system_real_time()) return(FALSE); break; }; /* midi events */ default: { switch(event & 0xf0) { /* 3 byte events */ case 0x90: case 0x80: case 0xa0: case 0xb0: case 0xe0: { BYTE c; SEND(event); GET8BITS(c,track_pos[trnr],track_left[trnr]); SEND(c); GET8BITS(c,track_pos[trnr],track_left[trnr]); SEND(c); running_status[trnr] = event; last_running_status = event; break; }; /* program change */ case 0xc0: { BYTE c; GET8BITS(c,track_pos[trnr],track_left[trnr]); if (f_Program) { SEND(event); SEND(c); running_status[trnr] = event; last_running_status = event; }; break; }; /* channel pressure */ case 0xd0: { BYTE c; GET8BITS(c,track_pos[trnr],track_left[trnr]); if (f_Channel_pressure) { SEND(event); SEND(c); running_status[trnr] = event; last_running_status = event; }; break; }; default: { /* running status */ if(running_status[trnr]!=last_running_status) { SEND(running_status[trnr]); last_running_status=running_status[trnr]; }; if((event&0x80)==0) { SEND(event); #ifdef DEBUG (void)printf("running stat (%02x) %02x\n", running_status[trnr],event); #endif switch(running_status[trnr] & 0xf0) { /* 3 byte events */ case 0x90: case 0x80: case 0xa0: case 0xb0: case 0xe0: { BYTE c; GET8BITS(c,track_pos[trnr], track_left[trnr]); SEND(c); break; }; /* ignoring other 0xf? events - naughty aren't I */ }; }; }; }; }; }; /* get delta time - but only if we're still going on this track */ if(!track_finished[trnr]) { GETVARLEN(delta,track_pos[trnr],track_left[trnr]); track_delta[trnr] += delta * time_per_beat; }; }; }; /* CHECK TO SEE IF CTRL C/S PRESSED */ if(Bconstat(2)) { BYTE c; c=(Bconin(2)&0xff); switch(c) { case 3: /* CTRL C */ { printf("\n** Exit by CTRL C **\n"); all_notes_off(); return(TRUE); }; case 19: /* CTRL S */ { printf("\n** Skip track by CTRL S **\n"); all_notes_off(); return(FALSE); }; }; }; }; if (f_Timing) SEND (0xFC); return(FALSE); }; /* system exclusive events */ int sysex_event(event) BYTE event; { LONG length,l; GETVARLEN(length,track_pos[trnr],track_left[trnr]); if(f_Sysex) { BYTE c; if(event==0xf0) SEND(0xf0); for(l=0;l %ld\n", track_delta[i]); */ } time_per_beat = t; #ifdef DEBUG (void)printf("our %ld\n",time_per_beat); #endif break; }; /* end of track - can't be bothered to check left == 0 too */ case 0x2f: track_finished[trnr]=TRUE; /* printf("track %d finished\n", trnr); */ finished_tracks++; break; /* ignore rest */ default: CHECKLEFT(track_left[trnr],length); track_pos[trnr] += length; track_left[trnr] -= length; break; }; return(TRUE); }; static int system_common(event) BYTE event; { switch(event) { /* 3 byte instructions */ case 0xf2: { CHECKLEFT(track_left[trnr],2); track_pos[trnr] += 2; track_left[trnr] -= 2; break; }; /* 2 byte instructions */ case 0xf1: case 0xf3: { CHECKLEFT(track_left[trnr],1); track_pos[trnr] += 1; track_left[trnr] -= 1; break; }; /* rest are single byte */ }; return(TRUE); }; static int system_real_time() { /* all 1 byte so ignore */ return(TRUE); }; static void all_notes_off() { /* MANUALLY TURN ALL NOTES OFF */ /* time= (2+8)x16x(1+128x2)/31250 ~= 1.5 seconds */ int i,j; if (f_Timing) SEND (0xFC); for(i=0;i<16;i++) { SEND((BYTE)(0x90+i)); for(j=0;j<128;j++) { SEND((BYTE)j); SEND(0); }; }; }; /* general error messages - I got fed up typing these over and over again */ static void truncated() { (void)fprintf(stderr,"%s: %s truncated\n",app_name,gFile); }; /* * REVISION LOG * ============ * 0.1 SGoldthorpe 20-Mar-91 Created for Atari ST / Sozobon C. It's * a bit atari specific in places but i've * tried to make it UNIX(tm) looking for * easier porting (if anyone feels brave * enough to try. * 0.2 SGoldthorpe 7-Apr-91 Messed up the code in mp_intp to * allow type 1 midi files. Timing is * still a bit hairy but it plays 80% * of the files I have OK. * 0.3 SGoldthorpe 27-May-91 Reformatted & tidied up, sorted out * running status and added flags. * 0.4 SGoldthorpe 20-Jul-91 Generally restructed and tidied up and * added sysex events. * */