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 *)¤tMidiBuffer->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, ¤tMidiBuffer->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, ¤tMidiBuffer->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, ¤tMidiBuffer->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 *)¤tMidiBuffer->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(); }