Blob Blame Raw
diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in
new file mode 100644
index 0000000..327f225
--- /dev/null
+++ b/dlls/winepulse.drv/Makefile.in
@@ -0,0 +1,16 @@
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../..
+SRCDIR    = @srcdir@
+VPATH     = @srcdir@
+MODULE    = winepulse.drv
+IMPORTS   = dxguid uuid winmm user32 advapi32 kernel32
+EXTRALIBS = @PULSELIBS@
+
+C_SRCS = dsoutput.c \
+	waveout.c \
+	wavein.c \
+	pulse.c
+
+@MAKE_DLL_RULES@
+
+@DEPENDENCIES@  # everything below this line is overwritten by make depend
diff --git a/dlls/winepulse.drv/dsoutput.c b/dlls/winepulse.drv/dsoutput.c
new file mode 100644
index 0000000..203fac0
--- /dev/null
+++ b/dlls/winepulse.drv/dsoutput.c
@@ -0,0 +1,578 @@
+/*
+ * Wine Driver for PulseAudio - DSound Output Functionality
+ * http://pulseaudio.org/
+ *
+ * Copyright	2009 Arthur Talyor <theycallhimart@gmail.com>
+ *
+ * Conatins code from other wine multimedia drivers.
+ *
+ * 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 <stdarg.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winerror.h"
+#include "mmddk.h"
+#include "dsound.h"
+#include "dsdriver.h"
+#include "winreg.h"
+
+#include <winepulse.h>
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(dspulse);
+
+#if HAVE_PULSEAUDIO
+
+/*======================================================================*
+ *                  Low level DSOUND implementation			*
+ *======================================================================*/
+
+/* A buffer is allocated with a pointer indicating the read position in the
+ * buffer. The pulse write callback reads data from the buffer, updating the
+ * read pointer to the location at the end of the read. Upon reaching the end
+ * or attempting to read past the end of buffer the read pointer wraps around
+ * to the beginning again. DirectSound applications can write anywhere in the
+ * buffer at anytime without locking and can know the location of the read
+ * pointer. The position of the read pointer cannot be changed by the
+ * application and access to it uses a locking scheme. A fake pointer
+ * indicating estimated playback position is also available to the application.
+ * Applications can potentially write to the same area of memory which is also
+ * being read by the pulse thread. However, this is uncommon as directsound
+ * applications know where pulse should be reading from via the pointer
+ * locations and MSDN says that such an operation should be avoided with the
+ * results being undefined.
+ */
+
+/* Fragment lengths try to be a power of two close to 10ms worth of data. See
+ * dlls/dsound/mixer.c
+ */
+static int fragment_length(pa_sample_spec *s) {
+    if (s->rate <= 12800)
+        return 128 * pa_frame_size(s);
+
+    if (s->rate <= 25600)
+        return 256 * pa_frame_size(s);
+
+    if (s->rate <= 51200)
+        return 512 * pa_frame_size(s);
+
+    return 1024 * pa_frame_size(s);
+}
+
+/* Callback from the pulse thread for stream data */
+static void DSPULSE_BufferReadCallback(pa_stream *s, size_t nbytes, void *userdata) {
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)userdata;
+
+    if (!This->buffer)
+        return;
+
+    /* Fraglens are always powers of 2 */
+    nbytes+= This->fraglen - 1;
+    nbytes&= ~(This->fraglen - 1);
+
+    /* If we advance more than 10 fragments at a time it appears that the buffer
+     * pointer is never advancing because of wrap-around. Evil magic numbers. */
+    if (nbytes > This->fraglen * 5)
+        nbytes = This->fraglen * 5;
+
+    TRACE("Reading %u bytes.\n", nbytes);
+
+    if (This->buffer_read_offset + nbytes <= This->buffer_length) {
+        pa_stream_write(s, This->buffer + This->buffer_read_offset, nbytes, NULL, 0, PA_SEEK_RELATIVE);
+        This->buffer_play_offset = This->buffer_read_offset;
+        This->buffer_read_offset += nbytes;
+    } else {
+        size_t write_length = This->buffer_length - This->buffer_read_offset;
+        nbytes -= write_length;
+        pa_stream_write(s, This->buffer + This->buffer_read_offset, write_length, NULL, 0, PA_SEEK_RELATIVE);
+        pa_stream_write(s, This->buffer, nbytes, NULL, 0, PA_SEEK_RELATIVE);
+        This->buffer_play_offset = This->buffer_read_offset;
+        This->buffer_read_offset = nbytes;
+    }
+
+    This->buffer_read_offset %= This->buffer_length;
+}
+
+/* Called when the stream underruns. Just for information */
+static void DSPULSE_BufferUnderflowCallback(pa_stream *s, void *userdata) {
+    WARN("(%p) underrun.\n", userdata);
+}
+
+/* Connects a stream to the server. Does not update
+ * IDsDriverBufferImpl->fraglen. Does not lock the pulse mainloop or free
+ * objects in case of failure. This should be handled by the calling function.
+ */
+static HRESULT DSPULSE_ConnectStream(IDsDriverBufferImpl* This) {
+    pa_buffer_attr ba_request;
+    const pa_buffer_attr *ba_obtained;
+    char c[PA_SAMPLE_SPEC_SNPRINT_MAX];
+    pa_stream_flags_t stream_flags = PA_STREAM_START_CORKED;
+
+#if PA_PROTOCOL_VERSION >= 14
+    /* We are a "fragment wait based" application, so this flag should be
+     * acceptable. */
+    stream_flags |= PA_STREAM_EARLY_REQUESTS;
+#elif PA_PROTOCOL_VERSION >= 13 && PA_API_VERSION >= 12
+    stream_flags |= PA_STREAM_ADJUST_LATENCY;
+#endif
+
+    pa_sample_spec_snprint(c, PA_SAMPLE_SPEC_SNPRINT_MAX, &This->sample_spec);
+    TRACE("Sample spec %s fragment size %u.\n", c, This->fraglen);
+
+    ba_request.tlength = This->fraglen * 4;     //  ~40ms
+    ba_request.minreq = This->fraglen;          //  ~10ms
+    ba_request.prebuf = (uint32_t)-1;           //  same as tlength
+    ba_request.maxlength = This->buffer_length; //  2^x = ~3s
+
+    TRACE("Asking for buffer tlength:%u (%llums) minreq:%u (%llums)\n",
+        ba_request.tlength, pa_bytes_to_usec(ba_request.tlength, &This->sample_spec)/1000,
+        ba_request.minreq, pa_bytes_to_usec(ba_request.minreq, &This->sample_spec)/1000);
+
+    This->stream = pa_stream_new(PULSE_context, "DirectSound Buffer", &This->sample_spec, NULL);
+    if (!This->stream) return DSERR_BADFORMAT;
+
+    pa_stream_set_state_callback(This->stream, PULSE_StreamStateCallback, This);
+    pa_stream_set_write_callback(This->stream, DSPULSE_BufferReadCallback, This);
+    pa_stream_set_underflow_callback(This->stream, DSPULSE_BufferUnderflowCallback, This);
+
+    TRACE("Attempting to connect (%p)->stream for playback on %s\n", This, WOutDev[This->drv->wDevID].device_name);
+    pa_stream_connect_playback(This->stream, WOutDev[This->drv->wDevID].device_name, &ba_request, stream_flags, NULL, NULL);
+    for (;;) {
+        pa_context_state_t cstate = pa_context_get_state(PULSE_context);
+        pa_stream_state_t sstate = pa_stream_get_state(This->stream);
+
+        if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED ||
+            sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) {
+            ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
+            return DSERR_BUFFERLOST;
+        }
+
+        if (sstate == PA_STREAM_READY)
+            break;
+
+        pa_threaded_mainloop_wait(PULSE_ml);
+    }
+    TRACE("(%p)->stream connected for playback.\n", This);
+    ba_obtained = pa_stream_get_buffer_attr(This->stream);
+
+    TRACE("Obtained buffer tlength:%u (%llums) minreq:%u (%llums)\n",
+        ba_obtained->tlength, pa_bytes_to_usec(ba_obtained->tlength, &This->sample_spec)/1000,
+        ba_obtained->minreq, pa_bytes_to_usec(ba_obtained->minreq, &This->sample_spec)/1000);
+
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj) {
+    /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
+    FIXME("(): stub!\n");
+    return DSERR_UNSUPPORTED;
+}
+
+static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface) {
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
+    ULONG refCount = InterlockedIncrement(&This->ref);
+
+    TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
+
+    return refCount;
+}
+
+static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface) {
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
+    ULONG refCount = InterlockedDecrement(&This->ref);
+
+    TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
+
+    if (refCount)
+        return refCount;
+
+    TRACE("mmap buffer %p destroyed\n", This->buffer);
+    pa_threaded_mainloop_lock(PULSE_ml);
+    PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This));
+    pa_stream_disconnect(This->stream);
+    if (This == This->drv->primary)
+        This->drv->primary = NULL;
+    HeapFree(GetProcessHeap(), 0, This->buffer);
+    This->buffer = NULL;
+    pa_stream_unref(This->stream);
+    This->stream = NULL;
+    HeapFree(GetProcessHeap(), 0, This);
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    return 0;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface,
+					       LPVOID*ppvAudio1,LPDWORD pdwLen1,
+					       LPVOID*ppvAudio2,LPDWORD pdwLen2,
+					       DWORD dwWritePosition,DWORD dwWriteLen,
+					       DWORD dwFlags)
+{
+    /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */
+    TRACE("(%p): stub", iface);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface,
+						 LPVOID pvAudio1,DWORD dwLen1,
+						 LPVOID pvAudio2,DWORD dwLen2)
+{
+    /* We set DSDDESC_DONTNEEDPRIMARYLOCK so this should never be called */
+    TRACE("(%p): stub", iface);
+    return DSERR_UNSUPPORTED;
+}
+
+/* You cannot change the sample format of a connected stream, so we need to
+ * destroy and re-create the stream if the sample spec is different */
+static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface, LPWAVEFORMATEX pwfx) {
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
+    pa_sample_spec old_spec;
+
+    TRACE("(%p, %p)\n", iface, pwfx);
+
+    old_spec.rate = This->sample_spec.rate;
+    old_spec.format = This->sample_spec.format;
+    old_spec.channels = This->sample_spec.channels;
+
+    if (!PULSE_SetupFormat(pwfx, &This->sample_spec))
+        return DSERR_BADFORMAT;
+
+    if (old_spec.rate ==    This->sample_spec.rate &&
+        old_spec.format ==  This->sample_spec.format &&
+        old_spec.channels == This->sample_spec.channels) {
+        TRACE("same as original sample spec, exiting.\n");
+	PULSE_WaitForOperation(pa_stream_flush(This->stream, PULSE_StreamSuccessCallback, This));
+        return DS_OK;
+    }
+
+    /* If the format doesn't match, return an error and the buffer will be remade */
+    TRACE("Formats don't match, failing causing re-creation.\n");
+    return DSERR_BUFFERLOST;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq)
+{
+    /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
+    /* You can't do for primary buffers anyways */
+    return S_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan) {
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
+
+    if (This->sample_spec.channels == 2) {
+        This->volume.channels = 2;
+        if (pVolPan->lPan < 0) {
+            This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 + pVolPan->lPan/10.0);
+            This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0);
+        } else {
+            This->volume.values[0] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0);
+            This->volume.values[1] = pa_sw_volume_from_dB(pVolPan->lVolume/-10.0 - pVolPan->lPan/10.0);
+        }
+    } else {
+        WARN("Panning non-stereo streams not supported yet!\n");
+        pa_cvolume_set(&This->volume, This->sample_spec.channels, pa_sw_volume_from_dB(pVolPan->lVolume/-10.0));
+        /* Would be nice to return DSERR_CONTROLUNAVAIL, but that isn't up to us
+         * here */
+    }
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    if (This->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY &&
+	pa_stream_get_state(This->stream) == PA_STREAM_READY && pa_cvolume_valid(&This->volume))
+        PULSE_WaitForOperation(
+	    pa_context_set_sink_input_volume(
+		PULSE_context, pa_stream_get_index(This->stream),
+		&This->volume, PULSE_ContextSuccessCallback, This));
+
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos)
+{
+    /* IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface; */
+    /* Not allowed on primary buffers */
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface,
+						      LPDWORD lpdwPlay, LPDWORD lpdwWrite)
+{
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    if (!This->buffer || pa_stream_get_state(This->stream) != PA_STREAM_READY) {
+        pa_threaded_mainloop_unlock(PULSE_ml);
+        return DSERR_UNINITIALIZED;
+    }
+
+    if (lpdwPlay)
+        *lpdwPlay = This->buffer_play_offset;
+    if (lpdwWrite)
+        *lpdwWrite = This->buffer_read_offset;
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags) {
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
+    TRACE("(%p,%x,%x,%x)\n",iface,dwRes1,dwRes2,dwFlags);
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    PULSE_WaitForOperation(pa_stream_cork(This->stream, 0, PULSE_StreamSuccessCallback, This));
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface) {
+    IDsDriverBufferImpl *This = (IDsDriverBufferImpl *)iface;
+    TRACE("(%p)\n",iface);
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    PULSE_WaitForOperation(pa_stream_cork(This->stream, 1, PULSE_StreamSuccessCallback, This));
+    pa_threaded_mainloop_unlock(PULSE_ml);
+    return DS_OK;
+}
+
+/*****************************************************************************/
+
+static const IDsDriverBufferVtbl dsdbvt =
+{
+    IDsDriverBufferImpl_QueryInterface,
+    IDsDriverBufferImpl_AddRef,
+    IDsDriverBufferImpl_Release,
+    IDsDriverBufferImpl_Lock,
+    IDsDriverBufferImpl_Unlock,
+    IDsDriverBufferImpl_SetFormat,
+    IDsDriverBufferImpl_SetFrequency,
+    IDsDriverBufferImpl_SetVolumePan,
+    IDsDriverBufferImpl_SetPosition,
+    IDsDriverBufferImpl_GetPosition,
+    IDsDriverBufferImpl_Play,
+    IDsDriverBufferImpl_Stop
+};
+
+static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface,
+						      LPWAVEFORMATEX pwfx,
+						      DWORD dwFlags, DWORD dwCardAddress,
+						      LPDWORD pdwcbBufferSize,
+						      LPBYTE *ppbBuffer,
+						      LPVOID *ppvObj) {
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+    IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj;
+    IDsDriverBufferImpl *That;
+    HRESULT ret;
+
+    TRACE("(%p,%p,%x,%x)\n",iface,pwfx,dwFlags,dwCardAddress);
+    /* we only support primary buffers */
+    
+    if (!(dwFlags & DSBCAPS_PRIMARYBUFFER))
+        return DSERR_UNSUPPORTED;
+    if (This->primary)
+        return DSERR_ALLOCATED;
+    This->primary = *ippdsdb;
+
+    *ippdsdb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDsDriverBufferImpl));
+
+    if (!*ippdsdb) {
+        ERR("Out of memory\n");
+        return DSERR_OUTOFMEMORY;
+    }
+
+    That = *ippdsdb;
+    That->lpVtbl  = &dsdbvt;
+    That->ref = 1;
+
+    That->drv = This;
+    TRACE("IdsDriverBufferImpl %p created.\n", That);
+        
+    if (!PULSE_SetupFormat(pwfx, &That->sample_spec)) {
+        WARN("Bad audio format.\n");
+        ret = DSERR_BADFORMAT;
+        goto err;
+    }
+
+    /* The buffer length has to be greater than fraglen * 20 or else logic in
+     * dlls/dsound/mixer.c fails to correctly understand buffer wrap around. */
+    That->fraglen = fragment_length(&That->sample_spec);
+    That->buffer_length = That->fraglen * 32;
+    That->buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, That->buffer_length);
+    if (!That->buffer) {
+        ret = DSERR_OUTOFMEMORY;
+        goto err;
+    }
+    That->buffer_read_offset = 0;
+    That->buffer_play_offset = 0;
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    ret = DSPULSE_ConnectStream(That);
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    if (ret != DS_OK)
+        goto err;
+
+    *pdwcbBufferSize = That->buffer_length;
+    *ppbBuffer = That->buffer;
+    
+    TRACE("Exiting with success. Have buffer %p of length %u\n", That->buffer, That->buffer_length);
+    return DS_OK;
+    
+err:
+    pa_threaded_mainloop_lock(PULSE_ml);
+    if (That->stream) {
+	    if (pa_stream_get_state(That->stream) == PA_STREAM_READY)
+	        pa_stream_disconnect(That->stream);
+	    pa_stream_unref(That->stream);
+	    That->stream = NULL;
+    }
+    pa_threaded_mainloop_unlock(PULSE_ml);
+    HeapFree(GetProcessHeap(), 0, That->buffer);
+    HeapFree(GetProcessHeap(), 0, *ippdsdb);
+    WARN("exiting with failure.\n");
+    return ret;
+}
+
+static HRESULT WINAPI IDsDriverImpl_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj) {
+    /* IDsDriverImpl *This = (IDsDriverImpl *)iface; */
+    FIXME("(%p): stub!\n",iface);
+    return DSERR_UNSUPPORTED;
+}
+
+static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface) {
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+    ULONG refCount = InterlockedIncrement(&This->ref);
+
+    TRACE("(%p)->(ref before=%u)\n",This, refCount - 1);
+
+    return refCount;
+}
+
+static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface) {
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+    ULONG refCount = InterlockedDecrement(&This->ref);
+
+    TRACE("(%p)->(ref before=%u)\n",This, refCount + 1);
+
+    if (refCount)
+        return refCount;
+
+    HeapFree(GetProcessHeap(), 0, This);
+    return 0;
+}
+
+static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, PDSDRIVERDESC pDesc) {
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+    TRACE("(%p,%p)\n",iface,pDesc);
+    *pDesc			= WOutDev[This->wDevID].ds_desc;
+    pDesc->dwFlags		= DSDDESC_DONTNEEDSECONDARYLOCK | DSDDESC_DONTNEEDPRIMARYLOCK;
+    pDesc->dnDevNode		= 0; /*TODO: Bwah? */
+    pDesc->wVxdId           = 0;
+    pDesc->wReserved		= 0;
+    pDesc->ulDeviceNum		= This->wDevID;
+    pDesc->dwHeapType		= DSDHEAP_NOHEAP;
+    pDesc->pvDirectDrawHeap	= NULL;
+    pDesc->dwMemStartAddress	= 0xDEAD0000;
+    pDesc->dwMemEndAddress	= 0xDEAF0000;
+    pDesc->dwMemAllocExtra	= 0;
+    pDesc->pvReserved1		= NULL;
+    pDesc->pvReserved2		= NULL;
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface) {
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+    TRACE("(%p) stub, harmless\n",This);
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface) {
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+    TRACE("(%p) stub, harmless\n",This);
+    return S_OK;
+}
+
+static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps) {
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+
+    if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY) {
+        ERR("Context failure.\n");
+        return DSERR_GENERIC;
+    }
+
+    TRACE("(%p,%p)\n",iface,pCaps);
+    *pCaps = WOutDev[This->wDevID].ds_caps;
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface,
+							 PIDSDRIVERBUFFER pBuffer,
+							 LPVOID *ppvObj)
+{
+    IDsDriverImpl *This = (IDsDriverImpl *)iface;
+    FIXME("(%p,%p): stub\n",This,pBuffer);
+    return DSERR_INVALIDCALL;
+}
+
+static const IDsDriverVtbl dsdvt =
+{
+    IDsDriverImpl_QueryInterface,
+    IDsDriverImpl_AddRef,
+    IDsDriverImpl_Release,
+    IDsDriverImpl_GetDriverDesc,
+    IDsDriverImpl_Open,
+    IDsDriverImpl_Close,
+    IDsDriverImpl_GetCaps,
+    IDsDriverImpl_CreateSoundBuffer,
+    IDsDriverImpl_DuplicateSoundBuffer
+};
+
+DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv) {
+    IDsDriverImpl** idrv = (IDsDriverImpl**)drv;
+
+    if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY || pa_context_is_local(PULSE_context) != 1) {
+        WARN("Connection failure or server is not local, falling back to WaveOut HEL.\n");
+        return MMSYSERR_NOTSUPPORTED;
+    }
+
+    *idrv = HeapAlloc(GetProcessHeap(), 0, sizeof(IDsDriverImpl));
+    if (!*idrv)
+        return MMSYSERR_NOMEM;
+    
+    TRACE("IDsDriverImpl %p created.\n", *idrv);
+
+    (*idrv)->lpVtbl = &dsdvt;
+    (*idrv)->ref = 1;
+    (*idrv)->wDevID = wDevID;
+    (*idrv)->primary = NULL;
+
+    return MMSYSERR_NOERROR;
+}
+
+DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc) {
+    TRACE("(%u, %p)\n", wDevID, desc);
+    *desc = WOutDev[wDevID].ds_desc;
+    return MMSYSERR_NOERROR;
+}
+#endif /* HAVE_PULSEAUDIO */
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c
new file mode 100644
index 0000000..b68fb05
--- /dev/null
+++ b/dlls/winepulse.drv/pulse.c
@@ -0,0 +1,770 @@
+/*
+ * Wine Driver for PulseAudio
+ * http://pulseaudio.org/
+ *
+ * Copyright	2009 Arthur Taylor <theycallhimart@gmail.com>
+ *
+ * Contains code from other wine sound drivers.
+ *
+ * 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 <stdarg.h>
+#include <stdio.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winreg.h"
+#include "mmddk.h"
+#include "ks.h"
+#include "ksguid.h"
+#include "ksmedia.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <poll.h>
+
+#ifdef HAVE_PULSEAUDIO
+
+#include "wine/unicode.h"
+#include "wine/debug.h"
+#include "wine/library.h"
+
+#include <winepulse.h>
+#include <pulse/pulseaudio.h>
+WINE_DEFAULT_DEBUG_CHANNEL(wave);
+
+/* These strings used only for tracing */
+const char * PULSE_getCmdString(enum win_wm_message msg) {
+    static char unknown[32];
+#define MSG_TO_STR(x) case x: return #x
+    switch(msg) {
+    MSG_TO_STR(WINE_WM_PAUSING);
+    MSG_TO_STR(WINE_WM_RESTARTING);
+    MSG_TO_STR(WINE_WM_RESETTING);
+    MSG_TO_STR(WINE_WM_HEADER);
+    MSG_TO_STR(WINE_WM_BREAKLOOP);
+    MSG_TO_STR(WINE_WM_CLOSING);
+    MSG_TO_STR(WINE_WM_STARTING);
+    MSG_TO_STR(WINE_WM_STOPPING);
+    MSG_TO_STR(WINE_WM_XRUN);
+    MSG_TO_STR(WINE_WM_FEED);
+    }
+#undef MSG_TO_STR
+    sprintf(unknown, "UNKNOWN(0x%08x)", msg);
+    return unknown;
+}
+
+/*======================================================================*
+ *          Ring Buffer Functions - copied from winealsa.drv            *
+ *======================================================================*/
+
+/* unless someone makes a wineserver kernel module, Unix pipes are faster than win32 events */
+#define USE_PIPE_SYNC
+
+#ifdef USE_PIPE_SYNC
+#define INIT_OMR(omr) do { if (pipe(omr->msg_pipe) < 0) { omr->msg_pipe[0] = omr->msg_pipe[1] = -1; } } while (0)
+#define CLOSE_OMR(omr) do { close(omr->msg_pipe[0]); close(omr->msg_pipe[1]); } while (0)
+#define SIGNAL_OMR(omr) do { int x = 0; write((omr)->msg_pipe[1], &x, sizeof(x)); } while (0)
+#define CLEAR_OMR(omr) do { int x = 0; read((omr)->msg_pipe[0], &x, sizeof(x)); } while (0)
+#define RESET_OMR(omr) do { } while (0)
+#define WAIT_OMR(omr, sleep) \
+  do { struct pollfd pfd; pfd.fd = (omr)->msg_pipe[0]; \
+       pfd.events = POLLIN; poll(&pfd, 1, sleep); } while (0)
+#else
+#define INIT_OMR(omr) do { omr->msg_event = CreateEventW(NULL, FALSE, FALSE, NULL); } while (0)
+#define CLOSE_OMR(omr) do { CloseHandle(omr->msg_event); } while (0)
+#define SIGNAL_OMR(omr) do { SetEvent((omr)->msg_event); } while (0)
+#define CLEAR_OMR(omr) do { } while (0)
+#define RESET_OMR(omr) do { ResetEvent((omr)->msg_event); } while (0)
+#define WAIT_OMR(omr, sleep) \
+  do { WaitForSingleObject((omr)->msg_event, sleep); } while (0)
+#endif
+
+#define PULSE_RING_BUFFER_INCREMENT      64
+
+/******************************************************************
+ *		PULSE_InitRingMessage
+ *
+ * Initialize the ring of messages for passing between driver's caller
+ * and playback/record thread
+ */
+int PULSE_InitRingMessage(PULSE_MSG_RING* omr)
+{
+    omr->msg_toget = 0;
+    omr->msg_tosave = 0;
+    INIT_OMR(omr);
+    omr->ring_buffer_size = PULSE_RING_BUFFER_INCREMENT;
+    omr->messages = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,omr->ring_buffer_size * sizeof(PULSE_MSG));
+
+    InitializeCriticalSection(&omr->msg_crst);
+    omr->msg_crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PULSE_MSG_RING.msg_crst");
+    return 0;
+}
+
+/******************************************************************
+ *		PULSE_DestroyRingMessage
+ *
+ */
+int PULSE_DestroyRingMessage(PULSE_MSG_RING* omr)
+{
+    CLOSE_OMR(omr);
+    HeapFree(GetProcessHeap(),0,omr->messages);
+    omr->messages = NULL;
+    omr->ring_buffer_size = PULSE_RING_BUFFER_INCREMENT;
+    omr->msg_crst.DebugInfo->Spare[0] = 0;
+    DeleteCriticalSection(&omr->msg_crst);
+    return 0;
+}
+/******************************************************************
+ *		PULSE_ResetRingMessage
+ *
+ */
+void PULSE_ResetRingMessage(PULSE_MSG_RING* omr)
+{
+    RESET_OMR(omr);
+}
+
+/******************************************************************
+ *		PULSE_WaitRingMessage
+ *
+ */
+void PULSE_WaitRingMessage(PULSE_MSG_RING* omr, DWORD sleep)
+{
+    WAIT_OMR(omr, sleep);
+}
+
+/******************************************************************
+ *		PULSE_AddRingMessage
+ *
+ * Inserts a new message into the ring (should be called from DriverProc derived routines)
+ */
+int PULSE_AddRingMessage(PULSE_MSG_RING* omr, enum win_wm_message msg, DWORD param, BOOL wait)
+{
+    HANDLE	hEvent = INVALID_HANDLE_VALUE;
+
+    EnterCriticalSection(&omr->msg_crst);
+    if ((omr->msg_toget == ((omr->msg_tosave + 1) % omr->ring_buffer_size)))
+    {
+	int old_ring_buffer_size = omr->ring_buffer_size;
+	omr->ring_buffer_size += PULSE_RING_BUFFER_INCREMENT;
+	omr->messages = HeapReAlloc(GetProcessHeap(),0,omr->messages, omr->ring_buffer_size * sizeof(PULSE_MSG));
+	/* Now we need to rearrange the ring buffer so that the new
+	   buffers just allocated are in between omr->msg_tosave and
+	   omr->msg_toget.
+	*/
+	if (omr->msg_tosave < omr->msg_toget)
+	{
+	    memmove(&(omr->messages[omr->msg_toget + PULSE_RING_BUFFER_INCREMENT]),
+		    &(omr->messages[omr->msg_toget]),
+		    sizeof(PULSE_MSG)*(old_ring_buffer_size - omr->msg_toget)
+		    );
+	    omr->msg_toget += PULSE_RING_BUFFER_INCREMENT;
+	}
+    }
+    if (wait)
+    {
+        hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+        if (hEvent == INVALID_HANDLE_VALUE)
+        {
+            ERR("can't create event !?\n");
+            LeaveCriticalSection(&omr->msg_crst);
+            return 0;
+        }
+        /* fast messages have to be added at the start of the queue */
+        omr->msg_toget = (omr->msg_toget + omr->ring_buffer_size - 1) % omr->ring_buffer_size;
+
+        omr->messages[omr->msg_toget].msg = msg;
+        omr->messages[omr->msg_toget].param = param;
+        omr->messages[omr->msg_toget].hEvent = hEvent;
+    }
+    else
+    {
+        omr->messages[omr->msg_tosave].msg = msg;
+        omr->messages[omr->msg_tosave].param = param;
+        omr->messages[omr->msg_tosave].hEvent = INVALID_HANDLE_VALUE;
+        omr->msg_tosave = (omr->msg_tosave + 1) % omr->ring_buffer_size;
+    }
+    LeaveCriticalSection(&omr->msg_crst);
+    /* signal a new message */
+    SIGNAL_OMR(omr);
+    if (wait)
+    {
+        /* wait for playback/record thread to have processed the message */
+        WaitForSingleObject(hEvent, INFINITE);
+        CloseHandle(hEvent);
+    }
+    return 1;
+}
+
+/******************************************************************
+ *		PULSE_RetrieveRingMessage
+ *
+ * Get a message from the ring. Should be called by the playback/record thread.
+ */
+int PULSE_RetrieveRingMessage(PULSE_MSG_RING* omr,
+                                   enum win_wm_message *msg, DWORD *param, HANDLE *hEvent)
+{
+    EnterCriticalSection(&omr->msg_crst);
+
+    if (omr->msg_toget == omr->msg_tosave) /* buffer empty ? */
+    {
+        LeaveCriticalSection(&omr->msg_crst);
+	return 0;
+    }
+
+    *msg = omr->messages[omr->msg_toget].msg;
+    omr->messages[omr->msg_toget].msg = 0;
+    *param = omr->messages[omr->msg_toget].param;
+    *hEvent = omr->messages[omr->msg_toget].hEvent;
+    omr->msg_toget = (omr->msg_toget + 1) % omr->ring_buffer_size;
+    CLEAR_OMR(omr);
+    LeaveCriticalSection(&omr->msg_crst);
+    return 1;
+}
+
+/**************************************************************************
+ * Utility Functions
+ *************************************************************************/
+
+/******************************************************************
+ *		PULSE_SetupFormat
+ *
+ * Checks to see if the audio format in wf is supported, and if so set up the
+ * pa_sample_spec at ss to that format.
+ */
+BOOL PULSE_SetupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss) {
+    WAVEFORMATEXTENSIBLE *wfex;
+
+    ss->channels = wf->nChannels;
+    ss->rate = wf->nSamplesPerSec;
+    ss->format = PA_SAMPLE_INVALID;
+    
+    if (ss->rate < DSBFREQUENCY_MIN || ss->rate > DSBFREQUENCY_MAX) return FALSE;
+
+    switch (wf->wFormatTag) {
+	case WAVE_FORMAT_PCM:
+	/* MSDN says that for WAVE_FORMAT_PCM, nChannels must be 1 or 2 and
+	 * wBitsPerSample must be 8 or 16, yet other values are used by some
+	 * applications in the wild for surround. */
+	if (ss->channels > 6 || ss->channels < 1) return FALSE;
+	ss->format = wf->wBitsPerSample == 8  ? PA_SAMPLE_U8
+		   : wf->wBitsPerSample == 16 ? PA_SAMPLE_S16NE
+#if PA_PROTOCOL_VERSION >= 15
+		   : wf->wBitsPerSample == 24 ? PA_SAMPLE_S24NE
+#endif
+		   : wf->wBitsPerSample == 32 ? PA_SAMPLE_S32NE
+		   : PA_SAMPLE_INVALID;
+	break;
+
+	case WAVE_FORMAT_MULAW:
+	if (wf->wBitsPerSample == 8) ss->format = PA_SAMPLE_ULAW;
+	break;
+
+	case WAVE_FORMAT_ALAW:
+	if (wf->wBitsPerSample == 8) ss->format = PA_SAMPLE_ALAW;
+	break;
+
+	case WAVE_FORMAT_EXTENSIBLE:
+	if (wf->cbSize > 22) return FALSE;
+        if (ss->channels < 1 || ss->channels > 6) return FALSE;
+	wfex = (WAVEFORMATEXTENSIBLE *)wf;
+        if (IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) {
+	    if (wf->wBitsPerSample == wfex->Samples.wValidBitsPerSample) {
+	        ss->format = wf->wBitsPerSample == 8  ? PA_SAMPLE_U8
+		           : wf->wBitsPerSample == 16 ? PA_SAMPLE_S16NE
+#if PA_PROTOCOL_VERSION >= 15 
+		           : wf->wBitsPerSample == 24 ? PA_SAMPLE_S24NE
+#endif
+		           : wf->wBitsPerSample == 32 ? PA_SAMPLE_S32NE
+		           : PA_SAMPLE_INVALID;
+	    } else {
+#if PA_PROTOCOL_VERSION >= 15
+		if (wf->wBitsPerSample == 32 && wfex->Samples.wValidBitsPerSample == 24)
+		    ss->format = PA_SAMPLE_S24_32NE;
+	        else
+#endif
+		return FALSE;
+	    }
+	} else if (wf->wBitsPerSample != wfex->Samples.wValidBitsPerSample) {
+	    return FALSE;
+	} else if ((IsEqualGUID(&wfex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) {
+	    ss->format = PA_SAMPLE_FLOAT32NE;
+	} else {
+	    WARN("only KSDATAFORMAT_SUBTYPE_PCM and KSDATAFORMAT_SUBTYPE_IEEE_FLOAT of WAVE_FORMAT_EXTENSIBLE supported\n");
+	    return FALSE;
+	}
+	break;
+
+	default:
+	WARN("only WAVE_FORMAT_PCM, WAVE_FORMAT_MULAW, WAVE_FORMAT_ALAW and WAVE_FORMAT_EXTENSIBLE supported\n");
+	return FALSE;
+    }
+
+    if (!pa_sample_spec_valid(ss)) return FALSE;
+    if (wf->nBlockAlign != pa_frame_size(ss)) {
+	ERR("wf->nBlockAlign != the format frame size!\n");
+	return FALSE;
+    }
+
+    return TRUE;
+}
+
+/**************************************************************************
+ *		PULSE_WaitForOperation
+ *
+ * Waits for pa operations to complete, and dereferences the operation.
+ */
+void PULSE_WaitForOperation(pa_operation *o) {
+    if (!o) return;
+
+    for (;;) {
+	if (pa_operation_get_state(o) != PA_OPERATION_RUNNING)
+	    break;
+	pa_threaded_mainloop_wait(PULSE_ml);
+    }
+    pa_operation_unref(o);
+}
+
+/**************************************************************************
+ * Common Callbacks
+ */
+
+/**************************************************************************
+ * 			PULSE_StreamSuspendedCallback		[internal]
+ *
+ * Called by the pulse mainloop any time stream playback is intentionally
+ * suspended or resumed from being suspended.
+ */
+void PULSE_StreamSuspendedCallback(pa_stream *s, void *userdata) {
+    WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
+    assert(s && wwo);
+
+    /* Currently we handle this kinda like an underrun. Perhaps we should
+     * tell the client somehow so it doesn't just hang? */
+
+    if (!pa_stream_is_suspended(s) && wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0)
+	PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 0, FALSE);
+}
+
+/**************************************************************************
+ * 			PULSE_StreamUnderflowCallback		[internal]
+ *
+ * Called by the pulse mainloop when the prebuf runs out of data.
+ */
+void PULSE_StreamUnderflowCallback(pa_stream *s, void *userdata) {
+    WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
+    assert(s && wwo);
+
+    /* If we aren't playing, don't care ^_^ */
+    if (wwo->state != WINE_WS_PLAYING) return;
+
+    TRACE("Underrun occurred.\n");
+
+    if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0);
+        PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 0, FALSE);
+}
+
+/**************************************************************************
+ * 			PULSE_StreamOverflowCallback	    	[internal]
+ *
+ * Called by the pulse mainloop when the write buffer was overflowed and data
+ * was lost.
+ */
+void PULSE_StreamOverflowCallback(pa_stream *s, void *userdata) {
+    WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
+    assert(s && wwo);
+
+    ERR("Overflow occurred!?!\n");
+
+    if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size > 0);
+        PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_XRUN, 1, FALSE);
+}
+
+/**************************************************************************
+ * 			PULSE_StreamMovedCallback		[internal]
+ *
+ * Called by the pulse mainloop when the stream gets moved, resulting in
+ * possibly different metrics.
+ */
+void PULSE_StreamMovedCallback(pa_stream *s, void *userdata) {
+    FIXME("stub");
+}
+
+
+/******************************************************************
+ *		PULSE_StreamStateCallback
+ *
+ * Called by pulse whenever the state of the stream changes.
+ *
+ * void *userdata is either the dsound or wave instance
+ */
+void PULSE_StreamStateCallback(pa_stream *s, void *userdata) {
+    assert(s);
+
+    switch (pa_stream_get_state(s)) {
+    	case PA_STREAM_READY:
+	TRACE("Stream %p ready\n", userdata);
+	break;
+
+    	case PA_STREAM_TERMINATED:
+	TRACE("Stream %p terminated\n", userdata);
+	break;
+
+    	case PA_STREAM_FAILED:
+	ERR("Stream %p failed!\n", userdata);
+	break;
+
+    	case PA_STREAM_UNCONNECTED:
+    	case PA_STREAM_CREATING:
+	return;
+    }
+    pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * 			PULSE_StreamSucessCallback
+ */
+void PULSE_StreamSuccessCallback(pa_stream *s, int success, void *userdata) {
+    if (!success)
+	WARN("Stream %p operation failed: %s\n", userdata, pa_strerror(pa_context_errno(PULSE_context)));
+
+    pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * 			PULSE_ContextSuccessCallback
+ */
+void PULSE_ContextSuccessCallback(pa_context *c, int success, void *userdata) {
+    if (!success) ERR("Context operation failed: %s\n", pa_strerror(pa_context_errno(c)));
+    pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * Connection management and sink / source management.
+ */
+
+/**************************************************************************
+ * 				PULSE_ContextStateCallback    [internal]
+ */
+static void PULSE_ContextStateCallback(pa_context *c, void *userdata) {
+    assert(c);
+
+    switch (pa_context_get_state(c)) {
+        case PA_CONTEXT_CONNECTING:
+	case PA_CONTEXT_UNCONNECTED:
+        case PA_CONTEXT_AUTHORIZING:
+        case PA_CONTEXT_SETTING_NAME:
+            break;
+
+	case PA_CONTEXT_READY:
+	case PA_CONTEXT_TERMINATED:
+	    pa_threaded_mainloop_signal(PULSE_ml, 0);
+	    break;
+
+	case PA_CONTEXT_FAILED:
+	    ERR("Context failure: %s\n", pa_strerror(pa_context_errno(c)));
+	    pa_threaded_mainloop_signal(PULSE_ml, 0);
+	    break;
+    }
+}
+
+/**************************************************************************
+ * 		    PULSE_AllocateWaveinDevice   		[internal]
+ *
+ * Creates or adds a device to WInDev based on the pa_source_info.
+ */
+static void PULSE_AllocateWaveinDevice(const char *name, const char *device, const char *description, const pa_cvolume *v) {
+    WINE_WAVEDEV *wdi;
+
+    if (WInDev)
+	wdi = HeapReAlloc(GetProcessHeap(), 0, WInDev, sizeof(WINE_WAVEDEV) * (PULSE_WidNumDevs + 1));
+    else
+	wdi = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV));
+
+    if (!wdi) return;
+    
+    WInDev = wdi;
+    wdi = &WInDev[PULSE_WidNumDevs++];
+    memset(wdi, 0, sizeof(WINE_WAVEDEV));
+    memset(&(wdi->caps.in), 0, sizeof(wdi->caps.in));
+    snprintf(wdi->interface_name, MAXPNAMELEN * 2, "winepulse: %s", name);
+    wdi->device_name = pa_xstrdup(device);
+    strcpy(wdi->interface_name, "winepulse: ");
+    MultiByteToWideChar(CP_ACP, 0, description, -1, wdi->caps.in.szPname, sizeof(wdi->caps.in.szPname)/sizeof(WCHAR));
+    wdi->caps.in.szPname[sizeof(wdi->caps.in.szPname)/sizeof(WCHAR) - 1] = '\0';
+    wdi->caps.in.wMid = MM_CREATIVE;
+    wdi->caps.in.wPid = MM_CREATIVE_SBP16_WAVEOUT;
+    wdi->caps.in.vDriverVersion = 0x0100;
+    wdi->caps.in.wChannels = v->channels == 1 ? 1 : 2;
+    wdi->caps.in.dwFormats = PULSE_ALL_FORMATS;
+    memset(&wdi->ds_desc, 0, sizeof(DSDRIVERDESC));
+    memcpy(wdi->ds_desc.szDesc, description, min(sizeof(wdi->ds_desc.szDesc) - 1, strlen(description)));
+    memcpy(wdi->ds_desc.szDrvname, "winepulse.drv", 14);
+    wdi->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN;
+    wdi->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX;
+    wdi->ds_caps.dwPrimaryBuffers = 1;
+    wdi->ds_caps.dwFlags = \
+	DSCAPS_PRIMARYMONO |
+	DSCAPS_PRIMARYSTEREO |
+	DSCAPS_PRIMARY8BIT |
+	DSCAPS_PRIMARY16BIT |
+	DSCAPS_SECONDARYMONO |
+	DSCAPS_SECONDARYSTEREO |
+	DSCAPS_SECONDARY8BIT |
+	DSCAPS_SECONDARY16BIT |
+	DSCCAPS_MULTIPLECAPTURE |
+	DSCAPS_CERTIFIED | /* Useful? */
+	DSCAPS_EMULDRIVER; /* Useful? */
+
+}
+
+/**************************************************************************
+ * 		    PULSE_AllocateWaveoutDevice			[internal]
+ *
+ *  Creates or adds a sink to the WOutDev array.
+ */
+static void PULSE_AllocateWaveoutDevice(const char *name, const char *device, const char *description, const pa_cvolume *v) {
+    WINE_WAVEDEV *wdo;
+    int x;
+
+    if (WOutDev)
+        wdo = HeapReAlloc(GetProcessHeap(), 0, WOutDev, sizeof(WINE_WAVEDEV) * (PULSE_WodNumDevs + 1));
+    else
+        wdo = HeapAlloc(GetProcessHeap(), 0, sizeof(WINE_WAVEDEV));
+
+    if (!wdo) return;
+
+    WOutDev = wdo;
+    wdo = &WOutDev[PULSE_WodNumDevs++];
+    memset(wdo, 0, sizeof(WINE_WAVEDEV));
+
+    wdo->device_name = pa_xstrdup(device);
+    wdo->volume.channels = v->channels;
+    for (x = 0; x < v->channels; x++) wdo->volume.values[x] = v->values[x];
+    snprintf(wdo->interface_name, MAXPNAMELEN * 2, "winepulse: %s", name);
+    MultiByteToWideChar(CP_ACP, 0, description, -1, wdo->caps.out.szPname, sizeof(wdo->caps.out.szPname)/sizeof(WCHAR));
+    wdo->caps.out.szPname[sizeof(wdo->caps.out.szPname)/sizeof(WCHAR) - 1] = '\0';
+    wdo->caps.out.wMid = MM_CREATIVE;
+    wdo->caps.out.wPid = MM_CREATIVE_SBP16_WAVEOUT;
+    wdo->caps.out.vDriverVersion = 0x0100;
+    wdo->caps.out.dwSupport = WAVECAPS_VOLUME | WAVECAPS_SAMPLEACCURATE | WAVECAPS_DIRECTSOUND;
+    if (v->channels >= 2) {
+	wdo->caps.out.wChannels = 2;
+	wdo->caps.out.dwSupport |= WAVECAPS_LRVOLUME;
+    } else
+	wdo->caps.out.wChannels = 1;
+    wdo->caps.out.dwFormats = PULSE_ALL_FORMATS;
+    memset(&wdo->ds_desc, 0, sizeof(DSDRIVERDESC));
+    memcpy(wdo->ds_desc.szDesc, description, min(sizeof(wdo->ds_desc.szDesc) - 1, strlen(description)));
+    memcpy(wdo->ds_desc.szDrvname, "winepulse.drv", 14);
+    wdo->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN;
+    wdo->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX;
+    wdo->ds_caps.dwPrimaryBuffers = 1;
+    wdo->ds_caps.dwFlags = \
+	DSCAPS_PRIMARYMONO |
+	DSCAPS_PRIMARYSTEREO |
+	DSCAPS_PRIMARY8BIT |
+	DSCAPS_PRIMARY16BIT |
+	DSCAPS_SECONDARYMONO |
+	DSCAPS_SECONDARYSTEREO |
+	DSCAPS_SECONDARY8BIT |
+	DSCAPS_SECONDARY16BIT |
+	DSCAPS_CERTIFIED;
+}
+
+/**************************************************************************
+ *                              PULSE_SourceInfoCallback        [internal]
+ */
+static void PULSE_SourceInfoCallback(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
+
+    if (!eol && i)
+        PULSE_AllocateWaveinDevice(i->name, i->name, i->description, &i->volume);
+
+    pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ *                              PULSE_SinkInfoCallback          [internal]
+ */
+static void PULSE_SinkInfoCallback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
+
+    if (!eol && i)
+	PULSE_AllocateWaveoutDevice(i->name, i->name, i->description, &i->volume);
+
+    pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ *                              PULSE_ContextNotifyCallback     [internal]
+ */
+static void PULSE_ContextNotifyCallback(pa_context *c, void *userdata) {
+    pa_threaded_mainloop_signal(PULSE_ml, 0);
+}
+
+/**************************************************************************
+ * 				PULSE_WaveClose                 [internal]
+ *
+ * Disconnect from the server, deallocated the WaveIn/WaveOut devices, stop and
+ * free the mainloop.
+ */
+
+static LONG PULSE_WaveClose(void) {
+    int x;
+    TRACE("()\n");
+    if (!PULSE_ml) return DRV_FAILURE;
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    /* device_name is allocated with pa_xstrdup, free with pa_xfree */
+    for (x = 0; x < PULSE_WodNumDevs; x++) pa_xfree(WOutDev[x].device_name);
+    for (x = 0; x < PULSE_WidNumDevs; x++) pa_xfree(WInDev[x].device_name);
+    HeapFree(GetProcessHeap(), 0, WOutDev);
+    HeapFree(GetProcessHeap(), 0, WInDev);
+    if (PULSE_context) {
+	PULSE_WaitForOperation(pa_context_drain(PULSE_context, PULSE_ContextNotifyCallback, NULL));
+	pa_context_disconnect(PULSE_context);
+	pa_context_unref(PULSE_context);
+	PULSE_context = NULL;
+    }
+
+    pa_threaded_mainloop_unlock(PULSE_ml);
+    pa_threaded_mainloop_stop(PULSE_ml);
+    pa_threaded_mainloop_free(PULSE_ml);
+    PULSE_ml = NULL;
+    
+    return DRV_SUCCESS;
+}
+
+/**************************************************************************
+ * 				PULSE_WaveInit                  [internal]
+ *
+ * Connects to the pulseaudio server, tries to discover sinks and sources and
+ * allocates the WaveIn/WaveOut devices.
+ */
+static LONG PULSE_WaveInit(void) {
+    char *app_name;
+    char path[PATH_MAX];
+    char *offset = NULL;
+    int x = 0;
+    pa_cvolume fake_cvolume;
+    
+    WOutDev = NULL;
+    WInDev = NULL;
+    PULSE_WodNumDevs = 0;
+    PULSE_WidNumDevs = 0;
+    PULSE_context = NULL;
+    PULSE_ml = NULL;
+
+    if (!(PULSE_ml = pa_threaded_mainloop_new())) {
+	WARN("Failed to create mainloop object.");
+	return -1;
+    }
+    
+    /* Application name giving to pulse should be unique to the binary so that
+     * pulse-*-restore can be useful */
+
+    /* Get binary path, and remove path a-la strrchr */
+    if (GetModuleFileNameA(NULL, path, PATH_MAX))
+	offset = strrchr(path, '\\');
+
+    if (offset && ++offset && offset < path + PATH_MAX) {
+        app_name = pa_xmalloc(strlen(offset) + 8);
+	snprintf(app_name, strlen(offset) + 8, "WINE [%s]", offset);
+    } else
+	app_name = pa_xstrdup("WINE Application");
+
+    TRACE("App name is \"%s\"\n", app_name);
+
+    pa_threaded_mainloop_start(PULSE_ml);
+    PULSE_context = pa_context_new(pa_threaded_mainloop_get_api(PULSE_ml), app_name);
+    assert(PULSE_context);
+    pa_xfree(app_name);
+    
+    pa_context_set_state_callback(PULSE_context, PULSE_ContextStateCallback, NULL);
+       
+    pa_threaded_mainloop_lock(PULSE_ml);
+
+    TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(PULSE_context), PA_API_VERSION);
+    TRACE("Attempting to connect to pulseaudio server.\n");
+    if (pa_context_connect(PULSE_context, NULL, 0, NULL) < 0)
+	goto fail;
+
+    /* Wait for connection */
+    for (;;) {
+	pa_context_state_t state = pa_context_get_state(PULSE_context);
+
+	if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED)
+	    goto fail;
+
+	if (state == PA_CONTEXT_READY)
+	    break;
+    
+	pa_threaded_mainloop_wait(PULSE_ml);
+    }
+
+    x = pa_context_get_server_protocol_version(PULSE_context);
+    TRACE("Connected to server %s with protocol version: %i.\n", pa_context_get_server(PULSE_context), x);
+    if (x < 14)
+	WARN("Server is old, expect poor latency or buggy-ness!\n");
+
+    fake_cvolume.channels = 2;
+    pa_cvolume_reset(&fake_cvolume, 2);
+    /* FIXME Translations? */
+    PULSE_AllocateWaveoutDevice("default", NULL, "Default", &fake_cvolume);
+    PULSE_AllocateWaveinDevice("default", NULL, "Default", &fake_cvolume);
+    PULSE_WaitForOperation(pa_context_get_sink_info_list(PULSE_context, PULSE_SinkInfoCallback, &PULSE_WodNumDevs));
+    PULSE_WaitForOperation(pa_context_get_source_info_list(PULSE_context, PULSE_SourceInfoCallback, &PULSE_WidNumDevs));
+    TRACE("Found %u output and %u input device(s).\n", PULSE_WodNumDevs - 1, PULSE_WidNumDevs - 1);
+
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    return DRV_SUCCESS;
+
+fail:
+    pa_threaded_mainloop_unlock(PULSE_ml);    
+    ERR("Failed to connect to server\n");
+    return DRV_FAILURE;
+}
+
+#endif /* HAVE_PULSEAUDIO */
+
+/**************************************************************************
+ * 				DriverProc (WINEPULSE.@)
+ */
+LRESULT CALLBACK PULSE_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
+                                 LPARAM dwParam1, LPARAM dwParam2) {
+
+    switch(wMsg) {
+#ifdef HAVE_PULSEAUDIO
+    case DRV_LOAD:		return PULSE_WaveInit();
+    case DRV_FREE:		return PULSE_WaveClose();
+    case DRV_OPEN:		return 1;
+    case DRV_CLOSE:		return 1;
+    case DRV_ENABLE:		return 1;
+    case DRV_DISABLE:		return 1;
+    case DRV_QUERYCONFIGURE:	return 1;
+    case DRV_CONFIGURE:		MessageBoxA(0, "PulseAudio MultiMedia Driver !", "PulseAudio Driver", MB_OK);	return 1;
+    case DRV_INSTALL:		return DRVCNF_RESTART;
+    case DRV_REMOVE:		return DRVCNF_RESTART;
+#endif
+    default:
+	return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
+    }
+}
diff --git a/dlls/winepulse.drv/wavein.c b/dlls/winepulse.drv/wavein.c
new file mode 100644
index 0000000..5495b2a
--- /dev/null
+++ b/dlls/winepulse.drv/wavein.c
@@ -0,0 +1,614 @@
+/*
+ * Wine Driver for PulseAudio - WaveIn Functionality
+ * http://pulseaudio.org/
+ *
+ * Copyright	2009 Arthur Taylor <theycallhimart@gmail.com>
+ *
+ * Contains code from other wine multimedia drivers.
+ *
+ * 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 <stdarg.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winnls.h"
+#include "mmddk.h"
+
+#include <winepulse.h>
+
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(wave);
+
+#if HAVE_PULSEAUDIO
+
+/*======================================================================*
+ *                  WAVE IN specific PulseAudio Callbacks		*
+ *======================================================================*/
+
+/**************************************************************************
+* 			widNotifyClient			[internal]
+*/
+static DWORD widNotifyClient(WINE_WAVEINST* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2) { 
+   TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2);
+
+   switch (wMsg) {
+   case WIM_OPEN:
+   case WIM_CLOSE:
+   case WIM_DATA:
+       if (wwi->wFlags != DCB_NULL &&
+	   !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, (HDRVR)wwi->waveDesc.hWave,
+			   wMsg, wwi->waveDesc.dwInstance, dwParam1, dwParam2)) {
+	   WARN("can't notify client !\n");
+	   return MMSYSERR_ERROR;
+       }
+       break;
+   default:
+       FIXME("Unknown callback message %u\n", wMsg);
+       return MMSYSERR_INVALPARAM;
+   }
+   return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                         widRecorder_NextFragment             [internal]
+ *
+ * Gets the next fragment of data from the server.
+ */
+static size_t widRecorder_NextFragment(WINE_WAVEINST *wwi) {
+    size_t nbytes;
+
+    pa_stream_peek(wwi->stream, &wwi->buffer, &nbytes);
+    wwi->buffer_length = nbytes;
+    wwi->buffer_read_offset = 0;
+
+    return nbytes;
+}
+
+
+/**************************************************************************
+ *                         widRecorder_CopyData                 [internal]
+ *
+ * Copys data from the fragments pulse returns to queued buffers.
+ */
+static void widRecorder_CopyData(WINE_WAVEINST *wwi) {
+    LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr;
+    size_t nbytes;
+
+    while (lpWaveHdr && wwi->state == WINE_WS_PLAYING) {
+
+        nbytes = min(wwi->buffer_length - wwi->buffer_read_offset, lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded);
+	if (nbytes == 0) break;
+	
+	TRACE("%u bytes from %p to %p\n", nbytes, (PBYTE)wwi->buffer + wwi->buffer_read_offset, lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded);
+	memcpy(lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded, (PBYTE)wwi->buffer + wwi->buffer_read_offset, nbytes);
+	
+	lpWaveHdr->dwBytesRecorded += nbytes;
+	wwi->buffer_read_offset += nbytes;
+
+	if (wwi->buffer_read_offset == wwi->buffer_length) {
+	    pa_threaded_mainloop_lock(PULSE_ml);
+	    pa_stream_drop(wwi->stream);
+	    if (pa_stream_readable_size(wwi->stream))
+		widRecorder_NextFragment(wwi);
+	    else {
+		wwi->buffer = NULL;
+		wwi->buffer_length = 0;
+		wwi->buffer_read_offset = 0;
+	    }
+	    pa_threaded_mainloop_unlock(PULSE_ml);
+	}
+
+	if (lpWaveHdr->dwBytesRecorded == lpWaveHdr->dwBufferLength) {
+	    lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+	    lpWaveHdr->dwFlags |= WHDR_DONE;
+	    wwi->lpQueuePtr = lpWaveHdr->lpNext;
+	    widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
+	    lpWaveHdr = wwi->lpQueuePtr;
+	}
+    }
+}
+
+/**************************************************************************
+ * 				widRecorder			[internal]
+ */
+static DWORD CALLBACK widRecorder(LPVOID lpParam) {
+    WINE_WAVEINST *wwi = (WINE_WAVEINST*)lpParam;
+    LPWAVEHDR           lpWaveHdr;
+    enum win_wm_message	msg;
+    DWORD		param;
+    HANDLE		ev;
+    DWORD		wait;
+
+    wwi->state = WINE_WS_STOPPED;
+    SetEvent(wwi->hStartUpEvent);
+
+    for (;;) {
+	
+	if (wwi->state != WINE_WS_PLAYING) {
+	    wait = INFINITE;
+	} else {
+	    if (wwi->buffer == NULL && pa_stream_readable_size(wwi->stream)) {
+		pa_threaded_mainloop_lock(PULSE_ml);
+		wait = pa_bytes_to_usec(widRecorder_NextFragment(wwi), &wwi->sample_spec)/1000;
+		pa_threaded_mainloop_unlock(PULSE_ml);
+	    }
+	}
+
+    	widRecorder_CopyData(wwi);
+
+	PULSE_WaitRingMessage(&wwi->msgRing, wait);
+
+	while (PULSE_RetrieveRingMessage(&wwi->msgRing, &msg, &param, &ev)) {
+	    TRACE("Received %s %x\n", PULSE_getCmdString(msg), param);
+	
+	    switch (msg) {
+	    case WINE_WM_FEED:
+		SetEvent(ev);
+		break;
+	    case WINE_WM_STARTING:
+		wwi->state = WINE_WS_PLAYING;
+		wait = pa_bytes_to_usec(wwi->lpQueuePtr->dwBufferLength, &wwi->sample_spec)/1000;
+		wwi->last_reset = wwi->timing_info->read_index;
+		pa_threaded_mainloop_lock(PULSE_ml);
+		PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 0, PULSE_StreamSuccessCallback, NULL));
+		pa_threaded_mainloop_unlock(PULSE_ml);
+		SetEvent(ev);
+		break;
+	    case WINE_WM_HEADER:
+		lpWaveHdr = (LPWAVEHDR)param;
+		lpWaveHdr->lpNext = 0;
+
+		/* insert buffer at the end of queue */
+		{
+		    LPWAVEHDR*	wh;
+		    for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
+		    *wh = lpWaveHdr;
+		}
+		break;
+	    case WINE_WM_STOPPING:
+		if (wwi->state != WINE_WS_STOPPED) {
+		    wwi->state = WINE_WS_STOPPED;
+		    pa_threaded_mainloop_lock(PULSE_ml);
+		    PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL));
+		    pa_threaded_mainloop_unlock(PULSE_ml);
+
+		    /* return current buffer to app */
+		    lpWaveHdr = wwi->lpQueuePtr;
+		    if (lpWaveHdr) {
+			LPWAVEHDR	lpNext = lpWaveHdr->lpNext;
+			TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext);
+			lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+			lpWaveHdr->dwFlags |= WHDR_DONE;
+			wwi->lpQueuePtr = lpNext;
+			widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
+		    }
+		}
+		SetEvent(ev);
+		break;
+	    case WINE_WM_RESETTING:
+		if (wwi->state != WINE_WS_STOPPED) {
+		    wwi->state = WINE_WS_STOPPED;
+		    pa_threaded_mainloop_lock(PULSE_ml);
+		    PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL));
+		    pa_threaded_mainloop_unlock(PULSE_ml);
+		}
+
+		/* return all buffers to the app */
+		for (lpWaveHdr = wwi->lpPlayPtr ? wwi->lpPlayPtr : wwi->lpQueuePtr; lpWaveHdr; lpWaveHdr = wwi->lpQueuePtr) {
+		    lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+		    lpWaveHdr->dwFlags |= WHDR_DONE;
+		    wwi->lpQueuePtr = lpWaveHdr->lpNext;
+		    widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
+		}
+
+		SetEvent(ev);
+		break;
+	    case WINE_WM_CLOSING:
+		wwi->hThread = 0;
+		if ((DWORD)param == 1) {
+		    /* If we are here, the stream failed */
+		    wwi->state = WINE_WS_FAILED;
+		    SetEvent(ev);
+		    PULSE_DestroyRingMessage(&wwi->msgRing);
+		    widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
+		    wwi->lpPlayPtr = wwi->lpQueuePtr = NULL;
+		    pa_threaded_mainloop_lock(PULSE_ml);
+		    pa_stream_disconnect(wwi->stream);
+		    pa_threaded_mainloop_unlock(PULSE_ml);
+		    TRACE("Thread exiting because of failure.\n");
+		    ExitThread(1);
+		}
+		wwi->state = WINE_WS_CLOSED;
+		SetEvent(ev);
+		ExitThread(0);
+		/* shouldn't go here */
+	    default:
+		FIXME("unknown message %d\n", msg);
+		break;
+	    } /* switch(msg) */
+	} /* while(PULSE_RetrieveRingMessage()) */
+    } /* for (;;) */
+}
+
+/**************************************************************************
+ * 				widOpen				[internal]
+ */
+static DWORD widOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) {
+    WINE_WAVEDEV *wdi;
+    WINE_WAVEINST *wwi = NULL;
+    DWORD ret = MMSYSERR_NOERROR;
+
+    TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
+    if (lpDesc == NULL) {
+	WARN("Invalid Parameter !\n");
+	return MMSYSERR_INVALPARAM;
+    }
+
+    if (wDevID >= PULSE_WidNumDevs) {
+	TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs);
+	return MMSYSERR_BADDEVICEID;
+    }
+    wdi = &WInDev[wDevID];
+
+    wwi = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST));
+    if (!wwi) return MMSYSERR_NOMEM;
+    *lpdwUser = (DWORD)wwi;
+    
+    /* check to see if format is supported and make pa_sample_spec struct */
+    if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwi->sample_spec)) {
+	WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	ret = WAVERR_BADFORMAT;
+	goto exit;
+    }
+
+    if (TRACE_ON(wave)) {
+    	char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
+    	pa_sample_spec_snprint(t, sizeof(t), &wwi->sample_spec);
+    	TRACE("Sample spec '%s'\n", t);
+    }
+
+    if (dwFlags & WAVE_FORMAT_QUERY) {
+	TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	ret = MMSYSERR_NOERROR;
+	goto exit;
+    }
+
+    wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
+    wwi->waveDesc = *lpDesc;
+    PULSE_InitRingMessage(&wwi->msgRing);
+
+    wwi->stream = pa_stream_new(PULSE_context, "WaveIn", &wwi->sample_spec, NULL);
+    if (!wwi->stream) {
+	ret = WAVERR_BADFORMAT;
+	goto exit;
+    }
+
+    pa_stream_set_state_callback(wwi->stream, PULSE_StreamStateCallback, wwi);
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    TRACE("Asking to open %s for recording.\n", wdi->device_name);
+    pa_stream_connect_record(wwi->stream, wdi->device_name, NULL,
+			     PA_STREAM_START_CORKED |
+			     PA_STREAM_AUTO_TIMING_UPDATE |
+			     PA_STREAM_INTERPOLATE_TIMING);
+
+    for (;;) {
+	pa_context_state_t cstate = pa_context_get_state(PULSE_context);
+	pa_stream_state_t sstate = pa_stream_get_state(wwi->stream);
+
+	if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED ||
+	    sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) {
+	    ERR("Failed to connect context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
+		ret = MMSYSERR_NODRIVER;
+		pa_threaded_mainloop_unlock(PULSE_ml);
+		goto exit;
+	    }
+
+	    if (sstate == PA_STREAM_READY)
+		break;
+
+	    pa_threaded_mainloop_wait(PULSE_ml);
+	}
+    TRACE("(%p)->stream connected for recording.\n", wwi);
+
+    PULSE_WaitForOperation(pa_stream_update_timing_info(wwi->stream, PULSE_StreamSuccessCallback, wwi));
+
+    wwi->timing_info = pa_stream_get_timing_info(wwi->stream);
+    assert(wwi->timing_info);
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    wwi->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+    wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)wwi, 0, &(wwi->dwThreadID));
+    if (wwi->hThread)
+        SetThreadPriority(wwi->hThread, THREAD_PRIORITY_TIME_CRITICAL);
+    else {
+        ERR("Thread creation for the widRecorder failed!\n");
+        ret = MMSYSERR_NOMEM;
+	goto exit;
+    }
+    WaitForSingleObject(wwi->hStartUpEvent, INFINITE);
+    CloseHandle(wwi->hStartUpEvent);
+    wwi->hStartUpEvent = INVALID_HANDLE_VALUE;
+
+    return widNotifyClient(wwi, WIM_OPEN, 0L, 0L);
+
+exit:
+    if (!wwi)
+	return ret;
+
+    if (wwi->hStartUpEvent != INVALID_HANDLE_VALUE)
+	CloseHandle(wwi->hStartUpEvent);
+
+    if (wwi->msgRing.ring_buffer_size > 0)
+	PULSE_DestroyRingMessage(&wwi->msgRing);
+
+    if (wwi->stream) {
+	if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY)
+	    pa_stream_disconnect(wwi->stream);
+        pa_stream_unref(wwi->stream);
+    }
+    HeapFree(GetProcessHeap(), 0, wwi);
+
+    return ret;
+}
+/**************************************************************************
+ *                              widClose                        [internal]
+ */
+static DWORD widClose(WORD wDevID, WINE_WAVEINST *wwi) {
+    DWORD ret;
+
+    TRACE("(%u, %p);\n", wDevID, wwi);
+    if (wDevID >= PULSE_WidNumDevs) {
+	WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+	return MMSYSERR_INVALHANDLE;
+    } else if (!wwi) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    if (wwi->state != WINE_WS_FAILED) {
+	if (wwi->lpQueuePtr) {
+	    WARN("buffers recording recording !\n");
+	    return WAVERR_STILLPLAYING;
+	}
+
+	pa_threaded_mainloop_lock(PULSE_ml);
+	if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY)
+	    pa_stream_drop(wwi->stream);
+	    pa_stream_disconnect(wwi->stream);
+	pa_threaded_mainloop_unlock(PULSE_ml);
+
+	if (wwi->hThread != INVALID_HANDLE_VALUE)
+	    PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE);
+
+	PULSE_DestroyRingMessage(&wwi->msgRing);
+    }
+    ret = widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
+
+    pa_stream_unref(wwi->stream);
+    TRACE("Deallocating record instance.\n");
+    HeapFree(GetProcessHeap(), 0, wwi);
+    return ret;
+}
+
+/**************************************************************************
+ * 				widAddBuffer			[internal]
+ *
+ */
+static DWORD widAddBuffer(WINE_WAVEINST* wwi, LPWAVEHDR lpWaveHdr, DWORD dwSize) {
+    TRACE("(%p, %p, %08X);\n", wwi, lpWaveHdr, dwSize);
+
+    if (!wwi ||	wwi->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
+	return WAVERR_UNPREPARED;
+
+    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
+	return WAVERR_STILLPLAYING;
+
+    lpWaveHdr->dwFlags &= ~WHDR_DONE;
+    lpWaveHdr->dwFlags |= WHDR_INQUEUE;
+    lpWaveHdr->dwBytesRecorded = 0;
+    lpWaveHdr->lpNext = 0;
+
+    PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE);
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              widRecorderMessage                [internal]
+ */
+static DWORD widRecorderMessage(WINE_WAVEINST *wwi, enum win_wm_message message) {
+    if (!wwi ||	wwi->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    PULSE_AddRingMessage(&wwi->msgRing, message, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              widGetPosition                  [internal]
+ */
+static DWORD widGetPosition(WINE_WAVEINST *wwi, LPMMTIME lpTime, DWORD uSize) {
+    DWORD   bytes, time;
+    if (!wwi ||	wwi->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    if (lpTime == NULL)	return MMSYSERR_INVALPARAM;
+
+    bytes  = wwi->timing_info->read_index - wwi->last_reset;
+    time = pa_bytes_to_usec(bytes, &wwi->sample_spec) / 1000;
+    
+    switch (lpTime->wType) {
+    case TIME_SAMPLES:
+        lpTime->u.sample = bytes / pa_frame_size(&wwi->sample_spec);
+	TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample);
+        break;
+    case TIME_MS:
+        lpTime->u.ms = time;
+	TRACE("TIME_MS=%u\n", lpTime->u.ms);
+	break;
+    case TIME_SMPTE:
+        lpTime->u.smpte.fps = 30;
+        lpTime->u.smpte.sec = time/1000;
+        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.frame = time / lpTime->u.smpte.fps * 1000;
+        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 = bytes;
+        TRACE("TIME_BYTES=%u\n", lpTime->u.cb);
+        break;
+    }
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              widGetDevCaps                   [internal]
+ */
+static DWORD widGetDevCaps(DWORD wDevID, LPWAVEINCAPSW lpCaps, DWORD dwSize) {
+    TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
+
+    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
+
+    if (wDevID >= PULSE_WidNumDevs) {
+	TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs);
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    memcpy(lpCaps, &(WInDev[wDevID].caps.in), min(dwSize, sizeof(*lpCaps)));
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              widGetNumDevs                   [internal]
+ * Context-sanity check here, as if we respond with 0, WINE will move on
+ * to the next wavein driver.
+ */
+static DWORD widGetNumDevs() {
+    if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY)
+	return 0;
+    
+    return PULSE_WidNumDevs;
+}
+
+/**************************************************************************
+ *                              widDevInterfaceSize             [internal]
+ */
+static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) {
+    TRACE("(%u, %p)\n", wDevID, dwParam1);
+
+    *dwParam1 = MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
+                                    NULL, 0 ) * sizeof(WCHAR);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              widDevInterface                 [internal]
+ */
+static DWORD widDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) {
+    if (dwParam2 >= MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
+                                        NULL, 0 ) * sizeof(WCHAR))
+    {
+        MultiByteToWideChar(CP_UNIXCP, 0, WInDev[wDevID].interface_name, -1,
+                            dwParam1, dwParam2 / sizeof(WCHAR));
+        return MMSYSERR_NOERROR;
+    }
+    return MMSYSERR_INVALPARAM;
+}
+
+/**************************************************************************
+ *                              widDsDesc                       [internal]
+ */
+DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc)
+{
+    *desc = WInDev[wDevID].ds_desc;
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				widMessage (WINEPULSE.@)
+ */
+DWORD WINAPI PULSE_widMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
+                             DWORD dwParam1, DWORD dwParam2) {
+
+    switch (wMsg) {
+    case DRVM_INIT:
+    case DRVM_EXIT:
+    case DRVM_ENABLE:
+    case DRVM_DISABLE:
+	/* FIXME: Pretend this is supported */
+	return 0;
+    case WIDM_OPEN:	 	return widOpen		(wDevID, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1,	dwParam2);
+    case WIDM_CLOSE:	 	return widClose		(wDevID, (WINE_WAVEINST*)dwUser);
+    case WIDM_ADDBUFFER:	return widAddBuffer	((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1,		dwParam2);
+    case WIDM_PREPARE:	 	return MMSYSERR_NOTSUPPORTED;
+    case WIDM_UNPREPARE: 	return MMSYSERR_NOTSUPPORTED;
+    case WIDM_GETDEVCAPS:	return widGetDevCaps	(wDevID, (LPWAVEINCAPSW)dwParam1,	dwParam2);
+    case WIDM_GETNUMDEVS:	return widGetNumDevs	();
+    case WIDM_GETPOS:	 	return widGetPosition	((WINE_WAVEINST*)dwUser,    (LPMMTIME)dwParam1, 		dwParam2);
+    case WIDM_RESET:		return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_RESETTING); 
+    case WIDM_START: 		return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STARTING);
+    case WIDM_STOP: 		return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STOPPING);
+    case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize   (wDevID, (LPDWORD)dwParam1);
+    case DRV_QUERYDEVICEINTERFACE:     return widDevInterface	    (wDevID, (PWCHAR)dwParam1, dwParam2);
+    case DRV_QUERYDSOUNDIFACE:	return MMSYSERR_NOTSUPPORTED; /* Use emulation, as there is no advantage */
+    case DRV_QUERYDSOUNDDESC:	return widDsDesc	(wDevID,    (PDSDRIVERDESC)dwParam1);
+    default:
+	FIXME("unknown message %d!\n", wMsg);
+    }
+    return MMSYSERR_NOTSUPPORTED;
+}
+
+#else /* HAVE_PULSEAUDIO */
+
+/**************************************************************************
+ * 				widMessage (WINEPULSE.@)
+ */
+DWORD WINAPI PULSE_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
+                             DWORD dwParam1, DWORD dwParam2) {
+//    FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
+    return MMSYSERR_NOTENABLED;
+}
+
+#endif /* HAVE_PULSEAUDIO */
diff --git a/dlls/winepulse.drv/waveout.c b/dlls/winepulse.drv/waveout.c
new file mode 100644
index 0000000..b531ee2
--- /dev/null
+++ b/dlls/winepulse.drv/waveout.c
@@ -0,0 +1,1176 @@
+/*
+ * Wine Driver for PulseAudio - WaveOut Functionality
+ * http://pulseaudio.org/
+ *
+ * Copyright	2009 Arthur Taylor <theycallhimart@gmail.com>
+ *
+ * Contains code from other wine multimedia drivers.
+ *
+ * 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 <stdarg.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winnls.h"
+#include "winerror.h"
+#include "mmddk.h"
+#include "mmreg.h"
+
+#include <winepulse.h>
+
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(wave);
+
+#if HAVE_PULSEAUDIO
+
+/* 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) |
+ * | PAUSED  | reset()     | RESETTING     | PAUSED                          |
+ * | (other) | reset()     | RESETTING     | STOPPED                         |
+ * | (any)   | close()     | CLOSING       | CLOSED                          |
+ * +---------+-------------+---------------+---------------------------------+
+ */
+
+/*
+ * - It is currently unknown if pausing in a loop works the same as expected.
+ */
+
+/*======================================================================*
+ *                  WAVE OUT specific PulseAudio Callbacks		*
+ *======================================================================*/
+
+/**************************************************************************
+ * 			WAVEOUT_StreamStartedCallback		[internal]
+ *
+ * Called by the pulse mainloop whenever stream playback resumes after an
+ * underflow or an initial start. Requires libpulse >= 0.9.11
+ */
+#if PA_API_VERSION >= 12
+static void WAVEOUT_StreamStartedCallback(pa_stream *s, void *userdata) {
+    WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
+    assert(s && wwo);
+
+    TRACE("Audio flowing.\n");
+
+    if (wwo->buffer_attr.tlength == 0)
+	wwo->buffer_attr.tlength = (uint32_t) -1;
+
+    if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size) {
+	PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE);
+    }
+}
+#endif
+    
+/**************************************************************************
+ * 			WAVEOUT_StreamRequestCallback
+ *
+ * Called by the pulse mainloop whenever it wants audio data.
+ */
+static void WAVEOUT_StreamRequestCallback(pa_stream *s, size_t nbytes, void *userdata) {
+    WINE_WAVEINST *ww = (WINE_WAVEINST*)userdata;
+
+    TRACE("Asking to be fed %u bytes\n", nbytes);
+
+    /* Make sure that the player/recorder is running */
+    if (ww->hThread != INVALID_HANDLE_VALUE && ww->msgRing.messages) {
+    	PULSE_AddRingMessage(&ww->msgRing, WINE_WM_FEED, (DWORD)nbytes, FALSE);
+    }
+}
+
+/**************************************************************************
+ * 		    WAVEOUT_StreamTimingInfoUpdateCallback      [internal]
+ *
+ * Called by the pulse mainloop whenever the timing info gets updated, we
+ * use this to send the started signal */
+static void WAVEOUT_StreamTimingInfoUpdateCallback(pa_stream *s, void *userdata) {
+    WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
+    assert(s && wwo);
+
+    if (!wwo->is_releasing && wwo->timing_info && wwo->timing_info->playing) {
+        TRACE("Audio flowing.\n");
+
+	if (wwo->buffer_attr.tlength == 0)
+	    wwo->buffer_attr.tlength = (uint32_t) -1;
+
+        if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size)
+	    PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE);
+    }
+}
+
+/**************************************************************************
+ * 			WAVEOUT_SinkInputInfoCallback		[internal]
+ *
+ * Called by the pulse thread. Used for wodGetVolume.
+ */
+static void WAVEOUT_SinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
+    WINE_WAVEINST* wwo = (WINE_WAVEINST*)userdata;
+    if (!eol && i) {
+	for (wwo->volume.channels = 0; wwo->volume.channels != i->volume.channels; wwo->volume.channels++)
+	    wwo->volume.values[wwo->volume.channels] = i->volume.values[wwo->volume.channels];
+	pa_threaded_mainloop_signal(PULSE_ml, 0);
+    }
+}
+
+/*======================================================================*
+ *                  "Low level" WAVE OUT implementation			*
+ *======================================================================*/
+
+/**************************************************************************
+ * 			wodPlayer_NotifyClient			[internal]
+ */
+static DWORD wodPlayer_NotifyClient(WINE_WAVEINST* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2) {
+    TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2);
+
+    switch (wMsg) {
+    case WOM_OPEN:
+    case WOM_CLOSE:
+    case WOM_DONE:
+	if (wwo->wFlags != DCB_NULL &&
+	    !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, (HDRVR)wwo->waveDesc.hWave,
+			    wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2)) {
+	    WARN("can't notify client !\n");
+	    return MMSYSERR_ERROR;
+	}
+	break;
+    default:
+	FIXME("Unknown callback message %u\n", wMsg);
+        return MMSYSERR_INVALPARAM;
+    }
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodPlayer_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.
+ */
+static void wodPlayer_BeginWaveHdr(WINE_WAVEINST* wwo, LPWAVEHDR lpWaveHdr) {
+    wwo->lpPlayPtr = lpWaveHdr;
+
+    if (!lpWaveHdr) return;
+
+    if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) {
+	if (wwo->lpLoopPtr) {
+	    WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
+	} else {
+            TRACE("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;
+}
+
+/**************************************************************************
+ * 				wodPlayer_PlayPtrNext	        [internal]
+ *
+ * Advance the play pointer to the next waveheader, looping if required.
+ */
+static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEINST* wwo) {
+    LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
+
+    wwo->dwPartialOffset = 0;
+    if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) {
+	/* We're at the end of a loop, loop if required */
+	if (--wwo->dwLoops > 0) {
+	    wwo->lpPlayPtr = wwo->lpLoopPtr;
+	} else {
+	    /* Handle overlapping loops correctly */
+	    if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
+		FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
+		/* 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;
+            wodPlayer_BeginWaveHdr(wwo, lpWaveHdr);
+	}
+    } else {
+	/* We're not in a loop.  Advance to the next wave header */
+	wodPlayer_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
+    }
+
+    return lpWaveHdr;
+}
+
+/**************************************************************************
+ * 				wodPlayer_CheckReleasing	[internal]
+ *
+ * Check to make sure that playback has not stalled
+ */
+static void wodPlayer_CheckReleasing(WINE_WAVEINST *wwo) {
+    LPWAVEHDR lpWaveHdr = wwo->lpQueuePtr;
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    if (!wwo->timing_info->playing && !wwo->is_releasing && lpWaveHdr && !wwo->lpPlayPtr && wwo->state == WINE_WS_PLAYING) {
+
+	/* Try and adjust the buffer attributes so there is less latency.
+	 * Because of bugs this call does not work on older servers. Once
+	 * new version of pulseaudio become ubiquitous we will drop support for
+	 * versions before 0.9.15 because they have too many bugs.*/
+	if (wwo->buffer_attr.tlength == 0 &&
+	    pa_context_get_server_protocol_version(PULSE_context) >= 15) {
+	    wwo->buffer_attr.tlength = wwo->timing_info->write_index;
+	    wwo->buffer_attr.prebuf = (uint32_t) -1;
+	    wwo->buffer_attr.minreq = (uint32_t) -1;
+	    wwo->buffer_attr.maxlength = (uint32_t) -1;
+	    WARN("Asking for new buffer tlength of %ums (%u bytes)\n", (unsigned int)(pa_bytes_to_usec(wwo->buffer_attr.tlength, &wwo->sample_spec)/1000), wwo->buffer_attr.tlength);
+	    pa_stream_set_buffer_attr(wwo->stream, &wwo->buffer_attr, PULSE_StreamSuccessCallback, wwo);
+	} else {
+	    /* Fake playback start earlier, introducing unknown latency */
+	    pa_gettimeofday(&wwo->started_releasing);
+	    wwo->is_releasing = TRUE;
+	    wwo->releasing_offset = wwo->lpQueuePtr->reserved;
+	    WARN("Starting to release early.\n");
+	}
+    }
+    pa_threaded_mainloop_unlock(PULSE_ml);
+}
+
+/**************************************************************************
+ * 				wodPlayer_NotifyCompletions	[internal]
+ *
+ * Notifies and remove from queue all wavehdrs which have been played to
+ * the speaker based on a reference time of (theoretical) playback start. If
+ * force is true, we notify all wavehdrs and remove them all from the queue
+ * even if they are unplayed or part of a loop. We return the time to wait
+ * until the next wavehdr needs to be freed, or INFINITE if there are no more
+ * wavehdrs. We use timevals rather than the stream position data that pulse
+ * gives us as realeasing needs to be constant and smooth for good application
+ * behaviour.
+ */
+static DWORD wodPlayer_NotifyCompletions(WINE_WAVEINST* wwo, BOOL force) {
+    LPWAVEHDR	lpWaveHdr = wwo->lpQueuePtr;
+    pa_usec_t	time;
+    pa_usec_t	wait;
+
+    time = pa_bytes_to_usec(wwo->releasing_offset, &wwo->sample_spec);
+    if (wwo->is_releasing)
+	time += pa_timeval_age(&wwo->started_releasing);
+
+    while (lpWaveHdr) {
+	if (!force) {
+	    /* 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
+	     */
+            if (lpWaveHdr == wwo->lpPlayPtr) { TRACE("play %p\n", lpWaveHdr); return INFINITE; }
+	    if (lpWaveHdr == wwo->lpLoopPtr) { TRACE("loop %p\n", lpWaveHdr); return INFINITE; }
+
+	    /* See if this data has been played, and if not, return when it will have been */
+	    wait = pa_bytes_to_usec(lpWaveHdr->reserved + lpWaveHdr->dwBufferLength, &wwo->sample_spec);
+	    if (wait >= time) {
+		wait = ((wait - time) + 999) / 1000;
+	        return wait ?: 1;
+	    }
+	}
+
+	/* return the wavehdr */
+	wwo->lpQueuePtr = lpWaveHdr->lpNext;
+	lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+	lpWaveHdr->dwFlags |= WHDR_DONE;
+
+	wodPlayer_NotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0);
+	lpWaveHdr = wwo->lpQueuePtr;
+    }
+    /* No more wavehdrs */
+    TRACE("Empty queue\n");
+    return INFINITE;
+}
+
+/**************************************************************************
+ * 			     wodPlayer_WriteMax			[internal]
+ *
+ * Write either how much free space or how much data we have, depending on
+ * which is less
+ */
+static int wodPlayer_WriteMax(WINE_WAVEINST *wwo, size_t *space) {
+    LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
+    size_t toWrite = min(lpWaveHdr->dwBufferLength - wwo->dwPartialOffset, *space);
+
+    if (toWrite > 0 && pa_stream_write(wwo->stream, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite, NULL, 0, PA_SEEK_RELATIVE) >= 0)
+	TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr, wwo->dwPartialOffset, lpWaveHdr->dwBufferLength);
+    else
+        return 0;
+
+    /* Check to see if we wrote all of the wavehdr */
+    if ((wwo->dwPartialOffset += toWrite) >= lpWaveHdr->dwBufferLength)
+	wodPlayer_PlayPtrNext(wwo);
+	
+    *space -= toWrite;
+
+    return toWrite;
+}
+
+/**************************************************************************
+ * 			     wodPlayer_Feed			[internal]
+ *
+ * Feed as much sound data as we can into pulse using wodPlayer_WriteMax.
+ * size_t space _must_ have come from either pa_stream_writable_size() or
+ * the value from a stream write callback, as if it isn't you run the risk
+ * of a buffer overflow in which audio data will be lost.
+ */
+static void wodPlayer_Feed(WINE_WAVEINST* wwo, size_t space) {
+
+    /* No more room... no need to try to feed */
+    if (space == 0) return;
+
+    if (!wwo->stream || !PULSE_context || 
+        pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
+        pa_stream_get_state(wwo->stream) != PA_STREAM_READY)
+        return;
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    /* Feed from a partial wavehdr */
+    if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0)
+        wodPlayer_WriteMax(wwo, &space);
+
+    /* Feed wavehdrs until we run out of wavehdrs or buffer space */
+    if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) {
+        do {
+            wwo->lpPlayPtr->reserved = wwo->timing_info->write_index;
+        } while (wodPlayer_WriteMax(wwo, &space) > 0 && wwo->lpPlayPtr && space > 0);
+    }
+    pa_threaded_mainloop_unlock(PULSE_ml);
+}
+
+/**************************************************************************
+ * 				wodPlayer_Reset			[internal]
+ *
+ * wodPlayer helper. Resets current output stream.
+ */
+static void wodPlayer_Reset(WINE_WAVEINST* wwo) {
+    enum win_wm_message	    msg;
+    DWORD		    param;
+    HANDLE		    ev;
+
+    TRACE("(%p)\n", wwo);
+
+    /* remove any buffer */
+    wodPlayer_NotifyCompletions(wwo, TRUE);
+
+    wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
+    if (wwo->state != WINE_WS_PAUSED)
+	wwo->state = WINE_WS_STOPPED;
+    wwo->dwPartialOffset = 0;
+
+    if (!wwo->stream ||
+        !PULSE_context ||
+        pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
+        pa_stream_get_state(wwo->stream) != PA_STREAM_READY) {
+	return;
+    }
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+
+    /* flush the output buffer of written data*/
+    PULSE_WaitForOperation(pa_stream_flush(wwo->stream, PULSE_StreamSuccessCallback, NULL));
+
+    /* Reset the written byte count as some data may have been flushed */
+    if (wwo->timing_info->write_index_corrupt)
+	PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
+    wwo->releasing_offset = wwo->last_reset = wwo->timing_info->write_index;
+    if (wwo->is_releasing)
+        pa_gettimeofday(&wwo->started_releasing);
+
+    /* return all pending headers in queue */ 
+    EnterCriticalSection(&wwo->msgRing.msg_crst);
+    while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
+	if (msg != WINE_WM_HEADER) {
+	    SetEvent(ev);
+	    continue;
+	}
+	((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE;
+	((LPWAVEHDR)param)->dwFlags |= WHDR_DONE;
+	wodPlayer_NotifyClient(wwo, WOM_DONE, param, 0);
+    }
+    PULSE_ResetRingMessage(&wwo->msgRing);
+    LeaveCriticalSection(&wwo->msgRing.msg_crst); 
+
+    pa_threaded_mainloop_unlock(PULSE_ml);
+}
+
+/**************************************************************************
+ * 				wodPlayer_Underrun		[internal]
+ *
+ * wodPlayer helper. Deal with a stream underrun.
+ */
+static void wodPlayer_Underrun(WINE_WAVEINST* wwo) {
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    
+    /* Ask for a timing update */
+    PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
+
+    /* See if we recovered while the message was waiting in the queue */
+    if (wwo->timing_info->playing) {
+	TRACE("False alarm\n");
+        pa_threaded_mainloop_unlock(PULSE_ml);
+	return;
+    }
+
+    if (wwo->lpPlayPtr) {
+    	size_t space;
+
+	TRACE("There is queued data. Trying to recover.\n");	
+
+        /* Ask for a timing update */
+        if (wwo->timing_info->write_index_corrupt)
+	    PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
+	space = pa_stream_writable_size(wwo->stream);
+	wodPlayer_Feed(wwo, space);
+    }
+    WARN("Stream underrun!\n");
+    wwo->is_releasing = FALSE;
+    wwo->releasing_offset = wwo->timing_info->write_index;
+    pa_threaded_mainloop_unlock(PULSE_ml);
+}
+
+
+/**************************************************************************
+ * 		      wodPlayer_ProcessMessages			[internal]
+ */
+static DWORD wodPlayer_ProcessMessages(WINE_WAVEINST* wwo) {
+    LPWAVEHDR           lpWaveHdr;
+    enum win_wm_message	msg;
+    DWORD		param, msgcount = 0;
+    HANDLE		ev;
+
+    while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
+	TRACE("Received %s %x\n", PULSE_getCmdString(msg), param);
+	msgcount++;
+
+	switch (msg) {
+	case WINE_WM_PAUSING:
+	    wwo->state = WINE_WS_PAUSED;
+
+	    pa_threaded_mainloop_lock(PULSE_ml);
+	    PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 1, PULSE_StreamSuccessCallback, wwo));
+	    /* save how far we are, as releasing will restart from here */
+	    if (wwo->is_releasing)
+		wwo->releasing_offset = wwo->timing_info->write_index;
+	    wwo->is_releasing = FALSE;
+	    pa_threaded_mainloop_unlock(PULSE_ml);
+	    SetEvent(ev);
+	    break;
+
+	case WINE_WM_RESTARTING:
+            if (wwo->state == WINE_WS_PAUSED) {
+		wwo->state = WINE_WS_PLAYING;
+		pa_threaded_mainloop_lock(PULSE_ml);
+		PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 0, PULSE_StreamSuccessCallback, wwo));
+		/* If the serverside buffer was near full before pause, we need to
+		 * have space to write soon, so force playback start */
+		PULSE_WaitForOperation(pa_stream_trigger(wwo->stream, PULSE_StreamSuccessCallback, wwo));
+		pa_threaded_mainloop_unlock(PULSE_ml);
+	    }
+	    SetEvent(ev);
+	    break;
+
+	case WINE_WM_HEADER:
+	    lpWaveHdr = (LPWAVEHDR)param;
+	    /* insert buffer at the end of queue */
+	    {
+		LPWAVEHDR*	wh;
+		for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
+		*wh = lpWaveHdr;
+	    }
+
+            if (!wwo->lpPlayPtr)
+	        wodPlayer_BeginWaveHdr(wwo,lpWaveHdr);
+	    if (wwo->state == WINE_WS_STOPPED)
+		wwo->state = WINE_WS_PLAYING;
+
+	    wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream));
+	    SetEvent(ev);
+	    break;
+
+	case WINE_WM_RESETTING:
+	    wodPlayer_Reset(wwo);
+	    SetEvent(ev);
+	    break;
+
+        case WINE_WM_BREAKLOOP:
+            if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL)
+                /* ensure exit at end of current loop */
+                wwo->dwLoops = 1;
+	    SetEvent(ev);
+            break;
+
+	case WINE_WM_FEED: /* Sent by the pulse thread */
+	    wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream));
+	    SetEvent(ev);
+	    break;
+
+	case WINE_WM_XRUN: /* Sent by the pulse thread */
+	    if ((DWORD)param == 1) {
+		ERR("Buffer overflow!\n");
+		pa_threaded_mainloop_lock(PULSE_ml);
+		PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, wwo));
+		wwo->is_releasing = FALSE;
+		pa_threaded_mainloop_unlock(PULSE_ml);
+	    } else
+		wodPlayer_Underrun(wwo);
+	    SetEvent(ev);
+	    break;
+
+	case WINE_WM_STARTING: /* Sent by the pulse thread */
+	    /* Start releasing wavehdrs if we haven't already */
+	    if (!wwo->is_releasing) {
+		wwo->is_releasing = TRUE;
+		pa_gettimeofday(&wwo->started_releasing);
+	    }
+	    SetEvent(ev);
+	    break;
+
+	case WINE_WM_CLOSING: /* If param = 1, close because of a failure */
+	    wwo->hThread = NULL;
+	    if ((DWORD)param == 1) {
+		/* If we are here, the stream has failed */
+		wwo->state = WINE_WS_FAILED;
+		SetEvent(ev);
+		PULSE_DestroyRingMessage(&wwo->msgRing);
+		wodPlayer_NotifyCompletions(wwo, TRUE);
+		wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L);
+		wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
+		pa_threaded_mainloop_lock(PULSE_ml);
+		pa_stream_disconnect(wwo->stream);
+		pa_threaded_mainloop_unlock(PULSE_ml);
+		TRACE("Thread exiting because of failure.\n");
+		ExitThread(1);
+		/* Stream instance will get de-refferenced upon close */
+	    }
+	    wwo->state = WINE_WS_CLOSED;
+	    /* sanity check: this should not happen since the device must have been reset before */
+	    if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
+	    SetEvent(ev);
+	    TRACE("Thread exiting.\n");
+	    ExitThread(0);
+	    /* shouldn't go here */
+
+	default:
+	    FIXME("unknown message %d\n", msg);
+	    break;
+	}
+    }
+
+    return msgcount;
+}
+
+/**************************************************************************
+ * 				wodPlayer			[internal]
+ *
+ *  The thread which is responsible for returning WaveHdrs via DriverCallback,
+ *  the writing of queued WaveHdrs, and all pause / reset stream management.
+ */
+static DWORD CALLBACK wodPlayer(LPVOID lpParam) {
+    WINE_WAVEINST *wwo = (WINE_WAVEINST*)lpParam;
+    DWORD         dwSleepTime = INFINITE;
+
+    wwo->state = WINE_WS_STOPPED;
+    SetEvent(wwo->hStartUpEvent);
+
+/* Wait for the shortest time before an action is required.  If there are no
+ * pending actions, wait forever for a command. */
+    for (;;) {
+	TRACE("Waiting %u ms\n", dwSleepTime);
+        PULSE_WaitRingMessage(&wwo->msgRing, dwSleepTime);
+
+/* If no messages were processed during the timeout it might be because audio
+ * is not flowing yet, so check. */
+	if (wodPlayer_ProcessMessages(wwo) == 0)
+	    wodPlayer_CheckReleasing(wwo);
+
+/* If there is audio playing, return headers and get next timeout */
+	if (wwo->state == WINE_WS_PLAYING)
+	    dwSleepTime = wodPlayer_NotifyCompletions(wwo, FALSE);
+	else
+	    dwSleepTime = INFINITE;
+    }
+}
+
+/**************************************************************************
+ *                              wodOpen                         [internal]
+ */
+static DWORD wodOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) {
+    WINE_WAVEDEV *wdo;
+    WINE_WAVEINST *wwo = NULL;
+    DWORD ret = MMSYSERR_NOERROR;
+    pa_stream_flags_t stream_flags;
+
+    TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
+    if (lpDesc == NULL) {
+	WARN("Invalid Parameter !\n");
+	return MMSYSERR_INVALPARAM;
+    }
+
+    if (wDevID >= PULSE_WodNumDevs) {
+	WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+	return MMSYSERR_BADDEVICEID;
+    }
+    wdo = &WOutDev[wDevID];
+
+    wwo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST));
+    if (!wwo) return MMSYSERR_NOMEM;
+    *lpdwUser = (DWORD)wwo;
+
+    /* check to see if format is supported and make pa_sample_spec struct */
+    if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwo->sample_spec)) {
+	WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	ret = WAVERR_BADFORMAT;
+	goto exit;
+    }
+
+    if (TRACE_ON(wave)) {
+    	char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
+    	pa_sample_spec_snprint(t, sizeof(t), &wwo->sample_spec);
+    	TRACE("Sample spec '%s'\n", t);
+    }
+
+    if (dwFlags & WAVE_FORMAT_QUERY) {
+	TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	ret = MMSYSERR_NOERROR;
+	goto exit;
+    }
+
+    wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
+    wwo->waveDesc = *lpDesc;
+    PULSE_InitRingMessage(&wwo->msgRing);
+
+    wwo->stream = pa_stream_new(PULSE_context, "WaveOut", &wwo->sample_spec, NULL);
+    /* If server doesn't support sample_spec, we will error out here */
+    if (!wwo->stream) {
+        ret = WAVERR_BADFORMAT;
+	goto exit;
+    }
+
+    pa_stream_set_write_callback	(wwo->stream, WAVEOUT_StreamRequestCallback,	wwo);
+    pa_stream_set_state_callback	(wwo->stream, PULSE_StreamStateCallback,	wwo);
+    pa_stream_set_underflow_callback	(wwo->stream, PULSE_StreamUnderflowCallback,	wwo);
+    pa_stream_set_overflow_callback	(wwo->stream, PULSE_StreamOverflowCallback,	wwo);
+    pa_stream_set_moved_callback	(wwo->stream, PULSE_StreamMovedCallback,	wwo);
+    pa_stream_set_suspended_callback	(wwo->stream, PULSE_StreamSuspendedCallback,	wwo);
+
+    stream_flags = PA_STREAM_AUTO_TIMING_UPDATE;
+
+#if PA_API_VERSION >= 12
+    if (pa_context_get_server_protocol_version(PULSE_context) >= 15) {
+	pa_stream_set_started_callback(wwo->stream, WAVEOUT_StreamStartedCallback, wwo);
+	stream_flags |= PA_STREAM_ADJUST_LATENCY;
+    } else
+#endif
+    {
+	pa_stream_set_latency_update_callback(wwo->stream, WAVEOUT_StreamTimingInfoUpdateCallback, wwo);
+    }
+
+    TRACE("Connecting stream for playback on %s.\n", wdo->device_name);
+    pa_threaded_mainloop_lock(PULSE_ml);
+    pa_stream_connect_playback(wwo->stream, wdo->device_name, NULL, stream_flags, NULL, NULL);
+
+    for (;;) {
+        pa_context_state_t cstate = pa_context_get_state(PULSE_context);
+        pa_stream_state_t sstate = pa_stream_get_state(wwo->stream);
+
+	if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED ||
+	    sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) {
+	    ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
+	    pa_threaded_mainloop_unlock(PULSE_ml);
+	    ret = MMSYSERR_NODRIVER;
+	    goto exit;
+	}
+
+	if (sstate == PA_STREAM_READY)
+	    break;
+
+	pa_threaded_mainloop_wait(PULSE_ml);
+    }
+    TRACE("(%p)->stream connected for playback.\n", wwo);
+
+    PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
+
+    wwo->timing_info = pa_stream_get_timing_info(wwo->stream);
+    assert(wwo->timing_info);
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    wwo->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
+    wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)wwo, 0, &(wwo->dwThreadID));
+    if (wwo->hThread)
+        SetThreadPriority(wwo->hThread, THREAD_PRIORITY_TIME_CRITICAL);
+    else {
+        ERR("Thread creation for the wodPlayer failed!\n");
+        ret = MMSYSERR_NOMEM;
+	goto exit;
+    }
+    WaitForSingleObject(wwo->hStartUpEvent, INFINITE);
+    CloseHandle(wwo->hStartUpEvent);
+    wwo->hStartUpEvent = INVALID_HANDLE_VALUE;
+
+
+    return wodPlayer_NotifyClient (wwo, WOM_OPEN, 0L, 0L);
+
+exit:
+    if (!wwo)
+	return ret;
+
+    if (wwo->hStartUpEvent != INVALID_HANDLE_VALUE)
+	CloseHandle(wwo->hStartUpEvent);
+
+    if (wwo->msgRing.ring_buffer_size > 0)
+	    PULSE_DestroyRingMessage(&wwo->msgRing);
+
+    if (wwo->stream) {
+        if (pa_stream_get_state(wwo->stream) == PA_STREAM_READY)
+	    pa_stream_disconnect(wwo->stream);
+	pa_stream_unref(wwo->stream);
+	wwo->stream = NULL;
+    }
+    HeapFree(GetProcessHeap(), 0, wwo);
+
+    return ret;
+}
+
+/**************************************************************************
+ *                              wodClose                        [internal]
+ */
+static DWORD wodClose(WINE_WAVEINST *wwo) {
+    DWORD ret;
+
+    TRACE("(%p);\n", wwo);
+    if (!wwo) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    if (wwo->state != WINE_WS_FAILED) {
+	if (wwo->lpQueuePtr && wwo->lpPlayPtr) {
+	    WARN("buffers still playing !\n");
+	    return WAVERR_STILLPLAYING;
+	}
+
+	pa_threaded_mainloop_lock(PULSE_ml);
+	PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, NULL));
+	pa_stream_disconnect(wwo->stream);
+	pa_threaded_mainloop_unlock(PULSE_ml);
+
+	if (wwo->hThread != INVALID_HANDLE_VALUE)
+	    PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE);
+
+	PULSE_DestroyRingMessage(&wwo->msgRing);
+    }
+
+    if (wwo->stream)
+	pa_stream_unref(wwo->stream);
+    ret = wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L);
+
+    HeapFree(GetProcessHeap(), 0, wwo);
+
+    return ret;
+}
+
+/**************************************************************************
+ *                              wodWrite                        [internal]
+ */
+static DWORD wodWrite(WINE_WAVEINST *wwo, LPWAVEHDR lpWaveHdr, DWORD dwSize) {
+    if (!wwo || wwo->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
+	return WAVERR_UNPREPARED;
+
+    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
+	return WAVERR_STILLPLAYING;
+
+    lpWaveHdr->dwFlags &= ~WHDR_DONE;
+    lpWaveHdr->dwFlags |= WHDR_INQUEUE;
+    lpWaveHdr->lpNext = 0;
+    lpWaveHdr->reserved = 0;
+
+    PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodPause                        [internal]
+ */
+static DWORD wodPause(WINE_WAVEINST *wwo) {
+    if (!wwo ||	wwo->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_PAUSING, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodGetPosition                  [internal]
+ */
+static DWORD wodGetPosition(WINE_WAVEINST *wwo, LPMMTIME lpTime, DWORD uSize) {
+    pa_usec_t	time, time_temp;
+    size_t	bytes, bytes_temp;
+
+    if (!wwo ||	wwo->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    if (lpTime == NULL)	return MMSYSERR_INVALPARAM;
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    if (wwo->timing_info->read_index_corrupt || wwo->timing_info->write_index_corrupt)
+	PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
+
+    bytes = wwo->timing_info->read_index;
+    time = pa_bytes_to_usec(bytes, &wwo->sample_spec);
+
+    if (wwo->timing_info->playing) {
+	bytes += ((pa_timeval_age(&wwo->timing_info->timestamp) / 1000) * pa_bytes_per_second(&wwo->sample_spec)) / 1000;
+	bytes_temp = (time_temp = wwo->timing_info->sink_usec + wwo->timing_info->transport_usec)/1000 * pa_bytes_per_second(&wwo->sample_spec)/1000;
+	time += pa_timeval_age(&wwo->timing_info->timestamp);
+    } else {
+	time = 0;
+	time_temp = 0;
+	bytes_temp = 0;
+    }
+
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+    if (wwo->last_reset < bytes) {
+	time -= pa_bytes_to_usec(wwo->last_reset, &wwo->sample_spec);
+	bytes -= wwo->last_reset;
+    }
+    if (bytes > bytes_temp)
+	bytes -= bytes_temp;
+    else
+	bytes = 0;
+
+    if (time > time_temp)
+	time -= time_temp;
+    else
+	time = 0;
+
+    bytes -= bytes % pa_frame_size(&wwo->sample_spec);
+    time /= 1000; /* In milliseconds now */
+
+    switch (lpTime->wType) {
+    case TIME_SAMPLES:
+        lpTime->u.sample = bytes / pa_frame_size(&wwo->sample_spec);
+	TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample);
+        break;
+    case TIME_MS:
+        lpTime->u.ms = time;
+	TRACE("TIME_MS=%u\n", lpTime->u.ms);
+	break;
+    case TIME_SMPTE:
+        lpTime->u.smpte.fps = 30;
+        lpTime->u.smpte.sec = time/1000;
+        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.frame = time / lpTime->u.smpte.fps * 1000;
+        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 = bytes;
+        TRACE("TIME_BYTES=%u\n", lpTime->u.cb);
+        break;
+    }
+    return MMSYSERR_NOERROR;
+}
+/**************************************************************************
+ *                              wodBreakLoop                    [internal]
+ */
+static DWORD wodBreakLoop(WINE_WAVEINST *wwo) {
+    if (!wwo ||	wwo->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_BREAKLOOP, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodGetDevCaps                   [internal]
+ */
+static DWORD wodGetDevCaps(DWORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize) {
+    TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
+
+    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
+
+    if (wDevID >= PULSE_WodNumDevs) {
+	TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    memcpy(lpCaps, &(WOutDev[wDevID].caps.out), min(dwSize, sizeof(*lpCaps)));
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodGetNumDevs                   [internal]
+ * Context-sanity check here, as if we respond with 0, WINE will move on
+ * to the next waveout driver.
+ */
+static DWORD wodGetNumDevs() {
+    if (!PULSE_ml || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY)
+	return 0;
+
+    return PULSE_WodNumDevs;
+}
+
+/**************************************************************************
+ *                              wodGetVolume                    [internal]
+ */
+static DWORD wodGetVolume(WINE_WAVEINST *wwo, LPDWORD lpdwVol) {
+    float   value1, value2;
+    DWORD   wleft, wright;
+
+    if (!wwo ||	wwo->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    TRACE("(%p, %p);\n", wwo, lpdwVol);
+
+    if (lpdwVol == NULL)
+	return MMSYSERR_NOTENABLED;
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    if (wwo->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY &&
+	pa_stream_get_state(wwo->stream) == PA_STREAM_READY) {
+	PULSE_WaitForOperation(pa_context_get_sink_input_info(PULSE_context, pa_stream_get_index(wwo->stream), WAVEOUT_SinkInputInfoCallback, wwo));
+    }
+    pa_threaded_mainloop_unlock(PULSE_ml);
+
+
+    if (wwo->volume.channels == 2) {
+	value1 = pa_sw_volume_to_dB(wwo->volume.values[0]);
+	value2 = pa_sw_volume_to_dB(wwo->volume.values[1]);
+    } else {
+	value1 = pa_sw_volume_to_dB(pa_cvolume_avg(&wwo->volume));
+	value2 = 0;
+    }
+
+    if (value1 < -60)
+	wleft = 0;
+    else
+
+    if (value2 < -60)
+	wright = 0;
+    else
+	wright = 0xFFFFl - ((value2 / -60)*(float)0xFFFFl);
+
+    if (wleft > 0xFFFFl)
+	wleft = 0xFFFFl;
+    if (wright > 0xFFFFl)
+	wright = 0xFFFFl;
+
+    *lpdwVol = (WORD)wleft + (WORD)(wright << 16);
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodSetVolume                    [internal]
+ */
+static DWORD wodSetVolume(WINE_WAVEINST *wwo, DWORD dwParam1) {
+    double value1, value2;
+
+    TRACE("(%p, %08X);\n", wwo, dwParam1);
+    if (!wwo ||	wwo->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    /* waveOut volumes are /supposed/ to be logarithmic */
+    value1 = LOWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - LOWORD(dwParam1))/0xFFFFl) * -60.0;
+    value2 = HIWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - HIWORD(dwParam1))/0xFFFFl) * -60.0;
+
+    if (wwo->sample_spec.channels == 2) {
+	wwo->volume.channels = 2;
+	wwo->volume.values[0] = pa_sw_volume_from_dB(value1);
+	wwo->volume.values[1] = pa_sw_volume_from_dB(value2);
+    } else {
+	if (value1 != value2) FIXME("Non-stereo streams can't pan!\n");
+        wwo->volume.channels = wwo->sample_spec.channels;
+	pa_cvolume_set(&wwo->volume, wwo->volume.channels, pa_sw_volume_from_dB(max(value1, value2)));
+    }
+
+    if (TRACE_ON(wave)) {
+	char s[PA_CVOLUME_SNPRINT_MAX];
+	pa_cvolume_snprint(s, PA_CVOLUME_SNPRINT_MAX, &wwo->volume);
+	TRACE("%s\n", s);
+    }
+
+    pa_threaded_mainloop_lock(PULSE_ml);
+    if (!wwo->stream || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
+	pa_stream_get_state(wwo->stream) != PA_STREAM_READY || !pa_cvolume_valid(&wwo->volume)) {
+	pa_threaded_mainloop_unlock(PULSE_ml);
+	return MMSYSERR_NOERROR;
+    }
+
+    PULSE_WaitForOperation(pa_context_set_sink_input_volume(PULSE_context,
+	    pa_stream_get_index(wwo->stream), &wwo->volume,
+	    PULSE_ContextSuccessCallback, wwo));
+    pa_threaded_mainloop_unlock(PULSE_ml);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodRestart                      [internal]
+ */
+static DWORD wodRestart(WINE_WAVEINST *wwo) {
+    if (!wwo ||	wwo->state == WINE_WS_FAILED) {
+        WARN("Stream instance invalid.\n");
+        return MMSYSERR_INVALHANDLE;
+    }
+
+    if (wwo->state == WINE_WS_PAUSED)
+	PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESTARTING, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodReset                        [internal]
+ */
+static DWORD wodReset(WINE_WAVEINST *wwo) {
+    if (!wwo ||	wwo->state == WINE_WS_FAILED) {
+	WARN("Stream instance invalid.\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESETTING, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodDevInterfaceSize             [internal]
+ */
+static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) {
+
+    *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, NULL, 0) * sizeof(WCHAR);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ *                              wodDevInterface                 [internal]
+ */
+static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) {
+    if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
+                                        NULL, 0 ) * sizeof(WCHAR))
+    {
+        MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
+                            dwParam1, dwParam2 / sizeof(WCHAR));
+	return MMSYSERR_NOERROR;
+    }
+    return MMSYSERR_INVALPARAM;
+}
+
+/**************************************************************************
+ * 				wodMessage (WINEPULSE.@)
+ */
+DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
+			    DWORD dwParam1, DWORD dwParam2) {
+/*
+    TRACE("(%u, %s, %08X, %08X, %08X);\n",
+	  wDevID, PULSE_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, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1,	dwParam2);
+    case WODM_CLOSE:	 	return wodClose		((WINE_WAVEINST*)dwUser);
+    case WODM_WRITE:	 	return wodWrite		((WINE_WAVEINST*)dwUser,    (LPWAVEHDR)dwParam1,		dwParam2);
+    case WODM_PAUSE:	 	return wodPause		((WINE_WAVEINST*)dwUser);
+    case WODM_GETPOS:	 	return wodGetPosition	((WINE_WAVEINST*)dwUser,    (LPMMTIME)dwParam1, 		dwParam2);
+    case WODM_BREAKLOOP: 	return wodBreakLoop     ((WINE_WAVEINST*)dwUser);
+    case WODM_PREPARE:	 	return MMSYSERR_NOTSUPPORTED;
+    case WODM_UNPREPARE: 	return MMSYSERR_NOTSUPPORTED;
+    case WODM_GETDEVCAPS:	return wodGetDevCaps	(wDevID, (LPWAVEOUTCAPSW)dwParam1,	dwParam2);
+    case WODM_GETNUMDEVS:	return wodGetNumDevs	();
+    case WODM_GETPITCH:	 	return MMSYSERR_NOTSUPPORTED;
+    case WODM_SETPITCH:	 	return MMSYSERR_NOTSUPPORTED;
+    case WODM_GETPLAYBACKRATE:	return MMSYSERR_NOTSUPPORTED; /* support if theoretically possible */
+    case WODM_SETPLAYBACKRATE:	return MMSYSERR_NOTSUPPORTED; /* since pulseaudio 0.9.8 */
+    case WODM_GETVOLUME:	return wodGetVolume	((WINE_WAVEINST*)dwUser, (LPDWORD)dwParam1);
+    case WODM_SETVOLUME:	return wodSetVolume	((WINE_WAVEINST*)dwUser, dwParam1);
+    case WODM_RESTART:		return wodRestart	((WINE_WAVEINST*)dwUser);
+    case WODM_RESET:		return wodReset		((WINE_WAVEINST*)dwUser);
+    case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize      (wDevID, (LPDWORD)dwParam1);
+    case DRV_QUERYDEVICEINTERFACE:     return wodDevInterface          (wDevID, (PWCHAR)dwParam1, dwParam2);
+    case DRV_QUERYDSOUNDIFACE:	return wodDsCreate	(wDevID, (PIDSDRIVER*)dwParam1);
+    case DRV_QUERYDSOUNDDESC:	return wodDsDesc	(wDevID, (PDSDRIVERDESC)dwParam1);
+    default:
+	FIXME("unknown message %d!\n", wMsg);
+    }
+    return MMSYSERR_NOTSUPPORTED;
+}
+
+#else /* !HAVE_PULSEAUDIO */
+
+/**************************************************************************
+ * 				wodMessage (WINEPULSE.@)
+ */
+DWORD WINAPI PULSE_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
+			      DWORD dwParam1, DWORD dwParam2) {
+    FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID, wMsg, dwUser,
+          dwParam1, dwParam2);
+    return MMSYSERR_NOTENABLED;
+}
+
+#endif /* HAVE_PULSEAUDIO */
diff --git a/dlls/winepulse.drv/winepulse.drv.spec b/dlls/winepulse.drv/winepulse.drv.spec
new file mode 100644
index 0000000..1b49460
--- /dev/null
+++ b/dlls/winepulse.drv/winepulse.drv.spec
@@ -0,0 +1,3 @@
+@ stdcall -private DriverProc(long long long long long long) PULSE_DriverProc
+@ stdcall -private wodMessage(long long long long long long) PULSE_wodMessage
+@ stdcall -private widMessage(long long long long long long) PULSE_widMessage
diff --git a/dlls/winepulse.drv/winepulse.h b/dlls/winepulse.drv/winepulse.h
new file mode 100644
index 0000000..6270e04
--- /dev/null
+++ b/dlls/winepulse.drv/winepulse.h
@@ -0,0 +1,225 @@
+/* Definitions for PulseAudio Wine Driver
+ *
+ * Copyright	2009 Arthur Taylor <theycallhimart@gmail.com>
+ *
+ * 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
+ */
+
+#ifndef __WINE_CONFIG_H
+# error You must include config.h to use this header
+#endif
+
+#if defined(HAVE_PULSEAUDIO) && !defined(__WINEPULSE_H)
+#define __WINEPULSE_H
+
+#include "mmreg.h"
+#include "dsound.h"
+#include "dsdriver.h"
+
+#include "ks.h"
+#include "ksmedia.h"
+#include "ksguid.h"
+
+#include <pulse/pulseaudio.h>
+
+/* 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         1
+#define WINE_WS_PAUSED          2
+#define WINE_WS_STOPPED         3
+#define WINE_WS_CLOSED          4
+#define WINE_WS_FAILED          5
+
+#define PULSE_ALL_FORMATS \
+        WAVE_FORMAT_1M08 |	/* Mono	    11025Hz 8-bit  */\
+        WAVE_FORMAT_1M16 |	/* Mono	    11025Hz 16-bit */\
+        WAVE_FORMAT_1S08 |	/* Stereo   11025Hz 8-bit  */\
+        WAVE_FORMAT_1S16 |	/* Stereo   11025Hz 16-bit */\
+        WAVE_FORMAT_2M08 |	/* Mono	    22050Hz 8-bit  */\
+        WAVE_FORMAT_2M16 |	/* Mono	    22050Hz 16-bit */\
+        WAVE_FORMAT_2S08 |	/* Stereo   22050Hz 8-bit  */\
+	WAVE_FORMAT_2S16 |	/* Stereo   22050Hz 16-bit */\
+        WAVE_FORMAT_4M08 |	/* Mono	    44100Hz 8-bit  */\
+	WAVE_FORMAT_4M16 |	/* Mono	    44100Hz 16-bit */\
+        WAVE_FORMAT_4S08 |	/* Stereo   44100Hz 8-bit  */\
+	WAVE_FORMAT_4S16 |	/* Stereo   44100Hz 16-bit */\
+        WAVE_FORMAT_48M08 |	/* Mono	    48000Hz 8-bit  */\
+        WAVE_FORMAT_48S08 |	/* Stereo   48000Hz 8-bit  */\
+        WAVE_FORMAT_48M16 |	/* Mono	    48000Hz 16-bit */\
+        WAVE_FORMAT_48S16 |	/* Stereo   48000Hz 16-bit */\
+	WAVE_FORMAT_96M08 |	/* Mono	    96000Hz 8-bit  */\
+	WAVE_FORMAT_96S08 |	/* Stereo   96000Hz 8-bit  */\
+        WAVE_FORMAT_96M16 |	/* Mono	    96000Hz 16-bit */\
+	WAVE_FORMAT_96S16	/* Stereo   96000Hz 16-bit */
+
+/* events to be sent to device */
+enum win_wm_message {
+    WINE_WM_PAUSING = WM_USER + 1, WINE_WM_RESTARTING, WINE_WM_RESETTING, WINE_WM_HEADER,
+    WINE_WM_BREAKLOOP, WINE_WM_CLOSING, WINE_WM_STARTING, WINE_WM_STOPPING, WINE_WM_XRUN, WINE_WM_FEED
+};
+
+typedef struct {
+    enum win_wm_message 	msg;	/* message identifier */
+    DWORD	                param;  /* parameter for this message */
+    HANDLE	                hEvent;	/* if message is synchronous, handle of event for synchro */
+} PULSE_MSG;
+
+/* implement an in-process message ring for better performance
+ * (compared to passing thru the server)
+ * this ring will be used by the input (resp output) record (resp playback) routine
+ */
+typedef struct {
+    PULSE_MSG			* messages;
+    int                         ring_buffer_size;
+    int				msg_tosave;
+    int				msg_toget;
+/* Either pipe or event is used, but that is defined in pulse.c,
+ * since this is a global header we define both here */
+    int                         msg_pipe[2];
+    HANDLE                      msg_event;
+    CRITICAL_SECTION		msg_crst;
+} PULSE_MSG_RING;
+
+typedef struct WINE_WAVEDEV WINE_WAVEDEV;
+typedef struct WINE_WAVEINST WINE_WAVEINST;
+typedef struct IDsDriverImpl IDsDriverImpl;
+typedef struct IDsDriverBufferImpl IDsDriverBufferImpl;
+
+struct IDsDriverImpl {
+    /* IUnknown fields */
+    const IDsDriverVtbl *lpVtbl;
+    LONG    ref;
+
+    IDsDriverBufferImpl *primary;
+    UINT    wDevID;
+};
+
+struct IDsDriverBufferImpl {
+    const IDsDriverBufferVtbl	*lpVtbl;
+    IDsDriverImpl*		drv;
+    LONG			ref;
+    pa_stream			*stream;
+    pa_sample_spec		sample_spec;
+    pa_cvolume			volume;
+
+    PBYTE		buffer;
+    DWORD		buffer_length;
+    DWORD		buffer_read_offset;
+    DWORD		buffer_play_offset;
+    DWORD		fraglen;
+};
+
+/* Per-playback/record device */
+struct WINE_WAVEDEV {
+    char                interface_name[MAXPNAMELEN * 2];
+    char		*device_name;
+    pa_cvolume		volume;
+
+    union {
+        WAVEOUTCAPSW	out;
+	WAVEINCAPSW	in;
+    } caps;
+    
+    /* DirectSound stuff */
+    DSDRIVERDESC                ds_desc;
+    DSDRIVERCAPS                ds_caps;
+};
+
+/* Per-playback/record instance */
+struct WINE_WAVEINST {
+    volatile INT        state;		    /* one of the WINE_WS_ manifest constants */
+    WAVEOPENDESC        waveDesc;
+    WORD		wFlags;
+
+    /* PulseAudio specific data */
+    pa_stream		*stream;	    /* The PulseAudio stream */
+    const pa_timing_info *timing_info;	    /* The timing info structure for the stream */
+    pa_sample_spec	sample_spec;	    /* Sample spec of this stream / device */
+    pa_cvolume		volume;		    /* Software volume of the stream */
+    pa_buffer_attr	buffer_attr;	    /* Buffer attribute, may not be used */
+
+    /* waveIn / waveOut wavaHdr */
+    LPWAVEHDR		lpQueuePtr;	    /* Start of queued WAVEHDRs (waiting to be notified) */
+    LPWAVEHDR		lpPlayPtr;	    /* Start of not yet fully written 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 */
+
+    /* Virtual stream positioning */
+    DWORD		last_reset;	    /* When the last reset occured, as pa stream time isn't reset */
+    BOOL		is_releasing;	    /* Whether we are releasing wavehdrs */
+    struct timeval	started_releasing;  /* When wavehdr releasing started, for comparison to queued written wavehdrs */
+    DWORD		releasing_offset;   /* How much audio has been released prior when releasing started in this instance */
+
+    /* waveIn specific */
+    const void		*buffer;	    /* Pointer to the latest data fragment for recording streams */
+    DWORD		buffer_length;	    /* How large the latest data fragment is */
+    DWORD		buffer_read_offset; /* How far into latest data fragment we last read */
+
+    /* Thread communication and synchronization stuff */
+    HANDLE		hStartUpEvent;
+    HANDLE		hThread;
+    DWORD		dwThreadID;
+    PULSE_MSG_RING	msgRing;
+};
+
+/* We establish one context per instance, so make it global to the lib */
+pa_context		*PULSE_context;   /* Connection Context */
+pa_threaded_mainloop	*PULSE_ml;        /* PA Runtime information */
+
+/* WaveIn / WaveOut devices */
+WINE_WAVEDEV *WOutDev;
+WINE_WAVEDEV *WInDev;
+DWORD PULSE_WodNumDevs;
+DWORD PULSE_WidNumDevs;
+
+/* pulse.c */
+void	PULSE_WaitForOperation(pa_operation *o);
+void	PULSE_StreamSuccessCallback(pa_stream *s, int success, void *userdata);
+void	PULSE_StreamStateCallback(pa_stream *s, void *userdata);
+void	PULSE_StreamUnderflowCallback(pa_stream *s, void *userdata);
+void	PULSE_StreamOverflowCallback(pa_stream *s, void *userdata);
+void	PULSE_StreamSuspendedCallback(pa_stream *s, void *userdata);
+void	PULSE_StreamMovedCallback(pa_stream *s, void *userdata);
+void	PULSE_ContextSuccessCallback(pa_context *c, int success, void *userdata);
+BOOL	PULSE_SetupFormat(LPWAVEFORMATEX wf, pa_sample_spec *ss);
+int	PULSE_InitRingMessage(PULSE_MSG_RING* omr);
+int	PULSE_DestroyRingMessage(PULSE_MSG_RING* omr);
+void	PULSE_ResetRingMessage(PULSE_MSG_RING* omr);
+void	PULSE_WaitRingMessage(PULSE_MSG_RING* omr, DWORD sleep);
+int	PULSE_AddRingMessage(PULSE_MSG_RING* omr, enum win_wm_message msg, DWORD param, BOOL wait);
+int	PULSE_RetrieveRingMessage(PULSE_MSG_RING* omr, enum win_wm_message *msg, DWORD *param, HANDLE *hEvent);
+const char * PULSE_getCmdString(enum win_wm_message msg);
+
+/* dsoutput.c */
+DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv);
+DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc);
+#endif