/* dostimer.c
*
* MIDAS Sound System timer for MS-DOS
*
* $Id: dostimer.c,v 1.6 1997/01/16 18:41:59 pekangas Exp $
*
* Copyright 1996,1997 Housemarque Inc.
*
* This file is part of the MIDAS Sound System, and may only be
* used, modified and distributed under the terms of the MIDAS
* Sound System license, LICENSE.TXT. By continuing to use,
* modify or distribute this file you indicate that you have
* read the license and understand and accept it fully.
*/
#include <dos.h>
#include "lang.h"
#include "mtypes.h"
#include "errors.h"
#include "sdevice.h"
#include "timer.h"
#include "mglobals.h"
RCSID(const char *dostimer_rcsid = "$Id: dostimer.c,v 1.6 1997/01/16 18:41:59 pekangas Exp $";)
//#define TIMERBORDERS
/* Time between to screen interrupts is 96.5% of total frame time - the
interrupt comes somewhat before the Vertical Retrace actually starts: */
#define FRAMETIME 965
/* Timer mode: */
#define TIMERMODE 0x30
/* Maximum # of music players: */
#define MAXPLAYERS 16
static void (__interrupt __far *oldTimer)();
static volatile int tmrState;
static volatile int playSD;
static volatile long sysTmrCount; /* system timer counter */
static volatile long playTmrCount; /* initial player timer count */
static volatile long playCount; /* player timer count */
static volatile SoundDevice *sdev; /* current SD */
static int CALLING (*musicPlayers[MAXPLAYERS])(void); /* music players */
static volatile int playSD; /* 1 if sound should be played */
static volatile int plTimer; /* 1 if player-timer is active */
static volatile int plError; /* music playing error code */
static volatile long scrCount; /* screen timer counter */
static volatile long scrTmrCount; /* initial value for screen timer */
static volatile long scrPVCount; /* count before retrace */
static void CALLING (*preVR)(); /* pre-VR function */
static void CALLING (*immVR)(); /* immVR() */
static void CALLING (*inVR)(); /* inVR() */
static volatile int scrSync; /* is timer synchronized to screen? */
static volatile int scrTimer; /* 1 if screen-timer is active */
static volatile int scrPlayer; /* synchronize player to screen? */
static volatile int tmrState; /* timer state */
/****************************************************************************\
* enum tmrStates
* --------------
* Description: Possible timer states
\****************************************************************************/
enum tmrStates
{
tmrSystem = 0, /* system timer */
tmrPlayer, /* music player */
tmrScreen /* display synchronized timer */
};
void outp(unsigned port, unsigned value);
#pragma aux outp = \
"out dx,al" \
parm [edx] [eax] \
modify exact [];
unsigned inp(unsigned port);
#pragma aux inp = \
"xor eax,eax" \
"in al,dx" \
parm [edx] \
value [eax] \
modify exact [eax];
void DoSTI(void);
#pragma aux DoSTI = "sti" modify exact[];
void DoCLI(void);
#pragma aux DoCLI = "cli" modify exact[];
void SendEOI(void);
#pragma aux SendEOI = \
" mov al,20h" \
" out 20h,al" \
modify exact[eax];
/* Set border color: */
#ifdef TIMERBORDERS
void SetBorder(int color);
#pragma aux SetBorder = \
" mov dx,03DAh" \
" in al,dx" \
" mov dx,03C0h" \
" mov al,31h" \
" out dx,al" \
" mov al,bl" \
" out dx,al" \
parm [ebx] \
modify exact [eax edx];
#else
#define SetBorder(x)
#endif
/* Wait for next Vertical Retrace: */
void WaitNextVR(void);
#pragma aux WaitNextVR = \
" mov dx,03DAh" \
"n: in al,dx" \
" test al,8" \
" jnz n" \
"v: in al,dx" \
" test al,8" \
" jz v" \
modify exact [eax edx];
/* Wait for Vertical Retrace: */
void WaitVR(void);
#pragma aux WaitVR = \
" mov dx,03DAh" \
"w: in al,dx" \
" test al,8" \
" jz w" \
modify exact [eax edx];
/* Wait for no Vertical Retrace: */
void WaitNoVR(void);
#pragma aux WaitNoVR = \
" mov dx,03DAh" \
"w: in al,dx" \
" test al,8" \
" jnz w" \
modify exact [eax edx];
/* Set DS to ES: */
void SetDStoES(void);
#pragma aux SetDStoES = \
" mov ax,ds" \
" mov es,ax" \
modify exact [eax];
/****************************************************************************\
*
* Function: void SetCount(unsigned count)
*
* Description: Sets new timer count
*
* Input: unsigned count new timer count
*
\****************************************************************************/
static void SetCount(unsigned count)
{
outp(0x43, TIMERMODE);
outp(0x40, count & 0xFF);
outp(0x40, (count >> 8) & 0xFF);
}
/****************************************************************************\
*
* Function: void NextTimer(void)
*
* Description: Sets everything up for the next timer interrupt
*
\****************************************************************************/
static void NextTimer(void)
{
if ( scrSync )
{
/* Timer is synchronized to screen: */
if ( playSD && mSyncScreen )
{
if ( playCount < scrCount )
{
if ( playCount < 10 )
playCount = 10;
/* Player interrupt, please */
tmrState = tmrPlayer;
SetCount(playCount);
return;
}
}
if ( scrCount < 10 )
scrCount = 10;
/* Screen interrupt, please: */
tmrState = tmrScreen;
SetCount(scrCount);
return;
}
if ( playSD )
{
if ( playCount < 10 )
playCount = 10;
/* Player interrupt: */
tmrState = tmrPlayer;
SetCount(playCount);
return;
}
/* System timer: */
tmrState = tmrSystem;
SetCount(0);
}
void CallDOSInt(void);
#pragma aux CallDOSInt = \
"pushfd" \
"call fword oldTimer" \
modify exact [];
/****************************************************************************\
*
* Function: void CheckSystemTimer(void)
*
* Description: Calls the system timer if necessary, send EOI otherwise
*
\****************************************************************************/
void CheckSystemTimer(void)
{
DoSTI();
if ( sysTmrCount < 0x10000 )
{
SendEOI();
return;
}
while ( sysTmrCount >= 0x10000 )
{
sysTmrCount -= 0x10000;
CallDOSInt();
}
}
/****************************************************************************\
*
* Function: void PollMIDAS(void)
*
* Description: Polls MIDAS
*
\****************************************************************************/
void PollMIDAS(void)
{
static int callMP;
int i;
if ( (playSD) && (plError == OK) )
{
/* Prepare SD for playing: */
if ( (plError = sdev->StartPlay()) != OK )
{
plTimer = 0;
return;
}
do
{
/* Poll Sound Device: */
if ( (plError = sdev->Play(&callMP)) != OK )
{
plTimer = 0;
return;
}
if ( callMP )
{
/* Call all music players: */
for ( i = 0; i < MAXPLAYERS; i++ )
{
if ( musicPlayers[i] != NULL )
{
if ( (plError = (*musicPlayers[i])()) != OK )
{
plTimer = 0;
return;
}
}
}
}
} while ( callMP && (sdev->tempoPoll == 0) );
}
}
/****************************************************************************\
*
* Function: void __interrupt __far tmrISR(void)
*
* Description: The timer ISR
*
\****************************************************************************/
void __interrupt __far tmrISR(void)
{
int chainDOS = 1;
long oldPlayCount;
/* Set DS to ES as well: (stupid Watcom doesn't to this, and then assumes
ES is valid) */
SetDStoES();
SetBorder(9);
switch ( tmrState )
{
case tmrScreen:
SetBorder(15);
DoCLI(); /* Disable interrupts here */
if ( scrTimer )
{
/* PANIC, screen timer still active! */
playCount -= scrCount + scrPVCount;
sysTmrCount += scrCount + scrPVCount;
scrCount = scrTmrCount;
NextTimer();
SendEOI();
DoSTI();
chainDOS = 0;
break;
}
if ( scrSync )
{
SetBorder(14);
scrTimer = 1;
if ( mSyncScreen )
WaitNoVR();
if ( preVR != NULL )
(*preVR)();
/* Update timer counters: */
sysTmrCount += scrCount + scrPVCount;
if ( scrPlayer )
playCount = playTmrCount;
else
playCount -= scrCount + scrPVCount;
scrCount = scrTmrCount;
if ( mSyncScreen )
WaitVR();
if ( immVR != NULL )
(*immVR)();
NextTimer();
CheckSystemTimer();
scrTimer = 0;
/* If not synchronizing to screen we need to poll MIDAS
here: */
if ( (!mSyncScreen) && playSD )
{
if ( sdev->tempoPoll )
{
while ( playCount < 0 )
{
PollMIDAS();
playCount += playTmrCount;
}
}
else
PollMIDAS();
}
if ( inVR != NULL )
(*inVR)();
chainDOS = 0;
break;
}
else
{
/* We shouldn't really be here - check system timer and
exit */
CheckSystemTimer();
chainDOS = 0;
break;
}
break;
case tmrPlayer:
if ( plTimer )
{
/* Player timer active - panic! */
scrCount -= playCount;
sysTmrCount += playCount;
playCount = playTmrCount;
NextTimer();
SendEOI();
DoSTI();
chainDOS = 0;
break;
}
plTimer = 1;
scrCount -= playCount;
sysTmrCount += playCount;
if ( scrPlayer )
playCount = 0xFFFF;
else
playCount = playTmrCount;
NextTimer();
CheckSystemTimer();
chainDOS = 0;
oldPlayCount = playTmrCount;
PollMIDAS();
/* Check if player timer rate has been updated: */
if ( (sdev->tempoPoll == 1) && (playTmrCount != oldPlayCount))
{
playCount = playTmrCount;
if ( tmrState == tmrPlayer )
NextTimer();
}
plTimer = 0;
chainDOS = 0;
break;
case tmrSystem:
default:
/* The system timer - set rate to 18.2Hz and chain to DOS: */
SetCount(0);
chainDOS = 1;
break;
}
SetBorder(0);
if ( chainDOS )
_chain_intr(oldTimer);
}
/****************************************************************************\
*
* Function: int tmrInit(void);
*
* Description: Initializes the timer
*
* Returns: MIDAS error code
*
\****************************************************************************/
int CALLING tmrInit(void)
{
tmrState = tmrSystem; /* system timer only */
playSD = 0;
/* Get old timer interrupt and set our own: */
oldTimer = _dos_getvect(8);
_dos_setvect(8, tmrISR);
/* Restart timer at 18.2Hz: */
// SetCount(0);
return OK;
}
/****************************************************************************\
*
* Function: int tmrClose(void);
*
* Description: Uninitializes the timer.
*
* Returns: MIDAS error code
*
\****************************************************************************/
int CALLING tmrClose(void)
{
/* Set DOS default timer mode and 18.2Hz rate: */
outp(0x43, 0x36);
outp(0x40, 0);
outp(0x40, 0);
/* Restore old interrupt vector: */
_dos_setvect(8, oldTimer);
/* Set DOS default timer mode and 18.2Hz rate: */
outp(0x43, 0x36);
outp(0x40, 0);
outp(0x40, 0);
return OK;
}
/****************************************************************************\
*
* Function: int tmrGetScrSync(unsigned *scrSync);
*
* Description: Calculates the screen synchronization value for timer
*
* Input: unsigned *scrSync pointer to screen synchronization
* value
*
* Returns: MIDAS error code.
* Screen syncronization value used with tmrSyncScr() is stored
* in *scrSync.
*
\****************************************************************************/
int CALLING tmrGetScrSync(unsigned *scrSync)
{
int failCount = 0, success = 0;
long count1, count2, prevCount = 0, count;
if ( !mSyncScreen )
{
/* No sync - just return the default frame rate: */
*scrSync = 119318000 / mDefaultFramerate;
return OK;
}
DoCLI();
while ( (failCount < 4) && (success != 1) )
{
WaitNextVR();
outp(0x43, 0x36);
outp(0x40, 0);
outp(0x40, 0);
WaitNextVR();
outp(0x43, 0);
count1 = inp(0x40);
count1 |= (inp(0x40)) << 8;
count1 = 0x10000-count1;
WaitNextVR();
outp(0x43, 0x36);
outp(0x40, 0);
outp(0x40, 0);
WaitNextVR();
outp(0x43, 0);
count2 = inp(0x40);
count2 |= (inp(0x40)) << 8;
count2 = 0x10000-count2;
if ( ((count2 - count1) > 2) || ((count2 - count1) < -2) )
{
failCount++;
}
else
{
count = count1 >> 1;
if ( ((prevCount - count) <= 2) && ((prevCount - count) >= -2) )
success = 1;
else
{
prevCount = count;
failCount++;
}
}
}
if ( success )
{
/* We got the synchronization value! */
*scrSync = count;
}
else
{
/* Couldn't synchronize - turn sync off and return default
frame rate: */
mSyncScreen = 0;
*scrSync = 119318000 / mDefaultFramerate;
}
DoSTI();
return OK;
}
/****************************************************************************\
*
* Function: int tmrPlaySD(SoundDevice *SD);
*
* Description: Starts playing sound with a Sound Device ie. calling its
* Play() function in the update rate, which is set to
* 50Hz.
*
* Input: SoundDevice *SD Sound Device that will be used
*
* Returns: MIDAS error code.
*
\****************************************************************************/
int CALLING tmrPlaySD(SoundDevice *SD)
{
int i;
sdev = SD;
/* Reset all music player pointers to NULL: */
for ( i = 0; i < MAXPLAYERS; i++ )
musicPlayers[i] = NULL;
if ( !sdev->tempoPoll )
{
if ( scrSync && mSyncScreen )
{
/* We are synchronizing to screen and have a non-tempoPoll SD -
call the music player a 1/4th of a frame after the retrace: */
scrPlayer = 1;
playTmrCount = playCount = scrTmrCount / 4;
}
else
{
/* No tempo-polling and no screen-sync - poll at 100Hz: */
playTmrCount = playCount = 1193180 / 100;
scrPlayer = 0;
}
}
else
{
/* Tempo-polling Sound Device - set initially to 50Hz: */
playTmrCount = playCount = 1193180 / 50;
scrPlayer = 0;
}
plTimer = 0;
plError = 0;
/* Start playing: */
DoCLI();
if ( tmrState == tmrSystem )
{
tmrState = tmrPlayer;
SetCount(playCount);
sysTmrCount = 0;
}
playSD = 1;
DoSTI();
return OK;
}
/****************************************************************************\
*
* Function: int tmrStopSD(void);
*
* Description: Stops playing sound with the Sound Device.
*
* Returns: MIDAS error code.
*
\****************************************************************************/
int CALLING tmrStopSD(void)
{
DoCLI();
playSD = 0;
if ( !scrSync )
{
/* No screen sync - only system timer now: */
tmrState = tmrSystem;
SetCount(0);
}
DoSTI();
return OK;
}
/****************************************************************************\
*
* Function: int tmrPlayMusic(void *play, int *playerNum);
*
* Description: Starts playing music with the timer.
*
* Input: void *play Pointer to music playing function,
* must return MIDAS error codes
* int *playerNum Pointer to player number, used
* for stopping music
*
* Returns: MIDAS error code. Player number is written to *playerNum.
*
* Notes: There can be a maximum of 16 music players active at the
* same time.
*
\****************************************************************************/
int CALLING tmrPlayMusic(int CALLING (*play)(), int *playerNum)
{
int i;
/* Try to find a free music player: */
for ( i = 0; i < MAXPLAYERS; i++ )
{
if ( musicPlayers[i] == NULL )
break;
}
if ( i >= MAXPLAYERS )
{
/* No free player found: */
ERROR(errOutOfResources, ID_tmrPlayMusic);
return errOutOfResources;
}
/* Free player found - store the pointer and return player ID: */
musicPlayers[i] = play;
*playerNum = i;
return OK;
}
/****************************************************************************\
*
* Function: int tmrStopMusic(int playerNum);
*
* Description: Stops playing music with the timer.
*
* Input: int playerNum Number of player to be stopped.
*
* Returns: MIDAS error code
*
\****************************************************************************/
int CALLING tmrStopMusic(int playerNum)
{
musicPlayers[playerNum] = NULL;
return OK;
}
/****************************************************************************\
*
* Function: int tmrSyncScr(unsigned sync, void (*preVR)(),
* void (*immVR)(), void (*inVR)());
*
* Description: Synchronizes the timer to screen refresh.
*
* Input: unsigned sync Screen synchronization value returned
* by tmrGetScrSync().
* void (*preVR)() Pointer to the routine that will be
* called BEFORE Vertical Retrace
* void (*immVR)() Pointer to the routine that will be
* called immediately after Vertical
* Retrace starts
* void (*inVR)() Pointer to the routine that will be
* called some time during Vertical
* Retrace
*
* Returns: MIDAS error code
*
* Notes: preVR() and immVR() functions must be as short as possible
* and do nothing else than update counters or set some VGA
* registers to avoid timer synchronization problems. inVR()
* can take a longer time and can be used for, for example,
* setting the palette.
*
* Remember to use the correct calling convention for the xxVR()
* routines! (pascal for Pascal programs, cdecl otherwise).
*
\****************************************************************************/
int CALLING tmrSyncScr(unsigned sync, void CALLING (*_preVR)(),
void CALLING (*_immVR)(), void CALLING (*_inVR)())
{
/* We don't want to get disturbed right now: */
DoCLI();
/* Save the function pointers: */
preVR = _preVR;
immVR = _immVR;
inVR = _inVR;
if ( mSyncScreen )
{
scrTmrCount = FRAMETIME * sync / 1000;
scrPVCount = sync - scrTmrCount;
}
else
{
scrTmrCount = sync;
scrPVCount = 0;
}
/* Next interrupt will be screen - synchronize to screen and start: */
scrCount = scrTmrCount;
tmrState = tmrScreen;
scrSync = 1;
WaitNextVR();
SetCount(scrCount);
/* If we are synchronizing to the screen fully and the card is not
tempopolling, start screen sync playing: */
if ( mSyncScreen && (!sdev->tempoPoll) )
{
playCount = playTmrCount = scrTmrCount / 4;
scrPlayer = 1;
}
DoSTI();
return OK;
}
/****************************************************************************\
*
* Function: int tmrStopScrSync(void);
*
* Description: Stops synchronizing the timer to the screen.
*
* Returns: MIDAS error code
*
\****************************************************************************/
int CALLING tmrStopScrSync(void)
{
DoCLI();
if ( scrPlayer )
{
/* Player was synchronized to screen - restart at 100Hz: */
playCount = playTmrCount = 1193180/100;
scrPlayer = 0;
}
scrSync = 0;
scrTimer = 0;
NextTimer();
DoSTI();
return OK;
}
/****************************************************************************\
*
* Function: int tmrSetUpdRate(unsigned updRate);
*
* Description: Sets the timer update rate, ie. the rate at which the music
* playing routines are called
*
* Input: unsigned updRate updating rate, in 100*Hz (5000=50Hz)
*
* Returns: MIDAS error code
*
\****************************************************************************/
int CALLING tmrSetUpdRate(unsigned updRate)
{
/* Only change if tempopolling: */
if ( sdev->tempoPoll )
{
playTmrCount = 119318000 / updRate;
}
return OK;
}
/*
* $Log: dostimer.c,v $
* Revision 1.6 1997/01/16 18:41:59 pekangas
* Changed copyright messages to Housemarque
*
* Revision 1.5 1996/10/13 16:53:07 pekangas
* The timer ISR now sets ES to DS
*
* Revision 1.4 1996/08/06 18:46:09 pekangas
* Removed border colors
*
* Revision 1.3 1996/08/06 16:51:36 pekangas
* Fixed SB and timer conflicts
*
* Revision 1.2 1996/08/04 18:08:21 pekangas
* Fixed a nasty bug in tmrGetScrSync - interrupts were left disabled
*
* Revision 1.1 1996/06/06 19:28:17 pekangas
* Initial revision
*
*/