******************************************************************************** OS/2 device driver interface to the Gravis Ultrasound family of soundcards 26-1-1996 Written by Sander van Leeuwen email: s509475@dutiwy.twi.tudelft.nl ******************************************************************************** This document is meant for programmers looking for an easy way to directly access the Gravis Ultrasound hardware, without developing their own device driver. MMPM/2 is good for writing multimedia applications, but it's not really suitable for writing modplayers or trackers (and probably some other software too) for wavetable soundcards. I've developed a device driver, that offers a subset of the routines available in the GUS SDK, to support my OS/2 modplayer UltiMOD. After UltiMOD was released to the public a few months ago, Robert Manley (the author of the GUS MMPM/2 drivers) and I joined forces to release one driver that provides both MMPM/2 support and a special MOD engine for my player. This driver (version 0.7a) was released about three months ago. I've added a few more routines in the driver so you should have enough to write (or port) your own player or tracker. The next section is a short introduction to programming the driver interface. I'm not going into too much detail, because the most calls were ported from the original GUS SDK. If you don't know what the calls do, just look them up in the docs that come with the Gravis SDK. There are a few extra calls, because OS/2 isn't quite like DOS. 8) ** Changes in release 0.85: - fixed bug in ultrasetall (no support for > 14 voices) - fixed ultradownload bug (samples > 64 kb) - added ultimod memory routines - UltraStartVoice & UltraBlockTimerHandlerX changes (see below) - extra warnings added - added UltraVoiceStopped & UltraSetLoopMode - example added that loads all the samples of an s3m file, plays them, tests if the sample is still playing after one second and stops a looping sample if it's still running. ** Changes in version 1.10: - Timo Maier's Pascal version of the toolkit is included (Thanks Timo!) - Fix in driver for samples that cross a 256 kb boundary - Fixed UltraDownload (already fixed in the previous release, but I included an older version (oops)) ******************************************************************************** Source Code ******************************************************************************** This package contains: - struct.h typedefs of structures needed to communicate with the device driver - ultradev.h definitions of all the provided routines - ultradev.c source for communication with the driver - readme.doc this file This code compiles without errors using Watcom C/C++ v10. It will probably work with the other major OS/2 compilers (IBM CSet and Borland C++) as I see no reason why it shouldn't. IMPORTANT NOTE: To compile ultradev.c you should enable structure packing. (not for your entire app, just for ultradev.c) ******************************************************************************** Opening and closing the MMPM/2 Driver. ******************************************************************************** The code below is pretty clear. You need to call UltraGetAccess in order to prevent any other applications (OS/2, Windows or dos) to use the driver. This call return a value (> 0) when for some reason you didn't get exclusive access to the device driver. int InitDevice() { APIRET status; ULONG action; status = DosOpen( "ULTRA1$", &GUSHandle, &action, 0, FILE_NORMAL, FILE_OPEN, OPEN_ACCESS_READWRITE | OPEN_SHARE_DENYNONE | OPEN_FLAGS_WRITE_THROUGH, NULL ); if(status) return(FALSE); if(UltraGetAccess()) { DosClose(GUSHandle); return(FALSE); //not only owner } } To check whether or not the user is running the right version of the driver use the following call: APIRET UltraGetDriverVersion(int *version) On return, the high word of version will contains the major version, the low word the minor version of the driver. This API is only implemented in v0.8 (and will be supported in future releases), so with older drivers, DosDevIOCTl will return 31 (ERR_GEN_FAILURE). To close the driver: UltraReleaseAccess(); //gives other programs access to the GUS DosClose(GUSHandle); ******************************************************************************** Original Gravis SDK calls. ******************************************************************************** The following procdures work exactly as described in Gravis' GUS SDK: The calls use different return values though: - 0 success - > 0 error (returned by DosDevIOCtl) - UltraSetNVoices - UltraEnableOutput - UltraDisableOutput - UltraMemInit You don't need to call UltraMemInit when initializing the GUS. This call is just an easy way to release all reserved memory. - UltraMemAlloc ** see remarks below - UltraMemFree ** see remarks below - UltraSizeDram - UltraStartTimer - UltraStopTimer - UltraStartVoice To improve efficiency UltraStartVoice stops the voice before starting it. - UltraStopVoice - UltraSetBalance - UltraSetFrequency - UltraSetLoopMode - UltraVectorLinearVolume - UltraDownload APIRET UltraVoiceStopped(int *voice); - UltraVoiceStopped returns boolean result in voice (0 - voice running, > 0 - voice stopped) The Peek/Poke calls aren't exactly the same as their SDK equivalents. There's no need to specify the Ultrasound's baseport. APIRET UltraPokeData(long, int); - UltraPokeData UltraPeekData expects (a pointer to) the address, from which you wish to read data, as a parameter. It returns this data in this parameter. APIRET UltraPeekData(int *); - UltraPeekData ** remarks APIRET UltiMODMemFree() //just frees all memory APIRET UltiMODMemAlloc(int size, int *location) APIRET UltiMODBigMemAlloc(int size, int *location) I use these three calls to allocate/free memory in UltiMOD. They are not as flexible as the Gravis SDK substitutes, but are safer to use and faster. UltiMODBigMemAlloc can be used to allocate samples bigger than 256 kb. Do not use 16 bits samples bigger than 256 kb. The ultrasound can't play them. (don't know what happens if you try anyway; most likely lots of noise) ** WARNING: - Do not pass illegal parameters to UltraMemAlloc or UltraMemFree. The Gravis memory alloc routines are not very safe and can cause crashes if they are used improperly. Only use them if the UltiMOD routines are unsuitable for your needs! - Do NOT mix the two different memory routine sets. Doing so might crash OS/2. ******************************************************************************** Writing a multithreaded player ******************************************************************************** When programming for OS/2 you should put time consuming task in a separate thread, so it's a good idea to do this for your plackback procedure(s). Use DosSetPriority to give the thread the highest priority. (to make sure the music will be played smoothly) OS/2 isn't a realtime system, so your thread might not always get a time slice in time. (especially when the system load is quite high) In OS/2 ring 3 applications cannot change the interrupt vector table, so you need the next solution: DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL, 31, 0); while(!Fatal) //StopPlaying changes Fatal to TRUE { UltraBlockTimerHandler1(); DoYourStuff(); //proccess song data .....Update the voices (vol, freq, balance) } //while To start playing you start timer 1 (UltraStartTimer). UltraBlockTimerHandler1 blocks the thread until timer 1 on the GUS produces an interrupt. (UltraBlockTimerHandler2 does the same, but for timer 2) **IMPORTANT**: UltraBlockTimerHandlerX returns after 1/2 second if the timer didn't produce an interrupt. This is a safety precaution. UltraUnBlockAll unblocks the blocked threads, so they can return to your code. You SHOULDN'T use DosKillThread to kill a thread that's blocked in the driver. This doesn't work and will cause your program to hang. Once it's hung, you can't kill it. If you application crashes, it can occur that the thread(s) will stay stuck in- side the driver. OS/2 should unblock threads blocked in a driver, when the application crashes, but most of the time this doesn't work. Don't ask me why. A solution for this is to set an exception handler that unblocks the play thread whenever something bad happens (div/0, protection violation etc) (this is very easy, just look it up in the online OS/2 API reference) UltraSetAll is a way to update the balance, frequency & volume settings for a voice in one DevIOCTL call. When you need to update these three settings for a voice, this call saves a lot of time. ******************************************************************************** ******************************************************************************** As you might have noticed I've removed a few of the DevIOCTL calls in ultradev.c. I use these in my modplayer to improve the efficiency. These routines are very dependant on my channel structures and overal program structure so they aren't usefull to you. You shouldn't call them though. At least two of them will crash you computer. (trap D in the driver) Don't tell me I didn't warn you. If you have any suggestions, bug reports or questions, feel free to mail me. I'd also appreciate mails from people using it, so I can decide whether it's useful to continue enhancing the interface. Enjoy your programming adventure in the world of REAL operating systems. Sander van Leeuwen email: s509475@dutiwy.twi.tudelft.nl ******************************************************************************** Standard Disclaimer ******************************************************************************** This code is provided as is. The author (Sander van Leeuwen) isn't responsible for any damage that may result from using this code. So don't mail me with claims about damaged ears/stereo's or crashed computers. ******************************************************************************** ********************************************************************************