Leaked source code of windows server 2003
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.

668 lines
23 KiB

  1. /*******************************Module*Header*********************************\
  2. * Module Name: util.c
  3. *
  4. * MultiMedia Systems MIDI Sequencer DLL
  5. *
  6. * Created: 4/11/90
  7. * Author: GREGSI
  8. *
  9. * History:
  10. *
  11. * Copyright (c) 1985-1998 Microsoft Corporation
  12. *
  13. \******************************************************************************/
  14. #define UNICODE
  15. //MMSYSTEM
  16. #define MMNOSOUND - Sound support
  17. #define MMNOWAVE - Waveform support
  18. #define MMNOAUX - Auxiliary output support
  19. #define MMNOJOY - Joystick support
  20. //MMDDK
  21. #define NOWAVEDEV - Waveform support
  22. #define NOAUXDEV - Auxiliary output support
  23. #define NOJOYDEV - Joystick support
  24. #include <windows.h>
  25. #include <mmsystem.h>
  26. #include <mmddk.h>
  27. #include "mmsys.h"
  28. #include "list.h"
  29. #include "mmseqi.h"
  30. #include "mciseq.h"
  31. typedef struct ts
  32. {
  33. BOOL valid;
  34. void (*func)(void *, LONG);
  35. LONG instance;
  36. LONG param;
  37. LONG time;
  38. } TimerStruct;
  39. static ListHandle timerList;
  40. static DWORD systemTime = 0; //global holding system time
  41. static DWORD nextEventTime;
  42. #ifdef DEBUG
  43. static BOOL fInterruptTime = 0;
  44. #endif
  45. /**************************** PUBLIC FUNCTIONS *************************/
  46. /****************************************************************************
  47. * @doc INTERNAL
  48. *
  49. * @api void | seqCallback | This calls DriverCallback for the seq device
  50. *
  51. * @parm NPTRACK | npTrack | pointer to the TRACK info
  52. *
  53. * @parm UINT | msg | message
  54. *
  55. * @parm DWORD | dw1 | message dword
  56. *
  57. * @parm DWORD | dw2 | message dword
  58. *
  59. * @rdesc There is no return value
  60. ***************************************************************************/
  61. PUBLIC VOID NEAR PASCAL seqCallback(NPTRACK npTrack, UINT msg, DWORD_PTR dw1, DWORD_PTR dw2)
  62. {
  63. //
  64. // don't use DriverCallback() because it will switch stacks and
  65. // we don't need to switch to a new stack.
  66. //
  67. // this function can get nested 1 or 2 deep and exaust MMSYSTEMs
  68. // internal stacks.
  69. //
  70. #if 0
  71. if(npTrack->dwCallback)
  72. DriverCallback(npTrack->dwCallback, DCB_FUNCTION,
  73. 0,
  74. msg,
  75. npTrack->dwInstance,
  76. dw1,
  77. dw2);
  78. #else
  79. if(npTrack->dwCallback)
  80. (*(LPDRVCALLBACK)npTrack->dwCallback)(0,msg,npTrack->dwInstance,dw1,dw2);
  81. #endif
  82. }
  83. PUBLIC NPLONGMIDI NEAR PASCAL GetSysExBuffer(NPSEQ npSeq)
  84. {
  85. int i;
  86. for (i = 0; i < NUMSYSEXHDRS; i++)
  87. if (npSeq->longMIDI[i].midihdr.dwFlags & MHDR_DONE)
  88. return &npSeq->longMIDI[i];
  89. return NULL;
  90. }
  91. /**********************************************************/
  92. PUBLIC BYTE NEAR PASCAL LookByte(NPTRACK npTrack)
  93. /* Gets next byte from track. Accounts for unfulfilled data buffer request,
  94. end of track, and (premature) end of stream. Returns byte read as fn result.
  95. If error, returns file status error code (differentiate because filestatus is
  96. an int, and err codes are > 255) */
  97. {
  98. if ((npTrack->blockedOn) || (!npTrack->inPort.hdrList))
  99. // return if blocked, or hdrList empty
  100. return 0;
  101. if (npTrack->inPort.currentPtr <= npTrack->inPort.endPtr)
  102. return *npTrack->inPort.currentPtr; // return next byte in buffer
  103. else
  104. { // oops, out of bytes in this buffer -- return this one and get next
  105. /* if this is the last buffer, set done stuff; else
  106. if there's a pending track message header, set pointers to
  107. it; else block */
  108. if (npTrack->inPort.hdrList->wFlags & MIDISEQHDR_EOT) //'last buffer' flag
  109. {
  110. npTrack->endOfTrack = TRUE;
  111. // pass the buffer back to the sequencer
  112. seqCallback(npTrack, MIDISEQ_DONE, (DWORD_PTR)npTrack->inPort.hdrList, 0L);
  113. npTrack->inPort.hdrList = npTrack->inPort.hdrList->lpNext;
  114. // point over it (should be NULL)
  115. // done with it!
  116. return ENDOFTRACK;
  117. }
  118. else if (npTrack->inPort.hdrList->lpNext)
  119. {
  120. npTrack->inPort.previousPtr = NULL; // can't back up.
  121. // pass the buffer back to the sequencer
  122. seqCallback(npTrack, MIDISEQ_DONE, (DWORD_PTR)npTrack->inPort.hdrList, 0L);
  123. // done with it!
  124. npTrack->inPort.hdrList = npTrack->inPort.hdrList->lpNext; // point over it
  125. npTrack->inPort.currentPtr = npTrack->inPort.hdrList->lpData;
  126. npTrack->inPort.endPtr = npTrack->inPort.currentPtr + npTrack->inPort.hdrList->dwLength - 1;
  127. return *npTrack->inPort.currentPtr;
  128. }
  129. else
  130. {
  131. npTrack->blockedOn = on_input;
  132. //
  133. // we CANT call wsprintf at interupt time
  134. //
  135. #ifdef ACK_DEBUG
  136. dprintf(("***** BLOCKED ON INPUT ********* Trk: %u", npTrack->inPort.hdrList->wTrack));
  137. #endif
  138. // Note: don't return buffer in this case 'cause it will be
  139. // needed when track gets unblocked (may have beginning of msg)
  140. return 0;
  141. }
  142. }
  143. }
  144. /**********************************************************/
  145. PUBLIC BYTE NEAR PASCAL GetByte(NPTRACK npTrack)
  146. {
  147. BYTE theByte;
  148. theByte = LookByte(npTrack);
  149. if (!npTrack->blockedOn)
  150. npTrack->inPort.currentPtr++;
  151. return theByte;
  152. }
  153. /* The mark and reset location code is used to recover from aborted operations
  154. during reading the input buffer. Note: it will bite like a big dog if a
  155. message can span 3 buffers. (I.E.-- can't back up over a message boundary!) */
  156. PUBLIC VOID NEAR PASCAL MarkLocation(NPTRACK npTrack)// remember current location
  157. {
  158. npTrack->inPort.previousPtr = npTrack->inPort.currentPtr;
  159. }
  160. PUBLIC VOID NEAR PASCAL ResetLocation(NPTRACK npTrack)// restore last saved location
  161. {
  162. npTrack->inPort.currentPtr = npTrack->inPort.previousPtr;
  163. }
  164. /**********************************************************/
  165. PUBLIC VOID NEAR PASCAL RewindToStart(NPSEQ npSeq, NPTRACK npTrack)
  166. /* sets all blocking status, frees buffers, and sends messages to stream mgr
  167. needed to prepare the track to play from the beginning. It is up to
  168. someone at a higher level to set 'blockedOn' to a more mature (specific)
  169. status. */
  170. {
  171. // Streamer sets done and signals all buffers
  172. npTrack->inPort.hdrList = NULL; // lose the list of headers
  173. npTrack->blockedOn = on_input;
  174. seqCallback(npTrack, MIDISEQ_RESET, npTrack->iTrackNum, 0L);
  175. }
  176. PUBLIC BOOL NEAR PASCAL AllTracksUnblocked(NPSEQ npSeq)
  177. { // use trackarray to avoid re-entrancy problems with list code
  178. UINT i;
  179. for(i = 0; i < npSeq->wNumTrks; i++)
  180. if (npSeq->npTrkArr->trkArr[i]->blockedOn != not_blocked)
  181. return FALSE;
  182. return TRUE; // made it thru all tracks -- must be none blocked.
  183. }
  184. PRIVATE VOID NEAR PASCAL PlayUnblock(NPSEQ npSeq, NPTRACK npTrack)
  185. {
  186. DestroyTimer(npSeq);
  187. FillInNextTrack(npTrack);
  188. if ((!npSeq->bSending) && (GetNextEvent(npSeq) == NoErr) &&
  189. (npSeq->playing))
  190. {
  191. dprintf2(("Fired up timer on track data"));
  192. SetTimerCallback(npSeq, MINPERIOD, npSeq->nextEventTrack->delta);
  193. }
  194. }
  195. PRIVATE VOID NEAR PASCAL SeekUnblock(NPSEQ npSeq, NPTRACK npTrack)
  196. {
  197. FillInNextTrack(npTrack);
  198. if ((!npSeq->bSending) && (AllTracksUnblocked(npSeq)) &&
  199. (GetNextEvent(npSeq) == NoErr)) // exit until all unblocked
  200. {
  201. SendAllEventsB4(npSeq, (npSeq->seekTicks - npSeq->currentTick),
  202. MODE_SEEK_TICKS);
  203. if ((AllTracksUnblocked(npSeq)) &&
  204. ((npSeq->currentTick + npSeq->nextEventTrack->delta)
  205. >= npSeq->seekTicks))
  206. { // have finished the song pointer command
  207. npSeq->seekTicks = NotInUse; // signify -- got there
  208. if (npSeq->wCBMessage == SEQ_SEEKTICKS)
  209. NotifyCallback(npSeq->hStream);
  210. npSeq->readyToPlay = TRUE;
  211. if (npSeq->playing) // someone hit play while locating
  212. {
  213. npSeq->seekTicks = NotInUse; // finally got there
  214. SetTimerCallback(npSeq, MINPERIOD, npSeq->nextEventTrack->delta);
  215. }
  216. }
  217. }
  218. }
  219. PUBLIC UINT NEAR PASCAL NewTrackData(NPSEQ npSeq, LPMIDISEQHDR msgHdr)
  220. {
  221. WORD wTrackNum;
  222. NPTRACK npTrack;
  223. LPMIDISEQHDR myMsgHdr;
  224. BOOL tempPlaying;
  225. int block;
  226. // look at track number it's for
  227. // get that track number (or return if couldn't find it)
  228. // make sure this one's next ptr is null
  229. // walk down the list of midi track data buffers and set last one to
  230. // point to this one
  231. // if blocked != NONE
  232. // save blocked status
  233. // clear any "blocked on input" bit in track (now that what it was
  234. // waiting for has arrived).
  235. // case blocked status: InputBetMsg: compute elapsed ticks & call timer int
  236. // InputWithinSysex: compute
  237. // Output:
  238. /* Get ptr to track referenced in message */
  239. wTrackNum = msgHdr->wTrack;
  240. #ifdef ACK_DEBUG
  241. {
  242. dprintf(("GOT TRACKDATA for TRACK#: %u", wTrackNum));
  243. }
  244. #endif
  245. /*
  246. can't call list routines for track at non-interrupt time
  247. npTrack = (NPTRACK) List_Get_First(npSeq->trackList);
  248. i = 0;
  249. while ((npTrack) && (i++ != wTrackNum))
  250. npTrack = (NPTRACK) List_Get_Next(npSeq->trackList, npTrack);
  251. */
  252. npTrack = npSeq->npTrkArr->trkArr[wTrackNum];
  253. if (!npTrack)
  254. {
  255. dprintf1(("ERROR -- BOGUS TRACK NUM IN TRACK DATA HEADER"));
  256. return 0;
  257. }
  258. EnterCrit();
  259. msgHdr->lpNext = NULL; //make sure header passed in is NULL
  260. if (myMsgHdr = npTrack->inPort.hdrList) // if track already has data
  261. { // put this one at end of list
  262. while (myMsgHdr->lpNext)
  263. myMsgHdr = myMsgHdr->lpNext;
  264. myMsgHdr->lpNext = msgHdr;
  265. }
  266. else // there are currently no headers in list
  267. {
  268. npTrack->inPort.hdrList = msgHdr; // make this 1st one
  269. npTrack->inPort.currentPtr = msgHdr->lpData;
  270. npTrack->inPort.endPtr = msgHdr->lpData + msgHdr->dwLength - 1;
  271. }
  272. LeaveCrit();
  273. /* Dispatcher for state machine */
  274. block = npTrack->blockedOn; // temp var just for switch
  275. npTrack->blockedOn = not_blocked;
  276. switch (block)
  277. {
  278. case not_blocked: /* null case */
  279. break;
  280. case in_SysEx:
  281. SendSysEx(npSeq); // send all sysex messages
  282. if (!npSeq->bSendingSysEx) // totally done
  283. PlayUnblock(npSeq, npTrack);
  284. break;
  285. case in_SkipBytes_Play:
  286. SkipBytes(npTrack, npTrack->dwBytesLeftToSkip);
  287. if (npTrack->blockedOn)
  288. {
  289. // if blocked again, reset status and leave
  290. npTrack->blockedOn = in_SkipBytes_Play;
  291. break;
  292. }
  293. PlayUnblock(npSeq, npTrack);
  294. break;
  295. case in_Normal_Meta:
  296. HandleMetaEvent(npSeq, npTrack, FALSE); // handle pending meta
  297. PlayUnblock(npSeq, npTrack);
  298. break;
  299. case between_msg_out: /* was blocked on input while filling a track */
  300. PlayUnblock(npSeq, npTrack);
  301. break;
  302. case in_SkipBytes_Seek:
  303. SkipBytes(npTrack, npTrack->dwBytesLeftToSkip);
  304. if (npTrack->blockedOn)
  305. {
  306. // if blocked again, reset status and leave
  307. npTrack->blockedOn = in_SkipBytes_Seek;
  308. break;
  309. }
  310. SeekUnblock(npSeq, npTrack);
  311. break;
  312. case in_Seek_Meta:
  313. HandleMetaEvent(npSeq, npTrack, FALSE); // handle pending meta
  314. SeekUnblock(npSeq, npTrack);
  315. break;
  316. case in_Seek_Tick:
  317. SeekUnblock(npSeq, npTrack);
  318. break;
  319. /* THE THREE STATES BELOW ARE ENCOUNTERED IN "SETUPTOPLAY" */
  320. case in_rewind_1: /* blocked while rewinding to get meta events */
  321. if (AllTracksUnblocked(npSeq)) // exits until last track unblocked
  322. {
  323. if (!(ScanEarlyMetas(npSeq, NULL, 0x7fffffff))) // gets tempo, time-sig, SMPTE offset...from file
  324. {
  325. List_Destroy(npSeq->tempoMapList); // tempo map alloc failed
  326. break; // (empty list signals it)
  327. }
  328. if (AllTracksUnblocked(npSeq)) // exits until last track unblocked
  329. {
  330. ResetToBeginning(npSeq); // this is considered reset 2
  331. SetBlockedTracksTo(npSeq, on_input, in_rewind_2); // 'mature' the input block state
  332. }
  333. }
  334. break;
  335. case in_rewind_2: /* blocked while rewinding to play the file */
  336. FillInNextTrack(npTrack); /* get ready to send 1st message -- this will
  337. wait until buffer arrives */
  338. if (AllTracksUnblocked(npSeq)) // DONE PARSING FILE NOW
  339. {
  340. npSeq->readyToPlay = TRUE;
  341. SendPatchCache(npSeq, TRUE);
  342. if (npSeq->seekTicks != NotInUse) // there's a pending song ptr
  343. {
  344. tempPlaying = npSeq->playing; /* if there's a pending play
  345. message, must temporarily turn off */
  346. npSeq->playing = FALSE;
  347. midiSeqMessage((HMIDISEQ) npSeq, SEQ_SEEKTICKS, npSeq->seekTicks,
  348. FALSE);
  349. npSeq->playing = tempPlaying;
  350. }
  351. if ((GetNextEvent(npSeq) == NoErr) && (npSeq->playing))
  352. {
  353. SetTimerCallback(npSeq, MINPERIOD, npSeq->nextEventTrack->delta);
  354. }
  355. // later can call DoSyncSetup(npSeq); (or roll this in with metas)
  356. }
  357. break;
  358. case in_SkipBytes_ScanEM: /* blocked while skipping bytes within a meta
  359. or sysex */
  360. SkipBytes(npTrack, npTrack->dwBytesLeftToSkip);
  361. if (npTrack->blockedOn)
  362. {
  363. // if blocked again, reset status and leave
  364. npTrack->blockedOn = in_SkipBytes_ScanEM;
  365. break;
  366. }
  367. // else fall through
  368. case in_ScanEarlyMetas: /* blocked in ScanEarlyMetas routine */
  369. if (!(ScanEarlyMetas(npSeq, npTrack, 0x7fffffff))) // gets tempo, time-sig, SMPTE offset...from file
  370. {
  371. List_Destroy(npSeq->tempoMapList); // tempo map alloc failed
  372. break; // (empty list signals it)
  373. }
  374. if (AllTracksUnblocked(npSeq)) // exits until last track unblocked
  375. {
  376. ResetToBeginning(npSeq); // this is considered reset 2
  377. SetBlockedTracksTo(npSeq, on_input, in_rewind_2); // 'mature' the input block state
  378. }
  379. break;
  380. case on_input:
  381. default:
  382. dprintf(("UNDEFINED STATE ENCOUNTERED IN NEWTRACKDATA"));
  383. break;
  384. }
  385. return 0;
  386. }
  387. /**********************************************************/
  388. PUBLIC VOID FAR PASCAL _LOADDS OneShotTimer(UINT wId, UINT msg, DWORD_PTR dwUser, DWORD_PTR dwTime, DWORD_PTR dw2) // called by l1timer.dll
  389. {
  390. NPSEQ npSeq;
  391. #ifdef DEBUG
  392. fInterruptTime++;
  393. #endif
  394. npSeq = (NPSEQ) dwUser; // parameter is sequence
  395. npSeq->wTimerID = 0; // invalidate timer id, since no longer in use
  396. TimerIntRoutine(npSeq, npSeq->dwTimerParam);
  397. /* debugging code enabled from time to time to isolate timer vs. seq. bugs
  398. static BOOL lockOut = FALSE;
  399. TimerStruct *myTimerStruct;
  400. if ((nextEventTime <= (systemTime++)) & (!lockOut)) //note that one ms has elapsed
  401. {
  402. lockOut = TRUE;
  403. myTimerStruct = (TimerStruct *) List_Get_First(timerList);
  404. while ((myTimerStruct) &&
  405. !((myTimerStruct->valid) && (myTimerStruct->time <= systemTime)))
  406. myTimerStruct = (TimerStruct *) List_Get_Next(timerList, myTimerStruct);
  407. if (myTimerStruct) // if didn't run completely thru list
  408. {
  409. // call the callback fn and mark record as invalid
  410. (*(myTimerStruct->func))((NPSEQ) myTimerStruct->instance, myTimerStruct->param);
  411. myTimerStruct->valid = FALSE;
  412. ComputeNextEventTime();
  413. }
  414. lockOut = FALSE;
  415. } // if nexteventtime...
  416. */
  417. #ifdef DEBUG
  418. fInterruptTime--;
  419. #endif
  420. }
  421. /**********************************************************/
  422. PUBLIC UINT NEAR PASCAL SetTimerCallback(NPSEQ npSeq, UINT msInterval, DWORD elapsedTicks)
  423. // sets up to call back timerIntRoutine in msInt ms with elapseTcks
  424. {
  425. npSeq->dwTimerParam = elapsedTicks;
  426. npSeq->wTimerID = timeSetEvent(msInterval, MINPERIOD, OneShotTimer,
  427. (DWORD_PTR)npSeq, TIME_ONESHOT | TIME_KILL_SYNCHRONOUS);
  428. if (!npSeq->wTimerID)
  429. return MIDISEQERR_TIMER;
  430. else
  431. return MIDISEQERR_NOERROR;
  432. }
  433. /**********************************************************/
  434. PUBLIC VOID NEAR PASCAL DestroyTimer(NPSEQ npSeq)
  435. {
  436. EnterCrit();
  437. if (npSeq->wTimerID) // if pending timer event
  438. {
  439. /*
  440. * In the sequencer there are effectively two 'threads'
  441. *
  442. * The application's thread and it's device threads which are
  443. * all mutually synchronized via the sequencer critical section.
  444. *
  445. * The timer callback thread which all runs off this process'
  446. * dedicated timer thread. We use EnterCrit(), LeaveCrit() to
  447. * share structures with this thread.
  448. *
  449. * This routine is always called in the first of these 2
  450. * environments so only one routine can be in it at any one time.
  451. *
  452. * We can't disable interrupts on NT so we can have a hanging
  453. * timer event and need to synchronize with this event on the
  454. * timer thread.
  455. *
  456. * In addition we can't keep the critical section
  457. * when we call timeKillEvent (poor design in the timer code?)
  458. * because all the timer work including timer cancelling is
  459. * serialized on the timer thread. Thus the hanging event may get
  460. * in on the timer thread so :
  461. *
  462. * This Thread Timer thread
  463. *
  464. * In DestroyTimer In timer event callback
  465. *
  466. * <Owns CritSec> Waiting for CritSec - BLOCKED
  467. *
  468. * WaitForSingleObject BLOCKED ... Would set (timer thread completion)
  469. * (timer thread completion) if it got CritSec
  470. *
  471. *
  472. * We therefore set the 'timer entered' flag which will stop the
  473. * timer routine doing anything when it gets in and get out of
  474. * the critical section so the Event routine can run.
  475. *
  476. * We know that nobody else is going to set a timer
  477. * because we have the sequencer critical section at this point
  478. * and we have disabled the interrupt routine.
  479. */
  480. npSeq->bTimerEntered = TRUE;
  481. /*
  482. * Now we can get out of the critical section and any timer can
  483. * fire without damaging anything.
  484. */
  485. LeaveCrit();
  486. /*
  487. * cancel any pending timer events
  488. */
  489. timeKillEvent(npSeq->wTimerID);
  490. /*
  491. * No timer will fire now because the timeKillEvent is synchronized
  492. * with the timer events so we can tidy up our flag and id without
  493. * needing the critical section.
  494. */
  495. npSeq->bTimerEntered = FALSE;
  496. npSeq->wTimerID = 0;
  497. } else {
  498. LeaveCrit();
  499. }
  500. }
  501. /*********************** DEBUG CODE *************************************/
  502. /*
  503. void Timer_Cancel_All(DWORD instance)
  504. // search the list, purging everything with same instance
  505. {
  506. TimerStruct *myTimerStruct;
  507. for(myTimerStruct = (TimerStruct *) List_Get_First(timerList);
  508. myTimerStruct ;myTimerStruct = (TimerStruct *) List_Get_Next(timerList, myTimerStruct))
  509. if ((myTimerStruct->valid) && (myTimerStruct->instance = instance))
  510. myTimerStruct->valid = FALSE;
  511. ComputeNextEventTime();
  512. }
  513. */
  514. /**********************************************************/
  515. /* void IdlePlayAllSeqs()
  516. {
  517. SeqStreamType *seqStream;
  518. TrackStreamType *trackStream;
  519. int iDataToRead;
  520. int i;
  521. DWORD sysTime;
  522. sysTime = timeGetTime();
  523. seqStream = (SeqStreamType*) List_Get_First(SeqStreamListHandle);
  524. while (seqStream)
  525. {
  526. if ((seqStream->seq) && (seqStream->seq->nextEventTrack) &&
  527. (sysTime >= seqStream->seq->nextExactTime))
  528. {
  529. TimerIntRoutine(seqStream->seq,
  530. seqStream->seq->nextEventTrack->delta);
  531. }
  532. }
  533. }
  534. */
  535. /*
  536. void ComputeNextEventTime()
  537. {
  538. TimerStruct *myTimerStruct;
  539. nextEventTime = 0x7FFFFFFF; // make it a long time
  540. myTimerStruct = (TimerStruct *) List_Get_First(timerList);
  541. while (myTimerStruct)
  542. {
  543. if ((myTimerStruct->valid) && (myTimerStruct->time < nextEventTime))
  544. nextEventTime = myTimerStruct->time; // replace it with shortest
  545. myTimerStruct = (TimerStruct *) List_Get_Next(timerList, myTimerStruct);
  546. }
  547. }
  548. */
  549. /**********************************************************/
  550. PUBLIC VOID FAR PASCAL _LOADDS MIDICallback(HMIDIOUT hMIDIOut, UINT wMsg,
  551. DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
  552. {
  553. if (wMsg == MOM_DONE)
  554. { // just finished a long buffer
  555. NPSEQ npSeq;
  556. // dprintf3(("Callback on long buffer"));
  557. npSeq = (NPSEQ)(UINT_PTR) ((LPMIDIHDR)dwParam1)->dwUser; //derive npSeq
  558. if (npSeq->bSysExBlock) // was it blocked on sysex?
  559. {
  560. npSeq->bSysExBlock = FALSE; // not anymore!
  561. SendSysEx(npSeq); // send all sysex messages
  562. if (!npSeq->bSendingSysEx) // if totally done with sysex
  563. {
  564. FillInNextTrack(npSeq->nextEventTrack); // set up to play
  565. if ((GetNextEvent(npSeq) == NoErr) && (npSeq->playing))
  566. {
  567. dprintf3(("resume play from sysex unblock"));
  568. // ...and play
  569. SetTimerCallback(npSeq, MINPERIOD, npSeq->nextEventTrack->delta);
  570. }
  571. }
  572. }
  573. }
  574. }