/*
 * Sample MIXER Wine Driver for Mac OS X (based on OSS mixer)
 *
 * Copyright 	1997 Marcus Meissner
 * 		1999,2001 Eric Pouech
 *              2006,2007 Emmanuel Maillard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"
#include "wine/port.h"

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "mmddk.h"
#include "coreaudio.h"
#include "wine/unicode.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(mixer);

#if defined(HAVE_COREAUDIO_COREAUDIO_H)
#include <CoreAudio/CoreAudio.h>
#include <CoreFoundation/CoreFoundation.h>

#define	WINE_MIXER_NAME "CoreAudio Mixer"

#define InputDevice (1 << 0)
#define OutputDevice (1 << 1)

#define IsInput(dir) ((dir) & InputDevice)
#define IsOutput(dir) ((dir) & OutputDevice)

#define ControlsPerLine 2 /* number of control per line : volume & (mute | onoff) */

#define IDControlVolume 0
#define IDControlMute 1

typedef struct tagMixerLine
{
    char *name;
    int direction;
    int numChannels;
    int componentType;
    AudioDeviceID deviceID;
} MixerLine;

typedef struct tagMixerCtrl
{
    DWORD dwLineID;
    MIXERCONTROLW ctrl;
} MixerCtrl;

typedef struct tagCoreAudio_Mixer
{
    MIXERCAPSW caps;

    MixerCtrl *mixerCtrls;
    MixerLine *lines;
    DWORD numCtrl;
} CoreAudio_Mixer;

static CoreAudio_Mixer mixer;
static int numMixers = 1;

/**************************************************************************
*/

static const char * getMessage(UINT uMsg)
{
    static char str[64];
#define MSG_TO_STR(x) case x: return #x;
    switch (uMsg) {
        MSG_TO_STR(DRVM_INIT);
        MSG_TO_STR(DRVM_EXIT);
        MSG_TO_STR(DRVM_ENABLE);
        MSG_TO_STR(DRVM_DISABLE);
        MSG_TO_STR(MXDM_GETDEVCAPS);
        MSG_TO_STR(MXDM_GETLINEINFO);
        MSG_TO_STR(MXDM_GETNUMDEVS);
        MSG_TO_STR(MXDM_OPEN);
        MSG_TO_STR(MXDM_CLOSE);
        MSG_TO_STR(MXDM_GETLINECONTROLS);
        MSG_TO_STR(MXDM_GETCONTROLDETAILS);
        MSG_TO_STR(MXDM_SETCONTROLDETAILS);
    }
#undef MSG_TO_STR
        sprintf(str, "UNKNOWN(%08x)", uMsg);
    return str;
}

static const char * getControlType(DWORD dwControlType)
{
    static char str[64];
#define TYPE_TO_STR(x) case x: return #x;
    switch (dwControlType) {
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
        TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
    }
#undef TYPE_TO_STR
        sprintf(str, "UNKNOWN(%08x)", dwControlType);
    return str;
}

static const char * getComponentType(DWORD dwComponentType)
{
    static char str[64];
#define TYPE_TO_STR(x) case x: return #x;
    switch (dwComponentType) {
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_UNDEFINED);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_DIGITAL);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_LINE);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_MONITOR);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_HEADPHONES);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_TELEPHONE);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_WAVEIN);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_VOICEIN);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_DIGITAL);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_LINE);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY);
        TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_ANALOG);
    }
#undef TYPE_TO_STR
        sprintf(str, "UNKNOWN(%08x)", dwComponentType);
    return str;
}

static const char * getTargetType(DWORD dwType)
{
    static char str[64];
#define TYPE_TO_STR(x) case x: return #x;
    switch (dwType) {
        TYPE_TO_STR(MIXERLINE_TARGETTYPE_UNDEFINED);
        TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEOUT);
        TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEIN);
        TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIOUT);
        TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIIN);
        TYPE_TO_STR(MIXERLINE_TARGETTYPE_AUX);
    }
#undef TYPE_TO_STR
        sprintf(str, "UNKNOWN(%08x)", dwType);
    return str;
}

/* FIXME is there a better way ? */
static DWORD DeviceComponentType(char *name)
{
    if (strcmp(name, "Built-in Microphone") == 0)
        return MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;

    if (strcmp(name, "Built-in Line Input") == 0)
        return MIXERLINE_COMPONENTTYPE_SRC_LINE;

    if (strcmp(name, "Built-in Output") == 0)
        return MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;

    return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
}

static BOOL DeviceHasMute(AudioDeviceID deviceID, Boolean isInput)
{
    Boolean writable = false;
    OSStatus err = noErr;
    err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyMute, NULL, NULL);
    if (err == noErr)
    {
        /* check if we can set it */
        err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyMute, NULL, &writable);
        if (err == noErr)
            return writable;
    }
    return FALSE;
}

/*
 * Getters
 */
static BOOL MIX_LineGetVolume(DWORD lineID, DWORD channels, Float32 *left, Float32 *right)
{
    MixerLine *line = &mixer.lines[lineID];
    UInt32 size = sizeof(Float32);
    OSStatus err = noErr;
    *left = *right = 0.0;

    err = AudioDeviceGetProperty(line->deviceID, 1, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, &size, left);
    if (err != noErr)
        return FALSE;

    if (channels == 2)
    {
        size = sizeof(Float32);
        err = AudioDeviceGetProperty(line->deviceID, 2, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, &size, right);
        if (err != noErr)
            return FALSE;
    }

    TRACE("lineID %d channels %d return left %f right %f\n", lineID, channels, *left, *right);
    return (err == noErr);
}

static BOOL MIX_LineGetMute(DWORD lineID, BOOL *muted)
{
    MixerLine *line = &mixer.lines[lineID];
    UInt32 size = sizeof(UInt32);
    UInt32 val = 0;
    OSStatus err = noErr;
    err = AudioDeviceGetProperty(line->deviceID, 0, IsInput(line->direction), kAudioDevicePropertyMute, &size, &val);
    *muted = val;

    return (err == noErr);
}

/*
 * Setters
 */
static BOOL MIX_LineSetVolume(DWORD lineID, DWORD channels, Float32 left, Float32 right)
{
    MixerLine *line = &mixer.lines[lineID];
    UInt32 size = sizeof(Float32);
    OSStatus err = noErr;
    TRACE("lineID %d channels %d left %f right %f\n", lineID, channels, left, right);

    if (channels == 2)
    {
        err = AudioDeviceSetProperty(line->deviceID, NULL, 1, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &left);
        if (err != noErr)
            return FALSE;

        err = AudioDeviceSetProperty(line->deviceID, NULL, 2, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &right);
    }
    else
    {
        /*
            FIXME Using master channel failed ?? return kAudioHardwareUnknownPropertyError
            err = AudioDeviceSetProperty(line->deviceID, NULL, 0, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &left);
        */
        right = left;
        err = AudioDeviceSetProperty(line->deviceID, NULL, 1, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &left);
        if (err != noErr)
            return FALSE;
        err = AudioDeviceSetProperty(line->deviceID, NULL, 2, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &right);
    }
    return (err == noErr);
}

static BOOL MIX_LineSetMute(DWORD lineID, BOOL mute)
{
    MixerLine *line = &mixer.lines[lineID];
    UInt32 val = mute;
    UInt32 size = sizeof(UInt32);
    OSStatus err = noErr;

    err = AudioDeviceSetProperty(line->deviceID, 0, 0, IsInput(line->direction), kAudioDevicePropertyMute, size, &val);
    return (err == noErr);
}

static void MIX_FillControls(void)
{
    int i;
    int ctrl = 0;
    MixerLine *line;
    for (i = 0; i < mixer.caps.cDestinations; i++)
    {
        line = &mixer.lines[i];
        mixer.mixerCtrls[ctrl].dwLineID = i;
        mixer.mixerCtrls[ctrl].ctrl.cbStruct = sizeof(MIXERCONTROLW);
        mixer.mixerCtrls[ctrl].ctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
        mixer.mixerCtrls[ctrl].ctrl.dwControlID = ctrl;
        mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMinimum = 0;
        mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMaximum = 65535;
        mixer.mixerCtrls[ctrl].ctrl.Metrics.cSteps = 656;
        ctrl++;

        mixer.mixerCtrls[ctrl].dwLineID = i;
        if ( !DeviceHasMute(line->deviceID, IsInput(line->direction)) )
            mixer.mixerCtrls[ctrl].ctrl.fdwControl |= MIXERCONTROL_CONTROLF_DISABLED;

        mixer.mixerCtrls[ctrl].ctrl.cbStruct = sizeof(MIXERCONTROLW);
        mixer.mixerCtrls[ctrl].ctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
        mixer.mixerCtrls[ctrl].ctrl.dwControlID = ctrl;
        mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMinimum = 0;
        mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMaximum = 1;
        ctrl++;
    }
    assert(ctrl == mixer.numCtrl);
}

/**************************************************************************
* 				CoreAudio_MixerInit
*/
LONG CoreAudio_MixerInit(void)
{
    OSStatus status;
    UInt32 propertySize;
    AudioDeviceID *deviceArray = NULL;
    char name[MAXPNAMELEN];
    int i;
    int numLines;

    AudioStreamBasicDescription streamDescription;

    /* Find number of lines */
    status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propertySize, NULL);
    if (status)
    {
        ERR("AudioHardwareGetPropertyInfo for kAudioHardwarePropertyDevices return %c%c%c%c\n", (char) (status >> 24),
            (char) (status >> 16),
            (char) (status >> 8),
            (char) status);
        return DRV_FAILURE;
    }

    numLines = propertySize / sizeof(AudioDeviceID);

    mixer.mixerCtrls = NULL;
    mixer.lines = NULL;
    mixer.numCtrl = 0;

    mixer.caps.cDestinations = numLines;
    mixer.caps.wMid = 0xAA;
    mixer.caps.wPid = 0x55;
    mixer.caps.vDriverVersion = 0x0100;

    MultiByteToWideChar(CP_ACP, 0, WINE_MIXER_NAME, -1, mixer.caps.szPname, sizeof(mixer.caps.szPname) / sizeof(WCHAR));

    mixer.caps.fdwSupport = 0; /* No bits defined yet */

    mixer.lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MixerLine) * numLines);
    if (!mixer.lines)
        goto error;

    deviceArray = HeapAlloc(GetProcessHeap(), 0, sizeof(AudioDeviceID) * numLines);

    propertySize = sizeof(AudioDeviceID) * numLines;
    status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &propertySize, deviceArray);
    if (status)
    {
        ERR("AudioHardwareGetProperty for kAudioHardwarePropertyDevices return %c%c%c%c\n", (char) (status >> 24),
            (char) (status >> 16),
            (char) (status >> 8),
            (char) status);
        goto error;
    }

    for (i = 0; i < numLines; i++)
    {
        Boolean write;
        MixerLine *line = &mixer.lines[i];

        line->deviceID = deviceArray[i];

        propertySize = MAXPNAMELEN;
        status = AudioDeviceGetProperty(line->deviceID, 0 , FALSE, kAudioDevicePropertyDeviceName, &propertySize, name);
        if (status) {
            ERR("AudioHardwareGetProperty for kAudioDevicePropertyDeviceName return %c%c%c%c\n", (char) (status >> 24),
                (char) (status >> 16),
                (char) (status >> 8),
                (char) status);
            goto error;
        }

        line->name = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, strlen(name) + 1);
        if (!line->name)
            goto error;

        memcpy(line->name, name, strlen(name));

        line->componentType = DeviceComponentType(line->name);

        /* check for directions */
        /* Output ? */
        propertySize = sizeof(UInt32);
	status = AudioDeviceGetPropertyInfo(line->deviceID, 0, FALSE, kAudioDevicePropertyStreams, &propertySize, &write );
        if (status) {
            ERR("AudioDeviceGetPropertyInfo for kAudioDevicePropertyDataSource return %c%c%c%c\n", (char) (status >> 24),
                (char) (status >> 16),
                (char) (status >> 8),
                (char) status);
            goto error;
        }

        if ( (propertySize / sizeof(AudioStreamID)) != 0)
        {
            line->direction |= OutputDevice;

            /* Check the number of channel for the stream */
            propertySize = sizeof(streamDescription);
            status = AudioDeviceGetProperty(line->deviceID, 0, FALSE , kAudioDevicePropertyStreamFormat, &propertySize, &streamDescription);
            if (status != noErr) {
                ERR("AudioHardwareGetProperty for kAudioDevicePropertyStreamFormat return %c%c%c%c\n", (char) (status >> 24),
                    (char) (status >> 16),
                    (char) (status >> 8),
                    (char) status);
                goto error;
            }
            line->numChannels = streamDescription.mChannelsPerFrame;
        }
        else
        {
            /* Input ? */
            propertySize = sizeof(UInt32);
            status = AudioDeviceGetPropertyInfo(line->deviceID, 0, TRUE, kAudioDevicePropertyStreams, &propertySize, &write );
            if (status) {
                ERR("AudioDeviceGetPropertyInfo for kAudioDevicePropertyStreams return %c%c%c%c\n", (char) (status >> 24),
                    (char) (status >> 16),
                    (char) (status >> 8),
                    (char) status);
                goto error;
            }
            if ( (propertySize / sizeof(AudioStreamID)) != 0)
            {
                line->direction |= InputDevice;

                /* Check the number of channel for the stream */
                propertySize = sizeof(streamDescription);
                status = AudioDeviceGetProperty(line->deviceID, 0, TRUE, kAudioDevicePropertyStreamFormat, &propertySize, &streamDescription);
                if (status != noErr) {
                    ERR("AudioHardwareGetProperty for kAudioDevicePropertyStreamFormat return %c%c%c%c\n", (char) (status >> 24),
                        (char) (status >> 16),
                        (char) (status >> 8),
                        (char) status);
                    goto error;
                }
                line->numChannels = streamDescription.mChannelsPerFrame;
            }
        }

        mixer.numCtrl += ControlsPerLine; /* volume & (mute | onoff) */
    }
    mixer.mixerCtrls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MixerCtrl) * mixer.numCtrl);
    if (!mixer.mixerCtrls)
        goto error;

    MIX_FillControls();

    HeapFree(GetProcessHeap(), 0, deviceArray);
    return DRV_SUCCESS;

error:
    if (mixer.lines)
    {
        int i;
        for (i = 0; i < mixer.caps.cDestinations; i++)
        {
            HeapFree(GetProcessHeap(), 0, mixer.lines[i].name);
        }
        HeapFree(GetProcessHeap(), 0, mixer.lines);
    }
    HeapFree(GetProcessHeap(), 0, deviceArray);
    if (mixer.mixerCtrls)
        HeapFree(GetProcessHeap(), 0, mixer.mixerCtrls);
    return DRV_FAILURE;
}

/**************************************************************************
* 				CoreAudio_MixerRelease
*/
void CoreAudio_MixerRelease(void)
{
    TRACE("()\n");

    if (mixer.lines)
    {
        int i;
        for (i = 0; i < mixer.caps.cDestinations; i++)
        {
            HeapFree(GetProcessHeap(), 0, mixer.lines[i].name);
        }
        HeapFree(GetProcessHeap(), 0, mixer.lines);
    }
    if (mixer.mixerCtrls)
        HeapFree(GetProcessHeap(), 0, mixer.mixerCtrls);
}

/**************************************************************************
* 				MIX_Open			[internal]
*/
static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD_PTR flags)
{
    TRACE("wDevID=%d lpMod=%p dwSize=%08lx\n", wDevID, lpMod, flags);
    if (lpMod == NULL) {
        WARN("invalid parameter: lpMod == NULL\n");
        return MMSYSERR_INVALPARAM;
    }

    if (wDevID >= numMixers) {
        WARN("bad device ID: %04X\n", wDevID);
        return MMSYSERR_BADDEVICEID;
    }
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 				MIX_GetNumDevs			[internal]
*/
static DWORD MIX_GetNumDevs(void)
{
    TRACE("()\n");
    return numMixers;
}

static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSW lpCaps, DWORD_PTR dwSize)
{
    TRACE("wDevID=%d lpCaps=%p\n", wDevID, lpCaps);

    if (lpCaps == NULL) {
	WARN("Invalid Parameter\n");
	return MMSYSERR_INVALPARAM;
    }

    if (wDevID >= numMixers) {
        WARN("bad device ID : %d\n", wDevID);
	return MMSYSERR_BADDEVICEID;
    }
    memcpy(lpCaps, &mixer.caps, min(dwSize, sizeof(*lpCaps)));
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 				MIX_GetLineInfo			[internal]
*/
static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEW lpMl, DWORD_PTR fdwInfo)
{
    int i;
    DWORD ret = MMSYSERR_ERROR;
    MixerLine *line = NULL;

    TRACE("%04X, %p, %08lx\n", wDevID, lpMl, fdwInfo);

    if (lpMl == NULL) {
        WARN("invalid parameter: lpMl = NULL\n");
	return MMSYSERR_INVALPARAM;
    }

    if (lpMl->cbStruct != sizeof(*lpMl)) {
        WARN("invalid parameter: lpMl->cbStruct\n");
	return MMSYSERR_INVALPARAM;
    }

    if (wDevID >= numMixers) {
        WARN("bad device ID: %04X\n", wDevID);
        return MMSYSERR_BADDEVICEID;
    }

    /* FIXME: set all the variables correctly... the lines below
        * are very wrong...
        */
    lpMl->dwUser = 0;

    switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK)
    {
        case MIXER_GETLINEINFOF_DESTINATION:
            TRACE("MIXER_GETLINEINFOF_DESTINATION %d\n", lpMl->dwDestination);
            if ( (lpMl->dwDestination >= 0) && (lpMl->dwDestination < mixer.caps.cDestinations) )
            {
                lpMl->dwLineID = lpMl->dwDestination;
                line = &mixer.lines[lpMl->dwDestination];
            }
            else ret = MIXERR_INVALLINE;
            break;
        case MIXER_GETLINEINFOF_COMPONENTTYPE:
            TRACE("MIXER_GETLINEINFOF_COMPONENTTYPE %s\n", getComponentType(lpMl->dwComponentType));
            for (i = 0; i < mixer.caps.cDestinations; i++)
            {
                if (mixer.lines[i].componentType == lpMl->dwComponentType)
                {
                    lpMl->dwDestination = lpMl->dwLineID = i;
                    line = &mixer.lines[i];
                    break;
                }
            }
            if (line == NULL)
            {
                WARN("can't find component type %s\n", getComponentType(lpMl->dwComponentType));
                ret = MIXERR_INVALVALUE;
            }
            break;
        case MIXER_GETLINEINFOF_SOURCE:
            FIXME("MIXER_GETLINEINFOF_SOURCE %d dst=%d\n", lpMl->dwSource, lpMl->dwDestination);
            break;
        case MIXER_GETLINEINFOF_LINEID:
            TRACE("MIXER_GETLINEINFOF_LINEID %d\n", lpMl->dwLineID);
            if ( (lpMl->dwLineID >= 0) && (lpMl->dwLineID < mixer.caps.cDestinations) )
            {
                lpMl->dwDestination = lpMl->dwLineID;
                line = &mixer.lines[lpMl->dwLineID];
            }
            else ret = MIXERR_INVALLINE;
            break;
        case MIXER_GETLINEINFOF_TARGETTYPE:
            FIXME("MIXER_GETLINEINFOF_TARGETTYPE (%s)\n", getTargetType(lpMl->Target.dwType));
            switch (lpMl->Target.dwType) {
                case MIXERLINE_TARGETTYPE_UNDEFINED:
                case MIXERLINE_TARGETTYPE_WAVEOUT:
                case MIXERLINE_TARGETTYPE_WAVEIN:
                case MIXERLINE_TARGETTYPE_MIDIOUT:
                case MIXERLINE_TARGETTYPE_MIDIIN:
                case MIXERLINE_TARGETTYPE_AUX:
                default:
                    FIXME("Unhandled target type (%s)\n",
                          getTargetType(lpMl->Target.dwType));
                    return MMSYSERR_INVALPARAM;
            }
                break;
        default:
            WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
            break;
    }

    if (line)
    {
        lpMl->dwComponentType = line->componentType;
        lpMl->cChannels = line->numChannels;
        lpMl->cControls = ControlsPerLine;

        /* FIXME check there with CoreAudio */
        lpMl->cConnections = 1;
        lpMl->fdwLine = MIXERLINE_LINEF_ACTIVE;

        MultiByteToWideChar(CP_ACP, 0, line->name, -1, lpMl->szShortName, sizeof(lpMl->szShortName) / sizeof(WCHAR));
        MultiByteToWideChar(CP_ACP, 0, line->name, -1, lpMl->szName, sizeof(lpMl->szName) / sizeof(WCHAR));

        if ( IsInput(line->direction) )
            lpMl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
        else
            lpMl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;

        lpMl->Target.dwDeviceID = line->deviceID;
        lpMl->Target.wMid = mixer.caps.wMid;
        lpMl->Target.wPid = mixer.caps.wPid;
        lpMl->Target.vDriverVersion = mixer.caps.vDriverVersion;

        MultiByteToWideChar(CP_ACP, 0, WINE_MIXER_NAME, -1, lpMl->Target.szPname, sizeof(lpMl->Target.szPname) / sizeof(WCHAR));
        ret = MMSYSERR_NOERROR;
    }
    return ret;
}

/**************************************************************************
* 				MIX_GetLineControls		[internal]
*/
static DWORD MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSW lpMlc, DWORD_PTR flags)
{
    DWORD ret = MMSYSERR_NOTENABLED;
    int ctrl = 0;
    TRACE("%04X, %p, %08lX\n", wDevID, lpMlc, flags);

    if (lpMlc == NULL) {
        WARN("invalid parameter: lpMlc == NULL\n");
        return MMSYSERR_INVALPARAM;
    }

    if (lpMlc->cbStruct < sizeof(*lpMlc)) {
        WARN("invalid parameter: lpMlc->cbStruct = %d\n", lpMlc->cbStruct);
	return MMSYSERR_INVALPARAM;
    }

    if (lpMlc->cbmxctrl < sizeof(MIXERCONTROLW)) {
        WARN("invalid parameter: lpMlc->cbmxctrl = %d\n", lpMlc->cbmxctrl);
	return MMSYSERR_INVALPARAM;
    }

    if (wDevID >= numMixers) {
        WARN("bad device ID: %04X\n", wDevID);
        return MMSYSERR_BADDEVICEID;
    }

    switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK)
    {
        case MIXER_GETLINECONTROLSF_ALL:
            FIXME("dwLineID=%d MIXER_GETLINECONTROLSF_ALL (%d)\n", lpMlc->dwLineID, lpMlc->cControls);
            if (lpMlc->cControls != ControlsPerLine)
            {
                WARN("invalid parameter lpMlc->cControls %d\n", lpMlc->cControls);
                ret = MMSYSERR_INVALPARAM;
	    }
            else
            {
                if ( (lpMlc->dwLineID >= 0) && (lpMlc->dwLineID < mixer.caps.cDestinations) )
                {
                    int i;
                    for (i = 0; i < lpMlc->cControls; i++)
                    {
                        lpMlc->pamxctrl[i] = mixer.mixerCtrls[lpMlc->dwLineID * i].ctrl;
                    }
                    ret = MMSYSERR_NOERROR;
                }
                else ret = MIXERR_INVALLINE;
            }
            break;
        case MIXER_GETLINECONTROLSF_ONEBYID:
            TRACE("dwLineID=%d MIXER_GETLINECONTROLSF_ONEBYID (%d)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
            if ( lpMlc->u.dwControlID >= 0 && lpMlc->u.dwControlID < mixer.numCtrl )
            {
                lpMlc->pamxctrl[0] = mixer.mixerCtrls[lpMlc->u.dwControlID].ctrl;
                ret = MMSYSERR_NOERROR;
            }
            else ret = MIXERR_INVALVALUE;
            break;
        case MIXER_GETLINECONTROLSF_ONEBYTYPE:
            TRACE("dwLineID=%d MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", lpMlc->dwLineID, getControlType(lpMlc->u.dwControlType));
            if ( (lpMlc->dwLineID < 0) || (lpMlc->dwLineID >= mixer.caps.cDestinations) )
            {
                ret = MIXERR_INVALLINE;
                break;
            }
            if (lpMlc->u.dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
            {
                ctrl = (lpMlc->dwLineID * ControlsPerLine) + IDControlVolume;
                lpMlc->pamxctrl[0] = mixer.mixerCtrls[ctrl].ctrl;
                ret = MMSYSERR_NOERROR;
            }
            else
                if (lpMlc->u.dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
                {
                    ctrl = (lpMlc->dwLineID * ControlsPerLine) + IDControlMute;
                    lpMlc->pamxctrl[0] = mixer.mixerCtrls[ctrl].ctrl;
                    ret = MMSYSERR_NOERROR;
                }
                break;
        default:
            ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
            ret = MMSYSERR_INVALPARAM;
    }

    return ret;
}

/**************************************************************************
 * 				MIX_GetControlDetails		[internal]
 */
static DWORD MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD_PTR fdwDetails)
{
    DWORD ret = MMSYSERR_NOTSUPPORTED;
    DWORD dwControlType;

    TRACE("%04X, %p, %08lx\n", wDevID, lpmcd, fdwDetails);

    if (lpmcd == NULL) {
        TRACE("invalid parameter: lpmcd == NULL\n");
        return MMSYSERR_INVALPARAM;
    }

    if (wDevID >= numMixers) {
        WARN("bad device ID: %04X\n", wDevID);
        return MMSYSERR_BADDEVICEID;
    }

    if ( (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) != MIXER_GETCONTROLDETAILSF_VALUE )
    {
        WARN("Unknown/unimplement GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
        return MMSYSERR_NOTSUPPORTED;
    }

    if ( lpmcd->dwControlID < 0 || lpmcd->dwControlID >= mixer.numCtrl )
    {
        WARN("bad control ID: %d\n", lpmcd->dwControlID);
        return MIXERR_INVALVALUE;
    }

    TRACE("MIXER_GETCONTROLDETAILSF_VALUE %d\n", lpmcd->dwControlID);

    dwControlType = mixer.mixerCtrls[lpmcd->dwControlID].ctrl.dwControlType;
    switch (dwControlType)
    {
        case MIXERCONTROL_CONTROLTYPE_VOLUME:
            FIXME("controlType : %s channels %d\n", getControlType(dwControlType), lpmcd->cChannels);
            {
                LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
                Float32 left, right;

                if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED)) {
                    WARN("invalid parameter: lpmcd->cbDetails == %d\n", lpmcd->cbDetails);
                    return MMSYSERR_INVALPARAM;
                }

                if ( MIX_LineGetVolume(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, lpmcd->cChannels, &left, &right) )
                {
                    mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;

		    switch (lpmcd->cChannels)
		    {
                        case 1:
                            /* mono... so R = L */
                            mcdu->dwValue = left * 65535;
                            TRACE("Reading RL = %d\n", mcdu->dwValue);
                            break;
                        case 2:
                            /* stereo, left is paDetails[0] */
                            mcdu->dwValue = left * 65535;
                            TRACE("Reading L = %d\n", mcdu->dwValue);
                            mcdu++;
                            mcdu->dwValue = right * 65535;
                            TRACE("Reading R = %d\n", mcdu->dwValue);
                            break;
                        default:
                            WARN("Unsupported cChannels (%d)\n", lpmcd->cChannels);
                            return MMSYSERR_INVALPARAM;
		    }
                    TRACE("=> %08x\n", mcdu->dwValue);
                    ret = MMSYSERR_NOERROR;
                }
            }
            break;
        case MIXERCONTROL_CONTROLTYPE_MUTE:
        case MIXERCONTROL_CONTROLTYPE_ONOFF:
            FIXME("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(dwControlType), lpmcd->cChannels);
            {
                LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
                BOOL muted;
                if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN)) {
                    WARN("invalid parameter: lpmcd->cbDetails = %d\n", lpmcd->cbDetails);
                    return MMSYSERR_INVALPARAM;
                }
                mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;

                if ( MIX_LineGetMute(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, &muted) )
                {
                    mcdb->fValue = muted;
		    TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
                    ret = MMSYSERR_NOERROR;
                }
            }
            break;
        case MIXERCONTROL_CONTROLTYPE_MIXER:
        case MIXERCONTROL_CONTROLTYPE_MUX:
        default:
            FIXME("controlType : %s\n", getControlType(dwControlType));
            break;
    }
    return ret;
}

/**************************************************************************
* 				MIX_SetControlDetails		[internal]
*/
static DWORD MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD_PTR fdwDetails)
{
    DWORD ret = MMSYSERR_NOTSUPPORTED;
    DWORD dwControlType;

    TRACE("%04X, %p, %08lx\n", wDevID, lpmcd, fdwDetails);

    if (lpmcd == NULL) {
        TRACE("invalid parameter: lpmcd == NULL\n");
        return MMSYSERR_INVALPARAM;
    }

    if (wDevID >= numMixers) {
        WARN("bad device ID: %04X\n", wDevID);
        return MMSYSERR_BADDEVICEID;
    }

    if ( (fdwDetails & MIXER_SETCONTROLDETAILSF_QUERYMASK) != MIXER_GETCONTROLDETAILSF_VALUE )
    {
        WARN("Unknown SetControlDetails flag (%08lx)\n", fdwDetails & MIXER_SETCONTROLDETAILSF_QUERYMASK);
        return MMSYSERR_NOTSUPPORTED;
    }

    TRACE("MIXER_SETCONTROLDETAILSF_VALUE dwControlID=%d\n", lpmcd->dwControlID);
    dwControlType = mixer.mixerCtrls[lpmcd->dwControlID].ctrl.dwControlType;
    switch (dwControlType)
    {
        case MIXERCONTROL_CONTROLTYPE_VOLUME:
            FIXME("controlType : %s\n", getControlType(dwControlType));
            {
                LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
                Float32 left, right = 0;

                if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED)) {
                    WARN("invalid parameter: lpmcd->cbDetails == %d\n", lpmcd->cbDetails);
                    return MMSYSERR_INVALPARAM;
                }

                mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;

                switch (lpmcd->cChannels)
                {
                    case 1:
                        /* mono... so R = L */
                        TRACE("Setting RL to %d\n", mcdu->dwValue);
                        left = (Float32) mcdu->dwValue / 65535.0;
                        break;
                    case 2:
                        /* stereo, left is paDetails[0] */
			TRACE("Setting L to %d\n", mcdu->dwValue);
			left = (Float32) mcdu->dwValue / 65535.0;
                        mcdu++;
			TRACE("Setting R to %d\n", mcdu->dwValue);
			right = (Float32) mcdu->dwValue / 65535.0;
			break;
                    default:
                        WARN("Unsupported cChannels (%d)\n", lpmcd->cChannels);
                        return MMSYSERR_INVALPARAM;
                }
                if ( MIX_LineSetVolume(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, lpmcd->cChannels, left, right) )
                    ret = MMSYSERR_NOERROR;
            }
            break;
        case MIXERCONTROL_CONTROLTYPE_MUTE:
        case MIXERCONTROL_CONTROLTYPE_ONOFF:
            TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(dwControlType), lpmcd->cChannels);
            {
                LPMIXERCONTROLDETAILS_BOOLEAN	mcdb;

                if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN)) {
                    WARN("invalid parameter: cbDetails\n");
                    return MMSYSERR_INVALPARAM;
                }
                mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
                if ( MIX_LineSetMute(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, mcdb->fValue) )
                    ret = MMSYSERR_NOERROR;
            }
            break;
        case MIXERCONTROL_CONTROLTYPE_MIXER:
        case MIXERCONTROL_CONTROLTYPE_MUX:
        default:
            FIXME("controlType : %s\n", getControlType(dwControlType));
            ret = MMSYSERR_NOTSUPPORTED;
            break;
    }
    return ret;
}

/**************************************************************************
* 				mxdMessage
*/
DWORD WINAPI CoreAudio_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
                                  DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
    TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
          dwUser, dwParam1, dwParam2);

    switch (wMsg)
    {
        case DRVM_INIT:
        case DRVM_EXIT:
        case DRVM_ENABLE:
        case DRVM_DISABLE:
            /* FIXME: Pretend this is supported */
            return 0;
        case MXDM_OPEN:
            return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
        case MXDM_CLOSE:
            return MMSYSERR_NOERROR;
        case MXDM_GETNUMDEVS:
            return MIX_GetNumDevs();
        case MXDM_GETDEVCAPS:
            return MIX_GetDevCaps(wDevID, (LPMIXERCAPSW)dwParam1, dwParam2);
        case MXDM_GETLINEINFO:
            return MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2);
        case MXDM_GETLINECONTROLS:
            return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2);
        case MXDM_GETCONTROLDETAILS:
            return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
        case MXDM_SETCONTROLDETAILS:
            return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
        default:
            WARN("unknown message %d!\n", wMsg);
            return MMSYSERR_NOTSUPPORTED;
    }
}

#else

DWORD WINAPI CoreAudio_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
                                  DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
    TRACE("(%04X, %04x, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
    return MMSYSERR_NOTENABLED;
}
#endif /* HAVE_COREAUDIO_COREAUDIO_H */
