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);