Metropoli BBS
VIEWER: play.c MODE: TEXT (ASCII)
/*
	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 <stdio.h>
#include <fcntl.h>

#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  <malloc.h>
#include  <string.h>
#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 <musfile.mus> <bankfile.snd>
*/
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 <musfile.mus> <bankfile.snd>");
		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: <F0> <7F> <00> <integer> <frac> <F7>
			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() */



[ RETURN TO DIRECTORY ]