
TITLE:      Advanced Gravis Tech Note #19
AREA:       PROGRAMMING
DATE:       May 19, 1993
KEY WORDS:  ULTRASOUND PROGRAMMING MCI PATCH CACHE

SUBJECT: Implementation of Patch Caching for the Gravis UltraSound

The following describes the specific implementation of patch caching in
the current release of the Ultrasound drivers for Windows 3.1.

The basic idea of patch caching involves setting up a 128 element WORD
array whose elements correspond to each of 128 patches, and setting bits
in each WORD to indicate which MIDI channels use a specific patch.  The
midiOutCachePatches call sends the driver a pointer to this array, along
with a flag indicating the action to be taken.  The midiOutCacheDrumPatches
call is used in a similar fashion to request that drum patches be cached.

Two things conspire to make the implementation and use of patch caching more
difficult than it should be.  First, the media player app that ships with
MS Windows incorrectly responds to the return value MMSYSERR_NOMEM, when
it calls midiOutCachePatches with the MIDI_CACHE_BESTFIT flag set.  A direct
reading of the documentation indicates that _BESTFIT should cause the driver
to attempt to load the specified patches, and if it is not possible to fit
all of the patches into memory, to change the PATCHARRAY to reflect which
patches were cached, and return MMSYSERR_NOMEM.  Unfortunately, if the driver
responds according to this description, the media player decides that no
midi devices are present, and puts up an ugly message box to that effect!

This forces a driver, including the Ultrasound, to always respond with
MMSYSERR_NOERROR in order to have the media player proceed.  Fortunately,
media player does not complain when the PATCHARRAY is changed to reflect
which patches were loaded, so another app using this function can do a
compare on a copy of the PATCHARRAY sent with that returned to determine the
current state.  If midiOutCachePatches is called with MIDI_CACHE_ALL set,
the driver does respond with MMSYSERR_NOMEM according to spec.

This might lead the alert victim, er, developer, to say, "I'll just use
_CACHE_ALL, check the return value, then use _BESTFIT to do it for real.",
in order to get around the problem of maintaining a separate copy of the
PATCHARRAY.
Well, there are a couple of gotchas lurking down that route.  First, the
driver is supposed to clear the PATCHARRAY if _CACHE_ALL fails, which it
does.  This means you still need that duplicate copy of the PATCHARRAY, in
some form or the other.  Also, with the original patches released with the
board, there was no single number giving the total size of the patch in the
first part of the header. This meant that if an app does a _CACHE_ALL to
avoid doing a compare on the array, the driver had to essentially attempt to
load everthing before determining it had run out of memory(bummer, huh?)!
The latest patches have that precious number in the first header, so that
part of the problem can be remedied.

The second problem is caused by the current implementation of the Ultrasound
"firmware".  There is no way currently to selectively delete and replace a
single patch in Ultrasound patch memory.  This fact, coupled with the media
player's behaviour, has forced a particular implementation of the basic
cache patch messages.
Specifically, the media player ALWAYS calls midiOutCachePatches prior to
midiOutCacheDrumPatches.  To accomodate this sequence, the Ultrasound driver
clears all of patch memory on receiving _CACHEPATCHES, but does NOT clear
patch memory on receiving _CACHEDRUMPATCHES. Patch memory is not cleared
when the device is closed, however, so a subsequent call to cache patches
with _exactly_ the same PATCHARRAY does not cause the driver to clear memory
and reload the patches.
I highlighted _exactly_, since the driver will flush memory if there is any
mismatch.  It would seem smarter to accept a subset of what's in memory as a
good reason not to flush, but then along comes a _CACHEDRUM and there's
suddenly not enough memory for the drums, when really there should've been.


What happens when MIDI_UNCACHE is set?  If the message is _CACHEPATCHES, all
of patch memory is cleared.  The local state of the patches, both melodic and
drums, is updated such that a subsequent call with MIDI_CACHE_QUERY reveals
no patches are currently cached.  If MIDI_UNCACHE is set and _CACHEDRUMPATCHES
is sent, ONLY the drum memory and KEYARRAY is cleared.  What is implied is
that, using the standard cache patch messages, drum patch memory can be
cleared and reloaded without affecting melodic patch memory, but not vice
versa.  Drum memory starts at the end of melodic memory.

A custom pair of cache patch messages has been implemented to accomodate
Howling Dog's Power Chords application.  The implementation is basically a
mirror of the standard messages, in that the drums have priority over the
melodics.  Also, there was no need to propagate the workaround for media
player, so MMSYSERR_NOMEM is properly returned in a low memory situation.
Details on the use of these messages is available if requested.  The type of
app that would benefit from their use would be one that wants a drum set
always present, and relatively static, and expects most patch changes to be
melodic.

Here are the manufacturer and product id's needed to identify the
Ultrasound driver:

#define MM_GRAVIS               34
#define MM_ULTRASND_WAVEOUT     13
#define MM_ULTRASND_WAVEIN      14
#define MM_ULTRASND_MIDIOUT     15
#define MM_ULTRASND_AUX         16
#define MM_ULTRASND_MIDISYNTH   17
#define MM_ULTRASND_MIDIIN      18


Code Snippet for getting patch memory available...

#define MODM_QUERYMEM   1000

    case IDM_MEMAVAIL:
if (midiOutOpen (&hMidiOut, (UINT) iUltraMidiID, NULL, 0L, 0L))
{
    MessageBeep (MB_ICONEXCLAMATION) ;
    MessageBox (hWnd, "Cannot open Ultrasound Device",
    szAppName, MB_OK | MB_ICONEXCLAMATION) ;
}
else
{
    midiOutMessage(hMidiOut, MODM_QUERYMEM,
                  (DWORD) ((LPLONG) &lnMemAvail), 0L);
    midiOutClose (hMidiOut);
    wsprintf(S,"Patch Memory Availiable: %ld Bytes\r\n",
             lnMemAvail);
    MessageBox (hWnd, S, szAppName,
                MB_ICONINFORMATION | MB_OK) ;
}

return 0;


Mixer programming info...

    To Set the mic, line, and output enable, OR on the appropriate bits and
    send an AUXDM_MIXCTL message to the aux driver...

    #define AUXDM_MIXCTL            70  // Driver Specific Message
    #define SELECT_MIC      0x01L
    #define SELECT_LINE     0x02L
    #define ENABLE_OUT      0x04L
    #define GET_CONTROL     0L
    #define SET_CONTROL     1L


    dwInpMask |= ENABLE_OUT; // etc...

    auxOutMessage(iAuxID, AUXDM_MIXCTL, dwInpMask, SET_CONTROL);

    To get the current state, do the following...

    dwInpMask = auxOutMessage(iAuxID, AUXDM_MIXCTL, dwInpMask, GET_CONTROL);
