home *** CD-ROM | disk | FTP | other *** search
- /*
- 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
-
- */
- #define MICROSOFT 1
- #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 */
-
- #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)
-
- /*
- Simple demonstration.
- Syntax: play <musfile.mus> <bankfile.snd>
- */
-
- /*
- Wait until the end of melody ( musRunning == 0)
- */
- WaitEndMelo()
- {
- static unsigned measure = 0, beat = 0;
- unsigned m, b, c, i;
-
- while( musRunning)
- {
- if( kbhit())
- {
- c = getch();
- if( c == 0x1b) StopMelo();
- }
- }
- } /* WaitEndMelo() */
-
-
- /*
- Initialize the driver.
- */
- InitSnd()
- {
- /* initalize the low-level sound-driver: */
- if( !SoundColdInit( 0x388))
- {
- exit( 1);
- }
- /* 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);
- }
- 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:
- 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() */
-