home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
TopWare 18: Liquid
/
Image.iso
/
liquid
/
top1120
/
pmidi.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-04-10
|
51KB
|
1,903 lines
; /*\
;---|*|----====< Play MIDI >====----
;---|*|
;---|*| play/record blocks of MIDI
;---|*|
;---|*| Copyright (c) 1993,1994 V.E.S.A, Inc. All Rights Reserved.
;---|*|
;---|*| VBE/AI 1.0 Specification
;---|*| February 2, 1994. 1.00 release
;---|*|
;---|*| Additional Changes:
;---|*| 02/16 - Added a switch to allow sending Active Sensing as an option.
;---|*| 02/16 - Added a routine to send All Notes Off before exiting.
;---|*| 02/27 - Removed a bunch of unused code to clean up the pgm
;---|*| 03/10 - Added cheap quantization of the tempo to reduce the
;---|*| interrupt rate to drive the tempo.
;---|*| 04/05 - Changed the handling of the delta and track processing
;---|*| because the cheap quantization threw things out of order.
;---|*|
; \*/
#include <stdio.h>
#include <signal.h>
#include "vbeai.h"
#include "vesa.h"
#define TRUE -1
#define FALSE 0
#define ON TRUE
#define OFF FALSE
#if DEBUG
#define BREAKPOINT _asm{int 3};
#else
#define BREAKPOINT _asm{nop};
#endif
; /*\
;---|*| External Variables
; \*/
extern char *melodicnames[];
extern char *percussivenames[];
; /*\
;---|*| Global Variables
; \*/
typedef struct {
char fileid[4]; // file ID - "MThd"
long flen; // header length
int ftype; // midi file type
int ftracks; // number of tracks
int fdiv; // time division
} MIDIheader;
typedef struct {
char fileid[4]; // file ID - "MTrk"
long flen; // midi track len
} MDtrack;
typedef struct {
MDtrack mt; // Track Header
int tnum; // track number
long delta; // delta time counting
char far *p; // pointer to the data
} MIDItrack;
int deltastep = 1; // incrementing step count
long deltacount= 0; // full delta count
#define MAXTRACKS 32
MIDIheader mhdr = { 0 }; // midi file header
MIDItrack mtrk[32] = { 0 }; // track structures
int CallBackOccured;
int clocktick = 0;
int ScatteredMessages = TRUE;
VESAHANDLE hMIDI = 0;
FILE *rfile;
char far *memptr = 0;
char far *midptr = 0;
char *MIDFileName;
GeneralDeviceClass gdc; // receives a copy of the VESA driver info block
fpMIDServ msv; // pointer to MIDI functions
#define FILE_UNK 0
#define FILE_MIDI 1
int filetype = FILE_UNK;
char DoTracks[16] = {
TRUE, TRUE, TRUE, TRUE,
TRUE, TRUE, TRUE, TRUE,
TRUE, TRUE, TRUE, TRUE,
TRUE, TRUE, TRUE, TRUE
};
unsigned char PatchXlate[256]; // patch translation table
unsigned char ChannelXlate[16]; // channel translation table
int keyoffset = 0; // key (note) offset by one octave
int patchoffset = 0; // no patch offset
int tempooffset = 0; // no tempo offset
int MicrosoftGMfmt = FALSE; // microsoft GM file format
long micros = 500000; // 500ms per quarter note
int sigdenom = 2; // qnote gets the beat
int SystemMsgTick = 0; // system msg (0xFE) needed
int msgsent = FALSE; // any msg has been sent
int ActiveSensing = FALSE; // defaults to OFF
; /*\
;---|*| prototypes
; \*/
int LoadMIDITrack ( MIDItrack * );
int LoadMIDIFile ( );
int ProcessMidiMsg ( MIDItrack * );
int SystemMessage ( unsigned int, MIDItrack * );
long GetValue ( char *, long );
int readblock ( int, char huge *, int );
int swapdw ( int );
long swapdd ( long );
long ReadVariable ( MIDItrack * );
void far pascal OurSystemMSGCallBack ( );
void far pascal OurTimerCallBack ( );
void far pascal OurMIDIReceiver ( int, int, char, long );
void SetSongTempo ( long, int );
VESAHANDLE OpenTheDriver ( int );
void OutputMidiMsg ( char far *, int );
PrintMsg ( int, char far *, int );
int DriverError;
int VerboseMode = FALSE;
int StatusMode = FALSE;
int UserPref = 0; // highest level to be used
int maxtones; // maximum # of tones supported by the h/w
int InputOnly = FALSE; // MIDI receiver tests
char *msgnames[8] = {
"Note Off", // 0x80
"Note On ", // 0x90
"Poly Key", // 0xA0
"Ctlr Chg", // 0xB0
"Pgm Chg", // 0xC0
"Chan Pre", // 0xD0
"Pitch wh", // 0xE0
"System " // 0xF0
};
// only allows us to run on this version of the VBE interface. This
// will be removed after the interface is ratified. Changes may be made
// that would cause this code to crash if run on other version. Again,
// this will be removed in the final software.
int VersionControl = 0x0100;
; /*\
;---|*|------------------==============================-------------------
;---|*|------------------====< Start of execution >====-------------------
;---|*|------------------==============================-------------------
; \*/
main(argc,argv)
int argc;
char *argv[];
{
int loop = TRUE;
int n,c;
int maxticks,ticks;
int NotHalted;
int exitcode = 0;
long l;
int trk,deadcnt;
int laps = 10;
int all,nxt;
// process the command line
CommandLine (argc,argv);
// disable the ^C so the devices will close properly.
signal (SIGINT,SIG_IGN);
// Find one MIDI device
if (!OpenTheDriver(UserPref)) { // get the driver the user prefer
printf ("Cannot find any installed VBE/AI devices!\n");
DoExit(-1);
}
// This is playback, so load the MIDI file header, then each track
if (!LoadMIDIFile())
DoExit(-1);
for (n=0;n<mhdr.ftracks;n++) {
mtrk[n].p=midptr;
LoadMIDITrack(&mtrk[n]);
midptr += mtrk[n].mt.flen; // get the next ptr
mtrk[n].delta = ReadVariable(&mtrk[n]); // read the 1st delta from the file
}
deltacount = 0;
// setup a callback for active system messages
VESARegisterTimer( 254, &OurSystemMSGCallBack, VESARateToDivisor(5) );
VESARegisterTimer( 255, &OurTimerCallBack, VESARateToDivisor(120) );
// process the tracks
while (loop) {
// see if we need an active sensing message to keep the device active
KeepActive();
// Callbacks occur to give us a tempo for MIDI timing
if (CallBackOccured) {
ReportCallback();
CallBackOccured--;
// if we have run out of data, exit the program...
deltacount += deltastep;
while(1) {
// find the track with the lowest delta time
deadcnt = 0;
for (nxt=n=0;n<mhdr.ftracks;n++) {
if (mtrk[n].mt.flen > 0) {
if (mtrk[n].delta < mtrk[nxt].delta)
nxt = n;
}
else {
deadcnt++;
if (nxt == n) nxt++;
}
}
// if all tracks are dead, exit
if (deadcnt >= mhdr.ftracks)
break;
// if higher that the current delta count, exit
if (mtrk[nxt].delta > deltacount)
break;
ProcessMidiMsg(&mtrk[nxt]);
}
}
// see if the user wishes to exit
if (kbhit()) {
switch (c = getch()) {
// quit the program
case 's': // do a fast forward in the song
case 'S':
for (n=0;n<mhdr.ftracks;n++)
mtrk[n].delta = deltacount;
break;
case 0x20:
// flush our callback control
VESARegisterTimer( 255, 0, 0 );
// wait on the user...
printf ("Song Now Paused!\n");
while (!kbhit())
KeepActive();
getch();
// set the tempo & let'er rip!
SetSongTempo ( micros, sigdenom );
break;
case 0x1b:
AllNotesOff();
loop = 0;
exitcode = TRUE;
default:
break;
}
}
// if the driver posts an error, go report it
if ( (DriverError = (msv->msGetLastError)()) )
printf ("Driver is reporting an internal error! (code=%d)\n",DriverError);
// if in status mode, dump status every x times through the loop
if (StatusMode) {
if (!--laps) {
laps = 10;
DumpSomeInfo();
}
}
}
// exit now...
DoExit(exitcode);
}
; /*\
;---|*|----------------------=======================---------------------------
;---|*|----------------------====< Subroutines >====---------------------------
;---|*|----------------------=======================---------------------------
; \*/
; /*\
;---|*|----====< AllNotesOff >====----
;---|*|
;---|*| Turn each track off
;---|*|
; \*/
AllNotesOff()
{
int n;
char msg[3] = { 0,0x7b,0x00 };
for(n=0;n<16;n++) {
msg[0] = 0xb0 + n;
OutputMidiMsg (msg,3);
}
}
; /*\
;---|*|----====< CommandLine >====----
;---|*|
;---|*| Process the command line switches
;---|*|
; \*/
CommandLine(argc,argv)
int argc;
char *argv[];
{
int n,x;
char *s,c;
FILE *mapnam;
int vh,vl;
// print the hello...
vh = VersionControl >> 8;
vl = VersionControl & 0xFF;
printf ("\nVESA VBE/AI MIDI Output Test Program, %02x.%02x\n",vh,vl);
printf ("Copyright (c) 1993,1994 VESA, Inc. All Rights Reserved.\n\n");
// setup the patch translation table defaults
for (n=0;n<256;n++)
PatchXlate[n] = n & 0x7F; // only 7 bits worth
// exit if no other parameters
if (argc < 2) {
GiveHelps();
DoExit(0);
}
MIDFileName = argv[1];
printf ("Attempting to use \"%s\" input file",MIDFileName);
filetype = FILE_MIDI;
printf (" as a MIDI file\n");
n = 2;
while (n<argc) {
s = argv[n++];
if (*s == '-') s++;
if (*s == '/') s++;
switch (*s & 0xDF) {
case 'A':
if (*++s == '-')
ActiveSensing = FALSE;
else
ActiveSensing = TRUE;
printf ("Active sensing is turned ");
(ActiveSensing) ? printf ("ON\n") : printf ("OFF\n");
break;
case 'C':
printf ("Using Channel map file \"%s\"\n",++s);
if (!(mapnam = fopen (s,"rb")) ) {
printf ("Unable to open the channel map file!\n");
DoExit(-1);
}
if (fread (&ChannelXlate,sizeof(char),16,mapnam) != 16) {
printf ("Unable to load the whole channel map file!\n");
DoExit(-1);
}
// if the channel is 0xFF, kill it...
BREAKPOINT
for (n=0;n<16;n++)
if (ChannelXlate[n] == 0xFF)
DoTracks[n] = FALSE;
fclose (mapnam);
break;
case 'F':
ScatteredMessages = FALSE;
printf ("Will process messages in full length\n");
break;
case 'H':
for (x=0;x<16;x++) {
if (x < 12)
DoTracks[x] = TRUE;
else
DoTracks[x] = FALSE;
}
printf ("High end synth for tracks 1 - 12\n");
break;
case 'I':
InputOnly = TRUE;
printf ("In INPUT mode only - no output performed\n");
break;
case 'K':
s++;
keyoffset = 1; // there will be an offset
if (*s == '-') // get the PLUS or MINUS direction
c = *s++;
if (*s == '+')
c = *s++;
keyoffset = (int)GetValue(s,(unsigned long)keyoffset);
if (c == '-')
keyoffset = -keyoffset;
printf ("Key offset by %d keys\n",keyoffset);
break;
case 'L':
MicrosoftGMfmt = TRUE;
for (x=0;x<16;x++) {
if (x < 12)
DoTracks[x] = FALSE;
else
DoTracks[x] = TRUE;
}
printf ("Low end synth for tracks 13 - 16\n");
break;
case 'M':
printf ("Using patch Map file \"%s\"\n",++s);
if (!(mapnam = fopen (s,"rb")) ) {
printf ("Unable to open the patch map file!\n");
DoExit(-1);
}
if (fread (&PatchXlate,sizeof(char),256,mapnam) != 256) {
printf ("Unable to load the whole patch map file!\n");
DoExit(-1);
}
fclose (mapnam);
break;
case 'P':
UserPref = (int)GetValue (++s,0);
printf ("User Prefers (%d) level drivers\n",UserPref);
break;
case 'R': // program change offset
s++;
patchoffset = 1; // there will be an offset
c = 0;
if (*s == '-') // get the PLUS or MINUS direction
c = *s++;
if (*s == '+')
c = *s++;
patchoffset = (int)GetValue(s,(unsigned long)patchoffset);
if (c == '-')
patchoffset = -patchoffset;
printf ("Patch offset by %d program steps\n",patchoffset);
break;
case 'S': // status dumps
StatusMode = GetValue(++s,(StatusMode = 10));
printf ("Status mode = %d\n",StatusMode);
break;
case 'T': // tempo offset percentage
s++;
c = 0;
if (*s == '-') // get the PLUS or MINUS direction
c = *s++;
if (*s == '+')
c = *s++;
tempooffset = (int)GetValue(s,10L);
if (c == '-')
tempooffset = -tempooffset;
printf ("Tempo offset by %d%%\n",tempooffset);
break;
case 'V': // print everything at a given level
VerboseMode = GetValue(++s,(VerboseMode = 10));
printf ("Verbose level = %d\n",VerboseMode);
break;
default:
printf ("Unknown option - %s\n",s);
DoExit(-1);
}
}
// separate the parameter text from the rest of the report
printf ("\n");
}
; /*\
;---|*|----====< DoExit >====----
;---|*|
;---|*| Shut everything down, then exit to DOS
;---|*|
; \*/
DoExit(cc)
int cc;
{
// close the device if already opened
if (msv)
VESACloseDevice(hMIDI); // close the device
// flush our callback control
VESARegisterTimer( 255, 0, 0 );
VESARegisterTimer( 254, 0, 0 );
// return to DOS, don't return to caller
exit(cc);
}
; /*\
;---|*|----====< ProcessMidiMsg >====----
;---|*|
;---|*| Process the MIDI notes
;---|*|
; \*/
int ProcessMidiMsg(m)
MIDItrack *m;
{
unsigned int status;
char far *msg;
char far *patch;
int psize,ch,doit,idx;
unsigned int ptch;
static unsigned int running = 0;
// if no more data, bail...
if (!m->mt.flen)
return(0);
// get the status, make it running
status = running;
// maintain running status
if (*m->p & 0x80) {
if (((status = *m->p++ & 0xFF) & 0xF0) != 0xF0)
running = status;
m->mt.flen--;
}
// always send the message. Running status will be done later.
*(msg=m->p-1) = status; // load the message into memory
// make sound if this is a track we want to hear
if (DoTracks[ch=status & 0x0F]) {
switch (status & 0xF0) {
case 0x80: // Note Off
if(((ch==9)&&DoTracks[9]) || ((MicrosoftGMfmt)&&(ch==15))) {
msg[0] = 0x89; // make it channel #9
msg[1] = PatchXlate[(msg[1]+0x80) & 0xFF] & 0x7F;
}
else {
msg[1] += keyoffset; // offset the note
msg[1] &= 0x7F; // keep it in 7 bits
}
PrintMsg (9,msg,3); // display the msg
OutputMidiMsg (msg,3); // send it to the h/w
m->mt.flen -= 2; // adjust the length
m->p += 2;
break;
case 0x90: // Note On
// make sure all GM MIDI percussive channel patches
// are loaded before starting
if ( ((ch==9)&&DoTracks[9]) || ((MicrosoftGMfmt)&&(ch == 15)) ) {
// for Microsofts MIDI channel 15 to channel 9
if ((ch == 15) && MicrosoftGMfmt)
msg[0] = 0x99; // make it channel #9
// translate the patch from the 2nd half of the tbl
ptch = PatchXlate[(msg[1] + 128) & 0xFF];
// if there are external patches, load them now
if (gdc.u.gdmi.milibrary[0]) {
// skip if preloaded
if (!PatchIsLoaded(ptch+0x80)) {
// load the percussive patch now
VESAPreLoadPatch(hMIDI, ptch, (msg[0]&0x0F));
if (VerboseMode >= 7)
printf ("%s ",percussivenames[ptch]);
PrintMsg (7,msg,3);
}
}
// save the translated patch #
msg[1] = ptch;
}
else {
msg[1] += keyoffset; // offset the note
msg[1] &= 0x7F; // keep it in 7 bits
}
PrintMsg (9,msg,3); // display the msg
OutputMidiMsg (msg,3); // send it to the h/w
m->mt.flen -= 2; // adjust the length
m->p += 2;
break;
case 0xA0: // Poly Key Pressure
if(((ch==9)&&DoTracks[9]) || ((MicrosoftGMfmt)&&(ch == 15)))
msg[1] = PatchXlate[(msg[1] + 0x80) & 0xFF] & 0x7F;
else
msg[1] += keyoffset; // offset the note
msg[1] &= 0x7F; // keep it in 7 bits
// falls through...
case 0xB0: // Control Change
case 0xE0: // Pitch Wheel change
PrintMsg (6,msg,3); // display the msg
OutputMidiMsg (msg,3); // send it to the h/w
m->mt.flen -= 2; // adjust the length
m->p += 2;
break;
case 0xC0: // Program Change
// handle the percussive channel separate from melodics
if (((ch==9)&&DoTracks[9]) || ((MicrosoftGMfmt)&&(ch==15))) {
// patch xlate on percussive patches
ptch = (msg[1] = PatchXlate[(msg[1] | 0x80) & 0xFF]);
// if MSGMFMT & channel 15, force to channel #9
if ((MicrosoftGMfmt)&&(ch == 15)) {
msg[0] = 0xC9; // make it channel #9
}
if (VerboseMode >= 7)
printf ("%s ",percussivenames[ptch]);
PrintMsg (7,msg,2);
}
else {
// patch xlate on melodic patches
ptch = PatchXlate[msg[1] & 0x7F]; // translate to a new patch
ptch = (ptch + patchoffset) & 0x7F;// offset the patch
msg[1] = ptch; // save the patch
if (VerboseMode >= 7)
printf ("%s ",melodicnames[ptch]);
PrintMsg (7,msg,2);
}
// if there is an external library, load a patch now
if (gdc.u.gdmi.milibrary[0]) {
// if preloaded, skip the process
doit=FALSE;
if (((ch==9)&&DoTracks[9]) || ((MicrosoftGMfmt)&&(ch==15))) {
if (!PatchIsLoaded(ptch+0x80))
doit=TRUE;
}
else {
if (!PatchIsLoaded(ptch))
doit=TRUE;
}
// if percussive or melodic needs one, do it
if (doit) {
// get the patch & get the size
VESAPreLoadPatch( hMIDI, ptch, msg[0]&0x0F );
}
}
OutputMidiMsg (msg,2);
m->mt.flen--; // adjust the length
m->p++;
break;
case 0xD0: // Channel Pressure
PrintMsg (6,msg,2);
OutputMidiMsg (msg,2);
m->mt.flen--; // adjust the length
m->p++;
break;
case 0xF0:
PrintMsg (6,msg,5);
SystemMessage (status,m);
break;
default:
PrintMsg (1,msg,5);
printf ("Broken MIDI stream! (no status/running status!)\n");
DoExit(1);
}
}
// toss out these tracks
else {
switch (status & 0xF0) {
case 0x80: // Note Off
case 0x90: // Note On
case 0xA0: // Poly Key Pressure
case 0xB0: // Control Change
case 0xE0: // Pitch Wheel change
if (VerboseMode >= 9)
printf (" ");
PrintMsg (9,msg,3);
m->mt.flen -= 2; // adjust the length
m->p += 2;
break;
case 0xC0: // Program Change
case 0xD0: // Channel Pressure
if (VerboseMode >= 9)
printf (" ");
PrintMsg (9,msg,2);
m->mt.flen--; // adjust the length
m->p++;
break;
case 0xF0: // always do these...
PrintMsg (9,msg,5);
SystemMessage (status,m);
break;
default:
if (VerboseMode >= 9)
printf (" ");
PrintMsg (9,msg,5);
printf ("Broken MIDI stream! (no status/running status!)\n");
DoExit(1);
}
}
// get the next midi msg, or bail out
if (m->mt.flen)
m->delta += ReadVariable(m); // read the 1st delta from the file
// return the adjusted track pointer
return(-1);
}
;
; /*\
;---|*|----====< OpenTheDriver >====----
;---|*|
;---|*| Find the driver with the highest user preference, and return it to
;---|*| the caller
;---|*|
; \*/
int OpenTheDriver(pref)
int pref;
{
int driverpref = 256; // real low preference
long l;
// get the device information
do {
// Find one MIDI device, else ]ail if non found
if ((hMIDI = VESAFindADevice(MIDDEVICE)) == 0)
return(0);
// get the device information
if (VESAQueryDevice(hMIDI, VESAQUERY2 ,&gdc) == 0) {
printf ("Cannot query the installed VBE/AI devices!\n");
DoExit(-1);
}
// make sure it's midi
if (gdc.gdclassid != MIDDEVICE) {
printf ("The VESA find device query returned a NON MIDI device!\n");
DoExit(-1);
}
// make sure it's matches the beta version #
if (gdc.gdvbever != VersionControl) {
printf ("The VESA device version # does not match, cannot continue!\n");
DoExit(-1);
}
// get the drivers user preference level
driverpref = gdc.u.gdmi.midevpref;
// if the caller is not expressing a preference, then use this one
if (pref == -1)
break;
} while (driverpref != pref);
// get the memory needed by the device
if (!(memptr = AllocateBuffer(gdc.u.gdmi.mimemreq))) {
printf ("We don't have memory for the device!\n");
DoExit(-1);
}
if (!(midptr = AllocateBuffer(0xffff))) {
printf ("We don't have memory for the loading the MIDI file!\n");
DoExit(-1);
}
// if the MIDI device doesn't open, bomb out...
if ((msv = (fpMIDServ)VESAOpenADevice(hMIDI,0,memptr)) == 0) {
printf ("Cannot Open the installed devices!\n");
DoExit(-1);
}
// if there is a patch library, load it now...
if (gdc.u.gdmi.milibrary[0])
if (VESALoadPatchBank(hMIDI, msv, &gdc.u.gdmi.milibrary[0]) == 0)
DoExit(-1);
// callbacks, reset & other things...
msv->msApplMIDIIn = &OurMIDIReceiver;
(msv->msGlobalReset)();
maxtones = gdc.u.gdmi.miactivetones;
return(hMIDI);
}
; /*\
;---|*|----====< PatchIsLoaded >====----
;---|*|
;---|*| Test the driver's bit table to determine if the patch is preloaded
;---|*|
; \*/
int PatchIsLoaded(int ptch)
{
int far *p = &msv->mspatches[0];
_asm {
push es
les bx,[p]
mov ax,[ptch]
and ax,0FFh
mov cx,ax
shr ax,3
add bx,ax
and cx,007h
mov ax,0080h
shr ax,cl
and ax,es:[bx]
pop es
}
}
; /*\
;---|*|----====< PrintMsg >====----
;---|*|
;---|*| Print the MIDI messages
;---|*|
; \*/
PrintMsg (level,msg,len)
int level;
char far *msg;
int len;
{
int n;
int cmd;
char far *m;
// verbose mode must be active to print
if (!VerboseMode)
return(0);
// the priority level must be higher (0=highest, 5=lowest)
if (level > VerboseMode)
return(0);
// get the command. change zero velocity NOTE ON to NOTE OFF
cmd = (*msg & 0xF0) >> 4;
if (cmd == 9)
if (msg[2] == 0)
cmd = 8;
// print each byte in the string as a hex pair
m = msg;
for (n=len;n;n--)
printf ("%02x ",*m++ & 0xFF);
// print the command name and a CR/LF
printf("%s\n",msgnames[cmd-8]);
}
; /*\
;---|*|----====< ReadVariable >====----
;---|*|
;---|*| Read a variable length 32 bit number
;---|*|
; \*/
long ReadVariable(m)
MIDItrack *m;
{
long result = 0;
register char c;
while (m->mt.flen) {
result = (result << 7) + ((c=*m->p++) & 0x7F);
m->mt.flen--;
if (!(c & 0x80)) break;
}
return(result);
}
; /*\
;---|*|----====< SystemMessage >====----
;---|*|
;---|*| Process a system message
;---|*|
; \*/
SystemMessage (status,mtk)
unsigned int status;
MIDItrack *mtk;
{
long melen;
long us;
char metype;
int n;
// parse the SYSTEM mesage
switch (status) {
case 0xF7: // sysex scan
case 0xF0: // sysex scan for the eot
melen = ReadVariable(mtk);
mtk->p += melen; // skip over the sysex
mtk->mt.flen -= melen;
break;
case 0xF2: // state
mtk->mt.flen--; // eat two parameters by
mtk->p++; // falling through
case 0xF1: // MTC Quarter-Frame
case 0xF3: // continue
mtk->mt.flen--; // eat one parameter...
mtk->p++;
case 0xF4: // undefined - no parameters
case 0xF5: // undefined - no parameters
case 0xF6: // active sensing
case 0xF8: // timing clock
case 0xF9: // undefined
case 0xFA: // state
case 0xFB: // continue
case 0xFC: // stop
case 0xFD: // undefined
case 0xFE: // active sensing
break;
case 0xFF: // meta event.
metype = *mtk->p++; // get the type
mtk->mt.flen--;
melen = ReadVariable(mtk); // get the length
switch (metype) {
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
// text messages
mtk->mt.flen -= melen;
// print all characters under 128
for (n=melen;n;n--) {
if ((*mtk->p & 0x80) == 0)
putchar(*mtk->p);
mtk->p++;
}
printf ("\n");
break;
case 0x51:
// microseconds per division
us = (long)(*mtk->p++ & 0xff) << 16;
us += (long)(*mtk->p++ & 0xff) << 8;
us += (long)(*mtk->p++ & 0xff);
mtk->mt.flen -= 3;
SetSongTempo ( (micros=us), sigdenom );
break;
case 0x58:
sigdenom = mtk->p[1]; // get the sign. denom
mtk->mt.flen -= melen;
mtk->p += melen;
////SetSongTempo ( micros, sigdenom );
break;
default:
mtk->p += melen; // just blow it off
mtk->mt.flen -= melen;
if (metype == 0x2F) {
if (VerboseMode)
printf ("End of Track #%d\n",mtk->tnum);
mtk->mt.flen = 0;
mtk->delta = deltacount;
}
break;
}
break;
default:
break;
}
}
; /*\
;---|*|----====< ProcessNote >====----
;---|*|
;---|*| Perfrom a ramp of notes up and down
;---|*|
; \*/
ProcessNote()
{
int n;
int p;
#define BASENOTE 30
static int direction = ON;
static int note = BASENOTE;
char midimsg[3] = { 0x90, 0x30, 0x7f };
// turn the voice on, or off
if (direction == ON) {
// if out of voices, start turning them off
if ((msv->msDeviceCheck) (MIDITONES, 0) == 0) {
direction = OFF;
note = BASENOTE;
}
else {
// build a message for NOTEON
midimsg[1] += note++; // make each note different
midimsg[2] = 0x7f; // full velocity
if (note > 90)
note = 90;
// go play the note
OutputMidiMsg (&midimsg[0],3);
}
}
if (direction == OFF) {
if ((msv->msDeviceCheck) (MIDITONES, 0) == maxtones) {
direction = ON;
note = BASENOTE;
}
else {
// build a message for NOTEON
midimsg[1] += note++; // make each note different
midimsg[2] = 0x00; // zero velocity
if (note > 90)
note = 90;
// go stop the note
OutputMidiMsg (&midimsg[0],3);
}
}
}
; /*\
;---|*|----------------------=======================----------------------
;---|*|----------------------====< Subroutines >====----------------------
;---|*|----------------------=======================----------------------
; \*/
; /*\
;---|*|----====< DumpSomeInfo >====----
;---|*|
;---|*| Do direct screen writes to display our status information. Dumps
;---|*| 256 characters indicating the preloaded bits
;---|*|
; \*/
DumpSomeInfo()
{
int far *vp = (int far *)0xB8000320; // starting on video row 5
int x,y,v,m;
// display the preloaded bits in the services table
for (y=0;y<16;y++) {
v = msv->mspatches[y];
m = 0x0001;
for (x=0;x<16;x++) {
*vp++ = 0x3f30 + ((v & m) ? 1 : 0);
m <<= 1;
}
}
}
;
; /*\
;---|*|----====< GetKey >====----
;---|*|
;---|*| Get a key from the keyboard
;---|*|
; \*/
int GetKey(flag)
int flag;
{
int c;
// flush the keys coming in
if (flag)
while (kbhit())
getch();
// get the real key
if ((c = getch()) == 0)
c = getch();
// return to the caller
return(c);
}
; /*\
;---|*|----====< GetValue >====----
;---|*|
;---|*| Return a value from the string, or the last value
;---|*|
; \*/
long GetValue (s,orig)
char * s;
long orig;
{
long w;
int NegateFlag = FALSE;
// if the first character is negative, then set out neg flag
if (*s == '-') {
s++;
NegateFlag = TRUE;
}
// check the first character, if zero, then it's octal or hex
if (*s == '0') {
w = 0;
if ((*++s & 0x5F) == 'X') {
if (sscanf (++s,"%lx",&w) != 1)
w = orig;
}
else {
if (isdigit(*s)) {
if (sscanf (s,"%lo",&w) != 1)
w = orig;
}
else {
w = 0;
}
}
}
// return a decimal value
else {
if (sscanf (s,"%ld",&w) != 1)
w = orig;
}
// we have something...
if (NegateFlag)
w = 0 - w;
return(w);
}
; /*\
;---|*|----====< GiveHelps >====----
;---|*|
;---|*| Give the user the commandline option list
;---|*|
; \*/
GiveHelps()
{
printf ("\nTo Use: DOS>pmidi [song] [A] [H] [L] [M] [K{+|-xx}] [R{+|-xx}] [V{xx}]\n\n");
printf ("Where: [song] is the .MID file to be played\n");
printf (" [A{-|+}] Active Sense Enable(+) or Disable(-)\n");
printf (" [H] High end synth playing on channels 1-10.\n");
printf (" [I] to receive only.\n");
printf (" [K{+|-}xx] shifts all key #s up|down by xx count (cheap transpose).\n");
printf (" [L] Low end synth playing on channels 11-16.\n");
printf (" [Mxxxx] patch Map file name (256 bytes for 2 tables).\n");
printf (" [Pxx] selects a VBE device at this (xx) user preference level.\n");
printf (" [R{+|-}xx] shifts all patch #s up|down by xx count.\n");
printf (" [T+|-xx] Tempo shift faster (+) or (-) slower, in percent.\n");
printf (" [V{xx}] verbose mode to dump events as they play. xx can be 1-5.\n");
printf ("\n");
}
; /*\
;---|*|----====< LoadMIDIFile >====----
;---|*|
;---|*| Load the MIDI file
;---|*|
; \*/
LoadMIDIFile()
{
int n;
int fhan;
long len;
char str[80];
if ((rfile = fopen (MIDFileName,"rb")) == 0) {
strcpy (str,MIDFileName);
strcat (str,".MID");
if ((rfile = fopen (str,"rb")) == 0) {
printf ("cannot open the MIDI file!\n");
DoExit(1);
}
}
fhan = fileno(rfile);
readblock (fhan,(char huge *)&mhdr,sizeof(MIDIheader));
// intel fixup
mhdr.flen = swapdd (mhdr.flen );
mhdr.ftype = swapdw (mhdr.ftype );
mhdr.ftracks = swapdw (mhdr.ftracks);
mhdr.fdiv = swapdw (mhdr.fdiv );
if (VerboseMode) {
printf ("mhdr.flen = %ld\n",mhdr.flen );
printf ("mhdr.ftype = %d\n", mhdr.ftype );
printf ("mhdr.ftracks = %d\n", mhdr.ftracks );
printf ("mhdr.fdiv = %d\n", mhdr.fdiv );
}
if (mhdr.fdiv < 0) {
printf ("This MIDI file is not based upon PPQN time!\n");
return(0);
}
if (mhdr.ftracks > MAXTRACKS) {
printf ("WARNING: This MIDI file has too many tracks!\n");
mhdr.ftracks = MAXTRACKS; // limit it to MAXTRACKS tracks
}
return(1);
}
; /*\
;---|*|----====< LoadMIDITrack >====----
;---|*|
;---|*| Load just one MIDI Track into memory
;---|*|
; \*/
LoadMIDITrack(m)
MIDItrack *m;
{
int n;
int fhan;
static int thistrk = 0;
// go direct to DOS using handles
fhan = fileno(rfile);
// get the header
readblock (fhan,(char huge *)&m->mt,sizeof(MDtrack));
if (strcmp (&m->mt.fileid,"MTrk")) {
printf ("Not a MIDI Track!\n");
DoExit(1);
}
// intel fixup
m->mt.flen = swapdd (m->mt.flen);
// if there is data, read it into the buffer
if (m->mt.flen)
readblock (fhan,(char huge *)m->p,(int)m->mt.flen);
// flush the delta time to start everything immediately, give a trk #
printf ("MIDI Track %d length = %ld\n",thistrk,m->mt.flen);
m->tnum = thistrk++;
}
; /*\
;---|*|----====< OurMIDIReceiver >====----
;---|*|
;---|*| Print the received data byte.
;---|*|
; \*/
void far pascal OurMIDIReceiver ( han, delta, mbyte, filler )
int han; // the caller's handle
int delta; // the delta time
char mbyte; // the midi byte
long filler; // reserved
{
}
; /*\
;---|*|----====< OurSystemMSGCallBack >====----
;---|*|
;---|*| Timer tick callback for processing 0xFE messages. NOTE:
;---|*| No assumptions can be made about
;---|*| the segment registers! (DS,ES,GS,FS)
;---|*|
; \*/
void far pascal OurSystemMSGCallBack()
{
// setup our data segment
_asm {
push ds
mov ax,seg SystemMsgTick
mov ds,ax
}
// save for later reporting...
SystemMsgTick++;
// we're done here...
_asm {
pop ds
}
}
; /*\
;---|*|----====< OurTimerCallBack >====----
;---|*|
;---|*| Timer tick callback. NOTE: No assumptions can be made about
;---|*| the segment registers! (DS,ES,GS,FS)
;---|*|
; \*/
void far pascal OurTimerCallBack()
{
// setup our data segment
_asm {
push ds
mov ax,seg CallBackOccured
mov ds,ax
}
// save for later reporting...
CallBackOccured++;
// we're done here...
_asm {
pop ds
}
}
;
; /*\
;---|*|----====< KeepActive >====----
;---|*|
;---|*| Check to see if we need to send active messages
;---|*|
; \*/
KeepActive()
{
static char activemsg = 0xFE;
// bail if not doing active sensing
if (!ActiveSensing)
return(0);
// if any message has been sent since the last system msg tick,
// we don't have to send the active msg
if (msgsent)
return(msgsent = SystemMsgTick = 0);
// send the message to keep the system from going idle
if (SystemMsgTick) {
PrintMsg (9,&activemsg,1);
OutputMidiMsg (&activemsg,1);
SystemMsgTick = 0;
}
}
;
; /*\
;---|*|----====< outputMIDIMsg >====----
;---|*|
;---|*| This routine is not necessary for normal midi, but is added here
;---|*| to thrash the drivers harder by making the data stream closer
;---|*| to "real world" occurances of running status, and incomplete
;---|*| message, etc. The incomplete messages are valid, but are sent to the
;---|*| driver, one byte at a time, to test it's handling of broken up
;---|*| messages. This routine will alternate between sending a whole block
;---|*| of msgs, and sending a block, one byte at a time. Remember, the
;---|*| MIDI driver is supposed to handle messages that span calls.
;---|*|
; \*/
void OutputMidiMsg (m,l)
char far *m;
int l;
{
int n;
char far *s;
static int toggle = 0;
static char lastmsg = 0;
static int col = 0;
// if in a verbose mode, then print the messages
if (VerboseMode == 1) {
s = m;
for (n=l;n;n--) {
if ((col++ & 0x0F) == 0)
printf ("\n");
printf ("%02x ",*s++ & 0xFF);
}
}
// test the drivers running status capaiblities
if (lastmsg == *m) { // if the same msg, drop the msg byte
if (l > 1) { // if the whole msg length is over one
m++; // one byte we will do this, else
l--; // let the whole thing pass to the driver
}
}
else
lastmsg = *m; // not the same, so make it our next victim
// test the drivers ability to receive incomplete messages by
// alternately sending a full message, incremental messages, full, etc...
if (ScatteredMessages) {
toggle ^= 1; // toggle the full/incremental flag
if (toggle) { // send it off a single byte at a time
for (;l;l--)
(msv->msMIDImsg) (m++,1);
}
else // send off the data as a block
(msv->msMIDImsg) (m,l);
}
// scattered messages are disabled in favor of full msgs so low
// level debugging can be done easier.
else
(msv->msMIDImsg) (m,l);
msgsent = TRUE; // we sent something...
}
;
; /*\
;---|*|----====< readblock >====----
;---|*|
;---|*| read a chunk of the PCM file into the huge buffer
;---|*|
; \*/
int readblock (han,tptr,len)
int han;
char huge *tptr;
int len;
{
int siz = 0;
// go get it...
_asm {
push ds
mov cx,[len]
mov ax,cx
add cx,word ptr [tptr] // wrap?
jnc rdbl05 // no, go get the size
sub ax,cx // ax holds the # of bytes to read
mov cx,ax
sub [len],ax
mov ah,03fh // cx holds the length
mov bx,[han]
lds dx,[tptr]
int 21h
mov [siz],ax // we moved this much
add word ptr [tptr+2],0x1000
cmp ax,cx // same size?
jnz rdbldone // no, exit...
;
rdbl05:
mov ah,03fh
mov bx,[han]
mov cx,[len]
jcxz rdbldone
lds dx,[tptr]
int 21h
add [siz],ax // we moved this much
;
rdbldone:
pop ds
}
// return the amount read
return(siz);
}
; /*\
;---|*|----====< ReportCallback >====----
;---|*|
;---|*| Print some useful information to the user
;---|*|
; \*/
ReportCallBack()
{
register char far *vidptr = (char far *)0xb80000A0;
char sstr[80],*s;
static int cbcnt = 0;
// create the string
sprintf (sstr,"%5d ",cbcnt++);
// blast it out...
s = sstr;
while (*s) {
*vidptr++ = *s++;
*vidptr++ = 0x3F;
}
}
; /*\
;---|*|----====< SetSongTempo >====----
;---|*|
;---|*| Set the song tempo from the MIDI file tempo meta event
;---|*|
; \*/
void SetSongTempo(us,denom)
long us; // microseconds per quarter note beat
int denom; // denominator - which partial qnote gets the beat
{
long cbrate;
int sign;
long cboff;
// calculate the callback rate
cbrate = (micros=us) / mhdr.fdiv; // # of us between timer callbacks
cbrate = 1000000/cbrate; // calc the # of ints at this rate
if (tempooffset) {
sign = 0;
if (tempooffset < 0) {
sign = -1;
tempooffset = -tempooffset;
}
cboff = cbrate * tempooffset / 100;
if (sign)
cboff = -cboff;
cbrate += cboff;
}
// report the true rate
printf ("Callback rate is %ld times per second\n",cbrate);
printf ("Tempo = %ldus\n",micros);
// reduce to a reasonable rate
if (ScatteredMessages) {
deltastep = 1;
if (cbrate > 300) {
while (cbrate > 300) {
deltastep <<= 1;
cbrate >>= 1;
}
printf("The callback rate is being adjusted to %ld callbacks per second\n",cbrate);
}
}
////if (denom != 2)
////////printf ("DENOMINATOR IS NOT A QUARTER NOTE!(%d)\n",denom);
// make sure we get callbacks too!
VESARegisterTimer( 255, &OurTimerCallBack, VESARateToDivisor((int)cbrate) );
}
; /*\
;---|*|----====< swapdw >====----
;---|*|
;---|*| swap the bytes in the words
;---|*|
; \*/
int swapdw ( val )
int val;
{
_asm {
mov ax,[val]
xchg ah,al
}
}
; /*\
;---|*|----====< swapdd >====----
;---|*|
;---|*| swap the bytes in the long
;---|*|
; \*/
long swapdd ( val )
long val;
{
_asm {
mov ax,word ptr [val+0]
mov dx,word ptr [val+2]
xchg ah,al
xchg dh,dl
xchg ax,dx
}
}
; /*\
;---|*| end of PMIDI.C
; \*/