/*
 * Wine Driver for CoreAudio based on Jack Driver
 *
 * Copyright 1994 Martin Ayotte
 * Copyright 1999 Eric Pouech (async playing in waveOut/waveIn)
 * Copyright 2000 Eric Pouech (loops in waveOut)
 * Copyright 2002 Chris Morgan (jack version of this file)
 * Copyright 2005, 2006 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 <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <fcntl.h>
#ifdef HAVE_PTHREAD_H
# include <pthread.h>
#endif

#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "wingdi.h"
#include "winerror.h"
#include "mmddk.h"
#include "dsound.h"
#include "dsdriver.h"
#include "coreaudio.h"
#include "wine/unicode.h"
#include "wine/library.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(wave);


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

/*
    Due to AudioUnit headers conflict define some needed types.
*/

typedef void *AudioUnit;

/* From AudioUnit/AUComponents.h */
typedef UInt32 AudioUnitRenderActionFlags;

/* only allow 10 output devices through this driver, this ought to be adequate */
#define MAX_WAVEOUTDRV  (1)
#define MAX_WAVEINDRV   (1)

/* state diagram for waveOut writing:
*
* +---------+-------------+---------------+---------------------------------+
* |  state  |  function   |     event     |            new state	     |
* +---------+-------------+---------------+---------------------------------+
* |	     | open()	   |		   | STOPPED		       	     |
* | PAUSED  | write()	   | 		   | PAUSED		       	     |
* | STOPPED | write()	   | <thrd create> | PLAYING		  	     |
* | PLAYING | write()	   | HEADER        | PLAYING		  	     |
* | (other) | write()	   | <error>       |		       		     |
* | (any)   | pause()	   | PAUSING	   | PAUSED		       	     |
* | PAUSED  | restart()   | RESTARTING    | PLAYING (if no thrd => STOPPED) |
* | (any)   | reset()	   | RESETTING     | STOPPED		      	     |
* | (any)   | close()	   | CLOSING	   | CLOSED		      	     |
* +---------+-------------+---------------+---------------------------------+
*/

/* states of the playing device */
#define	WINE_WS_PLAYING   0
#define	WINE_WS_PAUSED    1
#define	WINE_WS_STOPPED   2
#define WINE_WS_CLOSED    3

typedef struct tagCoreAudio_Device {
    char                        dev_name[32];
    char                        mixer_name[32];
    unsigned                    open_count;
    char*                       interface_name;
    
    WAVEOUTCAPSW                out_caps;
    WAVEINCAPSW                 in_caps;
    DWORD                       in_caps_support;
    int                         sample_rate;
    int                         stereo;
    int                         format;
    unsigned                    audio_fragment;
    BOOL                        full_duplex;
    BOOL                        bTriggerSupport;
    BOOL                        bOutputEnabled;
    BOOL                        bInputEnabled;
    DSDRIVERDESC                ds_desc;
    DSDRIVERCAPS                ds_caps;
    DSCDRIVERCAPS               dsc_caps;
    GUID                        ds_guid;
    GUID                        dsc_guid;
    
    AudioDeviceID outputDeviceID;
    AudioDeviceID inputDeviceID;
    AudioStreamBasicDescription streamDescription;
} CoreAudio_Device;

/* for now use the default device */
static CoreAudio_Device CoreAudio_DefaultDevice;

typedef struct {
    volatile int                state;      /* one of the WINE_WS_ manifest constants */
    CoreAudio_Device            *cadev;
    WAVEOPENDESC                waveDesc;
    WORD                        wFlags;
    PCMWAVEFORMAT               format;
    DWORD                       woID;
    AudioUnit                   audioUnit;
    AudioStreamBasicDescription streamDescription;
    
    WAVEOUTCAPSW                caps;
    char                        interface_name[32];
    LPWAVEHDR			lpQueuePtr;		/* start of queued WAVEHDRs (waiting to be notified) */
    LPWAVEHDR			lpPlayPtr;		/* start of not yet fully played buffers */
    DWORD			dwPartialOffset;	/* Offset of not yet written bytes in lpPlayPtr */
    
    LPWAVEHDR			lpLoopPtr;              /* pointer of first buffer in loop, if any */
    DWORD			dwLoops;		/* private copy of loop counter */
    
    DWORD			dwPlayedTotal;		/* number of bytes actually played since opening */
    DWORD                       dwWrittenTotal;         /* number of bytes written to OSS buffer since opening */
        
    DWORD                       tickCountMS; /* time in MS of last AudioUnit callback */

    pthread_mutex_t             lock;         /* synchronization stuff */
} WINE_WAVEOUT;

typedef struct {
    volatile int    state;
    CoreAudio_Device *cadev;
    WAVEOPENDESC    waveDesc;
    WORD            wFlags;
    PCMWAVEFORMAT   format;
    LPWAVEHDR       lpQueuePtr;
    DWORD           dwTotalRecorded;
    WAVEINCAPSW     caps;
    
    AudioUnit                   audioUnit;
    AudioStreamBasicDescription streamDescription;
        
    /*  BOOL            bTriggerSupport;
    WORD              wDevID;
    char              interface_name[32];*/
        
    /* synchronization stuff */
    pthread_mutex_t lock;
} WINE_WAVEIN;

static WINE_WAVEOUT WOutDev   [MAX_WAVEOUTDRV];

static CFStringRef MessageThreadPortName;

static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo);
static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force);

extern int AudioUnit_CreateDefaultAudioUnit(void *wwo, AudioUnit *au);
extern int AudioUnit_CloseAudioUnit(AudioUnit au);
extern int AudioUnit_InitializeWithStreamDescription(AudioUnit au, AudioStreamBasicDescription *streamFormat);

extern OSStatus AudioOutputUnitStart(AudioUnit au);
extern OSStatus AudioOutputUnitStop(AudioUnit au);
extern OSStatus AudioUnitUninitialize(AudioUnit au);

extern int AudioUnit_SetVolume(AudioUnit au, float left, float right);
extern int AudioUnit_GetVolume(AudioUnit au, float *left, float *right);

OSStatus CoreAudio_woAudioUnitIOProc(void *inRefCon, 
                                     AudioUnitRenderActionFlags *ioActionFlags, 
                                     const AudioTimeStamp *inTimeStamp, 
                                     UInt32 inBusNumber, 
                                     UInt32 inNumberFrames, 
                                     AudioBufferList *ioData);

/* These strings used only for tracing */

static const char * getMessage(UINT msg)
{
    static char unknown[32];
#define MSG_TO_STR(x) case x: return #x
    switch(msg) {
        MSG_TO_STR(DRVM_INIT);
        MSG_TO_STR(DRVM_EXIT);
        MSG_TO_STR(DRVM_ENABLE);
        MSG_TO_STR(DRVM_DISABLE);
        MSG_TO_STR(WIDM_OPEN);
        MSG_TO_STR(WIDM_CLOSE);
        MSG_TO_STR(WIDM_ADDBUFFER);
        MSG_TO_STR(WIDM_PREPARE);
        MSG_TO_STR(WIDM_UNPREPARE);
        MSG_TO_STR(WIDM_GETDEVCAPS);
        MSG_TO_STR(WIDM_GETNUMDEVS);
        MSG_TO_STR(WIDM_GETPOS);
        MSG_TO_STR(WIDM_RESET);
        MSG_TO_STR(WIDM_START);
        MSG_TO_STR(WIDM_STOP);
        MSG_TO_STR(WODM_OPEN);
        MSG_TO_STR(WODM_CLOSE);
        MSG_TO_STR(WODM_WRITE);
        MSG_TO_STR(WODM_PAUSE);
        MSG_TO_STR(WODM_GETPOS);
        MSG_TO_STR(WODM_BREAKLOOP);
        MSG_TO_STR(WODM_PREPARE);
        MSG_TO_STR(WODM_UNPREPARE);
        MSG_TO_STR(WODM_GETDEVCAPS);
        MSG_TO_STR(WODM_GETNUMDEVS);
        MSG_TO_STR(WODM_GETPITCH);
        MSG_TO_STR(WODM_SETPITCH);
        MSG_TO_STR(WODM_GETPLAYBACKRATE);
        MSG_TO_STR(WODM_SETPLAYBACKRATE);
        MSG_TO_STR(WODM_GETVOLUME);
        MSG_TO_STR(WODM_SETVOLUME);
        MSG_TO_STR(WODM_RESTART);
        MSG_TO_STR(WODM_RESET);
        MSG_TO_STR(DRV_QUERYDEVICEINTERFACESIZE);
        MSG_TO_STR(DRV_QUERYDEVICEINTERFACE);
        MSG_TO_STR(DRV_QUERYDSOUNDIFACE);
        MSG_TO_STR(DRV_QUERYDSOUNDDESC);
    }
#undef MSG_TO_STR
    sprintf(unknown, "UNKNOWN(0x%04x)", msg);
    return unknown;
}

#define kStopLoopMessage 0
#define kWaveOutCallbackMessage 1
#define kWaveInCallbackMessage 2

/* Mach Message Handling */
static CFDataRef wodMessageHandler(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info)
{
    UInt32 *buffer = NULL;
    WINE_WAVEOUT* wwo = NULL;
    
    switch (msgid)
    {
        case kWaveOutCallbackMessage:
            buffer = (UInt32 *) CFDataGetBytePtr(data);
            wwo = &WOutDev[buffer[0]];
            
            pthread_mutex_lock(&wwo->lock);
            DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags,
                           (HDRVR)wwo->waveDesc.hWave, (WORD)buffer[1], wwo->waveDesc.dwInstance,
                           (DWORD)buffer[2], (DWORD)buffer[3]);
            pthread_mutex_unlock(&wwo->lock);
            break;
        case kWaveInCallbackMessage:
        default:
            CFRunLoopStop(CFRunLoopGetCurrent());
            break;
    }
    
    return NULL;
}

static DWORD WINAPI messageThread(LPVOID p)
{
    CFMessagePortRef local;
    CFRunLoopSourceRef source;
    Boolean info;
    
    local = CFMessagePortCreateLocal(kCFAllocatorDefault, MessageThreadPortName,
                                        &wodMessageHandler, NULL, &info);
                                        
    source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, local, (CFIndex)0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
        
    CFRunLoopRun();

    CFRunLoopSourceInvalidate(source);
    CFRelease(source);
    CFRelease(local);
    CFRelease(MessageThreadPortName);
    MessageThreadPortName = NULL;

    return 0;
}

static DWORD wodSendDriverCallbackMessage(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
{
    CFDataRef data;
    UInt32 buffer[4];
    SInt32 ret;
    
    CFMessagePortRef messagePort;
    messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, MessageThreadPortName);
        
    buffer[0] = (UInt32) wwo->woID;
    buffer[1] = (UInt32) wMsg;
    buffer[2] = (UInt32) dwParam1;
    buffer[3] = (UInt32) dwParam2;

    data = CFDataCreate(kCFAllocatorDefault, (UInt8 *)buffer, sizeof(buffer));
    if (!data)
        return 0;
    
    ret = CFMessagePortSendRequest(messagePort, kWaveOutCallbackMessage, data, 0.0, 0.0, NULL, NULL);
    CFRelease(data);
    CFRelease(messagePort);
    
    return (ret == kCFMessagePortSuccess)?1:0;
}

static DWORD bytes_to_mmtime(LPMMTIME lpTime, DWORD position,
                             PCMWAVEFORMAT* format)
{
    TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%u nChannels=%u nAvgBytesPerSec=%u\n",
          lpTime->wType, format->wBitsPerSample, format->wf.nSamplesPerSec,
          format->wf.nChannels, format->wf.nAvgBytesPerSec);
    TRACE("Position in bytes=%u\n", position);

    switch (lpTime->wType) {
    case TIME_SAMPLES:
        lpTime->u.sample = position / (format->wBitsPerSample / 8 * format->wf.nChannels);
        TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample);
        break;
    case TIME_MS:
        lpTime->u.ms = 1000.0 * position / (format->wBitsPerSample / 8 * format->wf.nChannels * format->wf.nSamplesPerSec);
        TRACE("TIME_MS=%u\n", lpTime->u.ms);
        break;
    case TIME_SMPTE:
        lpTime->u.smpte.fps = 30;
        position = position / (format->wBitsPerSample / 8 * format->wf.nChannels);
        position += (format->wf.nSamplesPerSec / lpTime->u.smpte.fps) - 1; /* round up */
        lpTime->u.smpte.sec = position / format->wf.nSamplesPerSec;
        position -= lpTime->u.smpte.sec * format->wf.nSamplesPerSec;
        lpTime->u.smpte.min = lpTime->u.smpte.sec / 60;
        lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min;
        lpTime->u.smpte.hour = lpTime->u.smpte.min / 60;
        lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour;
        lpTime->u.smpte.fps = 30;
        lpTime->u.smpte.frame = position * lpTime->u.smpte.fps / format->wf.nSamplesPerSec;
        TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n",
              lpTime->u.smpte.hour, lpTime->u.smpte.min,
              lpTime->u.smpte.sec, lpTime->u.smpte.frame);
        break;
    default:
        WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType);
        lpTime->wType = TIME_BYTES;
        /* fall through */
    case TIME_BYTES:
        lpTime->u.cb = position;
        TRACE("TIME_BYTES=%u\n", lpTime->u.cb);
        break;
    }
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 			CoreAudio_GetDevCaps            [internal]
*/
BOOL CoreAudio_GetDevCaps (void)
{
    OSStatus status;
    UInt32 propertySize;
    AudioDeviceID devId = CoreAudio_DefaultDevice.outputDeviceID;
    
    char name[MAXPNAMELEN];
    
    propertySize = MAXPNAMELEN;
    status = AudioDeviceGetProperty(devId, 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);
        return FALSE;
    }
    
    memcpy(CoreAudio_DefaultDevice.ds_desc.szDesc, name, sizeof(name));
    strcpy(CoreAudio_DefaultDevice.ds_desc.szDrvname, "winecoreaudio.drv");
    MultiByteToWideChar(CP_ACP, 0, name, sizeof(name), 
                        CoreAudio_DefaultDevice.out_caps.szPname, 
                        sizeof(CoreAudio_DefaultDevice.out_caps.szPname) / sizeof(WCHAR));
    memcpy(CoreAudio_DefaultDevice.dev_name, name, 32);
    
    propertySize = sizeof(CoreAudio_DefaultDevice.streamDescription);
    status = AudioDeviceGetProperty(devId, 0, FALSE , kAudioDevicePropertyStreamFormat, &propertySize, &CoreAudio_DefaultDevice.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);
        return FALSE;
    }
    
    TRACE("Device Stream Description mSampleRate : %f\n mFormatID : %c%c%c%c\n"
            "mFormatFlags : %lX\n mBytesPerPacket : %lu\n mFramesPerPacket : %lu\n"
            "mBytesPerFrame : %lu\n mChannelsPerFrame : %lu\n mBitsPerChannel : %lu\n",
                               CoreAudio_DefaultDevice.streamDescription.mSampleRate,
                               (char) (CoreAudio_DefaultDevice.streamDescription.mFormatID >> 24),
                               (char) (CoreAudio_DefaultDevice.streamDescription.mFormatID >> 16),
                               (char) (CoreAudio_DefaultDevice.streamDescription.mFormatID >> 8),
                               (char) CoreAudio_DefaultDevice.streamDescription.mFormatID,
                               CoreAudio_DefaultDevice.streamDescription.mFormatFlags,
                               CoreAudio_DefaultDevice.streamDescription.mBytesPerPacket,
                               CoreAudio_DefaultDevice.streamDescription.mFramesPerPacket,
                               CoreAudio_DefaultDevice.streamDescription.mBytesPerFrame,
                               CoreAudio_DefaultDevice.streamDescription.mChannelsPerFrame,
                               CoreAudio_DefaultDevice.streamDescription.mBitsPerChannel);
    
    CoreAudio_DefaultDevice.out_caps.wMid = 0xcafe;
    CoreAudio_DefaultDevice.out_caps.wPid = 0x0001;
    
    CoreAudio_DefaultDevice.out_caps.vDriverVersion = 0x0001;
    CoreAudio_DefaultDevice.out_caps.dwFormats = 0x00000000;
    CoreAudio_DefaultDevice.out_caps.wReserved1 = 0;
    CoreAudio_DefaultDevice.out_caps.dwSupport = WAVECAPS_VOLUME;
    CoreAudio_DefaultDevice.out_caps.dwSupport |= WAVECAPS_LRVOLUME;
    
    CoreAudio_DefaultDevice.out_caps.wChannels = 2;
    CoreAudio_DefaultDevice.out_caps.dwFormats|= WAVE_FORMAT_4S16;
    
    return TRUE;
}

/******************************************************************
*		CoreAudio_WaveInit
*
* Initialize CoreAudio_DefaultDevice
*/
LONG CoreAudio_WaveInit(void)
{
    OSStatus status;
    UInt32 propertySize;
    CHAR szPname[MAXPNAMELEN];
    pthread_mutexattr_t mutexattr;
    int i;
    HANDLE hThread;

    TRACE("()\n");
    
    /* number of sound cards */
    AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propertySize, NULL);
    propertySize /= sizeof(AudioDeviceID);
    TRACE("sound cards : %lu\n", propertySize);
    
    /* Get the output device */
    propertySize = sizeof(CoreAudio_DefaultDevice.outputDeviceID);
    status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &CoreAudio_DefaultDevice.outputDeviceID);
    if (status) {
        ERR("AudioHardwareGetProperty return %c%c%c%c for kAudioHardwarePropertyDefaultOutputDevice\n", (char) (status >> 24),
                                                                                                (char) (status >> 16),
                                                                                                (char) (status >> 8),
                                                                                                (char) status);
        return 1;
    }
    if (CoreAudio_DefaultDevice.outputDeviceID == kAudioDeviceUnknown) {
        ERR("AudioHardwareGetProperty: CoreAudio_DefaultDevice.outputDeviceID == kAudioDeviceUnknown\n");
        return 1;
    }
    
    if ( ! CoreAudio_GetDevCaps() )
        return 1;
    
    CoreAudio_DefaultDevice.interface_name=HeapAlloc(GetProcessHeap(),0,strlen(CoreAudio_DefaultDevice.dev_name)+1);
    sprintf(CoreAudio_DefaultDevice.interface_name, "%s", CoreAudio_DefaultDevice.dev_name);
    
    pthread_mutexattr_init(&mutexattr);
    pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE);

    for (i = 0; i < MAX_WAVEOUTDRV; ++i)
    {
        WOutDev[i].state = WINE_WS_CLOSED;
        WOutDev[i].cadev = &CoreAudio_DefaultDevice; 
        WOutDev[i].woID = i;
        
        memset(&WOutDev[i].caps, 0, sizeof(WOutDev[i].caps));
        
        WOutDev[i].caps.wMid = 0xcafe; 	/* Manufac ID */
        WOutDev[i].caps.wPid = 0x0001; 	/* Product ID */
        snprintf(szPname, sizeof(szPname), "CoreAudio WaveOut %d", i);
        MultiByteToWideChar(CP_ACP, 0, szPname, -1, WOutDev[i].caps.szPname, sizeof(WOutDev[i].caps.szPname)/sizeof(WCHAR));
        snprintf(WOutDev[i].interface_name, sizeof(WOutDev[i].interface_name), "winecoreaudio: %d", i);
        
        WOutDev[i].caps.vDriverVersion = 0x0001;
        WOutDev[i].caps.dwFormats = 0x00000000;
        WOutDev[i].caps.dwSupport = WAVECAPS_VOLUME;
        
        WOutDev[i].caps.wChannels = 2;
      /*  WOutDev[i].caps.dwSupport |= WAVECAPS_LRVOLUME; */ /* FIXME */
        
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M08;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S08; 
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S16;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M16;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M08;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S08; 
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M16;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S16;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M08;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S08;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M16;
        WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S16;

        pthread_mutex_init(&WOutDev[i].lock, &mutexattr); /* initialize the mutex */
    }

    pthread_mutexattr_destroy(&mutexattr);
    
    /* create mach messages handler */
    srandomdev();
    MessageThreadPortName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
        CFSTR("WaveMessagePort.%d.%lu"), getpid(), (unsigned long)random());
    if (!MessageThreadPortName)
    {
        ERR("Can't create message thread port name\n");
        return 1;
    }

    hThread = CreateThread(NULL, 0, messageThread, NULL, 0, NULL);
    if ( !hThread )
    {
        ERR("Can't create message thread\n");
        return 1;
    }
    
    return 0;
}

void CoreAudio_WaveRelease(void)
{
    /* Stop CFRunLoop in messageThread */
    CFMessagePortRef messagePort;
    int i;

    TRACE("()\n");

    messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, MessageThreadPortName);
    CFMessagePortSendRequest(messagePort, kStopLoopMessage, NULL, 0.0, 0.0, NULL, NULL);
    CFRelease(messagePort);

    for (i = 0; i < MAX_WAVEOUTDRV; ++i)
    {
        pthread_mutex_destroy(&WOutDev[i].lock);
    }
}

/*======================================================================*
*                  Low level WAVE OUT implementation			*
*======================================================================*/

/**************************************************************************
* 			wodNotifyClient			[internal]
*   Call from AudioUnit IO thread can't use Wine debug channels.
*/
static DWORD wodNotifyClient(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
{
    switch (wMsg) {
        case WOM_OPEN:
        case WOM_CLOSE:
            if (wwo->wFlags != DCB_NULL &&
                !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags,
                                (HDRVR)wwo->waveDesc.hWave, wMsg, wwo->waveDesc.dwInstance,
                                dwParam1, dwParam2))
            {
                return MMSYSERR_ERROR;
            }
            break;
        case WOM_DONE:
            if (wwo->wFlags != DCB_NULL &&
                ! wodSendDriverCallbackMessage(wwo, wMsg, dwParam1, dwParam2))
            {
                return MMSYSERR_ERROR;
            }
            break;
        default:
            return MMSYSERR_INVALPARAM;
    }
    return MMSYSERR_NOERROR;
}


/**************************************************************************
* 			wodGetDevCaps               [internal]
*/
static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize)
{
    TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
    
    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        TRACE("MAX_WAVOUTDRV reached !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    TRACE("dwSupport=(0x%x), dwFormats=(0x%x)\n", WOutDev[wDevID].caps.dwSupport, WOutDev[wDevID].caps.dwFormats);
    memcpy(lpCaps, &WOutDev[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 				wodOpen				[internal]
*/
static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
{
    WINE_WAVEOUT*	wwo;
    DWORD retval;
    DWORD               ret;
    AudioStreamBasicDescription streamFormat;

    TRACE("(%u, %p, %08x);\n", wDevID, lpDesc, dwFlags);
    if (lpDesc == NULL)
    {
        WARN("Invalid Parameter !\n");
        return MMSYSERR_INVALPARAM;
    }
    if (wDevID >= MAX_WAVEOUTDRV) {
        TRACE("MAX_WAVOUTDRV reached !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    TRACE("Format: tag=%04X nChannels=%d nSamplesPerSec=%d wBitsPerSample=%d !\n",
          lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
          lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample);
    
    if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM ||
        lpDesc->lpFormat->nChannels == 0 ||
        lpDesc->lpFormat->nSamplesPerSec == 0
         )
    {
        WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d wBitsPerSample=%d !\n",
             lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
             lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample);
        return WAVERR_BADFORMAT;
    }
    
    if (dwFlags & WAVE_FORMAT_QUERY)
    {
        TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
              lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
              lpDesc->lpFormat->nSamplesPerSec);
        return MMSYSERR_NOERROR;
    }
    
    wwo = &WOutDev[wDevID];
    pthread_mutex_lock(&wwo->lock);

    if (wwo->state != WINE_WS_CLOSED)
    {
        pthread_mutex_unlock(&wwo->lock);
        return MMSYSERR_ALLOCATED;
    }

    if (!AudioUnit_CreateDefaultAudioUnit((void *) wwo, &wwo->audioUnit))
    {
        ERR("CoreAudio_CreateDefaultAudioUnit(%p) failed\n", wwo);
        pthread_mutex_unlock(&wwo->lock);
        return MMSYSERR_ERROR;
    }

    if ((dwFlags & WAVE_DIRECTSOUND) && 
        !(wwo->caps.dwSupport & WAVECAPS_DIRECTSOUND))
	/* not supported, ignore it */
	dwFlags &= ~WAVE_DIRECTSOUND;

    streamFormat.mFormatID = kAudioFormatLinearPCM;
    streamFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked;
    /* FIXME check for 32bits float -> kLinearPCMFormatFlagIsFloat */
    if (lpDesc->lpFormat->wBitsPerSample != 8)
        streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
# ifdef WORDS_BIGENDIAN
    streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; /* FIXME Wave format is little endian */
# endif

    streamFormat.mSampleRate = lpDesc->lpFormat->nSamplesPerSec;
    streamFormat.mChannelsPerFrame = lpDesc->lpFormat->nChannels;	
    streamFormat.mFramesPerPacket = 1;	
    streamFormat.mBitsPerChannel = lpDesc->lpFormat->wBitsPerSample;
    streamFormat.mBytesPerFrame = streamFormat.mBitsPerChannel * streamFormat.mChannelsPerFrame / 8;	
    streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket;		

    ret = AudioUnit_InitializeWithStreamDescription(wwo->audioUnit, &streamFormat);
    if (!ret) 
    {
        AudioUnit_CloseAudioUnit(wwo->audioUnit);
        pthread_mutex_unlock(&wwo->lock);
        return WAVERR_BADFORMAT; /* FIXME return an error based on the OSStatus */
    }
    wwo->streamDescription = streamFormat;
    wwo->state = WINE_WS_STOPPED;
                
    wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
    
    memcpy(&wwo->waveDesc, lpDesc, 	     sizeof(WAVEOPENDESC));
    memcpy(&wwo->format,   lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));
    
    if (wwo->format.wBitsPerSample == 0) {
	WARN("Resetting zeroed wBitsPerSample\n");
	wwo->format.wBitsPerSample = 8 *
	    (wwo->format.wf.nAvgBytesPerSec /
	     wwo->format.wf.nSamplesPerSec) /
	    wwo->format.wf.nChannels;
    }
    
    wwo->dwPlayedTotal = 0;
    wwo->dwWrittenTotal = 0;
        
    pthread_mutex_unlock(&wwo->lock);
    
    retval = wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);
    
    return retval;
}

/**************************************************************************
* 				wodClose			[internal]
*/
static DWORD wodClose(WORD wDevID)
{
    DWORD		ret = MMSYSERR_NOERROR;
    WINE_WAVEOUT*	wwo;
    
    TRACE("(%u);\n", wDevID);
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        WARN("bad device ID !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    wwo = &WOutDev[wDevID];
    pthread_mutex_lock(&wwo->lock);
    if (wwo->lpQueuePtr)
    {
        WARN("buffers still playing !\n");
        pthread_mutex_unlock(&wwo->lock);
        ret = WAVERR_STILLPLAYING;
    } else
    {
        OSStatus err;
        /* sanity check: this should not happen since the device must have been reset before */
        if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
        
        wwo->state = WINE_WS_CLOSED; /* mark the device as closed */
        
        err = AudioUnitUninitialize(wwo->audioUnit);
        if (err) {
            ERR("AudioUnitUninitialize return %c%c%c%c\n", (char) (err >> 24),
                                                            (char) (err >> 16),
                                                            (char) (err >> 8),
                                                            (char) err);
            pthread_mutex_unlock(&wwo->lock);
            return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
        }
        
        if ( !AudioUnit_CloseAudioUnit(wwo->audioUnit) )
        {
            ERR("Can't close AudioUnit\n");
            pthread_mutex_unlock(&wwo->lock);
            return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
        }  
        pthread_mutex_unlock(&wwo->lock);
        
        ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
    }
    
    return ret;
}

/**************************************************************************
* 				wodPrepare			[internal]
*/
static DWORD wodPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
    TRACE("(%u, %p, %08x);\n", wDevID, lpWaveHdr, dwSize);
    
    if (wDevID >= MAX_WAVEOUTDRV) {
	WARN("bad device ID !\n");
	return MMSYSERR_BADDEVICEID;
    }
    
    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
	return WAVERR_STILLPLAYING;
    
    lpWaveHdr->dwFlags |= WHDR_PREPARED;
    lpWaveHdr->dwFlags &= ~WHDR_DONE;

    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 				wodUnprepare			[internal]
*/
static DWORD wodUnprepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
    TRACE("(%u, %p, %08x);\n", wDevID, lpWaveHdr, dwSize);
    
    if (wDevID >= MAX_WAVEOUTDRV) {
	WARN("bad device ID !\n");
	return MMSYSERR_BADDEVICEID;
    }
    
    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
	return WAVERR_STILLPLAYING;
    
    lpWaveHdr->dwFlags &= ~WHDR_PREPARED;
    lpWaveHdr->dwFlags |= WHDR_DONE;
   
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 				wodHelper_BeginWaveHdr          [internal]
*
* Makes the specified lpWaveHdr the currently playing wave header.
* If the specified wave header is a begin loop and we're not already in
* a loop, setup the loop.
* Call from AudioUnit IO thread can't use Wine debug channels.
*/
static void wodHelper_BeginWaveHdr(WINE_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr)
{
    OSStatus status;

    wwo->lpPlayPtr = lpWaveHdr;
    
    if (!lpWaveHdr)
    {
        if (wwo->state == WINE_WS_PLAYING)
        {
            wwo->state = WINE_WS_STOPPED;
            status = AudioOutputUnitStop(wwo->audioUnit);
            if (status)
                fprintf(stderr, "err:winecoreaudio:wodHelper_BeginWaveHdr AudioOutputUnitStop return %c%c%c%c\n",
                        (char) (status >> 24), (char) (status >> 16), (char) (status >> 8), (char) status);
        }
        return;
    }

    if (wwo->state == WINE_WS_STOPPED)
    {
        status = AudioOutputUnitStart(wwo->audioUnit);
        if (status) {
            fprintf(stderr, "err:winecoreaudio:AudioOutputUnitStart return %c%c%c%c\n",
                    (char) (status >> 24), (char) (status >> 16), (char) (status >> 8), (char) status);
        }
        else wwo->state = WINE_WS_PLAYING;
    }
    
    if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)
    {
        if (wwo->lpLoopPtr)
        {
            fprintf(stderr, "trace:winecoreaudio:wodHelper_BeginWaveHdr Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
        } else
        {
            fprintf(stderr, "trace:winecoreaudio:wodHelper_BeginWaveHdr Starting loop (%dx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);

            wwo->lpLoopPtr = lpWaveHdr;
            /* Windows does not touch WAVEHDR.dwLoops,
                * so we need to make an internal copy */
            wwo->dwLoops = lpWaveHdr->dwLoops;
        }
    }
    wwo->dwPartialOffset = 0;
}


/**************************************************************************
* 				wodHelper_PlayPtrNext	        [internal]
*
* Advance the play pointer to the next waveheader, looping if required.
* Call from AudioUnit IO thread can't use Wine debug channels.
*/
static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo)
{
    LPWAVEHDR lpWaveHdr;

    pthread_mutex_lock(&wwo->lock);
    
    lpWaveHdr = wwo->lpPlayPtr;
    if (!lpWaveHdr)
    {
        pthread_mutex_unlock(&wwo->lock);
        return NULL;
    }

    wwo->dwPartialOffset = 0;
    if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr)
    {
        /* We're at the end of a loop, loop if required */
        if (wwo->dwLoops > 1)
        {
            wwo->dwLoops--;
            wwo->lpPlayPtr = wwo->lpLoopPtr;
        } else
        {
            /* Handle overlapping loops correctly */
            if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
                /* shall we consider the END flag for the closing loop or for
                * the opening one or for both ???
                * code assumes for closing loop only
                */
            } else
            {
                lpWaveHdr = lpWaveHdr->lpNext;
            }
            wwo->lpLoopPtr = NULL;
            wodHelper_BeginWaveHdr(wwo, lpWaveHdr);
        }
    } else
    {
        /* We're not in a loop.  Advance to the next wave header */
        wodHelper_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
    }
    
    pthread_mutex_unlock(&wwo->lock);
    
    return lpWaveHdr;
}

/* if force is TRUE then notify the client that all the headers were completed
 * Call from AudioUnit IO thread can't use Wine debug channels.
 */
static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force)
{
    LPWAVEHDR		lpWaveHdr;
    DWORD retval;

    pthread_mutex_lock(&wwo->lock);

    /* Start from lpQueuePtr and keep notifying until:
        * - we hit an unwritten wavehdr
        * - we hit the beginning of a running loop
        * - we hit a wavehdr which hasn't finished playing
        */
    while ((lpWaveHdr = wwo->lpQueuePtr) &&
           (force || 
            (lpWaveHdr != wwo->lpPlayPtr &&
             lpWaveHdr != wwo->lpLoopPtr)))
    {
        wwo->lpQueuePtr = lpWaveHdr->lpNext;
        
        lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
        lpWaveHdr->dwFlags |= WHDR_DONE;
        
        wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0);
    }

    retval = (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != 
              wwo->lpLoopPtr) ? 0 : INFINITE;
    
    pthread_mutex_unlock(&wwo->lock);
    
    return retval;
}

/**************************************************************************
* 				wodHelper_Reset			[internal]
*
* Resets current output stream.
*/
static  DWORD  wodHelper_Reset(WINE_WAVEOUT* wwo, BOOL reset)
{
    OSStatus status;

    FIXME("\n");
   
    /* updates current notify list */
    /* if resetting, remove all wave headers and notify client that all headers were completed */
    wodHelper_NotifyCompletions(wwo, reset);
    
    pthread_mutex_lock(&wwo->lock);
    
    if (reset)
    {
        wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
        wwo->state = WINE_WS_STOPPED;
        wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
        
        wwo->dwPartialOffset = 0;        /* Clear partial wavehdr */
    } 
    else
    {
        if (wwo->lpLoopPtr)
        {
            /* complicated case, not handled yet (could imply modifying the loop counter) */
            FIXME("Pausing while in loop isn't correctly handled yet, except strange results\n");
            wwo->lpPlayPtr = wwo->lpLoopPtr;
            wwo->dwPartialOffset = 0;
            wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */
        } else
        {
            LPWAVEHDR   ptr;
            DWORD       sz = wwo->dwPartialOffset;
            
            /* reset all the data as if we had written only up to lpPlayedTotal bytes */
            /* compute the max size playable from lpQueuePtr */
            for (ptr = wwo->lpQueuePtr; ptr && ptr != wwo->lpPlayPtr; ptr = ptr->lpNext)
            {
                sz += ptr->dwBufferLength;
            }
            
            /* because the reset lpPlayPtr will be lpQueuePtr */
            if (wwo->dwWrittenTotal > wwo->dwPlayedTotal + sz) ERR("doh\n");
            wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal);
            wwo->dwWrittenTotal = wwo->dwPlayedTotal;
            wwo->lpPlayPtr = wwo->lpQueuePtr;
        }
        
        wwo->state = WINE_WS_PAUSED;
    }

    status = AudioOutputUnitStop(wwo->audioUnit);

    pthread_mutex_unlock(&wwo->lock);

    if (status) {
        ERR( "AudioOutputUnitStop return %c%c%c%c\n",
             (char) (status >> 24), (char) (status >> 16), (char) (status >> 8), (char) status);
        return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
    }

    return MMSYSERR_NOERROR;
}


/**************************************************************************
* 				wodWrite			[internal]
* 
*/
static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
    LPWAVEHDR*wh;
    WINE_WAVEOUT *wwo;
    
    TRACE("(%u, %p, %08X);\n", wDevID, lpWaveHdr, dwSize);
    
    /* first, do the sanity checks... */
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        WARN("bad dev ID !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    wwo = &WOutDev[wDevID];
    
    if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
    {
        TRACE("unprepared\n");
        return WAVERR_UNPREPARED;
    }
    
    if (lpWaveHdr->dwFlags & WHDR_INQUEUE) 
    {
        TRACE("still playing\n");
        return WAVERR_STILLPLAYING;
    }
    
    lpWaveHdr->dwFlags &= ~WHDR_DONE;
    lpWaveHdr->dwFlags |= WHDR_INQUEUE;
    lpWaveHdr->lpNext = 0;

    pthread_mutex_lock(&wwo->lock);
    /* insert buffer at the end of queue */
    for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext))
        /* Do nothing */;
    *wh = lpWaveHdr;
    
    if (!wwo->lpPlayPtr)
        wodHelper_BeginWaveHdr(wwo,lpWaveHdr);
    pthread_mutex_unlock(&wwo->lock);
    
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 			wodPause				[internal]
*/
static DWORD wodPause(WORD wDevID)
{
    OSStatus status;

    TRACE("(%u);!\n", wDevID);
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        WARN("bad device ID !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    pthread_mutex_lock(&WOutDev[wDevID].lock);
    if (WOutDev[wDevID].state == WINE_WS_PLAYING || WOutDev[wDevID].state == WINE_WS_STOPPED)
    {
        WOutDev[wDevID].state = WINE_WS_PAUSED;
        status = AudioOutputUnitStop(WOutDev[wDevID].audioUnit);
        if (status) {
            ERR( "AudioOutputUnitStop return %c%c%c%c\n",
                 (char) (status >> 24), (char) (status >> 16), (char) (status >> 8), (char) status);
            pthread_mutex_unlock(&WOutDev[wDevID].lock);
            return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
        }
    }
    pthread_mutex_unlock(&WOutDev[wDevID].lock);
    
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 			wodRestart				[internal]
*/
static DWORD wodRestart(WORD wDevID)
{
    TRACE("(%u);\n", wDevID);
    
    if (wDevID >= MAX_WAVEOUTDRV )
    {
        WARN("bad device ID !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    pthread_mutex_lock(&WOutDev[wDevID].lock);
    if (WOutDev[wDevID].state == WINE_WS_PAUSED)
    {
        if (WOutDev[wDevID].lpPlayPtr)
        {
            OSStatus status = AudioOutputUnitStart(WOutDev[wDevID].audioUnit);
            if (status) {
                ERR("AudioOutputUnitStart return %c%c%c%c\n",
                    (char) (status >> 24), (char) (status >> 16), (char) (status >> 8), (char) status);
                pthread_mutex_unlock(&WOutDev[wDevID].lock);
                return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
            }
            WOutDev[wDevID].state = WINE_WS_PLAYING;
        }
        else
            WOutDev[wDevID].state = WINE_WS_STOPPED;
    }
    pthread_mutex_unlock(&WOutDev[wDevID].lock);
    
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 			wodReset				[internal]
*/
static DWORD wodReset(WORD wDevID)
{
    TRACE("(%u);\n", wDevID);
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        WARN("bad device ID !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    return wodHelper_Reset(&WOutDev[wDevID], TRUE);
}

/**************************************************************************
* 				wodGetPosition			[internal]
*/
static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
{
    DWORD		val;
    WINE_WAVEOUT*	wwo;

    TRACE("(%u, %p, %u);\n", wDevID, lpTime, uSize);
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        WARN("bad device ID !\n");
        return MMSYSERR_BADDEVICEID;
    }
    
    /* if null pointer to time structure return error */
    if (lpTime == NULL)	return MMSYSERR_INVALPARAM;
    
    wwo = &WOutDev[wDevID];
    
    pthread_mutex_lock(&WOutDev[wDevID].lock);
    val = wwo->dwPlayedTotal;
    pthread_mutex_unlock(&WOutDev[wDevID].lock);
    
    return bytes_to_mmtime(lpTime, val, &wwo->format);
}

/**************************************************************************
* 				wodGetVolume			[internal]
*/
static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol)
{
    float left;
    float right;
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        WARN("bad device ID !\n");
        return MMSYSERR_BADDEVICEID;
    }    
    
    TRACE("(%u, %p);\n", wDevID, lpdwVol);
    
    pthread_mutex_lock(&WOutDev[wDevID].lock);
    
    AudioUnit_GetVolume(WOutDev[wDevID].audioUnit, &left, &right); 
        
    pthread_mutex_unlock(&WOutDev[wDevID].lock);
        
    *lpdwVol = ((WORD) left * 0xFFFFl) + (((WORD) right * 0xFFFFl) << 16);
    
    return MMSYSERR_NOERROR;
}

/**************************************************************************
* 				wodSetVolume			[internal]
*/
static DWORD wodSetVolume(WORD wDevID, DWORD dwParam)
{
    float left;
    float right;
    
    if (wDevID >= MAX_WAVEOUTDRV)
    {
        WARN("bad device ID !\n");
        return MMSYSERR_BADDEVICEID;
    }

    left  = LOWORD(dwParam) / 65535.0f;
    right = HIWORD(dwParam) / 65535.0f;
    
    TRACE("(%u, %08x);\n", wDevID, dwParam);
    
    pthread_mutex_lock(&WOutDev[wDevID].lock);
    
    AudioUnit_SetVolume(WOutDev[wDevID].audioUnit, left, right); 
        
    pthread_mutex_unlock(&WOutDev[wDevID].lock);
    
    return MMSYSERR_NOERROR;
}

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

/**************************************************************************
*                              wodDevInterfaceSize             [internal]
*/
static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
{
    TRACE("(%u, %p)\n", wDevID, dwParam1);
    
    *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].cadev->interface_name, -1,
                                    NULL, 0 ) * sizeof(WCHAR);
    return MMSYSERR_NOERROR;
}

/**************************************************************************
*                              wodDevInterface                 [internal]
*/
static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2)
{
    TRACE("\n");
    if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].cadev->interface_name, -1,
                                        NULL, 0 ) * sizeof(WCHAR))
    {
        MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].cadev->interface_name, -1,
                            dwParam1, dwParam2 / sizeof(WCHAR));
        return MMSYSERR_NOERROR;
    }
    return MMSYSERR_INVALPARAM;
}

/**************************************************************************
* 				wodMessage (WINEJACK.7)
*/
DWORD WINAPI CoreAudio_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
                                  DWORD dwParam1, DWORD dwParam2)
{
    TRACE("(%u, %s, %08x, %08x, %08x);\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 WODM_OPEN:         return wodOpen(wDevID, (LPWAVEOPENDESC) dwParam1, dwParam2);          
        case WODM_CLOSE:        return wodClose(wDevID);
        case WODM_WRITE:        return wodWrite(wDevID, (LPWAVEHDR) dwParam1, dwParam2);
        case WODM_PAUSE:        return wodPause(wDevID);  
        case WODM_GETPOS:       return wodGetPosition(wDevID, (LPMMTIME) dwParam1, dwParam2);
        case WODM_BREAKLOOP:    return MMSYSERR_NOTSUPPORTED;
        case WODM_PREPARE:      return wodPrepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2);
        case WODM_UNPREPARE:    return wodUnprepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2);
            
        case WODM_GETDEVCAPS:   return wodGetDevCaps(wDevID, (LPWAVEOUTCAPSW) dwParam1, dwParam2);
        case WODM_GETNUMDEVS:   return wodGetNumDevs();  
            
        case WODM_GETPITCH:         
        case WODM_SETPITCH:        
        case WODM_GETPLAYBACKRATE:	
        case WODM_SETPLAYBACKRATE:	return MMSYSERR_NOTSUPPORTED;
        case WODM_GETVOLUME:    return wodGetVolume(wDevID, (LPDWORD)dwParam1);
        case WODM_SETVOLUME:    return wodSetVolume(wDevID, dwParam1);
        case WODM_RESTART:      return wodRestart(wDevID);
        case WODM_RESET:    	return wodReset(wDevID);
            
        case DRV_QUERYDEVICEINTERFACESIZE:  return wodDevInterfaceSize (wDevID, (LPDWORD)dwParam1);
        case DRV_QUERYDEVICEINTERFACE:      return wodDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2);
        case DRV_QUERYDSOUNDIFACE:	
        case DRV_QUERYDSOUNDDESC:	
            return MMSYSERR_NOTSUPPORTED;
            
        default:
            FIXME("unknown message %d!\n", wMsg);
    }
    
    return MMSYSERR_NOTSUPPORTED;
}

/*======================================================================*
*                  Low level DSOUND implementation			*
*======================================================================*/

typedef struct IDsDriverImpl IDsDriverImpl;
typedef struct IDsDriverBufferImpl IDsDriverBufferImpl;

struct IDsDriverImpl
{
    /* IUnknown fields */
    const IDsDriverVtbl *lpVtbl;
    DWORD		ref;
    /* IDsDriverImpl fields */
    UINT		wDevID;
    IDsDriverBufferImpl*primary;
};

struct IDsDriverBufferImpl
{
    /* IUnknown fields */
    const IDsDriverBufferVtbl *lpVtbl;
    DWORD ref;
    /* IDsDriverBufferImpl fields */
    IDsDriverImpl* drv;
    DWORD buflen;
};


/*
    CoreAudio IO threaded callback,
    we can't call Wine debug channels, critical section or anything using NtCurrentTeb here.
*/
OSStatus CoreAudio_woAudioUnitIOProc(void *inRefCon, 
                                     AudioUnitRenderActionFlags *ioActionFlags, 
                                     const AudioTimeStamp *inTimeStamp, 
                                     UInt32 inBusNumber, 
                                     UInt32 inNumberFrames, 
                                     AudioBufferList *ioData)
{
    UInt32 buffer;
    WINE_WAVEOUT *wwo = (WINE_WAVEOUT *) inRefCon;
    int nextPtr = 0;
    int needNotify = 0;

    unsigned int dataNeeded = ioData->mBuffers[0].mDataByteSize;
    unsigned int dataProvided = 0;

    while (dataNeeded > 0)
    {
        pthread_mutex_lock(&wwo->lock);

        if (wwo->state == WINE_WS_PLAYING && wwo->lpPlayPtr)
        {
            unsigned int available = wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset;
            unsigned int toCopy;

            if (available >= dataNeeded)
                toCopy = dataNeeded;
            else
                toCopy = available;

            if (toCopy > 0)
            {
                memcpy((char*)ioData->mBuffers[0].mData + dataProvided,
                    wwo->lpPlayPtr->lpData + wwo->dwPartialOffset, toCopy);
                wwo->dwPartialOffset += toCopy;
                wwo->dwPlayedTotal += toCopy;
                dataProvided += toCopy;
                dataNeeded -= toCopy;
                available -= toCopy;
            }

            if (available == 0)
                nextPtr = 1;

            needNotify = 1;
        }
        else
        {
            memset((char*)ioData->mBuffers[0].mData + dataProvided, 0, dataNeeded);
            dataProvided += dataNeeded;
            dataNeeded = 0;
        }

        pthread_mutex_unlock(&wwo->lock);

        if (nextPtr)
        {
            wodHelper_PlayPtrNext(wwo);
            nextPtr = 0;
        }
    }

    /* We only fill buffer 0.  Set any others that might be requested to 0. */
    for (buffer = 1; buffer < ioData->mNumberBuffers; buffer++)
    {
        memset(ioData->mBuffers[buffer].mData, 0, ioData->mBuffers[buffer].mDataByteSize);
    }

    if (needNotify) wodHelper_NotifyCompletions(wwo, FALSE);
    return noErr;
}
#else

/**************************************************************************
* 				wodMessage (WINECOREAUDIO.7)
*/
DWORD WINAPI CoreAudio_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser, 
                                  DWORD dwParam1, DWORD dwParam2)
{
    FIXME("(%u, %04X, %08X, %08X, %08X): CoreAudio support not compiled into wine\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
    return MMSYSERR_NOTENABLED;
}

#endif
