winecoreaudio: Implement MIDIOut_Data.
[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
56 typedef struct tagMIDIDestination {
57     /* graph and synth are only used for MIDI Synth */
58     AUGraph graph;
59     AudioUnit synth;
60
61     MIDIPortRef port;
62     MIDIOUTCAPSW caps;
63     MIDIOPENDESC midiDesc;
64     WORD wFlags;
65 } MIDIDestination;
66
67 #define MAX_MIDI_SYNTHS 1
68
69 MIDIDestination *destinations;
70
71 extern int SynthUnit_CreateDefaultSynthUnit(AUGraph *graph, AudioUnit *synth);
72 extern int SynthUnit_Initialize(AudioUnit synth, AUGraph graph);
73 extern int SynthUnit_Close(AUGraph graph);
74
75
76 LONG CoreAudio_MIDIInit(void)
77 {
78     int i;
79     CHAR szPname[MAXPNAMELEN] = {0};
80
81     int numDest = MIDIGetNumberOfDestinations();
82     CFStringRef name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("wineMIDIClient.%d"), getpid());
83
84     wineMIDIClient = CoreMIDI_CreateClient( name );
85     if (wineMIDIClient == NULL)
86     {
87         CFRelease(name);
88         ERR("can't create wineMIDIClient\n");
89         return 0;
90     }
91     CFRelease(name);
92
93     MIDIOut_NumDevs = MAX_MIDI_SYNTHS;
94     MIDIOut_NumDevs += numDest;
95
96     TRACE("MIDIOut_NumDevs %d\n", MIDIOut_NumDevs);
97
98     destinations = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MIDIOut_NumDevs * sizeof(MIDIDestination));
99
100     /* initialise MIDI synths */
101     for (i = 0; i < MAX_MIDI_SYNTHS; i++)
102     {
103         snprintf(szPname, sizeof(szPname), "CoreAudio MIDI Synth %d", i);
104         MultiByteToWideChar(CP_ACP, 0, szPname, -1, destinations[i].caps.szPname, sizeof(destinations[i].caps.szPname)/sizeof(WCHAR));
105
106         destinations[i].caps.wTechnology = MOD_SYNTH;
107         destinations[i].caps.wChannelMask = 0xFFFF;
108
109         destinations[i].caps.wMid = 0x00FF;     /* Manufac ID */
110         destinations[i].caps.wPid = 0x0001;     /* Product ID */
111         destinations[i].caps.vDriverVersion = 0x0001;
112         destinations[i].caps.dwSupport = MIDICAPS_VOLUME;
113         destinations[i].caps.wVoices = 16;
114         destinations[i].caps.wNotes = 16;
115     }
116     /* initialise available destinations */
117     for (i = MAX_MIDI_SYNTHS; i < numDest + MAX_MIDI_SYNTHS; i++)
118     {
119         MIDIEndpointRef endpoint = MIDIGetDestination(i - MAX_MIDI_SYNTHS);
120
121         CoreMIDI_GetObjectName(endpoint, szPname, sizeof(szPname));
122         MultiByteToWideChar(CP_ACP, 0, szPname, -1, destinations[i].caps.szPname, sizeof(destinations[i].caps.szPname)/sizeof(WCHAR));
123
124         name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("WineOutputPort.%d.%u"), i, getpid());
125         MIDIOutputPortCreate(wineMIDIClient, name, &destinations[i].port);
126         CFRelease(name);
127
128         destinations[i].caps.wTechnology = MOD_MIDIPORT;
129         destinations[i].caps.wChannelMask = 0xFFFF;
130
131         destinations[i].caps.wMid = 0x00FF;     /* Manufac ID */
132         destinations[i].caps.wPid = 0x0001;
133         destinations[i].caps.vDriverVersion = 0x0001;
134         destinations[i].caps.dwSupport = 0;
135         destinations[i].caps.wVoices = 16;
136         destinations[i].caps.wNotes = 16;
137     }
138     return 1;
139 }
140
141 LONG CoreAudio_MIDIRelease(void)
142 {
143     TRACE("\n");
144     if (wineMIDIClient) MIDIClientDispose(wineMIDIClient); /* MIDIClientDispose will close all ports */
145     HeapFree(GetProcessHeap(), 0, destinations);
146     return 1;
147 }
148
149
150 /**************************************************************************
151  *                      MIDI_NotifyClient                       [internal]
152  */
153 static DWORD MIDI_NotifyClient(UINT wDevID, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
154 {
155     DWORD               dwCallBack;
156     UINT                uFlags;
157     HANDLE              hDev;
158     DWORD               dwInstance;
159
160     TRACE("wDevID=%d wMsg=%d dwParm1=%04X dwParam2=%04X\n", wDevID, wMsg, dwParam1, dwParam2);
161
162     switch (wMsg) {
163     case MOM_OPEN:
164     case MOM_CLOSE:
165     case MOM_DONE:
166     case MOM_POSITIONCB:
167         dwCallBack = destinations[wDevID].midiDesc.dwCallback;
168         uFlags = destinations[wDevID].wFlags;
169         hDev = destinations[wDevID].midiDesc.hMidi;
170         dwInstance = destinations[wDevID].midiDesc.dwInstance;
171         break;
172
173     case MIM_OPEN:
174     case MIM_CLOSE:
175     case MIM_DATA:
176     case MIM_LONGDATA:
177     case MIM_ERROR:
178     case MIM_LONGERROR:
179     case MIM_MOREDATA:
180     default:
181         WARN("Unsupported MSW-MIDI message %u\n", wMsg);
182         return MMSYSERR_ERROR;
183     }
184
185     return DriverCallback(dwCallBack, uFlags, hDev, wMsg, dwInstance, dwParam1, dwParam2) ?
186         MMSYSERR_NOERROR : MMSYSERR_ERROR;
187 }
188
189 static DWORD MIDIOut_Open(WORD wDevID, LPMIDIOPENDESC lpDesc, DWORD dwFlags)
190 {
191     MIDIDestination *dest;
192
193     TRACE("wDevID=%d lpDesc=%p dwFlags=%08x\n", wDevID, lpDesc, dwFlags);
194
195     if (lpDesc == NULL) {
196         WARN("Invalid Parameter\n");
197         return MMSYSERR_INVALPARAM;
198     }
199
200     if (wDevID >= MIDIOut_NumDevs) {
201         WARN("bad device ID : %d\n", wDevID);
202         return MMSYSERR_BADDEVICEID;
203     }
204
205     if (destinations[wDevID].midiDesc.hMidi != 0) {
206         WARN("device already open !\n");
207         return MMSYSERR_ALLOCATED;
208     }
209
210     if ((dwFlags & ~CALLBACK_TYPEMASK) != 0) {
211         WARN("bad dwFlags\n");
212         return MMSYSERR_INVALFLAG;
213     }
214     dest = &destinations[wDevID];
215
216     if (dest->caps.wTechnology == MOD_SYNTH)
217     {
218         if (!SynthUnit_CreateDefaultSynthUnit(&dest->graph, &dest->synth))
219         {
220             ERR("SynthUnit_CreateDefaultSynthUnit dest=%p failed\n", dest);
221             return MMSYSERR_ERROR;
222         }
223
224         if (!SynthUnit_Initialize(dest->synth, dest->graph))
225         {
226             ERR("SynthUnit_Initialise dest=%p failed\n", dest);
227             return MMSYSERR_ERROR;
228         }
229     }
230     else
231     {
232         FIXME("MOD_MIDIPORT\n");
233     }
234     dest->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
235     dest->midiDesc = *lpDesc;
236
237     return MIDI_NotifyClient(wDevID, MOM_OPEN, 0L, 0L);
238 }
239
240 static DWORD MIDIOut_Close(WORD wDevID)
241 {
242     DWORD ret = MMSYSERR_NOERROR;
243
244     TRACE("wDevID=%d\n", wDevID);
245
246     if (wDevID >= MIDIOut_NumDevs) {
247         WARN("bad device ID : %d\n", wDevID);
248         return MMSYSERR_BADDEVICEID;
249     }
250
251     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH)
252         SynthUnit_Close(destinations[wDevID].graph);
253     else
254         FIXME("MOD_MIDIPORT\n");
255
256     destinations[wDevID].graph = 0;
257     destinations[wDevID].synth = 0;
258
259     if (MIDI_NotifyClient(wDevID, MOM_CLOSE, 0L, 0L) != MMSYSERR_NOERROR) {
260         WARN("can't notify client !\n");
261         ret = MMSYSERR_INVALPARAM;
262     }
263     destinations[wDevID].midiDesc.hMidi = 0;
264
265     return ret;
266 }
267
268 static DWORD MIDIOut_Data(WORD wDevID, DWORD dwParam)
269 {
270     WORD evt = LOBYTE(LOWORD(dwParam));
271     WORD d1  = HIBYTE(LOWORD(dwParam));
272     WORD d2  = LOBYTE(HIWORD(dwParam));
273     UInt8 chn = (evt & 0x0F);
274     OSStatus err = noErr;
275
276     TRACE("wDevID=%d dwParam=%08X\n", wDevID, dwParam);
277
278     if (wDevID >= MIDIOut_NumDevs) {
279         WARN("bad device ID : %d\n", wDevID);
280         return MMSYSERR_BADDEVICEID;
281     }
282
283     TRACE("evt=%08x d1=%04x d2=%04x (evt & 0xF0)=%04x chn=%d\n", evt, d1, d2, (evt & 0xF0), chn);
284
285     if (destinations[wDevID].caps.wTechnology == MOD_SYNTH)
286     {
287         err = MusicDeviceMIDIEvent(destinations[wDevID].synth, (evt & 0xF0) | chn, d1, d2, 0);
288         if (err != noErr)
289         {
290             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);
291             return MMSYSERR_ERROR;
292         }
293     }
294     else FIXME("MOD_MIDIPORT\n");
295
296     return MMSYSERR_NOERROR;
297 }
298
299 /**************************************************************************
300  *                      MIDIOut_Prepare                         [internal]
301  */
302 static DWORD MIDIOut_Prepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
303 {
304     TRACE("wDevID=%d lpMidiHdr=%p dwSize=%d\n", wDevID, lpMidiHdr, dwSize);
305
306     if (wDevID >= MIDIOut_NumDevs) {
307         WARN("bad device ID : %d\n", wDevID);
308         return MMSYSERR_BADDEVICEID;
309     }
310
311     /* MS doc says that dwFlags must be set to zero, but (kinda funny) MS mciseq drivers
312      * asks to prepare MIDIHDR which dwFlags != 0.
313      * So at least check for the inqueue flag
314      */
315     if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0 ||
316         lpMidiHdr->lpData == 0 || (lpMidiHdr->dwFlags & MHDR_INQUEUE) != 0 ||
317         lpMidiHdr->dwBufferLength >= 0x10000ul) {
318         WARN("%p %p %08x %lu/%d\n", lpMidiHdr, lpMidiHdr->lpData,
319                    lpMidiHdr->dwFlags, sizeof(MIDIHDR), dwSize);
320         return MMSYSERR_INVALPARAM;
321     }
322
323     lpMidiHdr->lpNext = 0;
324     lpMidiHdr->dwFlags |= MHDR_PREPARED;
325     lpMidiHdr->dwFlags &= ~MHDR_DONE;
326     return MMSYSERR_NOERROR;
327 }
328
329 /**************************************************************************
330  *                              MIDIOut_Unprepare                       [internal]
331  */
332 static DWORD MIDIOut_Unprepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
333 {
334     TRACE("wDevID=%d lpMidiHdr=%p dwSize=%d\n", wDevID, lpMidiHdr, dwSize);
335
336     if (wDevID >= MIDIOut_NumDevs) {
337         WARN("bad device ID : %d\n", wDevID);
338         return MMSYSERR_BADDEVICEID;
339     }
340     if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0)
341         return MMSYSERR_INVALPARAM;
342     if (lpMidiHdr->dwFlags & MHDR_INQUEUE)
343         return MIDIERR_STILLPLAYING;
344
345     lpMidiHdr->dwFlags &= ~MHDR_PREPARED;
346
347     return MMSYSERR_NOERROR;
348 }
349
350 static DWORD MIDIOut_GetDevCaps(WORD wDevID, LPMIDIOUTCAPSW lpCaps, DWORD dwSize)
351 {
352     TRACE("wDevID=%d lpCaps=%p dwSize=%d\n", wDevID, lpCaps, dwSize);
353
354     if (lpCaps == NULL) {
355         WARN("Invalid Parameter\n");
356         return MMSYSERR_INVALPARAM;
357     }
358
359     if (wDevID >= MIDIOut_NumDevs) {
360         WARN("bad device ID : %d\n", wDevID);
361         return MMSYSERR_BADDEVICEID;
362     }
363     memcpy(lpCaps, &destinations[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
364     return MMSYSERR_NOERROR;
365 }
366
367 static DWORD MIDIOut_GetNumDevs(void)
368 {
369     TRACE("\n");
370     return MIDIOut_NumDevs;
371 }
372
373 /**************************************************************************
374 *                               modMessage
375 */
376 DWORD WINAPI CoreAudio_modMessage(UINT wDevID, UINT wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2)
377 {
378     TRACE("%d %08x %08x %08x %08x\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
379
380     switch (wMsg) {
381         case DRVM_INIT:
382         case DRVM_EXIT:
383         case DRVM_ENABLE:
384         case DRVM_DISABLE:
385             return 0;
386         case MODM_OPEN:
387             return MIDIOut_Open(wDevID, (LPMIDIOPENDESC)dwParam1, dwParam2);
388         case MODM_CLOSE:
389             return MIDIOut_Close(wDevID);
390         case MODM_DATA:
391             return MIDIOut_Data(wDevID, dwParam1);
392         case MODM_LONGDATA:
393             TRACE("Unsupported message (08%x)\n", wMsg);
394             return MMSYSERR_NOTSUPPORTED;
395         case MODM_PREPARE:
396             return MIDIOut_Prepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
397         case MODM_UNPREPARE:
398             return MIDIOut_Unprepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
399         case MODM_GETDEVCAPS:
400             return MIDIOut_GetDevCaps(wDevID, (LPMIDIOUTCAPSW) dwParam1, dwParam2);
401         case MODM_GETNUMDEVS:
402             return MIDIOut_GetNumDevs();
403         case MODM_GETVOLUME:
404         case MODM_SETVOLUME:
405         case MODM_RESET:
406         default:
407             TRACE("Unsupported message (08%x)\n", wMsg);
408     }
409     return MMSYSERR_NOTSUPPORTED;
410 }
411
412 #else
413
414 DWORD WINAPI CoreAudio_modMessage(UINT wDevID, UINT wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2)
415 {
416     TRACE("%08x, %08x, %08x, %08x, %08x\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
417     return MMSYSERR_NOTENABLED;
418 }
419
420 #endif