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, &currentMidiBuffer->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, &currentMidiBuffer->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, &currentMidiBuffer->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