/ source / audiolib / src / driver_winmm.cpp
driver_winmm.cpp
  1  /*
  2   Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
  3   Copyright (C) EDuke32 developers and contributors
  4  
  5   This program is free software; you can redistribute it and/or
  6   modify it under the terms of the GNU General Public License
  7   as published by the Free Software Foundation; either version 2
  8   of the License, or (at your option) any later version.
  9  
 10   This program is distributed in the hope that it will be useful,
 11   but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 13  
 14   See the GNU General Public License for more details.
 15  
 16   You should have received a copy of the GNU General Public License
 17   along with this program; if not, write to the Free Software
 18   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 19  
 20   */
 21  
 22  /**
 23   * WinMM MIDI output driver
 24   */
 25  
 26  #include "driver_winmm.h"
 27  
 28  #include "baselayer.h"
 29  #include "linklist.h"
 30  #include "midi.h"
 31  #include "midifuncs.h"
 32  #include "multivoc.h"
 33  #include "osd.h"
 34  #include "winbits.h"
 35  
 36  #include <mmsystem.h>
 37  
 38  #ifdef _MSC_VER
 39  #define inline _inline
 40  #endif
 41  
 42  UINT WinMM_DeviceID = MIDI_MAPPER;
 43  
 44  static int ErrorCode = WinMMErr_Ok;
 45  
 46  static BOOL      midiInstalled;
 47  static HMIDISTRM midiStream;
 48  static uint32_t midiThreadTimer;
 49  static uint32_t midiLastEventTime;
 50  static uint32_t midiThreadQueueTimer;
 51  static uint32_t midiThreadQueueTicks;
 52  static HANDLE   midiThread;
 53  static HANDLE   midiThreadQuitEvent;
 54  static HANDLE   midiBufferFinishedEvent;
 55  static HANDLE   midiThreadResetEvent;
 56  static HANDLE   midiMutex;
 57  static int      midiStreamRunning;
 58  static int      midiLastDivision;
 59  
 60  #define MME_THREAD_QUEUE_INTERVAL 10        // 1/10 sec
 61  #define MME_MIDI_BUFFER_SPACE (12 * 128u)  // 128 note-on events
 62  
 63  #ifdef _MSC_VER
 64  #pragma warning(push)
 65  #pragma warning(disable:4200)
 66  #endif
 67  typedef struct MidiBuffer
 68  {
 69      struct MidiBuffer *next;
 70      struct MidiBuffer *prev;
 71      MIDIHDR hdr;
 72      DWORD data[];
 73  } MidiBuffer;
 74  #ifdef _MSC_VER
 75  #pragma warning(pop)
 76  #endif
 77  
 78  static MidiBuffer finishedBufferList;
 79  static MidiBuffer activeBufferList;
 80  static MidiBuffer spareBufferList;
 81  static MidiBuffer *currentMidiBuffer;
 82  
 83  static int maxActiveMidiBuffers, maxSpareMidiBuffers;
 84  static int numActiveMidiBuffers, numSpareMidiBuffers;
 85  
 86  static void midi_destroy_thread(void);
 87  static void CALLBACK midi_callback(HMIDIOUT out, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
 88  
 89  int WinMMDrv_GetError(void) { return ErrorCode; }
 90  
 91  const char *WinMMDrv_ErrorString(int ErrorNumber)
 92  {
 93      switch (ErrorNumber)
 94      {
 95          case WinMMErr_Error:             return WinMMDrv_ErrorString(ErrorCode);
 96          case WinMMErr_Ok:                return "MME ok.";
 97          case WinMMErr_MIDIStreamOpen:    return "MIDI error: failed opening stream.";
 98          case WinMMErr_MIDIStreamRestart: return "MIDI error: failed starting stream.";
 99          case WinMMErr_MIDICreateEvent:   return "MIDI error: failed creating play thread quit event.";
100          case WinMMErr_MIDIPlayThread:    return "MIDI error: failed creating play thread.";
101          case WinMMErr_MIDICreateMutex:   return "MIDI error: failed creating play mutex.";
102          default:                         return "Unknown MME error code.";
103      }
104  }
105  
106  
107  // will append "err nnn (ssss)\n" to the end of the string it emits
108  static void midi_error(MMRESULT rv, const char *str)
109  {
110      const char * errtxt = "?";
111  
112      switch (rv)
113      {
114          case MMSYSERR_NOERROR:      errtxt = "MMSYSERR_NOERROR";      break;
115          case MMSYSERR_BADDEVICEID:  errtxt = "MMSYSERR_BADDEVICEID";  break;
116          case MMSYSERR_NOTENABLED:   errtxt = "MMSYSERR_NOTENABLED";   break;
117          case MMSYSERR_ALLOCATED:    errtxt = "MMSYSERR_ALLOCATED";    break;
118          case MMSYSERR_INVALHANDLE:  errtxt = "MMSYSERR_INVALHANDLE";  break;
119          case MMSYSERR_NODRIVER:     errtxt = "MMSYSERR_NODRIVER";     break;
120          case MMSYSERR_NOMEM:        errtxt = "MMSYSERR_NOMEM";        break;
121          case MMSYSERR_NOTSUPPORTED: errtxt = "MMSYSERR_NOTSUPPORTED"; break;
122          case MMSYSERR_BADERRNUM:    errtxt = "MMSYSERR_BADERRNUM";    break;
123          case MMSYSERR_INVALFLAG:    errtxt = "MMSYSERR_INVALFLAG";    break;
124          case MMSYSERR_INVALPARAM:   errtxt = "MMSYSERR_INVALPARAM";   break;
125          case MMSYSERR_HANDLEBUSY:   errtxt = "MMSYSERR_HANDLEBUSY";   break;
126          case MMSYSERR_INVALIDALIAS: errtxt = "MMSYSERR_INVALIDALIAS"; break;
127          case MMSYSERR_BADDB:        errtxt = "MMSYSERR_BADDB";        break;
128          case MMSYSERR_KEYNOTFOUND:  errtxt = "MMSYSERR_KEYNOTFOUND";  break;
129          case MMSYSERR_READERROR:    errtxt = "MMSYSERR_READERROR";    break;
130          case MMSYSERR_WRITEERROR:   errtxt = "MMSYSERR_WRITEERROR";   break;
131          case MMSYSERR_DELETEERROR:  errtxt = "MMSYSERR_DELETEERROR";  break;
132          case MMSYSERR_VALNOTFOUND:  errtxt = "MMSYSERR_VALNOTFOUND";  break;
133          case MMSYSERR_NODRIVERCB:   errtxt = "MMSYSERR_NODRIVERCB";   break;
134          default:                                                      break;
135      }
136      debug_break();
137  
138      LOG_F(ERROR, "%s: %s (0x%08x)", str, errtxt, rv);
139  }
140  
141  // AddressSanitizer bug causes heap destruction with some functions from winmm.lib
142  // see https://vsf-prod.westus.cloudapp.azure.com/content/problem/1100813/false-positive-bad-free-when-using-asan-with-winmm.html
143  static void midi_dispose_buffer(MidiBuffer *node, const char *caller)
144  {
145      if (node->hdr.dwFlags & MHDR_PREPARED)
146      {
147  #if __SANITIZE_ADDRESS__ != 1
148          VLOG_F(LOG_DEBUG, "%s/midi_dispose_buffer unpreparing buffer %p", caller, node);
149          auto rv = midiOutUnprepareHeader((HMIDIOUT)midiStream, &node->hdr, sizeof(MIDIHDR));
150          if (rv != MMSYSERR_NOERROR)
151              midi_error(rv, "midi_dispose_buffer: error in midiOutUnprepareHeader");
152  #endif
153         node->hdr.dwFlags &= ~MHDR_PREPARED;
154      }
155  
156      node->hdr.dwBufferLength = max<DWORD>(MME_MIDI_BUFFER_SPACE, node->hdr.dwBufferLength);
157      LL::Move(node, &spareBufferList);
158  
159      if (++numSpareMidiBuffers > maxSpareMidiBuffers)
160          maxSpareMidiBuffers = numSpareMidiBuffers;
161      VLOG_F(LOG_DEBUG, "MME %s/midi_dispose_buffer recycling buffer %p", caller, node);
162  }
163  
164  static void midi_gc_buffers(void)
165  {
166      if (!midiStreamRunning)
167          return;
168  
169      for (auto node = finishedBufferList.next, next = node->next; node != &finishedBufferList; node = next, next = node->next)
170      {
171          if (node->hdr.dwFlags & MHDR_DONE)
172          {
173              midi_dispose_buffer(node, "midi_gc_buffers");
174          }
175      }
176  
177      // prune excess spare buffers for when users hold down buttons on the music toggle in the menu
178      for (auto node = spareBufferList.next, next = node->next; node != &spareBufferList; node = next, next = node->next)
179      {
180          if (numSpareMidiBuffers < numActiveMidiBuffers)
181              break;
182  
183          LL::Remove(node);
184          Xfree(node);
185          numSpareMidiBuffers--;
186          VLOG_F(LOG_DEBUG, "MME midi_gc_buffers pruning spare buffer %p", node);
187      }
188  }
189  
190  static void midi_free_buffers(void)
191  {
192      //Bassert(activeBufferList.next == activeBufferList.prev);
193      while (activeBufferList.next != activeBufferList.prev)
194          WaitForSingleObject(midiBufferFinishedEvent, INFINITE);
195  
196      for (auto node = finishedBufferList.next, next = node->next; node != &finishedBufferList; node = next, next = node->next)
197      {
198          LL::Move(node, &spareBufferList);
199          numSpareMidiBuffers++;
200      }
201  
202      for (auto node = spareBufferList.next, next = node->next; node != &spareBufferList; node = next, next = node->next)
203      {
204          LL::Remove(node);
205          Xfree(node);
206          numSpareMidiBuffers--;
207          VLOG_F(LOG_DEBUG, "MME midi_free_buffers pruning spare buffer %p", node);
208      }
209  
210      Bassert(numSpareMidiBuffers == 0);
211  
212      numActiveMidiBuffers = 0;
213      currentMidiBuffer = nullptr;
214  }
215  
216  static void midi_flush_current_buffer(void)
217  {
218      BOOL needsPrepare = FALSE;
219      BOOL running = midiStreamRunning;
220  
221      if (!currentMidiBuffer)
222      {
223          //LOG_F(INFO, "no buffer");
224          return;
225      }
226      auto evt = (MIDIEVENT *)&currentMidiBuffer->data[0];
227  
228      if (!running)
229      {
230          // immediate messages don't use a MIDIEVENT header so strip it off and
231          // make some adjustments
232  
233          if (currentMidiBuffer->hdr.dwBytesRecorded)
234          {
235              currentMidiBuffer->hdr.dwBufferLength  = currentMidiBuffer->hdr.dwBytesRecorded - 12;
236              currentMidiBuffer->hdr.dwBytesRecorded = 0;
237          }
238  
239          if (currentMidiBuffer->hdr.dwBufferLength > 0)
240              needsPrepare = TRUE;
241      }
242      else
243          needsPrepare = TRUE;
244  
245      if (needsPrepare)
246      {
247          Bassert((currentMidiBuffer->hdr.dwFlags & MHDR_PREPARED) == 0);
248          // playing a file, or sending a sysex when not playing means
249          // we need to prepare the buffer
250          currentMidiBuffer->hdr.dwBufferLength = currentMidiBuffer->hdr.dwBytesRecorded;
251          auto rv = midiOutPrepareHeader((HMIDIOUT)midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
252          if (rv != MMSYSERR_NOERROR)
253          {
254              midi_error(rv, "midi_flush_current_buffer: error in midiOutPrepareHeader");
255              return;
256          }
257      }
258  
259      if (running)
260      {
261          // midi file playing, so send events to the stream
262          auto rv = midiStreamOut(midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
263          if (rv != MMSYSERR_NOERROR)
264              midi_error(rv, "midi_flush_current_buffer: error in midiStreamOut");
265  
266          //LOG_F(INFO, "MME midi_flush_current_buffer queued buffer %p", currentMidiBuffer);
267      }
268      else
269      {
270          // midi file not playing, so send immediately
271          if (currentMidiBuffer->hdr.dwBufferLength > 0)
272          {
273              currentMidiBuffer->hdr.dwUser = 0x1337;
274              auto rv = midiOutLongMsg((HMIDIOUT)midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
275              if (rv == MMSYSERR_NOERROR)
276              {
277                  // busy-wait for Windows to be done with it
278                  WaitForSingleObject(midiBufferFinishedEvent, INFINITE);
279  
280                  //LOG_F(INFO, "MME midi_flush_current_buffer sent immediate long");
281              }
282              else
283                  midi_error(rv, "midi_flush_current_buffer: error in midiOutLongMsg");
284  
285              midi_dispose_buffer(currentMidiBuffer, "midi_flush_current_buffer");
286              currentMidiBuffer = 0;
287          }
288          else
289          {
290              auto rv = midiOutShortMsg((HMIDIOUT)midiStream, evt->dwEvent);
291              if (rv != MMSYSERR_NOERROR)
292                  midi_error(rv, "midi_flush_current_buffer: error in midiOutShortMsg");
293          }
294          return;
295      }
296  
297      LL::Insert((MidiBuffer *)&activeBufferList, currentMidiBuffer);
298  
299      if (++numActiveMidiBuffers > maxActiveMidiBuffers)
300          maxActiveMidiBuffers = numActiveMidiBuffers;
301  
302      currentMidiBuffer = 0;
303  }
304  
305  static void midi_setup_event(int length, unsigned char **data)
306  {
307      int i = currentMidiBuffer->hdr.dwBytesRecorded / sizeof(DWORD);
308      auto evt = (MIDIEVENT *)&currentMidiBuffer->data[i];
309  
310      evt->dwDeltaTime = midiThread ? (midiThreadTimer - midiLastEventTime) : 0;
311      evt->dwStreamID  = 0;
312  
313      if (length <= 3)
314      {
315          evt->dwEvent = (DWORD)MEVT_SHORTMSG << 24;
316          *data        = (unsigned char *)&evt->dwEvent;
317      }
318      else
319      {
320          evt->dwEvent = ((DWORD)MEVT_LONGMSG << 24) | (length & 0x00ffffff);
321          *data        = (unsigned char *)&evt->dwParms[0];
322      }
323  }
324  
325  /* Gets space in the buffer presently being filled.
326     If insufficient space can be found in the buffer,
327     what is there is flushed to the stream and a new
328     buffer large enough is allocated.
329  
330     Returns a pointer to starting writing at in 'data'.
331   */
332  static BOOL midi_get_buffer(int length, unsigned char **data)
333  {
334      BOOL const running = midiStreamRunning;
335      uint32_t    datalen;
336  
337      // determine the space to alloc.
338      // the size of a MIDIEVENT is 3*sizeof(DWORD) = 12.
339      // short messages need only that amount of space.
340      // long messages need additional space equal to the length of
341      //    the message, padded to 4 bytes
342  
343      if (length <= 3)
344          datalen = 12;
345      else
346          datalen = 12 + ((length + 3) & ~3);
347  
348      if (midiStreamRunning && currentMidiBuffer && currentMidiBuffer->hdr.dwBytesRecorded + datalen <= MME_MIDI_BUFFER_SPACE)
349      {
350          // there was enough space in the current buffer, so hand that back
351          midi_setup_event(length, data);
352          currentMidiBuffer->hdr.dwBytesRecorded += datalen;
353          return TRUE;
354      }
355  
356      if (currentMidiBuffer)
357      {
358          // not enough space in the current buffer to accommodate the
359          // new data, so flush it to the stream
360          midi_flush_current_buffer();
361          currentMidiBuffer = 0;
362      }
363  
364      // check if there's a spare buffer big enough to hold the message
365      if (running)
366      {
367          for (auto node = spareBufferList.next; node != &spareBufferList; node = node->next)
368          {
369              if (node->hdr.dwBufferLength >= datalen)
370              {
371                  // yes!
372                  LL::Remove(node);
373                  numSpareMidiBuffers--;
374                  node->hdr.dwBytesRecorded = 0;
375                  Bmemset(node->hdr.lpData, 0, node->hdr.dwBufferLength);
376  
377                  currentMidiBuffer = node;
378  
379                  VLOG_F(LOG_DEBUG, "MME midi_get_buffer fetched buffer %p", node);
380                  break;
381              }
382          }
383      }
384  
385      if (!currentMidiBuffer)
386      {
387          // there were no spare buffers, or none were big enough, so allocate a new one
388          int const size = max(MME_MIDI_BUFFER_SPACE, datalen);
389          auto      node = (MidiBuffer *)Xcalloc(1, sizeof(MidiBuffer) + size);
390  
391          node->hdr.dwUser = (DWORD_PTR)node;
392          node->hdr.lpData = (LPSTR)node->data;
393  
394          node->hdr.dwBufferLength  = size;
395          node->hdr.dwBytesRecorded = 0;
396  
397          currentMidiBuffer = node;
398          LL::Reset(node);
399          VLOG_F(LOG_DEBUG, "MME midi_get_buffer allocated buffer %p", node);
400      }
401  
402      midi_setup_event(length, data);
403  
404      currentMidiBuffer->hdr.dwBytesRecorded += datalen;
405  
406      return TRUE;
407  }
408  
409  static inline void midi_sequence_event(void)
410  {
411      if (!midiThread)
412      {
413          // a midi event being sent out of playback (streaming) mode
414          midi_flush_current_buffer();
415          return;
416      }
417  
418      //LOG_F(INFO, "MME midi_sequence_event buffered");
419  
420      midiLastEventTime = midiThreadTimer;
421  }
422  
423  static void MME_NoteOff(int channel, int key, int velocity)
424  {
425      unsigned char *data;
426  
427      if (midi_get_buffer(3, &data))
428      {
429          data[0] = WINMM_NOTE_OFF | channel;
430          data[1] = key;
431          data[2] = velocity;
432          midi_sequence_event();
433      }
434      else
435          LOG_F(ERROR, "Error in MME_NoteOff()");
436  }
437  
438  static void MME_NoteOn(int channel, int key, int velocity)
439  {
440      unsigned char *data;
441  
442      if (midi_get_buffer(3, &data))
443      {
444          data[0] = WINMM_NOTE_ON | channel;
445          data[1] = key;
446          data[2] = velocity;
447          midi_sequence_event();
448      }
449      else
450          LOG_F(ERROR, "Error in MME_NoteOn()");
451  }
452  
453  static void MME_PolyAftertouch(int channel, int key, int pressure)
454  {
455      unsigned char *data;
456  
457      if (midi_get_buffer(3, &data))
458      {
459          data[0] = WINMM_POLY_AFTER_TCH | channel;
460          data[1] = key;
461          data[2] = pressure;
462          midi_sequence_event();
463      }
464      else
465          LOG_F(ERROR, "Error in MME_PolyAftertouch()");
466  }
467  
468  static void MME_ControlChange(int channel, int number, int value)
469  {
470      unsigned char *data;
471  
472      if (midi_get_buffer(3, &data))
473      {
474          data[0] = WINMM_CONTROL_CHANGE | channel;
475          data[1] = number;
476          data[2] = value;
477          midi_sequence_event();
478      }
479      else
480          LOG_F(ERROR, "Error in MME_ControlChange()");
481  }
482  
483  static void MME_ProgramChange(int channel, int program)
484  {
485      unsigned char *data;
486  
487      if (midi_get_buffer(2, &data))
488      {
489          data[0] = WINMM_PROGRAM_CHANGE | channel;
490          data[1] = program;
491          midi_sequence_event();
492      }
493      else
494          LOG_F(ERROR, "Error in MME_ProgramChange()");
495  }
496  
497  static void MME_ChannelAftertouch(int channel, int pressure)
498  {
499      unsigned char *data;
500  
501      if (midi_get_buffer(2, &data))
502      {
503          data[0] = WINMM_AFTER_TOUCH | channel;
504          data[1] = pressure;
505          midi_sequence_event();
506      }
507      else
508          LOG_F(ERROR, "Error in MME_ChannelAftertouch()");
509  }
510  
511  static void MME_PitchBend(int channel, int lsb, int msb)
512  {
513      unsigned char *data;
514  
515      if (midi_get_buffer(3, &data))
516      {
517          data[0] = WINMM_PITCH_BEND | channel;
518          data[1] = lsb;
519          data[2] = msb;
520          midi_sequence_event();
521      }
522      else
523          LOG_F(ERROR, "Error in MME_PitchBend()");
524  }
525  
526  static void MME_SysEx(const unsigned char *data, int length)
527  {
528      unsigned char *wdata;
529  
530      if (midi_get_buffer(length, &wdata))
531      {
532          Bmemcpy(wdata, data, length);
533          midi_sequence_event();
534      }
535      else
536          LOG_F(ERROR, "Error in MME_SysEx()");
537  }
538  
539  void WinMMDrv_MIDI_PrintDevices(void)
540  {
541      auto numDevices = (int)midiOutGetNumDevs();
542      MIDIOUTCAPS midicaps;
543  
544      for (int i = -1; i < numDevices; i++)
545      {
546          if (!midiOutGetDevCaps(i, &midicaps, sizeof(MIDIOUTCAPS)))
547              LOG_F(INFO, "%d: %s  ", i, midicaps.szPname);
548      }
549  }
550  
551  int WinMMDrv_MIDI_PrintBufferInfo(osdcmdptr_t UNUSED(parm))
552  {
553      UNREFERENCED_CONST_PARAMETER(parm);
554      LOG_F(INFO, "MME MIDI buffers:");
555      LOG_F(INFO, "%6s: %d (max %d)", "active", numActiveMidiBuffers, maxActiveMidiBuffers);
556      LOG_F(INFO, "%6s: %d (max %d)", "spare", numSpareMidiBuffers, maxSpareMidiBuffers);
557      return OSDCMD_OK;
558  }
559  
560  int WinMMDrv_MIDI_GetNumDevices(void) { return midiOutGetNumDevs(); }
561  
562  int WinMMDrv_MIDI_Init(midifuncs * funcs)
563  {
564      if (midiInstalled)
565          WinMMDrv_MIDI_Shutdown();
566  
567      LL::Reset(&finishedBufferList);
568      LL::Reset(&activeBufferList);
569      LL::Reset(&spareBufferList);
570  
571      Bmemset(funcs, 0, sizeof(midifuncs));
572  
573      if ((midiMutex = CreateMutex(0, FALSE, 0)) == 0)
574      {
575          ErrorCode = WinMMErr_MIDICreateMutex;
576          return WinMMErr_Error;
577      }
578  
579      if ((midiBufferFinishedEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr)) == 0)
580      {
581          ErrorCode = WinMMErr_MIDICreateEvent;
582          return WinMMErr_Error;
583      }
584  
585      MIDIOUTCAPS midicaps;
586  
587      if (WinMM_DeviceID > midiOutGetNumDevs() || midiOutGetDevCaps(WinMM_DeviceID, &midicaps, sizeof(MIDIOUTCAPS)))
588          WinMM_DeviceID = MIDI_MAPPER;
589  
590      if (!midiOutGetDevCaps(WinMM_DeviceID, &midicaps, sizeof(MIDIOUTCAPS)))
591          LOG_F(INFO, ": [%d] %s", WinMM_DeviceID, midicaps.szPname);
592  
593      auto rv = midiStreamOpen(&midiStream, &WinMM_DeviceID, 1, (DWORD_PTR)midi_callback, (DWORD_PTR)0, CALLBACK_FUNCTION);
594  
595      if (rv != MMSYSERR_NOERROR)
596      {
597          WinMMDrv_MIDI_Shutdown();
598          midi_error(rv, "WinMMDrv_MIDI_Init: error in midiStreamOpen");
599          ErrorCode = WinMMErr_MIDIStreamOpen;
600          return WinMMErr_Error;
601      }
602  
603      funcs->NoteOff           = MME_NoteOff;
604      funcs->NoteOn            = MME_NoteOn;
605      funcs->PolyAftertouch    = MME_PolyAftertouch;
606      funcs->ControlChange     = MME_ControlChange;
607      funcs->ProgramChange     = MME_ProgramChange;
608      funcs->ChannelAftertouch = MME_ChannelAftertouch;
609      funcs->PitchBend         = MME_PitchBend;
610      funcs->SysEx             = MME_SysEx;
611  
612      midiInstalled = TRUE;
613  
614      return WinMMErr_Ok;
615  }
616  
617  void WinMMDrv_MIDI_Shutdown(void)
618  {
619      WinMMDrv_MIDI_HaltPlayback();
620      midi_free_buffers();
621  
622      if (midiStream)
623      {
624          // LOG_F(INFO, "stopping stream");
625          auto rv = midiStreamClose(midiStream);
626          if (rv != MMSYSERR_NOERROR)
627              midi_error(rv, "WinMMDrv_MIDI_Shutdown: error in midiStreamClose");
628          // LOG_F(INFO, "stream stopped");
629  
630          midiStream = 0;
631      }
632  
633      midi_destroy_thread();
634  
635      if (midiMutex)
636      {
637          CloseHandle(midiMutex);
638          midiMutex = 0;
639      }
640  
641      if (midiBufferFinishedEvent)
642      {
643          CloseHandle(midiBufferFinishedEvent);
644          midiBufferFinishedEvent = 0;
645      }
646  
647      midiInstalled = FALSE;
648  
649      VLOG_F(LOG_DEBUG, "MME finished, max active buffers: %d max spare buffers: %d", maxActiveMidiBuffers, maxSpareMidiBuffers);
650  }
651  
652  static DWORD midi_get_tick(void)
653  {
654      if (!midiStreamRunning)
655          return 0;
656  
657      MMTIME mmtime = { TIME_TICKS, 0 };
658  
659      auto rv = midiStreamPosition(midiStream, &mmtime, sizeof(MMTIME));
660      if (rv != MMSYSERR_NOERROR)
661      {
662          midi_error(rv, "midi_get_tick: error in midiStreamPosition");
663          return 0;
664      }
665  
666      return mmtime.u.ticks;
667  }
668  
669  static void CALLBACK midi_callback(HMIDIOUT out, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
670  {
671      UNREFERENCED_PARAMETER(out);
672      UNREFERENCED_PARAMETER(dwInstance);
673      UNREFERENCED_PARAMETER(dwParam2);
674  
675      if (msg != MOM_DONE)
676          return;
677  
678      WinMMDrv_MIDI_Lock();
679      for (auto node = activeBufferList.next; node != &activeBufferList; node = node->next)
680      {
681          if (&node->hdr == (MIDIHDR *)dwParam1)
682          {
683              Bassert(node->hdr.dwUser != 0x1337);
684              numActiveMidiBuffers--;
685              LL::Move(node, &finishedBufferList);
686              SetEvent(midiBufferFinishedEvent);
687              WinMMDrv_MIDI_Unlock();
688              return;
689          }
690      }
691      debug_break();
692      WinMMDrv_MIDI_Unlock();
693  }
694  
695  static unsigned WINAPI midiDataThread(LPVOID lpParameter)
696  {
697      UNREFERENCED_PARAMETER(lpParameter);
698  
699      debugThreadName("midiDataThread");
700  
701      DWORD sleepAmount = 100 / MME_THREAD_QUEUE_INTERVAL;
702  
703      do
704      {
705          HANDLE const events[3] = { midiThreadQuitEvent, midiBufferFinishedEvent, midiThreadResetEvent };
706          auto result = WaitForMultipleObjects(3, events, false, sleepAmount);
707  
708          if (result != WAIT_OBJECT_0 && midiStreamRunning == false)
709              continue;
710  
711          switch (result)
712          {
713          case WAIT_OBJECT_0+2: // midiThreadResetEvent
714              midiThreadTimer      = midi_get_tick();
715              midiLastEventTime    = midiThreadTimer;
716              midiThreadQueueTimer = midiThreadTimer + midiThreadQueueTicks;
717              break;
718          case WAIT_OBJECT_0+1: // midiBufferFinishedEvent
719              sleepAmount = 0;
720              WinMMDrv_MIDI_Lock();
721              midi_gc_buffers();
722              WinMMDrv_MIDI_Unlock();
723              break;
724          case WAIT_TIMEOUT:
725          {
726              // queue a tick
727              WinMMDrv_MIDI_Lock();
728              DWORD sequenceTime = midi_get_tick();
729              sleepAmount = 100 / MME_THREAD_QUEUE_INTERVAL;
730  
731              if (((int64_t)midiThreadTimer - (int64_t)sequenceTime) > midiThreadQueueTicks)
732              {
733                  // we're running ahead, so sleep for half the usual
734                  // amount and try again
735                  sleepAmount /= 2;
736                  WinMMDrv_MIDI_Unlock();
737                  continue;
738              }
739  
740              midiThreadQueueTimer = sequenceTime + midiThreadQueueTicks;
741              while (midiThreadTimer < midiThreadQueueTimer)
742              {
743                  WinMMDrv_MIDI_Service();
744                  midiThreadTimer++;
745              }
746              midi_flush_current_buffer();
747              WinMMDrv_MIDI_Unlock();
748              break;
749          }
750          case WAIT_OBJECT_0: // midiThreadQuitEvent
751              return 0;
752          }
753      } while (1);
754  
755      return 0;
756  }
757  
758  int midi_create_thread(void)
759  {
760      midiThreadQuitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
761      if (!midiThreadQuitEvent)
762      {
763          ErrorCode = WinMMErr_MIDICreateEvent;
764          return WinMMErr_Error;
765      }
766  
767      midiThreadResetEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
768      if (!midiThreadResetEvent)
769      {
770          ErrorCode = WinMMErr_MIDICreateEvent;
771          return WinMMErr_Error;
772      }
773  
774      midiThread = (HANDLE)_beginthreadex(nullptr, 0, midiDataThread, 0, 0, 0);
775      if (!midiThread)
776      {
777          ErrorCode = WinMMErr_MIDIPlayThread;
778          return WinMMErr_Error;
779      }
780  
781      return WinMMErr_Ok;
782  }
783  
784  void midi_destroy_thread(void)
785  {
786      if (midiThread)
787      {
788          SetEvent(midiThreadQuitEvent);
789          WaitForSingleObject(midiThread, INFINITE);
790          // LOG_F(INFO,"MME MIDI_HaltPlayback synched");
791          CloseHandle(midiThread);
792          midiThread = 0;
793          CloseHandle(midiThreadQuitEvent);
794          midiThreadQuitEvent = 0;
795          CloseHandle(midiThreadResetEvent);
796          midiThreadResetEvent = 0;
797      }
798  }
799  
800  int WinMMDrv_MIDI_StartPlayback(void)
801  {
802      WinMMDrv_MIDI_HaltPlayback();
803  
804      auto rv = midiStreamRestart(midiStream);
805      if (rv != MMSYSERR_NOERROR)
806      {
807          midi_error(rv, "WinMMDrv_MIDI_StartPlayback: error in midiStreamRestart");
808          WinMMDrv_MIDI_HaltPlayback();
809          ErrorCode = WinMMErr_MIDIStreamRestart;
810          return WinMMErr_Error;
811      }
812  
813      if (!midiThread && midi_create_thread() != WinMMErr_Ok)
814          return WinMMErr_Error;
815  
816      midiLastDivision = 0;
817  
818      if (midiThread)
819          SetEvent(midiThreadResetEvent);
820  
821      midiStreamRunning = TRUE;
822  
823      return WinMMErr_Ok;
824  }
825  
826  
827  void WinMMDrv_MIDI_HaltPlayback(void)
828  {
829      midiStreamRunning = FALSE;
830  }
831  
832  void WinMMDrv_MIDI_SetTempo(int tempo, int division)
833  {
834      //LOG_F(INFO, "MIDI_SetTempo %d/%d", tempo, division);
835      MIDIPROPTEMPO   propTempo   = { sizeof(MIDIPROPTEMPO), (DWORD)(60000000l / tempo) };
836      MIDIPROPTIMEDIV propTimediv = { sizeof(MIDIPROPTIMEDIV), (DWORD)division };
837  
838      if (midiLastDivision != division)
839      {
840          auto rv = midiStreamStop(midiStream);
841          if (rv != MMSYSERR_NOERROR)
842              midi_error(rv, "WinMMDrv_MIDI_SetTempo: error in midiStreamStop");
843  
844          rv = midiStreamProperty(midiStream, (LPBYTE)&propTimediv, MIDIPROP_SET | MIDIPROP_TIMEDIV);
845          if (rv != MMSYSERR_NOERROR)
846              midi_error(rv, "WinMMDrv_MIDI_SetTempo: error in midiStreamProperty (MIDIPROP_TIMEDIV)");
847      }
848  
849      auto rv = midiStreamProperty(midiStream, (LPBYTE)&propTempo, MIDIPROP_SET | MIDIPROP_TEMPO);
850      if (rv != MMSYSERR_NOERROR)
851          midi_error(rv, "WinMMDrv_MIDI_SetTempo: error in midiStreamProperty (MIDIPROP_TEMPO)");
852  
853      if (midiLastDivision != division)
854      {
855          if (midiStreamRunning)
856              midiStreamRestart(midiStream);
857  
858          midiLastDivision = division;
859      }
860  
861      midiThreadQueueTicks = (int)ceil((((double)tempo * (double)division) / 60.0) / (double)MME_THREAD_QUEUE_INTERVAL);
862      if (midiThreadQueueTicks <= 0)
863          midiThreadQueueTicks = 1;
864  }
865  
866  void WinMMDrv_MIDI_Lock(void)
867  {
868      DWORD err = WaitForSingleObject(midiMutex, INFINITE);
869      if (err != WAIT_OBJECT_0)
870          LOG_F(ERROR, "Error in WinMMDrv_MIDI_Lock(): WaitForSingleObject() returned %d", (int) err);
871  }
872  
873  void WinMMDrv_MIDI_Unlock(void)  { ReleaseMutex(midiMutex); }
874  void WinMMDrv_MIDI_Service(void) { MIDI_ServiceRoutine(); }