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

