Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

360 lines
10 KiB

  1. /*****************************************************************************
  2. microclk.c
  3. Micro-ClockWork for MIDI subsystem
  4. Copyright (c) 1993-1999 Microsoft Corporation
  5. *****************************************************************************/
  6. #define INCL_WINMM
  7. #include "winmmi.h"
  8. #include "muldiv32.h"
  9. //#define STRICT
  10. //#include <windows.h>
  11. //#include <windowsx.h>
  12. //#include "mmsystem.h"
  13. //#include "mmddk.h"
  14. //#include "mmsysi.h"
  15. //#include "debug.h"
  16. //
  17. // This stuff needs to be do-able from inside a callback.
  18. //
  19. #ifndef WIN32
  20. #pragma alloc_text(FIXMIDI, clockSetRate)
  21. #pragma alloc_text(FIXMIDI, clockTime)
  22. #pragma alloc_text(FIXMIDI, clockOffsetTo)
  23. #endif
  24. /****************************************************************************
  25. * @doc INTERNAL CLOCK
  26. *
  27. * @func void | clockInit | This function initializes a clock for the first
  28. * time. It prepares the clock for use without actually starting it.
  29. *
  30. * @parm PCLOCK | pclock | The clock to initialize.
  31. *
  32. * @parm MILLISECS | msPrev | The number of milliseconds that have passed
  33. * up to the time when the clock is started. This option is provided so
  34. * that a clock may be initialized and started in the middle of a stream
  35. * without actually running to that point. Normally, this will be zero.
  36. *
  37. * @parm TICKS | tkPrev | The number of ticks that have elapsed up to the
  38. * next time the clock starts. This should specify the same instant in
  39. * time as msPrev.
  40. *
  41. * @comm The clock's numerator and divisor will be set to 1 indicating that
  42. * the clock will run in milliseconds. Use clockSetRate before starting the
  43. * clock for the first time if this is not the desired rate.
  44. *
  45. ***************************************************************************/
  46. void FAR PASCAL clockInit
  47. (
  48. PCLOCK pclock,
  49. MILLISECS msPrev,
  50. TICKS tkPrev,
  51. CLK_TIMEBASE fnTimebase
  52. )
  53. {
  54. // dprintf1(( "clockInit(%04X) %lums %lutk", pclock, msPrev, tkPrev));
  55. pclock->msPrev = msPrev;
  56. pclock->tkPrev = tkPrev;
  57. pclock->dwNum = 1;
  58. pclock->dwDenom = 1;
  59. pclock->dwState = CLK_CS_PAUSED;
  60. pclock->msT0 = 0;
  61. pclock->fnTimebase = fnTimebase;
  62. }
  63. /****************************************************************************
  64. * @doc INTERNAL CLOCK
  65. *
  66. * @func void | clockSetRate | This functions sets a new rate for the clock.
  67. *
  68. * @parm PCLOCK | pclock | The clock to set the rate.
  69. *
  70. * @parm TICKS | tkWhen | This parameter specifies the absolute tick
  71. * time at which the rate change happened. This must be at or before the
  72. * current tick; you cannot schedule a pending rate change.
  73. * @flag CLK_TK_NOW | Specify this flag if you want the rate change to
  74. * happen now (this will be the time the clock was paused if it is paused
  75. * now).
  76. *
  77. * @parm DWORD | dwNum | Specifies the new numerator for converting
  78. * milliseconds to ticks.
  79. *
  80. * @parm DWORD | dwDenom | Specifies the new denominator for converting
  81. * milliseconds to ticks.
  82. *
  83. * @comm The clock's state will not be changed by this call; if it is
  84. * paused, it will stay paused.
  85. *
  86. ***************************************************************************/
  87. void FAR PASCAL clockSetRate
  88. (
  89. PCLOCK pclock,
  90. TICKS tkWhen,
  91. DWORD dwNum,
  92. DWORD dwDenom
  93. )
  94. {
  95. MILLISECS msInPrevEpoch = pclock->fnTimebase(pclock) - pclock->msT0;
  96. TICKS tkInPrevEpoch;
  97. dprintf1(( "clockSetRate(%04X) %lutk Rate=%lu/%lu", pclock, tkWhen, dwNum, dwDenom));
  98. if (CLK_CS_PAUSED == pclock->dwState)
  99. {
  100. //
  101. // !!! Calling clockSetRate on a paused clock which has never been
  102. // started causes problems !!!
  103. //
  104. dprintf1(( "clockSetRate called when clock is paused."));
  105. }
  106. if (0 == dwNum || 0 == dwDenom)
  107. {
  108. dprintf1(( "Attempt to set 0 or infinite tick ratio!"));
  109. return;
  110. }
  111. if (CLK_TK_NOW == tkWhen)
  112. {
  113. tkInPrevEpoch = clockTime(pclock);
  114. }
  115. else
  116. {
  117. tkInPrevEpoch = tkWhen - pclock->tkPrev;
  118. msInPrevEpoch = muldiv32(tkInPrevEpoch, pclock->dwDenom, pclock->dwNum);
  119. }
  120. pclock->tkPrev += tkInPrevEpoch;
  121. pclock->msPrev += msInPrevEpoch;
  122. pclock->msT0 += msInPrevEpoch;
  123. pclock->dwNum = dwNum;
  124. pclock->dwDenom = dwDenom;
  125. }
  126. /****************************************************************************
  127. * @doc INTERNAL CLOCK
  128. *
  129. * @func void | clockPause | This functions pauses a clock.
  130. *
  131. * @parm PCLOCK | pclock | The clock to pause.
  132. *
  133. * @parm TICKS | tkWhen | The tick time to pause the clock.
  134. * @flag CLK_TK_NOW | Specify this flag if you want the rate change to
  135. * happen now (this will be the time the clock was paused if it is paused
  136. * now).
  137. *
  138. * @comm If the clock is already paused, this call will have no effect.
  139. *
  140. ***************************************************************************/
  141. void FAR PASCAL clockPause
  142. (
  143. PCLOCK pclock,
  144. TICKS tkWhen
  145. )
  146. {
  147. MILLISECS msNow = pclock->fnTimebase(pclock) - pclock->msT0;
  148. TICKS tkNow;
  149. // dprintf1(( "clockPause(%04X) %lutk", pclock, tkWhen));
  150. if (CLK_CS_PAUSED == pclock->dwState)
  151. {
  152. dprintf1(( "Pause already paused clock!"));
  153. return;
  154. }
  155. //
  156. // Start a new epoch at the same rate. Then start will just have to
  157. // change the state and set a new T0.
  158. //
  159. if (CLK_TK_NOW == tkWhen)
  160. {
  161. tkNow = pclock->tkPrev +
  162. muldiv32(msNow, pclock->dwNum, pclock->dwDenom);
  163. }
  164. else
  165. {
  166. msNow = muldiv32(tkWhen - pclock->tkPrev, pclock->dwDenom, pclock->dwNum);
  167. tkNow = tkWhen;
  168. }
  169. pclock->dwState = CLK_CS_PAUSED;
  170. pclock->msPrev += msNow;
  171. pclock->tkPrev = tkNow;
  172. }
  173. /****************************************************************************
  174. * @doc INTERNAL CLOCK
  175. *
  176. * @func void | clockRestart | This functions starts a paused clock.
  177. *
  178. * @parm PCLOCK | pclock | The clock to start.
  179. *
  180. * @comm If the clock is already running, this call will have no effect.
  181. *
  182. ***************************************************************************/
  183. void FAR PASCAL clockRestart
  184. (
  185. PCLOCK pclock,
  186. TICKS tkWhen, // What time it is now
  187. MILLISECS msWhen // Offset for fnTimebase()
  188. )
  189. {
  190. MILLISECS msDelta;
  191. // dprintf1(( "clockRestart(%04X)", pclock));
  192. if (CLK_CS_RUNNING == pclock->dwState)
  193. {
  194. dprintf1(( "Start already running clock!"));
  195. return;
  196. }
  197. // We've been given what tick time the clock SHOULD be at. Adjust the
  198. // clock to match this. We need to add the equivalent number of ms
  199. // into msPrev
  200. //
  201. msDelta = muldiv32(tkWhen - pclock->tkPrev, pclock->dwDenom, pclock->dwNum);
  202. dprintf1(( "clockRestart: Was tick %lu, now %lu, added %lu ms", pclock->tkPrev, tkWhen, msDelta));
  203. pclock->tkPrev = tkWhen;
  204. pclock->msPrev += msDelta;
  205. pclock->dwState = CLK_CS_RUNNING;
  206. pclock->msT0 = msWhen;
  207. }
  208. /****************************************************************************
  209. * @doc INTERNAL CLOCK
  210. *
  211. * @func DWORD | clockTime | This function returns the current absolute tick
  212. * time.
  213. *
  214. * @parm PCLOCK | pclock | The clock to read.
  215. *
  216. * @rdesc The current time.
  217. *
  218. * @comm If the clock is paused, the returned time will be the time the
  219. * clock was paused.
  220. *
  221. ***************************************************************************/
  222. TICKS FAR PASCAL clockTime
  223. (
  224. PCLOCK pclock
  225. )
  226. {
  227. MILLISECS msNow;
  228. TICKS tkNow;
  229. TICKS tkDelta;
  230. msNow = pclock->fnTimebase(pclock) - pclock->msT0;
  231. tkNow = pclock->tkPrev;
  232. if (CLK_CS_RUNNING == pclock->dwState)
  233. {
  234. tkDelta = muldiv32(msNow, pclock->dwNum, pclock->dwDenom);
  235. tkNow += tkDelta;
  236. }
  237. // dprintf1(( "clockTime() timeGetTime() %lu msT0 %lu", (MILLISECS)pclock->fnTimebase(pclock), pclock->msT0));
  238. // dprintf1(( "clockTime() tkPrev %lutk msNow %lums dwNum %lu dwDenom %lu tkDelta %lutk", pclock->tkPrev, msNow, pclock->dwNum, pclock->dwDenom, tkDelta));
  239. // dprintf1(( "clockTime(%04X) -> %lutk", pclock, tkNow));
  240. return tkNow;
  241. }
  242. /****************************************************************************
  243. * @doc INTERNAL CLOCK
  244. *
  245. * @func DWORD | clockMsTime | This function returns the current absolute
  246. * millisecond time.
  247. *
  248. * @parm PCLOCK | pclock | The clock to read.
  249. *
  250. * @rdesc The current time.
  251. *
  252. * @comm If the clock is paused, the returned time will be the time the
  253. * clock was paused.
  254. *
  255. ***************************************************************************/
  256. MILLISECS FAR PASCAL clockMsTime
  257. (
  258. PCLOCK pclock
  259. )
  260. {
  261. MILLISECS msNow = pclock->fnTimebase(pclock) - pclock->msT0;
  262. MILLISECS msRet;
  263. msRet = pclock->msPrev;
  264. if (CLK_CS_RUNNING == pclock->dwState)
  265. {
  266. msRet += msNow;
  267. }
  268. // dprintf1(( "clockMsTime(%04X) -> %lums", pclock, msRet));
  269. return msRet;
  270. }
  271. /****************************************************************************
  272. * @doc INTERNAL CLOCK
  273. *
  274. * @func DWORD | clockOffsetTo | This function determines the number
  275. * of milliseconds in the future that a given tick time will occur,
  276. * assuming the clock runs continously and monotonically until then.
  277. *
  278. * @parm PCLOCK | pclock | The clock to read.
  279. *
  280. * @parm TICKS | tkWhen | The tick value to calculate the offset to.
  281. *
  282. * @rdesc The number of milliseconds until the desired time. If the time
  283. * has already passed, 0 will be returned. If the clock is paused,
  284. * the largest possible value will be returned ((DWORD)-1L).
  285. *
  286. ***************************************************************************/
  287. MILLISECS FAR PASCAL clockOffsetTo
  288. (
  289. PCLOCK pclock,
  290. TICKS tkWhen
  291. )
  292. {
  293. TICKS tkOffset;
  294. MILLISECS msOffset;
  295. if (CLK_CS_PAUSED == pclock->dwState)
  296. {
  297. msOffset = (MILLISECS)-1L;
  298. }
  299. else
  300. {
  301. tkOffset = clockTime(pclock);
  302. if (tkOffset >= tkWhen)
  303. {
  304. msOffset = 0;
  305. }
  306. else
  307. {
  308. msOffset = muldiv32(tkWhen-tkOffset, pclock->dwDenom, pclock->dwNum);
  309. }
  310. }
  311. // dprintf1(( "clockOffsetTo(%04X, %lutk)@%lutk -> %lums", pclock, tkWhen, tkOffset, msOffset));
  312. return msOffset;
  313. }