Native NetBSD audio driver

Yorick Hardy yh at metroweb.co.za
Mon Feb 23 14:29:04 CST 2004


Thanks for the input.
Here is the patch again, with the bug specified below fixed.
I definitely think it would be useful to integrate at least the
winenbsd and wineoss drivers somehow. However, I think
a separate winenbsd driver will have to do in the mean time, and
provides a starting point to compare the drivers in case someone
else is also interested.

Changelog: Add the winenbsd audio driver using the native NetBSD audio system.

On Sun, Feb 22, 2004 at 06:15:47PM +0100, Eric Pouech wrote:
> I really think we're starting to create a real mess for the audio 
> drivers. It's always useful to have drivers access the HW at the lowest 
> level, so we should support NetBSD.
> However, if the iface is rather close to OSS, I'd rather suggest we try 
> to integrate all the drivers with the same features into a single piece 
> of driver
> 
> >+++ ./dlls/Makefile.in	Sat Feb 21 23:43:35 2004
> >@@ -1745,6 +1749,7 @@
> > winmm/winejack/winejack.drv$(DLLEXT): winmm/winejack
> > msacm/winemp3/winemp3.acm$(DLLEXT): msacm/winemp3
> > winmm/winenas/winenas.drv$(DLLEXT): winmm/winenas
> >+winmm/winenas/winenbsd.drv$(DLLEXT): winmm/winenbsd
>              ***
> this should be winenbsd

Kind regards,

-- 
Yorick Hardy

diff -urN /home/yorick/software/wine/wine/DEVELOPERS-HINTS ./DEVELOPERS-HINTS
--- /home/yorick/software/wine/wine/DEVELOPERS-HINTS	Sat Feb 14 19:19:56 2004
+++ ./DEVELOPERS-HINTS	Sat Feb 21 23:41:20 2004
@@ -135,6 +135,7 @@
 	winmm/wineaudioio/ 	- audioio audio driver
 	winmm/winejack/		- JACK audio server driver
 	winmm/winenas/		- NAS audio driver
+	winmm/winenbsd/		- NetBSD audio driver
 	winmm/wineoss/		- OSS audio driver
 	winnls/			- National Language Support
 	winsock/		- Sockets 2.0 (networking)
diff -urN /home/yorick/software/wine/wine/configure.ac ./configure.ac
--- /home/yorick/software/wine/wine/configure.ac	Sat Feb 14 19:19:59 2004
+++ ./configure.ac	Sun Feb 22 11:12:21 2004
@@ -631,6 +631,31 @@
                   [AUDIOIOLIBS="-laudioio"
                    AC_DEFINE(HAVE_LIBAUDIOIO, 1, [Define if you have libaudioIO])])])
 
+dnl **** Check for NetBSD audioio Sound System ****
+AC_CHECK_HEADERS(sys/audioio.h, break)
+
+AC_CACHE_CHECK([for NetBSD audioio Sound System],
+	ac_cv_c_netbsdsoundsystem,
+	AC_TRY_COMPILE([
+	#if defined(HAVE_SYS_AUDIOIO_H)
+		#include <sys/types.h>
+		#include <sys/audioio.h>
+	#endif
+	],[
+
+/* check for one of the NetBSD audioio specific defines and typedef */
+#if !defined(AUDIO_GETINFO)
+#error No NetBSD audioio
+#endif
+audio_info_t ai;
+AUDIO_INITINFO(&ai);
+],ac_cv_c_netbsdsoundsystem="yes",ac_cv_c_netbsdsoundsystem="no"))
+
+if test "$ac_cv_c_netbsdsoundsystem" = "yes"
+then
+    AC_DEFINE(HAVE_NBSDAUDIOIO, 1, [Define to support NetBSD audioio])
+fi
+
 dnl **** Check for capi4linux ****
 
 AC_CHECK_HEADERS(capi20.h,[
@@ -1609,6 +1634,7 @@
 dlls/winmm/wineaudioio/Makefile
 dlls/winmm/winejack/Makefile
 dlls/winmm/winenas/Makefile
+dlls/winmm/winenbsd/Makefile
 dlls/winmm/wineoss/Makefile
 dlls/winnls/Makefile
 dlls/winsock/Makefile
diff -urN /home/yorick/software/wine/wine/dlls/Makefile.in ./dlls/Makefile.in
--- /home/yorick/software/wine/wine/dlls/Makefile.in	Sat Feb 14 19:20:00 2004
+++ ./dlls/Makefile.in	Sat Feb 21 23:43:35 2004
@@ -130,6 +130,7 @@
 	winmm/wineaudioio \
 	winmm/winejack \
 	winmm/winenas \
+	winmm/winenbsd \
 	winmm/wineoss \
 	winnls \
 	winsock \
@@ -764,6 +765,9 @@
 winenas.drv$(DLLEXT): winmm/winenas/winenas.drv$(DLLEXT)
 	$(RM) $@ && $(LN_S) winmm/winenas/winenas.drv$(DLLEXT) $@
 
+winenbsd.drv$(DLLEXT): winmm/winenbsd/winenbsd.drv$(DLLEXT)
+	$(RM) $@ && $(LN_S) winmm/winenbsd/winenbsd.drv$(DLLEXT) $@
+
 wineoss.drv$(DLLEXT): winmm/wineoss/wineoss.drv$(DLLEXT)
 	$(RM) $@ && $(LN_S) winmm/wineoss/wineoss.drv$(DLLEXT) $@
 
@@ -1745,6 +1749,7 @@
 winmm/winejack/winejack.drv$(DLLEXT): winmm/winejack
 msacm/winemp3/winemp3.acm$(DLLEXT): msacm/winemp3
 winmm/winenas/winenas.drv$(DLLEXT): winmm/winenas
+winmm/winenbsd/winenbsd.drv$(DLLEXT): winmm/winenbsd
 winmm/wineoss/wineoss.drv$(DLLEXT): winmm/wineoss
 wineps/wineps.dll$(DLLEXT): wineps
 wininet/wininet.dll$(DLLEXT): wininet
diff -urN /home/yorick/software/wine/wine/dlls/winmm/winenbsd/Makefile.in ./dlls/winmm/winenbsd/Makefile.in
--- /home/yorick/software/wine/wine/dlls/winmm/winenbsd/Makefile.in	Thu Jan  1 02:00:00 1970
+++ ./dlls/winmm/winenbsd/Makefile.in	Sat Feb 21 23:57:46 2004
@@ -0,0 +1,16 @@
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../../..
+SRCDIR    = @srcdir@
+VPATH     = @srcdir@
+MODULE    = winenbsd.drv
+IMPORTS   = winmm user32 kernel32
+EXTRALIBS = -ldxguid -luuid
+
+C_SRCS = \
+	audio.c \
+	mmaux.c \
+	nbsd.c
+
+ at MAKE_DLL_RULES@
+
+### Dependencies:
diff -urN /home/yorick/software/wine/wine/dlls/winmm/winenbsd/audio.c ./dlls/winmm/winenbsd/audio.c
--- /home/yorick/software/wine/wine/dlls/winmm/winenbsd/audio.c	Thu Jan  1 02:00:00 1970
+++ ./dlls/winmm/winenbsd/audio.c	Sun Feb 22 12:23:22 2004
@@ -0,0 +1,4249 @@
+/* -*- tab-width: 8; c-basic-offset: 4 -*- */
+
+/*
+ * Wine Driver for NetBSD audioio
+ * Derived from the Wine OSS Sample Driver
+ *
+ * Copyright 1994 Martin Ayotte
+ *           1999 Eric Pouech (async playing in waveOut/waveIn)
+ *	     2000 Eric Pouech (loops in waveOut)
+ *           2002 Eric Pouech (full duplex)
+ *           2004 Yorick Hardy (NetBSD audioio)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * FIXME:
+ *	pause in waveOut does not work correctly in loop mode
+ *	Direct Sound Capture driver does not work (not complete yet)
+ */
+
+/*#define EMULATE_SB16*/
+
+/* unless someone makes a wineserver kernel module, Unix pipes are faster than win32 events */
+#define USE_PIPE_SYNC
+
+/* an exact wodGetPosition is usually not worth the extra context switches,
+ * as we're going to have near fragment accuracy anyway */
+/* #define EXACT_WODPOSITION */
+
+#include "config.h"
+#include "wine/port.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_MMAN_H
+# include <sys/mman.h>
+#endif
+#ifdef HAVE_SYS_POLL_H
+# include <sys/poll.h>
+#endif
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winerror.h"
+#include "wine/winuser16.h"
+#include "mmddk.h"
+#include "dsound.h"
+#include "dsdriver.h"
+#include "nbsd.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(wave);
+
+/* Allow 1% deviation for sample rates (some ES137x cards) */
+#define NEAR_MATCH(rate1,rate2) (((100*((int)(rate1)-(int)(rate2)))/(rate1))==0)
+
+#ifdef HAVE_NBSDAUDIOIO
+
+#define MAX_WAVEDRV 	(6)
+
+/* state diagram for waveOut writing:
+ *
+ * +---------+-------------+---------------+---------------------------------+
+ * |  state  |  function   |     event     |            new state	     |
+ * +---------+-------------+---------------+---------------------------------+
+ * |	     | open()	   |		   | STOPPED		       	     |
+ * | PAUSED  | write()	   | 		   | PAUSED		       	     |
+ * | STOPPED | write()	   | <thrd create> | PLAYING		  	     |
+ * | PLAYING | write()	   | HEADER        | PLAYING		  	     |
+ * | (other) | write()	   | <error>       |		       		     |
+ * | (any)   | pause()	   | PAUSING	   | PAUSED		       	     |
+ * | PAUSED  | restart()   | RESTARTING    | PLAYING (if no thrd => STOPPED) |
+ * | (any)   | reset()	   | RESETTING     | STOPPED		      	     |
+ * | (any)   | close()	   | CLOSING	   | CLOSED		      	     |
+ * +---------+-------------+---------------+---------------------------------+
+ */
+
+/* states of the playing device */
+#define	WINE_WS_PLAYING		0
+#define	WINE_WS_PAUSED		1
+#define	WINE_WS_STOPPED		2
+#define WINE_WS_CLOSED		3
+
+/* events to be send to device */
+enum win_wm_message {
+    WINE_WM_PAUSING = WM_USER + 1, WINE_WM_RESTARTING, WINE_WM_RESETTING, WINE_WM_HEADER,
+    WINE_WM_UPDATE, WINE_WM_BREAKLOOP, WINE_WM_CLOSING, WINE_WM_STARTING, WINE_WM_STOPPING
+};
+
+#ifdef USE_PIPE_SYNC
+#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 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
+
+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 */
+} NBSD_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
+ */
+#define NBSD_RING_BUFFER_INCREMENT	64
+typedef struct {
+    int                         ring_buffer_size;
+    NBSD_MSG			* messages;
+    int				msg_tosave;
+    int				msg_toget;
+#ifdef USE_PIPE_SYNC
+    int				msg_pipe[2];
+#else
+    HANDLE			msg_event;
+#endif
+    CRITICAL_SECTION		msg_crst;
+} NBSD_MSG_RING;
+
+typedef struct tagNBSD_DEVICE {
+    char                        dev_name[32];
+    char                        mixer_name[32];
+    unsigned                    open_count;
+    WAVEOUTCAPSA                out_caps;
+    WAVEINCAPSA                 in_caps;
+    DWORD                       in_caps_support;
+    unsigned                    open_access;
+    int                         fd;
+    DWORD                       owner_tid;
+    int                         sample_rate;
+    int                         stereo;
+    int                         encoding;
+    int                         precision;
+    unsigned                    audio_fragment;
+    BOOL                        full_duplex;
+    BOOL                        bOutputEnabled;
+    BOOL                        bInputEnabled;
+    DSDRIVERDESC                ds_desc;
+    DSDRIVERCAPS                ds_caps;
+    DSCDRIVERCAPS               dsc_caps;
+    GUID                        ds_guid;
+    GUID                        dsc_guid;
+} NBSD_DEVICE;
+
+static NBSD_DEVICE   NBSD_Devices[MAX_WAVEDRV];
+
+typedef struct {
+    NBSD_DEVICE*                nbsdDev;
+    volatile int		state;			/* one of the WINE_WS_ manifest constants */
+    WAVEOPENDESC		waveDesc;
+    WORD			wFlags;
+    PCMWAVEFORMAT		format;
+
+    /* driver information */
+    DWORD			dwFragmentSize;		/* size of buffer fragment */
+    DWORD                       dwBufferSize;           /* size of whole buffer in bytes */
+    LPWAVEHDR			lpQueuePtr;		/* start of queued WAVEHDRs (waiting to be notified) */
+    LPWAVEHDR			lpPlayPtr;		/* start of not yet fully played buffers */
+    DWORD			dwPartialOffset;	/* Offset of not yet written bytes in lpPlayPtr */
+
+    LPWAVEHDR			lpLoopPtr;              /* pointer of first buffer in loop, if any */
+    DWORD			dwLoops;		/* private copy of loop counter */
+
+    DWORD			dwPlayedTotal;		/* number of bytes actually played since opening */
+    DWORD                       dwWrittenTotal;         /* number of bytes written to buffer since opening */
+
+    /* synchronization stuff */
+    HANDLE			hStartUpEvent;
+    HANDLE			hThread;
+    DWORD			dwThreadID;
+    NBSD_MSG_RING		msgRing;
+} WINE_WAVEOUT;
+
+typedef struct {
+    NBSD_DEVICE*                nbsdDev;
+    volatile int		state;
+    DWORD			dwFragmentSize;
+    WAVEOPENDESC		waveDesc;
+    WORD			wFlags;
+    PCMWAVEFORMAT		format;
+    LPWAVEHDR			lpQueuePtr;
+    DWORD			dwTotalRecorded;
+
+    /* synchronization stuff */
+    HANDLE			hThread;
+    DWORD			dwThreadID;
+    HANDLE			hStartUpEvent;
+    NBSD_MSG_RING		msgRing;
+} WINE_WAVEIN;
+
+static WINE_WAVEOUT	WOutDev   [MAX_WAVEDRV];
+static WINE_WAVEIN	WInDev    [MAX_WAVEDRV];
+static unsigned         numOutDev;
+static unsigned         numInDev;
+
+static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv);
+static DWORD widDsCreate(UINT wDevID, PIDSCDRIVER* drv);
+static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc);
+static DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc);
+static DWORD wodDsGuid(UINT wDevID, LPGUID pGuid);
+static DWORD widDsGuid(UINT wDevID, LPGUID pGuid);
+
+/* These strings used only for tracing */
+static const char *wodPlayerCmdString[] = {
+    "WINE_WM_PAUSING",
+    "WINE_WM_RESTARTING",
+    "WINE_WM_RESETTING",
+    "WINE_WM_HEADER",
+    "WINE_WM_UPDATE",
+    "WINE_WM_BREAKLOOP",
+    "WINE_WM_CLOSING",
+    "WINE_WM_STARTING",
+    "WINE_WM_STOPPING",
+};
+
+static DWORD wdDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
+{
+    TRACE("(%u, %p)\n", wDevID, dwParam1);
+
+    *dwParam1 = MultiByteToWideChar(CP_ACP, 0, NBSD_Devices[wDevID].dev_name, -1,
+                                    NULL, 0 ) * sizeof(WCHAR);
+    return MMSYSERR_NOERROR;
+}
+
+static DWORD wdDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2)
+{
+    if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, NBSD_Devices[wDevID].dev_name, -1,
+                                        NULL, 0 ) * sizeof(WCHAR))
+    {
+        MultiByteToWideChar(CP_ACP, 0, NBSD_Devices[wDevID].dev_name, -1,
+                            dwParam1, dwParam2 / sizeof(WCHAR));
+	return MMSYSERR_NOERROR;
+    }
+
+    return MMSYSERR_INVALPARAM;
+}
+
+/*======================================================================*
+ *                  Low level WAVE implementation			*
+ *======================================================================*/
+
+/******************************************************************
+ *		NBSD_RawOpenDevice
+ *
+ * Low level device opening (from values stored in nbsdDev)
+ */
+static DWORD      NBSD_RawOpenDevice(NBSD_DEVICE* nbsdDev, int strict_format)
+{
+    int fd, rc;
+    audio_info_t info;
+    TRACE("(%p,%d)\n",nbsdDev,strict_format);
+
+    if ((fd = open(nbsdDev->dev_name, nbsdDev->open_access|O_NDELAY, 0)) == -1)
+    {
+        WARN("Couldn't open %s (%s)\n", nbsdDev->dev_name, strerror(errno));
+        return (errno == EBUSY) ? MMSYSERR_ALLOCATED : MMSYSERR_ERROR;
+    }
+    fcntl(fd, F_SETFD, 1); /* set close on exec flag */
+    /* turn full duplex on if it has been requested */
+    if (nbsdDev->open_access == O_RDWR && nbsdDev->full_duplex) {
+        rc = 1;
+        rc = ioctl(fd, AUDIO_SETFD, &rc);
+        if (rc != 0) {
+	    ERR("ioctl(%s, AUDIO_SETFD) failed (%s)\n", nbsdDev->dev_name, strerror(errno));
+            close(fd);
+            return MMSYSERR_ERROR;
+	}
+    }
+
+    AUDIO_INITINFO(&info);
+
+    if (nbsdDev->open_access == O_RDWR && nbsdDev->full_duplex)
+        info.mode = AUMODE_PLAY | AUMODE_RECORD | AUMODE_PLAY_ALL;
+    else if (nbsdDev->open_access == O_WRONLY)
+        info.mode = AUMODE_PLAY | AUMODE_PLAY_ALL;
+    else if (nbsdDev->open_access == O_RDONLY)
+        info.mode = AUMODE_RECORD | AUMODE_PLAY_ALL;
+
+    TRACE("blocksize=%d\n", nbsdDev->audio_fragment);
+
+    if (nbsdDev->audio_fragment)
+        info.blocksize = nbsdDev->audio_fragment;
+
+    if (nbsdDev->open_access == O_WRONLY || nbsdDev->open_access == O_RDWR) {
+        if (nbsdDev->encoding>=0) info.play.encoding = nbsdDev->encoding;
+        if (nbsdDev->precision>=0) info.play.precision = nbsdDev->precision;
+        if (nbsdDev->stereo>=0) info.play.channels = (nbsdDev->stereo) ? 2 : 1;
+        if (nbsdDev->sample_rate>=0) info.play.sample_rate = nbsdDev->sample_rate;
+    }
+   
+    if (nbsdDev->open_access == O_RDONLY || nbsdDev->open_access == O_RDWR) {
+        if (nbsdDev->encoding>=0) info.record.encoding = nbsdDev->encoding;
+        if (nbsdDev->precision>=0) info.record.precision = nbsdDev->precision;
+        if (nbsdDev->stereo>=0) info.record.channels = (nbsdDev->stereo) ? 2 : 1;
+        if (nbsdDev->sample_rate>=0) info.record.sample_rate = nbsdDev->sample_rate;
+    }
+   
+    if (ioctl(fd, AUDIO_SETINFO, &info) == -1) {
+        close(fd);
+        if (errno == EINVAL) return WAVERR_BADFORMAT;
+        return MMSYSERR_ERROR;
+    }
+
+    if (ioctl(fd, AUDIO_GETINFO, &info) == -1) {
+        close(fd);
+        return MMSYSERR_ERROR;
+    }
+
+    nbsdDev->fd = fd;
+
+    nbsdDev->bOutputEnabled = ! info.play.pause;
+    nbsdDev->bInputEnabled  = ! info.record.pause;
+
+    TRACE("NBSD_RawOpenDevice Success\n");
+    return MMSYSERR_NOERROR;
+}
+
+/******************************************************************
+ *		NBSD_OpenDevice
+ */
+static DWORD NBSD_OpenDevice(NBSD_DEVICE* nbsdDev, unsigned req_access,
+                            int* frag, int strict_format,
+                            int sample_rate, int stereo, int enc, int precision)
+{
+    DWORD       ret;
+    TRACE("(%p,%u,%p,%d,%d,%d,%d,%d)\n",nbsdDev,req_access,frag,strict_format,
+                                        sample_rate,stereo,enc,precision);
+
+    if (nbsdDev->full_duplex && (req_access == O_RDONLY || req_access == O_WRONLY))
+        req_access = O_RDWR;
+
+    /* FIXME: this should be protected, and it also contains a race with NBSD_CloseDevice */
+    if (nbsdDev->open_count == 0)
+    {
+	if (access(nbsdDev->dev_name, 0) != 0) return MMSYSERR_NODRIVER;
+
+        nbsdDev->audio_fragment = (frag) ? *frag : 0;
+        nbsdDev->sample_rate = sample_rate;
+        nbsdDev->stereo = stereo;
+        nbsdDev->encoding = enc;
+        nbsdDev->precision = precision;
+        nbsdDev->open_access = req_access;
+        nbsdDev->owner_tid = GetCurrentThreadId();
+
+        if ((ret = NBSD_RawOpenDevice(nbsdDev,strict_format)) != MMSYSERR_NOERROR) return ret;
+    }
+    else
+    {
+        /* check we really open with the same parameters */
+        if (nbsdDev->open_access != req_access)
+        {
+            ERR("FullDuplex: Mismatch in access. Your sound device is not full duplex capable.\n");
+            return WAVERR_BADFORMAT;
+        }
+
+	/* check if the audio parameters are the same */
+        if (nbsdDev->sample_rate != sample_rate ||
+            nbsdDev->stereo != stereo ||
+            nbsdDev->encoding != enc ||
+            nbsdDev->precision != precision)
+        {
+	    /* This is not a fatal error because MSACM might do the remapping */ 
+            WARN("FullDuplex: mismatch in PCM parameters for input and output\n"
+                 "audioio doesn't allow us different parameters\n"
+                 "audio_frag(%x/%x) sample_rate(%d/%d) stereo(%d/%d)\n"
+                 "encoding(%d/%d) precision(%d,%d)\n",
+                 nbsdDev->audio_fragment, frag ? *frag : 0,
+                 nbsdDev->sample_rate, sample_rate,
+                 nbsdDev->stereo, stereo,
+                 nbsdDev->encoding, enc,
+                 nbsdDev->precision, precision);
+            return WAVERR_BADFORMAT;
+        }
+	/* check if the fragment sizes are the same */
+        if (nbsdDev->audio_fragment != (frag ? *frag : 0) ) {
+	    ERR("FullDuplex: Playback and Capture hardware acceleration levels are different.\n"
+		"Use: \"HardwareAcceleration\" = \"Emulation\" in the [dsound] section of your config file.\n");
+	    return WAVERR_BADFORMAT;
+	}
+        if (GetCurrentThreadId() != nbsdDev->owner_tid)
+        {
+            WARN("Another thread is trying to access audio...\n");
+            return MMSYSERR_ERROR;
+        }
+    }
+
+    nbsdDev->open_count++;
+
+    return MMSYSERR_NOERROR;
+}
+
+/******************************************************************
+ *		NBSD_CloseDevice
+ *
+ *
+ */
+static void	NBSD_CloseDevice(NBSD_DEVICE* nbsdDev)
+{
+    TRACE("(%p)\n",nbsdDev);
+    if (nbsdDev->open_count>0) {
+        nbsdDev->open_count--;
+    } else {
+        WARN("NBSD_CloseDevice called too many times\n");
+    }
+    if (nbsdDev->open_count == 0)
+    {
+       /* reset the device before we close it in case it is in a bad state */
+       ioctl(nbsdDev->fd, AUDIO_FLUSH);
+       close(nbsdDev->fd);
+    }
+}
+
+/******************************************************************
+ *		NBSD_ResetDevice
+ *
+ * Resets the device.
+ * FIXME: This causes problems when doing full duplex so we really
+ * only reset when not doing full duplex. We need to do this better
+ * someday. 
+ */
+static DWORD     NBSD_ResetDevice(NBSD_DEVICE* nbsdDev)
+{
+    DWORD       ret = MMSYSERR_NOERROR;
+    int         old_fd = nbsdDev->fd;
+    TRACE("(%p)\n", nbsdDev);
+
+    if (nbsdDev->open_count == 1) {
+	if (ioctl(nbsdDev->fd, AUDIO_FLUSH) == -1) 
+	{
+	    perror("ioctl AUDIO_FLUSH");
+            return -1;
+	}
+	close(nbsdDev->fd);
+	ret = NBSD_RawOpenDevice(nbsdDev, 1);
+	TRACE("Changing fd from %d to %d\n", old_fd, nbsdDev->fd);
+     } else 
+	WARN("Not resetting device because it is in full duplex mode!\n");
+    
+    return ret;
+}
+
+const static int win_std_nbsd_fmts[2]=
+    {AUDIO_ENCODING_ULINEAR_LE,AUDIO_ENCODING_SLINEAR_LE};
+const static int win_std_rates[5]={96000,48000,44100,22050,11025};
+const static int win_std_formats[2][2][5]=
+    {{{WAVE_FORMAT_96M08, WAVE_FORMAT_48M08, WAVE_FORMAT_4M08,
+       WAVE_FORMAT_2M08,  WAVE_FORMAT_1M08},
+      {WAVE_FORMAT_96S08, WAVE_FORMAT_48S08, WAVE_FORMAT_4S08,
+       WAVE_FORMAT_2S08,  WAVE_FORMAT_1S08}},
+     {{WAVE_FORMAT_96M16, WAVE_FORMAT_48M16, WAVE_FORMAT_4M16,
+       WAVE_FORMAT_2M16,  WAVE_FORMAT_1M16},
+      {WAVE_FORMAT_96S16, WAVE_FORMAT_48S16, WAVE_FORMAT_4S16,
+       WAVE_FORMAT_2S16,  WAVE_FORMAT_1S16}},
+    };
+
+/******************************************************************
+ *		NBSD_WaveOutInit
+ *
+ *
+ */
+static BOOL NBSD_WaveOutInit(NBSD_DEVICE* nbsdDev)
+{
+    int rc,arg;
+    int f,c,r;
+    audio_info_t info;
+    audio_device_t dev;
+
+    TRACE("(%p) %s\n", nbsdDev, nbsdDev->dev_name);
+
+    if (NBSD_OpenDevice(nbsdDev, O_WRONLY, NULL, 0,-1,-1,-1,-1) != 0) return FALSE;
+    ioctl(nbsdDev->fd, AUDIO_FLUSH);
+
+    if (ioctl(nbsdDev->fd, AUDIO_GETDEV, &dev) >= 0) {
+        strncpy(nbsdDev->ds_desc.szDesc, dev.name, sizeof(dev.name));
+        strcpy(nbsdDev->ds_desc.szDrvName, "winenbsd.drv");
+        strncpy(nbsdDev->out_caps.szPname, dev.config, sizeof(dev.config));
+        TRACE("%s\n", nbsdDev->ds_desc.szDesc);
+    } else {
+        ERR("%s: can't read info!\n", nbsdDev->dev_name);
+        NBSD_CloseDevice(nbsdDev);
+        return FALSE;
+    }
+
+    /* FIXME: some programs compare this string against the content of the
+     * registry for MM drivers. The names have to match in order for the
+     * program to work (e.g. MS win9x mplayer.exe)
+     */
+#ifdef EMULATE_SB16
+    nbsdDev->out_caps.wMid = 0x0002;
+    nbsdDev->out_caps.wPid = 0x0104;
+    strcpy(nbsdDev->out_caps.szPname, "SB16 Wave Out");
+#else
+    nbsdDev->out_caps.wMid = 0x00FF; /* Manufac ID */
+    nbsdDev->out_caps.wPid = 0x0001; /* Product ID */
+#endif
+    nbsdDev->out_caps.vDriverVersion = 0x0100;
+    nbsdDev->out_caps.wChannels = 1;
+    nbsdDev->out_caps.dwFormats = 0x00000000;
+    nbsdDev->out_caps.wReserved1 = 0;
+    nbsdDev->out_caps.dwSupport = WAVECAPS_VOLUME;
+
+    /* direct sound caps */
+    nbsdDev->ds_caps.dwFlags = 0;
+    nbsdDev->ds_caps.dwPrimaryBuffers = 1;
+    nbsdDev->ds_caps.dwMinSecondarySampleRate = DSBFREQUENCY_MIN;
+    nbsdDev->ds_caps.dwMaxSecondarySampleRate = DSBFREQUENCY_MAX;
+
+
+    for (f=0;f<2;f++) {
+        for (c=1;c<=2;c++) {
+            for (r=0;r<sizeof(win_std_rates)/sizeof(*win_std_rates);r++) {
+                AUDIO_INITINFO(&info);
+                info.play.sample_rate = win_std_rates[r];
+                info.play.channels = c;
+                info.play.precision = (f + 1) * 8;
+                info.play.encoding = win_std_nbsd_fmts[f];
+
+                rc = ioctl(nbsdDev->fd, AUDIO_SETINFO, &info);
+                if (rc==0) rc = ioctl(nbsdDev->fd, AUDIO_GETINFO, &info);
+                if (rc==0 && NEAR_MATCH(info.play.sample_rate,win_std_rates[r])) {
+                    if (f == 0) 
+                        nbsdDev->ds_caps.dwFlags |= DSCAPS_PRIMARY8BIT;
+                    else if (f == 1)
+                        nbsdDev->ds_caps.dwFlags |= DSCAPS_PRIMARY16BIT;
+                    if (c == 0) {
+                        nbsdDev->ds_caps.dwFlags |= DSCAPS_PRIMARYMONO;
+                    } else if (c == 1) {
+                        nbsdDev->out_caps.wChannels = 2;
+                        nbsdDev->out_caps.dwSupport |= WAVECAPS_LRVOLUME;
+                    	nbsdDev->ds_caps.dwFlags |= DSCAPS_PRIMARYSTEREO;
+                    }
+                    nbsdDev->out_caps.dwFormats |= win_std_formats[f][c-1][r];
+                }
+            }
+        }
+    }
+
+    nbsdDev->out_caps.dwSupport |= WAVECAPS_SAMPLEACCURATE;
+    if (ioctl(nbsdDev->fd, AUDIO_GETPROPS, &arg) == 0) {
+        TRACE("audioio dsp out caps=%08X\n", arg);
+
+        /* well, might as well use the DirectSound cap flag for something */
+        if (arg & AUDIO_PROP_MMAP) {
+            nbsdDev->out_caps.dwSupport |= WAVECAPS_DIRECTSOUND;
+	} else {
+	    nbsdDev->ds_caps.dwFlags |= DSCAPS_EMULDRIVER;
+	}
+    }
+    NBSD_CloseDevice(nbsdDev);
+    TRACE("out dwFormats = %08lX, dwSupport = %08lX\n",
+          nbsdDev->out_caps.dwFormats, nbsdDev->out_caps.dwSupport);
+    return TRUE;
+}
+
+/******************************************************************
+ *		NBSD_WaveInInit
+ *
+ *
+ */
+static BOOL NBSD_WaveInInit(NBSD_DEVICE* nbsdDev)
+{
+    int rc,arg;
+    int f,c,r;
+    audio_info_t info;
+    audio_device_t dev;
+
+    TRACE("(%p) %s\n", nbsdDev, nbsdDev->dev_name);
+
+    if (NBSD_OpenDevice(nbsdDev, O_RDONLY, NULL, 0,-1,-1,-1,-1) != 0) return FALSE;
+    ioctl(nbsdDev->fd, AUDIO_FLUSH);
+
+    if (ioctl(nbsdDev->fd, AUDIO_GETDEV, &dev) >= 0) {
+        strncpy(nbsdDev->in_caps.szPname, dev.name, sizeof(dev.name));
+        TRACE("%s\n", nbsdDev->ds_desc.szDesc);
+    } else {
+        ERR("%s: can't read info!\n", nbsdDev->mixer_name);
+        NBSD_CloseDevice(nbsdDev);
+        return FALSE;
+    }
+
+    /* See comment in NBSD_WaveOutInit */
+#ifdef EMULATE_SB16
+    nbsdDev->in_caps.wMid = 0x0002;
+    nbsdDev->in_caps.wPid = 0x0004;
+    strcpy(nbsdDev->in_caps.szPname, "SB16 Wave In");
+#else
+    nbsdDev->in_caps.wMid = 0x00FF; /* Manufac ID */
+    nbsdDev->in_caps.wPid = 0x0001; /* Product ID */
+#endif
+    nbsdDev->in_caps.dwFormats = 0x00000000;
+    nbsdDev->in_caps.wChannels = 1;
+    nbsdDev->in_caps.wReserved1 = 0;
+
+    /* direct sound caps */
+    nbsdDev->dsc_caps.dwSize = sizeof(nbsdDev->dsc_caps);
+    nbsdDev->dsc_caps.dwFlags = 0;
+    nbsdDev->dsc_caps.dwFormats = 0x00000000;
+    nbsdDev->dsc_caps.dwChannels = 1;
+
+    /* See the comment in NBSD_WaveOutInit */
+    for (f=0;f<2;f++) {
+        for (c=1;c<=2;c++) {
+            for (r=0;r<sizeof(win_std_rates)/sizeof(*win_std_rates);r++) {
+                AUDIO_INITINFO(&info);
+                info.record.sample_rate = win_std_rates[r];
+                info.record.channels = c;
+                info.record.precision = (f + 1) * 8;
+                info.record.encoding = win_std_nbsd_fmts[f];
+
+                rc = ioctl(nbsdDev->fd, AUDIO_SETINFO, &info);
+                if (rc==0) rc = ioctl(nbsdDev->fd, AUDIO_GETINFO, &info);
+                if (rc==0 && NEAR_MATCH(info.record.sample_rate,win_std_rates[r])) {
+                    if (c == 1) {
+                        nbsdDev->in_caps.wChannels = 2;
+                        nbsdDev->dsc_caps.dwChannels = 2;
+                    }
+                    nbsdDev->in_caps.dwFormats |= win_std_formats[f][c-1][r];
+		    nbsdDev->dsc_caps.dwFormats |= win_std_formats[f][c-1][r];
+                }
+            }
+        }
+    }
+
+    nbsdDev->in_caps_support |= WAVECAPS_SAMPLEACCURATE;
+    if (ioctl(nbsdDev->fd, AUDIO_GETPROPS, &arg) == 0) {
+        TRACE("audioio dsp in caps=%08X\n", arg);
+        /* FIXME: enable the next statement if you want to work on the driver */
+#if 0
+        nbsdDev->in_caps_support |= WAVECAPS_DIRECTSOUND;
+#endif
+    }
+    NBSD_CloseDevice(nbsdDev);
+    TRACE("in dwFormats = %08lX\n", nbsdDev->in_caps.dwFormats);
+    return TRUE;
+}
+
+/******************************************************************
+ *		NBSD_WaveFullDuplexInit
+ *
+ *
+ */
+static void NBSD_WaveFullDuplexInit(NBSD_DEVICE* nbsdDev)
+{
+    int 	caps;
+    TRACE("(%p)\n",nbsdDev);
+
+    if (NBSD_OpenDevice(nbsdDev, O_RDWR, NULL, 0,-1,-1,-1,-1) != 0) return;
+    if (ioctl(nbsdDev->fd, AUDIO_GETPROPS, &caps) == 0)
+    {
+	nbsdDev->full_duplex = (caps & AUDIO_PROP_FULLDUPLEX);
+    }
+    NBSD_CloseDevice(nbsdDev);
+}
+
+#define INIT_GUID(guid, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8)	\
+	guid.Data1 = l; guid.Data2 = w1; guid.Data3 = w2;		\
+	guid.Data4[0] = b1; guid.Data4[1] = b2; guid.Data4[2] = b3;	\
+	guid.Data4[3] = b4; guid.Data4[4] = b5; guid.Data4[5] = b6;	\
+	guid.Data4[6] = b7; guid.Data4[7] = b8;
+/******************************************************************
+ *		NBSD_WaveInit
+ *
+ * Initialize internal structures from audioio information
+ */
+LONG NBSD_WaveInit(void)
+{
+    int 	i;
+    TRACE("()\n");
+
+    for (i = 0; i < MAX_WAVEDRV; ++i)
+    {
+	if (i == 0) {
+	    sprintf((char *)NBSD_Devices[i].dev_name, "/dev/audio");
+	    sprintf((char *)NBSD_Devices[i].mixer_name, "/dev/mixer");
+	} else {
+	    sprintf((char *)NBSD_Devices[i].dev_name, "/dev/audio%d", i);
+	    sprintf((char *)NBSD_Devices[i].mixer_name, "/dev/mixer%d", i);
+	}
+
+	INIT_GUID(NBSD_Devices[i].ds_guid,  0xbd6dd71a, 0x3deb, 0x11d1, 0xb1, 0x71, 0x00, 0xc0, 0x4f, 0xc2, 0x00, 0x00 + i);
+	INIT_GUID(NBSD_Devices[i].dsc_guid, 0xbd6dd71b, 0x3deb, 0x11d1, 0xb1, 0x71, 0x00, 0xc0, 0x4f, 0xc2, 0x00, 0x00 + i);
+
+    }
+
+    /* start with output devices */
+    for (i = 0; i < MAX_WAVEDRV; ++i)
+    {
+        if (NBSD_WaveOutInit(&NBSD_Devices[i]))
+        {
+            WOutDev[numOutDev].state = WINE_WS_CLOSED;
+            WOutDev[numOutDev].nbsdDev = &NBSD_Devices[i];
+            numOutDev++;
+        }
+    }
+
+    /* then do input devices */
+    for (i = 0; i < MAX_WAVEDRV; ++i)
+    {
+        if (NBSD_WaveInInit(&NBSD_Devices[i]))
+        {
+            WInDev[numInDev].state = WINE_WS_CLOSED;
+            WInDev[numInDev].nbsdDev = &NBSD_Devices[i];
+            numInDev++;
+        }
+    }
+
+    /* finish with the full duplex bits */
+    for (i = 0; i < MAX_WAVEDRV; i++)
+        NBSD_WaveFullDuplexInit(&NBSD_Devices[i]);
+
+    return 0;
+}
+
+/******************************************************************
+ *		NBSD_InitRingMessage
+ *
+ * Initialize the ring of messages for passing between driver's caller and playback/record
+ * thread
+ */
+static int NBSD_InitRingMessage(NBSD_MSG_RING* omr)
+{
+    omr->msg_toget = 0;
+    omr->msg_tosave = 0;
+#ifdef USE_PIPE_SYNC
+    if (pipe(omr->msg_pipe) < 0) {
+	omr->msg_pipe[0] = -1;
+	omr->msg_pipe[1] = -1;
+	ERR("could not create pipe, error=%s\n", strerror(errno));
+    }
+#else
+    omr->msg_event = CreateEventA(NULL, FALSE, FALSE, NULL);
+#endif
+    omr->ring_buffer_size = NBSD_RING_BUFFER_INCREMENT;
+    omr->messages = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,omr->ring_buffer_size * sizeof(NBSD_MSG));
+    InitializeCriticalSection(&omr->msg_crst);
+    return 0;
+}
+
+/******************************************************************
+ *		NBSD_DestroyRingMessage
+ *
+ */
+static int NBSD_DestroyRingMessage(NBSD_MSG_RING* omr)
+{
+#ifdef USE_PIPE_SYNC
+    close(omr->msg_pipe[0]);
+    close(omr->msg_pipe[1]);
+#else
+    CloseHandle(omr->msg_event);
+#endif
+    HeapFree(GetProcessHeap(),0,omr->messages);
+    DeleteCriticalSection(&omr->msg_crst);
+    return 0;
+}
+
+/******************************************************************
+ *		NBSD_AddRingMessage
+ *
+ * Inserts a new message into the ring (should be called from DriverProc derivated routines)
+ */
+static int NBSD_AddRingMessage(NBSD_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 += NBSD_RING_BUFFER_INCREMENT;
+	TRACE("omr->ring_buffer_size=%d\n",omr->ring_buffer_size);
+	omr->messages = HeapReAlloc(GetProcessHeap(),0,omr->messages, omr->ring_buffer_size * sizeof(NBSD_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 + NBSD_RING_BUFFER_INCREMENT]),
+		    &(omr->messages[omr->msg_toget]),
+		    sizeof(NBSD_MSG)*(old_ring_buffer_size - omr->msg_toget)
+		    );
+	    omr->msg_toget += NBSD_RING_BUFFER_INCREMENT;
+	}
+    }
+    if (wait)
+    {
+        hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
+        if (hEvent == INVALID_HANDLE_VALUE)
+        {
+            ERR("can't create event !?\n");
+            LeaveCriticalSection(&omr->msg_crst);
+            return 0;
+        }
+        if (omr->msg_toget != omr->msg_tosave && omr->messages[omr->msg_toget].msg != WINE_WM_HEADER)
+            FIXME("two fast messages in the queue!!!!\n");
+
+        /* 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;
+}
+
+/******************************************************************
+ *		NBSD_RetrieveRingMessage
+ *
+ * Get a message from the ring. Should be called by the playback/record thread.
+ */
+static int NBSD_RetrieveRingMessage(NBSD_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;
+}
+
+/******************************************************************
+ *              NBSD_PeekRingMessage
+ *
+ * Peek at a message from the ring but do not remove it.
+ * Should be called by the playback/record thread.
+ */
+static int NBSD_PeekRingMessage(NBSD_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;
+    *param = omr->messages[omr->msg_toget].param;
+    *hEvent = omr->messages[omr->msg_toget].hEvent;
+    LeaveCriticalSection(&omr->msg_crst);
+    return 1;
+}
+
+/*======================================================================*
+ *                  Low level WAVE OUT implementation			*
+ *======================================================================*/
+
+/**************************************************************************
+ * 			wodNotifyClient			[internal]
+ */
+static DWORD wodNotifyClient(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
+{
+    TRACE("wMsg = 0x%04x (%s) dwParm1 = %04lX dwParam2 = %04lX\n", wMsg,
+        wMsg == WOM_OPEN ? "WOM_OPEN" : wMsg == WOM_CLOSE ? "WOM_CLOSE" :
+        wMsg == WOM_DONE ? "WOM_DONE" : "Unknown", 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;
+}
+
+/**************************************************************************
+ * 				wodUpdatePlayedTotal	[internal]
+ *
+ */
+static BOOL wodUpdatePlayedTotal(WINE_WAVEOUT* wwo, audio_info_t* info)
+{
+    audio_info_t dspspace;
+    if (!info) info = &dspspace;
+
+    if (ioctl(wwo->nbsdDev->fd, AUDIO_GETINFO, info) < 0) {
+        ERR("ioctl(%s, AUDIO_GETINFO) failed (%s)\n", wwo->nbsdDev->dev_name, strerror(errno));
+        return FALSE;
+    }
+    wwo->dwPlayedTotal = wwo->dwWrittenTotal - info->play.seek;
+    return TRUE;
+}
+
+/**************************************************************************
+ * 				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_WAVEOUT* 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 (%ldx) 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_WAVEOUT* 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_DSPWait			[internal]
+ * Returns the number of milliseconds to wait for the DSP buffer to write
+ * one fragment.
+ */
+static DWORD wodPlayer_DSPWait(const WINE_WAVEOUT *wwo)
+{
+    /* time for one fragment to be played */
+    return wwo->dwFragmentSize * 1000 / wwo->format.wf.nAvgBytesPerSec;
+}
+
+/**************************************************************************
+ * 			     wodPlayer_NotifyWait               [internal]
+ * Returns the number of milliseconds to wait before attempting to notify
+ * completion of the specified wavehdr.
+ * This is based on the number of bytes remaining to be written in the
+ * wave.
+ */
+static DWORD wodPlayer_NotifyWait(const WINE_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr)
+{
+    DWORD dwMillis;
+
+    if (lpWaveHdr->reserved < wwo->dwPlayedTotal) {
+	dwMillis = 1;
+    } else {
+	dwMillis = (lpWaveHdr->reserved - wwo->dwPlayedTotal) * 1000 / wwo->format.wf.nAvgBytesPerSec;
+	if (!dwMillis) dwMillis = 1;
+    }
+
+    return dwMillis;
+}
+
+
+/**************************************************************************
+ * 			     wodPlayer_WriteMaxFrags            [internal]
+ * Writes the maximum number of bytes possible to the DSP and returns
+ * TRUE iff the current playPtr has been fully played
+ */
+static BOOL wodPlayer_WriteMaxFrags(WINE_WAVEOUT* wwo, DWORD* bytes)
+{
+    DWORD       dwLength = wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset;
+    DWORD       toWrite = min(dwLength, *bytes);
+    int         written;
+    BOOL        ret = FALSE;
+
+    TRACE("Writing wavehdr %p.%lu[%lu]/%lu\n",
+          wwo->lpPlayPtr, wwo->dwPartialOffset, wwo->lpPlayPtr->dwBufferLength, toWrite);
+
+    if (toWrite > 0)
+    {
+        written = write(wwo->nbsdDev->fd, wwo->lpPlayPtr->lpData + wwo->dwPartialOffset, toWrite);
+        if (written <= 0) return FALSE;
+    }
+    else
+        written = 0;
+
+    if (written >= dwLength) {
+        /* If we wrote all current wavehdr, skip to the next one */
+        wodPlayer_PlayPtrNext(wwo);
+        ret = TRUE;
+    } else {
+        /* Remove the amount written */
+        wwo->dwPartialOffset += written;
+    }
+    *bytes -= written;
+    wwo->dwWrittenTotal += written;
+    TRACE("dwWrittenTotal=%lu\n", wwo->dwWrittenTotal);
+    return ret;
+}
+
+
+/**************************************************************************
+ * 				wodPlayer_NotifyCompletions	[internal]
+ *
+ * Notifies and remove from queue all wavehdrs which have been played to
+ * the speaker (ie. they have cleared the buffer).  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.
+ */
+static DWORD wodPlayer_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force)
+{
+    LPWAVEHDR		lpWaveHdr;
+
+    /* Start from lpQueuePtr and keep notifying until:
+     * - we hit an unwritten wavehdr
+     * - we hit the beginning of a running loop
+     * - we hit a wavehdr which hasn't finished playing
+     */
+    while ((lpWaveHdr = wwo->lpQueuePtr) &&
+           (force ||
+            (lpWaveHdr != wwo->lpPlayPtr &&
+             lpWaveHdr != wwo->lpLoopPtr &&
+             lpWaveHdr->reserved <= wwo->dwPlayedTotal))) {
+
+	wwo->lpQueuePtr = lpWaveHdr->lpNext;
+
+	lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+	lpWaveHdr->dwFlags |= WHDR_DONE;
+
+	wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0);
+    }
+    return  (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr) ?
+        wodPlayer_NotifyWait(wwo, lpWaveHdr) : INFINITE;
+}
+
+/**************************************************************************
+ * 				wodPlayer_Reset			[internal]
+ *
+ * wodPlayer helper. Resets current output stream.
+ */
+static	void	wodPlayer_Reset(WINE_WAVEOUT* wwo, BOOL reset)
+{
+    wodUpdatePlayedTotal(wwo, NULL);
+    /* updates current notify list */
+    wodPlayer_NotifyCompletions(wwo, FALSE);
+
+    /* flush all possible output */
+    if (NBSD_ResetDevice(wwo->nbsdDev) != MMSYSERR_NOERROR)
+    {
+	wwo->hThread = 0;
+	wwo->state = WINE_WS_STOPPED;
+	ExitThread(-1);
+    }
+
+    if (reset) {
+        enum win_wm_message	msg;
+        DWORD		        param;
+        HANDLE		        ev;
+
+	/* remove any buffer */
+	wodPlayer_NotifyCompletions(wwo, TRUE);
+
+	wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
+	wwo->state = WINE_WS_STOPPED;
+	wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
+        /* Clear partial wavehdr */
+        wwo->dwPartialOffset = 0;
+
+        /* remove any existing message in the ring */
+        EnterCriticalSection(&wwo->msgRing.msg_crst);
+        /* return all pending headers in queue */
+        while (NBSD_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev))
+        {
+            if (msg != WINE_WM_HEADER)
+            {
+                FIXME("shouldn't have headers left\n");
+                SetEvent(ev);
+                continue;
+            }
+            ((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE;
+            ((LPWAVEHDR)param)->dwFlags |= WHDR_DONE;
+
+            wodNotifyClient(wwo, WOM_DONE, param, 0);
+        }
+        RESET_OMR(&wwo->msgRing);
+        LeaveCriticalSection(&wwo->msgRing.msg_crst);
+    } else {
+        if (wwo->lpLoopPtr) {
+            /* complicated case, not handled yet (could imply modifying the loop counter */
+            FIXME("Pausing while in loop isn't correctly handled yet, except strange results\n");
+            wwo->lpPlayPtr = wwo->lpLoopPtr;
+            wwo->dwPartialOffset = 0;
+            wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */
+        } else {
+            LPWAVEHDR   ptr;
+            DWORD       sz = wwo->dwPartialOffset;
+
+            /* reset all the data as if we had written only up to lpPlayedTotal bytes */
+            /* compute the max size playable from lpQueuePtr */
+            for (ptr = wwo->lpQueuePtr; ptr != wwo->lpPlayPtr; ptr = ptr->lpNext) {
+                sz += ptr->dwBufferLength;
+            }
+            /* because the reset lpPlayPtr will be lpQueuePtr */
+            if (wwo->dwWrittenTotal > wwo->dwPlayedTotal + sz) ERR("grin\n");
+            wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal);
+            wwo->dwWrittenTotal = wwo->dwPlayedTotal;
+            wwo->lpPlayPtr = wwo->lpQueuePtr;
+        }
+	wwo->state = WINE_WS_PAUSED;
+    }
+}
+
+/**************************************************************************
+ * 		      wodPlayer_ProcessMessages			[internal]
+ */
+static void wodPlayer_ProcessMessages(WINE_WAVEOUT* wwo)
+{
+    LPWAVEHDR           lpWaveHdr;
+    enum win_wm_message	msg;
+    DWORD		param;
+    HANDLE		ev;
+
+    while (NBSD_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
+	TRACE("Received %s %lx\n", wodPlayerCmdString[msg - WM_USER - 1], param);
+	switch (msg) {
+	case WINE_WM_PAUSING:
+	    wodPlayer_Reset(wwo, FALSE);
+	    SetEvent(ev);
+	    break;
+	case WINE_WM_RESTARTING:
+            if (wwo->state == WINE_WS_PAUSED)
+            {
+                wwo->state = WINE_WS_PLAYING;
+            }
+	    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;
+	    break;
+	case WINE_WM_RESETTING:
+	    wodPlayer_Reset(wwo, TRUE);
+	    SetEvent(ev);
+	    break;
+        case WINE_WM_UPDATE:
+            wodUpdatePlayedTotal(wwo, NULL);
+	    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_CLOSING:
+	    /* sanity check: this should not happen since the device must have been reset before */
+	    if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
+	    wwo->hThread = 0;
+	    wwo->state = WINE_WS_CLOSED;
+	    SetEvent(ev);
+	    ExitThread(0);
+	    /* shouldn't go here */
+	default:
+	    FIXME("unknown message %d\n", msg);
+	    break;
+	}
+    }
+}
+
+/**************************************************************************
+ * 			     wodPlayer_FeedDSP			[internal]
+ * Feed as much sound data as we can into the DSP and return the number of
+ * milliseconds before it will be necessary to feed the DSP again.
+ */
+static DWORD wodPlayer_FeedDSP(WINE_WAVEOUT* wwo)
+{
+    audio_info_t dspspace;
+    DWORD       availInQ;
+    int         fragments;
+
+    wodUpdatePlayedTotal(wwo, &dspspace);
+    availInQ = dspspace.hiwat * dspspace.blocksize - dspspace.play.seek;
+    if (availInQ < 0) availInQ = 0;
+    fragments = availInQ / dspspace.blocksize;
+    TRACE("fragments=%d/%d, fragsize=%d, bytes=%d\n",
+	  fragments, dspspace.hiwat, dspspace.blocksize, (int) availInQ);
+
+    /* input queue empty and output buffer with less than one fragment to play 
+     * actually some cards do not play the fragment before the last if this one is partially feed
+     * so we need to test for full the availability of 2 fragments
+     */
+    if (!wwo->lpPlayPtr && wwo->dwBufferSize < availInQ + 2 * wwo->dwFragmentSize) {
+	TRACE("Run out of wavehdr:s...\n");
+        return INFINITE;
+    }
+
+    /* no more room... no need to try to feed */
+    if (fragments != 0) {
+        /* Feed from partial wavehdr */
+        if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0) {
+            wodPlayer_WriteMaxFrags(wwo, &availInQ);
+        }
+
+        /* Feed wavehdrs until we run out of wavehdrs or DSP space */
+        if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) {
+            do {
+                TRACE("Setting time to elapse for %p to %lu\n",
+                      wwo->lpPlayPtr, wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength);
+                /* note the value that dwPlayedTotal will return when this wave finishes playing */
+                wwo->lpPlayPtr->reserved = wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength;
+            } while (wodPlayer_WriteMaxFrags(wwo, &availInQ) && wwo->lpPlayPtr && availInQ > 0);
+        }
+    }
+
+    /* FIXME("%d bytes available in audio buffer\n", (int)availInQ); */
+    return wodPlayer_DSPWait(wwo);
+}
+
+
+/**************************************************************************
+ * 				wodPlayer			[internal]
+ */
+static	DWORD	CALLBACK	wodPlayer(LPVOID pmt)
+{
+    WORD	  uDevID = (DWORD)pmt;
+    WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)&WOutDev[uDevID];
+    DWORD         dwNextFeedTime = INFINITE;   /* Time before DSP needs feeding */
+    DWORD         dwNextNotifyTime = INFINITE; /* Time before next wave completion */
+    DWORD         dwSleepTime;
+
+    wwo->state = WINE_WS_STOPPED;
+    SetEvent(wwo->hStartUpEvent);
+
+    for (;;) {
+        /** Wait for the shortest time before an action is required.  If there
+         *  are no pending actions, wait forever for a command.
+         */
+        dwSleepTime = min(dwNextFeedTime, dwNextNotifyTime);
+        TRACE("waiting %lums (%lu,%lu)\n", dwSleepTime, dwNextFeedTime, dwNextNotifyTime);
+	WAIT_OMR(&wwo->msgRing, dwSleepTime);
+	wodPlayer_ProcessMessages(wwo);
+	if (wwo->state == WINE_WS_PLAYING) {
+	    dwNextFeedTime = wodPlayer_FeedDSP(wwo);
+	    dwNextNotifyTime = wodPlayer_NotifyCompletions(wwo, FALSE);
+	    if (dwNextFeedTime == INFINITE) {
+		/* FeedDSP ran out of data, but before flushing, */
+		/* check that a notification didn't give us more */
+		wodPlayer_ProcessMessages(wwo);
+		if (!wwo->lpPlayPtr) {
+		    TRACE("flushing\n");
+		    ioctl(wwo->nbsdDev->fd, AUDIO_DRAIN);
+		    wwo->dwPlayedTotal = wwo->dwWrittenTotal;
+                    dwNextNotifyTime = wodPlayer_NotifyCompletions(wwo, FALSE);
+		} else {
+		    TRACE("recovering\n");
+		    dwNextFeedTime = wodPlayer_FeedDSP(wwo);
+		}
+	    }
+	} else {
+	    dwNextFeedTime = dwNextNotifyTime = INFINITE;
+	}
+    }
+}
+
+/**************************************************************************
+ * 			wodGetDevCaps				[internal]
+ */
+static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSA lpCaps, DWORD dwSize)
+{
+    TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize);
+
+    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
+
+    if (wDevID >= numOutDev) {
+	TRACE("numOutDev reached !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    memcpy(lpCaps, &WOutDev[wDevID].nbsdDev->out_caps, min(dwSize, sizeof(*lpCaps)));
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodOpen				[internal]
+ */
+static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
+{
+    int			audio_fragment;
+    WINE_WAVEOUT*	wwo;
+    audio_info_t	info;
+    DWORD               ret;
+
+    TRACE("(%u, %p[cb=%08lx], %08lX);\n", wDevID, lpDesc, lpDesc->dwCallback, dwFlags);
+    if (lpDesc == NULL) {
+	WARN("Invalid Parameter !\n");
+	return MMSYSERR_INVALPARAM;
+    }
+    if (wDevID >= numOutDev) {
+	TRACE("MAX_WAVOUTDRV reached !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    /* only PCM format is supported so far... */
+    if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM ||
+	lpDesc->lpFormat->nChannels == 0 ||
+	lpDesc->lpFormat->nSamplesPerSec == 0) {
+	WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	return WAVERR_BADFORMAT;
+    }
+
+    if (dwFlags & WAVE_FORMAT_QUERY) {
+	TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	return MMSYSERR_NOERROR;
+    }
+
+    wwo = &WOutDev[wDevID];
+
+    if ((dwFlags & WAVE_DIRECTSOUND) && 
+        !(wwo->nbsdDev->out_caps.dwSupport & WAVECAPS_DIRECTSOUND))
+	/* not supported, ignore it */
+	dwFlags &= ~WAVE_DIRECTSOUND;
+
+    if (dwFlags & WAVE_DIRECTSOUND) {
+        if (wwo->nbsdDev->out_caps.dwSupport & WAVECAPS_SAMPLEACCURATE)
+	    /* we have realtime DirectSound, however we do not know the
+	     * resolution of pointer updates - so choose a small blocksize */
+	    audio_fragment = 256;
+    } else {
+	/* shockwave player uses only 4 1k-fragments at a rate of 22050 bytes/sec
+	 * thus leading to 46ms per fragment, and a turnaround time of 185ms
+	 */
+	/* 1024 bytes per fragment */
+	audio_fragment = 1024;
+    }
+    if (wwo->state != WINE_WS_CLOSED) return MMSYSERR_ALLOCATED;
+    /* we want to be able to mmap() the device, which means it must be opened readable,
+     * otherwise mmap() will fail */
+    ret = NBSD_OpenDevice(wwo->nbsdDev,
+                         (dwFlags & WAVE_DIRECTSOUND) ? O_RDWR : O_WRONLY,
+                         &audio_fragment,
+                         (dwFlags & WAVE_DIRECTSOUND) ? 0 : 1,
+                         lpDesc->lpFormat->nSamplesPerSec,
+                         (lpDesc->lpFormat->nChannels > 1) ? 1 : 0,
+                         (lpDesc->lpFormat->wBitsPerSample == 16)
+                         ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_ULINEAR_LE,
+                         lpDesc->lpFormat->wBitsPerSample);
+    if ((ret==MMSYSERR_NOERROR) && (dwFlags & WAVE_DIRECTSOUND)) {
+        lpDesc->lpFormat->nSamplesPerSec=wwo->nbsdDev->sample_rate;
+        lpDesc->lpFormat->nChannels=(wwo->nbsdDev->stereo ? 2 : 1);
+        lpDesc->lpFormat->wBitsPerSample=wwo->nbsdDev->precision;
+        lpDesc->lpFormat->nBlockAlign=lpDesc->lpFormat->nChannels*lpDesc->lpFormat->wBitsPerSample/8;
+        lpDesc->lpFormat->nAvgBytesPerSec=lpDesc->lpFormat->nSamplesPerSec*lpDesc->lpFormat->nBlockAlign;
+        TRACE("NBSD_OpenDevice returned this format: %ldx%dx%d\n",
+              lpDesc->lpFormat->nSamplesPerSec,
+              lpDesc->lpFormat->wBitsPerSample,
+              lpDesc->lpFormat->nChannels);
+    }
+    if (ret != 0) return ret;
+    wwo->state = WINE_WS_STOPPED;
+
+    wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
+
+    memcpy(&wwo->waveDesc, lpDesc, 	     sizeof(WAVEOPENDESC));
+    memcpy(&wwo->format,   lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));
+
+    if (wwo->format.wBitsPerSample == 0) {
+	WARN("Resetting zeroed wBitsPerSample\n");
+	wwo->format.wBitsPerSample = 8 *
+	    (wwo->format.wf.nAvgBytesPerSec /
+	     wwo->format.wf.nSamplesPerSec) /
+	    wwo->format.wf.nChannels;
+    }
+    /* Read output space info for future reference */
+    if (ioctl(wwo->nbsdDev->fd, AUDIO_GETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_GETINFO) failed (%s)\n", wwo->nbsdDev->dev_name, strerror(errno));
+        NBSD_CloseDevice(wwo->nbsdDev);
+	wwo->state = WINE_WS_CLOSED;
+	return MMSYSERR_NOTENABLED;
+    }
+
+    /* Check that fragsize is correct per our settings above */
+#if 0
+    if ((info.blocksize > 1024) && (audio_fragment < 1024)) {
+#else
+    if (info.blocksize > audio_fragment) {
+#endif
+	/* we've tried to set 1K fragments or less, but it didn't work */
+	ERR("fragment size set failed, size is now %d\n", info.blocksize);
+	MESSAGE("Your AudioIO driver did not let us configure small enough sound fragments.\n");
+	MESSAGE("This may cause delays and other problems in audio playback with certain applications.\n");
+    }
+
+    /* Remember fragsize and total buffer size for future use */
+    wwo->dwFragmentSize = info.blocksize;
+    wwo->dwBufferSize = info.play.buffer_size;
+    wwo->dwPlayedTotal = 0;
+    wwo->dwWrittenTotal = 0;
+
+    NBSD_InitRingMessage(&wwo->msgRing);
+
+    wwo->hStartUpEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
+    wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)(DWORD)wDevID, 0, &(wwo->dwThreadID));
+    WaitForSingleObject(wwo->hStartUpEvent, INFINITE);
+    CloseHandle(wwo->hStartUpEvent);
+    wwo->hStartUpEvent = INVALID_HANDLE_VALUE;
+
+    TRACE("fd=%d fragmentSize=%ld\n",
+	  wwo->nbsdDev->fd, wwo->dwFragmentSize);
+    if (wwo->dwFragmentSize % wwo->format.wf.nBlockAlign)
+	ERR("Fragment doesn't contain an integral number of data blocks\n");
+
+    TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%lu, nSamplesPerSec=%lu, nChannels=%u nBlockAlign=%u!\n",
+	  wwo->format.wBitsPerSample, wwo->format.wf.nAvgBytesPerSec,
+	  wwo->format.wf.nSamplesPerSec, wwo->format.wf.nChannels,
+	  wwo->format.wf.nBlockAlign);
+
+    return wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);
+}
+
+/**************************************************************************
+ * 				wodClose			[internal]
+ */
+static DWORD wodClose(WORD wDevID)
+{
+    DWORD		ret = MMSYSERR_NOERROR;
+    WINE_WAVEOUT*	wwo;
+
+    TRACE("(%u);\n", wDevID);
+
+    if (wDevID >= numOutDev || WOutDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    wwo = &WOutDev[wDevID];
+    if (wwo->lpQueuePtr) {
+	WARN("buffers still playing !\n");
+	ret = WAVERR_STILLPLAYING;
+    } else {
+	if (wwo->hThread != INVALID_HANDLE_VALUE) {
+	    NBSD_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE);
+	}
+
+        NBSD_DestroyRingMessage(&wwo->msgRing);
+
+        NBSD_CloseDevice(wwo->nbsdDev);
+	wwo->state = WINE_WS_CLOSED;
+	wwo->dwFragmentSize = 0;
+	ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
+    }
+    return ret;
+}
+
+/**************************************************************************
+ * 				wodWrite			[internal]
+ *
+ */
+static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
+{
+    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
+
+    /* first, do the sanity checks... */
+    if (wDevID >= numOutDev || WOutDev[wDevID].state == WINE_WS_CLOSED) {
+        WARN("bad dev ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    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;
+
+    if ((lpWaveHdr->dwBufferLength & (WOutDev[wDevID].format.wf.nBlockAlign - 1)) != 0)
+    {
+        WARN("WaveHdr length isn't a multiple of the PCM block size: %ld %% %d\n",lpWaveHdr->dwBufferLength,WOutDev[wDevID].format.wf.nBlockAlign);
+        lpWaveHdr->dwBufferLength &= ~(WOutDev[wDevID].format.wf.nBlockAlign - 1);
+    }
+
+    NBSD_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE);
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodPrepare			[internal]
+ */
+static DWORD wodPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
+{
+    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
+
+    if (wDevID >= numOutDev) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
+	return WAVERR_STILLPLAYING;
+
+    lpWaveHdr->dwFlags |= WHDR_PREPARED;
+    lpWaveHdr->dwFlags &= ~WHDR_DONE;
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodUnprepare			[internal]
+ */
+static DWORD wodUnprepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
+{
+    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
+
+    if (wDevID >= numOutDev) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
+	return WAVERR_STILLPLAYING;
+
+    lpWaveHdr->dwFlags &= ~WHDR_PREPARED;
+    lpWaveHdr->dwFlags |= WHDR_DONE;
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 			wodPause				[internal]
+ */
+static DWORD wodPause(WORD wDevID)
+{
+    TRACE("(%u);!\n", wDevID);
+
+    if (wDevID >= numOutDev || WOutDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    NBSD_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_PAUSING, 0, TRUE);
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 			wodRestart				[internal]
+ */
+static DWORD wodRestart(WORD wDevID)
+{
+    TRACE("(%u);\n", wDevID);
+
+    if (wDevID >= numOutDev || WOutDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    NBSD_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESTARTING, 0, TRUE);
+
+    /* FIXME: is NotifyClient with WOM_DONE right ? (Comet Busters 1.3.3 needs this notification) */
+    /* FIXME: Myst crashes with this ... hmm -MM
+       return wodNotifyClient(wwo, WOM_DONE, 0L, 0L);
+    */
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 			wodReset				[internal]
+ */
+static DWORD wodReset(WORD wDevID)
+{
+    TRACE("(%u);\n", wDevID);
+
+    if (wDevID >= numOutDev || WOutDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    NBSD_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESETTING, 0, TRUE);
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodGetPosition			[internal]
+ */
+static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
+{
+    int			time;
+    DWORD		val;
+    WINE_WAVEOUT*	wwo;
+
+    TRACE("(%u, %p, %lu);\n", wDevID, lpTime, uSize);
+
+    if (wDevID >= numOutDev || WOutDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    if (lpTime == NULL)	return MMSYSERR_INVALPARAM;
+
+    wwo = &WOutDev[wDevID];
+#ifdef EXACT_WODPOSITION
+    NBSD_AddRingMessage(&wwo->msgRing, WINE_WM_UPDATE, 0, TRUE);
+#endif
+    val = wwo->dwPlayedTotal;
+
+    TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%lu nChannels=%u nAvgBytesPerSec=%lu\n",
+	  lpTime->wType, wwo->format.wBitsPerSample,
+	  wwo->format.wf.nSamplesPerSec, wwo->format.wf.nChannels,
+	  wwo->format.wf.nAvgBytesPerSec);
+    TRACE("dwPlayedTotal=%lu\n", val);
+
+    switch (lpTime->wType) {
+    case TIME_BYTES:
+	lpTime->u.cb = val;
+	TRACE("TIME_BYTES=%lu\n", lpTime->u.cb);
+	break;
+    case TIME_SAMPLES:
+	lpTime->u.sample = val * 8 / wwo->format.wBitsPerSample /wwo->format.wf.nChannels;
+	TRACE("TIME_SAMPLES=%lu\n", lpTime->u.sample);
+	break;
+    case TIME_SMPTE:
+	time = val / (wwo->format.wf.nAvgBytesPerSec / 1000);
+	lpTime->u.smpte.hour = time / (60 * 60 * 1000);
+	time -= lpTime->u.smpte.hour * (60 * 60 * 1000);
+	lpTime->u.smpte.min = time / (60 * 1000);
+	time -= lpTime->u.smpte.min * (60 * 1000);
+	lpTime->u.smpte.sec = time / 1000;
+	time -= lpTime->u.smpte.sec * 1000;
+	lpTime->u.smpte.frame = time * 30 / 1000;
+	lpTime->u.smpte.fps = 30;
+	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:
+	FIXME("Format %d not supported ! use TIME_MS !\n", lpTime->wType);
+	lpTime->wType = TIME_MS;
+    case TIME_MS:
+	lpTime->u.ms = val / (wwo->format.wf.nAvgBytesPerSec / 1000);
+	TRACE("TIME_MS=%lu\n", lpTime->u.ms);
+	break;
+    }
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodBreakLoop			[internal]
+ */
+static DWORD wodBreakLoop(WORD wDevID)
+{
+    TRACE("(%u);\n", wDevID);
+
+    if (wDevID >= numOutDev || WOutDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("bad device ID !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+    NBSD_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_BREAKLOOP, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodGetVolume			[internal]
+ */
+static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol)
+{
+    int		volume;
+    DWORD	left, right;
+    audio_info_t info;
+
+    TRACE("(%u, %p);\n", wDevID, lpdwVol);
+
+    if (lpdwVol == NULL)
+	return MMSYSERR_NOTENABLED;
+    if (wDevID >= numOutDev) 
+	return MMSYSERR_INVALPARAM;
+
+    if (ioctl(WOutDev[wDevID].nbsdDev->fd, AUDIO_GETINFO, &info) == -1) {
+	WARN("ioctl(%s, AUDIO_GETINFO) failed (%s)n", WOutDev[wDevID].nbsdDev->dev_name, strerror(errno));
+	return MMSYSERR_NOTENABLED;
+    }
+
+    volume = (info.play.gain - AUDIO_MIN_GAIN);
+    left = volume * (AUDIO_RIGHT_BALANCE - info.play.balance) /
+           (AUDIO_RIGHT_BALANCE - AUDIO_LEFT_BALANCE);
+    right = volume * (info.play.balance - AUDIO_LEFT_BALANCE) /
+           (AUDIO_RIGHT_BALANCE - AUDIO_LEFT_BALANCE);
+    TRACE("left=%ld right=%ld !\n", left, right);
+    *lpdwVol = ((left * 0xFFFFl) / (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN))
+             + (((right * 0xFFFFl) / (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) << 16);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodSetVolume			[internal]
+ */
+static DWORD wodSetVolume(WORD wDevID, DWORD dwParam)
+{
+    DWORD	left, right;
+    audio_info_t info;
+
+    TRACE("(%u, %08lX);\n", wDevID, dwParam);
+
+    AUDIO_INITINFO(&info);
+
+    left  = (LOWORD(dwParam) * (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) / 0xFFFFl;
+    right = (HIWORD(dwParam) * (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) / 0xFFFFl;
+    if (left + right == 0)
+        info.play.balance = AUDIO_MID_BALANCE;
+    else
+        info.play.balance = (left * AUDIO_LEFT_BALANCE 
+                           + right * AUDIO_RIGHT_BALANCE)
+                           / (left + right) + AUDIO_LEFT_BALANCE;
+    if (info.play.balance == AUDIO_RIGHT_BALANCE)
+        info.play.gain = AUDIO_MAX_GAIN;
+    else
+        info.play.gain = left * (AUDIO_RIGHT_BALANCE - AUDIO_LEFT_BALANCE)
+                       / (AUDIO_RIGHT_BALANCE - info.play.balance);
+
+    if (wDevID >= numOutDev) return MMSYSERR_INVALPARAM;
+
+    if (ioctl(WOutDev[wDevID].nbsdDev->fd, AUDIO_SETINFO, &info) == -1) {
+	WARN("ioctl(%s, AUDIO_SETINFO) failed (%s)\n", WOutDev[wDevID].nbsdDev->dev_name, strerror(errno));
+	return MMSYSERR_NOTENABLED;
+    } else {
+	TRACE("gain=%04x balance=%04x\n", info.play.gain, info.play.balance);
+    }
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				wodMessage (WINENBSD.4)
+ */
+DWORD WINAPI NBSD_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
+			    DWORD dwParam1, DWORD dwParam2)
+{
+    TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n",
+	  wDevID, wMsg, dwUser, dwParam1, dwParam2);
+
+    switch (wMsg) {
+    case DRVM_INIT:
+    case DRVM_EXIT:
+    case DRVM_ENABLE:
+    case DRVM_DISABLE:
+	/* FIXME: Pretend this is supported */
+	return 0;
+    case WODM_OPEN:	 	return wodOpen		(wDevID, (LPWAVEOPENDESC)dwParam1,	dwParam2);
+    case WODM_CLOSE:	 	return wodClose		(wDevID);
+    case WODM_WRITE:	 	return wodWrite		(wDevID, (LPWAVEHDR)dwParam1,		dwParam2);
+    case WODM_PAUSE:	 	return wodPause		(wDevID);
+    case WODM_GETPOS:	 	return wodGetPosition	(wDevID, (LPMMTIME)dwParam1, 		dwParam2);
+    case WODM_BREAKLOOP: 	return wodBreakLoop     (wDevID);
+    case WODM_PREPARE:	 	return wodPrepare	(wDevID, (LPWAVEHDR)dwParam1, 		dwParam2);
+    case WODM_UNPREPARE: 	return wodUnprepare	(wDevID, (LPWAVEHDR)dwParam1, 		dwParam2);
+    case WODM_GETDEVCAPS:	return wodGetDevCaps	(wDevID, (LPWAVEOUTCAPSA)dwParam1,	dwParam2);
+    case WODM_GETNUMDEVS:	return numOutDev;
+    case WODM_GETPITCH:	 	return MMSYSERR_NOTSUPPORTED;
+    case WODM_SETPITCH:	 	return MMSYSERR_NOTSUPPORTED;
+    case WODM_GETPLAYBACKRATE:	return MMSYSERR_NOTSUPPORTED;
+    case WODM_SETPLAYBACKRATE:	return MMSYSERR_NOTSUPPORTED;
+    case WODM_GETVOLUME:	return wodGetVolume	(wDevID, (LPDWORD)dwParam1);
+    case WODM_SETVOLUME:	return wodSetVolume	(wDevID, dwParam1);
+    case WODM_RESTART:		return wodRestart	(wDevID);
+    case WODM_RESET:		return wodReset		(wDevID);
+
+    case DRV_QUERYDEVICEINTERFACESIZE: return wdDevInterfaceSize       (wDevID, (LPDWORD)dwParam1);
+    case DRV_QUERYDEVICEINTERFACE:     return wdDevInterface           (wDevID, (PWCHAR)dwParam1, dwParam2);
+    case DRV_QUERYDSOUNDIFACE:	return wodDsCreate	(wDevID, (PIDSDRIVER*)dwParam1);
+    case DRV_QUERYDSOUNDDESC:	return wodDsDesc	(wDevID, (PDSDRIVERDESC)dwParam1);
+    case DRV_QUERYDSOUNDGUID:	return wodDsGuid	(wDevID, (LPGUID)dwParam1);
+    default:
+	FIXME("unknown message %d!\n", wMsg);
+    }
+    return MMSYSERR_NOTSUPPORTED;
+}
+
+/*======================================================================*
+ *                  Low level DSOUND definitions                        *
+ *======================================================================*/
+
+typedef struct IDsDriverPropertySetImpl IDsDriverPropertySetImpl;
+typedef struct IDsDriverNotifyImpl IDsDriverNotifyImpl;
+typedef struct IDsDriverImpl IDsDriverImpl;
+typedef struct IDsDriverBufferImpl IDsDriverBufferImpl;
+
+struct IDsDriverPropertySetImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsDriverPropertySet);
+    DWORD                       ref;
+
+    IDsDriverBufferImpl*        buffer;
+};
+
+struct IDsDriverNotifyImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsDriverNotify);
+    DWORD                       ref;
+
+    /* IDsDriverNotifyImpl fields */
+    LPDSBPOSITIONNOTIFY         notifies;
+    int                         nrofnotifies;
+
+    IDsDriverBufferImpl*        buffer;
+};
+
+struct IDsDriverImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsDriver);
+    DWORD                       ref;
+
+    /* IDsDriverImpl fields */
+    UINT                        wDevID;
+    IDsDriverBufferImpl*        primary;
+};
+
+struct IDsDriverBufferImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsDriverBuffer);
+    DWORD                       ref;
+
+    /* IDsDriverBufferImpl fields */
+    IDsDriverImpl*              drv;
+    DWORD                       buflen;
+    WAVEFORMATEX                wfx;
+    LPBYTE                      mapping;
+    DWORD                       maplen;
+    int                         fd;
+    DWORD                       dwFlags;
+
+    /* IDsDriverNotifyImpl fields */
+    IDsDriverNotifyImpl*        notify;
+    int                         notify_index;
+
+    /* IDsDriverPropertySetImpl fields */
+    IDsDriverPropertySetImpl*   property_set;
+};
+
+static HRESULT WINAPI IDsDriverPropertySetImpl_Create(
+    IDsDriverBufferImpl * dsdb,
+    IDsDriverPropertySetImpl **pdsdps);
+
+static HRESULT WINAPI IDsDriverNotifyImpl_Create(
+    IDsDriverBufferImpl * dsdb,
+    IDsDriverNotifyImpl **pdsdn);
+
+/*======================================================================*
+ *                  Low level DSOUND property set implementation        *
+ *======================================================================*/
+
+static HRESULT WINAPI IDsDriverPropertySetImpl_QueryInterface(
+    PIDSDRIVERPROPERTYSET iface,
+    REFIID riid,
+    LPVOID *ppobj) 
+{
+    ICOM_THIS(IDsDriverPropertySetImpl,iface);
+    TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsDriverPropertySet) ) {
+        IDsDriverPropertySet_AddRef(iface);
+        *ppobj = (LPVOID)This;
+        return DS_OK;
+    }
+
+    FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
+
+    *ppobj = 0;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI IDsDriverPropertySetImpl_AddRef(PIDSDRIVERPROPERTYSET iface) 
+{
+    ICOM_THIS(IDsDriverPropertySetImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedIncrement(&(This->ref));
+    return ref;
+}
+
+static ULONG WINAPI IDsDriverPropertySetImpl_Release(PIDSDRIVERPROPERTYSET iface) 
+{
+    ICOM_THIS(IDsDriverPropertySetImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedDecrement(&(This->ref));
+    if (ref == 0) {
+        IDsDriverBuffer_Release((PIDSDRIVERBUFFER)This->buffer);
+        HeapFree(GetProcessHeap(),0,This);
+        TRACE("(%p) released\n",This);
+    }
+    return ref;
+}
+
+static HRESULT WINAPI IDsDriverPropertySetImpl_Get(
+    PIDSDRIVERPROPERTYSET iface,
+    PDSPROPERTY pDsProperty,
+    LPVOID pPropertyParams,
+    ULONG cbPropertyParams,
+    LPVOID pPropertyData,
+    ULONG cbPropertyData,
+    PULONG pcbReturnedData )
+{
+    ICOM_THIS(IDsDriverPropertySetImpl,iface);
+    FIXME("(%p,%p,%p,%lx,%p,%lx,%p)\n",This,pDsProperty,pPropertyParams,cbPropertyParams,pPropertyData,cbPropertyData,pcbReturnedData);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverPropertySetImpl_Set(
+    PIDSDRIVERPROPERTYSET iface,
+    PDSPROPERTY pDsProperty,
+    LPVOID pPropertyParams,
+    ULONG cbPropertyParams,
+    LPVOID pPropertyData,
+    ULONG cbPropertyData )
+{
+    ICOM_THIS(IDsDriverPropertySetImpl,iface);
+    FIXME("(%p,%p,%p,%lx,%p,%lx)\n",This,pDsProperty,pPropertyParams,cbPropertyParams,pPropertyData,cbPropertyData);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverPropertySetImpl_QuerySupport(
+    PIDSDRIVERPROPERTYSET iface,
+    REFGUID PropertySetId,
+    ULONG PropertyId,
+    PULONG pSupport )
+{
+    ICOM_THIS(IDsDriverPropertySetImpl,iface);
+    FIXME("(%p,%s,%lx,%p)\n",This,debugstr_guid(PropertySetId),PropertyId,pSupport);
+    return DSERR_UNSUPPORTED;
+}
+
+ICOM_VTABLE(IDsDriverPropertySet) dsdpsvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    IDsDriverPropertySetImpl_QueryInterface,
+    IDsDriverPropertySetImpl_AddRef,
+    IDsDriverPropertySetImpl_Release,
+    IDsDriverPropertySetImpl_Get,
+    IDsDriverPropertySetImpl_Set,
+    IDsDriverPropertySetImpl_QuerySupport,
+};
+
+/*======================================================================*
+ *                  Low level DSOUND notify implementation              *
+ *======================================================================*/
+
+static HRESULT WINAPI IDsDriverNotifyImpl_QueryInterface(
+    PIDSDRIVERNOTIFY iface,
+    REFIID riid,
+    LPVOID *ppobj) 
+{
+    ICOM_THIS(IDsDriverNotifyImpl,iface);
+    TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsDriverNotify) ) {
+        IDsDriverNotify_AddRef(iface);
+        *ppobj = This;
+        return DS_OK;
+    }
+
+    FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
+
+    *ppobj = 0;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI IDsDriverNotifyImpl_AddRef(PIDSDRIVERNOTIFY iface) 
+{
+    ICOM_THIS(IDsDriverNotifyImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedIncrement(&(This->ref));
+    return ref;
+}
+
+static ULONG WINAPI IDsDriverNotifyImpl_Release(PIDSDRIVERNOTIFY iface) 
+{
+    ICOM_THIS(IDsDriverNotifyImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedDecrement(&(This->ref));
+    if (ref == 0) {
+        IDsDriverBuffer_Release((PIDSDRIVERBUFFER)This->buffer);
+        if (This->notifies != NULL)
+            HeapFree(GetProcessHeap(), 0, This->notifies);
+
+        HeapFree(GetProcessHeap(),0,This);
+        TRACE("(%p) released\n",This);
+    }
+
+    return ref;
+}
+
+static HRESULT WINAPI IDsDriverNotifyImpl_SetNotificationPositions(
+    PIDSDRIVERNOTIFY iface,
+    DWORD howmuch,
+    LPCDSBPOSITIONNOTIFY notify) 
+{
+    ICOM_THIS(IDsDriverNotifyImpl,iface);
+    TRACE("(%p,0x%08lx,%p)\n",This,howmuch,notify);
+
+    if (!notify) {
+        WARN("invalid parameter\n");
+        return DSERR_INVALIDPARAM;
+    }
+
+    if (TRACE_ON(wave)) {
+        int i;
+        for (i=0;i<howmuch;i++)
+            TRACE("notify at %ld to 0x%08lx\n",
+                notify[i].dwOffset,(DWORD)notify[i].hEventNotify);
+    }
+
+    /* Make an internal copy of the caller-supplied array.
+     * Replace the existing copy if one is already present. */
+    if (This->notifies) 
+        This->notifies = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
+        This->notifies, howmuch * sizeof(DSBPOSITIONNOTIFY));
+    else 
+        This->notifies = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
+        howmuch * sizeof(DSBPOSITIONNOTIFY));
+
+    memcpy(This->notifies, notify, howmuch * sizeof(DSBPOSITIONNOTIFY));
+    This->nrofnotifies = howmuch;
+
+    return S_OK;
+}
+
+ICOM_VTABLE(IDsDriverNotify) dsdnvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    IDsDriverNotifyImpl_QueryInterface,
+    IDsDriverNotifyImpl_AddRef,
+    IDsDriverNotifyImpl_Release,
+    IDsDriverNotifyImpl_SetNotificationPositions,
+};
+
+/*======================================================================*
+ *                  Low level DSOUND implementation                     *
+ *======================================================================*/
+
+static HRESULT DSDB_MapBuffer(IDsDriverBufferImpl *dsdb)
+{
+    TRACE("(%p)\n",dsdb);
+    if (!dsdb->mapping) {
+        dsdb->mapping = mmap(NULL, dsdb->maplen, PROT_WRITE, MAP_SHARED,
+                             dsdb->fd, 0);
+        if (dsdb->mapping == (LPBYTE)-1) {
+            TRACE("(%p): Could not map sound device for direct access (%s)\n", dsdb, strerror(errno));
+            return DSERR_GENERIC;
+        }
+        TRACE("(%p): sound device has been mapped for direct access at %p, size=%ld\n", dsdb, dsdb->mapping, dsdb->maplen);
+
+	/* for some reason, es1371 and sblive! sometimes have junk in here.
+	 * clear it, or we get junk noise */
+	/* some libc implementations are buggy: their memset reads from the buffer...
+	 * to work around it, we have to zero the block by hand. We don't do the expected:
+	 * memset(dsdb->mapping,0, dsdb->maplen);
+	 */
+	{
+	    unsigned char*	p1 = dsdb->mapping;
+	    unsigned		len = dsdb->maplen;
+	    unsigned char	silence = (dsdb->wfx.wBitsPerSample == 8) ? 128 : 0;
+	    unsigned long	ulsilence = (dsdb->wfx.wBitsPerSample == 8) ? 0x80808080 : 0;
+
+	    if (len >= 16) /* so we can have at least a 4 long area to store... */
+	    {
+		/* the mmap:ed value is (at least) dword aligned
+		 * so, start filling the complete unsigned long:s
+		 */
+		int		b = len >> 2;
+		unsigned long*	p4 = (unsigned long*)p1;
+
+		while (b--) *p4++ = ulsilence;
+		/* prepare for filling the rest */
+		len &= 3;
+		p1 = (unsigned char*)p4;
+	    }
+	    /* in all cases, fill the remaining bytes */
+	    while (len-- != 0) *p1++ = silence;
+	}
+    }
+    return DS_OK;
+}
+
+static HRESULT DSDB_UnmapBuffer(IDsDriverBufferImpl *dsdb)
+{
+    TRACE("(%p)\n",dsdb);
+    if (dsdb->mapping) {
+        if (munmap(dsdb->mapping, dsdb->maplen) < 0) {
+            ERR("(%p): Could not unmap sound device (%s)\n", dsdb, strerror(errno));
+            return DSERR_GENERIC;
+        }
+        dsdb->mapping = NULL;
+        TRACE("(%p): sound device unmapped\n", dsdb);
+    }
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_QueryInterface(PIDSDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj)
+{
+    ICOM_THIS(IDsDriverBufferImpl,iface);
+    TRACE("(%p,%s,%p)\n",iface,debugstr_guid(riid),*ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsDriverBuffer) ) {
+	IDsDriverBuffer_AddRef(iface);
+	*ppobj = (LPVOID)This;
+	return DS_OK;
+    }
+
+    if ( IsEqualGUID( &IID_IDsDriverNotify, riid ) ) {
+        if (!This->notify)
+            IDsDriverNotifyImpl_Create(This, &(This->notify));
+        if (This->notify) {
+            IDsDriverNotify_AddRef((PIDSDRIVERNOTIFY)This->notify);
+            *ppobj = (LPVOID)This->notify;
+            return DS_OK;
+        }
+        *ppobj = 0;
+        return E_FAIL;
+    }
+
+    if ( IsEqualGUID( &IID_IDsDriverPropertySet, riid ) ) {
+        if (!This->property_set)
+            IDsDriverPropertySetImpl_Create(This, &(This->property_set));
+        if (This->property_set) {
+            IDsDriverPropertySet_AddRef((PIDSDRIVERPROPERTYSET)This->property_set);
+            *ppobj = (LPVOID)This->property_set;
+            return DS_OK;
+        }
+	*ppobj = 0;
+	return E_FAIL;
+    }
+
+    FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
+
+    *ppobj = 0;
+
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI IDsDriverBufferImpl_AddRef(PIDSDRIVERBUFFER iface)
+{
+    ICOM_THIS(IDsDriverBufferImpl,iface);
+    TRACE("(%p)\n",This);
+    This->ref++;
+    TRACE("ref=%ld\n",This->ref);
+    return This->ref;
+}
+
+static ULONG WINAPI IDsDriverBufferImpl_Release(PIDSDRIVERBUFFER iface)
+{
+    ICOM_THIS(IDsDriverBufferImpl,iface);
+    TRACE("(%p)\n",This);
+    if (--This->ref) {
+	TRACE("ref=%ld\n",This->ref);
+	return This->ref;
+    }
+    if (This == This->drv->primary)
+	This->drv->primary = NULL;
+    DSDB_UnmapBuffer(This);
+    HeapFree(GetProcessHeap(),0,This);
+    TRACE("ref=0\n");
+    return 0;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Lock(PIDSDRIVERBUFFER iface,
+					       LPVOID*ppvAudio1,LPDWORD pdwLen1,
+					       LPVOID*ppvAudio2,LPDWORD pdwLen2,
+					       DWORD dwWritePosition,DWORD dwWriteLen,
+					       DWORD dwFlags)
+{
+    /* ICOM_THIS(IDsDriverBufferImpl,iface); */
+    /* since we (GetDriverDesc flags) have specified DSDDESC_DONTNEEDPRIMARYLOCK,
+     * and that we don't support secondary buffers, this method will never be called */
+    TRACE("(%p): stub\n",iface);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Unlock(PIDSDRIVERBUFFER iface,
+						 LPVOID pvAudio1,DWORD dwLen1,
+						 LPVOID pvAudio2,DWORD dwLen2)
+{
+    /* ICOM_THIS(IDsDriverBufferImpl,iface); */
+    TRACE("(%p): stub\n",iface);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_SetFormat(PIDSDRIVERBUFFER iface,
+						    LPWAVEFORMATEX pwfx)
+{
+    /* ICOM_THIS(IDsDriverBufferImpl,iface); */
+
+    TRACE("(%p,%p)\n",iface,pwfx);
+    /* On our request (GetDriverDesc flags), DirectSound has by now used
+     * waveOutClose/waveOutOpen to set the format...
+     * unfortunately, this means our mmap() is now gone...
+     * so we need to somehow signal to our DirectSound implementation
+     * that it should completely recreate this HW buffer...
+     * this unexpected error code should do the trick... */
+    return DSERR_BUFFERLOST;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_SetFrequency(PIDSDRIVERBUFFER iface, DWORD dwFreq)
+{
+    /* ICOM_THIS(IDsDriverBufferImpl,iface); */
+    TRACE("(%p,%ld): stub\n",iface,dwFreq);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_SetVolumePan(PIDSDRIVERBUFFER iface, PDSVOLUMEPAN pVolPan)
+{
+    DWORD vol;
+    ICOM_THIS(IDsDriverBufferImpl,iface);
+    TRACE("(%p,%p)\n",This,pVolPan);
+
+    vol = pVolPan->dwTotalLeftAmpFactor | (pVolPan->dwTotalRightAmpFactor << 16);
+
+    if (wodSetVolume(This->drv->wDevID, vol) != MMSYSERR_NOERROR) {
+	WARN("wodSetVolume failed\n");
+	return DSERR_INVALIDPARAM;
+    }
+
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_SetPosition(PIDSDRIVERBUFFER iface, DWORD dwNewPos)
+{
+    /* ICOM_THIS(IDsDriverImpl,iface); */
+    TRACE("(%p,%ld): stub\n",iface,dwNewPos);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_GetPosition(PIDSDRIVERBUFFER iface,
+						      LPDWORD lpdwPlay, LPDWORD lpdwWrite)
+{
+    ICOM_THIS(IDsDriverBufferImpl,iface);
+    audio_offset_t offs;
+    DWORD ptr;
+
+    TRACE("(%p)\n",iface);
+    if (WOutDev[This->drv->wDevID].state == WINE_WS_CLOSED) {
+	ERR("device not open, but accessing?\n");
+	return DSERR_UNINITIALIZED;
+    }
+    if (ioctl(This->fd, AUDIO_GETOOFFS, &offs) < 0) {
+	ERR("ioctl(%s, AUDIO_GETOOFFS) failed (%s)\n",
+            WOutDev[This->drv->wDevID].nbsdDev->dev_name, strerror(errno));
+	return DSERR_GENERIC;
+    }
+    ptr = offs.offset & ~3; /* align the pointer, just in case */
+    if (lpdwPlay) *lpdwPlay = ptr;
+    if (lpdwWrite) {
+	/* add some safety margin (not strictly necessary, but...) */
+	if (WOutDev[This->drv->wDevID].nbsdDev->out_caps.dwSupport & WAVECAPS_SAMPLEACCURATE)
+	    *lpdwWrite = ptr + 32;
+	else
+	    *lpdwWrite = ptr + WOutDev[This->drv->wDevID].dwFragmentSize;
+	while (*lpdwWrite > This->buflen)
+	    *lpdwWrite -= This->buflen;
+    }
+    TRACE("playpos=%ld, writepos=%ld\n", lpdwPlay?*lpdwPlay:0, lpdwWrite?*lpdwWrite:0);
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Play(PIDSDRIVERBUFFER iface, DWORD dwRes1, DWORD dwRes2, DWORD dwFlags)
+{
+    ICOM_THIS(IDsDriverBufferImpl,iface);
+    audio_info_t info;
+
+    TRACE("(%p,%lx,%lx,%lx)\n",iface,dwRes1,dwRes2,dwFlags);
+    WOutDev[This->drv->wDevID].nbsdDev->bOutputEnabled = TRUE;
+    AUDIO_INITINFO(&info);
+    info.play.pause = 0;
+    if (ioctl(This->fd, AUDIO_SETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_SETINFO pause = 0 ) failed (%s)\n",WOutDev[This->drv->wDevID].nbsdDev->dev_name, strerror(errno));
+	WOutDev[This->drv->wDevID].nbsdDev->bOutputEnabled = FALSE;
+	return DSERR_GENERIC;
+    }
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverBufferImpl_Stop(PIDSDRIVERBUFFER iface)
+{
+    ICOM_THIS(IDsDriverBufferImpl,iface);
+    audio_info_t info;
+
+    TRACE("(%p)\n",iface);
+    /* no more playing */
+    WOutDev[This->drv->wDevID].nbsdDev->bOutputEnabled = FALSE;
+    AUDIO_INITINFO(&info);
+    info.play.pause = 1;
+    if (ioctl(This->fd, AUDIO_SETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_SETINFO pause = 1) failed (%s)\n", WOutDev[This->drv->wDevID].nbsdDev->dev_name, strerror(errno));
+	return DSERR_GENERIC;
+    }
+#if 0
+    /* the play position must be reset to the beginning of the buffer */
+    if (ioctl(This->fd, AUDIO_FLUSH) < 0) {
+	ERR("ioctl(%s, AUDIO_FLUSH) failed (%s)\n", WOutDev[This->drv->wDevID].nbsdDev->dev_name, strerror(errno));
+	return DSERR_GENERIC;
+    }
+#endif
+    /* Most drivers just can't stop the playback without closing the device...
+     * so we need to somehow signal to our DirectSound implementation
+     * that it should completely recreate this HW buffer...
+     * this unexpected error code should do the trick... */
+    /* FIXME: ...unless we are doing full duplex, then its not nice to close the device */
+    if (WOutDev[This->drv->wDevID].nbsdDev->open_count == 1)
+	return DSERR_BUFFERLOST;
+
+    return DS_OK;
+}
+
+static ICOM_VTABLE(IDsDriverBuffer) dsdbvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    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_QueryInterface(PIDSDRIVER iface, REFIID riid, LPVOID *ppobj)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsDriver) ) {
+	IDsDriver_AddRef(iface);
+	*ppobj = (LPVOID)This;
+	return DS_OK;
+    }
+
+    FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
+
+    *ppobj = 0;
+
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI IDsDriverImpl_AddRef(PIDSDRIVER iface)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    TRACE("(%p)\n",This);
+    This->ref++;
+    TRACE("ref=%ld\n",This->ref);
+    return This->ref;
+}
+
+static ULONG WINAPI IDsDriverImpl_Release(PIDSDRIVER iface)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    TRACE("(%p)\n",This);
+    if (--This->ref) {
+	TRACE("ref=%ld\n",This->ref);
+	return This->ref;
+    }
+    HeapFree(GetProcessHeap(),0,This);
+    TRACE("ref=0\n");
+    return 0;
+}
+
+static HRESULT WINAPI IDsDriverImpl_GetDriverDesc(PIDSDRIVER iface, PDSDRIVERDESC pDesc)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    TRACE("(%p,%p)\n",iface,pDesc);
+
+    /* copy version from driver */
+    memcpy(pDesc, &(WOutDev[This->wDevID].nbsdDev->ds_desc), sizeof(DSDRIVERDESC));
+
+    pDesc->dwFlags |= DSDDESC_DOMMSYSTEMOPEN | DSDDESC_DOMMSYSTEMSETFORMAT |
+	DSDDESC_USESYSTEMMEMORY | DSDDESC_DONTNEEDPRIMARYLOCK;
+    pDesc->dnDevNode		= WOutDev[This->wDevID].waveDesc.dnDevNode;
+    pDesc->wVxdId		= 0;
+    pDesc->wReserved		= 0;
+    pDesc->ulDeviceNum		= This->wDevID;
+    pDesc->dwHeapType		= DSDHEAP_NOHEAP;
+    pDesc->pvDirectDrawHeap	= NULL;
+    pDesc->dwMemStartAddress	= 0;
+    pDesc->dwMemEndAddress	= 0;
+    pDesc->dwMemAllocExtra	= 0;
+    pDesc->pvReserved1		= NULL;
+    pDesc->pvReserved2		= NULL;
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverImpl_Open(PIDSDRIVER iface)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    audio_info_t info;
+
+    TRACE("(%p)\n",iface);
+
+    AUDIO_INITINFO(&info);
+    info.play.pause = 1;
+
+    /* make sure the card doesn't start playing before we want it to */
+    WOutDev[This->wDevID].nbsdDev->bOutputEnabled = FALSE;
+    if (ioctl(WOutDev[This->wDevID].nbsdDev->fd, AUDIO_SETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_SETINFO pause=1) failed (%s)\n",WOutDev[This->wDevID].nbsdDev->dev_name, strerror(errno));
+	return DSERR_GENERIC;
+    }
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverImpl_Close(PIDSDRIVER iface)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    TRACE("(%p)\n",iface);
+    if (This->primary) {
+	ERR("problem with DirectSound: primary not released\n");
+	return DSERR_GENERIC;
+    }
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverImpl_GetCaps(PIDSDRIVER iface, PDSDRIVERCAPS pCaps)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    TRACE("(%p,%p)\n",iface,pCaps);
+    memcpy(pCaps, &(WOutDev[This->wDevID].nbsdDev->ds_caps), sizeof(DSDRIVERCAPS));
+    return DS_OK;
+}
+
+static HRESULT WINAPI DSD_CreatePrimaryBuffer(PIDSDRIVER iface,
+                                              LPWAVEFORMATEX pwfx,
+                                              DWORD dwFlags, 
+                                              DWORD dwCardAddress,
+                                              LPDWORD pdwcbBufferSize,
+                                              LPBYTE *ppbBuffer,
+                                              LPVOID *ppvObj)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj;
+    HRESULT err;
+    audio_info_t info;
+    TRACE("(%p,%p,%lx,%lx,%p,%p,%p)\n",iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj);
+
+    if (This->primary)
+	return DSERR_ALLOCATED;
+    if (dwFlags & (DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN))
+	return DSERR_CONTROLUNAVAIL;
+
+    *ippdsdb = (IDsDriverBufferImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDsDriverBufferImpl));
+    if (*ippdsdb == NULL)
+	return DSERR_OUTOFMEMORY;
+    (*ippdsdb)->lpVtbl  = &dsdbvt;
+    (*ippdsdb)->ref	= 1;
+    (*ippdsdb)->drv	= This;
+    (*ippdsdb)->wfx     = *pwfx;
+    (*ippdsdb)->fd      = WOutDev[This->wDevID].nbsdDev->fd;
+    (*ippdsdb)->dwFlags = dwFlags;
+
+
+    /* check how big the DMA buffer is now */
+    if (ioctl((*ippdsdb)->fd, AUDIO_GETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_GETINFO) failed (%s)\n",
+            WOutDev[This->wDevID].nbsdDev->dev_name, strerror(errno));
+	HeapFree(GetProcessHeap(),0,*ippdsdb);
+	*ippdsdb = NULL;
+	return DSERR_GENERIC;
+    }
+    (*ippdsdb)->maplen = (*ippdsdb)->buflen = info.play.buffer_size;
+
+    /* map the DMA buffer */
+    err = DSDB_MapBuffer(*ippdsdb);
+    if (err != DS_OK) {
+	HeapFree(GetProcessHeap(),0,*ippdsdb);
+	*ippdsdb = NULL;
+	return err;
+    }
+
+    /* primary buffer is ready to go */
+    *pdwcbBufferSize    = (*ippdsdb)->maplen;
+    *ppbBuffer          = (*ippdsdb)->mapping;
+
+    AUDIO_INITINFO(&info);
+
+    /* some drivers need some extra nudging after mapping */
+    WOutDev[This->wDevID].nbsdDev->bOutputEnabled = FALSE;
+    info.play.pause = 1;
+    if (ioctl((*ippdsdb)->fd, AUDIO_SETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_SETINFO pause=1) failed (%s)\n",
+            WOutDev[This->wDevID].nbsdDev->dev_name, strerror(errno));
+	return DSERR_GENERIC;
+    }
+
+    This->primary = *ippdsdb;
+
+    return DS_OK;
+}
+
+static HRESULT WINAPI DSD_CreateSecondaryBuffer(PIDSDRIVER iface,
+                                                LPWAVEFORMATEX pwfx,
+                                                DWORD dwFlags, 
+                                                DWORD dwCardAddress,
+                                                LPDWORD pdwcbBufferSize,
+                                                LPBYTE *ppbBuffer,
+                                                LPVOID *ppvObj)
+{
+    ICOM_THIS(IDsDriverImpl,iface);
+    IDsDriverBufferImpl** ippdsdb = (IDsDriverBufferImpl**)ppvObj;
+    FIXME("(%p,%p,%lx,%lx,%p,%p,%p): stub\n",This,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj);
+
+    *ippdsdb = 0;
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsDriverImpl_CreateSoundBuffer(PIDSDRIVER iface,
+                                                      LPWAVEFORMATEX pwfx,
+                                                      DWORD dwFlags, 
+                                                      DWORD dwCardAddress,
+                                                      LPDWORD pdwcbBufferSize,
+                                                      LPBYTE *ppbBuffer,
+                                                      LPVOID *ppvObj)
+{
+    TRACE("(%p,%p,%lx,%lx,%p,%p,%p)\n",iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj);
+
+    if (dwFlags & DSBCAPS_PRIMARYBUFFER)
+        return DSD_CreatePrimaryBuffer(iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj);
+
+    return DSD_CreateSecondaryBuffer(iface,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj);
+}
+
+static HRESULT WINAPI IDsDriverImpl_DuplicateSoundBuffer(PIDSDRIVER iface,
+							 PIDSDRIVERBUFFER pBuffer,
+							 LPVOID *ppvObj)
+{
+    /* ICOM_THIS(IDsDriverImpl,iface); */
+    TRACE("(%p,%p): stub\n",iface,pBuffer);
+    return DSERR_INVALIDCALL;
+}
+
+static ICOM_VTABLE(IDsDriver) dsdvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    IDsDriverImpl_QueryInterface,
+    IDsDriverImpl_AddRef,
+    IDsDriverImpl_Release,
+    IDsDriverImpl_GetDriverDesc,
+    IDsDriverImpl_Open,
+    IDsDriverImpl_Close,
+    IDsDriverImpl_GetCaps,
+    IDsDriverImpl_CreateSoundBuffer,
+    IDsDriverImpl_DuplicateSoundBuffer
+};
+
+static HRESULT WINAPI IDsDriverPropertySetImpl_Create(
+    IDsDriverBufferImpl * dsdb,
+    IDsDriverPropertySetImpl **pdsdps)
+{
+    IDsDriverPropertySetImpl * dsdps;
+    TRACE("(%p,%p)\n",dsdb,pdsdps);
+
+    dsdps = (IDsDriverPropertySetImpl*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dsdps));
+    if (dsdps == NULL) {
+        WARN("out of memory\n");
+        return DSERR_OUTOFMEMORY;
+    }
+                                                                                
+    dsdps->ref = 0;
+    dsdps->lpVtbl = &dsdpsvt;
+    dsdps->buffer = dsdb;
+    dsdb->property_set = dsdps;
+    IDsDriverBuffer_AddRef((PIDSDRIVER)dsdb);
+                                                                                
+    *pdsdps = dsdps;
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsDriverNotifyImpl_Create(
+    IDsDriverBufferImpl * dsdb,
+    IDsDriverNotifyImpl **pdsdn)
+{
+    IDsDriverNotifyImpl * dsdn;
+    TRACE("(%p,%p)\n",dsdb,pdsdn);
+
+    dsdn = (IDsDriverNotifyImpl*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dsdn));
+                                                                                
+    if (dsdn == NULL) {
+        WARN("out of memory\n");
+        return DSERR_OUTOFMEMORY;
+    }
+                                                                                
+    dsdn->ref = 0;
+    dsdn->lpVtbl = &dsdnvt;
+    dsdn->buffer = dsdb;
+    dsdb->notify = dsdn;
+    IDsDriverBuffer_AddRef((PIDSDRIVER)dsdb);
+                                                                                
+    *pdsdn = dsdn;
+    return DS_OK;
+};
+
+static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv)
+{
+    IDsDriverImpl** idrv = (IDsDriverImpl**)drv;
+    TRACE("(%d,%p)\n",wDevID,drv);
+
+    /* the HAL isn't much better than the HEL if we can't do mmap() */
+    if (!(WOutDev[wDevID].nbsdDev->out_caps.dwSupport & WAVECAPS_DIRECTSOUND)) {
+	ERR("DirectSound flag not set\n");
+	MESSAGE("This sound card's driver does not support direct access\n");
+	MESSAGE("The (slower) DirectSound HEL mode will be used instead.\n");
+	return MMSYSERR_NOTSUPPORTED;
+    }
+
+    *idrv = (IDsDriverImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDsDriverImpl));
+    if (!*idrv)
+	return MMSYSERR_NOMEM;
+    (*idrv)->lpVtbl	= &dsdvt;
+    (*idrv)->ref	= 1;
+
+    (*idrv)->wDevID	= wDevID;
+    (*idrv)->primary	= NULL;
+    return MMSYSERR_NOERROR;
+}
+
+static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc)
+{
+    TRACE("(%d,%p)\n",wDevID,desc);
+    memcpy(desc, &(WOutDev[wDevID].nbsdDev->ds_desc), sizeof(DSDRIVERDESC));
+    return MMSYSERR_NOERROR;
+}
+
+static DWORD wodDsGuid(UINT wDevID, LPGUID pGuid)
+{
+    TRACE("(%d,%p)\n",wDevID,pGuid);
+    memcpy(pGuid, &(WOutDev[wDevID].nbsdDev->ds_guid), sizeof(GUID));
+    return MMSYSERR_NOERROR;
+}
+
+/*======================================================================*
+ *                  Low level WAVE IN implementation			*
+ *======================================================================*/
+
+/**************************************************************************
+ * 			widNotifyClient			[internal]
+ */
+static DWORD widNotifyClient(WINE_WAVEIN* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
+{
+    TRACE("wMsg = 0x%04x (%s) dwParm1 = %04lX dwParam2 = %04lX\n", wMsg,
+        wMsg == WIM_OPEN ? "WIM_OPEN" : wMsg == WIM_CLOSE ? "WIM_CLOSE" :
+        wMsg == WIM_DATA ? "WIM_DATA" : "Unknown", 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;
+}
+
+/**************************************************************************
+ * 			widGetDevCaps				[internal]
+ */
+static DWORD widGetDevCaps(WORD wDevID, LPWAVEINCAPSA lpCaps, DWORD dwSize)
+{
+    TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize);
+
+    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
+
+    if (wDevID >= numInDev) {
+	TRACE("numOutDev reached !\n");
+	return MMSYSERR_BADDEVICEID;
+    }
+
+    memcpy(lpCaps, &WInDev[wDevID].nbsdDev->in_caps, min(dwSize, sizeof(*lpCaps)));
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				widRecorder_ReadHeaders		[internal]
+ */
+static void widRecorder_ReadHeaders(WINE_WAVEIN * wwi)
+{
+    enum win_wm_message tmp_msg;
+    DWORD		tmp_param;
+    HANDLE		tmp_ev;
+    WAVEHDR*		lpWaveHdr;
+
+    while (NBSD_RetrieveRingMessage(&wwi->msgRing, &tmp_msg, &tmp_param, &tmp_ev)) {
+        if (tmp_msg == WINE_WM_HEADER) {
+	    LPWAVEHDR*	wh;
+	    lpWaveHdr = (LPWAVEHDR)tmp_param;
+	    lpWaveHdr->lpNext = 0;
+
+	    if (wwi->lpQueuePtr == 0)
+		wwi->lpQueuePtr = lpWaveHdr;
+	    else {
+	        for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
+	        *wh = lpWaveHdr;
+	    }
+	} else {
+            ERR("should only have headers left\n");
+        }
+    }
+}
+
+/**************************************************************************
+ * 				widRecorder			[internal]
+ */
+static	DWORD	CALLBACK	widRecorder(LPVOID pmt)
+{
+    WORD		uDevID = (DWORD)pmt;
+    WINE_WAVEIN*	wwi = (WINE_WAVEIN*)&WInDev[uDevID];
+    WAVEHDR*		lpWaveHdr;
+    DWORD		dwSleepTime;
+    DWORD		bytesRead;
+    LPVOID		buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, wwi->dwFragmentSize);
+    char               *pOffset = buffer;
+    enum win_wm_message msg;
+    DWORD		param;
+    HANDLE		ev;
+    audio_info_t	info;
+    audio_offset_t	offs;
+    int			xs;
+
+    wwi->state = WINE_WS_STOPPED;
+    wwi->dwTotalRecorded = 0;
+    wwi->lpQueuePtr = NULL;
+
+    SetEvent(wwi->hStartUpEvent);
+
+    AUDIO_INITINFO(&info);
+
+    /* disable input so capture will begin when triggered */
+    wwi->nbsdDev->bInputEnabled = FALSE;
+    info.record.pause = 1;
+    if (ioctl(wwi->nbsdDev->fd, AUDIO_SETINFO, &info) < 0)
+	ERR("ioctl(%s, AUDIO_SETINFO pause=1) failed (%s)\n", wwi->nbsdDev->dev_name, strerror(errno));
+
+    /* the soundblaster live needs a micro wake to get its recording started
+     * (or GETISPACE will have 0 frags all the time)
+     */
+    read(wwi->nbsdDev->fd, &xs, 4);
+
+    /* make sleep time to be # of ms to output a fragment */
+    dwSleepTime = (wwi->dwFragmentSize * 1000) / wwi->format.wf.nAvgBytesPerSec;
+    TRACE("sleeptime=%ld ms\n", dwSleepTime);
+
+    for (;;) {
+	/* wait for dwSleepTime or an event in thread's queue */
+	/* FIXME: could improve wait time depending on queue state,
+	 * ie, number of queued fragments
+	 */
+
+	if (wwi->lpQueuePtr != NULL && wwi->state == WINE_WS_PLAYING)
+        {
+            lpWaveHdr = wwi->lpQueuePtr;
+
+	    ioctl(wwi->nbsdDev->fd, AUDIO_GETINFO, &info);
+	    ioctl(wwi->nbsdDev->fd, AUDIO_GETIOFFS, &offs);
+            TRACE("info={frag=%d fsize=%d ftotal=%d bytes=%d}\n", offs.deltablks, info.blocksize, info.hiwat, info.record.buffer_size);
+
+            /* read all the fragments accumulated so far */
+            while ((offs.deltablks > 0) && (wwi->lpQueuePtr))
+            {
+                offs.deltablks --;
+
+                if (lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded >= wwi->dwFragmentSize)
+                {
+                    /* directly read fragment in wavehdr */
+                    bytesRead = read(wwi->nbsdDev->fd,
+		      		     lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded,
+                                     wwi->dwFragmentSize);
+
+                    TRACE("bytesRead=%ld (direct)\n", bytesRead);
+		    if (bytesRead != (DWORD) -1)
+		    {
+			/* update number of bytes recorded in current buffer and by this device */
+                        lpWaveHdr->dwBytesRecorded += bytesRead;
+			wwi->dwTotalRecorded       += bytesRead;
+
+			/* buffer is full. notify client */
+			if (lpWaveHdr->dwBytesRecorded == lpWaveHdr->dwBufferLength)
+			{
+			    /* must copy the value of next waveHdr, because we have no idea of what
+			     * will be done with the content of lpWaveHdr in callback
+			     */
+			    LPWAVEHDR	lpNext = lpWaveHdr->lpNext;
+
+			    lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+			    lpWaveHdr->dwFlags |=  WHDR_DONE;
+
+			    wwi->lpQueuePtr = lpNext;
+			    widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
+			    lpWaveHdr = lpNext;
+			}
+                    }
+                }
+                else
+		{
+                    /* read the fragment in a local buffer */
+                    bytesRead = read(wwi->nbsdDev->fd, buffer, wwi->dwFragmentSize);
+                    pOffset = buffer;
+
+                    TRACE("bytesRead=%ld (local)\n", bytesRead);
+
+                    /* copy data in client buffers */
+                    while (bytesRead != (DWORD) -1 && bytesRead > 0)
+                    {
+                        DWORD dwToCopy = min (bytesRead, lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded);
+
+                        memcpy(lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded,
+                               pOffset,
+                               dwToCopy);
+
+                        /* update number of bytes recorded in current buffer and by this device */
+                        lpWaveHdr->dwBytesRecorded += dwToCopy;
+                        wwi->dwTotalRecorded += dwToCopy;
+                        bytesRead -= dwToCopy;
+                        pOffset   += dwToCopy;
+
+                        /* client buffer is full. notify client */
+                        if (lpWaveHdr->dwBytesRecorded == lpWaveHdr->dwBufferLength)
+                        {
+			    /* must copy the value of next waveHdr, because we have no idea of what
+			     * will be done with the content of lpWaveHdr in callback
+			     */
+			    LPWAVEHDR	lpNext = lpWaveHdr->lpNext;
+			    TRACE("lpNext=%p\n", lpNext);
+
+                            lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+                            lpWaveHdr->dwFlags |=  WHDR_DONE;
+
+			    wwi->lpQueuePtr = lpNext;
+                            widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
+
+			    lpWaveHdr = lpNext;
+			    if (!lpNext && bytesRead) {
+				/* before we give up, check for more header messages */
+				while (NBSD_PeekRingMessage(&wwi->msgRing, &msg, &param, &ev))
+				{
+				    if (msg == WINE_WM_HEADER) {
+					LPWAVEHDR hdr;
+					NBSD_RetrieveRingMessage(&wwi->msgRing, &msg, &param, &ev);
+					hdr = ((LPWAVEHDR)param);
+					TRACE("msg = %s, hdr = %p, ev = %p\n", wodPlayerCmdString[msg - WM_USER - 1], hdr, ev);
+					hdr->lpNext = 0;
+					if (lpWaveHdr == 0) {
+					    /* new head of queue */
+					    wwi->lpQueuePtr = lpWaveHdr = hdr;
+					} else {
+					    /* insert buffer at the end of queue */
+					    LPWAVEHDR*  wh;
+					    for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
+					    *wh = hdr;
+					}
+				    } else
+					break;
+				}
+
+				if (lpWaveHdr == 0) {
+                                    /* no more buffer to copy data to, but we did read more.
+                                     * what hasn't been copied will be dropped
+                                     */
+                                    WARN("buffer under run! %lu bytes dropped.\n", bytesRead);
+                                    wwi->lpQueuePtr = NULL;
+                                    break;
+				}
+                            }
+                        }
+                    }
+                }
+            }
+	}
+
+	WAIT_OMR(&wwi->msgRing, dwSleepTime);
+
+	while (NBSD_RetrieveRingMessage(&wwi->msgRing, &msg, &param, &ev))
+	{
+            TRACE("msg=%s param=0x%lx\n", wodPlayerCmdString[msg - WM_USER - 1], param);
+	    switch (msg) {
+	    case WINE_WM_PAUSING:
+		wwi->state = WINE_WS_PAUSED;
+                /*FIXME("Device should stop recording\n");*/
+		SetEvent(ev);
+		break;
+	    case WINE_WM_STARTING:
+		wwi->state = WINE_WS_PLAYING;
+
+                /* start the recording */
+	        wwi->nbsdDev->bInputEnabled = TRUE;
+                info.record.pause = 0;
+                if (ioctl(wwi->nbsdDev->fd, AUDIO_SETINFO, &info) < 0) {
+		    wwi->nbsdDev->bInputEnabled = FALSE;
+                    ERR("ioctl(%s, AUDIO_SETINFO pause=0) failed (%s)\n", wwi->nbsdDev->dev_name, strerror(errno));
+                }
+
+		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)
+		{
+                    /* stop the recording */
+		    wwi->nbsdDev->bInputEnabled = FALSE;
+		    AUDIO_INITINFO(&info);
+		    info.record.pause = 1;
+                    if (ioctl(wwi->nbsdDev->fd, AUDIO_SETINFO, &info) < 0) {
+                        ERR("ioctl(%s, AUDIO_SETINFO) failed (%s)\n", wwi->nbsdDev->dev_name, strerror(errno));
+		    }
+
+		    /* read any headers in queue */
+		    widRecorder_ReadHeaders(wwi);
+
+		    /* 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);
+		    }
+		}
+		wwi->state = WINE_WS_STOPPED;
+		SetEvent(ev);
+		break;
+	    case WINE_WM_RESETTING:
+		if (wwi->state != WINE_WS_STOPPED)
+		{
+                    /* stop the recording */
+		    wwi->nbsdDev->bInputEnabled = FALSE;
+		    AUDIO_INITINFO(&info);
+		    info.record.pause = 1;
+                    if (ioctl(wwi->nbsdDev->fd, AUDIO_SETINFO, &info) < 0) {
+                        ERR("ioctl(%s, AUDIO_SETINFO) failed (%s)\n", wwi->nbsdDev->dev_name, strerror(errno));
+		    }
+		}
+		wwi->state = WINE_WS_STOPPED;
+    		wwi->dwTotalRecorded = 0;
+
+		/* read any headers in queue */
+		widRecorder_ReadHeaders(wwi);
+
+		/* return all buffers to the app */
+		for (lpWaveHdr = wwi->lpQueuePtr; lpWaveHdr; lpWaveHdr = lpWaveHdr->lpNext) {
+		    TRACE("reset %p %p\n", lpWaveHdr, lpWaveHdr->lpNext);
+		    lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
+		    lpWaveHdr->dwFlags |= WHDR_DONE;
+                    wwi->lpQueuePtr = lpWaveHdr->lpNext;
+		    widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
+		}
+
+		wwi->lpQueuePtr = NULL;
+		SetEvent(ev);
+		break;
+	    case WINE_WM_CLOSING:
+		wwi->hThread = 0;
+		wwi->state = WINE_WS_CLOSED;
+		SetEvent(ev);
+		HeapFree(GetProcessHeap(), 0, buffer);
+		ExitThread(0);
+		/* shouldn't go here */
+	    default:
+		FIXME("unknown message %d\n", msg);
+		break;
+	    }
+	}
+    }
+    ExitThread(0);
+    /* just for not generating compilation warnings... should never be executed */
+    return 0;
+}
+
+
+/**************************************************************************
+ * 				widOpen				[internal]
+ */
+static DWORD widOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
+{
+    WINE_WAVEIN*	wwi;
+    DWORD               ret;
+    audio_info_t	info;
+    int			audio_fragment;
+
+    TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags);
+
+    AUDIO_INITINFO(&info);
+
+    if (lpDesc == NULL) {
+	WARN("Invalid Parameter !\n");
+	return MMSYSERR_INVALPARAM;
+    }
+    if (wDevID >= numInDev) return MMSYSERR_BADDEVICEID;
+
+    /* only PCM format is supported so far... */
+    if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM ||
+	lpDesc->lpFormat->nChannels == 0 ||
+	lpDesc->lpFormat->nSamplesPerSec == 0) {
+	WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	return WAVERR_BADFORMAT;
+    }
+
+    if (dwFlags & WAVE_FORMAT_QUERY) {
+	TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n",
+	     lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
+	     lpDesc->lpFormat->nSamplesPerSec);
+	return MMSYSERR_NOERROR;
+    }
+
+    wwi = &WInDev[wDevID];
+
+    if (wwi->state != WINE_WS_CLOSED) return MMSYSERR_ALLOCATED;
+
+    if ((dwFlags & WAVE_DIRECTSOUND) && 
+        !(wwi->nbsdDev->in_caps_support & WAVECAPS_DIRECTSOUND))
+	/* not supported, ignore it */
+	dwFlags &= ~WAVE_DIRECTSOUND;
+
+    if (dwFlags & WAVE_DIRECTSOUND) {
+	TRACE("has DirectSoundCapture driver\n");
+        if (wwi->nbsdDev->in_caps_support & WAVECAPS_SAMPLEACCURATE)
+	    /* we have realtime DirectSound, however we do not know the 
+	     * resolution of pointer updates - so choose a small blocksize */
+	    audio_fragment = 256;
+    } else {
+        audio_fragment = 1024;
+    }
+
+    TRACE("using %d byte fragments\n", audio_fragment);
+
+    ret = NBSD_OpenDevice(wwi->nbsdDev, O_RDONLY, &audio_fragment,
+                         1,
+                         lpDesc->lpFormat->nSamplesPerSec,
+                         (lpDesc->lpFormat->nChannels > 1) ? 1 : 0,
+                         (lpDesc->lpFormat->wBitsPerSample == 16)
+                         ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_ULINEAR_LE,
+                         lpDesc->lpFormat->wBitsPerSample);
+    if (ret != 0) return ret;
+    wwi->state = WINE_WS_STOPPED;
+
+    if (wwi->lpQueuePtr) {
+	WARN("Should have an empty queue (%p)\n", wwi->lpQueuePtr);
+	wwi->lpQueuePtr = NULL;
+    }
+    wwi->dwTotalRecorded = 0;
+    wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
+
+    memcpy(&wwi->waveDesc, lpDesc,           sizeof(WAVEOPENDESC));
+    memcpy(&wwi->format,   lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));
+
+    if (wwi->format.wBitsPerSample == 0) {
+	WARN("Resetting zeroed wBitsPerSample\n");
+	wwi->format.wBitsPerSample = 8 *
+	    (wwi->format.wf.nAvgBytesPerSec /
+	     wwi->format.wf.nSamplesPerSec) /
+	    wwi->format.wf.nChannels;
+    }
+
+    if (ioctl(wwi->nbsdDev->fd, AUDIO_GETINFO, &info) == -1) {
+	WARN("ioctl(%s, AUDIO_GETINFO) failed (%s)\n", wwi->nbsdDev->dev_name, strerror(errno));
+        NBSD_CloseDevice(wwi->nbsdDev);
+	wwi->state = WINE_WS_CLOSED;
+	return MMSYSERR_NOTENABLED;
+    }
+    wwi->dwFragmentSize = info.blocksize;
+
+    TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%lu, nSamplesPerSec=%lu, nChannels=%u nBlockAlign=%u!\n",
+	  wwi->format.wBitsPerSample, wwi->format.wf.nAvgBytesPerSec,
+	  wwi->format.wf.nSamplesPerSec, wwi->format.wf.nChannels,
+	  wwi->format.wf.nBlockAlign);
+
+    NBSD_InitRingMessage(&wwi->msgRing);
+
+    wwi->hStartUpEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
+    wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)(DWORD)wDevID, 0, &(wwi->dwThreadID));
+    WaitForSingleObject(wwi->hStartUpEvent, INFINITE);
+    CloseHandle(wwi->hStartUpEvent);
+    wwi->hStartUpEvent = INVALID_HANDLE_VALUE;
+
+    return widNotifyClient(wwi, WIM_OPEN, 0L, 0L);
+}
+
+/**************************************************************************
+ * 				widClose			[internal]
+ */
+static DWORD widClose(WORD wDevID)
+{
+    WINE_WAVEIN*	wwi;
+
+    TRACE("(%u);\n", wDevID);
+    if (wDevID >= numInDev || WInDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("can't close !\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    wwi = &WInDev[wDevID];
+
+    if (wwi->lpQueuePtr != NULL) {
+	WARN("still buffers open !\n");
+	return WAVERR_STILLPLAYING;
+    }
+
+    NBSD_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE);
+    NBSD_CloseDevice(wwi->nbsdDev);
+    wwi->state = WINE_WS_CLOSED;
+    wwi->dwFragmentSize = 0;
+    NBSD_DestroyRingMessage(&wwi->msgRing);
+    return widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
+}
+
+/**************************************************************************
+ * 				widAddBuffer		[internal]
+ */
+static DWORD widAddBuffer(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
+{
+    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
+
+    if (wDevID >= numInDev || WInDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("can't do it !\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+    if (!(lpWaveHdr->dwFlags & WHDR_PREPARED)) {
+	TRACE("never been prepared !\n");
+	return WAVERR_UNPREPARED;
+    }
+    if (lpWaveHdr->dwFlags & WHDR_INQUEUE) {
+	TRACE("header already in use !\n");
+	return WAVERR_STILLPLAYING;
+    }
+
+    lpWaveHdr->dwFlags |= WHDR_INQUEUE;
+    lpWaveHdr->dwFlags &= ~WHDR_DONE;
+    lpWaveHdr->dwBytesRecorded = 0;
+    lpWaveHdr->lpNext = NULL;
+
+    NBSD_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				widPrepare			[internal]
+ */
+static DWORD widPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
+{
+    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
+
+    if (wDevID >= numInDev) return MMSYSERR_INVALHANDLE;
+
+    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
+	return WAVERR_STILLPLAYING;
+
+    lpWaveHdr->dwFlags |= WHDR_PREPARED;
+    lpWaveHdr->dwFlags &= ~WHDR_DONE;
+    lpWaveHdr->dwBytesRecorded = 0;
+    TRACE("header prepared !\n");
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				widUnprepare			[internal]
+ */
+static DWORD widUnprepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
+{
+    TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
+    if (wDevID >= numInDev) return MMSYSERR_INVALHANDLE;
+
+    if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
+	return WAVERR_STILLPLAYING;
+
+    lpWaveHdr->dwFlags &= ~WHDR_PREPARED;
+    lpWaveHdr->dwFlags |= WHDR_DONE;
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 			widStart				[internal]
+ */
+static DWORD widStart(WORD wDevID)
+{
+    TRACE("(%u);\n", wDevID);
+    if (wDevID >= numInDev || WInDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("can't start recording !\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    NBSD_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_STARTING, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 			widStop					[internal]
+ */
+static DWORD widStop(WORD wDevID)
+{
+    TRACE("(%u);\n", wDevID);
+    if (wDevID >= numInDev || WInDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("can't stop !\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+
+    NBSD_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_STOPPING, 0, TRUE);
+
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 			widReset				[internal]
+ */
+static DWORD widReset(WORD wDevID)
+{
+    TRACE("(%u);\n", wDevID);
+    if (wDevID >= numInDev || WInDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("can't reset !\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+    NBSD_AddRingMessage(&WInDev[wDevID].msgRing, WINE_WM_RESETTING, 0, TRUE);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				widGetPosition			[internal]
+ */
+static DWORD widGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
+{
+    int			time;
+    WINE_WAVEIN*	wwi;
+
+    TRACE("(%u, %p, %lu);\n", wDevID, lpTime, uSize);
+
+    if (wDevID >= numInDev || WInDev[wDevID].state == WINE_WS_CLOSED) {
+	WARN("can't get pos !\n");
+	return MMSYSERR_INVALHANDLE;
+    }
+    if (lpTime == NULL)	return MMSYSERR_INVALPARAM;
+
+    wwi = &WInDev[wDevID];
+
+    TRACE("wType=%04X !\n", lpTime->wType);
+    TRACE("wBitsPerSample=%u\n", wwi->format.wBitsPerSample);
+    TRACE("nSamplesPerSec=%lu\n", wwi->format.wf.nSamplesPerSec);
+    TRACE("nChannels=%u\n", wwi->format.wf.nChannels);
+    TRACE("nAvgBytesPerSec=%lu\n", wwi->format.wf.nAvgBytesPerSec);
+    TRACE("dwTotalRecorded=%lu\n",wwi->dwTotalRecorded);
+    switch (lpTime->wType) {
+    case TIME_BYTES:
+	lpTime->u.cb = wwi->dwTotalRecorded;
+	TRACE("TIME_BYTES=%lu\n", lpTime->u.cb);
+	break;
+    case TIME_SAMPLES:
+	lpTime->u.sample = wwi->dwTotalRecorded * 8 /
+	    wwi->format.wBitsPerSample / wwi->format.wf.nChannels;
+	TRACE("TIME_SAMPLES=%lu\n", lpTime->u.sample);
+	break;
+    case TIME_SMPTE:
+	time = wwi->dwTotalRecorded /
+	    (wwi->format.wf.nAvgBytesPerSec / 1000);
+	lpTime->u.smpte.hour = time / (60 * 60 * 1000);
+	time -= lpTime->u.smpte.hour * (60 * 60 * 1000);
+	lpTime->u.smpte.min = time / (60 * 1000);
+	time -= lpTime->u.smpte.min * (60 * 1000);
+	lpTime->u.smpte.sec = time / 1000;
+	time -= lpTime->u.smpte.sec * 1000;
+	lpTime->u.smpte.frame = time * 30 / 1000;
+	lpTime->u.smpte.fps = 30;
+	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:
+	FIXME("format not supported (%u) ! use TIME_MS !\n", lpTime->wType);
+	lpTime->wType = TIME_MS;
+    case TIME_MS:
+	lpTime->u.ms = wwi->dwTotalRecorded /
+	    (wwi->format.wf.nAvgBytesPerSec / 1000);
+	TRACE("TIME_MS=%lu\n", lpTime->u.ms);
+	break;
+    }
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				widMessage (WINENBSD.3)
+ */
+DWORD WINAPI NBSD_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
+			    DWORD dwParam1, DWORD dwParam2)
+{
+    TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n",
+	  wDevID, 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 WIDM_OPEN:		return widOpen       (wDevID, (LPWAVEOPENDESC)dwParam1, dwParam2);
+    case WIDM_CLOSE:		return widClose      (wDevID);
+    case WIDM_ADDBUFFER:	return widAddBuffer  (wDevID, (LPWAVEHDR)dwParam1, dwParam2);
+    case WIDM_PREPARE:		return widPrepare    (wDevID, (LPWAVEHDR)dwParam1, dwParam2);
+    case WIDM_UNPREPARE:	return widUnprepare  (wDevID, (LPWAVEHDR)dwParam1, dwParam2);
+    case WIDM_GETDEVCAPS:	return widGetDevCaps (wDevID, (LPWAVEINCAPSA)dwParam1, dwParam2);
+    case WIDM_GETNUMDEVS:	return numInDev;
+    case WIDM_GETPOS:		return widGetPosition(wDevID, (LPMMTIME)dwParam1, dwParam2);
+    case WIDM_RESET:		return widReset      (wDevID);
+    case WIDM_START:		return widStart      (wDevID);
+    case WIDM_STOP:		return widStop       (wDevID);
+    case DRV_QUERYDEVICEINTERFACESIZE: return wdDevInterfaceSize       (wDevID, (LPDWORD)dwParam1);
+    case DRV_QUERYDEVICEINTERFACE:     return wdDevInterface           (wDevID, (PWCHAR)dwParam1, dwParam2);
+    case DRV_QUERYDSOUNDIFACE:	return widDsCreate   (wDevID, (PIDSCDRIVER*)dwParam1);
+    case DRV_QUERYDSOUNDDESC:	return widDsDesc     (wDevID, (PDSDRIVERDESC)dwParam1);
+    case DRV_QUERYDSOUNDGUID:	return widDsGuid     (wDevID, (LPGUID)dwParam1);
+    default:
+	FIXME("unknown message %u!\n", wMsg);
+    }
+    return MMSYSERR_NOTSUPPORTED;
+}
+
+/*======================================================================*
+ *           Low level DSOUND capture definitions                       *
+ *======================================================================*/
+
+typedef struct IDsCaptureDriverPropertySetImpl IDsCaptureDriverPropertySetImpl;
+typedef struct IDsCaptureDriverNotifyImpl IDsCaptureDriverNotifyImpl;
+typedef struct IDsCaptureDriverImpl IDsCaptureDriverImpl;
+typedef struct IDsCaptureDriverBufferImpl IDsCaptureDriverBufferImpl;
+
+struct IDsCaptureDriverPropertySetImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsDriverPropertySet);
+    DWORD                               ref;
+
+    IDsCaptureDriverBufferImpl*         capture_buffer;
+};
+
+struct IDsCaptureDriverNotifyImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsDriverNotify);
+    DWORD                               ref;
+
+    /* IDsDriverNotifyImpl fields */
+    LPDSBPOSITIONNOTIFY                 notifies;
+    int                                 nrofnotifies;
+
+    IDsCaptureDriverBufferImpl*        capture_buffer;
+};
+
+struct IDsCaptureDriverImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsCaptureDriver);
+    DWORD                               ref;
+
+    /* IDsCaptureDriverImpl fields */
+    UINT                                wDevID;
+    IDsCaptureDriverBufferImpl*         capture_buffer;
+};
+
+struct IDsCaptureDriverBufferImpl
+{
+    /* IUnknown fields */
+    ICOM_VFIELD(IDsCaptureDriverBuffer);
+    DWORD                               ref;
+
+    /* IDsCaptureDriverBufferImpl fields */
+    IDsCaptureDriverImpl*               drv;
+    DWORD                               buflen;
+    LPBYTE                              buffer;
+    DWORD                               writeptr;
+    LPBYTE                              mapping;
+    DWORD                               maplen;
+
+    /* IDsDriverNotifyImpl fields */
+    IDsCaptureDriverNotifyImpl*         notify;
+    int                                 notify_index;
+
+    /* IDsDriverPropertySetImpl fields */
+    IDsCaptureDriverPropertySetImpl*    property_set;
+};
+
+static HRESULT WINAPI IDsCaptureDriverPropertySetImpl_Create(
+    IDsCaptureDriverBufferImpl * dscdb,
+    IDsCaptureDriverPropertySetImpl **pdscdps);
+
+static HRESULT WINAPI IDsCaptureDriverNotifyImpl_Create(
+    IDsCaptureDriverBufferImpl * dsdcb,
+    IDsCaptureDriverNotifyImpl **pdscdn);
+
+/*======================================================================*
+ *           Low level DSOUND capture property set implementation       *
+ *======================================================================*/
+
+static HRESULT WINAPI IDsCaptureDriverPropertySetImpl_QueryInterface(
+    PIDSDRIVERPROPERTYSET iface,
+    REFIID riid,
+    LPVOID *ppobj) 
+{
+    ICOM_THIS(IDsCaptureDriverPropertySetImpl,iface);
+    TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsDriverPropertySet) ) {
+        IDsDriverPropertySet_AddRef(iface);
+        *ppobj = (LPVOID)This;
+        return DS_OK;
+    }
+
+    FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
+
+    *ppobj = 0;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI IDsCaptureDriverPropertySetImpl_AddRef(PIDSDRIVERPROPERTYSET iface) 
+{
+    ICOM_THIS(IDsCaptureDriverPropertySetImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedIncrement(&(This->ref));
+    return ref;
+}
+
+static ULONG WINAPI IDsCaptureDriverPropertySetImpl_Release(PIDSDRIVERPROPERTYSET iface) 
+{
+    ICOM_THIS(IDsCaptureDriverPropertySetImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedDecrement(&(This->ref));
+    if (ref == 0) {
+        IDsCaptureDriverBuffer_Release((PIDSCDRIVERBUFFER)This->capture_buffer);
+        HeapFree(GetProcessHeap(),0,This);
+        TRACE("(%p) released\n",This);
+    }
+    return ref;
+}
+
+static HRESULT WINAPI IDsCaptureDriverPropertySetImpl_Get(
+    PIDSDRIVERPROPERTYSET iface,
+    PDSPROPERTY pDsProperty,
+    LPVOID pPropertyParams,
+    ULONG cbPropertyParams,
+    LPVOID pPropertyData,
+    ULONG cbPropertyData,
+    PULONG pcbReturnedData )
+{
+    ICOM_THIS(IDsCaptureDriverPropertySetImpl,iface);
+    FIXME("(%p,%p,%p,%lx,%p,%lx,%p)\n",This,pDsProperty,pPropertyParams,cbPropertyParams,pPropertyData,cbPropertyData,pcbReturnedData);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsCaptureDriverPropertySetImpl_Set(
+    PIDSDRIVERPROPERTYSET iface,
+    PDSPROPERTY pDsProperty,
+    LPVOID pPropertyParams,
+    ULONG cbPropertyParams,
+    LPVOID pPropertyData,
+    ULONG cbPropertyData )
+{
+    ICOM_THIS(IDsCaptureDriverPropertySetImpl,iface);
+    FIXME("(%p,%p,%p,%lx,%p,%lx)\n",This,pDsProperty,pPropertyParams,cbPropertyParams,pPropertyData,cbPropertyData);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsCaptureDriverPropertySetImpl_QuerySupport(
+    PIDSDRIVERPROPERTYSET iface,
+    REFGUID PropertySetId,
+    ULONG PropertyId,
+    PULONG pSupport )
+{
+    ICOM_THIS(IDsCaptureDriverPropertySetImpl,iface);
+    FIXME("(%p,%s,%lx,%p)\n",This,debugstr_guid(PropertySetId),PropertyId,pSupport);
+    return DSERR_UNSUPPORTED;
+}
+
+ICOM_VTABLE(IDsDriverPropertySet) dscdpsvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    IDsCaptureDriverPropertySetImpl_QueryInterface,
+    IDsCaptureDriverPropertySetImpl_AddRef,
+    IDsCaptureDriverPropertySetImpl_Release,
+    IDsCaptureDriverPropertySetImpl_Get,
+    IDsCaptureDriverPropertySetImpl_Set,
+    IDsCaptureDriverPropertySetImpl_QuerySupport,
+};
+
+/*======================================================================*
+ *                  Low level DSOUND capture notify implementation      *
+ *======================================================================*/
+
+static HRESULT WINAPI IDsCaptureDriverNotifyImpl_QueryInterface(
+    PIDSDRIVERNOTIFY iface,
+    REFIID riid,
+    LPVOID *ppobj) 
+{
+    ICOM_THIS(IDsCaptureDriverNotifyImpl,iface);
+    TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsDriverNotify) ) {
+        IDsDriverNotify_AddRef(iface);
+        *ppobj = This;
+        return DS_OK;
+    }
+
+    FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
+
+    *ppobj = 0;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI IDsCaptureDriverNotifyImpl_AddRef(PIDSDRIVERNOTIFY iface) 
+{
+    ICOM_THIS(IDsCaptureDriverNotifyImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedIncrement(&(This->ref));
+    return ref;
+}
+
+static ULONG WINAPI IDsCaptureDriverNotifyImpl_Release(PIDSDRIVERNOTIFY iface) 
+{
+    ICOM_THIS(IDsCaptureDriverNotifyImpl,iface);
+    DWORD ref;
+    TRACE("(%p) ref was %ld\n", This, This->ref);
+
+    ref = InterlockedDecrement(&(This->ref));
+    if (ref == 0) {
+        IDsCaptureDriverBuffer_Release((PIDSCDRIVERBUFFER)This->capture_buffer);
+        if (This->notifies != NULL)
+            HeapFree(GetProcessHeap(), 0, This->notifies);
+
+        HeapFree(GetProcessHeap(),0,This);
+        TRACE("(%p) released\n",This);
+    }
+
+    return ref;
+}
+
+static HRESULT WINAPI IDsCaptureDriverNotifyImpl_SetNotificationPositions(
+    PIDSDRIVERNOTIFY iface,
+    DWORD howmuch,
+    LPCDSBPOSITIONNOTIFY notify) 
+{
+    ICOM_THIS(IDsCaptureDriverNotifyImpl,iface);
+    TRACE("(%p,0x%08lx,%p)\n",This,howmuch,notify);
+
+    if (!notify) {
+        WARN("invalid parameter\n");
+        return DSERR_INVALIDPARAM;
+    }
+
+    if (TRACE_ON(wave)) {
+        int i;
+        for (i=0;i<howmuch;i++)
+            TRACE("notify at %ld to 0x%08lx\n",
+                notify[i].dwOffset,(DWORD)notify[i].hEventNotify);
+    }
+
+    /* Make an internal copy of the caller-supplied array.
+     * Replace the existing copy if one is already present. */
+    if (This->notifies) 
+        This->notifies = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
+            This->notifies, howmuch * sizeof(DSBPOSITIONNOTIFY));
+    else 
+        This->notifies = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
+            howmuch * sizeof(DSBPOSITIONNOTIFY));
+
+    memcpy(This->notifies, notify, howmuch * sizeof(DSBPOSITIONNOTIFY));
+    This->nrofnotifies = howmuch;
+
+    return S_OK;
+}
+
+ICOM_VTABLE(IDsDriverNotify) dscdnvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    IDsCaptureDriverNotifyImpl_QueryInterface,
+    IDsCaptureDriverNotifyImpl_AddRef,
+    IDsCaptureDriverNotifyImpl_Release,
+    IDsCaptureDriverNotifyImpl_SetNotificationPositions,
+};
+
+/*======================================================================*
+ *                  Low level DSOUND capture implementation             *
+ *======================================================================*/
+
+static HRESULT DSCDB_MapBuffer(IDsCaptureDriverBufferImpl *dscdb)
+{
+    if (!dscdb->mapping) {
+        dscdb->mapping = mmap(NULL, dscdb->maplen, PROT_READ, MAP_SHARED,
+                              WInDev[dscdb->drv->wDevID].nbsdDev->fd, 0);
+        if (dscdb->mapping == (LPBYTE)-1) {
+            TRACE("(%p): Could not map sound device for direct access (%s)\n", dscdb, strerror(errno));
+            return DSERR_GENERIC;
+        }
+        TRACE("(%p): sound device has been mapped for direct access at %p, size=%ld\n", dscdb, dscdb->mapping, dscdb->maplen);
+    }
+    return DS_OK;
+}
+
+static HRESULT DSCDB_UnmapBuffer(IDsCaptureDriverBufferImpl *dscdb)
+{
+    if (dscdb->mapping) {
+        if (munmap(dscdb->mapping, dscdb->maplen) < 0) {
+            ERR("(%p): Could not unmap sound device (%s)\n", dscdb, strerror(errno));
+            return DSERR_GENERIC;
+        }
+        dscdb->mapping = NULL;
+        TRACE("(%p): sound device unmapped\n", dscdb);
+    }
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_QueryInterface(PIDSCDRIVERBUFFER iface, REFIID riid, LPVOID *ppobj)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsCaptureDriverBuffer) ) {
+	IDsCaptureDriverBuffer_AddRef(iface);
+	*ppobj = (LPVOID)This;
+	return DS_OK;
+    }
+
+    if ( IsEqualGUID( &IID_IDsDriverNotify, riid ) ) {
+        if (!This->notify)
+            IDsCaptureDriverNotifyImpl_Create(This, &(This->notify));
+        if (This->notify) {
+            IDsDriverNotify_AddRef((PIDSDRIVERNOTIFY)This->notify);
+            *ppobj = (LPVOID)This->notify;
+            return DS_OK;
+        }
+        *ppobj = 0;
+        return E_FAIL;
+    }
+
+    if ( IsEqualGUID( &IID_IDsDriverPropertySet, riid ) ) {
+        if (!This->property_set)
+            IDsCaptureDriverPropertySetImpl_Create(This, &(This->property_set));
+        if (This->property_set) {
+            IDsDriverPropertySet_AddRef((PIDSDRIVERPROPERTYSET)This->property_set);
+            *ppobj = (LPVOID)This->property_set;
+            return DS_OK;
+        }
+        *ppobj = 0;
+        return E_FAIL;
+    }
+
+    FIXME("(%p,%s,%p) unsupported GUID\n", This, debugstr_guid(riid), ppobj);
+
+    *ppobj = 0;
+
+    return DSERR_UNSUPPORTED;
+}
+
+static ULONG WINAPI IDsCaptureDriverBufferImpl_AddRef(PIDSCDRIVERBUFFER iface)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    This->ref++;
+    return This->ref;
+}
+
+static ULONG WINAPI IDsCaptureDriverBufferImpl_Release(PIDSCDRIVERBUFFER iface)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    if (--This->ref)
+	return This->ref;
+    DSCDB_UnmapBuffer(This);
+    if (This->notify)
+	IDsDriverNotify_Release((PIDSDRIVERNOTIFY)This->notify);
+    if (This->property_set)
+	IDsDriverPropertySet_Release((PIDSDRIVERPROPERTYSET)This->property_set);
+    HeapFree(GetProcessHeap(),0,This);
+    return 0;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_Lock(PIDSCDRIVERBUFFER iface,
+					              LPVOID*ppvAudio1,LPDWORD pdwLen1,
+					              LPVOID*ppvAudio2,LPDWORD pdwLen2,
+					              DWORD dwWritePosition,DWORD dwWriteLen,
+					              DWORD dwFlags)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    FIXME("(%p,%p,%p,%p,%p,%ld,%ld,0x%08lx): stub!\n",This,ppvAudio1,pdwLen1,ppvAudio2,pdwLen2,
+	dwWritePosition,dwWriteLen,dwFlags);
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_Unlock(PIDSCDRIVERBUFFER iface,
+						        LPVOID pvAudio1,DWORD dwLen1,
+						        LPVOID pvAudio2,DWORD dwLen2)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    FIXME("(%p,%p,%ld,%p,%ld): stub!\n",This,pvAudio1,dwLen1,pvAudio2,dwLen2);
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_GetPosition(PIDSCDRIVERBUFFER iface,
+						             LPDWORD lpdwCapture, 
+							     LPDWORD lpdwRead)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    audio_offset_t info;
+    DWORD ptr;
+    TRACE("(%p,%p,%p)\n",This,lpdwCapture,lpdwRead);
+
+    if (WInDev[This->drv->wDevID].state == WINE_WS_CLOSED) {
+	ERR("device not open, but accessing?\n");
+	return DSERR_UNINITIALIZED;
+    }
+    if (ioctl(WInDev[This->drv->wDevID].nbsdDev->fd, AUDIO_GETIOFFS, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_GETIOFFS) failed (%s)\n", WInDev[This->drv->wDevID].nbsdDev->dev_name, strerror(errno));
+	return DSERR_GENERIC;
+    }
+    ptr = info.offset & ~3; /* align the pointer, just in case */
+    if (lpdwCapture) *lpdwCapture = ptr;
+    if (lpdwRead) {
+	/* add some safety margin (not strictly necessary, but...) */
+	if (WInDev[This->drv->wDevID].nbsdDev->in_caps_support & WAVECAPS_SAMPLEACCURATE)
+	    *lpdwRead = ptr + 32;
+	else
+	    *lpdwRead = ptr + WInDev[This->drv->wDevID].dwFragmentSize;
+	while (*lpdwRead > This->buflen)
+	    *lpdwRead -= This->buflen;
+    }
+    TRACE("capturepos=%ld, readpos=%ld\n", lpdwCapture?*lpdwCapture:0, lpdwRead?*lpdwRead:0);
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_GetStatus(PIDSCDRIVERBUFFER iface, LPDWORD lpdwStatus)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    FIXME("(%p,%p): stub!\n",This,lpdwStatus);
+    return DSERR_UNSUPPORTED;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_Start(PIDSCDRIVERBUFFER iface, DWORD dwFlags)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    audio_info_t info;
+
+    TRACE("(%p,%lx)\n",This,dwFlags);
+    AUDIO_INITINFO(&info);
+    WInDev[This->drv->wDevID].nbsdDev->bInputEnabled = TRUE;
+    info.record.pause = 0;
+    if (ioctl(WInDev[This->drv->wDevID].nbsdDev->fd, AUDIO_SETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_SETINFO pause=0) failed (%s)\n", WInDev[This->drv->wDevID].nbsdDev->dev_name, strerror(errno));
+	WInDev[This->drv->wDevID].nbsdDev->bInputEnabled = FALSE;
+	return DSERR_GENERIC;
+    }
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_Stop(PIDSCDRIVERBUFFER iface)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    audio_info_t info;
+
+    TRACE("(%p)\n",This);
+    AUDIO_INITINFO(&info);
+    /* no more captureing */
+    WInDev[This->drv->wDevID].nbsdDev->bInputEnabled = FALSE;
+    info.record.pause = 1;
+    if (ioctl(WInDev[This->drv->wDevID].nbsdDev->fd, AUDIO_SETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_SETINFO pause=1) failed (%s)\n", WInDev[This->drv->wDevID].nbsdDev->dev_name,  strerror(errno));
+	return DSERR_GENERIC;
+    }
+
+    /* Most drivers just can't stop capturing without closing the device...
+     * so we need to somehow signal to our DirectSound implementation
+     * that it should completely recreate this HW buffer...
+     * this unexpected error code should do the trick... */
+    return DSERR_BUFFERLOST;
+}
+
+static HRESULT WINAPI IDsCaptureDriverBufferImpl_SetFormat(PIDSCDRIVERBUFFER iface, LPWAVEFORMATEX pwfx)
+{
+    ICOM_THIS(IDsCaptureDriverBufferImpl,iface);
+    FIXME("(%p): stub!\n",This);
+    return DSERR_UNSUPPORTED;
+}
+
+static ICOM_VTABLE(IDsCaptureDriverBuffer) dscdbvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    IDsCaptureDriverBufferImpl_QueryInterface,
+    IDsCaptureDriverBufferImpl_AddRef,
+    IDsCaptureDriverBufferImpl_Release,
+    IDsCaptureDriverBufferImpl_Lock,
+    IDsCaptureDriverBufferImpl_Unlock,
+    IDsCaptureDriverBufferImpl_SetFormat,
+    IDsCaptureDriverBufferImpl_GetPosition,
+    IDsCaptureDriverBufferImpl_GetStatus,
+    IDsCaptureDriverBufferImpl_Start,
+    IDsCaptureDriverBufferImpl_Stop
+};
+
+static HRESULT WINAPI IDsCaptureDriverImpl_QueryInterface(PIDSCDRIVER iface, REFIID riid, LPVOID *ppobj)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
+
+    if ( IsEqualGUID(riid, &IID_IUnknown) ||
+         IsEqualGUID(riid, &IID_IDsCaptureDriver) ) {
+	IDsCaptureDriver_AddRef(iface);
+	*ppobj = (LPVOID)This;
+	return DS_OK;
+    }
+
+    FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
+
+    *ppobj = 0;
+
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI IDsCaptureDriverImpl_AddRef(PIDSCDRIVER iface)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    TRACE("(%p)\n",This);
+    This->ref++;
+    TRACE("ref=%ld\n",This->ref);
+    return This->ref;
+}
+
+static ULONG WINAPI IDsCaptureDriverImpl_Release(PIDSCDRIVER iface)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    TRACE("(%p)\n",This);
+    if (--This->ref) {
+        TRACE("ref=%ld\n",This->ref);
+	return This->ref;
+    }
+    HeapFree(GetProcessHeap(),0,This);
+    TRACE("ref=0\n");
+    return 0;
+}
+
+static HRESULT WINAPI IDsCaptureDriverImpl_GetDriverDesc(PIDSCDRIVER iface, PDSDRIVERDESC pDesc)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    TRACE("(%p,%p)\n",This,pDesc);
+
+    if (!pDesc) {
+	TRACE("invalid parameter\n");
+	return DSERR_INVALIDPARAM;
+    }
+
+    /* copy version from driver */
+    memcpy(pDesc, &(WInDev[This->wDevID].nbsdDev->ds_desc), sizeof(DSDRIVERDESC));
+
+    pDesc->dwFlags |= DSDDESC_DOMMSYSTEMOPEN | DSDDESC_DOMMSYSTEMSETFORMAT |
+	DSDDESC_USESYSTEMMEMORY | DSDDESC_DONTNEEDPRIMARYLOCK | 
+	DSDDESC_DONTNEEDSECONDARYLOCK;
+    pDesc->dnDevNode		= WInDev[This->wDevID].waveDesc.dnDevNode;
+    pDesc->wVxdId		= 0;
+    pDesc->wReserved		= 0;
+    pDesc->ulDeviceNum		= This->wDevID;
+    pDesc->dwHeapType		= DSDHEAP_NOHEAP;
+    pDesc->pvDirectDrawHeap	= NULL;
+    pDesc->dwMemStartAddress	= 0;
+    pDesc->dwMemEndAddress	= 0;
+    pDesc->dwMemAllocExtra	= 0;
+    pDesc->pvReserved1		= NULL;
+    pDesc->pvReserved2		= NULL;
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverImpl_Open(PIDSCDRIVER iface)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    TRACE("(%p)\n",This);
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverImpl_Close(PIDSCDRIVER iface)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    TRACE("(%p)\n",This);
+    if (This->capture_buffer) {
+	ERR("problem with DirectSound: capture buffer not released\n");
+	return DSERR_GENERIC;
+    }
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverImpl_GetCaps(PIDSCDRIVER iface, PDSCDRIVERCAPS pCaps)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    TRACE("(%p,%p)\n",This,pCaps);
+    memcpy(pCaps, &(WInDev[This->wDevID].nbsdDev->dsc_caps), sizeof(DSCDRIVERCAPS)); 
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverImpl_CreateCaptureBuffer(PIDSCDRIVER iface,
+						               LPWAVEFORMATEX pwfx,
+						               DWORD dwFlags, 
+							       DWORD dwCardAddress,
+						               LPDWORD pdwcbBufferSize,
+						               LPBYTE *ppbBuffer,
+						               LPVOID *ppvObj)
+{
+    ICOM_THIS(IDsCaptureDriverImpl,iface);
+    IDsCaptureDriverBufferImpl** ippdscdb = (IDsCaptureDriverBufferImpl**)ppvObj;
+    HRESULT err;
+    audio_info_t info;
+
+    TRACE("(%p,%p,%lx,%lx,%p,%p,%p)\n",This,pwfx,dwFlags,dwCardAddress,pdwcbBufferSize,ppbBuffer,ppvObj);
+
+    if (This->capture_buffer) {
+	TRACE("already allocated\n");
+	return DSERR_ALLOCATED;
+    }
+
+    *ippdscdb = (IDsCaptureDriverBufferImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDsCaptureDriverBufferImpl));
+    if (*ippdscdb == NULL) {
+	TRACE("out of memory\n");
+	return DSERR_OUTOFMEMORY;
+    }
+
+    (*ippdscdb)->lpVtbl = &dscdbvt;
+    (*ippdscdb)->ref          = 1;
+    (*ippdscdb)->drv          = This;
+    (*ippdscdb)->notify       = 0;
+    (*ippdscdb)->notify_index = 0;
+    (*ippdscdb)->property_set = 0;
+
+    if (WInDev[This->wDevID].state == WINE_WS_CLOSED) {
+	WAVEOPENDESC desc;
+	desc.hWave = 0;
+	desc.lpFormat = pwfx; 
+	desc.dwCallback = 0;
+	desc.dwInstance = 0;
+	desc.uMappedDeviceID = 0;
+	desc.dnDevNode = 0;
+	err = widOpen(This->wDevID, &desc, dwFlags | WAVE_DIRECTSOUND);
+	if (err != MMSYSERR_NOERROR) {
+	    TRACE("widOpen failed\n");
+	    return err;
+	}
+    }
+
+    /* check how big the DMA buffer is now */
+    if (ioctl(WInDev[This->wDevID].nbsdDev->fd, AUDIO_GETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_GETINFO) failed (%s)\n", WInDev[This->wDevID].nbsdDev->dev_name, strerror(errno));
+	HeapFree(GetProcessHeap(),0,*ippdscdb);
+	*ippdscdb = NULL;
+	return DSERR_GENERIC;
+    }
+    (*ippdscdb)->maplen = (*ippdscdb)->buflen = info.record.buffer_size;
+
+    /* map the DMA buffer */
+    err = DSCDB_MapBuffer(*ippdscdb);
+    if (err != DS_OK) {
+	HeapFree(GetProcessHeap(),0,*ippdscdb);
+	*ippdscdb = NULL;
+	return err;
+    }
+
+    /* capture buffer is ready to go */
+    *pdwcbBufferSize    = (*ippdscdb)->maplen;
+    *ppbBuffer          = (*ippdscdb)->mapping;
+
+    /* some drivers need some extra nudging after mapping */
+    WInDev[This->wDevID].nbsdDev->bInputEnabled = FALSE;
+    AUDIO_INITINFO(&info);
+    info.record.pause = 1;
+    if (ioctl(WInDev[This->wDevID].nbsdDev->fd, AUDIO_SETINFO, &info) < 0) {
+	ERR("ioctl(%s, AUDIO_SETINFO pause=1) failed (%s)\n", WInDev[This->wDevID].nbsdDev->dev_name, strerror(errno));
+	return DSERR_GENERIC;
+    }
+
+    This->capture_buffer = *ippdscdb;
+
+    return DS_OK;
+}
+
+static ICOM_VTABLE(IDsCaptureDriver) dscdvt =
+{
+    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
+    IDsCaptureDriverImpl_QueryInterface,
+    IDsCaptureDriverImpl_AddRef,
+    IDsCaptureDriverImpl_Release,
+    IDsCaptureDriverImpl_GetDriverDesc,
+    IDsCaptureDriverImpl_Open,
+    IDsCaptureDriverImpl_Close,
+    IDsCaptureDriverImpl_GetCaps,
+    IDsCaptureDriverImpl_CreateCaptureBuffer
+};
+
+static HRESULT WINAPI IDsCaptureDriverPropertySetImpl_Create(
+    IDsCaptureDriverBufferImpl * dscdb,
+    IDsCaptureDriverPropertySetImpl **pdscdps)
+{
+    IDsCaptureDriverPropertySetImpl * dscdps;
+    TRACE("(%p,%p)\n",dscdb,pdscdps);
+
+    dscdps = (IDsCaptureDriverPropertySetImpl*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dscdps));
+    if (dscdps == NULL) {
+        WARN("out of memory\n");
+        return DSERR_OUTOFMEMORY;
+    }
+                                                                                
+    dscdps->ref = 0;
+    dscdps->lpVtbl = &dscdpsvt;
+    dscdps->capture_buffer = dscdb;
+    dscdb->property_set = dscdps;
+    IDsCaptureDriverBuffer_AddRef((PIDSCDRIVER)dscdb);
+                                                                                
+    *pdscdps = dscdps;
+    return DS_OK;
+}
+
+static HRESULT WINAPI IDsCaptureDriverNotifyImpl_Create(
+    IDsCaptureDriverBufferImpl * dscdb,
+    IDsCaptureDriverNotifyImpl **pdscdn)
+{
+    IDsCaptureDriverNotifyImpl * dscdn;
+    TRACE("(%p,%p)\n",dscdb,pdscdn);
+
+    dscdn = (IDsCaptureDriverNotifyImpl*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dscdn));
+    if (dscdn == NULL) {
+        WARN("out of memory\n");
+        return DSERR_OUTOFMEMORY;
+    }
+                                                                                
+    dscdn->ref = 0;
+    dscdn->lpVtbl = &dscdnvt;
+    dscdn->capture_buffer = dscdb;
+    dscdb->notify = dscdn;
+    IDsCaptureDriverBuffer_AddRef((PIDSCDRIVER)dscdb);
+                                                                                
+    *pdscdn = dscdn;
+    return DS_OK;
+};
+
+static DWORD widDsCreate(UINT wDevID, PIDSCDRIVER* drv)
+{
+    IDsCaptureDriverImpl** idrv = (IDsCaptureDriverImpl**)drv;
+    TRACE("(%d,%p)\n",wDevID,drv);
+
+    /* the HAL isn't much better than the HEL if we can't do mmap() */
+    if (!(WInDev[wDevID].nbsdDev->in_caps_support & WAVECAPS_DIRECTSOUND)) {
+	ERR("DirectSoundCapture flag not set\n");
+	MESSAGE("This sound card's driver does not support direct access\n");
+	MESSAGE("The (slower) DirectSound HEL mode will be used instead.\n");
+	return MMSYSERR_NOTSUPPORTED;
+    }
+
+    *idrv = (IDsCaptureDriverImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(IDsCaptureDriverImpl));
+    if (!*idrv)
+	return MMSYSERR_NOMEM;
+    (*idrv)->lpVtbl	= &dscdvt;
+    (*idrv)->ref	= 1;
+
+    (*idrv)->wDevID	= wDevID;
+    (*idrv)->capture_buffer = NULL;
+    return MMSYSERR_NOERROR;
+}
+
+static DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc)
+{
+    memcpy(desc, &(WInDev[wDevID].nbsdDev->ds_desc), sizeof(DSDRIVERDESC));
+    return MMSYSERR_NOERROR;
+}
+
+static DWORD widDsGuid(UINT wDevID, LPGUID pGuid)
+{
+    TRACE("(%d,%p)\n",wDevID,pGuid);
+
+    memcpy(pGuid, &(WInDev[wDevID].nbsdDev->dsc_guid), sizeof(GUID));
+
+    return MMSYSERR_NOERROR;
+}
+
+#else /* !HAVE_NBSDAUDIOIO */
+
+/**************************************************************************
+ * 				wodMessage (WINENBSD.4)
+ */
+DWORD WINAPI NBSD_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
+			    DWORD dwParam1, DWORD dwParam2)
+{
+    FIXME("(%u, %04X, %08lX, %08lX, %08lX):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
+    return MMSYSERR_NOTENABLED;
+}
+
+/**************************************************************************
+ * 				widMessage (WINENBSD.3)
+ */
+DWORD WINAPI NBSD_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
+			    DWORD dwParam1, DWORD dwParam2)
+{
+    FIXME("(%u, %04X, %08lX, %08lX, %08lX):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
+    return MMSYSERR_NOTENABLED;
+}
+
+#endif /* HAVE_NBSDAUDIOIO */
diff -urN /home/yorick/software/wine/wine/dlls/winmm/winenbsd/mmaux.c ./dlls/winmm/winenbsd/mmaux.c
--- /home/yorick/software/wine/wine/dlls/winmm/winenbsd/mmaux.c	Thu Jan  1 02:00:00 1970
+++ ./dlls/winmm/winenbsd/mmaux.c	Sun Feb 22 00:28:45 2004
@@ -0,0 +1,341 @@
+/* -*- tab-width: 8; c-basic-offset: 4 -*- */
+/*
+ * Wine Driver for NetBSD audioio
+ * Derived from the Wine OSS Sample Driver
+ *
+ * Copyright 1994 Martin Ayotte
+ *           2004 Yorick Hardy (NetBSD audioio)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define EMULATE_SB16
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+
+#include "windef.h"
+#include "winbase.h"
+#include "mmddk.h"
+#include "nbsd.h"
+#include "wine/debug.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(mmaux);
+
+#ifdef HAVE_NBSDAUDIOIO
+
+#define MIXER_DEV "/dev/mixer"
+
+static int	NumDev = 6;
+
+/*-----------------------------------------------------------------------*/
+
+static	int	AUXDRV_Init(void)
+{
+    int	mixer;
+
+    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
+	WARN("mixer device not available !\n");
+	NumDev = 0;
+    } else {
+	close(mixer);
+	NumDev = 6;
+    }
+    return NumDev;
+}
+
+/**************************************************************************
+ * 				AUX_GetDevCaps			[internal]
+ */
+static DWORD AUX_GetDevCaps(WORD wDevID, LPAUXCAPSA lpCaps, DWORD dwSize)
+{
+    int 	mixer;
+    audio_device_t dev;
+
+    TRACE("(%04X, %p, %lu);\n", wDevID, lpCaps, dwSize);
+    if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
+    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
+	WARN("mixer device not available !\n");
+	return MMSYSERR_NOTENABLED;
+    }
+    if (ioctl(mixer, AUDIO_GETDEV, &dev) == -1) {
+	close(mixer);
+	WARN("unable to read mixer !\n");
+	return MMSYSERR_NOTENABLED;
+    }
+    close(mixer);
+#ifdef EMULATE_SB16
+    lpCaps->wMid = 0x0002;
+    lpCaps->vDriverVersion = 0x0200;
+    lpCaps->dwSupport = AUXCAPS_VOLUME | AUXCAPS_LRVOLUME;
+    switch (wDevID) {
+    case 0:
+	lpCaps->wPid = 0x0196;
+	strcpy(lpCaps->szPname, "SB16 Aux: Wave");
+	lpCaps->wTechnology = AUXCAPS_AUXIN;
+	break;
+    case 1:
+	lpCaps->wPid = 0x0197;
+	strcpy(lpCaps->szPname, "SB16 Aux: Midi Synth");
+	lpCaps->wTechnology = AUXCAPS_AUXIN;
+	break;
+    case 2:
+	lpCaps->wPid = 0x0191;
+	strcpy(lpCaps->szPname, "SB16 Aux: CD");
+	lpCaps->wTechnology = AUXCAPS_CDAUDIO;
+	break;
+    case 3:
+	lpCaps->wPid = 0x0192;
+	strcpy(lpCaps->szPname, "SB16 Aux: Line-In");
+	lpCaps->wTechnology = AUXCAPS_AUXIN;
+	break;
+    case 4:
+	lpCaps->wPid = 0x0193;
+	strcpy(lpCaps->szPname, "SB16 Aux: Mic");
+	lpCaps->wTechnology = AUXCAPS_AUXIN;
+	break;
+    case 5:
+	lpCaps->wPid = 0x0194;
+	strcpy(lpCaps->szPname, "SB16 Aux: Master");
+	lpCaps->wTechnology = AUXCAPS_AUXIN;
+	break;
+    }
+#else
+    lpCaps->wMid = 0xAA;
+    lpCaps->wPid = 0x55;
+    lpCaps->vDriverVersion = 0x0100;
+    strcpy(lpCaps->szPname, "Generic Wine Auxiliary Driver");
+    lpCaps->wTechnology = AUXCAPS_CDAUDIO;
+    lpCaps->dwSupport = AUXCAPS_VOLUME | AUXCAPS_LRVOLUME;
+#endif
+    return MMSYSERR_NOERROR;
+}
+
+
+/**************************************************************************
+ * 				AUX_GetVolume			[internal]
+ */
+static DWORD AUX_GetVolume(WORD wDevID, LPDWORD lpdwVol)
+{
+    int 	i, mixer, left, right;
+    char        *name = NULL;
+    mixer_devinfo_t devinfo;
+    mixer_ctrl_t ctrl;
+
+    TRACE("(%04X, %p);\n", wDevID, lpdwVol);
+    if (lpdwVol == NULL) return MMSYSERR_NOTENABLED;
+    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
+	WARN("mixer device not available !\n");
+	return MMSYSERR_NOTENABLED;
+    }
+    switch(wDevID) {
+    case 0:
+	TRACE("AudioNwave !\n");
+        name = AudioNwave;
+	break;
+    case 1:
+	TRACE("AudioNmidi !\n");
+        name = AudioNmidi;
+	break;
+    case 2:
+	TRACE("AudioNcd !\n");
+        name = AudioNcd;
+	break;
+    case 3:
+	TRACE("AudioNline !\n");
+        name = AudioNline;
+	break;
+    case 4:
+	TRACE("AudioNmicrophone !\n");
+        name = AudioNmicrophone;
+	break;
+    case 5:
+	TRACE("AudioNvolume !\n");
+        name = AudioNvolume;
+	break;
+    default:
+	WARN("invalid device id=%04X !\n", wDevID);
+        close(mixer);
+	return MMSYSERR_NOTENABLED;
+    }
+
+    if (name == NULL) {
+        close(mixer);
+	return MMSYSERR_NOTENABLED;
+    }
+
+    for (i = devinfo.next = 0; devinfo.next != AUDIO_MIXER_LAST; i++) {
+        if (ioctl(mixer, AUDIO_MIXER_DEVINFO, &devinfo) == -1) {
+       	    WARN("unable to read mixer !\n");
+        }
+        if ( ! strcmp(devinfo.label.name, name) ) break;
+    }
+    if (devinfo.next == AUDIO_MIXER_LAST) {
+	WARN("unable to read mixer !\n");
+        close(mixer);
+	return MMSYSERR_NOTENABLED;
+    } else {
+        ctrl.dev = i;
+        if (ioctl(mixer, AUDIO_MIXER_READ, &ctrl) == -1
+             || ctrl.type != AUDIO_MIXER_VALUE) {
+	    WARN("unable to read mixer !\n");
+            close(mixer);
+	    return MMSYSERR_NOTENABLED;
+        }
+    }
+    
+    close(mixer);
+    left  = ctrl.un.value.level[0];
+    right  = (ctrl.un.value.num_channels > 1) ? 
+              ctrl.un.value.level[2] : left;
+    TRACE("left=%d right=%d !\n", left, right);
+    *lpdwVol = MAKELONG((left * 0xFFFFL) / AUDIO_MAX_GAIN,
+                        (right * 0xFFFFL) / AUDIO_MAX_GAIN);
+    return MMSYSERR_NOERROR;
+}
+
+/**************************************************************************
+ * 				AUX_SetVolume			[internal]
+ */
+static DWORD AUX_SetVolume(WORD wDevID, DWORD dwParam)
+{
+    int 	mixer;
+    int		i, left, right;
+    char        *name = NULL;
+    mixer_devinfo_t devinfo;
+    mixer_ctrl_t ctrl;
+
+    TRACE("(%04X, %08lX);\n", wDevID, dwParam);
+
+    if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) {
+	WARN("mixer device not available !\n");
+	return MMSYSERR_NOTENABLED;
+    }
+
+    switch(wDevID) {
+    case 0:
+	TRACE("AudioNwave !\n");
+        name = AudioNwave;
+	break;
+    case 1:
+	TRACE("AudioNmidi !\n");
+        name = AudioNmidi;
+	break;
+    case 2:
+	TRACE("AudioNcd !\n");
+        name = AudioNcd;
+	break;
+    case 3:
+	TRACE("AudioNcd !\n");
+        name = AudioNcd;
+	break;
+    case 4:
+	TRACE("AudioNmicrophone !\n");
+        name = AudioNmicrophone;
+	break;
+    case 5:
+	TRACE("AudioNvolume !\n");
+        name = AudioNvolume;
+	break;
+    default:
+	WARN("invalid device id=%04X !\n", wDevID);
+        close(mixer);
+	return MMSYSERR_NOTENABLED;
+    }
+
+    if (name == NULL) {
+        close(mixer);
+	return MMSYSERR_NOTENABLED;
+    }
+
+    for (i = devinfo.next = 0; devinfo.next != AUDIO_MIXER_LAST; i++) {
+        if (ioctl(mixer, AUDIO_MIXER_DEVINFO, &devinfo) == -1) {
+       	    WARN("unable to write mixer !\n");
+        }
+        if ( ! strcmp(devinfo.label.name, name) ) break;
+    }
+    if (devinfo.next == AUDIO_MIXER_LAST) {
+	WARN("unable to write mixer !\n");
+        close(mixer);
+	return MMSYSERR_NOTENABLED;
+    } else {
+        ctrl.dev = i;
+        ctrl.type = AUDIO_MIXER_VALUE;
+        left   = (LOWORD(dwParam) * 255) >> 16;
+        right  = (HIWORD(dwParam) * 255) >> 16;
+        ctrl.un.value.level[0] = (u_char)left;
+        if (ctrl.un.value.num_channels > 1)
+            ctrl.un.value.level[1] = (u_char)right;
+
+        if (ioctl(mixer, AUDIO_MIXER_WRITE, &ctrl) == -1
+             || ctrl.type != AUDIO_MIXER_VALUE) {
+	    WARN("unable to write mixer !\n");
+            close(mixer);
+	    return MMSYSERR_NOTENABLED;
+        }
+    }
+
+    close(mixer);
+    return MMSYSERR_NOERROR;
+}
+
+#endif
+
+/**************************************************************************
+ *		auxMessage (WINENBSD.2)
+ */
+DWORD WINAPI NBSD_auxMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
+			    DWORD dwParam1, DWORD dwParam2)
+{
+    TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n",
+	  wDevID, wMsg, dwUser, dwParam1, dwParam2);
+
+#ifdef HAVE_NBSDAUDIOIO
+    switch (wMsg) {
+    case DRVM_INIT:
+	AUXDRV_Init();
+	/* fall through */
+    case DRVM_EXIT:
+    case DRVM_ENABLE:
+    case DRVM_DISABLE:
+	/* FIXME: Pretend this is supported */
+	return 0;
+    case AUXDM_GETDEVCAPS:
+	return AUX_GetDevCaps(wDevID, (LPAUXCAPSA)dwParam1,dwParam2);
+    case AUXDM_GETNUMDEVS:
+	TRACE("return %d;\n", NumDev);
+	return NumDev;
+    case AUXDM_GETVOLUME:
+	return AUX_GetVolume(wDevID, (LPDWORD)dwParam1);
+    case AUXDM_SETVOLUME:
+	return AUX_SetVolume(wDevID, dwParam1);
+    default:
+	WARN("unknown message !\n");
+    }
+    return MMSYSERR_NOTSUPPORTED;
+#else
+    return MMSYSERR_NOTENABLED;
+#endif
+}
diff -urN /home/yorick/software/wine/wine/dlls/winmm/winenbsd/nbsd.c ./dlls/winmm/winenbsd/nbsd.c
--- /home/yorick/software/wine/wine/dlls/winmm/winenbsd/nbsd.c	Thu Jan  1 02:00:00 1970
+++ ./dlls/winmm/winenbsd/nbsd.c	Sun Feb 22 00:28:54 2004
@@ -0,0 +1,95 @@
+/* -*- tab-width: 8; c-basic-offset: 4 -*- */
+/*
+ * Wine Driver for NetBSD audioio
+ * Derived from the Wine OSS Sample Driver
+ *
+ * Copyright 	1999 Eric Pouech
+ *           	2004 Yorick Hardy (NetBSD audioio)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "mmddk.h"
+#include "nbsd.h"
+
+#ifdef HAVE_NBSDAUDIOIO
+
+static	struct WINE_NBSD* nbsd = NULL;
+
+/**************************************************************************
+ * 				NBSD_drvOpen			[internal]
+ */
+static	DWORD	NBSD_drvOpen(LPSTR str)
+{
+    if (nbsd)
+	return 0;
+
+    /* I know, this is ugly, but who cares... */
+    nbsd = (struct WINE_NBSD*)1;
+    return 1;
+}
+
+/**************************************************************************
+ * 				NBSD_drvClose			[internal]
+ */
+static	DWORD	NBSD_drvClose(DWORD dwDevID)
+{
+    if (nbsd) {
+	nbsd = NULL;
+	return 1;
+    }
+    return 0;
+}
+
+#endif
+
+
+/**************************************************************************
+ * 				DriverProc (WINENBSD.1)
+ */
+LONG CALLBACK	NBSD_DriverProc(DWORD dwDevID, HDRVR hDriv, DWORD wMsg,
+			       DWORD dwParam1, DWORD dwParam2)
+{
+/* EPP     TRACE("(%08lX, %04X, %08lX, %08lX, %08lX)\n",  */
+/* EPP 	  dwDevID, hDriv, wMsg, dwParam1, dwParam2); */
+
+    switch(wMsg) {
+#ifdef HAVE_NBSDAUDIOIO
+    case DRV_LOAD:		NBSD_WaveInit();
+				return 1;
+    case DRV_FREE:		return 1;
+    case DRV_OPEN:		return NBSD_drvOpen((LPSTR)dwParam1);
+    case DRV_CLOSE:		return NBSD_drvClose(dwDevID);
+    case DRV_ENABLE:		return 1;
+    case DRV_DISABLE:		return 1;
+    case DRV_QUERYCONFIGURE:	return 1;
+    case DRV_CONFIGURE:		MessageBoxA(0, "NetBSD AudioIO MultiMedia Driver !", "NetBSD AudioIO 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 -urN /home/yorick/software/wine/wine/dlls/winmm/winenbsd/nbsd.h ./dlls/winmm/winenbsd/nbsd.h
--- /home/yorick/software/wine/wine/dlls/winmm/winenbsd/nbsd.h	Thu Jan  1 02:00:00 1970
+++ ./dlls/winmm/winenbsd/nbsd.h	Sun Feb 22 00:28:17 2004
@@ -0,0 +1,35 @@
+/* Definition for NetBSD audioio drivers : wine multimedia system
+ *
+ * Copyright 1999 Eric Pouech
+ *           2004 Yorick Hardy (NetBSD audioio)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __WINE_CONFIG_H
+# error You must include config.h to use this header
+#endif
+
+#ifdef HAVE_SYS_AUDIOIO_H
+# include <sys/types.h>
+# include <sys/audioio.h>
+#endif
+
+#ifdef HAVE_SYS_ERRNO_H
+# include <sys/errno.h>
+#endif
+
+extern LONG NBSD_WaveInit(void);
+extern BOOL NBSD_MidiInit(void);
diff -urN /home/yorick/software/wine/wine/dlls/winmm/winenbsd/winenbsd.drv.spec ./dlls/winmm/winenbsd/winenbsd.drv.spec
--- /home/yorick/software/wine/wine/dlls/winmm/winenbsd/winenbsd.drv.spec	Thu Jan  1 02:00:00 1970
+++ ./dlls/winmm/winenbsd/winenbsd.drv.spec	Sat Feb 21 23:57:46 2004
@@ -0,0 +1,4 @@
+1 stdcall DriverProc(long long long long long) NBSD_DriverProc
+2 stdcall auxMessage(long long long long long) NBSD_auxMessage
+3 stdcall widMessage(long long long long long) NBSD_widMessage
+4 stdcall wodMessage(long long long long long) NBSD_wodMessage
diff -urN /home/yorick/software/wine/wine/documentation/samples/config ./documentation/samples/config
--- /home/yorick/software/wine/wine/documentation/samples/config	Sat Jan 10 16:47:35 2004
+++ ./documentation/samples/config	Sat Feb 21 23:45:04 2004
@@ -253,6 +253,7 @@
 ;"Drivers" = "winealsa.drv"    ; for ALSA users
 ;"Drivers" = "winejack.drv"    ; for Jack sound server
 ;"Drivers" = "winenas.drv"     ; for NAS sound system
+;"Drivers" = "winenbsd.drv"    ; for NetBSD audioio sound system
 ;"Drivers" = "wineaudioio.drv" ; for Solaris machines
 ;"Drivers" = ""                ; to disable sound
 "WaveMapper" = "msacm.drv"
diff -urN /home/yorick/software/wine/wine/include/config.h.in ./include/config.h.in
--- /home/yorick/software/wine/wine/include/config.h.in	Sun Feb  8 13:09:34 2004
+++ ./include/config.h.in	Sun Feb 22 12:05:15 2004
@@ -32,6 +32,9 @@
 /* Define if you have ARTS sound server */
 #undef HAVE_ARTS
 
+/* Define to 1 if you have the <sys/audioio.h> header file. */
+#undef HAVE_SYS_AUDIOIO_H
+
 /* Define to 1 if you have the <audio/audiolib.h> header file. */
 #undef HAVE_AUDIO_AUDIOLIB_H
 
@@ -352,6 +355,9 @@
 
 /* Define to 1 if you have the <ncurses.h> header file. */
 #undef HAVE_NCURSES_H
+
+/* Define if you have NetBSD audioio */
+#undef HAVE_NBSDAUDIOIO
 
 /* Define to 1 if you have the <netdb.h> header file. */
 #undef HAVE_NETDB_H
diff -urN /home/yorick/software/wine/wine/programs/winecfg/properties.c ./programs/winecfg/properties.c
--- /home/yorick/software/wine/wine/programs/winecfg/properties.c	Sun Feb  8 13:09:53 2004
+++ ./programs/winecfg/properties.c	Sat Feb 21 23:46:57 2004
@@ -86,6 +86,7 @@
   {"OSS", "wineoss.drv"},
   {"Jack", "winejack.drv"},
   {"Nas", "winenas.drv"},
+  {"NetBSD", "winenbsd.drv"},
   {"Audio IO(Solaris)", "wineaudioio.drv"},
   {"Disable sound", ""},
   {"", ""}
diff -urN /home/yorick/software/wine/wine/tools/winapi/win32.api ./tools/winapi/win32.api
--- /home/yorick/software/wine/wine/tools/winapi/win32.api	Sat Aug  9 09:54:52 2003
+++ ./tools/winapi/win32.api	Sat Feb 21 23:48:31 2004
@@ -3367,6 +3367,16 @@
 WORD
 UINT
 
+%%winenbsd.drv
+
+%long
+
+DWORD
+HDRVR
+LONG
+WORD
+UINT
+
 %%wineoss.drv
 
 %long



More information about the wine-patches mailing list