********************************************************************************
   OS/2 device driver interface to the Gravis Ultrasound family of soundcards 
				6-6-1996
		     Written by Sander van Leeuwen
		  email: s509475@dutiwy.twi.tudelft.nl	
******************* The current driver version is 1.20 *************************
***********************        WARNING         *********************************
 Never close the driver in your exception handler! Only call UltraReleaseAccess!
    Closing it anyway will result in a hanging process that you cannot kill!
********************************************************************************
********************************************************************************
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, 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. 

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

** Changes in version 1.20
	- Memory alloc/free changed
	- added UltraSetLoopStart and UltraGetUltraType
	  
********************************************************************************
			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)
Major version = version / 100;	(1.2 ->  1)
Minor version = version % 100;  (1.2 -> 20)
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. 

	- UltraSizeDram
	- UltraStartTimer
	- UltraStopTimer
	- UltraStartVoice
 To improve efficiency UltraStartVoice stops the voice before starting it.

	- UltraStopVoice
	- UltraSetBalance	
	- UltraSetFrequency	
	- UltraSetLoopMode
	- UltraSetLoopStart		(NEW)
	- 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		
	
	- UltraMemAlloc			(UPDATED)
	- UltraMemFree			(UPDATED)
 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.
 UltraMemAlloc and UltiMODBigMemAlloc are combined in UltraMemAlloc in
 version 1.11 and aren't included anymore.
 
 The new version takes one extra parameter:
	 APIRET UltraMemAlloc(size, location, type)
	    	unsigned long size;
	     	unsigned long *location;
		unsigned char type;	//16 or 8 bit

 UltraMemAlloc is a bit smarter than before. It will allow 8 bits samples (any
 size) to cross a 256 kb boundary. (UltiMODBigMemAlloc used to allow this for
 > 256 kb 8 bits samples)
 The UltraSound Plug 'n Play (Pro) allows 16 bits samples to cross a 256 kb
 boundary, so UltraMemAlloc allows this now. If you try it on a normal GUS (or
 Max), it will return an error.

 ********************************** NEW **************************************
 UltraGetUltraType can be used to determine what kind of Ultrasound model
 the user owns.
         APIRET UltraGetUltraType(int *type)

 Afterwards 'type' can contain any of these values:
    #define IW_MIX_PLAIN        1 /* Analog switches only */
    #define IW_MIX_ICS2101FLR   2 /* 2101 with some left/right flip problems */
    #define IW_MIX_ICS2101      3 /* ICS2101 with analog switches */
    #define IW_MIX_CS4231       4 /* CS4231 with analog switches */
    #define IW_MIX_ACE          5 /* Ace has no mixer capabilities at all */
    #define IW_MIX_CD3          6 /* Same as ICS2101 */
    #define IW_MIX_ICS2102      7 /* ICS2102 with analog switches */
    #define IW_MIX_MAX23        8 /* CS4231 without analog switches */
    #define IW_MIX_CS4231_DB    9 /* RJM: added this */
    #define IW_MIX_PNP	       11 // SvL: UltraSound Plug 'n Play (Pro)

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

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. 
********************************************************************************
********************************************************************************		

