1 /*
2 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20
21 /**
22 * WinMM CD and MIDI output driver
23 */
24
25 #define WIN32_LEAN_AND_MEAN
26 #include <windows.h>
27 #include <mmsystem.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <math.h>
31 #include <stdint.h>
32 #include <stdarg.h>
33 #include <assert.h>
34
35 #include "midifuncs.h"
36 #include "driver_winmm.h"
37 #include "linklist.h"
38
39 #ifdef _MSC_VER
40 #define inline _inline
41 #endif
42
43 enum {
44 WinMMErr_Warning = -2,
45 WinMMErr_Error = -1,
46 WinMMErr_Ok = 0,
47 WinMMErr_Uninitialised,
48 WinMMErr_NotifyWindow,
49 WinMMErr_CDMCIOpen,
50 WinMMErr_CDMCISetTimeFormat,
51 WinMMErr_CDMCIPlay,
52 WinMMErr_MIDIStreamOpen,
53 WinMMErr_MIDIStreamRestart,
54 WinMMErr_MIDICreateEvent,
55 WinMMErr_MIDIPlayThread,
56 WinMMErr_MIDICreateMutex
57 };
58
59 static int ErrorCode = WinMMErr_Ok;
60
61 enum {
62 UsedByNothing = 0,
63 UsedByMIDI = 1,
64 UsedByCD = 2
65 };
66
67 static HWND notifyWindow = 0;
68 static int notifyWindowClassRegistered = 0;
69 static int notifyWindowUsedBy = UsedByNothing;
70
71 static UINT cdDeviceID = 0;
72 static DWORD cdPausePosition = 0;
73 static int cdPaused = 0;
74 static int cdLoop = 0;
75 static int cdPlayTrack = 0;
76
77 static BOOL midiInstalled = FALSE;
78 static HMIDISTRM midiStream = 0;
79 static UINT midiDeviceID = MIDI_MAPPER;
80 static void (*midiThreadService)(void) = 0;
81 static unsigned int midiThreadTimer = 0;
82 static unsigned int midiLastEventTime = 0;
83 static unsigned int midiThreadQueueTimer = 0;
84 static unsigned int midiThreadQueueTicks = 0;
85 static HANDLE midiThread = 0;
86 static HANDLE midiThreadQuitEvent = 0;
87 static HANDLE midiMutex = 0;
88 static BOOL midiStreamRunning = FALSE;
89 static int midiLastDivision = 0;
90 #define THREAD_QUEUE_INTERVAL 10 // 1/10 sec
91 #define MIDI_BUFFER_SPACE (12*128) // 128 note-on events
92
93 typedef struct MidiBuffer {
94 struct MidiBuffer *next;
95 struct MidiBuffer *prev;
96
97 BOOL prepared;
98 MIDIHDR hdr;
99 } MidiBuffer;
100
101 static volatile MidiBuffer activeMidiBuffers;
102 static volatile MidiBuffer spareMidiBuffers;
103 static MidiBuffer *currentMidiBuffer = 0;
104
105
106 #define MIDI_NOTE_OFF 0x80
107 #define MIDI_NOTE_ON 0x90
108 #define MIDI_POLY_AFTER_TCH 0xA0
109 #define MIDI_CONTROL_CHANGE 0xB0
110 #define MIDI_PROGRAM_CHANGE 0xC0
111 #define MIDI_AFTER_TOUCH 0xD0
112 #define MIDI_PITCH_BEND 0xE0
113 #define MIDI_META_EVENT 0xFF
114 #define MIDI_END_OF_TRACK 0x2F
115 #define MIDI_TEMPO_CHANGE 0x51
116 #define MIDI_MONO_MODE_ON 0x7E
117 #define MIDI_ALL_NOTES_OFF 0x7B
118
119
120
121
WinMMDrv_GetError(void)122 int WinMMDrv_GetError(void)
123 {
124 return ErrorCode;
125 }
126
WinMMDrv_ErrorString(int ErrorNumber)127 const char *WinMMDrv_ErrorString( int ErrorNumber )
128 {
129 const char *ErrorString;
130
131 switch( ErrorNumber )
132 {
133 case WinMMErr_Warning :
134 case WinMMErr_Error :
135 ErrorString = WinMMDrv_ErrorString( ErrorCode );
136 break;
137
138 case WinMMErr_Ok :
139 ErrorString = "WinMM ok.";
140 break;
141
142 case WinMMErr_Uninitialised:
143 ErrorString = "WinMM uninitialised.";
144 break;
145
146 case WinMMErr_NotifyWindow:
147 ErrorString = "Failed creating notification window for CD/MIDI.";
148 break;
149
150 case WinMMErr_CDMCIOpen:
151 ErrorString = "MCI error: failed opening CD audio device.";
152 break;
153
154 case WinMMErr_CDMCISetTimeFormat:
155 ErrorString = "MCI error: failed setting time format for CD audio device.";
156 break;
157
158 case WinMMErr_CDMCIPlay:
159 ErrorString = "MCI error: failed playing CD audio track.";
160 break;
161
162 case WinMMErr_MIDIStreamOpen:
163 ErrorString = "MIDI error: failed opening stream.";
164 break;
165
166 case WinMMErr_MIDIStreamRestart:
167 ErrorString = "MIDI error: failed starting stream.";
168 break;
169
170 case WinMMErr_MIDICreateEvent:
171 ErrorString = "MIDI error: failed creating play thread quit event.";
172 break;
173
174 case WinMMErr_MIDIPlayThread:
175 ErrorString = "MIDI error: failed creating play thread.";
176 break;
177
178 case WinMMErr_MIDICreateMutex:
179 ErrorString = "MIDI error: failed creating play mutex.";
180 break;
181
182 default:
183 ErrorString = "Unknown WinMM error code.";
184 break;
185 }
186
187 return ErrorString;
188
189 }
190
191
notifyWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)192 static LRESULT CALLBACK notifyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
193 {
194 switch (uMsg) {
195 case MM_MCINOTIFY:
196 if (wParam == MCI_NOTIFY_SUCCESSFUL && lParam == cdDeviceID) {
197 if (cdLoop && cdPlayTrack) {
198 WinMMDrv_CD_Play(cdPlayTrack, 1);
199 }
200 }
201 break;
202 default: break;
203 }
204 return DefWindowProc(hwnd, uMsg, wParam, lParam);
205 }
206
openNotifyWindow(int useby)207 static int openNotifyWindow(int useby)
208 {
209 if (!notifyWindow) {
210 if (!notifyWindowClassRegistered) {
211 WNDCLASS wc;
212
213 memset(&wc, 0, sizeof(wc));
214 wc.lpfnWndProc = notifyWindowProc;
215 wc.hInstance = GetModuleHandle(NULL);
216 wc.lpszClassName = "JFAudiolibNotifyWindow";
217
218 if (!RegisterClass(&wc)) {
219 return 0;
220 }
221
222 notifyWindowClassRegistered = 1;
223 }
224
225 notifyWindow = CreateWindow("JFAudiolibNotifyWindow", "", WS_POPUP,
226 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), NULL);
227 if (!notifyWindow) {
228 return 0;
229 }
230 }
231
232 notifyWindowUsedBy |= useby;
233
234 return 1;
235 }
236
closeNotifyWindow(int useby)237 static void closeNotifyWindow(int useby)
238 {
239 notifyWindowUsedBy &= ~useby;
240
241 if (!notifyWindowUsedBy && notifyWindow) {
242 DestroyWindow(notifyWindow);
243 notifyWindow = 0;
244 }
245 }
246
247
WinMMDrv_CD_Init(void)248 int WinMMDrv_CD_Init(void)
249 {
250 MCI_OPEN_PARMS mciopenparms;
251 MCI_SET_PARMS mcisetparms;
252 DWORD rv;
253
254 WinMMDrv_CD_Shutdown();
255
256 mciopenparms.lpstrDeviceType = "cdaudio";
257 rv = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE, (DWORD_PTR) &mciopenparms);
258 if (rv) {
259 fprintf(stderr, "WinMM CD_Init MCI_OPEN err %d\n", (int) rv);
260 ErrorCode = WinMMErr_CDMCIOpen;
261 return WinMMErr_Error;
262 }
263
264 cdDeviceID = mciopenparms.wDeviceID;
265
266 mcisetparms.dwTimeFormat = MCI_FORMAT_MSF;
267 rv = mciSendCommand(cdDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR) &mcisetparms);
268 if (rv) {
269 fprintf(stderr, "WinMM CD_Init MCI_SET err %d\n", (int) rv);
270 mciSendCommand(cdDeviceID, MCI_CLOSE, 0, 0);
271 cdDeviceID = 0;
272
273 ErrorCode = WinMMErr_CDMCISetTimeFormat;
274 return WinMMErr_Error;
275 }
276
277 if (!openNotifyWindow(UsedByCD)) {
278 mciSendCommand(cdDeviceID, MCI_CLOSE, 0, 0);
279 cdDeviceID = 0;
280
281 ErrorCode = WinMMErr_NotifyWindow;
282 return WinMMErr_Error;
283 }
284
285 return WinMMErr_Ok;
286 }
287
WinMMDrv_CD_Shutdown(void)288 void WinMMDrv_CD_Shutdown(void)
289 {
290 if (cdDeviceID) {
291 WinMMDrv_CD_Stop();
292
293 mciSendCommand(cdDeviceID, MCI_CLOSE, 0, 0);
294 }
295 cdDeviceID = 0;
296
297 closeNotifyWindow(UsedByCD);
298 }
299
WinMMDrv_CD_Play(int track,int loop)300 int WinMMDrv_CD_Play(int track, int loop)
301 {
302 MCI_PLAY_PARMS mciplayparms;
303 MCI_STATUS_PARMS mcistatusparms;
304 DWORD rv, start, end;
305 BYTE minutes, seconds, frames;
306
307 if (!cdDeviceID) {
308 ErrorCode = WinMMErr_Uninitialised;
309 return WinMMErr_Error;
310 }
311
312 cdPlayTrack = track;
313 cdLoop = loop;
314 cdPaused = 0;
315
316 mcistatusparms.dwItem = MCI_STATUS_POSITION;
317 mcistatusparms.dwTrack = track;
318 rv = mciSendCommand(cdDeviceID, MCI_STATUS, MCI_WAIT | MCI_TRACK | MCI_STATUS_ITEM, (DWORD_PTR) &mcistatusparms);
319 if (rv) {
320 fprintf(stderr, "WinMM CD_Play MCI_STATUS position err %d\n", (int) rv);
321 ErrorCode = WinMMErr_CDMCIPlay;
322 return WinMMErr_Error;
323 }
324 start = mcistatusparms.dwReturn;
325
326 mcistatusparms.dwItem = MCI_STATUS_LENGTH;
327 mcistatusparms.dwTrack = track;
328 rv = mciSendCommand(cdDeviceID, MCI_STATUS, MCI_WAIT | MCI_TRACK | MCI_STATUS_ITEM, (DWORD_PTR) &mcistatusparms);
329 if (rv) {
330 fprintf(stderr, "WinMM CD_Play MCI_STATUS length err %d\n", (int) rv);
331 ErrorCode = WinMMErr_CDMCIPlay;
332 return WinMMErr_Error;
333 }
334 end = mcistatusparms.dwReturn;
335
336 minutes = MCI_MSF_MINUTE(start) + MCI_MSF_MINUTE(end);
337 seconds = MCI_MSF_SECOND(start) + MCI_MSF_SECOND(end);
338 frames = MCI_MSF_FRAME(start) + MCI_MSF_FRAME(end);
339 while (frames >= 75) { seconds += 1; frames -= 75; }
340 while (seconds >= 60) { minutes += 1; seconds -= 60; }
341
342 mciplayparms.dwFrom = start;
343 mciplayparms.dwTo = MCI_MAKE_MSF(minutes, seconds, frames);
344 mciplayparms.dwCallback = (DWORD_PTR) notifyWindow;
345 rv = mciSendCommand(cdDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR) &mciplayparms);
346 if (rv) {
347 fprintf(stderr, "WinMM CD_Play MCI_PLAY err %d\n", (int) rv);
348 ErrorCode = WinMMErr_CDMCIPlay;
349 return WinMMErr_Error;
350 }
351
352 return WinMMErr_Ok;
353 }
354
WinMMDrv_CD_Stop(void)355 void WinMMDrv_CD_Stop(void)
356 {
357 MCI_GENERIC_PARMS mcigenparms;
358 DWORD rv;
359
360 if (!cdDeviceID) {
361 return;
362 }
363
364 cdPlayTrack = 0;
365 cdLoop = 0;
366 cdPaused = 0;
367
368 rv = mciSendCommand(cdDeviceID, MCI_STOP, 0, (DWORD_PTR) &mcigenparms);
369 if (rv) {
370 fprintf(stderr, "WinMM CD_Stop MCI_STOP err %d\n", (int) rv);
371 }
372 }
373
WinMMDrv_CD_Pause(int pauseon)374 void WinMMDrv_CD_Pause(int pauseon)
375 {
376 if (!cdDeviceID) {
377 return;
378 }
379
380 if (cdPaused == pauseon) {
381 return;
382 }
383
384 if (pauseon) {
385 MCI_STATUS_PARMS mcistatusparms;
386 MCI_GENERIC_PARMS mcigenparms;
387 DWORD rv;
388
389 mcistatusparms.dwItem = MCI_STATUS_POSITION;
390 rv = mciSendCommand(cdDeviceID, MCI_STATUS, MCI_WAIT | MCI_STATUS_ITEM, (DWORD_PTR) &mcistatusparms);
391 if (rv) {
392 fprintf(stderr, "WinMM CD_Pause MCI_STATUS err %d\n", (int) rv);
393 return;
394 }
395
396 cdPausePosition = mcistatusparms.dwReturn;
397
398 rv = mciSendCommand(cdDeviceID, MCI_STOP, 0, (DWORD_PTR) &mcigenparms);
399 if (rv) {
400 fprintf(stderr, "WinMM CD_Pause MCI_STOP err %d\n", (int) rv);
401 }
402 } else {
403 MCI_PLAY_PARMS mciplayparms;
404 DWORD rv;
405
406 mciplayparms.dwFrom = cdPausePosition;
407 mciplayparms.dwTo = MCI_MAKE_TMSF(cdPlayTrack + 1, 0, 0, 0);
408 mciplayparms.dwCallback = (DWORD_PTR) notifyWindow;
409 rv = mciSendCommand(cdDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR) &mciplayparms);
410 if (rv) {
411 fprintf(stderr, "WinMM CD_Pause MCI_PLAY err %d\n", (int) rv);
412 return;
413 }
414
415 cdPausePosition = 0;
416 }
417
418 cdPaused = pauseon;
419 }
420
WinMMDrv_CD_IsPlaying(void)421 int WinMMDrv_CD_IsPlaying(void)
422 {
423 MCI_STATUS_PARMS mcistatusparms;
424 DWORD rv;
425
426 if (!cdDeviceID) {
427 return 0;
428 }
429
430 mcistatusparms.dwItem = MCI_STATUS_MODE;
431 rv = mciSendCommand(cdDeviceID, MCI_STATUS, MCI_WAIT | MCI_STATUS_ITEM, (DWORD_PTR) &mcistatusparms);
432 if (rv) {
433 fprintf(stderr, "WinMM CD_IsPlaying MCI_STATUS err %d\n", (int) rv);
434 return 0;
435 }
436
437 return (mcistatusparms.dwReturn == MCI_MODE_PLAY);
438 }
439
WinMMDrv_CD_SetVolume(int volume)440 void WinMMDrv_CD_SetVolume(int volume)
441 {
442 }
443
444
445
446 // will append "err nnn (ssss)\n" to the end of the string it emits
midi_error(MMRESULT rv,const char * fmt,...)447 static void midi_error(MMRESULT rv, const char * fmt, ...)
448 {
449 va_list va;
450 const char * errtxt = "?";
451
452 switch (rv) {
453 case MMSYSERR_NOERROR: errtxt = "MMSYSERR_NOERROR"; break;
454 case MMSYSERR_BADDEVICEID: errtxt = "MMSYSERR_BADDEVICEID"; break;
455 case MMSYSERR_NOTENABLED: errtxt = "MMSYSERR_NOTENABLED"; break;
456 case MMSYSERR_ALLOCATED: errtxt = "MMSYSERR_ALLOCATED"; break;
457 case MMSYSERR_INVALHANDLE: errtxt = "MMSYSERR_INVALHANDLE"; break;
458 case MMSYSERR_NODRIVER: errtxt = "MMSYSERR_NODRIVER"; break;
459 case MMSYSERR_NOMEM: errtxt = "MMSYSERR_NOMEM"; break;
460 case MMSYSERR_NOTSUPPORTED: errtxt = "MMSYSERR_NOTSUPPORTED"; break;
461 case MMSYSERR_BADERRNUM: errtxt = "MMSYSERR_BADERRNUM"; break;
462 case MMSYSERR_INVALFLAG: errtxt = "MMSYSERR_INVALFLAG"; break;
463 case MMSYSERR_INVALPARAM: errtxt = "MMSYSERR_INVALPARAM"; break;
464 case MMSYSERR_HANDLEBUSY: errtxt = "MMSYSERR_HANDLEBUSY"; break;
465 case MMSYSERR_INVALIDALIAS: errtxt = "MMSYSERR_INVALIDALIAS"; break;
466 case MMSYSERR_BADDB: errtxt = "MMSYSERR_BADDB"; break;
467 case MMSYSERR_KEYNOTFOUND: errtxt = "MMSYSERR_KEYNOTFOUND"; break;
468 case MMSYSERR_READERROR: errtxt = "MMSYSERR_READERROR"; break;
469 case MMSYSERR_WRITEERROR: errtxt = "MMSYSERR_WRITEERROR"; break;
470 case MMSYSERR_DELETEERROR: errtxt = "MMSYSERR_DELETEERROR"; break;
471 case MMSYSERR_VALNOTFOUND: errtxt = "MMSYSERR_VALNOTFOUND"; break;
472 case MMSYSERR_NODRIVERCB: errtxt = "MMSYSERR_NODRIVERCB"; break;
473 default: break;
474 }
475
476 va_start(va, fmt);
477 vfprintf(stderr, fmt, va);
478 va_end(va);
479
480 fprintf(stderr, " err %d (%s)\n", (int)rv, errtxt);
481 }
482
midi_dispose_buffer(MidiBuffer * node,const char * caller)483 static void midi_dispose_buffer(MidiBuffer * node, const char * caller)
484 {
485 MMRESULT rv;
486
487 if (node->prepared) {
488 rv = midiOutUnprepareHeader( (HMIDIOUT) midiStream, &node->hdr, sizeof(MIDIHDR) );
489 if (rv != MMSYSERR_NOERROR) {
490 midi_error(rv, "WinMM %s/midi_dispose_buffer midiOutUnprepareHeader", caller);
491 }
492 node->prepared = FALSE;
493 }
494
495 if (midiThread) {
496 // remove the node from the activeMidiBuffers list
497 LL_Remove( node, next, prev );
498
499 // when playing, we keep the buffers
500 LL_Add( (MidiBuffer*) &spareMidiBuffers, node, next, prev );
501 //fprintf(stderr, "WinMM %s/midi_dispose_buffer recycling buffer %p\n", caller, node);
502 } else {
503 // when not, we throw them away
504 free(node);
505 //fprintf(stderr, "WinMM %s/midi_dispose_buffer freeing buffer %p\n", caller, node);
506 }
507 }
508
midi_gc_buffers(void)509 static void midi_gc_buffers(void)
510 {
511 MidiBuffer *node, *next;
512
513 for ( node = activeMidiBuffers.next; node != &activeMidiBuffers; node = next ) {
514 next = node->next;
515
516 if (node->hdr.dwFlags & MHDR_DONE) {
517 midi_dispose_buffer(node, "midi_gc_buffers");
518 }
519 }
520 }
521
midi_free_buffers(void)522 static void midi_free_buffers(void)
523 {
524 MidiBuffer *node, *next;
525
526 //fprintf(stderr, "waiting for active buffers to return\n");
527 while (!LL_ListEmpty(&activeMidiBuffers, next, prev)) {
528 // wait for Windows to finish with all the buffers queued
529 midi_gc_buffers();
530 //fprintf(stderr, "waiting...\n");
531 Sleep(10);
532 }
533 //fprintf(stderr, "waiting over\n");
534
535 for ( node = spareMidiBuffers.next; node != &spareMidiBuffers; node = next ) {
536 next = node->next;
537 LL_Remove( node, next, prev );
538 free(node);
539 //fprintf(stderr, "WinMM midi_free_buffers freeing buffer %p\n", node);
540 }
541
542 assert(currentMidiBuffer == 0);
543 }
544
midi_flush_current_buffer(void)545 static void midi_flush_current_buffer(void)
546 {
547 MMRESULT rv;
548 MIDIEVENT * evt;
549 BOOL needsPrepare = FALSE;
550
551 if (!currentMidiBuffer) {
552 return;
553 }
554
555 evt = (MIDIEVENT *) currentMidiBuffer->hdr.lpData;
556
557 if (!midiThread) {
558 // immediate messages don't use a MIDIEVENT header so strip it off and
559 // make some adjustments
560
561 currentMidiBuffer->hdr.dwBufferLength = currentMidiBuffer->hdr.dwBytesRecorded - 12;
562 currentMidiBuffer->hdr.dwBytesRecorded = 0;
563 currentMidiBuffer->hdr.lpData = (LPSTR) &evt->dwParms[0];
564
565 if (currentMidiBuffer->hdr.dwBufferLength > 0) {
566 needsPrepare = TRUE;
567 }
568 } else {
569 needsPrepare = TRUE;
570 }
571
572 if (needsPrepare) {
573 // playing a file, or sending a sysex when not playing means
574 // we need to prepare the buffer
575 rv = midiOutPrepareHeader( (HMIDIOUT) midiStream, ¤tMidiBuffer->hdr, sizeof(MIDIHDR) );
576 if (rv != MMSYSERR_NOERROR) {
577 midi_error(rv, "WinMM midi_flush_current_buffer midiOutPrepareHeader");
578 return;
579 }
580
581 currentMidiBuffer->prepared = TRUE;
582 }
583
584 if (midiThread) {
585 // midi file playing, so send events to the stream
586
587 LL_Add( (MidiBuffer*) &activeMidiBuffers, currentMidiBuffer, next, prev );
588
589 rv = midiStreamOut(midiStream, ¤tMidiBuffer->hdr, sizeof(MIDIHDR));
590 if (rv != MMSYSERR_NOERROR) {
591 midi_error(rv, "WinMM midi_flush_current_buffer midiStreamOut");
592 midi_dispose_buffer(currentMidiBuffer, "midi_flush_current_buffer");
593 return;
594 }
595
596 //fprintf(stderr, "WinMM midi_flush_current_buffer queued buffer %p\n", currentMidiBuffer);
597 } else {
598 // midi file not playing, so send immediately
599
600 if (currentMidiBuffer->hdr.dwBufferLength > 0) {
601 rv = midiOutLongMsg( (HMIDIOUT) midiStream, ¤tMidiBuffer->hdr, sizeof(MIDIHDR) );
602 if (rv == MMSYSERR_NOERROR) {
603 // busy-wait for Windows to be done with it
604 while (!(currentMidiBuffer->hdr.dwFlags & MHDR_DONE)) ;
605
606 //fprintf(stderr, "WinMM midi_flush_current_buffer sent immediate long\n");
607 } else {
608 midi_error(rv, "WinMM midi_flush_current_buffer midiOutLongMsg");
609 }
610 } else {
611 rv = midiOutShortMsg( (HMIDIOUT) midiStream, evt->dwEvent );
612 if (rv == MMSYSERR_NOERROR) {
613 //fprintf(stderr, "WinMM midi_flush_current_buffer sent immediate short\n");
614 } else {
615 midi_error(rv, "WinMM midi_flush_current_buffer midiOutShortMsg");
616 }
617 }
618
619 midi_dispose_buffer(currentMidiBuffer, "midi_flush_current_buffer");
620 }
621
622 currentMidiBuffer = 0;
623 }
624
midi_setup_event(int length,unsigned char ** data)625 static void midi_setup_event(int length, unsigned char ** data)
626 {
627 MIDIEVENT * evt;
628
629 evt = (MIDIEVENT *) ((intptr_t) currentMidiBuffer->hdr.lpData +
630 currentMidiBuffer->hdr.dwBytesRecorded);
631
632 evt->dwDeltaTime = midiThread ? (midiThreadTimer - midiLastEventTime) : 0;
633 evt->dwStreamID = 0;
634
635 if (length <= 3) {
636 evt->dwEvent = (DWORD)MEVT_SHORTMSG << 24;
637 *data = (unsigned char *) &evt->dwEvent;
638 } else {
639 evt->dwEvent = ((DWORD)MEVT_LONGMSG << 24) | (length & 0x00ffffff);
640 *data = (unsigned char *) &evt->dwParms[0];
641 }
642 }
643
644 /* Gets space in the buffer presently being filled.
645 If insufficient space can be found in the buffer,
646 what is there is flushed to the stream and a new
647 buffer large enough is allocated.
648
649 Returns a pointer to starting writing at in 'data'.
650 */
midi_get_buffer(int length,unsigned char ** data)651 static BOOL midi_get_buffer(int length, unsigned char ** data)
652 {
653 int datalen;
654 MidiBuffer * node;
655
656 // determine the space to alloc.
657 // the size of a MIDIEVENT is 3*sizeof(DWORD) = 12.
658 // short messages need only that amount of space.
659 // long messages need additional space equal to the length of
660 // the message, padded to 4 bytes
661
662 if (length <= 3) {
663 datalen = 12;
664 } else {
665 datalen = 12 + length;
666 if ((datalen & 3) > 0) {
667 datalen += 4 - (datalen & 3);
668 }
669 }
670
671 if (!midiThread) {
672 assert(currentMidiBuffer == 0);
673 }
674
675 if (currentMidiBuffer && (currentMidiBuffer->hdr.dwBufferLength -
676 currentMidiBuffer->hdr.dwBytesRecorded) >= datalen) {
677 // there was enough space in the current buffer, so hand that back
678 midi_setup_event(length, data);
679
680 currentMidiBuffer->hdr.dwBytesRecorded += datalen;
681
682 return TRUE;
683 }
684
685 if (currentMidiBuffer) {
686 // not enough space in the current buffer to accommodate the
687 // new data, so flush it to the stream
688 midi_flush_current_buffer();
689 currentMidiBuffer = 0;
690 }
691
692 // check if there's a spare buffer big enough to hold the message
693 if (midiThread) {
694 for ( node = spareMidiBuffers.next; node != &spareMidiBuffers; node = node->next ) {
695 if (node->hdr.dwBufferLength >= datalen) {
696 // yes!
697 LL_Remove( node, next, prev );
698
699 node->hdr.dwBytesRecorded = 0;
700 memset(node->hdr.lpData, 0, node->hdr.dwBufferLength);
701
702 currentMidiBuffer = node;
703
704 //fprintf(stderr, "WinMM midi_get_buffer fetched buffer %p\n", node);
705 break;
706 }
707 }
708 }
709
710 if (!currentMidiBuffer) {
711 // there were no spare buffers, or none were big enough, so
712 // allocate a new one
713 int size;
714
715 if (midiThread) {
716 // playing a file, so allocate a buffer for more than
717 // one event
718 size = max(MIDI_BUFFER_SPACE, datalen);
719 } else {
720 // not playing a file, so allocate just a buffer for
721 // the event we'll be sending immediately
722 size = datalen;
723 }
724
725 node = (MidiBuffer *) malloc( sizeof(MidiBuffer) + size );
726 if (node == 0) {
727 return FALSE;
728 }
729
730 memset(node, 0, sizeof(MidiBuffer) + datalen);
731 node->hdr.dwUser = (DWORD_PTR) node;
732 node->hdr.lpData = (LPSTR) ((intptr_t)node + sizeof(MidiBuffer));
733 node->hdr.dwBufferLength = size;
734 node->hdr.dwBytesRecorded = 0;
735
736 currentMidiBuffer = node;
737
738 //fprintf(stderr, "WinMM midi_get_buffer allocated buffer %p\n", node);
739 }
740
741 midi_setup_event(length, data);
742
743 currentMidiBuffer->hdr.dwBytesRecorded += datalen;
744
745 return TRUE;
746 }
747
midi_sequence_event(void)748 static inline void midi_sequence_event(void)
749 {
750 if (!midiThread) {
751 // a midi event being sent out of playback (streaming) mode
752 midi_flush_current_buffer();
753 return;
754 }
755
756 //fprintf(stderr, "WinMM midi_sequence_event buffered\n");
757
758 // update the delta time counter
759 midiLastEventTime = midiThreadTimer;
760 }
761
Func_NoteOff(int channel,int key,int velocity)762 static void Func_NoteOff( int channel, int key, int velocity )
763 {
764 unsigned char * data;
765
766 if (midi_get_buffer(3, &data)) {
767 data[0] = MIDI_NOTE_OFF | channel;
768 data[1] = key;
769 data[2] = velocity;
770 midi_sequence_event();
771 } else fprintf(stderr, "WinMM Func_NoteOff error\n");
772 }
773
Func_NoteOn(int channel,int key,int velocity)774 static void Func_NoteOn( int channel, int key, int velocity )
775 {
776 unsigned char * data;
777
778 if (midi_get_buffer(3, &data)) {
779 data[0] = MIDI_NOTE_ON | channel;
780 data[1] = key;
781 data[2] = velocity;
782 midi_sequence_event();
783 } else fprintf(stderr, "WinMM Func_NoteOn error\n");
784 }
785
Func_PolyAftertouch(int channel,int key,int pressure)786 static void Func_PolyAftertouch( int channel, int key, int pressure )
787 {
788 unsigned char * data;
789
790 if (midi_get_buffer(3, &data)) {
791 data[0] = MIDI_POLY_AFTER_TCH | channel;
792 data[1] = key;
793 data[2] = pressure;
794 midi_sequence_event();
795 } else fprintf(stderr, "WinMM Func_PolyAftertouch error\n");
796 }
797
Func_ControlChange(int channel,int number,int value)798 static void Func_ControlChange( int channel, int number, int value )
799 {
800 unsigned char * data;
801
802 if (midi_get_buffer(3, &data)) {
803 data[0] = MIDI_CONTROL_CHANGE | channel;
804 data[1] = number;
805 data[2] = value;
806 midi_sequence_event();
807 } else fprintf(stderr, "WinMM Func_ControlChange error\n");
808 }
809
Func_ProgramChange(int channel,int program)810 static void Func_ProgramChange( int channel, int program )
811 {
812 unsigned char * data;
813
814 if (midi_get_buffer(2, &data)) {
815 data[0] = MIDI_PROGRAM_CHANGE | channel;
816 data[1] = program;
817 midi_sequence_event();
818 } else fprintf(stderr, "WinMM Func_ProgramChange error\n");
819 }
820
Func_ChannelAftertouch(int channel,int pressure)821 static void Func_ChannelAftertouch( int channel, int pressure )
822 {
823 unsigned char * data;
824
825 if (midi_get_buffer(2, &data)) {
826 data[0] = MIDI_AFTER_TOUCH | channel;
827 data[1] = pressure;
828 midi_sequence_event();
829 } else fprintf(stderr, "WinMM Func_ChannelAftertouch error\n");
830 }
831
Func_PitchBend(int channel,int lsb,int msb)832 static void Func_PitchBend( int channel, int lsb, int msb )
833 {
834 unsigned char * data;
835
836 if (midi_get_buffer(3, &data)) {
837 data[0] = MIDI_PITCH_BEND | channel;
838 data[1] = lsb;
839 data[2] = msb;
840 midi_sequence_event();
841 } else fprintf(stderr, "WinMM Func_PitchBend error\n");
842 }
843
Func_SysEx(const unsigned char * data,int length)844 static void Func_SysEx( const unsigned char * data, int length )
845 {
846 unsigned char * wdata;
847
848 if (midi_get_buffer(length, &wdata)) {
849 memcpy(wdata, data, length);
850 midi_sequence_event();
851 } else fprintf(stderr, "WinMM Func_SysEx error\n");
852 }
853
WinMMDrv_MIDI_Init(midifuncs * funcs,const char * params)854 int WinMMDrv_MIDI_Init(midifuncs * funcs, const char *params)
855 {
856 MMRESULT rv;
857
858 if (midiInstalled) {
859 WinMMDrv_MIDI_Shutdown();
860 }
861
862 memset(funcs, 0, sizeof(midifuncs));
863
864 LL_Reset( (MidiBuffer*) &activeMidiBuffers, next, prev );
865 LL_Reset( (MidiBuffer*) &spareMidiBuffers, next, prev );
866
867 midiMutex = CreateMutex(0, FALSE, 0);
868 if (!midiMutex) {
869 ErrorCode = WinMMErr_MIDICreateMutex;
870 return WinMMErr_Error;
871 }
872
873 rv = midiStreamOpen(&midiStream, &midiDeviceID, 1, (DWORD_PTR) 0, (DWORD_PTR) 0, CALLBACK_NULL);
874 if (rv != MMSYSERR_NOERROR) {
875 CloseHandle(midiMutex);
876 midiMutex = 0;
877
878 midi_error(rv, "WinMM MIDI_Init midiStreamOpen");
879 ErrorCode = WinMMErr_MIDIStreamOpen;
880 return WinMMErr_Error;
881 }
882
883 funcs->NoteOff = Func_NoteOff;
884 funcs->NoteOn = Func_NoteOn;
885 funcs->PolyAftertouch = Func_PolyAftertouch;
886 funcs->ControlChange = Func_ControlChange;
887 funcs->ProgramChange = Func_ProgramChange;
888 funcs->ChannelAftertouch = Func_ChannelAftertouch;
889 funcs->PitchBend = Func_PitchBend;
890 funcs->SysEx = Func_SysEx;
891
892 midiInstalled = TRUE;
893
894 return WinMMErr_Ok;
895 }
896
WinMMDrv_MIDI_Shutdown(void)897 void WinMMDrv_MIDI_Shutdown(void)
898 {
899 MMRESULT rv;
900
901 if (!midiInstalled) {
902 return;
903 }
904
905 WinMMDrv_MIDI_HaltPlayback();
906
907 if (midiStream) {
908 rv = midiStreamClose(midiStream);
909 if (rv != MMSYSERR_NOERROR) {
910 midi_error(rv, "WinMM MIDI_Shutdown midiStreamClose");
911 }
912 }
913
914 if (midiMutex) {
915 CloseHandle(midiMutex);
916 }
917
918 midiStream = 0;
919 midiMutex = 0;
920
921 midiInstalled = FALSE;
922 }
923
midi_get_tick(void)924 static DWORD midi_get_tick(void)
925 {
926 MMRESULT rv;
927 MMTIME mmtime;
928
929 mmtime.wType = TIME_TICKS;
930
931 rv = midiStreamPosition(midiStream, &mmtime, sizeof(MMTIME));
932 if (rv != MMSYSERR_NOERROR) {
933 midi_error(rv, "WinMM midi_get_tick midiStreamPosition");
934 return 0;
935 }
936
937 return mmtime.u.ticks;
938 }
939
midiDataThread(LPVOID lpParameter)940 static DWORD WINAPI midiDataThread(LPVOID lpParameter)
941 {
942 DWORD waitret;
943 DWORD sequenceTime;
944 DWORD sleepAmount = 100 / THREAD_QUEUE_INTERVAL;
945
946 fprintf(stderr, "WinMM midiDataThread: started\n");
947
948 midiThreadTimer = midi_get_tick();
949 midiLastEventTime = midiThreadTimer;
950 midiThreadQueueTimer = midiThreadTimer + midiThreadQueueTicks;
951
952 WinMMDrv_MIDI_Lock();
953 midi_gc_buffers();
954 while (midiThreadTimer < midiThreadQueueTimer) {
955 if (midiThreadService) {
956 midiThreadService();
957 }
958 midiThreadTimer++;
959 }
960 midi_flush_current_buffer();
961 WinMMDrv_MIDI_Unlock();
962
963 do {
964 waitret = WaitForSingleObject(midiThreadQuitEvent, sleepAmount);
965 if (waitret == WAIT_OBJECT_0) {
966 fprintf(stderr, "WinMM midiDataThread: exiting\n");
967 break;
968 } else if (waitret == WAIT_TIMEOUT) {
969 // queue a tick
970 sequenceTime = midi_get_tick();
971
972 sleepAmount = 100 / THREAD_QUEUE_INTERVAL;
973 if ((int)(midiThreadTimer - sequenceTime) > midiThreadQueueTicks) {
974 // we're running ahead, so sleep for half the usual
975 // amount and try again
976 sleepAmount /= 2;
977 continue;
978 }
979
980 midiThreadQueueTimer = sequenceTime + midiThreadQueueTicks;
981
982 WinMMDrv_MIDI_Lock();
983 midi_gc_buffers();
984 while (midiThreadTimer < midiThreadQueueTimer) {
985 if (midiThreadService) {
986 midiThreadService();
987 }
988 midiThreadTimer++;
989 }
990 midi_flush_current_buffer();
991 WinMMDrv_MIDI_Unlock();
992
993 } else {
994 fprintf(stderr, "WinMM midiDataThread: wfmo err %d\n", (int) waitret);
995 }
996 } while (1);
997
998 return 0;
999 }
1000
WinMMDrv_MIDI_StartPlayback(void (* service)(void))1001 int WinMMDrv_MIDI_StartPlayback(void (*service)(void))
1002 {
1003 MMRESULT rv;
1004
1005 WinMMDrv_MIDI_HaltPlayback();
1006
1007 midiThreadService = service;
1008
1009 midiThreadQuitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
1010 if (!midiThreadQuitEvent) {
1011 ErrorCode = WinMMErr_MIDICreateEvent;
1012 return WinMMErr_Error;
1013 }
1014
1015 if (!midiStreamRunning) {
1016 rv = midiStreamRestart(midiStream);
1017 if (rv != MMSYSERR_NOERROR) {
1018 midi_error(rv, "MIDI_StartPlayback midiStreamRestart");
1019 WinMMDrv_MIDI_HaltPlayback();
1020 ErrorCode = WinMMErr_MIDIStreamRestart;
1021 return WinMMErr_Error;
1022 }
1023
1024 midiStreamRunning = TRUE;
1025 }
1026
1027 midiThread = CreateThread(NULL, 0, midiDataThread, 0, 0, 0);
1028 if (!midiThread) {
1029 WinMMDrv_MIDI_HaltPlayback();
1030 ErrorCode = WinMMErr_MIDIPlayThread;
1031 return WinMMErr_Error;
1032 }
1033
1034 return WinMMErr_Ok;
1035 }
1036
WinMMDrv_MIDI_HaltPlayback(void)1037 void WinMMDrv_MIDI_HaltPlayback(void)
1038 {
1039 MMRESULT rv;
1040
1041 if (midiThread) {
1042 SetEvent(midiThreadQuitEvent);
1043
1044 WaitForSingleObject(midiThread, INFINITE);
1045 fprintf(stderr, "WinMM MIDI_HaltPlayback synched\n");
1046
1047 CloseHandle(midiThread);
1048 }
1049
1050 if (midiThreadQuitEvent) {
1051 CloseHandle(midiThreadQuitEvent);
1052 }
1053
1054 if (midiStreamRunning) {
1055 fprintf(stderr, "stopping stream\n");
1056 rv = midiStreamStop(midiStream);
1057 if (rv != MMSYSERR_NOERROR) {
1058 midi_error(rv, "WinMM MIDI_HaltPlayback midiStreamStop");
1059 }
1060 fprintf(stderr, "stream stopped\n");
1061
1062 midiStreamRunning = FALSE;
1063 }
1064
1065 midi_free_buffers();
1066
1067 midiThread = 0;
1068 midiThreadQuitEvent = 0;
1069 }
1070
WinMMDrv_MIDI_SetTempo(int tempo,int division)1071 void WinMMDrv_MIDI_SetTempo(int tempo, int division)
1072 {
1073 MMRESULT rv;
1074 MIDIPROPTEMPO propTempo;
1075 MIDIPROPTIMEDIV propTimediv;
1076 BOOL running = midiStreamRunning;
1077
1078 //fprintf(stderr, "MIDI_SetTempo %d/%d\n", tempo, division);
1079
1080 propTempo.cbStruct = sizeof(MIDIPROPTEMPO);
1081 propTempo.dwTempo = 60000000l / tempo;
1082 propTimediv.cbStruct = sizeof(MIDIPROPTIMEDIV);
1083 propTimediv.dwTimeDiv = division;
1084
1085 if (midiLastDivision != division) {
1086 // changing the division means halting the stream
1087 WinMMDrv_MIDI_HaltPlayback();
1088
1089 rv = midiStreamProperty(midiStream, (LPBYTE) &propTimediv, MIDIPROP_SET | MIDIPROP_TIMEDIV);
1090 if (rv != MMSYSERR_NOERROR) {
1091 midi_error(rv, "WinMM MIDI_SetTempo midiStreamProperty timediv");
1092 }
1093 }
1094
1095 rv = midiStreamProperty(midiStream, (LPBYTE) &propTempo, MIDIPROP_SET | MIDIPROP_TEMPO);
1096 if (rv != MMSYSERR_NOERROR) {
1097 midi_error(rv, "WinMM MIDI_SetTempo midiStreamProperty tempo");
1098 }
1099
1100 if (midiLastDivision != division) {
1101 if (running && WinMMDrv_MIDI_StartPlayback(midiThreadService) != WinMMErr_Ok) {
1102 return;
1103 }
1104
1105 midiLastDivision = division;
1106 }
1107
1108 midiThreadQueueTicks = (int) ceil( ( ( (double) tempo * (double) division ) / 60.0 ) /
1109 (double) THREAD_QUEUE_INTERVAL );
1110 if (midiThreadQueueTicks <= 0) {
1111 midiThreadQueueTicks = 1;
1112 }
1113 }
1114
WinMMDrv_MIDI_Lock(void)1115 void WinMMDrv_MIDI_Lock(void)
1116 {
1117 DWORD err;
1118
1119 err = WaitForSingleObject(midiMutex, INFINITE);
1120 if (err != WAIT_OBJECT_0) {
1121 fprintf(stderr, "WinMM midiMutex lock: wfso %d\n", (int) err);
1122 }
1123 }
1124
WinMMDrv_MIDI_Unlock(void)1125 void WinMMDrv_MIDI_Unlock(void)
1126 {
1127 ReleaseMutex(midiMutex);
1128 }
1129
1130 // vim:ts=4:sw=4:expandtab:
1131