361 lines
10 KiB
C
361 lines
10 KiB
C
|
/*****************************************************************************
|
||
|
microclk.c
|
||
|
|
||
|
Micro-ClockWork for MIDI subsystem
|
||
|
|
||
|
Copyright (c) 1993-1999 Microsoft Corporation
|
||
|
|
||
|
*****************************************************************************/
|
||
|
|
||
|
#define INCL_WINMM
|
||
|
#include "winmmi.h"
|
||
|
#include "muldiv32.h"
|
||
|
|
||
|
//#define STRICT
|
||
|
//#include <windows.h>
|
||
|
//#include <windowsx.h>
|
||
|
//#include "mmsystem.h"
|
||
|
//#include "mmddk.h"
|
||
|
//#include "mmsysi.h"
|
||
|
//#include "debug.h"
|
||
|
|
||
|
//
|
||
|
// This stuff needs to be do-able from inside a callback.
|
||
|
//
|
||
|
#ifndef WIN32
|
||
|
#pragma alloc_text(FIXMIDI, clockSetRate)
|
||
|
#pragma alloc_text(FIXMIDI, clockTime)
|
||
|
#pragma alloc_text(FIXMIDI, clockOffsetTo)
|
||
|
#endif
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc INTERNAL CLOCK
|
||
|
*
|
||
|
* @func void | clockInit | This function initializes a clock for the first
|
||
|
* time. It prepares the clock for use without actually starting it.
|
||
|
*
|
||
|
* @parm PCLOCK | pclock | The clock to initialize.
|
||
|
*
|
||
|
* @parm MILLISECS | msPrev | The number of milliseconds that have passed
|
||
|
* up to the time when the clock is started. This option is provided so
|
||
|
* that a clock may be initialized and started in the middle of a stream
|
||
|
* without actually running to that point. Normally, this will be zero.
|
||
|
*
|
||
|
* @parm TICKS | tkPrev | The number of ticks that have elapsed up to the
|
||
|
* next time the clock starts. This should specify the same instant in
|
||
|
* time as msPrev.
|
||
|
*
|
||
|
* @comm The clock's numerator and divisor will be set to 1 indicating that
|
||
|
* the clock will run in milliseconds. Use clockSetRate before starting the
|
||
|
* clock for the first time if this is not the desired rate.
|
||
|
*
|
||
|
***************************************************************************/
|
||
|
|
||
|
void FAR PASCAL clockInit
|
||
|
(
|
||
|
PCLOCK pclock,
|
||
|
MILLISECS msPrev,
|
||
|
TICKS tkPrev,
|
||
|
CLK_TIMEBASE fnTimebase
|
||
|
)
|
||
|
{
|
||
|
// dprintf1(( "clockInit(%04X) %lums %lutk", pclock, msPrev, tkPrev));
|
||
|
|
||
|
pclock->msPrev = msPrev;
|
||
|
pclock->tkPrev = tkPrev;
|
||
|
pclock->dwNum = 1;
|
||
|
pclock->dwDenom = 1;
|
||
|
pclock->dwState = CLK_CS_PAUSED;
|
||
|
pclock->msT0 = 0;
|
||
|
pclock->fnTimebase = fnTimebase;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc INTERNAL CLOCK
|
||
|
*
|
||
|
* @func void | clockSetRate | This functions sets a new rate for the clock.
|
||
|
*
|
||
|
* @parm PCLOCK | pclock | The clock to set the rate.
|
||
|
*
|
||
|
* @parm TICKS | tkWhen | This parameter specifies the absolute tick
|
||
|
* time at which the rate change happened. This must be at or before the
|
||
|
* current tick; you cannot schedule a pending rate change.
|
||
|
* @flag CLK_TK_NOW | Specify this flag if you want the rate change to
|
||
|
* happen now (this will be the time the clock was paused if it is paused
|
||
|
* now).
|
||
|
*
|
||
|
* @parm DWORD | dwNum | Specifies the new numerator for converting
|
||
|
* milliseconds to ticks.
|
||
|
*
|
||
|
* @parm DWORD | dwDenom | Specifies the new denominator for converting
|
||
|
* milliseconds to ticks.
|
||
|
*
|
||
|
* @comm The clock's state will not be changed by this call; if it is
|
||
|
* paused, it will stay paused.
|
||
|
*
|
||
|
***************************************************************************/
|
||
|
|
||
|
void FAR PASCAL clockSetRate
|
||
|
(
|
||
|
PCLOCK pclock,
|
||
|
TICKS tkWhen,
|
||
|
DWORD dwNum,
|
||
|
DWORD dwDenom
|
||
|
)
|
||
|
{
|
||
|
MILLISECS msInPrevEpoch = pclock->fnTimebase(pclock) - pclock->msT0;
|
||
|
TICKS tkInPrevEpoch;
|
||
|
|
||
|
dprintf1(( "clockSetRate(%04X) %lutk Rate=%lu/%lu", pclock, tkWhen, dwNum, dwDenom));
|
||
|
|
||
|
if (CLK_CS_PAUSED == pclock->dwState)
|
||
|
{
|
||
|
//
|
||
|
// !!! Calling clockSetRate on a paused clock which has never been
|
||
|
// started causes problems !!!
|
||
|
//
|
||
|
|
||
|
dprintf1(( "clockSetRate called when clock is paused."));
|
||
|
}
|
||
|
|
||
|
if (0 == dwNum || 0 == dwDenom)
|
||
|
{
|
||
|
dprintf1(( "Attempt to set 0 or infinite tick ratio!"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (CLK_TK_NOW == tkWhen)
|
||
|
{
|
||
|
tkInPrevEpoch = clockTime(pclock);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
tkInPrevEpoch = tkWhen - pclock->tkPrev;
|
||
|
msInPrevEpoch = muldiv32(tkInPrevEpoch, pclock->dwDenom, pclock->dwNum);
|
||
|
}
|
||
|
|
||
|
pclock->tkPrev += tkInPrevEpoch;
|
||
|
pclock->msPrev += msInPrevEpoch;
|
||
|
pclock->msT0 += msInPrevEpoch;
|
||
|
|
||
|
pclock->dwNum = dwNum;
|
||
|
pclock->dwDenom = dwDenom;
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc INTERNAL CLOCK
|
||
|
*
|
||
|
* @func void | clockPause | This functions pauses a clock.
|
||
|
*
|
||
|
* @parm PCLOCK | pclock | The clock to pause.
|
||
|
*
|
||
|
* @parm TICKS | tkWhen | The tick time to pause the clock.
|
||
|
* @flag CLK_TK_NOW | Specify this flag if you want the rate change to
|
||
|
* happen now (this will be the time the clock was paused if it is paused
|
||
|
* now).
|
||
|
*
|
||
|
* @comm If the clock is already paused, this call will have no effect.
|
||
|
*
|
||
|
***************************************************************************/
|
||
|
|
||
|
void FAR PASCAL clockPause
|
||
|
(
|
||
|
PCLOCK pclock,
|
||
|
TICKS tkWhen
|
||
|
)
|
||
|
{
|
||
|
MILLISECS msNow = pclock->fnTimebase(pclock) - pclock->msT0;
|
||
|
TICKS tkNow;
|
||
|
|
||
|
// dprintf1(( "clockPause(%04X) %lutk", pclock, tkWhen));
|
||
|
|
||
|
if (CLK_CS_PAUSED == pclock->dwState)
|
||
|
{
|
||
|
dprintf1(( "Pause already paused clock!"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Start a new epoch at the same rate. Then start will just have to
|
||
|
// change the state and set a new T0.
|
||
|
//
|
||
|
if (CLK_TK_NOW == tkWhen)
|
||
|
{
|
||
|
tkNow = pclock->tkPrev +
|
||
|
muldiv32(msNow, pclock->dwNum, pclock->dwDenom);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
msNow = muldiv32(tkWhen - pclock->tkPrev, pclock->dwDenom, pclock->dwNum);
|
||
|
tkNow = tkWhen;
|
||
|
}
|
||
|
|
||
|
pclock->dwState = CLK_CS_PAUSED;
|
||
|
pclock->msPrev += msNow;
|
||
|
pclock->tkPrev = tkNow;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc INTERNAL CLOCK
|
||
|
*
|
||
|
* @func void | clockRestart | This functions starts a paused clock.
|
||
|
*
|
||
|
* @parm PCLOCK | pclock | The clock to start.
|
||
|
*
|
||
|
* @comm If the clock is already running, this call will have no effect.
|
||
|
*
|
||
|
***************************************************************************/
|
||
|
|
||
|
void FAR PASCAL clockRestart
|
||
|
(
|
||
|
PCLOCK pclock,
|
||
|
TICKS tkWhen, // What time it is now
|
||
|
MILLISECS msWhen // Offset for fnTimebase()
|
||
|
)
|
||
|
{
|
||
|
MILLISECS msDelta;
|
||
|
|
||
|
// dprintf1(( "clockRestart(%04X)", pclock));
|
||
|
|
||
|
if (CLK_CS_RUNNING == pclock->dwState)
|
||
|
{
|
||
|
dprintf1(( "Start already running clock!"));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// We've been given what tick time the clock SHOULD be at. Adjust the
|
||
|
// clock to match this. We need to add the equivalent number of ms
|
||
|
// into msPrev
|
||
|
//
|
||
|
msDelta = muldiv32(tkWhen - pclock->tkPrev, pclock->dwDenom, pclock->dwNum);
|
||
|
|
||
|
dprintf1(( "clockRestart: Was tick %lu, now %lu, added %lu ms", pclock->tkPrev, tkWhen, msDelta));
|
||
|
|
||
|
pclock->tkPrev = tkWhen;
|
||
|
pclock->msPrev += msDelta;
|
||
|
pclock->dwState = CLK_CS_RUNNING;
|
||
|
pclock->msT0 = msWhen;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc INTERNAL CLOCK
|
||
|
*
|
||
|
* @func DWORD | clockTime | This function returns the current absolute tick
|
||
|
* time.
|
||
|
*
|
||
|
* @parm PCLOCK | pclock | The clock to read.
|
||
|
*
|
||
|
* @rdesc The current time.
|
||
|
*
|
||
|
* @comm If the clock is paused, the returned time will be the time the
|
||
|
* clock was paused.
|
||
|
*
|
||
|
***************************************************************************/
|
||
|
|
||
|
TICKS FAR PASCAL clockTime
|
||
|
(
|
||
|
PCLOCK pclock
|
||
|
)
|
||
|
{
|
||
|
MILLISECS msNow;
|
||
|
TICKS tkNow;
|
||
|
TICKS tkDelta;
|
||
|
|
||
|
msNow = pclock->fnTimebase(pclock) - pclock->msT0;
|
||
|
tkNow = pclock->tkPrev;
|
||
|
|
||
|
if (CLK_CS_RUNNING == pclock->dwState)
|
||
|
{
|
||
|
tkDelta = muldiv32(msNow, pclock->dwNum, pclock->dwDenom);
|
||
|
tkNow += tkDelta;
|
||
|
}
|
||
|
|
||
|
// dprintf1(( "clockTime() timeGetTime() %lu msT0 %lu", (MILLISECS)pclock->fnTimebase(pclock), pclock->msT0));
|
||
|
// dprintf1(( "clockTime() tkPrev %lutk msNow %lums dwNum %lu dwDenom %lu tkDelta %lutk", pclock->tkPrev, msNow, pclock->dwNum, pclock->dwDenom, tkDelta));
|
||
|
// dprintf1(( "clockTime(%04X) -> %lutk", pclock, tkNow));
|
||
|
return tkNow;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc INTERNAL CLOCK
|
||
|
*
|
||
|
* @func DWORD | clockMsTime | This function returns the current absolute
|
||
|
* millisecond time.
|
||
|
*
|
||
|
* @parm PCLOCK | pclock | The clock to read.
|
||
|
*
|
||
|
* @rdesc The current time.
|
||
|
*
|
||
|
* @comm If the clock is paused, the returned time will be the time the
|
||
|
* clock was paused.
|
||
|
*
|
||
|
***************************************************************************/
|
||
|
|
||
|
MILLISECS FAR PASCAL clockMsTime
|
||
|
(
|
||
|
PCLOCK pclock
|
||
|
)
|
||
|
{
|
||
|
MILLISECS msNow = pclock->fnTimebase(pclock) - pclock->msT0;
|
||
|
MILLISECS msRet;
|
||
|
|
||
|
msRet = pclock->msPrev;
|
||
|
|
||
|
if (CLK_CS_RUNNING == pclock->dwState)
|
||
|
{
|
||
|
msRet += msNow;
|
||
|
}
|
||
|
|
||
|
// dprintf1(( "clockMsTime(%04X) -> %lums", pclock, msRet));
|
||
|
return msRet;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* @doc INTERNAL CLOCK
|
||
|
*
|
||
|
* @func DWORD | clockOffsetTo | This function determines the number
|
||
|
* of milliseconds in the future that a given tick time will occur,
|
||
|
* assuming the clock runs continously and monotonically until then.
|
||
|
*
|
||
|
* @parm PCLOCK | pclock | The clock to read.
|
||
|
*
|
||
|
* @parm TICKS | tkWhen | The tick value to calculate the offset to.
|
||
|
*
|
||
|
* @rdesc The number of milliseconds until the desired time. If the time
|
||
|
* has already passed, 0 will be returned. If the clock is paused,
|
||
|
* the largest possible value will be returned ((DWORD)-1L).
|
||
|
*
|
||
|
***************************************************************************/
|
||
|
|
||
|
MILLISECS FAR PASCAL clockOffsetTo
|
||
|
(
|
||
|
PCLOCK pclock,
|
||
|
TICKS tkWhen
|
||
|
)
|
||
|
{
|
||
|
TICKS tkOffset;
|
||
|
MILLISECS msOffset;
|
||
|
|
||
|
if (CLK_CS_PAUSED == pclock->dwState)
|
||
|
{
|
||
|
msOffset = (MILLISECS)-1L;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
tkOffset = clockTime(pclock);
|
||
|
if (tkOffset >= tkWhen)
|
||
|
{
|
||
|
msOffset = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
msOffset = muldiv32(tkWhen-tkOffset, pclock->dwDenom, pclock->dwNum);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// dprintf1(( "clockOffsetTo(%04X, %lutk)@%lutk -> %lums", pclock, tkWhen, tkOffset, msOffset));
|
||
|
|
||
|
return msOffset;
|
||
|
}
|