winecoreaudio: Implement MIDIIn_Open and MIDIIn_Close.
[wine] / dlls / winecoreaudio.drv / midi.c
1 /*
2  * Sample MIDI Wine Driver for MacOSX (based on OSS midi driver)
3  *
4  * Copyright 1994       Martin Ayotte
5  * Copyright 1998       Luiz Otavio L. Zorzella (init procedures)
6  * Copyright 1998/1999  Eric POUECH :
7  *              98/7    changes for making this MIDI driver work on OSS
8  *                      current support is limited to MIDI ports of OSS systems
9  *              98/9    rewriting MCI code for MIDI
10  *              98/11   splitted in midi.c and mcimidi.c
11  * Copyright 2006       Emmanuel Maillard
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Lesser General Public
15  * License as published by the Free Software Foundation; either
16  * version 2.1 of the License, or (at your option) any later version.
17  *
18  * This library is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21  * Lesser General Public License for more details.
22  *
23  * You should have received a copy of the GNU Lesser General Public
24  * License along with this library; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26  */
27
28 #include "config.h"
29 #include "wine/port.h"
30
31 #include <string.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34
35 #include "windef.h"
36 #include "winbase.h"
37 #include "wingdi.h"
38 #include "winuser.h"
39 #include "winnls.h"
40 #include "mmddk.h"
41 #include "wine/unicode.h"
42 #include "wine/debug.h"
43
44 WINE_DEFAULT_DEBUG_CHANNEL(midi);
45
46 #if defined(HAVE_COREAUDIO_COREAUDIO_H)
47 #include <CoreAudio/CoreAudio.h>
48
49 #define WINE_DEFINITIONS
50 #include "coremidi.h"
51
52 static MIDIClientRef wineMIDIClient = NULL;
53
54 static DWORD MIDIOut_NumDevs = 0;
55 static DWORD MIDIIn_NumDevs = 0;
56
57 typedef struct tagMIDIDestination {
58     /* graph and synth are only used for MIDI Synth */
59     AUGraph graph;
60     AudioUnit synth;
61
62     MIDIPortRef port;
63     MIDIOUTCAPSW caps;
64     MIDIOPENDESC midiDesc;
65     WORD wFlags;
66 } MIDIDestination;
67
68 typedef struct tagMIDISource {
69     MIDIPortRef port;
70     WORD wDevID;
71     int state; /* 0 is no recording started, 1 in recording, bit 2 set if in sys exclusive recording */
72     MIDIINCAPSW caps;
73     MIDIOPENDESC midiDesc;
74     LPMIDIHDR lpQueueHdr;
75     WORD wFlags;
76     DWORD startTime;
77 } MIDISource;
78
79 static CRITICAL_SECTION midiInLock; /* Critical section for MIDI In */
80 static CFStringRef MIDIInThreadPortName = NULL;
81
82 static DWORD WINAPI MIDIIn_MessageThread(LPVOID p);
83
84 #define MAX_MIDI_SYNTHS 1
85
86 MIDIDestination *destinations;
87 MIDISource *sources;
88
89 extern int SynthUnit_CreateDefaultSynthUnit(AUGraph *graph, AudioUnit *synth);
90 extern int SynthUnit_Initialize(AudioUnit synth, AUGraph graph);
91 extern int SynthUnit_Close(AUGraph graph);
92
93
94 LONG CoreAudio_MIDIInit(void)
95 {
96     int i;
97     CHAR szPname[MAXPNAMELEN] = {0};
98
99     int numDest = MIDIGetNumberOfDestinations();
100     CFStringRef name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("wineMIDIClient.%d"), getpid());
101
102     wineMIDIClient = CoreMIDI_CreateClient( name );
103     if (wineMIDIClient == NULL)
104     {
105         CFRelease(name);
106         ERR("can't create wineMIDIClient\n");
107         return 0;
108     }
109     CFRelease(name);
110
111     MIDIOut_NumDevs = MAX_MIDI_SYNTHS;
112     MIDIOut_NumDevs += numDest;
113
114     MIDIIn_NumDevs = MIDIGetNumberOfSources();
115
116     TRACE("MIDIOut_NumDevs %d MIDIIn_NumDevs %d\n", MIDIOut_NumDevs, MIDIIn_NumDevs);
117
118     destinations = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MIDIOut_NumDevs * sizeof(MIDIDestination));
119     sources = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MIDIIn_NumDevs * sizeof(MIDISource));
120
121     if (MIDIIn_NumDevs > 0)
122     {
123         InitializeCriticalSection(&midiInLock);
124         MIDIInThreadPortName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("MIDIInThreadPortName.%u"), getpid());
125         CreateThread(NULL, 0, MIDIIn_MessageThread, NULL, 0, NULL);
126     }
127
128     /* initialize sources */
129     for (i = 0; i < MIDIIn_NumDevs; i++)
130     {
131         MIDIEndpointRef endpoint = MIDIGetSource(i);
132
133         sources[i].wDevID = i;
134
135         CoreMIDI_GetObjectName(endpoint, szPname, sizeof(szPname));
136         MultiByteToWideChar(CP_ACP, 0, szPname, -1, sources[i].caps.szPname, sizeof(sources[i].caps.szPname)/sizeof(WCHAR));
137
138         name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("WineInputPort.%d.%u"), i, getpid());
139         MIDIInputPortCreate(wineMIDIClient, name, MIDIIn_ReadProc, &sources[i].wDevID, &sources[i].port);
140         CFRelease(name);
141
142         MIDIPortConnectSource(sources[i].port, endpoint, NULL);
143
144         sources[i].state = 0;
145         /* FIXME */
146         sources[i].caps.wMid = 0x00FF;  /* Manufac ID */
147         sources[i].caps.wPid = 0x0001;  /* Product ID */
148         sources[i].caps.vDriverVersion = 0x0001;
149         sources[i].caps.dwSupport = 0;
150     }
151
152     /* initialise MIDI synths */
153     for (i = 0; i < MAX_MIDI_SYNTHS; i++)
154     {
155         snprintf(szPname, sizeof(szPname), "CoreAudio MIDI Synth %d", i);
156         MultiByteToWideChar(CP_ACP, 0, szPname, -1, destinations[i].caps.szPname, sizeof(destinations[i].caps.szPname)/sizeof(WCHAR));
157
158         destinations[i].caps.wTechnology = MOD_SYNTH;
159         destinations[i].caps.wChannelMask = 0xFFFF;
160
161         destinations[i].caps.wMid = 0x00FF;     /* Manufac ID */
162         destinations[i].caps.wPid = 0x0001;     /* Product ID */
163         destinations[i].caps.vDriverVersion = 0x0001;
164         destinations[i].caps.dwSupport = MIDICAPS_VOLUME;
165         destinations[i].caps.wVoices = 16;
166         destinations[i].caps.wNotes = 16;
167     }
168     /* initialise available destinations */
169     for (i = MAX_MIDI_SYNTHS; i < numDest + MAX_MIDI_SYNTHS; i++)
170     {
171         MIDIEndpointRef endpoint = MIDIGetDestination(i - MAX_MIDI_SYNTHS);
172
173         CoreMIDI_GetObjectName(endpoint, szPname, sizeof(szPname));
174         MultiByteToWideChar(CP_ACP, 0, szPname, -1, destinations[i].caps.szPname, sizeof(destinations[i].caps.szPname)/sizeof(WCHAR));
175
176         name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("WineOutputPort.%d.%u"), i, getpid());
177         MIDIOutputPortCreate(wineMIDIClient, name, &destinations[i].port);
178         CFRelease(name);
179
180         destinations[i].caps.wTechnology = MOD_MIDIPORT;
181         destinations[i].caps.wChannelMask = 0xFFFF;
182
183         destinations[i].caps.wMid = 0x00FF;     /* Manufac ID */
184         destinations[i].caps.wPid = 0x0001;
185         destinations[i].caps.vDriverVersion = 0x0001;
186         destinations[i].caps.dwSupport = 0;
187         destinations[i].caps.wVoices = 16;
188         destinations[i].caps.wNotes = 16;
189     }
190     return 1;
191 }
192
193 LONG CoreAudio_MIDIRelease(void)
194 {
195     TRACE("\n");
196     if (MIDIIn_NumDevs > 0)
197     {
198         CFMessagePortRef messagePort;
199         /* Stop CFRunLoop in MIDIIn_MessageThread */
200         messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, MIDIInThreadPortName);
201         CFMessagePortSendRequest(messagePort, 1, NULL, 0.0, 0.0, NULL, NULL);
202         CFRelease(messagePort);
203
204         DeleteCriticalSection(&midiInLock);
205     }
206
207     if (wineMIDIClient) MIDIClientDispose(wineMIDIClient); /* MIDIClientDispose will close all ports */
208
209     HeapFree(GetProcessHeap(), 0, sources);
210     HeapFree(GetProcessHeap(), 0, destinations);
211     return 1;
212 }
213
214
215 /**************************************************************************
216  *                      MIDI_NotifyClient                       [internal]
217  */
218 static DWORD MIDI_NotifyClient(UINT wDevID, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
219 {
220     DWORD               dwCallBack;
221     UINT                uFlags;
222     HANDLE              hDev;
223     DWORD               dwInstance;
224
225     TRACE("wDevID=%d wMsg=%d dwParm1=%04X dwParam2=%04X\n", wDevID, wMsg, dwParam1, dwParam2);
226
227     switch (wMsg) {
228     case MOM_OPEN:
229     case MOM_CLOSE:
230     case MOM_DONE:
231     case MOM_POSITIONCB:
232         dwCallBack = destinations[wDevID].midiDesc.dwCallback;
233         uFlags = destinations[wDevID].wFlags;
234         hDev = destinations[wDevID].midiDesc.hMidi;
235         dwInstance = destinations[wDevID].midiDesc.dwInstance;
236         break;
237
238     case MIM_OPEN:
239     case MIM_CLOSE:
240     case MIM_DATA:
241     case MIM_LONGDATA:
242     case MIM_ERROR:
243     case MIM_LONGERROR:
244     case MIM_MOREDATA:
245         dwCallBack = sources[wDevID].midiDesc.dwCallback;
246         uFlags = sources[wDevID].wFlags;
247         hDev = sources[wDevID].midiDesc.hMidi;
248         dwInstance = sources[wDevID].midiDesc.dwInstance;
249         break;
250     default:
251         WARN("Unsupported MSW-MIDI message %u\n", wMsg);
252         return MMSYSERR_ERROR;
253     }
254
255     return DriverCallback(dwCallBack, uFlags, hDev, wMsg, dwInstance, dwParam1, dwParam2) ?
256         MMSYSERR_NOERROR : MMSYSERR_ERROR;
257 }
258
259 static DWORD MIDIOut_Open(WORD wDevID, LPMIDIOPENDESC lpDesc, DWORD dwFlags)
260 {
261     MIDIDestination *dest;
262
263     TRACE("wDevID=%d lpDesc=%p dwFlags=%08x\n", wDevID, lpDesc, dwFlags);
264
265     if (lpDesc == NULL) {
266         WARN("Invalid Parameter\n");
267         return MMSYSERR_INVALPARAM;
268     }
269
270     if (wDevID >= MIDIOut_NumDevs) {
271         WARN("bad device ID : %d\n", wDevID);
272         return MMSYSERR_BADDEVICEID;
273     }
274
275     if (destinations[wDevID].midiDesc.hMidi != 0) {
276         WARN("device already open !\n");
277         return MMSYSERR_ALLOCATED;
278     }
279
280     if ((dwFlags & ~CALLBACK_TYPEMASK) != 0) {
281         WARN("bad dwFlags\n");
282         return MMSYSERR_INVALFLAG;
283     }
284     dest = &destinations[wDevID];
285
286     if (dest->caps.wTechnology == MOD_SYNTH)
287     {
288         if (!SynthUnit_CreateDefaultSynthUnit(&dest->graph, &dest->synth))
289         {
290             ERR("SynthUnit_CreateDefaultSynthUnit dest=%p failed\n", dest);
291             return MMSYSERR_ERROR;
292         }
293
294         if (!SynthUnit_Initialize(dest->synth, dest->graph))
295         {
296             ERR("SynthUnit_Initialise dest=%p failed\n", dest);
297             return MMSYSERR_ERROR;
298         }
299     }
300     else
301     {
302         FIXME("MOD_MIDIPORT\n");
303     }
304     dest->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
305     dest->midiDesc = *lpDesc;
306
307     return MIDI_NotifyClient(wDevID, MOM_OPEN, 0L, 0L);
308 }
309
310 static DWORD MIDIOut_Close(WORD wDevID)
311 {
312     DWORD ret = MMSYSERR_NOERROR;
313
314     TRACE("wDevID=%d\n", wDevID);
315
316     if (wDevID >= MIDIOut_NumDevs) {
317         WARN("bad device ID : %d\n", wDevID);
318         return MMSYSERR_BADDEVICEID;
319     }
320
321     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH)
322         SynthUnit_Close(destinations[wDevID].graph);
323     else
324         FIXME("MOD_MIDIPORT\n");
325
326     destinations[wDevID].graph = 0;
327     destinations[wDevID].synth = 0;
328
329     if (MIDI_NotifyClient(wDevID, MOM_CLOSE, 0L, 0L) != MMSYSERR_NOERROR) {
330         WARN("can't notify client !\n");
331         ret = MMSYSERR_INVALPARAM;
332     }
333     destinations[wDevID].midiDesc.hMidi = 0;
334
335     return ret;
336 }
337
338 static DWORD MIDIOut_Data(WORD wDevID, DWORD dwParam)
339 {
340     WORD evt = LOBYTE(LOWORD(dwParam));
341     WORD d1  = HIBYTE(LOWORD(dwParam));
342     WORD d2  = LOBYTE(HIWORD(dwParam));
343     UInt8 chn = (evt & 0x0F);
344     OSStatus err = noErr;
345
346     TRACE("wDevID=%d dwParam=%08X\n", wDevID, dwParam);
347
348     if (wDevID >= MIDIOut_NumDevs) {
349         WARN("bad device ID : %d\n", wDevID);
350         return MMSYSERR_BADDEVICEID;
351     }
352
353     TRACE("evt=%08x d1=%04x d2=%04x (evt & 0xF0)=%04x chn=%d\n", evt, d1, d2, (evt & 0xF0), chn);
354
355     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH)
356     {
357         err = MusicDeviceMIDIEvent(destinations[wDevID].synth, (evt & 0xF0) | chn, d1, d2, 0);
358         if (err != noErr)
359         {
360             ERR("MusicDeviceMIDIEvent(%p, %04x, %04x, %04x, %d) return %c%c%c%c\n", destinations[wDevID].synth, (evt & 0xF0) | chn, d1, d2, 0, (char) (err >> 24), (char) (err >> 16), (char) (err >> 8), (char) err);
361             return MMSYSERR_ERROR;
362         }
363     }
364     else FIXME("MOD_MIDIPORT\n");
365
366     return MMSYSERR_NOERROR;
367 }
368
369 static DWORD MIDIOut_LongData(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
370 {
371     LPBYTE lpData;
372     OSStatus err = noErr;
373
374     TRACE("wDevID=%d lpMidiHdr=%p dwSize=%d\n", wDevID, lpMidiHdr, dwSize);
375
376     /* Note: MS doc does not say much about the dwBytesRecorded member of the MIDIHDR structure
377      * but it seems to be used only for midi input.
378      * Taking a look at the WAVEHDR structure (which is quite similar) confirms this assumption.
379      */
380
381     if (wDevID >= MIDIOut_NumDevs) {
382         WARN("bad device ID : %d\n", wDevID);
383         return MMSYSERR_BADDEVICEID;
384     }
385
386     if (lpMidiHdr == NULL) {
387         WARN("Invalid Parameter\n");
388         return MMSYSERR_INVALPARAM;
389     }
390
391     lpData = (LPBYTE) lpMidiHdr->lpData;
392
393     if (lpData == NULL)
394         return MIDIERR_UNPREPARED;
395     if (!(lpMidiHdr->dwFlags & MHDR_PREPARED))
396         return MIDIERR_UNPREPARED;
397     if (lpMidiHdr->dwFlags & MHDR_INQUEUE)
398         return MIDIERR_STILLPLAYING;
399     lpMidiHdr->dwFlags &= ~MHDR_DONE;
400     lpMidiHdr->dwFlags |= MHDR_INQUEUE;
401
402     /* FIXME: MS doc is not 100% clear. Will lpData only contain system exclusive
403      * data, or can it also contain raw MIDI data, to be split up and sent to
404      * modShortData() ?
405      * If the latest is true, then the following WARNing will fire up
406      */
407     if (lpData[0] != 0xF0 || lpData[lpMidiHdr->dwBufferLength - 1] != 0xF7) {
408         WARN("Alledged system exclusive buffer is not correct\n\tPlease report with MIDI file\n");
409     }
410
411     TRACE("dwBufferLength=%u !\n", lpMidiHdr->dwBufferLength);
412     TRACE("                 %02X %02X %02X ... %02X %02X %02X\n",
413           lpData[0], lpData[1], lpData[2], lpData[lpMidiHdr->dwBufferLength-3],
414           lpData[lpMidiHdr->dwBufferLength-2], lpData[lpMidiHdr->dwBufferLength-1]);
415
416
417     if (lpData[0] != 0xF0) {
418         /* System Exclusive */
419         ERR("Add missing 0xF0 marker at the beginning of system exclusive byte stream\n");
420     }
421     if (lpData[lpMidiHdr->dwBufferLength - 1] != 0xF7) {
422         /* Send end of System Exclusive */
423         ERR("Add missing 0xF7 marker at the end of system exclusive byte stream\n");
424     }
425     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH) /* FIXME */
426     {
427         err = MusicDeviceSysEx(destinations[wDevID].synth, (const UInt8 *) lpData, lpMidiHdr->dwBufferLength);
428         if (err != noErr)
429         {
430             ERR("MusicDeviceSysEx(%p, %p, %d) return %c%c%c%c\n", destinations[wDevID].synth, lpData, lpMidiHdr->dwBufferLength, (char) (err >> 24), (char) (err >> 16), (char) (err >> 8), (char) err);
431             return MMSYSERR_ERROR;
432         }
433     }
434     else
435     {
436         FIXME("MOD_MIDIPORT\n");
437     }
438
439     lpMidiHdr->dwFlags &= ~MHDR_INQUEUE;
440     lpMidiHdr->dwFlags |= MHDR_DONE;
441     if (MIDI_NotifyClient(wDevID, MOM_DONE, (DWORD)lpMidiHdr, 0L) != MMSYSERR_NOERROR) {
442         WARN("can't notify client !\n");
443         return MMSYSERR_INVALPARAM;
444     }
445     return MMSYSERR_NOERROR;
446 }
447
448 /**************************************************************************
449  *                      MIDIOut_Prepare                         [internal]
450  */
451 static DWORD MIDIOut_Prepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
452 {
453     TRACE("wDevID=%d lpMidiHdr=%p dwSize=%d\n", wDevID, lpMidiHdr, dwSize);
454
455     if (wDevID >= MIDIOut_NumDevs) {
456         WARN("bad device ID : %d\n", wDevID);
457         return MMSYSERR_BADDEVICEID;
458     }
459
460     /* MS doc says that dwFlags must be set to zero, but (kinda funny) MS mciseq drivers
461      * asks to prepare MIDIHDR which dwFlags != 0.
462      * So at least check for the inqueue flag
463      */
464     if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0 ||
465         lpMidiHdr->lpData == 0 || (lpMidiHdr->dwFlags & MHDR_INQUEUE) != 0 ||
466         lpMidiHdr->dwBufferLength >= 0x10000ul) {
467         WARN("%p %p %08x %lu/%d\n", lpMidiHdr, lpMidiHdr->lpData,
468                    lpMidiHdr->dwFlags, sizeof(MIDIHDR), dwSize);
469         return MMSYSERR_INVALPARAM;
470     }
471
472     lpMidiHdr->lpNext = 0;
473     lpMidiHdr->dwFlags |= MHDR_PREPARED;
474     lpMidiHdr->dwFlags &= ~MHDR_DONE;
475     return MMSYSERR_NOERROR;
476 }
477
478 /**************************************************************************
479  *                              MIDIOut_Unprepare                       [internal]
480  */
481 static DWORD MIDIOut_Unprepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
482 {
483     TRACE("wDevID=%d lpMidiHdr=%p dwSize=%d\n", wDevID, lpMidiHdr, dwSize);
484
485     if (wDevID >= MIDIOut_NumDevs) {
486         WARN("bad device ID : %d\n", wDevID);
487         return MMSYSERR_BADDEVICEID;
488     }
489     if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0)
490         return MMSYSERR_INVALPARAM;
491     if (lpMidiHdr->dwFlags & MHDR_INQUEUE)
492         return MIDIERR_STILLPLAYING;
493
494     lpMidiHdr->dwFlags &= ~MHDR_PREPARED;
495
496     return MMSYSERR_NOERROR;
497 }
498
499 static DWORD MIDIOut_GetDevCaps(WORD wDevID, LPMIDIOUTCAPSW lpCaps, DWORD dwSize)
500 {
501     TRACE("wDevID=%d lpCaps=%p dwSize=%d\n", wDevID, lpCaps, dwSize);
502
503     if (lpCaps == NULL) {
504         WARN("Invalid Parameter\n");
505         return MMSYSERR_INVALPARAM;
506     }
507
508     if (wDevID >= MIDIOut_NumDevs) {
509         WARN("bad device ID : %d\n", wDevID);
510         return MMSYSERR_BADDEVICEID;
511     }
512     memcpy(lpCaps, &destinations[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
513     return MMSYSERR_NOERROR;
514 }
515
516 static DWORD MIDIOut_GetNumDevs(void)
517 {
518     TRACE("\n");
519     return MIDIOut_NumDevs;
520 }
521
522 static DWORD MIDIOut_GetVolume(WORD wDevID, DWORD *lpdwVolume)
523 {
524     TRACE("%d\n", wDevID);
525
526     if (wDevID >= MIDIOut_NumDevs) {
527         WARN("bad device ID : %d\n", wDevID);
528         return MMSYSERR_BADDEVICEID;
529     }
530     if (lpdwVolume == NULL) {
531         WARN("Invalid Parameter\n");
532         return MMSYSERR_INVALPARAM;
533     }
534
535     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH)
536     {
537         float left;
538         float right;
539         AudioUnit_GetVolume(destinations[wDevID].synth, &left, &right);
540
541         *lpdwVolume = ((WORD) left * 0xFFFFl) + (((WORD) right * 0xFFFFl) << 16);
542
543         return MMSYSERR_NOERROR;
544     }
545
546     return MMSYSERR_NOTSUPPORTED;
547 }
548
549 static DWORD MIDIOut_SetVolume(WORD wDevID, DWORD dwVolume)
550 {
551     TRACE("%d\n", wDevID);
552
553     if (wDevID >= MIDIOut_NumDevs) {
554         WARN("bad device ID : %d\n", wDevID);
555         return MMSYSERR_BADDEVICEID;
556     }
557     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH)
558     {
559         float left;
560         float right;
561
562         left  = LOWORD(dwVolume) / 65535.0f;
563         right = HIWORD(dwVolume) / 65535.0f;
564         AudioUnit_SetVolume(destinations[wDevID].synth, left, right);
565
566         return MMSYSERR_NOERROR;
567     }
568
569     return MMSYSERR_NOTSUPPORTED;
570 }
571
572 static DWORD MIDIOut_Reset(WORD wDevID)
573 {
574     unsigned chn;
575
576     TRACE("%d\n", wDevID);
577
578     if (wDevID >= MIDIOut_NumDevs) {
579         WARN("bad device ID : %d\n", wDevID);
580         return MMSYSERR_BADDEVICEID;
581     }
582     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH)
583     {
584         for (chn = 0; chn < 16; chn++) {
585             /* turn off every note */
586             MusicDeviceMIDIEvent(destinations[wDevID].synth, 0xB0 | chn, 0x7B, 0, 0);
587             /* remove sustain on channel */
588             MusicDeviceMIDIEvent(destinations[wDevID].synth, 0xB0 | chn, 0x40, 0, 0);
589         }
590     }
591     else FIXME("MOD_MIDIPORT\n");
592
593     /* FIXME: the LongData buffers must also be returned to the app */
594     return MMSYSERR_NOERROR;
595 }
596
597 static DWORD MIDIIn_Open(WORD wDevID, LPMIDIOPENDESC lpDesc, DWORD dwFlags)
598 {
599     TRACE("wDevID=%d lpDesc=%p dwFlags=%08x\n", wDevID, lpDesc, dwFlags);
600
601     if (lpDesc == NULL) {
602         WARN("Invalid Parameter\n");
603         return MMSYSERR_INVALPARAM;
604     }
605     if (wDevID >= MIDIIn_NumDevs) {
606         WARN("bad device ID : %d\n", wDevID);
607         return MMSYSERR_BADDEVICEID;
608     }
609     if (sources[wDevID].midiDesc.hMidi != 0) {
610         WARN("device already open !\n");
611         return MMSYSERR_ALLOCATED;
612     }
613     if ((dwFlags & MIDI_IO_STATUS) != 0) {
614         WARN("No support for MIDI_IO_STATUS in dwFlags yet, ignoring it\n");
615         dwFlags &= ~MIDI_IO_STATUS;
616     }
617     if ((dwFlags & ~CALLBACK_TYPEMASK) != 0) {
618         FIXME("Bad dwFlags\n");
619         return MMSYSERR_INVALFLAG;
620     }
621
622     sources[wDevID].wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
623     sources[wDevID].lpQueueHdr = NULL;
624     sources[wDevID].midiDesc = *lpDesc;
625     sources[wDevID].startTime = 0;
626     sources[wDevID].state = 0;
627
628     return MIDI_NotifyClient(wDevID, MIM_OPEN, 0L, 0L);
629 }
630
631 static DWORD MIDIIn_Close(WORD wDevID)
632 {
633     DWORD ret = MMSYSERR_NOERROR;
634
635     TRACE("wDevID=%d\n", wDevID);
636
637     if (wDevID >= MIDIIn_NumDevs) {
638         WARN("bad device ID : %d\n", wDevID);
639         return MMSYSERR_BADDEVICEID;
640     }
641
642     if (sources[wDevID].midiDesc.hMidi == 0) {
643         WARN("device not opened !\n");
644         return MMSYSERR_ERROR;
645     }
646     if (sources[wDevID].lpQueueHdr != 0) {
647         return MIDIERR_STILLPLAYING;
648     }
649
650     ret = MIDI_NotifyClient(wDevID, MIM_CLOSE, 0L, 0L);
651     sources[wDevID].midiDesc.hMidi = 0;
652     return ret;
653 }
654
655
656 /*
657  * MIDI In Mach message handling
658  */
659
660 /*
661  *  Call from CoreMIDI IO threaded callback,
662  *  we can't call Wine debug channels, critical section or anything using NtCurrentTeb here.
663  */
664 void MIDIIn_SendMessage(MIDIMessage msg)
665 {
666     CFDataRef data;
667
668     CFMessagePortRef messagePort;
669     messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, MIDIInThreadPortName);
670
671     data = CFDataCreate(kCFAllocatorDefault, (UInt8 *) &msg, sizeof(msg));
672     if (data)
673     {
674         CFMessagePortSendRequest(messagePort, 0, data, 0.0, 0.0, NULL, NULL);
675         CFRelease(data);
676         CFRelease(messagePort);
677     }
678 }
679
680 static CFDataRef MIDIIn_MessageHandler(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info)
681 {
682     MIDIMessage *msg = NULL;
683     int i = 0;
684     FIXME("\n");
685
686     switch (msgid)
687     {
688         case 0:
689             msg = (MIDIMessage *) CFDataGetBytePtr(data);
690             TRACE("devID=%d\n", msg->devID);
691              for (i = 0; i < msg->length; ++i) {
692                 TRACE("%02X ", msg->data[i]);
693             }
694             TRACE("\n");
695             break;
696         default:
697             CFRunLoopStop(CFRunLoopGetCurrent());
698             break;
699     }
700     return NULL;
701 }
702
703 static DWORD WINAPI MIDIIn_MessageThread(LPVOID p)
704 {
705     CFMessagePortRef local;
706     CFRunLoopSourceRef source;
707     Boolean info;
708
709     local = CFMessagePortCreateLocal(kCFAllocatorDefault, MIDIInThreadPortName, &MIDIIn_MessageHandler, NULL, &info);
710
711     source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, local, (CFIndex)0);
712     CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
713
714     CFRunLoopRun();
715
716     CFRunLoopSourceInvalidate(source);
717     CFRelease(source);
718     CFRelease(local);
719     CFRelease(MIDIInThreadPortName);
720     MIDIInThreadPortName = NULL;
721
722     return 0;
723 }
724
725 /**************************************************************************
726 *                               modMessage
727 */
728 DWORD WINAPI CoreAudio_modMessage(UINT wDevID, UINT wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2)
729 {
730     TRACE("%d %08x %08x %08x %08x\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
731
732     switch (wMsg) {
733         case DRVM_INIT:
734         case DRVM_EXIT:
735         case DRVM_ENABLE:
736         case DRVM_DISABLE:
737             return 0;
738         case MODM_OPEN:
739             return MIDIOut_Open(wDevID, (LPMIDIOPENDESC)dwParam1, dwParam2);
740         case MODM_CLOSE:
741             return MIDIOut_Close(wDevID);
742         case MODM_DATA:
743             return MIDIOut_Data(wDevID, dwParam1);
744         case MODM_LONGDATA:
745             return MIDIOut_LongData(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
746         case MODM_PREPARE:
747             return MIDIOut_Prepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
748         case MODM_UNPREPARE:
749             return MIDIOut_Unprepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
750         case MODM_GETDEVCAPS:
751             return MIDIOut_GetDevCaps(wDevID, (LPMIDIOUTCAPSW) dwParam1, dwParam2);
752         case MODM_GETNUMDEVS:
753             return MIDIOut_GetNumDevs();
754         case MODM_GETVOLUME:
755             return MIDIOut_GetVolume(wDevID, (DWORD*)dwParam1);
756         case MODM_SETVOLUME:
757             return MIDIOut_SetVolume(wDevID, dwParam1);
758         case MODM_RESET:
759             return MIDIOut_Reset(wDevID);
760         default:
761             TRACE("Unsupported message (08%x)\n", wMsg);
762     }
763     return MMSYSERR_NOTSUPPORTED;
764 }
765
766 /**************************************************************************
767 *                       midMessage
768 */
769 DWORD WINAPI CoreAudio_midMessage(UINT wDevID, UINT wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2)
770 {
771     TRACE("%d %08x %08x %08x %08x\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
772     switch (wMsg) {
773         case DRVM_INIT:
774         case DRVM_EXIT:
775         case DRVM_ENABLE:
776         case DRVM_DISABLE:
777             return 0;
778         case MIDM_OPEN:
779             return MIDIIn_Open(wDevID, (LPMIDIOPENDESC)dwParam1, dwParam2);
780         case MIDM_CLOSE:
781             return MIDIIn_Close(wDevID);
782         case MIDM_ADDBUFFER:
783         case MIDM_PREPARE:
784         case MIDM_UNPREPARE:
785         case MIDM_GETDEVCAPS:
786         case MIDM_GETNUMDEVS:
787         case MIDM_START:
788         case MIDM_STOP:
789         case MIDM_RESET:
790         default:
791             TRACE("Unsupported message\n");
792     }
793     return MMSYSERR_NOTSUPPORTED;
794 }
795 #else
796
797 DWORD WINAPI CoreAudio_modMessage(UINT wDevID, UINT wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2)
798 {
799     TRACE("%08x, %08x, %08x, %08x, %08x\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
800     return MMSYSERR_NOTENABLED;
801 }
802
803 DWORD WINAPI CoreAudio_midMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
804                                   DWORD dwParam1, DWORD dwParam2)
805 {
806     TRACE("%08x, %08x, %08x, %08x, %08x\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
807     return MMSYSERR_NOTENABLED;
808 }
809 #endif