/***************************************************************************** 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 //#include //#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; }