winecoreaudio: Do not access MIDIHDR past notification callback.
[wine] / dlls / winecoreaudio.drv / mixer.c
1 /*
2  * Sample MIXER Wine Driver for Mac OS X (based on OSS mixer)
3  *
4  * Copyright    1997 Marcus Meissner
5  *              1999,2001 Eric Pouech
6  *              2006,2007 Emmanuel Maillard
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22
23 #include "config.h"
24 #include "wine/port.h"
25
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <string.h>
30 #ifdef HAVE_UNISTD_H
31 # include <unistd.h>
32 #endif
33
34 #define NONAMELESSUNION
35 #define NONAMELESSSTRUCT
36 #include "windef.h"
37 #include "winbase.h"
38 #include "winnls.h"
39 #include "mmddk.h"
40 #include "coreaudio.h"
41 #include "wine/unicode.h"
42 #include "wine/debug.h"
43
44 WINE_DEFAULT_DEBUG_CHANNEL(mixer);
45
46 #if defined(HAVE_COREAUDIO_COREAUDIO_H)
47 #include <CoreAudio/CoreAudio.h>
48 #include <CoreFoundation/CoreFoundation.h>
49
50 #define WINE_MIXER_NAME "CoreAudio Mixer"
51
52 #define InputDevice (1 << 0)
53 #define OutputDevice (1 << 1)
54
55 #define IsInput(dir) ((dir) & InputDevice)
56 #define IsOutput(dir) ((dir) & OutputDevice)
57
58 #define ControlsPerLine 2 /* number of control per line : volume & (mute | onoff) */
59
60 #define IDControlVolume 0
61 #define IDControlMute 1
62
63 typedef struct tagMixerLine
64 {
65     char *name;
66     int direction;
67     int numChannels;
68     int componentType;
69     AudioDeviceID deviceID;
70 } MixerLine;
71
72 typedef struct tagMixerCtrl
73 {
74     DWORD dwLineID;
75     MIXERCONTROLW ctrl;
76 } MixerCtrl;
77
78 typedef struct tagCoreAudio_Mixer
79 {
80     MIXERCAPSW caps;
81
82     MixerCtrl *mixerCtrls;
83     MixerLine *lines;
84     DWORD numCtrl;
85 } CoreAudio_Mixer;
86
87 static CoreAudio_Mixer mixer;
88 static int numMixers = 1;
89
90 /**************************************************************************
91 */
92
93 static const char * getMessage(UINT uMsg)
94 {
95 #define MSG_TO_STR(x) case x: return #x;
96     switch (uMsg) {
97         MSG_TO_STR(DRVM_INIT);
98         MSG_TO_STR(DRVM_EXIT);
99         MSG_TO_STR(DRVM_ENABLE);
100         MSG_TO_STR(DRVM_DISABLE);
101         MSG_TO_STR(MXDM_GETDEVCAPS);
102         MSG_TO_STR(MXDM_GETLINEINFO);
103         MSG_TO_STR(MXDM_GETNUMDEVS);
104         MSG_TO_STR(MXDM_OPEN);
105         MSG_TO_STR(MXDM_CLOSE);
106         MSG_TO_STR(MXDM_GETLINECONTROLS);
107         MSG_TO_STR(MXDM_GETCONTROLDETAILS);
108         MSG_TO_STR(MXDM_SETCONTROLDETAILS);
109     }
110 #undef MSG_TO_STR
111     return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg);
112 }
113
114 static const char * getControlType(DWORD dwControlType)
115 {
116 #define TYPE_TO_STR(x) case x: return #x;
117     switch (dwControlType) {
118         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
119         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
120         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
121         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
122         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
123         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
124         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
125         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
126         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
127         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
128         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
129         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
130         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
131         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
132         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
133         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
134         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
135         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
136         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
137         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
138         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
139         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
140         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
141         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
142         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
143         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
144         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
145         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
146         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
147         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
148         TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
149     }
150 #undef TYPE_TO_STR
151     return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
152 }
153
154 static const char * getComponentType(DWORD dwComponentType)
155 {
156 #define TYPE_TO_STR(x) case x: return #x;
157     switch (dwComponentType) {
158         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_UNDEFINED);
159         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_DIGITAL);
160         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_LINE);
161         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_MONITOR);
162         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS);
163         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_HEADPHONES);
164         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_TELEPHONE);
165         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_WAVEIN);
166         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_VOICEIN);
167         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED);
168         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_DIGITAL);
169         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_LINE);
170         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE);
171         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER);
172         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC);
173         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE);
174         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER);
175         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT);
176         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY);
177         TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_ANALOG);
178     }
179 #undef TYPE_TO_STR
180     return wine_dbg_sprintf("UNKNOWN(%08x)", dwComponentType);
181 }
182
183 static const char * getTargetType(DWORD dwType)
184 {
185 #define TYPE_TO_STR(x) case x: return #x;
186     switch (dwType) {
187         TYPE_TO_STR(MIXERLINE_TARGETTYPE_UNDEFINED);
188         TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEOUT);
189         TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEIN);
190         TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIOUT);
191         TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIIN);
192         TYPE_TO_STR(MIXERLINE_TARGETTYPE_AUX);
193     }
194 #undef TYPE_TO_STR
195     return wine_dbg_sprintf("UNKNOWN(%08x)", dwType);
196 }
197
198 /* FIXME is there a better way ? */
199 static DWORD DeviceComponentType(char *name)
200 {
201     if (strcmp(name, "Built-in Microphone") == 0)
202         return MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
203
204     if (strcmp(name, "Built-in Line Input") == 0)
205         return MIXERLINE_COMPONENTTYPE_SRC_LINE;
206
207     if (strcmp(name, "Built-in Output") == 0)
208         return MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
209
210     return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
211 }
212
213 static BOOL DeviceHasMute(AudioDeviceID deviceID, Boolean isInput)
214 {
215     Boolean writable = false;
216     OSStatus err = noErr;
217     AudioObjectPropertyAddress propertyAddress;
218     propertyAddress.mSelector = kAudioDevicePropertyMute;
219     propertyAddress.mScope = isInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
220     propertyAddress.mElement = 0;
221     if (AudioObjectHasProperty(deviceID, &propertyAddress))
222     {
223         /* check if we can set it */
224         err = AudioObjectIsPropertySettable(deviceID, &propertyAddress, &writable);
225         if (err == noErr)
226             return writable;
227     }
228     return FALSE;
229 }
230
231 /*
232  * Getters
233  */
234 static BOOL MIX_LineGetVolume(DWORD lineID, DWORD channels, Float32 *left, Float32 *right)
235 {
236     MixerLine *line = &mixer.lines[lineID];
237     UInt32 size = sizeof(Float32);
238     OSStatus err = noErr;
239     AudioObjectPropertyAddress address;
240     *left = *right = 0.0;
241
242     address.mSelector = kAudioDevicePropertyVolumeScalar;
243     address.mScope = IsInput(line->direction) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
244     address.mElement = 1;
245     err = AudioObjectGetPropertyData(line->deviceID, &address, 0, NULL, &size, left);
246     if (err != noErr)
247         return FALSE;
248
249     if (channels == 2)
250     {
251         size = sizeof(Float32);
252         address.mElement = 2;
253         err = AudioObjectGetPropertyData(line->deviceID, &address, 0, NULL, &size, right);
254         if (err != noErr)
255             return FALSE;
256     }
257
258     TRACE("lineID %d channels %d return left %f right %f\n", lineID, channels, *left, *right);
259     return (err == noErr);
260 }
261
262 static BOOL MIX_LineGetMute(DWORD lineID, BOOL *muted)
263 {
264     MixerLine *line = &mixer.lines[lineID];
265     UInt32 size = sizeof(UInt32);
266     UInt32 val = 0;
267     OSStatus err = noErr;
268     AudioObjectPropertyAddress address;
269     address.mSelector = kAudioDevicePropertyMute;
270     address.mScope = IsInput(line->direction) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
271     address.mElement = 0;
272     err = AudioObjectGetPropertyData(line->deviceID, &address, 0, NULL, &size, &val);
273     *muted = val;
274
275     return (err == noErr);
276 }
277
278 /*
279  * Setters
280  */
281 static BOOL MIX_LineSetVolume(DWORD lineID, DWORD channels, Float32 left, Float32 right)
282 {
283     MixerLine *line = &mixer.lines[lineID];
284     UInt32 size = sizeof(Float32);
285     AudioObjectPropertyAddress address;
286     OSStatus err = noErr;
287     TRACE("lineID %d channels %d left %f right %f\n", lineID, channels, left, right);
288
289     address.mSelector = kAudioDevicePropertyVolumeScalar;
290     address.mScope = IsInput(line->direction) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
291     if (channels == 2)
292     {
293         address.mElement = 1;
294         err = AudioObjectSetPropertyData(line->deviceID, &address, 0, NULL, size, &left);
295         if (err != noErr)
296             return FALSE;
297
298         address.mElement = 2;
299         err = AudioObjectSetPropertyData(line->deviceID, &address, 0, NULL, size, &right);
300     }
301     else
302     {
303         /*
304             FIXME Using master channel failed ?? return kAudioHardwareUnknownPropertyError
305             address.mElement = 0;
306             err = AudioObjectSetPropertyData(line->deviceID, &address, 0, NULL, size, &left);
307         */
308         right = left;
309         address.mElement = 1;
310         err = AudioObjectSetPropertyData(line->deviceID, &address, 0, NULL, size, &left);
311         if (err != noErr)
312             return FALSE;
313         address.mElement = 2;
314         err = AudioObjectSetPropertyData(line->deviceID, &address, 0, NULL, size, &right);
315     }
316     return (err == noErr);
317 }
318
319 static BOOL MIX_LineSetMute(DWORD lineID, BOOL mute)
320 {
321     MixerLine *line = &mixer.lines[lineID];
322     UInt32 val = mute;
323     UInt32 size = sizeof(UInt32);
324     AudioObjectPropertyAddress address;
325     OSStatus err = noErr;
326
327     address.mSelector = kAudioDevicePropertyMute;
328     address.mScope = IsInput(line->direction) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
329     address.mElement = 0;
330     err = AudioObjectSetPropertyData(line->deviceID, &address, 0, 0, size, &val);
331     return (err == noErr);
332 }
333
334 static void MIX_FillControls(void)
335 {
336     int i;
337     int ctrl = 0;
338     MixerLine *line;
339     for (i = 0; i < mixer.caps.cDestinations; i++)
340     {
341         line = &mixer.lines[i];
342         mixer.mixerCtrls[ctrl].dwLineID = i;
343         mixer.mixerCtrls[ctrl].ctrl.cbStruct = sizeof(MIXERCONTROLW);
344         mixer.mixerCtrls[ctrl].ctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
345         mixer.mixerCtrls[ctrl].ctrl.dwControlID = ctrl;
346         mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMinimum = 0;
347         mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMaximum = 65535;
348         mixer.mixerCtrls[ctrl].ctrl.Metrics.cSteps = 656;
349         ctrl++;
350
351         mixer.mixerCtrls[ctrl].dwLineID = i;
352         if ( !DeviceHasMute(line->deviceID, IsInput(line->direction)) )
353             mixer.mixerCtrls[ctrl].ctrl.fdwControl |= MIXERCONTROL_CONTROLF_DISABLED;
354
355         mixer.mixerCtrls[ctrl].ctrl.cbStruct = sizeof(MIXERCONTROLW);
356         mixer.mixerCtrls[ctrl].ctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
357         mixer.mixerCtrls[ctrl].ctrl.dwControlID = ctrl;
358         mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMinimum = 0;
359         mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMaximum = 1;
360         ctrl++;
361     }
362     assert(ctrl == mixer.numCtrl);
363 }
364
365 /**************************************************************************
366 *                               CoreAudio_MixerInit
367 */
368 LONG CoreAudio_MixerInit(void)
369 {
370     OSStatus status;
371     UInt32 propertySize;
372     AudioObjectPropertyAddress propertyAddress;
373     AudioDeviceID *deviceArray = NULL;
374     CFStringRef name;
375     int i;
376     int numLines;
377
378     AudioStreamBasicDescription streamDescription;
379
380     /* Find number of lines */
381     propertyAddress.mSelector = kAudioHardwarePropertyDevices;
382     propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
383     propertyAddress.mElement = kAudioObjectPropertyElementMaster;
384     status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize);
385     if (status)
386     {
387         ERR("AudioObjectGetPropertyDataSize for kAudioHardwarePropertyDevices return %s\n", wine_dbgstr_fourcc(status));
388         return DRV_FAILURE;
389     }
390
391     numLines = propertySize / sizeof(AudioDeviceID);
392
393     mixer.mixerCtrls = NULL;
394     mixer.lines = NULL;
395     mixer.numCtrl = 0;
396
397     mixer.caps.cDestinations = numLines;
398     mixer.caps.wMid = 0xAA;
399     mixer.caps.wPid = 0x55;
400     mixer.caps.vDriverVersion = 0x0100;
401
402     MultiByteToWideChar(CP_ACP, 0, WINE_MIXER_NAME, -1, mixer.caps.szPname, sizeof(mixer.caps.szPname) / sizeof(WCHAR));
403
404     mixer.caps.fdwSupport = 0; /* No bits defined yet */
405
406     mixer.lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MixerLine) * numLines);
407     if (!mixer.lines)
408         goto error;
409
410     deviceArray = HeapAlloc(GetProcessHeap(), 0, sizeof(AudioDeviceID) * numLines);
411
412     propertySize = sizeof(AudioDeviceID) * numLines;
413     status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, deviceArray);
414     if (status)
415     {
416         ERR("AudioObjectGetPropertyData for kAudioHardwarePropertyDevices return %s\n", wine_dbgstr_fourcc(status));
417         goto error;
418     }
419
420     for (i = 0; i < numLines; i++)
421     {
422         MixerLine *line = &mixer.lines[i];
423
424         line->deviceID = deviceArray[i];
425
426         propertySize = sizeof(CFStringRef);
427         propertyAddress.mSelector = kAudioObjectPropertyName;
428         propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
429         propertyAddress.mElement = kAudioObjectPropertyElementMaster;
430         status = AudioObjectGetPropertyData(line->deviceID, &propertyAddress, 0, NULL, &propertySize, &name);
431         if (status) {
432             ERR("AudioObjectGetPropertyData for kAudioObjectPropertyName return %s\n", wine_dbgstr_fourcc(status));
433             goto error;
434         }
435
436         line->name = HeapAlloc(GetProcessHeap(), 0, CFStringGetLength(name) + 1);
437         if (!line->name)
438             goto error;
439
440         CFStringGetCString(name, line->name, CFStringGetLength(name) + 1, kCFStringEncodingUTF8);
441
442         line->componentType = DeviceComponentType(line->name);
443
444         /* check for directions */
445         /* Output ? */
446         propertySize = sizeof(UInt32);
447         propertyAddress.mSelector = kAudioDevicePropertyStreams;
448         propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
449         status = AudioObjectGetPropertyDataSize(line->deviceID, &propertyAddress, 0, NULL, &propertySize);
450         if (status) {
451             ERR("AudioObjectGetPropertyDataSize for kAudioDevicePropertyStreams return %s\n", wine_dbgstr_fourcc(status));
452             goto error;
453         }
454
455         if ( (propertySize / sizeof(AudioStreamID)) != 0)
456         {
457             line->direction |= OutputDevice;
458
459             /* Check the number of channel for the stream */
460             propertySize = sizeof(streamDescription);
461             propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
462             status = AudioObjectGetPropertyData(line->deviceID, &propertyAddress, 0, NULL, &propertySize, &streamDescription);
463             if (status != noErr) {
464                 ERR("AudioObjectGetPropertyData for kAudioDevicePropertyStreamFormat return %s\n", wine_dbgstr_fourcc(status));
465                 goto error;
466             }
467             line->numChannels = streamDescription.mChannelsPerFrame;
468         }
469         else
470         {
471             /* Input ? */
472             propertySize = sizeof(UInt32);
473             propertyAddress.mScope = kAudioDevicePropertyScopeInput;
474             status = AudioObjectGetPropertyDataSize(line->deviceID, &propertyAddress, 0, NULL, &propertySize);
475             if (status) {
476                 ERR("AudioObjectGetPropertyDataSize for kAudioDevicePropertyStreams return %s\n", wine_dbgstr_fourcc(status));
477                 goto error;
478             }
479             if ( (propertySize / sizeof(AudioStreamID)) != 0)
480             {
481                 line->direction |= InputDevice;
482
483                 /* Check the number of channel for the stream */
484                 propertySize = sizeof(streamDescription);
485                 propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
486                 status = AudioObjectGetPropertyData(line->deviceID, &propertyAddress, 0, NULL, &propertySize, &streamDescription);
487                 if (status != noErr) {
488                     ERR("AudioObjectGetPropertyData for kAudioDevicePropertyStreamFormat return %s\n", wine_dbgstr_fourcc(status));
489                     goto error;
490                 }
491                 line->numChannels = streamDescription.mChannelsPerFrame;
492             }
493         }
494
495         mixer.numCtrl += ControlsPerLine; /* volume & (mute | onoff) */
496     }
497     mixer.mixerCtrls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MixerCtrl) * mixer.numCtrl);
498     if (!mixer.mixerCtrls)
499         goto error;
500
501     MIX_FillControls();
502
503     HeapFree(GetProcessHeap(), 0, deviceArray);
504     return DRV_SUCCESS;
505
506 error:
507     if (mixer.lines)
508     {
509         int i;
510         for (i = 0; i < mixer.caps.cDestinations; i++)
511         {
512             HeapFree(GetProcessHeap(), 0, mixer.lines[i].name);
513         }
514         HeapFree(GetProcessHeap(), 0, mixer.lines);
515     }
516     HeapFree(GetProcessHeap(), 0, deviceArray);
517     if (mixer.mixerCtrls)
518         HeapFree(GetProcessHeap(), 0, mixer.mixerCtrls);
519     return DRV_FAILURE;
520 }
521
522 /**************************************************************************
523 *                               CoreAudio_MixerRelease
524 */
525 void CoreAudio_MixerRelease(void)
526 {
527     TRACE("()\n");
528
529     if (mixer.lines)
530     {
531         int i;
532         for (i = 0; i < mixer.caps.cDestinations; i++)
533         {
534             HeapFree(GetProcessHeap(), 0, mixer.lines[i].name);
535         }
536         HeapFree(GetProcessHeap(), 0, mixer.lines);
537     }
538     if (mixer.mixerCtrls)
539         HeapFree(GetProcessHeap(), 0, mixer.mixerCtrls);
540 }
541
542 /**************************************************************************
543 *                               MIX_Open                        [internal]
544 */
545 static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD_PTR flags)
546 {
547     TRACE("wDevID=%d lpMod=%p dwSize=%08lx\n", wDevID, lpMod, flags);
548     if (lpMod == NULL) {
549         WARN("invalid parameter: lpMod == NULL\n");
550         return MMSYSERR_INVALPARAM;
551     }
552
553     if (wDevID >= numMixers) {
554         WARN("bad device ID: %04X\n", wDevID);
555         return MMSYSERR_BADDEVICEID;
556     }
557     return MMSYSERR_NOERROR;
558 }
559
560 /**************************************************************************
561 *                               MIX_GetNumDevs                  [internal]
562 */
563 static DWORD MIX_GetNumDevs(void)
564 {
565     TRACE("()\n");
566     return numMixers;
567 }
568
569 static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSW lpCaps, DWORD_PTR dwSize)
570 {
571     TRACE("wDevID=%d lpCaps=%p\n", wDevID, lpCaps);
572
573     if (lpCaps == NULL) {
574         WARN("Invalid Parameter\n");
575         return MMSYSERR_INVALPARAM;
576     }
577
578     if (wDevID >= numMixers) {
579         WARN("bad device ID : %d\n", wDevID);
580         return MMSYSERR_BADDEVICEID;
581     }
582     memcpy(lpCaps, &mixer.caps, min(dwSize, sizeof(*lpCaps)));
583     return MMSYSERR_NOERROR;
584 }
585
586 /**************************************************************************
587 *                               MIX_GetLineInfo                 [internal]
588 */
589 static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEW lpMl, DWORD_PTR fdwInfo)
590 {
591     int i;
592     DWORD ret = MMSYSERR_ERROR;
593     MixerLine *line = NULL;
594
595     TRACE("%04X, %p, %08lx\n", wDevID, lpMl, fdwInfo);
596
597     if (lpMl == NULL) {
598         WARN("invalid parameter: lpMl = NULL\n");
599         return MMSYSERR_INVALPARAM;
600     }
601
602     if (lpMl->cbStruct != sizeof(*lpMl)) {
603         WARN("invalid parameter: lpMl->cbStruct\n");
604         return MMSYSERR_INVALPARAM;
605     }
606
607     if (wDevID >= numMixers) {
608         WARN("bad device ID: %04X\n", wDevID);
609         return MMSYSERR_BADDEVICEID;
610     }
611
612     /* FIXME: set all the variables correctly... the lines below
613         * are very wrong...
614         */
615     lpMl->dwUser = 0;
616
617     switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK)
618     {
619         case MIXER_GETLINEINFOF_DESTINATION:
620             TRACE("MIXER_GETLINEINFOF_DESTINATION %d\n", lpMl->dwDestination);
621             if ( (lpMl->dwDestination >= 0) && (lpMl->dwDestination < mixer.caps.cDestinations) )
622             {
623                 lpMl->dwLineID = lpMl->dwDestination;
624                 line = &mixer.lines[lpMl->dwDestination];
625             }
626             else ret = MIXERR_INVALLINE;
627             break;
628         case MIXER_GETLINEINFOF_COMPONENTTYPE:
629             TRACE("MIXER_GETLINEINFOF_COMPONENTTYPE %s\n", getComponentType(lpMl->dwComponentType));
630             for (i = 0; i < mixer.caps.cDestinations; i++)
631             {
632                 if (mixer.lines[i].componentType == lpMl->dwComponentType)
633                 {
634                     lpMl->dwDestination = lpMl->dwLineID = i;
635                     line = &mixer.lines[i];
636                     break;
637                 }
638             }
639             if (line == NULL)
640             {
641                 WARN("can't find component type %s\n", getComponentType(lpMl->dwComponentType));
642                 ret = MIXERR_INVALVALUE;
643             }
644             break;
645         case MIXER_GETLINEINFOF_SOURCE:
646             FIXME("MIXER_GETLINEINFOF_SOURCE %d dst=%d\n", lpMl->dwSource, lpMl->dwDestination);
647             break;
648         case MIXER_GETLINEINFOF_LINEID:
649             TRACE("MIXER_GETLINEINFOF_LINEID %d\n", lpMl->dwLineID);
650             if ( (lpMl->dwLineID >= 0) && (lpMl->dwLineID < mixer.caps.cDestinations) )
651             {
652                 lpMl->dwDestination = lpMl->dwLineID;
653                 line = &mixer.lines[lpMl->dwLineID];
654             }
655             else ret = MIXERR_INVALLINE;
656             break;
657         case MIXER_GETLINEINFOF_TARGETTYPE:
658             FIXME("MIXER_GETLINEINFOF_TARGETTYPE (%s)\n", getTargetType(lpMl->Target.dwType));
659             switch (lpMl->Target.dwType) {
660                 case MIXERLINE_TARGETTYPE_UNDEFINED:
661                 case MIXERLINE_TARGETTYPE_WAVEOUT:
662                 case MIXERLINE_TARGETTYPE_WAVEIN:
663                 case MIXERLINE_TARGETTYPE_MIDIOUT:
664                 case MIXERLINE_TARGETTYPE_MIDIIN:
665                 case MIXERLINE_TARGETTYPE_AUX:
666                 default:
667                     FIXME("Unhandled target type (%s)\n",
668                           getTargetType(lpMl->Target.dwType));
669                     return MMSYSERR_INVALPARAM;
670             }
671                 break;
672         default:
673             WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
674             break;
675     }
676
677     if (line)
678     {
679         lpMl->dwComponentType = line->componentType;
680         lpMl->cChannels = line->numChannels;
681         lpMl->cControls = ControlsPerLine;
682
683         /* FIXME check there with CoreAudio */
684         lpMl->cConnections = 1;
685         lpMl->fdwLine = MIXERLINE_LINEF_ACTIVE;
686
687         MultiByteToWideChar(CP_ACP, 0, line->name, -1, lpMl->szShortName, sizeof(lpMl->szShortName) / sizeof(WCHAR));
688         MultiByteToWideChar(CP_ACP, 0, line->name, -1, lpMl->szName, sizeof(lpMl->szName) / sizeof(WCHAR));
689
690         if ( IsInput(line->direction) )
691             lpMl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
692         else
693             lpMl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
694
695         lpMl->Target.dwDeviceID = line->deviceID;
696         lpMl->Target.wMid = mixer.caps.wMid;
697         lpMl->Target.wPid = mixer.caps.wPid;
698         lpMl->Target.vDriverVersion = mixer.caps.vDriverVersion;
699
700         MultiByteToWideChar(CP_ACP, 0, WINE_MIXER_NAME, -1, lpMl->Target.szPname, sizeof(lpMl->Target.szPname) / sizeof(WCHAR));
701         ret = MMSYSERR_NOERROR;
702     }
703     return ret;
704 }
705
706 /**************************************************************************
707 *                               MIX_GetLineControls             [internal]
708 */
709 static DWORD MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSW lpMlc, DWORD_PTR flags)
710 {
711     DWORD ret = MMSYSERR_NOTENABLED;
712     int ctrl = 0;
713     TRACE("%04X, %p, %08lX\n", wDevID, lpMlc, flags);
714
715     if (lpMlc == NULL) {
716         WARN("invalid parameter: lpMlc == NULL\n");
717         return MMSYSERR_INVALPARAM;
718     }
719
720     if (lpMlc->cbStruct < sizeof(*lpMlc)) {
721         WARN("invalid parameter: lpMlc->cbStruct = %d\n", lpMlc->cbStruct);
722         return MMSYSERR_INVALPARAM;
723     }
724
725     if (lpMlc->cbmxctrl < sizeof(MIXERCONTROLW)) {
726         WARN("invalid parameter: lpMlc->cbmxctrl = %d\n", lpMlc->cbmxctrl);
727         return MMSYSERR_INVALPARAM;
728     }
729
730     if (wDevID >= numMixers) {
731         WARN("bad device ID: %04X\n", wDevID);
732         return MMSYSERR_BADDEVICEID;
733     }
734
735     switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK)
736     {
737         case MIXER_GETLINECONTROLSF_ALL:
738             FIXME("dwLineID=%d MIXER_GETLINECONTROLSF_ALL (%d)\n", lpMlc->dwLineID, lpMlc->cControls);
739             if (lpMlc->cControls != ControlsPerLine)
740             {
741                 WARN("invalid parameter lpMlc->cControls %d\n", lpMlc->cControls);
742                 ret = MMSYSERR_INVALPARAM;
743             }
744             else
745             {
746                 if ( (lpMlc->dwLineID >= 0) && (lpMlc->dwLineID < mixer.caps.cDestinations) )
747                 {
748                     int i;
749                     for (i = 0; i < lpMlc->cControls; i++)
750                     {
751                         lpMlc->pamxctrl[i] = mixer.mixerCtrls[lpMlc->dwLineID * i].ctrl;
752                     }
753                     ret = MMSYSERR_NOERROR;
754                 }
755                 else ret = MIXERR_INVALLINE;
756             }
757             break;
758         case MIXER_GETLINECONTROLSF_ONEBYID:
759             TRACE("dwLineID=%d MIXER_GETLINECONTROLSF_ONEBYID (%d)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
760             if ( lpMlc->u.dwControlID >= 0 && lpMlc->u.dwControlID < mixer.numCtrl )
761             {
762                 lpMlc->pamxctrl[0] = mixer.mixerCtrls[lpMlc->u.dwControlID].ctrl;
763                 ret = MMSYSERR_NOERROR;
764             }
765             else ret = MIXERR_INVALVALUE;
766             break;
767         case MIXER_GETLINECONTROLSF_ONEBYTYPE:
768             TRACE("dwLineID=%d MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", lpMlc->dwLineID, getControlType(lpMlc->u.dwControlType));
769             if ( (lpMlc->dwLineID < 0) || (lpMlc->dwLineID >= mixer.caps.cDestinations) )
770             {
771                 ret = MIXERR_INVALLINE;
772                 break;
773             }
774             if (lpMlc->u.dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
775             {
776                 ctrl = (lpMlc->dwLineID * ControlsPerLine) + IDControlVolume;
777                 lpMlc->pamxctrl[0] = mixer.mixerCtrls[ctrl].ctrl;
778                 ret = MMSYSERR_NOERROR;
779             }
780             else
781                 if (lpMlc->u.dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
782                 {
783                     ctrl = (lpMlc->dwLineID * ControlsPerLine) + IDControlMute;
784                     lpMlc->pamxctrl[0] = mixer.mixerCtrls[ctrl].ctrl;
785                     ret = MMSYSERR_NOERROR;
786                 }
787                 break;
788         default:
789             ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
790             ret = MMSYSERR_INVALPARAM;
791     }
792
793     return ret;
794 }
795
796 /**************************************************************************
797  *                              MIX_GetControlDetails           [internal]
798  */
799 static DWORD MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD_PTR fdwDetails)
800 {
801     DWORD ret = MMSYSERR_NOTSUPPORTED;
802     DWORD dwControlType;
803
804     TRACE("%04X, %p, %08lx\n", wDevID, lpmcd, fdwDetails);
805
806     if (lpmcd == NULL) {
807         TRACE("invalid parameter: lpmcd == NULL\n");
808         return MMSYSERR_INVALPARAM;
809     }
810
811     if (wDevID >= numMixers) {
812         WARN("bad device ID: %04X\n", wDevID);
813         return MMSYSERR_BADDEVICEID;
814     }
815
816     if ( (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) != MIXER_GETCONTROLDETAILSF_VALUE )
817     {
818         WARN("Unknown/unimplement GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
819         return MMSYSERR_NOTSUPPORTED;
820     }
821
822     if ( lpmcd->dwControlID < 0 || lpmcd->dwControlID >= mixer.numCtrl )
823     {
824         WARN("bad control ID: %d\n", lpmcd->dwControlID);
825         return MIXERR_INVALVALUE;
826     }
827
828     TRACE("MIXER_GETCONTROLDETAILSF_VALUE %d\n", lpmcd->dwControlID);
829
830     dwControlType = mixer.mixerCtrls[lpmcd->dwControlID].ctrl.dwControlType;
831     switch (dwControlType)
832     {
833         case MIXERCONTROL_CONTROLTYPE_VOLUME:
834             FIXME("controlType : %s channels %d\n", getControlType(dwControlType), lpmcd->cChannels);
835             {
836                 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
837                 Float32 left, right;
838
839                 if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED)) {
840                     WARN("invalid parameter: lpmcd->cbDetails == %d\n", lpmcd->cbDetails);
841                     return MMSYSERR_INVALPARAM;
842                 }
843
844                 if ( MIX_LineGetVolume(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, lpmcd->cChannels, &left, &right) )
845                 {
846                     mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
847
848                     switch (lpmcd->cChannels)
849                     {
850                         case 1:
851                             /* mono... so R = L */
852                             mcdu->dwValue = left * 65535;
853                             TRACE("Reading RL = %d\n", mcdu->dwValue);
854                             break;
855                         case 2:
856                             /* stereo, left is paDetails[0] */
857                             mcdu->dwValue = left * 65535;
858                             TRACE("Reading L = %d\n", mcdu->dwValue);
859                             mcdu++;
860                             mcdu->dwValue = right * 65535;
861                             TRACE("Reading R = %d\n", mcdu->dwValue);
862                             break;
863                         default:
864                             WARN("Unsupported cChannels (%d)\n", lpmcd->cChannels);
865                             return MMSYSERR_INVALPARAM;
866                     }
867                     TRACE("=> %08x\n", mcdu->dwValue);
868                     ret = MMSYSERR_NOERROR;
869                 }
870             }
871             break;
872         case MIXERCONTROL_CONTROLTYPE_MUTE:
873         case MIXERCONTROL_CONTROLTYPE_ONOFF:
874             FIXME("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(dwControlType), lpmcd->cChannels);
875             {
876                 LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
877                 BOOL muted;
878                 if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN)) {
879                     WARN("invalid parameter: lpmcd->cbDetails = %d\n", lpmcd->cbDetails);
880                     return MMSYSERR_INVALPARAM;
881                 }
882                 mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
883
884                 if ( MIX_LineGetMute(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, &muted) )
885                 {
886                     mcdb->fValue = muted;
887                     TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
888                     ret = MMSYSERR_NOERROR;
889                 }
890             }
891             break;
892         case MIXERCONTROL_CONTROLTYPE_MIXER:
893         case MIXERCONTROL_CONTROLTYPE_MUX:
894         default:
895             FIXME("controlType : %s\n", getControlType(dwControlType));
896             break;
897     }
898     return ret;
899 }
900
901 /**************************************************************************
902 *                               MIX_SetControlDetails           [internal]
903 */
904 static DWORD MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD_PTR fdwDetails)
905 {
906     DWORD ret = MMSYSERR_NOTSUPPORTED;
907     DWORD dwControlType;
908
909     TRACE("%04X, %p, %08lx\n", wDevID, lpmcd, fdwDetails);
910
911     if (lpmcd == NULL) {
912         TRACE("invalid parameter: lpmcd == NULL\n");
913         return MMSYSERR_INVALPARAM;
914     }
915
916     if (wDevID >= numMixers) {
917         WARN("bad device ID: %04X\n", wDevID);
918         return MMSYSERR_BADDEVICEID;
919     }
920
921     if ( (fdwDetails & MIXER_SETCONTROLDETAILSF_QUERYMASK) != MIXER_GETCONTROLDETAILSF_VALUE )
922     {
923         WARN("Unknown SetControlDetails flag (%08lx)\n", fdwDetails & MIXER_SETCONTROLDETAILSF_QUERYMASK);
924         return MMSYSERR_NOTSUPPORTED;
925     }
926
927     TRACE("MIXER_SETCONTROLDETAILSF_VALUE dwControlID=%d\n", lpmcd->dwControlID);
928     dwControlType = mixer.mixerCtrls[lpmcd->dwControlID].ctrl.dwControlType;
929     switch (dwControlType)
930     {
931         case MIXERCONTROL_CONTROLTYPE_VOLUME:
932             FIXME("controlType : %s\n", getControlType(dwControlType));
933             {
934                 LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
935                 Float32 left, right = 0;
936
937                 if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED)) {
938                     WARN("invalid parameter: lpmcd->cbDetails == %d\n", lpmcd->cbDetails);
939                     return MMSYSERR_INVALPARAM;
940                 }
941
942                 mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
943
944                 switch (lpmcd->cChannels)
945                 {
946                     case 1:
947                         /* mono... so R = L */
948                         TRACE("Setting RL to %d\n", mcdu->dwValue);
949                         left = (Float32) mcdu->dwValue / 65535.0;
950                         break;
951                     case 2:
952                         /* stereo, left is paDetails[0] */
953                         TRACE("Setting L to %d\n", mcdu->dwValue);
954                         left = (Float32) mcdu->dwValue / 65535.0;
955                         mcdu++;
956                         TRACE("Setting R to %d\n", mcdu->dwValue);
957                         right = (Float32) mcdu->dwValue / 65535.0;
958                         break;
959                     default:
960                         WARN("Unsupported cChannels (%d)\n", lpmcd->cChannels);
961                         return MMSYSERR_INVALPARAM;
962                 }
963                 if ( MIX_LineSetVolume(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, lpmcd->cChannels, left, right) )
964                     ret = MMSYSERR_NOERROR;
965             }
966             break;
967         case MIXERCONTROL_CONTROLTYPE_MUTE:
968         case MIXERCONTROL_CONTROLTYPE_ONOFF:
969             TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(dwControlType), lpmcd->cChannels);
970             {
971                 LPMIXERCONTROLDETAILS_BOOLEAN   mcdb;
972
973                 if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN)) {
974                     WARN("invalid parameter: cbDetails\n");
975                     return MMSYSERR_INVALPARAM;
976                 }
977                 mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
978                 if ( MIX_LineSetMute(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, mcdb->fValue) )
979                     ret = MMSYSERR_NOERROR;
980             }
981             break;
982         case MIXERCONTROL_CONTROLTYPE_MIXER:
983         case MIXERCONTROL_CONTROLTYPE_MUX:
984         default:
985             FIXME("controlType : %s\n", getControlType(dwControlType));
986             ret = MMSYSERR_NOTSUPPORTED;
987             break;
988     }
989     return ret;
990 }
991
992 /**************************************************************************
993 *                               mxdMessage
994 */
995 DWORD WINAPI CoreAudio_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
996                                   DWORD_PTR dwParam1, DWORD_PTR dwParam2)
997 {
998     TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
999           dwUser, dwParam1, dwParam2);
1000
1001     switch (wMsg)
1002     {
1003         case DRVM_INIT:
1004         case DRVM_EXIT:
1005         case DRVM_ENABLE:
1006         case DRVM_DISABLE:
1007             /* FIXME: Pretend this is supported */
1008             return 0;
1009         case MXDM_OPEN:
1010             return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
1011         case MXDM_CLOSE:
1012             return MMSYSERR_NOERROR;
1013         case MXDM_GETNUMDEVS:
1014             return MIX_GetNumDevs();
1015         case MXDM_GETDEVCAPS:
1016             return MIX_GetDevCaps(wDevID, (LPMIXERCAPSW)dwParam1, dwParam2);
1017         case MXDM_GETLINEINFO:
1018             return MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2);
1019         case MXDM_GETLINECONTROLS:
1020             return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2);
1021         case MXDM_GETCONTROLDETAILS:
1022             return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
1023         case MXDM_SETCONTROLDETAILS:
1024             return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
1025         default:
1026             WARN("unknown message %d!\n", wMsg);
1027             return MMSYSERR_NOTSUPPORTED;
1028     }
1029 }
1030
1031 #else
1032
1033 DWORD WINAPI CoreAudio_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1034                                   DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1035 {
1036     TRACE("(%04X, %04x, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
1037     return MMSYSERR_NOTENABLED;
1038 }
1039 #endif /* HAVE_COREAUDIO_COREAUDIO_H */