/* PLAY.C 29-jun-88, Marc Savary, Ad Lib Inc. Demo file to play the AdLib MIDI music file ( *.MUS) with the *.SND timbre bank file. This simple driver use the PC's timer-0 for timing. (see TIMER.ASM ) See "convert.c" for *.MUS file structure. This file and the file BANK.C (timbre bank manager) MUST BE compiled WITHOUT stack overflow check. (-v option of Lattice C Compiler, -Gs for Microsoft) If compiling with Lattice, 'LATTICE' must be defined for this file and BANK.C. With Microsoft, use the following ('MICROSOFT' must be defined for this file and BANK.C): masm timer.asm cl -DMICROSOFT -AS -J -Zi -Zp1 -Ox -Gs -c play.c bank.c adlib.c link play.obj adlib.obj bank.obj timer.obj */ #include #include #include "convert.h" #include "bank.h" #ifdef MICROSOFT #define UNSIGNED_CHAR unsigned char #endif #ifdef LATTICE #define UNSIGNED_CHAR char #endif BankPtr bank; /* pointer to instrument bank */ char * musPtr; /* pointer to first data byte of melody */ struct MusHeader * headPtr; /* pointer to header of .MUS file */ char musRunning; /* != 0 if music is playing */ UNSIGNED_CHAR status; /* running status byte */ char volume[ NR_VOICES]; /* actual volume of all voices */ long tickCount; /* time counter, for information only */ unsigned delay; /* length of last delay */ #ifdef LATTICE extern char * getmem(); extern int allmem(); extern int rlsmem(); #endif #ifdef MICROSOFT #include #include #define rlsmem(x,y) free(x) #define getmem(x) malloc(x) #define setmem(x,y,z) memset(x,z,y) #define movmem(x,y,z) memmove(y,x,z) #define max(x,y) ((x > y) ? x:y) #endif /* Simple demonstration. Syntax: play */ main( argc, argv) int argc; char * argv[]; { struct MusHeader mH; int meloFile; char * music; unsigned len; BankPtr theBank; if( argc < 3) { fprintf( stderr, "\nUSE: play "); exit( 1); } /* Perform some initialisations ... */ Init(); if( NULL == ( theBank = OpenBank( argv[ 2], 0))) { fprintf( stderr, "\nUnable to open timbre bank file '%s'.", argv[ 2]); Terminate(); exit( 1); } if( !LoadBank( theBank)) { fprintf( stderr, "\nError while reading timbre bank."); Terminate(); exit( 1); } meloFile = open( argv[ 1], O_RDONLY + O_RAW); if( -1 == meloFile) { fprintf( stderr, "\nUnable to open music file '%s'.", argv[ 1]); Terminate(); exit( 1); } /* read the music file's header: */ read( meloFile, &mH, sizeof( struct MusHeader)); len = mH.dataSize; music = (char *) getmem( (unsigned) len); if( music == NULL) { fprintf( stderr, "\nMemory allocation error."); exit( 1); } /* load all the data in memory: */ read( meloFile, music, len); /* Start playing: */ StartMelo( &mH, music, len, theBank); /* wait until end.... */ WaitEndMelo(); /* uninstall the clock driver: */ Terminate(); rlsmem( music, len); CloseBank( theBank); } /* main() */ /* Wait until the end of melody ( musRunning == 0) */ WaitEndMelo() { static unsigned measure = 0, beat = 0; unsigned m, b, c, i; /* cprintf( "\nPress ESC key to stop"); */ while( musRunning) { if( kbhit()) { c = getch(); if( c == 0x1b) StopMelo(); } #ifdef DEBUG m = tickCount / (headPtr->beatMeasure * headPtr->tickBeat); b = tickCount / headPtr->tickBeat; if( m != measure) { printf( "\nMeasure: %03d ", m); measure = m; } if( b != beat) { printf( "+ "); beat = b; } #endif } } /* WaitEndMelo() */ /* Initialize the driver. */ Init() { /* initalize the low-level sound-driver: */ if( !SoundColdInit( 0x388)) { printf( "\nAdlib board not found!"); exit( 1); } /* allocate all the memory available:*/ #ifdef LATTICE allmem(); #endif /* install the clock driver: */ Clk_install(); } /* Uninstall the clock driver ... */ Terminate() { Clk_uninstall(); } /* Start playing a melody. Set some global pointers for the interrupt routine. Set the tempo, sound mode & pitch bend range. Reset volume of each voice. Start the clock driver with the first delay ( >= 1) */ StartMelo( header, data, len, timBank) struct MusHeader * header; /* pointer to header struc. of music file */ char * data; /* pointer to music data */ unsigned len; /* size of data */ BankPtr timBank; /* bank of timbres */ { int i; musPtr = data; headPtr = header; bank = timBank; tickCount = 0; for( i = 0; i < NR_VOICES; i++) volume[ i] = 0; SetMode( header->soundMode); SetPitchRange( header->pitchBRange); SetTempo( header->basicTempo, header->tickBeat); StartTimeOut( 0); delay = *musPtr++; musRunning = 1; /* NEVER START COUNT-DOWN WITH 0, since 0 means MAXIMUM delay!!! */ StartTimeOut( max( delay, 1)); } /* StartMelo() */ /* Stop playing the melody. Send note-off to all voices and reset the clock frequency to nominal ( 18.2 Hz). */ StopMelo() { int i; musRunning = 0; for( i = 0; i < NR_VOICES; i++) NoteOff( i); SetTempo( 0, 1); } /* StopMelo() */ /* Change the tempo. Reload the timer-0 with the proper divider for generating the appropriate frequency. If tempo is zero, reprogram the counter for 18.2 Hz. */ SetTempo( tempo, tickBeat) unsigned tempo; /* beats per minute */ unsigned tickBeat; /* ticks per beat */ { long t1; unsigned freq; unsigned low, high, flags, count; t1 = tickBeat * (long)tempo; freq = t1 /60; /* interrupt rate needed */ if( !freq) count = 0; else { /* make sure that frequency is >= 19 Hz, since counter min. output frequency is 18.2 Hz: */ freq = freq < 19 ? 19 : freq; /* compute counter divider: */ count = (1193180 /(long)freq); } /* and set the counter: */ SetClkRate( count); } /* SetTempo() */ /* Interrupt routine. Called by low-level clock driver when the delay count has expired. 'musPtr' always points to an OVERFLOW BYTE or to the first byte AFTER the timing byte. When this routine is called, the active SS ( stack segment) is not the original of the application, so take care. This routine, and all others called by, must be compiled without stack-overflow checking, since the SS has changed!!! Return to caller the number of clock ticks to wait for before the next call. */ unsigned TimeOut() { unsigned pitch, tempo, haut, vol; UNSIGNED_CHAR newStatus; int timbreDef[ TIMBRE_DEF_LEN], timbre; int comm, id, integer, frac, voice = 1; if( ! musRunning) /* Music has not started or has been stopped, so wait the minimum delay ... */ return 1; tickCount += delay; do { newStatus = *musPtr; if( newStatus == OVERFLOW_BYTE) { /* timing overflow ... */ musPtr++; delay = OVERFLOW_BYTE; break; } else if( newStatus == STOP_BYTE) { StopMelo(); return 0; /* maximum delay ... */ } else if( newStatus == SYSTEM_XOR_BYTE) { /* non-standard... this is a tempo multiplier: data format: <7F> <00> tempo = basicTempo * integerPart + basicTempo * fractionPart/128 */ musPtr++; id = *musPtr++; comm = *musPtr++; if( id != ADLIB_CTRL_BYTE || comm != TEMPO_CTRL_BYTE) { /* unknown format ... skip all the XOR message */ musPtr -= 2; while( *musPtr++ != EOX_BYTE) ; } else { integer = *musPtr++; frac = *musPtr++; tempo = headPtr->basicTempo; tempo = tempo * integer + (unsigned)(((long)tempo * frac) >> 7); SetTempo( tempo, headPtr->tickBeat); musPtr++; /* skip EOX_BYTE */ } delay = *musPtr++; } else { if( newStatus >= 0x80) { musPtr++; status = newStatus; } voice = (int) (status & 0x0f); switch( status & 0xf0) { case NOTE_ON_BYTE: haut = *musPtr++; vol = *musPtr++; if( ! vol) { NoteOff( voice); } else { if( vol != volume[ voice]) { SetVoiceVolume( voice, vol); volume[ voice] = vol; } NoteOn( voice, haut); } break; case NOTE_OFF_BYTE: musPtr += 2; NoteOff( voice); break; case AFTER_TOUCH_BYTE: SetVoiceVolume( voice, *musPtr++); break; case PROG_CHANGE_BYTE: timbre = *musPtr++; if( GetTimbre( "", &timbre, timbreDef, bank)) { SetVoiceTimbre( voice, timbreDef); } else cprintf( "\nTimbre not found: %d", timbre); break; case PITCH_BEND_BYTE: pitch = *musPtr++; pitch += (unsigned)(*musPtr++) << 7; SetVoicePitch( voice, pitch); break; case CONTROL_CHANGE_BYTE: /* not implemented ... */ musPtr += 2; break; case CHANNEL_PRESSURE_BYTE: /* not implemented ... */ musPtr++; break; default: cprintf( "\nBad MIDI status byte: %d", status); SkipToTiming(); break; } delay = *musPtr++; } } while( delay == 0); if( delay == OVERFLOW_BYTE) { delay = OVERFLOW; if( *musPtr != OVERFLOW_BYTE) delay += *musPtr++; } return delay; } /* A bad status byte ( or unimplemented MIDI command) has been encontered. Skip bytes until next timing byte followed by status byte. */ static SkipToTiming() { while( *musPtr < 0x80) musPtr++; if( *musPtr != OVERFLOW_BYTE) musPtr--; } /* SkipToTiming() */