Source file: /~heha/argon/multimed.zip/ADLIB/MIDIMAIN.C

/***************************************************************************
 *
 *   midimain.c
 *
 *   Copyright (c) 1991-1992 Microsoft Corporation.  All Rights Reserved.
 *
 ***************************************************************************/

#include <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#include "adlib.h"

/***************************************************************************

    internal function prototypes

***************************************************************************/

static void NEAR PASCAL synthNoteOff(MIDIMSG msg);
static void NEAR PASCAL synthNoteOn(MIDIMSG msg);
static void NEAR PASCAL synthPitchBend(MIDIMSG msg);
static void NEAR PASCAL synthControlChange(MIDIMSG msg);
static void NEAR PASCAL synthProgramChange(MIDIMSG msg);

static BYTE NEAR PASCAL FindVoice(BYTE note, BYTE channel);
static BYTE NEAR PASCAL GetNewVoice(BYTE note, BYTE channel);
static void NEAR PASCAL FreeVoice(BYTE voice);


/***************************************************************************

    local data

***************************************************************************/

typedef struct _VOICE {
    BYTE alloc;               /* is voice allocated? */
    BYTE note;                /* note that is currently being played */
    BYTE channel;             /* channel that it is being played on */
    BYTE volume;              /* current volume setting of voice */
    DWORD dwTimeStamp;        /* time voice was allocated */
} VOICE;

static VOICE voices[11];      /* 9 voices if melodic mode or 11 if percussive */

typedef struct _CHANNEL {
    BYTE patch;               /* the patch on this channel */
    WORD wPitchBend;
} CHANNEL;

/* which patch and PB value (0x2000 = normal) is active on which channel */
static CHANNEL channels[NUMCHANNELS] = {
    0, 0x2000,    /* 0 */
    0, 0x2000,    /* 1 */
    0, 0x2000,    /* 2 */
    0, 0x2000,    /* 3 */
    0, 0x2000,    /* 4 */
    0, 0x2000,    /* 5 */
    0, 0x2000,    /* 6 */
    0, 0x2000,    /* 7 */
    0, 0x2000,    /* 9 */
    0, 0x2000,    /* 8 */
    0, 0x2000,    /* 10 */
    0, 0x2000,    /* 11 */
    0, 0x2000,    /* 12 */
    0, 0x2000,    /* 13 */
    0, 0x2000,    /* 14 */
    129, 0x2000   /* 15 - percussive channel */
};

static BYTE loudervol[128] = {
    0,   0,  65,  65,  66,  66,  67,  67,         /* 0 - 7 */
   68,  68,  69,  69,  70,  70,  71,  71,         /* 8 - 15 */
   72,  72,  73,  73,  74,  74,  75,  75,         /* 16 - 23 */
   76,  76,  77,  77,  78,  78,  79,  79,         /* 24 - 31 */
   80,  80,  81,  81,  82,  82,  83,  83,         /* 32 - 39 */
   84,  84,  85,  85,  86,  86,  87,  87,         /* 40 - 47 */
   88,  88,  89,  89,  90,  90,  91,  91,         /* 48 - 55 */
   92,  92,  93,  93,  94,  94,  95,  95,         /* 56 - 63 */
   96,  96,  97,  97,  98,  98,  99,  99,         /* 64 - 71 */
  100, 100, 101, 101, 102, 102, 103, 103,         /* 72 - 79 */
  104, 104, 105, 105, 106, 106, 107, 107,         /* 80 - 87 */
  108, 108, 109, 109, 110, 110, 111, 111,         /* 88 - 95 */
  112, 112, 113, 113, 114, 114, 115, 115,         /* 96 - 103 */
  116, 116, 117, 117, 118, 118, 119, 119,         /* 104 - 111 */
  120, 120, 121, 121, 122, 122, 123, 123,         /* 112 - 119 */
  124, 124, 125, 125, 126, 126, 127, 127};        /* 120 - 127 */


static char patchKeyOffset[] = {
       0, -12,  12,   0,   0,  12, -12,   0,   0, -24,   /* 0 - 9 */
       0,   0,   0,   0,   0,   0,   0,   0, -12,   0,   /* 10 - 19 */
       0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   /* 20 - 29 */
       0,   0,  12,  12,  12,   0,   0,  12,  12,   0,   /* 30 - 39 */
     -12, -12,   0,  12, -12, -12,   0,  12,   0,   0,   /* 40 - 49 */
     -12,   0,   0,   0,  12,  12,   0,   0,  12,   0,   /* 50 - 59 */
       0,   0,  12,   0,   0,   0,  12,  12,   0,  12,   /* 60 - 69 */
       0,   0, -12,   0, -12, -12,   0,   0, -12, -12,   /* 70 - 79 */
       0,   0,   0,   0,   0, -12, -19,   0,   0, -12,   /* 80 - 89 */
       0,   0,   0,   0,   0,   0, -31, -12,   0,  12,   /* 90 - 99 */
      12,  12,  12,   0,  12,   0,  12,   0,   0,   0,   /* 100 - 109 */
       0,  12,   0,   0,   0,   0,  12,  12,  12,   0,   /* 110 - 119 */
       0,   0,   0,   0, -24, -36,   0,   0};            /* 120 - 127 */


static DWORD dwAge;           /* voice relative age */

#define msg_ch         msg.ch /* all messages */

#define msg_note       msg.b1 /* noteoff(0x80),noteon(0x90),keypressure(0xA0) */
#define msg_controller msg.b1 /* controlchange(0xB0) */
#define msg_patch      msg.b1 /* programchange(0xC0) */
#define msg_cpress     msg.b1 /* channelpressure(0xD0) */
#define msg_lsb        msg.b1 /* pitchbend(0xE0) */

#define msg_velocity   msg.b2 /* noteoff(0x80), noteon(0x90) */
#define msg_kpress     msg.b2 /* keypressure(0xA0) */
#define msg_value      msg.b2 /* controlchange(0xB0) */
#define msg_unused     msg.b2 /* programchange(0xC0), channelpressure(0xD0) */
#define msg_msb        msg.b2 /* pitchbend(0xE0) */

/***************************************************************************
 *
 *  MIDI function director array
 *
 *  (x = channel)
 *  0x8x        Note Off
 *  0x9x        Note On                (vel 0 == Note Off)
 *  0xAx        Key Pressure (Aftertouch)
 *  0xBx        Control Change
 *  0xCx        Program Change
 *  0xDx        Channel Pressure (Aftertouch)
 *  0xEx        Pitch Bend Change
 *  0xF0        Sysex
 *  011111sssb  System Common
 *  011110tttb  System Real Time
 *
 *************************************************************************/
void (NEAR PASCAL * synthmidi [8]) (MIDIMSG);

    void (NEAR PASCAL * synthmidi []) () = {
        synthNoteOff,
        synthNoteOn,
        NULL,                   /* key pressure not currently implemented */
        synthControlChange,
        synthProgramChange,
        NULL,                   /* channel pressure not currently implemented */
        synthPitchBend,
        NULL                    /* sysex etc. not currently implemented */
    };

/***************************************************************************

    public functions

***************************************************************************/

/**************************************************************************
 * @doc INTERNAL
 *
 * @func void | synthMidiData | Process a stream of MIDI messages by calling
 *     the appropriate function based on each status byte.  The function deals
 *     with messages spanning buffers, and with invalid data being passed.
 *
 *     In general, the function steps through the buffer, using the current
 *     state in determining what to look for in checking the next data byte.
 *     This may mean looking for a new status byte, or looking for data
 *     associated with a current status whose data has not been completely
 *     read yet.
 *
 *     The key item to determine the current state of message processing is
 *     the "bCurrentLen" global static byte, which indicates the number of
 *     bytes that are needed to complete the current message being processed.
 *     The current message is stored in the local static "bCurrentStatus", not
 *     to be confused with the global static "status", which is the running
 *     status.  The local static "msg" is used to build the current message,
 *     and is static in order to enable messages to cross buffer boundaries.
 *     The local static "bPosition" determines where in the message buffer to
 *     place the next byte of the message, if any.
 *
 *     The first item in the processing loop is a check for the presence of a
 *     real time message, which can occur anywhere in the data stream, and does
 *     not affect the current state (unless it is possibly a reset command).
 *     Real time messages are ignored for now, including the reset command.
 *     After ignoring them, the loop is continued in case that was the last
 *     byte in the buffer.  If the loop was not continued at this point, the
 *     message sending portion would not function correctly, as "bCurrentLen"
 *     and "bCurrentStatus" is not modified by a real time message.
 *
 *     The next loop item checks to determine if a message is currently being
 *     built.  If "bCurrentLen" is zero, no message is being built, and the
 *     next status byte can be retrieved.  At this point, the current message
 *     position is reset, as any new byte will be the first for this new
 *     message.
 *
 *     If the next byte is a system command, as opposed to a channel command,
 *     it must reset the current running status, and extract the message length
 *     from a different message length table (subtracting one for the status
 *     already retrieved).  Even though these messages are eventually ignored,
 *     the actual message buffer is built as normal.  This will enable a
 *     function to be attached to the message function table which would deal
 *     with system messages.  Note that the system message id is placed into
 *     the channel portion of the message, in place of the channel for a normal
 *     message.
 *
 *     If the next byte is not a system command, then it might be either a
 *     new status byte, or a data byte which depends upon the running status.
 *     If it is a new status byte, running status is updated.  If it is not,
 *     and there is not running status, the byte is ignored, and the loop is
 *     continued.  This might be the case when ignoring data after a SYSEX,
 *     or when invalid data occurs.  If a valid status byte is retrieved, or
 *     is already present in the running status, the internal current status
 *     is updated, in case this message spans buffers, and the length of the
 *     message is retrieved from the channel table (subtracting one for the
 *     status byte already retrieved).
 *
 *     At this point, the message may be completely built (i.e., a one byte
 *     message), in which case, it will fall into the message dispatch code.
 *
 *     The next loop item is fallen into if a message is currently being
 *     processed.  It checks the next byte in the buffer to ensure that it
 *     is actually a data byte.  If it is a status byte instead, the current
 *     message is aborted by resetting "bCurrentLen", and the loop is
 *     continued.  Note that running status is not reset in this case.
 *
 *     If however the next byte is valid, it is placed in either the first or
 *     the second data position in the message being built.  The local static
 *     "bPosition" is used to determine which in position to place the data.
 *
 *     The next loop item checks to see if a complete message has been built.
 *     If so, it dispatches the message based on the current command.  It does
 *     not use running status, as that might have been reset for a system
 *     command.  If a function for the particular command is present, the
 *     message is dispatched, else it is ignored.  If the message was not
 *     complete, the next pass through the loop will pick up the next data
 *     byte for the message.
 *
 *     The loop then continues until it is out of data.
 *
 * @parm HPBYTE | lpBuf | Points to a buffer containing the stream of MIDI
 *     data.
 *
 * @parm DWORD | dwLength | Contains the length of the data pointed to by
 *     <p>lpBuf<d>.
 *
 * @rdesc There is no return value.
*************************************************************************/
void NEAR PASCAL synthMidiData(HPBYTE lpBuf, DWORD dwLength)
{
static MIDIMSG msg;
static BYTE bCurrentStatus;
static BYTE bPosition;
BYTE bByte;

    for (; dwLength; dwLength--) {
        bByte = *lpBuf++;
        
        if (bByte >= STATUS_TIMINGCLOCK)
            continue;

        if (!bCurrentLen) {
kludge_city:
            bPosition = 0;
            if (bByte >= STATUS_SYSEX) {
                bCurrentStatus = bByte;
                status = 0;
                bCurrentLen = (BYTE)(SYSLENGTH(bCurrentStatus) - 1);
            }
	    else {
                if (bByte >= STATUS_NOTEOFF)
                    status = bByte;
                else if (!status)
                    continue;
                bCurrentStatus = status;
                bCurrentLen = (BYTE)(MIDILENGTH(status) - 1);
                if (bByte < STATUS_NOTEOFF)
                    goto first_byte;
            }
            msg_ch = FILTERSTATUS(bCurrentStatus);
        }
	else {
            if (bByte >= STATUS_NOTEOFF)
                goto kludge_city;
            if (!bPosition) {
first_byte:
                bPosition++;
                msg.b1 = bByte;
            }
	    else
                msg.b2 = bByte;
            bCurrentLen--;
        }
        if (!bCurrentLen) {
            bByte = (BYTE)((bCurrentStatus >> 4) & 0x07);
            if (*synthmidi[bByte])
                (*synthmidi[bByte]) (msg);
            else
                D1("MIDI message type not supported");
        }
    }
}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthAllNotesOff | Turn any notes off which are playing.
 *
 * @rdesc There is no return value.
 ************************************************************************/
void NEAR PASCAL synthAllNotesOff(void)
{
BYTE voice;
MIDIMSG msg;
    
    for (voice = 0; voice < (BYTE)NUMVOICES; voice++) {
        if (voices[voice].alloc) {
            msg_ch = voices[voice].channel;
            msg_note = voices[voice].note;
            synthNoteOff(msg);
        }
    }
}

/***************************************************************************

    private functions

***************************************************************************/

/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthOctaveReg | Perform octave registration on a message.
 *
 * @parm BYTE | msg_ch | The channel the note is to be played on.
 *
 * @parm BYTE | msg_note | The MIDI note number.
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthOctaveReg(MIDIMSG FAR *pMsg)
{
int  iModNote;
MIDIMSG msg = *pMsg;

    if ((msg_ch == DRUMKITCHANNEL) || (channels[msg_ch].patch > 127))
        return;  /* only affect normal melodic patches */
    
    iModNote = msg_note + patchKeyOffset[channels[msg_ch].patch];
    if ((iModNote < 0) || (iModNote > 127))
        iModNote = msg_note;

    msg_note = (BYTE) iModNote;
    *pMsg = msg;  /* modify what was pointed to */
}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthNoteOn | Turn on a requested note.
 *
 * @parm BYTE | msg_ch | The channel the note is to be played on.
 *
 * @parm BYTE | msg_note | The MIDI note number.
 *
 * @parm BYTE | msg_velocity | The velocity level for the note.
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthNoteOn(MIDIMSG msg)
{
BYTE voice;

    if (msg_velocity == 0) {               /* 0 velocity means note off */
        synthNoteOff(msg);
        return;
    }

    synthOctaveReg(&msg);  /* adjust key # to overcome patch octave diffs */

    /* hack to overcome how quiet this thing is in relation to wave output */
    msg_velocity = loudervol[msg_velocity];

    if (msg_ch == DRUMKITCHANNEL) {       /* drum kit hardwired on channel 15 */
        if ((msg_note < FIRSTDRUMNOTE) || (msg_note > LASTDRUMNOTE))
            return;

        channels[DRUMKITCHANNEL].patch = drumpatch[msg_note - FIRSTDRUMNOTE].patch;
        msg_note = drumpatch[msg_note - FIRSTDRUMNOTE].note;

        if ((voice = FindVoice(msg_note, msg_ch)) != 0xFF)
            NoteOff(voice);
        voice = GetNewVoice(msg_note, msg_ch);
    }

    else {
        voice = FindVoice(msg_note, msg_ch);       /* voice already assigned? */
        if (voice == 0xff)
            voice = GetNewVoice(msg_note, msg_ch); /* if not, get one */
        else
            NoteOff(voice);
    }

    if (voices[voice].volume != msg_velocity) { /* check if it's already set */
        SetVoiceVolume(voice, msg_velocity);
        voices[voice].volume = msg_velocity;
    }
    /* apply any pb for this channel */
    SetVoicePitch(voice, channels[msg_ch].wPitchBend);
    /* adjust for octave reg. */
    NoteOn(voice, msg_note);
}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthNoteOff | Turn off a requested note.
 *
 * @parm BYTE | msg_ch | The channel the note is on.
 *
 * @parm BYTE | msg_note | The MIDI note number.
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthNoteOff(MIDIMSG msg)
{
BYTE voice;
    
    /* adjust key # to overcome patch octave differences */
    synthOctaveReg(&msg); 

    if (msg_ch == DRUMKITCHANNEL) {       /* drum kit hardwired on channel 15 */
        BYTE bPatch;

        if ((msg_note < FIRSTDRUMNOTE) || (msg_note > LASTDRUMNOTE))
            return;

        bPatch = drumpatch[msg_note - FIRSTDRUMNOTE].patch;
        msg_note = drumpatch[msg_note - FIRSTDRUMNOTE].note;

        if ((voice = FindVoice(msg_note, msg_ch)) != 0xFF) {
            if (LOWORD(voices[voice].dwTimeStamp) == bPatch) {
                NoteOff(voice);
                FreeVoice(voice);
            }
        }
        return;
    }

    else {
        voice = FindVoice(msg_note, msg_ch);       /* get the assigned voice */
    }

    if (voice == 0xFF)
        return;

    /* turn the note off */

    if (voices[voice].note) {               /* check if note is playing */
        NoteOff(voice);
        /* return note to pool of notes. */
        FreeVoice(voice);
    }
}

#if 0
/* These functions are commented out because we are not currently supporting
 * channel and key pressure messages in this driver.  Ad Lib had originally
 * interpreted them as volume values, which produces incorrect results.
 * I haven't implemented them because it's not clear from the technical
 * documentation how produce the low-frequency oscillation that is often
 * produced by these messages.  To support the messages, change the
 * entries in the synthmidi array to call these functions again, uncomment
 * these two functions, and define an xSetVoicePressure routine.
 */

static void NEAR PASCAL synthKeyPressure(MIDIMSG msg);
static void NEAR PASCAL synthChannelPressure(MIDIMSG msg);
/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthChannelPressure | Set the pressure for a channel.
 *
 * @parm BYTE | msg_ch | The channel to be set.
 *
 * @parm BYTE | msg_cpress | The pressure level for the channel.
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthChannelPressure(MIDIMSG msg)
{
int i;

    for (i = 0; i < NUMVOICES; i++) {
        if ((voices[i].alloc) && (voices[i].channel == msg_ch))
            xSetVoicePressure(i, msg_cpress);
    }
}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthKeyPressure | Set the key pressure for a note.
 *
 * @parm BYTE | msg_ch | The channel the note is on.
 *
 * @parm BYTE | msg_note | The MIDI note number.
 *
 * @parm BYTE | msg_kpress | The pressure level for the note.
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthKeyPressure(MIDIMSG msg)
{
BYTE voice;

    voice = FindVoice(msg_note, msg_ch);
    if (voice == 0xFF)
        return;

    xSetVoicePressure(voice, msg_kpress);
}
#endif

/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthPitchBend | Bend the pitch.
 *
 * @parm BYTE | msg_ch | The channel to bend the pitch for.
 *
 * @parm BYTE | msg_lsb | LSB of pitch bend.
 *
 * @parm BYTE | msg_msb | MSB of pitch bend.
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthPitchBend(MIDIMSG msg)
{
BYTE i;
WORD wPB = (((WORD)msg_msb) << 7) | msg_lsb;

    /* msb is shifted by 7 because we've redefined the MIDI pitch bend
     * range of 0 - 0x7f7f to 0 - 3fff by concatenating the two
     * 7-bit values in msb and lsb together
     */

    for (i = 0; i < (BYTE)NUMVOICES; i++) {
        if ((voices[i].alloc) && (voices[i].channel == msg_ch))
            SetVoicePitch(i, wPB);
    }
    channels[msg_ch].wPitchBend = wPB; /* remember for subsequent notes */
                                       /* on this channel */
}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api void | synthControlChange | Change a controller value.
 *
 * @parm BYTE | msg_ch | The MIDI channel.
 *
 * @parm BYTE | msg_controller | The controller number to change.
 *
 * @parm BYTE | msg_value | The value to change the controller to.
 *
 * @comm The only controllers supported are 123-127 (all notes off).
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthControlChange(MIDIMSG msg)
{
    if (msg_controller >= 123)
        synthAllNotesOff();
}

/**************************************************************************
 *  @doc INTERNAL
 *
 *  @api void | synthProgramChange | Change a channel patch assignment.
 *
 *  @parm BYTE | msg_ch | The channel the patch is to apply to.
 *
 *  @parm BYTE | msg_patch | The new patch number.
 *
 *  @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL synthProgramChange(MIDIMSG msg)
{
BYTE voice;

    /* drum kit hardwired on channel 15, so ignore patch changes there */
    if (msg_ch == DRUMKITCHANNEL)
        return;

    /* turn off any notes on this channel which are currently on */
    for (voice = 0; voice < (BYTE)NUMVOICES; voice++) {
        if ((voices[voice].alloc) && (voices[voice].channel == msg_ch)
             && (voices[voice].note)) {      /* check if note is playing */
                NoteOff(voice);              /* turn note off */
                FreeVoice(voice);            /* return note to pool of notes. */
        }
    }

    /* change the patch for this channel */
    channels[msg_ch].patch = msg_patch;

}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api BYTE | FindVoice | Find the voice a note/channel is using.
 *
 * @parm BYTE | note | The note in use.
 *
 * @parm BYTE | channel | The channel in use.
 *
 * @rdesc There return value is the voice number or 0xFF if no match is found.
 **************************************************************************/
static BYTE NEAR PASCAL FindVoice(BYTE note, BYTE channel)
{
BYTE i;

    if (channel == DRUMKITCHANNEL) {
        i = patches[channels[DRUMKITCHANNEL].patch].percVoice;

        if (voices[i].alloc)
            return i;
    }

    else {
        for (i = 0; i < (BYTE)NUMVOICES; i++) {
            if ((voices[i].alloc) && (voices[i].note == note)
            && (voices[i].channel == channel)) {
                voices[i].dwTimeStamp = dwAge++;
                return i;
            }
        }
    }

    return 0xFF;                          /* not found */
}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api BYTE | GetNewVoice | Find a new voice to play a note.  Uses an LRU
 *     algorithm to steal voices.  The timestamp is set at allocation time
 *     incremented in <f FindVoice>.
 *
 * @parm BYTE | note | The note we want to play.
 *
 * @parm BYTE | channel | The channel we want to play it on.
 *
 * @rdesc Returns the voice number.
 **************************************************************************/
static BYTE NEAR PASCAL GetNewVoice(BYTE note, BYTE channel) 
{
BYTE  i;
BYTE  voice;
BYTE  patch;
BYTE  bVoiceToUse;
DWORD dwOldestTime = dwAge;                     /* init to current "time" */

    /* get the patch in use for this channel */
    patch = channels[channel].patch;

    if (patches[patch].mode) {                  /* it's a percussive patch */
        voice = patches[patch].percVoice;       /* use fixed percussion voice */
        voices[voice].alloc = TRUE;
        voices[voice].note = note;
        voices[voice].channel = channel;
        voices[voice].dwTimeStamp = MAKELONG(patch, 0);
        SetVoiceTimbre(voice, &patches[patch].op0);  /* set the timbre */
        return voice;
    }

    /* find a free melodic voice to use */
    for (i = 0; i < (BYTE)NUMMELODIC; i++) {  /* it's a melodic patch */
        if (!voices[i].alloc) {
            bVoiceToUse = i;                  /* grab first unused one */
            break;
        }
        else if (voices[i].dwTimeStamp < dwOldestTime) {
                dwOldestTime = voices[i].dwTimeStamp;
                bVoiceToUse = i;              /* remember oldest one to steal */
        }
    }

    /* at this point, we have either found an unused voice, */
    /* or have found the oldest one among a totally used voice bank */

    if (voices[bVoiceToUse].alloc)            /* if we stole it, turn it off */
        NoteOff(bVoiceToUse);

    voices[bVoiceToUse].alloc = 1;
    voices[bVoiceToUse].note = note;
    voices[bVoiceToUse].channel = channel;
    voices[bVoiceToUse].dwTimeStamp = dwAge++;

    SetVoiceTimbre(bVoiceToUse, &patches[patch].op0);   /* set the timbre */

    return bVoiceToUse;
}

/**************************************************************************
 * @doc INTERNAL
 *
 * @api BYTE | FreeVoice | Free a voice we have been using.
 *
 * @parm BYTE | voice | The voice to free.
 *
 * @rdesc There is no return value.
 **************************************************************************/
static void NEAR PASCAL FreeVoice(BYTE voice) 
{
    voices[voice].alloc = 0;
}
Detected encoding: ASCII (7 bit)2