wined3d: Remove COM from the shader implementation.
[wine] / dlls / winealsa.drv / waveout.c
1 /*
2  * Sample Wine Driver for Advanced Linux Sound System (ALSA)
3  *      Based on version <final> of the ALSA API
4  *
5  * Copyright    2002 Eric Pouech
6  *              2002 Marco Pietrobono
7  *              2003 Christian Costa : WaveIn support
8  *              2006-2007 Maarten Lankhorst
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24
25 /*======================================================================*
26  *                  Low level WAVE OUT implementation                   *
27  *======================================================================*/
28
29 #include "config.h"
30 #include "wine/port.h"
31
32 #include <stdlib.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <string.h>
36 #ifdef HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39 #include <errno.h>
40 #include <limits.h>
41 #include <fcntl.h>
42 #ifdef HAVE_SYS_IOCTL_H
43 # include <sys/ioctl.h>
44 #endif
45 #ifdef HAVE_SYS_MMAN_H
46 # include <sys/mman.h>
47 #endif
48 #include "windef.h"
49 #include "winbase.h"
50 #include "wingdi.h"
51 #include "winuser.h"
52 #include "winnls.h"
53 #include "mmddk.h"
54
55 #include "alsa.h"
56
57 #include "wine/library.h"
58 #include "wine/unicode.h"
59 #include "wine/debug.h"
60
61 WINE_DEFAULT_DEBUG_CHANNEL(wave);
62
63 WINE_WAVEDEV    *WOutDev;
64 DWORD            ALSA_WodNumMallocedDevs;
65 DWORD            ALSA_WodNumDevs;
66
67 /**************************************************************************
68  *                      wodNotifyClient                 [internal]
69  */
70 static void wodNotifyClient(WINE_WAVEDEV* wwo, WORD wMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
71 {
72     TRACE("wMsg = 0x%04x dwParm1 = %lx dwParam2 = %lx\n", wMsg, dwParam1, dwParam2);
73
74     switch (wMsg) {
75     case WOM_OPEN:
76     case WOM_CLOSE:
77     case WOM_DONE:
78         if (wwo->wFlags != DCB_NULL &&
79             !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, (HDRVR)wwo->waveDesc.hWave,
80                             wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2)) {
81             WARN("can't notify client !\n");
82         }
83         break;
84     default:
85         FIXME("Unknown callback message %u\n", wMsg);
86     }
87 }
88
89 /**************************************************************************
90  *                              wodUpdatePlayedTotal    [internal]
91  *
92  */
93 static BOOL wodUpdatePlayedTotal(WINE_WAVEDEV* wwo, snd_pcm_status_t* ps)
94 {
95     snd_pcm_sframes_t delay;
96     snd_pcm_sframes_t avail;
97     snd_pcm_uframes_t buf_size = 0;
98     snd_pcm_state_t state;
99
100     state = snd_pcm_state(wwo->pcm);
101     avail = snd_pcm_avail_update(wwo->pcm);
102     snd_pcm_hw_params_get_buffer_size(wwo->hw_params, &buf_size);
103     delay = buf_size - avail;
104
105     if (state != SND_PCM_STATE_RUNNING && state != SND_PCM_STATE_PREPARED)
106     {
107         WARN("Unexpected state (%d) while updating Total Played, resetting\n", state);
108         wine_snd_pcm_recover(wwo->pcm, -EPIPE, 0);
109         delay=0;
110     }
111
112     /* A delay < 0 indicates an underrun; for our purposes that's 0.  */
113     if (delay < 0)
114     {
115         WARN("Unexpected delay (%ld) while updating Total Played, resetting\n", delay);
116         delay=0;
117     }
118
119     InterlockedExchange((LONG*)&wwo->dwPlayedTotal, wwo->dwWrittenTotal - snd_pcm_frames_to_bytes(wwo->pcm, delay));
120     return TRUE;
121 }
122
123 /**************************************************************************
124  *                              wodPlayer_BeginWaveHdr          [internal]
125  *
126  * Makes the specified lpWaveHdr the currently playing wave header.
127  * If the specified wave header is a begin loop and we're not already in
128  * a loop, setup the loop.
129  */
130 static void wodPlayer_BeginWaveHdr(WINE_WAVEDEV* wwo, LPWAVEHDR lpWaveHdr)
131 {
132     wwo->lpPlayPtr = lpWaveHdr;
133
134     if (!lpWaveHdr) return;
135
136     if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) {
137         if (wwo->lpLoopPtr) {
138             WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
139         } else {
140             TRACE("Starting loop (%dx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);
141             wwo->lpLoopPtr = lpWaveHdr;
142             /* Windows does not touch WAVEHDR.dwLoops,
143              * so we need to make an internal copy */
144             wwo->dwLoops = lpWaveHdr->dwLoops;
145         }
146     }
147     wwo->dwPartialOffset = 0;
148 }
149
150 /**************************************************************************
151  *                              wodPlayer_PlayPtrNext           [internal]
152  *
153  * Advance the play pointer to the next waveheader, looping if required.
154  */
155 static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEDEV* wwo)
156 {
157     LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
158
159     wwo->dwPartialOffset = 0;
160     if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) {
161         /* We're at the end of a loop, loop if required */
162         if (--wwo->dwLoops > 0) {
163             wwo->lpPlayPtr = wwo->lpLoopPtr;
164         } else {
165             /* Handle overlapping loops correctly */
166             if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
167                 FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
168                 /* shall we consider the END flag for the closing loop or for
169                  * the opening one or for both ???
170                  * code assumes for closing loop only
171                  */
172             } else {
173                 lpWaveHdr = lpWaveHdr->lpNext;
174             }
175             wwo->lpLoopPtr = NULL;
176             wodPlayer_BeginWaveHdr(wwo, lpWaveHdr);
177         }
178     } else {
179         /* We're not in a loop.  Advance to the next wave header */
180         wodPlayer_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
181     }
182
183     return lpWaveHdr;
184 }
185
186 /**************************************************************************
187  *                           wodPlayer_DSPWait                  [internal]
188  * Returns the number of milliseconds to wait for the DSP buffer to play a
189  * period
190  */
191 static DWORD wodPlayer_DSPWait(const WINE_WAVEDEV *wwo)
192 {
193     /* time for one period to be played */
194     unsigned int val=0;
195     int dir=0;
196     int err=0;
197     err = snd_pcm_hw_params_get_period_time(wwo->hw_params, &val, &dir);
198     return val / 1000;
199 }
200
201 /**************************************************************************
202  *                           wodPlayer_NotifyWait               [internal]
203  * Returns the number of milliseconds to wait before attempting to notify
204  * completion of the specified wavehdr.
205  * This is based on the number of bytes remaining to be written in the
206  * wave.
207  */
208 static DWORD wodPlayer_NotifyWait(const WINE_WAVEDEV* wwo, LPWAVEHDR lpWaveHdr)
209 {
210     DWORD dwMillis;
211
212     if (lpWaveHdr->reserved < wwo->dwPlayedTotal) {
213         dwMillis = 1;
214     } else {
215         dwMillis = (lpWaveHdr->reserved - wwo->dwPlayedTotal) * 1000 / wwo->format.Format.nAvgBytesPerSec;
216         if (!dwMillis) dwMillis = 1;
217     }
218
219     return dwMillis;
220 }
221
222
223 /**************************************************************************
224  *                           wodPlayer_WriteMaxFrags            [internal]
225  * Writes the maximum number of frames possible to the DSP and returns
226  * the number of frames written.
227  */
228 static int wodPlayer_WriteMaxFrags(WINE_WAVEDEV* wwo, DWORD* frames)
229 {
230     /* Only attempt to write to free frames */
231     LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
232     DWORD dwLength = snd_pcm_bytes_to_frames(wwo->pcm, lpWaveHdr->dwBufferLength - wwo->dwPartialOffset);
233     int toWrite = min(dwLength, *frames);
234     int written;
235
236     TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr, wwo->dwPartialOffset, lpWaveHdr->dwBufferLength);
237
238     if (toWrite > 0) {
239         written = (wwo->write)(wwo->pcm, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite);
240         if ( written < 0) {
241             /* XRUN occurred. let's try to recover */
242             wine_snd_pcm_recover(wwo->pcm, written, 0);
243             written = (wwo->write)(wwo->pcm, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite);
244         }
245         if (written <= 0) {
246             /* still in error */
247             ERR("Error in writing wavehdr. Reason: %s\n", snd_strerror(written));
248             return written;
249         }
250     } else
251         written = 0;
252
253     wwo->dwPartialOffset += snd_pcm_frames_to_bytes(wwo->pcm, written);
254     if (wwo->dwPartialOffset + wwo->format.Format.nBlockAlign - 1 >= lpWaveHdr->dwBufferLength) {
255         /* this will be used to check if the given wave header has been fully played or not... */
256         wwo->dwPartialOffset = lpWaveHdr->dwBufferLength;
257         /* If we wrote all current wavehdr, skip to the next one */
258         wodPlayer_PlayPtrNext(wwo);
259     }
260     *frames -= written;
261     wwo->dwWrittenTotal += snd_pcm_frames_to_bytes(wwo->pcm, written);
262     TRACE("dwWrittenTotal=%u\n", wwo->dwWrittenTotal);
263
264     return written;
265 }
266
267
268 /**************************************************************************
269  *                              wodPlayer_NotifyCompletions     [internal]
270  *
271  * Notifies and remove from queue all wavehdrs which have been played to
272  * the speaker (ie. they have cleared the ALSA buffer).  If force is true,
273  * we notify all wavehdrs and remove them all from the queue even if they
274  * are unplayed or part of a loop.
275  */
276 static DWORD wodPlayer_NotifyCompletions(WINE_WAVEDEV* wwo, BOOL force)
277 {
278     LPWAVEHDR           lpWaveHdr;
279
280     /* Start from lpQueuePtr and keep notifying until:
281      * - we hit an unwritten wavehdr
282      * - we hit the beginning of a running loop
283      * - we hit a wavehdr which hasn't finished playing
284      */
285     for (;;)
286     {
287         lpWaveHdr = wwo->lpQueuePtr;
288         if (!lpWaveHdr) {TRACE("Empty queue\n"); break;}
289         if (!force)
290         {
291             snd_pcm_uframes_t frames;
292             snd_pcm_hw_params_get_period_size(wwo->hw_params, &frames, NULL);
293
294             if (lpWaveHdr == wwo->lpPlayPtr) {TRACE("play %p\n", lpWaveHdr); break;}
295             if (lpWaveHdr == wwo->lpLoopPtr) {TRACE("loop %p\n", lpWaveHdr); break;}
296             if (lpWaveHdr->reserved > wwo->dwPlayedTotal + frames) {TRACE("still playing %p (%lu/%u)\n", lpWaveHdr, lpWaveHdr->reserved, wwo->dwPlayedTotal);break;}
297         }
298         wwo->dwPlayedTotal += lpWaveHdr->reserved - wwo->dwPlayedTotal;
299         wwo->lpQueuePtr = lpWaveHdr->lpNext;
300
301         lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
302         lpWaveHdr->dwFlags |= WHDR_DONE;
303
304         wodNotifyClient(wwo, WOM_DONE, (DWORD_PTR)lpWaveHdr, 0);
305     }
306     return  (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr) ?
307         wodPlayer_NotifyWait(wwo, lpWaveHdr) : INFINITE;
308 }
309
310
311 /**************************************************************************
312  *                              wodPlayer_Reset                 [internal]
313  *
314  * wodPlayer helper. Resets current output stream.
315  */
316 static  void    wodPlayer_Reset(WINE_WAVEDEV* wwo, BOOL reset)
317 {
318     int                         err;
319     TRACE("(%p)\n", wwo);
320
321     wodUpdatePlayedTotal(wwo, NULL);
322     /* updates current notify list */
323     wodPlayer_NotifyCompletions(wwo, FALSE);
324
325     if ( (err = snd_pcm_drop(wwo->pcm)) < 0) {
326         FIXME("flush: %s\n", snd_strerror(err));
327         wwo->hThread = 0;
328         wwo->state = WINE_WS_STOPPED;
329         ExitThread(-1);
330     }
331     if ( (err = snd_pcm_prepare(wwo->pcm)) < 0 )
332         ERR("pcm prepare failed: %s\n", snd_strerror(err));
333
334     if (reset) {
335         enum win_wm_message     msg;
336         DWORD_PTR               param;
337         HANDLE                  ev;
338
339         /* remove any buffer */
340         wodPlayer_NotifyCompletions(wwo, TRUE);
341
342         wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
343         wwo->state = WINE_WS_STOPPED;
344         wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
345         /* Clear partial wavehdr */
346         wwo->dwPartialOffset = 0;
347
348         /* remove any existing message in the ring */
349         EnterCriticalSection(&wwo->msgRing.msg_crst);
350         /* return all pending headers in queue */
351         while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev))
352         {
353             if (msg != WINE_WM_HEADER)
354             {
355                 FIXME("shouldn't have headers left\n");
356                 SetEvent(ev);
357                 continue;
358             }
359             ((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE;
360             ((LPWAVEHDR)param)->dwFlags |= WHDR_DONE;
361
362             wodNotifyClient(wwo, WOM_DONE, param, 0);
363         }
364         ALSA_ResetRingMessage(&wwo->msgRing);
365         LeaveCriticalSection(&wwo->msgRing.msg_crst);
366     } else {
367         if (wwo->lpLoopPtr) {
368             /* complicated case, not handled yet (could imply modifying the loop counter */
369             FIXME("Pausing while in loop isn't correctly handled yet, expect strange results\n");
370             wwo->lpPlayPtr = wwo->lpLoopPtr;
371             wwo->dwPartialOffset = 0;
372             wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */
373         } else {
374             LPWAVEHDR   ptr;
375             DWORD       sz = wwo->dwPartialOffset;
376
377             /* reset all the data as if we had written only up to lpPlayedTotal bytes */
378             /* compute the max size playable from lpQueuePtr */
379             for (ptr = wwo->lpQueuePtr; ptr != wwo->lpPlayPtr; ptr = ptr->lpNext) {
380                 sz += ptr->dwBufferLength;
381             }
382             /* because the reset lpPlayPtr will be lpQueuePtr */
383             if (wwo->dwWrittenTotal > wwo->dwPlayedTotal + sz) ERR("grin\n");
384             wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal);
385             wwo->dwWrittenTotal = wwo->dwPlayedTotal;
386             wwo->lpPlayPtr = wwo->lpQueuePtr;
387         }
388         wwo->state = WINE_WS_PAUSED;
389     }
390 }
391
392 /**************************************************************************
393  *                    wodPlayer_ProcessMessages                 [internal]
394  */
395 static void wodPlayer_ProcessMessages(WINE_WAVEDEV* wwo)
396 {
397     LPWAVEHDR           lpWaveHdr;
398     enum win_wm_message msg;
399     DWORD_PTR           param;
400     HANDLE              ev;
401     int                 err;
402
403     while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
404      TRACE("Received %s %lx\n", ALSA_getCmdString(msg), param);
405
406         switch (msg) {
407         case WINE_WM_PAUSING:
408             if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_RUNNING )
409              {
410                 if ( snd_pcm_hw_params_can_pause(wwo->hw_params) )
411                 {
412                     err = snd_pcm_pause(wwo->pcm, 1);
413                     if ( err < 0 )
414                         ERR("pcm_pause failed: %s\n", snd_strerror(err));
415                     wwo->state = WINE_WS_PAUSED;
416                 }
417                 else
418                 {
419                     wodPlayer_Reset(wwo,FALSE);
420                 }
421              }
422             SetEvent(ev);
423             break;
424         case WINE_WM_RESTARTING:
425             if (wwo->state == WINE_WS_PAUSED)
426             {
427                 if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_PAUSED )
428                  {
429                     err = snd_pcm_pause(wwo->pcm, 0);
430                     if ( err < 0 )
431                         ERR("pcm_pause failed: %s\n", snd_strerror(err));
432                  }
433                 wwo->state = WINE_WS_PLAYING;
434             }
435             SetEvent(ev);
436             break;
437         case WINE_WM_HEADER:
438             lpWaveHdr = (LPWAVEHDR)param;
439
440             /* insert buffer at the end of queue */
441             {
442                 LPWAVEHDR*      wh;
443                 for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
444                 *wh = lpWaveHdr;
445             }
446             if (!wwo->lpPlayPtr)
447                 wodPlayer_BeginWaveHdr(wwo,lpWaveHdr);
448             if (wwo->state == WINE_WS_STOPPED)
449                 wwo->state = WINE_WS_PLAYING;
450             break;
451         case WINE_WM_RESETTING:
452             wodPlayer_Reset(wwo,TRUE);
453             SetEvent(ev);
454             break;
455         case WINE_WM_BREAKLOOP:
456             if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL) {
457                 /* ensure exit at end of current loop */
458                 wwo->dwLoops = 1;
459             }
460             SetEvent(ev);
461             break;
462         case WINE_WM_CLOSING:
463             /* sanity check: this should not happen since the device must have been reset before */
464             if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
465             wwo->hThread = 0;
466             wwo->state = WINE_WS_CLOSED;
467             SetEvent(ev);
468             ExitThread(0);
469             /* shouldn't go here */
470         default:
471             FIXME("unknown message %d\n", msg);
472             break;
473         }
474     }
475 }
476
477 /**************************************************************************
478  *                           wodPlayer_FeedDSP                  [internal]
479  * Feed as much sound data as we can into the DSP and return the number of
480  * milliseconds before it will be necessary to feed the DSP again.
481  */
482 static DWORD wodPlayer_FeedDSP(WINE_WAVEDEV* wwo)
483 {
484     DWORD               availInQ;
485
486     wodUpdatePlayedTotal(wwo, NULL);
487     availInQ = snd_pcm_avail_update(wwo->pcm);
488
489     /* no more room... no need to try to feed */
490     if (availInQ > 0) {
491         /* Feed from partial wavehdr */
492         if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0) {
493             wodPlayer_WriteMaxFrags(wwo, &availInQ);
494         }
495
496         /* Feed wavehdrs until we run out of wavehdrs or DSP space */
497         if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) {
498             do {
499                 TRACE("Setting time to elapse for %p to %u\n",
500                       wwo->lpPlayPtr, wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength);
501                 /* note the value that dwPlayedTotal will return when this wave finishes playing */
502                 wwo->lpPlayPtr->reserved = wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength;
503             } while (wodPlayer_WriteMaxFrags(wwo, &availInQ) && wwo->lpPlayPtr && availInQ > 0);
504         }
505     }
506
507     return wodPlayer_DSPWait(wwo);
508 }
509
510 /**************************************************************************
511  *                              wodPlayer                       [internal]
512  */
513 static  DWORD   CALLBACK        wodPlayer(LPVOID pmt)
514 {
515     WORD          uDevID = (DWORD_PTR)pmt;
516     WINE_WAVEDEV* wwo = &WOutDev[uDevID];
517     DWORD         dwNextFeedTime = INFINITE;   /* Time before DSP needs feeding */
518     DWORD         dwNextNotifyTime = INFINITE; /* Time before next wave completion */
519     DWORD         dwSleepTime;
520
521     wwo->state = WINE_WS_STOPPED;
522     SetEvent(wwo->hStartUpEvent);
523
524     for (;;) {
525         /** Wait for the shortest time before an action is required.  If there
526          *  are no pending actions, wait forever for a command.
527          */
528         dwSleepTime = min(dwNextFeedTime, dwNextNotifyTime);
529         TRACE("waiting %ums (%u,%u)\n", dwSleepTime, dwNextFeedTime, dwNextNotifyTime);
530         ALSA_WaitRingMessage(&wwo->msgRing, dwSleepTime);
531         wodPlayer_ProcessMessages(wwo);
532         if (wwo->state == WINE_WS_PLAYING) {
533             dwNextFeedTime = wodPlayer_FeedDSP(wwo);
534             dwNextNotifyTime = wodPlayer_NotifyCompletions(wwo, FALSE);
535             if (dwNextFeedTime == INFINITE) {
536                 /* FeedDSP ran out of data, but before giving up, */
537                 /* check that a notification didn't give us more */
538                 wodPlayer_ProcessMessages(wwo);
539                 if (wwo->lpPlayPtr) {
540                     TRACE("recovering\n");
541                     dwNextFeedTime = wodPlayer_FeedDSP(wwo);
542                 }
543             }
544         } else {
545             dwNextFeedTime = dwNextNotifyTime = INFINITE;
546         }
547     }
548     return 0;
549 }
550
551 /**************************************************************************
552  *                      wodGetDevCaps                           [internal]
553  */
554 static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize)
555 {
556     TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
557
558     if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
559
560     if (wDevID >= ALSA_WodNumDevs) {
561         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
562         return MMSYSERR_BADDEVICEID;
563     }
564
565     memcpy(lpCaps, &WOutDev[wDevID].outcaps, min(dwSize, sizeof(*lpCaps)));
566     return MMSYSERR_NOERROR;
567 }
568
569 /**************************************************************************
570  *                              wodOpen                         [internal]
571  */
572 static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
573 {
574     WINE_WAVEDEV*               wwo;
575     snd_pcm_t *                 pcm = NULL;
576     snd_hctl_t *                hctl = NULL;
577     snd_pcm_hw_params_t *       hw_params;
578     snd_pcm_sw_params_t *       sw_params;
579     snd_pcm_access_t            access;
580     snd_pcm_format_t            format = -1;
581     unsigned int                rate;
582     unsigned int                buffer_time = 120000;
583     unsigned int                period_time = 22000;
584     snd_pcm_uframes_t           buffer_size;
585     snd_pcm_uframes_t           period_size;
586     int                         flags;
587     int                         err=0;
588     int                         dir=0;
589     DWORD                       retcode = 0;
590
591     TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
592     if (lpDesc == NULL) {
593         WARN("Invalid Parameter !\n");
594         return MMSYSERR_INVALPARAM;
595     }
596     if (wDevID >= ALSA_WodNumDevs) {
597         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
598         return MMSYSERR_BADDEVICEID;
599     }
600
601     /* only PCM format is supported so far... */
602     if (!ALSA_supportedFormat(lpDesc->lpFormat)) {
603         WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
604              lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
605              lpDesc->lpFormat->nSamplesPerSec);
606         return WAVERR_BADFORMAT;
607     }
608
609     if (dwFlags & WAVE_FORMAT_QUERY) {
610         TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
611              lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
612              lpDesc->lpFormat->nSamplesPerSec);
613         return MMSYSERR_NOERROR;
614     }
615
616     wwo = &WOutDev[wDevID];
617
618     if (wwo->pcm != NULL) {
619         WARN("%d already allocated\n", wDevID);
620         return MMSYSERR_ALLOCATED;
621     }
622
623     if (dwFlags & WAVE_DIRECTSOUND)
624         FIXME("Why are we called with DirectSound flag? It doesn't use MMSYSTEM any more\n");
625         /* not supported, ignore it */
626     dwFlags &= ~WAVE_DIRECTSOUND;
627
628     flags = SND_PCM_NONBLOCK;
629
630     if ( (err = snd_pcm_open(&pcm, wwo->pcmname, SND_PCM_STREAM_PLAYBACK, flags)) < 0)
631     {
632         ERR("Error open: %s\n", snd_strerror(err));
633         return MMSYSERR_NOTENABLED;
634     }
635
636     if (wwo->ctlname)
637     {
638         err = snd_hctl_open(&hctl, wwo->ctlname, 0);
639         if (err >= 0)
640         {
641             snd_hctl_load(hctl);
642         }
643         else
644         {
645             WARN("Could not open hctl for [%s]: %s\n", wwo->ctlname, snd_strerror(err));
646             hctl = NULL;
647         }
648     }
649
650     wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
651
652     wwo->waveDesc = *lpDesc;
653     ALSA_copyFormat(lpDesc->lpFormat, &wwo->format);
654
655     TRACE("Requested this format: %dx%dx%d %s\n",
656           wwo->format.Format.nSamplesPerSec,
657           wwo->format.Format.wBitsPerSample,
658           wwo->format.Format.nChannels,
659           ALSA_getFormat(wwo->format.Format.wFormatTag));
660
661     if (wwo->format.Format.wBitsPerSample == 0) {
662         WARN("Resetting zeroed wBitsPerSample\n");
663         wwo->format.Format.wBitsPerSample = 8 *
664             (wwo->format.Format.nAvgBytesPerSec /
665              wwo->format.Format.nSamplesPerSec) /
666             wwo->format.Format.nChannels;
667     }
668
669 #define EXIT_ON_ERROR(f,e,txt) do \
670 { \
671     int err; \
672     if ( (err = (f) ) < 0) \
673     { \
674         WARN(txt ": %s\n", snd_strerror(err)); \
675         retcode=e; \
676         goto errexit; \
677     } \
678 } while(0)
679
680     sw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_sw_params_sizeof() );
681     hw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() );
682     if (!hw_params || !sw_params)
683     {
684         retcode = MMSYSERR_NOMEM;
685         goto errexit;
686     }
687     snd_pcm_hw_params_any(pcm, hw_params);
688
689     access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
690     if ( ( err = snd_pcm_hw_params_set_access(pcm, hw_params, access ) ) < 0) {
691         WARN("mmap not available. switching to standard write.\n");
692         access = SND_PCM_ACCESS_RW_INTERLEAVED;
693         EXIT_ON_ERROR( snd_pcm_hw_params_set_access(pcm, hw_params, access ), MMSYSERR_INVALPARAM, "unable to set access for playback");
694         wwo->write = snd_pcm_writei;
695     }
696     else
697         wwo->write = snd_pcm_mmap_writei;
698
699     if ((err = snd_pcm_hw_params_set_channels(pcm, hw_params, wwo->format.Format.nChannels)) < 0) {
700         WARN("unable to set required channels: %d\n", wwo->format.Format.nChannels);
701         EXIT_ON_ERROR( snd_pcm_hw_params_set_channels(pcm, hw_params, wwo->format.Format.nChannels ), WAVERR_BADFORMAT, "unable to set required channels" );
702     }
703
704     if ((wwo->format.Format.wFormatTag == WAVE_FORMAT_PCM) ||
705         ((wwo->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
706         IsEqualGUID(&wwo->format.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) {
707         format = (wwo->format.Format.wBitsPerSample == 8) ? SND_PCM_FORMAT_U8 :
708                  (wwo->format.Format.wBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE :
709                  (wwo->format.Format.wBitsPerSample == 24) ? SND_PCM_FORMAT_S24_3LE :
710                  (wwo->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_S32_LE : -1;
711     } else if ((wwo->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
712         IsEqualGUID(&wwo->format.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)){
713         format = (wwo->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_FLOAT_LE : -1;
714     } else {
715         ERR("invalid format: %0x04x\n", wwo->format.Format.wFormatTag);
716         retcode = WAVERR_BADFORMAT;
717         goto errexit;
718     }
719
720     if ((err = snd_pcm_hw_params_set_format(pcm, hw_params, format)) < 0) {
721         WARN("unable to set required format: %s\n", snd_pcm_format_name(format));
722         EXIT_ON_ERROR( snd_pcm_hw_params_set_format(pcm, hw_params, format), WAVERR_BADFORMAT, "unable to set required format" );
723     }
724
725     rate = wwo->format.Format.nSamplesPerSec;
726     dir=0;
727     err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, &dir);
728     if (err < 0) {
729         WARN("Rate %d Hz not available for playback: %s\n", wwo->format.Format.nSamplesPerSec, snd_strerror(rate));
730         retcode = WAVERR_BADFORMAT;
731         goto errexit;
732     }
733     if (!ALSA_NearMatch(rate, wwo->format.Format.nSamplesPerSec)) {
734         WARN("Rate doesn't match (requested %d Hz, got %d Hz)\n", wwo->format.Format.nSamplesPerSec, rate);
735         retcode = WAVERR_BADFORMAT;
736         goto errexit;
737     }
738
739     TRACE("Got this format: %dx%dx%d %s\n",
740           wwo->format.Format.nSamplesPerSec,
741           wwo->format.Format.wBitsPerSample,
742           wwo->format.Format.nChannels,
743           ALSA_getFormat(wwo->format.Format.wFormatTag));
744
745     dir=0;
746     EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_time, &dir), MMSYSERR_INVALPARAM, "unable to set buffer time");
747     dir=0;
748     EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm, hw_params, &period_time, &dir), MMSYSERR_INVALPARAM, "unable to set period time");
749
750     EXIT_ON_ERROR( snd_pcm_hw_params(pcm, hw_params), MMSYSERR_INVALPARAM, "unable to set hw params for playback");
751
752     err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir);
753     err = snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
754
755     snd_pcm_sw_params_current(pcm, sw_params);
756
757     EXIT_ON_ERROR( snd_pcm_sw_params_set_start_threshold(pcm, sw_params, 1), MMSYSERR_ERROR, "unable to set start threshold");
758     EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_size(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence size");
759     EXIT_ON_ERROR( snd_pcm_sw_params_set_avail_min(pcm, sw_params, period_size), MMSYSERR_ERROR, "unable to set avail min");
760     EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence threshold");
761     EXIT_ON_ERROR( snd_pcm_sw_params(pcm, sw_params), MMSYSERR_ERROR, "unable to set sw params for playback");
762 #undef EXIT_ON_ERROR
763
764     snd_pcm_prepare(pcm);
765
766     if (TRACE_ON(wave))
767         ALSA_TraceParameters(hw_params, sw_params, FALSE);
768
769     /* now, we can save all required data for later use... */
770
771     wwo->dwBufferSize = snd_pcm_frames_to_bytes(pcm, buffer_size);
772     wwo->lpQueuePtr = wwo->lpPlayPtr = wwo->lpLoopPtr = NULL;
773     wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
774     wwo->dwPartialOffset = 0;
775
776     ALSA_InitRingMessage(&wwo->msgRing);
777
778     wwo->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
779     wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)(DWORD_PTR)wDevID, 0, &(wwo->dwThreadID));
780     if (!wwo->hThread) {
781         ERR("Thread creation for the wodPlayer failed!\n");
782         CloseHandle(wwo->hStartUpEvent);
783         retcode = MMSYSERR_NOMEM;
784         goto errexit;
785     }
786     SetThreadPriority(wwo->hThread, THREAD_PRIORITY_TIME_CRITICAL);
787     WaitForSingleObject(wwo->hStartUpEvent, INFINITE);
788     CloseHandle(wwo->hStartUpEvent);
789     wwo->hStartUpEvent = NULL;
790
791     TRACE("handle=%p\n", pcm);
792     TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%u, nSamplesPerSec=%u, nChannels=%u nBlockAlign=%u!\n",
793           wwo->format.Format.wBitsPerSample, wwo->format.Format.nAvgBytesPerSec,
794           wwo->format.Format.nSamplesPerSec, wwo->format.Format.nChannels,
795           wwo->format.Format.nBlockAlign);
796
797     HeapFree( GetProcessHeap(), 0, sw_params );
798     wwo->hw_params = hw_params;
799     wwo->hctl = hctl;
800     wwo->pcm = pcm;
801
802     wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);
803     return MMSYSERR_NOERROR;
804
805 errexit:
806     if (pcm)
807         snd_pcm_close(pcm);
808
809     if (hctl)
810     {
811         snd_hctl_free(hctl);
812         snd_hctl_close(hctl);
813     }
814
815     HeapFree( GetProcessHeap(), 0, hw_params );
816     HeapFree( GetProcessHeap(), 0, sw_params );
817     if (wwo->msgRing.ring_buffer_size > 0)
818         ALSA_DestroyRingMessage(&wwo->msgRing);
819
820     return retcode;
821 }
822
823
824 /**************************************************************************
825  *                              wodClose                        [internal]
826  */
827 static DWORD wodClose(WORD wDevID)
828 {
829     WINE_WAVEDEV*       wwo;
830
831     TRACE("(%u);\n", wDevID);
832
833     if (wDevID >= ALSA_WodNumDevs) {
834         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
835         return MMSYSERR_BADDEVICEID;
836     }
837
838     wwo = &WOutDev[wDevID];
839     if (wwo->pcm == NULL) {
840         WARN("Requested to close already closed device %d!\n", wDevID);
841         return MMSYSERR_BADDEVICEID;
842     }
843
844     if (wwo->lpQueuePtr) {
845         WARN("buffers still playing !\n");
846         return WAVERR_STILLPLAYING;
847     } else {
848         if (wwo->hThread) {
849             ALSA_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE);
850         }
851         ALSA_DestroyRingMessage(&wwo->msgRing);
852
853         HeapFree( GetProcessHeap(), 0, wwo->hw_params );
854         wwo->hw_params = NULL;
855
856         if (wwo->hctl)
857         {
858             snd_hctl_free(wwo->hctl);
859             snd_hctl_close(wwo->hctl);
860             wwo->hctl = NULL;
861         }
862
863         snd_pcm_close(wwo->pcm);
864         wwo->pcm = NULL;
865
866         wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
867     }
868
869     return MMSYSERR_NOERROR;
870 }
871
872
873 /**************************************************************************
874  *                              wodWrite                        [internal]
875  *
876  */
877 static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
878 {
879     TRACE("(%u, %p, %08X);\n", wDevID, lpWaveHdr, dwSize);
880
881     if (wDevID >= ALSA_WodNumDevs) {
882         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
883         return MMSYSERR_BADDEVICEID;
884     }
885
886     if (WOutDev[wDevID].pcm == NULL) {
887         WARN("Requested to write to closed device %d!\n", wDevID);
888         return MMSYSERR_BADDEVICEID;
889     }
890
891     if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
892         return WAVERR_UNPREPARED;
893
894     if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
895         return WAVERR_STILLPLAYING;
896
897     lpWaveHdr->dwFlags &= ~WHDR_DONE;
898     lpWaveHdr->dwFlags |= WHDR_INQUEUE;
899     lpWaveHdr->lpNext = 0;
900
901     ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD_PTR)lpWaveHdr, FALSE);
902
903     return MMSYSERR_NOERROR;
904 }
905
906 /**************************************************************************
907  *                      wodPause                                [internal]
908  */
909 static DWORD wodPause(WORD wDevID)
910 {
911     TRACE("(%u);!\n", wDevID);
912
913     if (wDevID >= ALSA_WodNumDevs) {
914         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
915         return MMSYSERR_BADDEVICEID;
916     }
917
918     if (WOutDev[wDevID].pcm == NULL) {
919         WARN("Requested to pause closed device %d!\n", wDevID);
920         return MMSYSERR_BADDEVICEID;
921     }
922
923     ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_PAUSING, 0, TRUE);
924
925     return MMSYSERR_NOERROR;
926 }
927
928 /**************************************************************************
929  *                      wodRestart                              [internal]
930  */
931 static DWORD wodRestart(WORD wDevID)
932 {
933     TRACE("(%u);\n", wDevID);
934
935     if (wDevID >= ALSA_WodNumDevs) {
936         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
937         return MMSYSERR_BADDEVICEID;
938     }
939
940     if (WOutDev[wDevID].pcm == NULL) {
941         WARN("Requested to restart closed device %d!\n", wDevID);
942         return MMSYSERR_BADDEVICEID;
943     }
944
945     if (WOutDev[wDevID].state == WINE_WS_PAUSED) {
946         ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESTARTING, 0, TRUE);
947     }
948
949     /* FIXME: is NotifyClient with WOM_DONE right ? (Comet Busters 1.3.3 needs this notification) */
950     /* FIXME: Myst crashes with this ... hmm -MM
951        return wodNotifyClient(wwo, WOM_DONE, 0L, 0L);
952     */
953
954     return MMSYSERR_NOERROR;
955 }
956
957 /**************************************************************************
958  *                      wodReset                                [internal]
959  */
960 static DWORD wodReset(WORD wDevID)
961 {
962     TRACE("(%u);\n", wDevID);
963
964     if (wDevID >= ALSA_WodNumDevs) {
965         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
966         return MMSYSERR_BADDEVICEID;
967     }
968
969     if (WOutDev[wDevID].pcm == NULL) {
970         WARN("Requested to reset closed device %d!\n", wDevID);
971         return MMSYSERR_BADDEVICEID;
972     }
973
974     ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESETTING, 0, TRUE);
975
976     return MMSYSERR_NOERROR;
977 }
978
979 /**************************************************************************
980  *                              wodGetPosition                  [internal]
981  */
982 static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
983 {
984     WINE_WAVEDEV*       wwo;
985
986     TRACE("(%u, %p, %u);\n", wDevID, lpTime, uSize);
987
988     if (wDevID >= ALSA_WodNumDevs) {
989         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
990         return MMSYSERR_BADDEVICEID;
991     }
992
993     if (WOutDev[wDevID].pcm == NULL) {
994         WARN("Requested to get position of closed device %d!\n", wDevID);
995         return MMSYSERR_BADDEVICEID;
996     }
997
998     if (lpTime == NULL) return MMSYSERR_INVALPARAM;
999
1000     wwo = &WOutDev[wDevID];
1001     return ALSA_bytes_to_mmtime(lpTime, wwo->dwPlayedTotal, &wwo->format);
1002 }
1003
1004 /**************************************************************************
1005  *                              wodBreakLoop                    [internal]
1006  */
1007 static DWORD wodBreakLoop(WORD wDevID)
1008 {
1009     TRACE("(%u);\n", wDevID);
1010
1011     if (wDevID >= ALSA_WodNumDevs) {
1012         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
1013         return MMSYSERR_BADDEVICEID;
1014     }
1015
1016     if (WOutDev[wDevID].pcm == NULL) {
1017         WARN("Requested to breakloop of closed device %d!\n", wDevID);
1018         return MMSYSERR_BADDEVICEID;
1019     }
1020
1021     ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_BREAKLOOP, 0, TRUE);
1022     return MMSYSERR_NOERROR;
1023 }
1024
1025 /**************************************************************************
1026  *                              wodGetVolume                    [internal]
1027  */
1028 static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol)
1029 {
1030     WORD               wleft, wright;
1031     WINE_WAVEDEV*      wwo;
1032     int                min, max;
1033     int                left, right;
1034     DWORD              rc;
1035
1036     TRACE("(%u, %p);\n", wDevID, lpdwVol);
1037     if (wDevID >= ALSA_WodNumDevs) {
1038         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
1039         return MMSYSERR_BADDEVICEID;
1040     }
1041
1042     if (lpdwVol == NULL)
1043         return MMSYSERR_NOTENABLED;
1044
1045     wwo = &WOutDev[wDevID];
1046
1047     rc = ALSA_CheckSetVolume(wwo->hctl, &left, &right, &min, &max, NULL, NULL, NULL);
1048     if (rc == MMSYSERR_NOERROR)
1049     {
1050 #define VOLUME_ALSA_TO_WIN(x) (  ( (((x)-min) * 65535) + (max-min)/2 ) /(max-min))
1051         wleft = VOLUME_ALSA_TO_WIN(left);
1052         wright = VOLUME_ALSA_TO_WIN(right);
1053 #undef VOLUME_ALSA_TO_WIN
1054         TRACE("left=%d,right=%d,converted to windows left %d, right %d\n", left, right, wleft, wright);
1055         *lpdwVol = MAKELONG( wleft, wright );
1056     }
1057     else
1058         TRACE("CheckSetVolume failed; rc %d\n", rc);
1059
1060     return rc;
1061 }
1062
1063 /**************************************************************************
1064  *                              wodSetVolume                    [internal]
1065  */
1066 static DWORD wodSetVolume(WORD wDevID, DWORD dwParam)
1067 {
1068     WORD               wleft, wright;
1069     WINE_WAVEDEV*      wwo;
1070     int                min, max;
1071     int                left, right;
1072     DWORD              rc;
1073
1074     TRACE("(%u, %08X);\n", wDevID, dwParam);
1075     if (wDevID >= ALSA_WodNumDevs) {
1076         TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
1077         return MMSYSERR_BADDEVICEID;
1078     }
1079
1080     wwo = &WOutDev[wDevID];
1081
1082     rc = ALSA_CheckSetVolume(wwo->hctl, NULL, NULL, &min, &max, NULL, NULL, NULL);
1083     if (rc == MMSYSERR_NOERROR)
1084     {
1085         wleft  = LOWORD(dwParam);
1086         wright = HIWORD(dwParam);
1087 #define VOLUME_WIN_TO_ALSA(x) ( (  ( ((x) * (max-min)) + 32767) / 65535) + min )
1088         left = VOLUME_WIN_TO_ALSA(wleft);
1089         right = VOLUME_WIN_TO_ALSA(wright);
1090 #undef VOLUME_WIN_TO_ALSA
1091         rc = ALSA_CheckSetVolume(wwo->hctl, NULL, NULL, NULL, NULL, NULL, &left, &right);
1092         if (rc == MMSYSERR_NOERROR)
1093             TRACE("set volume:  wleft=%d, wright=%d, converted to alsa left %d, right %d\n", wleft, wright, left, right);
1094         else
1095             TRACE("SetVolume failed; rc %d\n", rc);
1096     }
1097
1098     return rc;
1099 }
1100
1101 /**************************************************************************
1102  *                              wodGetNumDevs                   [internal]
1103  */
1104 static  DWORD   wodGetNumDevs(void)
1105 {
1106     return ALSA_WodNumDevs;
1107 }
1108
1109 /**************************************************************************
1110  *                              wodDevInterfaceSize             [internal]
1111  */
1112 static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
1113 {
1114     TRACE("(%u, %p)\n", wDevID, dwParam1);
1115
1116     *dwParam1 = MultiByteToWideChar(CP_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
1117                                     NULL, 0 ) * sizeof(WCHAR);
1118     return MMSYSERR_NOERROR;
1119 }
1120
1121 /**************************************************************************
1122  *                              wodDevInterface                 [internal]
1123  */
1124 static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2)
1125 {
1126     if (dwParam2 >= MultiByteToWideChar(CP_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
1127                                         NULL, 0 ) * sizeof(WCHAR))
1128     {
1129         MultiByteToWideChar(CP_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
1130                             dwParam1, dwParam2 / sizeof(WCHAR));
1131         return MMSYSERR_NOERROR;
1132     }
1133     return MMSYSERR_INVALPARAM;
1134 }
1135
1136 /**************************************************************************
1137  *                              wodMessage (WINEALSA.@)
1138  */
1139 DWORD WINAPI ALSA_wodMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1140                              DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1141 {
1142     TRACE("(%u, %s, %08lX, %08lX, %08lX);\n",
1143           wDevID, ALSA_getMessage(wMsg), dwUser, dwParam1, dwParam2);
1144
1145     switch (wMsg) {
1146     case DRVM_INIT:
1147         ALSA_WaveInit();
1148     case DRVM_EXIT:
1149     case DRVM_ENABLE:
1150     case DRVM_DISABLE:
1151         /* FIXME: Pretend this is supported */
1152         return 0;
1153     case WODM_OPEN:             return wodOpen          (wDevID, (LPWAVEOPENDESC)dwParam1,      dwParam2);
1154     case WODM_CLOSE:            return wodClose         (wDevID);
1155     case WODM_GETDEVCAPS:       return wodGetDevCaps    (wDevID, (LPWAVEOUTCAPSW)dwParam1,      dwParam2);
1156     case WODM_GETNUMDEVS:       return wodGetNumDevs    ();
1157     case WODM_GETPITCH:         return MMSYSERR_NOTSUPPORTED;
1158     case WODM_SETPITCH:         return MMSYSERR_NOTSUPPORTED;
1159     case WODM_GETPLAYBACKRATE:  return MMSYSERR_NOTSUPPORTED;
1160     case WODM_SETPLAYBACKRATE:  return MMSYSERR_NOTSUPPORTED;
1161     case WODM_WRITE:            return wodWrite         (wDevID, (LPWAVEHDR)dwParam1,           dwParam2);
1162     case WODM_PAUSE:            return wodPause         (wDevID);
1163     case WODM_GETPOS:           return wodGetPosition   (wDevID, (LPMMTIME)dwParam1,            dwParam2);
1164     case WODM_BREAKLOOP:        return wodBreakLoop     (wDevID);
1165     case WODM_PREPARE:          return MMSYSERR_NOTSUPPORTED;
1166     case WODM_UNPREPARE:        return MMSYSERR_NOTSUPPORTED;
1167     case WODM_GETVOLUME:        return wodGetVolume     (wDevID, (LPDWORD)dwParam1);
1168     case WODM_SETVOLUME:        return wodSetVolume     (wDevID, dwParam1);
1169     case WODM_RESTART:          return wodRestart       (wDevID);
1170     case WODM_RESET:            return wodReset         (wDevID);
1171     case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize       (wDevID, (LPDWORD)dwParam1);
1172     case DRV_QUERYDEVICEINTERFACE:     return wodDevInterface           (wDevID, (PWCHAR)dwParam1, dwParam2);
1173     case DRV_QUERYDSOUNDIFACE:  return wodDsCreate      (wDevID, (PIDSDRIVER*)dwParam1);
1174     case DRV_QUERYDSOUNDDESC:   return wodDsDesc        (wDevID, (PDSDRIVERDESC)dwParam1);
1175
1176     default:
1177         FIXME("unknown message %d!\n", wMsg);
1178     }
1179     return MMSYSERR_NOTSUPPORTED;
1180 }