This section describes how MIDAS Digital Audio System uses the MS-DOS system timer, and how to install user timer callbacks. This information is not relevant in other operating systems.
To be able to play music in the background in MS-DOS, and to keep proper tempo with all sound cards, MIDAS needs to use the system timer (IRQ 0, interrupt 8) for music playback. Because of this, user programs may not access the timer directly, as this would cause conflicts with MIDAS music playback. As the system timer is often used for controlling program speed, and running some tasks in the background, MIDAS provides a separate user timer callback for these purposes. This callback should used instead of accessing the timer hardware directly.
The callbacks can be ran at any speed, and can optionally be synchronized to display refresh.
Basic MIDAS timer callback usage is very simple: Simply call MIDASsetTimerCallbacks, passing it the desired callback rate and pointers to the callback functions. After that, the callback functions will be called periodically until MIDASremoveTimerCallbacks is called. MIDASsetTimerCallbacks takes the callback rate in milliHertz (one thousandth of a Hertz) units, so to get a callback that will be called 70 times per second, set the rate to 70000. The callback functions need to use MIDAS_CALL calling convention (__cdecl for Watcom C, empty for DJGPP), take no arguments and return no value.
For example, this code snippet will use the timer to increment a variable tickCount 100 times per second:
void MIDAS_CALL TimerCallback(void) { tickCount++; } ... MIDASinit(); ... MIDASsetTimerCallbacks(100000, FALSE, &TimerCallback, NULL, NULL); ...
The MIDAS timer supports synchronizing the user callbacks to display refresh under some circumstances. Display synchronization does not work when running under Windows 95 and similar systems, and may fail in SVGA modes with many SVGA cards. As display synchronization is somewhat unreliable, and also more difficult to use than normal callbacks, using it is not recommended if a normal callback is sufficient.
To synchronize the timer callbacks to screen refresh, use the following procedure:
1. BEFORE MIDAS Digital Audio System is initialized, set up the display mode you are going to use, and get the display refresh rate corresponding to that mode using MIDASgetDisplayRefreshRate. If your application uses several different display modes, you will need to set up each of them in turn and read the refresh rate for each separately. If MIDASgetDisplayRefreshRate returns zero, it was unable to determine the display refresh rate, and you should use some default value instead. Display refresh rates, like timer callback rates, are specified in milliHertz (1000*Hz), so 70Hz refresh rate becomes 70000.
2. Initialize MIDAS Digital Audio System etc.
3. Set up the display mode
4. Start the timer callbacks by calling MIDASsetTimerCallbacks. The first argument is the refresh rate from step 1, second argument should be set to TRUE (to enable display synchronization), and the remaining three arguments are pointers to the preVR, immVR and inVR callback functions (see descriptions below).
5. When the callbacks are no longer used, remove them by calling MIDASremoveTimerCallbacks.
When you are changing display modes, you must first remove the existing timer callbacks, change the display modes, and restart the callbacks with the correct rate. Please note that synchronizing the timer to the screen update takes a while, and as the timer is disabled for that time it may introduce breaks in the music. Therefore we suggest you handle the timer screen synchronization before you start playing music.
If MIDAS is unable to synchronize the timer to display refresh, it will simply run the callbacks like normal user callbacks. Therefore there is no guarantee that the callbacks will actually get synchronized to display, and your program should not depend on that. For example, you should not use the timer callbacks for double buffering the display, as preVR might not be called at the correct time -- use triple buffering instead to prevent possible flicker.
MIDASsetTimerCallbacks takes as its three last arguments three pointers to the timer callback functions. These functions are:
preVR() -- if the callbacks are synchronized to display refresh, this function is called immediately before Vertical Retrace starts. It should be kept as short as possible, and can be used for changing a couple of hardware registers (in particular the display start address) or updating a counter.
immVR() -- if the callbacks are synchronized to display refresh, this function is called immediately after Vertical Retrace starts. As preVR(), it should be kept as short as possible.
inVR() -- if the callbacks are synchronized to display refresh, this function is called some time later during Vertical Retrace. It can take a longer time than the two previous functions, and can be used, for example, for setting the VGA palette. It should not take longer than a quarter of the time between callbacks though.
If the callbacks are not synchronized to display refresh, the functions are simply called one after another. The same timing requirements still hold though.
DOS programs typically control their framerate by checking the Vertical Retrace from the VGA hardware. If MIDAS is playing music in the background, this is not a good idea, since the music player can cause the program to miss retraces. Instead, the program should set up a timer callback, possibly synchronize it to display refresh, use that callback to increment a counter, and wait until the counter changes.
For example:
volatile unsigned frameCount; ... void MIDAS_CALL PreVR(void) { frameCount++; } ... MIDASsetTimerCallbacks(70000, FALSE, &PreVR, NULL, NULL); ... while ( !quit ) { DoFrame(); oldCount = frameCount; while ( frameCount == oldCount ); }
Note that frameCount needs to be declared volatile, otherwise the compiler might optimize the wait completely away.
A similar strategy can be used to keep the program run at the same speed on different computers. You can use the frame counter to determine how many frames rendering the display takes, and run the movements for all those frames before rendering the next display.