winealsa.drv: Implement IAudioSessionControl::GetState.
[wine/multimedia.git] / dlls / winealsa.drv / waveout.c
blob88153e76bee5fdb9274fdf8535e9fbc8ef26aaeb
1 /*
2 * Sample Wine Driver for Advanced Linux Sound System (ALSA)
3 * Based on version <final> of the ALSA API
5 * Copyright 2002 Eric Pouech
6 * 2002 Marco Pietrobono
7 * 2003 Christian Costa : WaveIn support
8 * 2006-2007 Maarten Lankhorst
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.
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.
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
25 /*======================================================================*
26 * Low level WAVE OUT implementation *
27 *======================================================================*/
29 #include "config.h"
30 #include "wine/port.h"
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 #include "mmreg.h"
55 #include "dsound.h"
56 #include "dsdriver.h"
57 #include "ks.h"
58 #include "ksmedia.h"
60 #include "alsa.h"
62 #include "wine/library.h"
63 #include "wine/unicode.h"
64 #include "wine/debug.h"
66 WINE_DEFAULT_DEBUG_CHANNEL(wave);
68 WINE_WAVEDEV *WOutDev;
69 DWORD ALSA_WodNumMallocedDevs;
70 DWORD ALSA_WodNumDevs;
72 /**************************************************************************
73 * wodNotifyClient [internal]
75 static void wodNotifyClient(WINE_WAVEDEV* wwo, WORD wMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
77 TRACE("wMsg = 0x%04x dwParm1 = %lx dwParam2 = %lx\n", wMsg, dwParam1, dwParam2);
79 switch (wMsg) {
80 case WOM_OPEN:
81 case WOM_CLOSE:
82 case WOM_DONE:
83 DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, (HDRVR)wwo->waveDesc.hWave,
84 wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2);
85 break;
86 default:
87 FIXME("Unknown callback message %u\n", wMsg);
91 /**************************************************************************
92 * wodUpdatePlayedTotal [internal]
95 static BOOL wodUpdatePlayedTotal(WINE_WAVEDEV* wwo, snd_pcm_status_t* ps)
97 snd_pcm_sframes_t delay;
98 snd_pcm_sframes_t avail;
99 snd_pcm_uframes_t buf_size = 0;
100 snd_pcm_state_t state;
102 state = snd_pcm_state(wwo->pcm);
103 avail = snd_pcm_avail_update(wwo->pcm);
104 snd_pcm_hw_params_get_buffer_size(wwo->hw_params, &buf_size);
105 delay = buf_size - avail;
107 if (state != SND_PCM_STATE_RUNNING && state != SND_PCM_STATE_PREPARED)
109 WARN("Unexpected state (%d) while updating Total Played, resetting\n", state);
110 wine_snd_pcm_recover(wwo->pcm, -EPIPE, 0);
111 delay=0;
114 /* A delay < 0 indicates an underrun; for our purposes that's 0. */
115 if (delay < 0)
117 WARN("Unexpected delay (%ld) while updating Total Played, resetting\n", delay);
118 delay=0;
121 InterlockedExchange((LONG*)&wwo->dwPlayedTotal, wwo->dwWrittenTotal - snd_pcm_frames_to_bytes(wwo->pcm, delay));
122 return TRUE;
125 /**************************************************************************
126 * wodPlayer_BeginWaveHdr [internal]
128 * Makes the specified lpWaveHdr the currently playing wave header.
129 * If the specified wave header is a begin loop and we're not already in
130 * a loop, setup the loop.
132 static void wodPlayer_BeginWaveHdr(WINE_WAVEDEV* wwo, LPWAVEHDR lpWaveHdr)
134 wwo->lpPlayPtr = lpWaveHdr;
136 if (!lpWaveHdr) return;
138 if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) {
139 if (wwo->lpLoopPtr) {
140 WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
141 } else {
142 TRACE("Starting loop (%dx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);
143 wwo->lpLoopPtr = lpWaveHdr;
144 /* Windows does not touch WAVEHDR.dwLoops,
145 * so we need to make an internal copy */
146 wwo->dwLoops = lpWaveHdr->dwLoops;
149 wwo->dwPartialOffset = 0;
152 /**************************************************************************
153 * wodPlayer_PlayPtrNext [internal]
155 * Advance the play pointer to the next waveheader, looping if required.
157 static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEDEV* wwo)
159 LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
161 wwo->dwPartialOffset = 0;
162 if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) {
163 /* We're at the end of a loop, loop if required */
164 if (--wwo->dwLoops > 0) {
165 wwo->lpPlayPtr = wwo->lpLoopPtr;
166 } else {
167 /* Handle overlapping loops correctly */
168 if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
169 FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
170 /* shall we consider the END flag for the closing loop or for
171 * the opening one or for both ???
172 * code assumes for closing loop only
174 } else {
175 lpWaveHdr = lpWaveHdr->lpNext;
177 wwo->lpLoopPtr = NULL;
178 wodPlayer_BeginWaveHdr(wwo, lpWaveHdr);
180 } else {
181 /* We're not in a loop. Advance to the next wave header */
182 wodPlayer_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
185 return lpWaveHdr;
188 /**************************************************************************
189 * wodPlayer_DSPWait [internal]
190 * Returns the number of milliseconds to wait for the DSP buffer to play a
191 * period
193 static DWORD wodPlayer_DSPWait(const WINE_WAVEDEV *wwo)
195 /* time for one period to be played */
196 unsigned int val=0;
197 int dir=0;
198 int err=0;
199 err = snd_pcm_hw_params_get_period_time(wwo->hw_params, &val, &dir);
200 return val / 1000;
203 /**************************************************************************
204 * wodPlayer_NotifyWait [internal]
205 * Returns the number of milliseconds to wait before attempting to notify
206 * completion of the specified wavehdr.
207 * This is based on the number of bytes remaining to be written in the
208 * wave.
210 static DWORD wodPlayer_NotifyWait(const WINE_WAVEDEV* wwo, LPWAVEHDR lpWaveHdr)
212 DWORD dwMillis;
214 if (lpWaveHdr->reserved < wwo->dwPlayedTotal) {
215 dwMillis = 1;
216 } else {
217 dwMillis = (lpWaveHdr->reserved - wwo->dwPlayedTotal) * 1000 / wwo->format.Format.nAvgBytesPerSec;
218 if (!dwMillis) dwMillis = 1;
221 return dwMillis;
225 /**************************************************************************
226 * wodPlayer_WriteMaxFrags [internal]
227 * Writes the maximum number of frames possible to the DSP and returns
228 * the number of frames written.
230 static int wodPlayer_WriteMaxFrags(WINE_WAVEDEV* wwo, DWORD* frames)
232 /* Only attempt to write to free frames */
233 LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
234 DWORD dwLength = snd_pcm_bytes_to_frames(wwo->pcm, lpWaveHdr->dwBufferLength - wwo->dwPartialOffset);
235 int toWrite = min(dwLength, *frames);
236 int written;
238 TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr, wwo->dwPartialOffset, lpWaveHdr->dwBufferLength);
240 if (toWrite > 0) {
241 written = (wwo->write)(wwo->pcm, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite);
242 if ( written < 0) {
243 /* XRUN occurred. let's try to recover */
244 wine_snd_pcm_recover(wwo->pcm, written, 0);
245 written = (wwo->write)(wwo->pcm, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite);
247 if (written <= 0) {
248 /* still in error */
249 ERR("Error in writing wavehdr. Reason: %s\n", snd_strerror(written));
250 return written;
252 } else
253 written = 0;
255 wwo->dwPartialOffset += snd_pcm_frames_to_bytes(wwo->pcm, written);
256 if (wwo->dwPartialOffset + wwo->format.Format.nBlockAlign - 1 >= lpWaveHdr->dwBufferLength) {
257 /* this will be used to check if the given wave header has been fully played or not... */
258 wwo->dwPartialOffset = lpWaveHdr->dwBufferLength;
259 /* If we wrote all current wavehdr, skip to the next one */
260 wodPlayer_PlayPtrNext(wwo);
262 *frames -= written;
263 wwo->dwWrittenTotal += snd_pcm_frames_to_bytes(wwo->pcm, written);
264 TRACE("dwWrittenTotal=%u\n", wwo->dwWrittenTotal);
266 return written;
270 /**************************************************************************
271 * wodPlayer_NotifyCompletions [internal]
273 * Notifies and remove from queue all wavehdrs which have been played to
274 * the speaker (ie. they have cleared the ALSA buffer). If force is true,
275 * we notify all wavehdrs and remove them all from the queue even if they
276 * are unplayed or part of a loop.
278 static DWORD wodPlayer_NotifyCompletions(WINE_WAVEDEV* wwo, BOOL force)
280 LPWAVEHDR lpWaveHdr;
282 /* Start from lpQueuePtr and keep notifying until:
283 * - we hit an unwritten wavehdr
284 * - we hit the beginning of a running loop
285 * - we hit a wavehdr which hasn't finished playing
287 for (;;)
289 lpWaveHdr = wwo->lpQueuePtr;
290 if (!lpWaveHdr) {TRACE("Empty queue\n"); break;}
291 if (!force)
293 snd_pcm_uframes_t frames;
294 snd_pcm_hw_params_get_period_size(wwo->hw_params, &frames, NULL);
296 if (lpWaveHdr == wwo->lpPlayPtr) {TRACE("play %p\n", lpWaveHdr); break;}
297 if (lpWaveHdr == wwo->lpLoopPtr) {TRACE("loop %p\n", lpWaveHdr); break;}
298 if (lpWaveHdr->reserved > wwo->dwPlayedTotal + frames) {TRACE("still playing %p (%lu/%u)\n", lpWaveHdr, lpWaveHdr->reserved, wwo->dwPlayedTotal);break;}
300 wwo->dwPlayedTotal += lpWaveHdr->reserved - wwo->dwPlayedTotal;
301 wwo->lpQueuePtr = lpWaveHdr->lpNext;
303 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
304 lpWaveHdr->dwFlags |= WHDR_DONE;
306 wodNotifyClient(wwo, WOM_DONE, (DWORD_PTR)lpWaveHdr, 0);
308 return (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr != wwo->lpLoopPtr) ?
309 wodPlayer_NotifyWait(wwo, lpWaveHdr) : INFINITE;
313 /**************************************************************************
314 * wodPlayer_Reset [internal]
316 * wodPlayer helper. Resets current output stream.
318 static void wodPlayer_Reset(WINE_WAVEDEV* wwo, BOOL reset)
320 int err;
321 TRACE("(%p)\n", wwo);
323 wodUpdatePlayedTotal(wwo, NULL);
324 /* updates current notify list */
325 wodPlayer_NotifyCompletions(wwo, FALSE);
327 if ( (err = snd_pcm_drop(wwo->pcm)) < 0) {
328 FIXME("flush: %s\n", snd_strerror(err));
329 wwo->hThread = 0;
330 wwo->state = WINE_WS_STOPPED;
331 ExitThread(-1);
333 if ( (err = snd_pcm_prepare(wwo->pcm)) < 0 )
334 ERR("pcm prepare failed: %s\n", snd_strerror(err));
336 if (reset) {
337 enum win_wm_message msg;
338 DWORD_PTR param;
339 HANDLE ev;
341 /* remove any buffer */
342 wodPlayer_NotifyCompletions(wwo, TRUE);
344 wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
345 wwo->state = WINE_WS_STOPPED;
346 wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
347 /* Clear partial wavehdr */
348 wwo->dwPartialOffset = 0;
350 /* remove any existing message in the ring */
351 EnterCriticalSection(&wwo->msgRing.msg_crst);
352 /* return all pending headers in queue */
353 while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev))
355 if (msg != WINE_WM_HEADER)
357 FIXME("shouldn't have headers left\n");
358 SetEvent(ev);
359 continue;
361 ((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE;
362 ((LPWAVEHDR)param)->dwFlags |= WHDR_DONE;
364 wodNotifyClient(wwo, WOM_DONE, param, 0);
366 ALSA_ResetRingMessage(&wwo->msgRing);
367 LeaveCriticalSection(&wwo->msgRing.msg_crst);
368 } else {
369 if (wwo->lpLoopPtr) {
370 /* complicated case, not handled yet (could imply modifying the loop counter */
371 FIXME("Pausing while in loop isn't correctly handled yet, expect strange results\n");
372 wwo->lpPlayPtr = wwo->lpLoopPtr;
373 wwo->dwPartialOffset = 0;
374 wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */
375 } else {
376 LPWAVEHDR ptr;
377 DWORD sz = wwo->dwPartialOffset;
379 /* reset all the data as if we had written only up to lpPlayedTotal bytes */
380 /* compute the max size playable from lpQueuePtr */
381 for (ptr = wwo->lpQueuePtr; ptr != wwo->lpPlayPtr; ptr = ptr->lpNext) {
382 sz += ptr->dwBufferLength;
384 /* because the reset lpPlayPtr will be lpQueuePtr */
385 if (wwo->dwWrittenTotal > wwo->dwPlayedTotal + sz) ERR("grin\n");
386 wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal);
387 wwo->dwWrittenTotal = wwo->dwPlayedTotal;
388 wwo->lpPlayPtr = wwo->lpQueuePtr;
390 wwo->state = WINE_WS_PAUSED;
394 /**************************************************************************
395 * wodPlayer_ProcessMessages [internal]
397 static void wodPlayer_ProcessMessages(WINE_WAVEDEV* wwo)
399 LPWAVEHDR lpWaveHdr;
400 enum win_wm_message msg;
401 DWORD_PTR param;
402 HANDLE ev;
403 int err;
405 while (ALSA_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
406 TRACE("Received %s %lx\n", ALSA_getCmdString(msg), param);
408 switch (msg) {
409 case WINE_WM_PAUSING:
410 if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_RUNNING )
412 if ( snd_pcm_hw_params_can_pause(wwo->hw_params) )
414 err = snd_pcm_pause(wwo->pcm, 1);
415 if ( err < 0 )
416 ERR("pcm_pause failed: %s\n", snd_strerror(err));
417 wwo->state = WINE_WS_PAUSED;
419 else
421 wodPlayer_Reset(wwo,FALSE);
424 SetEvent(ev);
425 break;
426 case WINE_WM_RESTARTING:
427 if (wwo->state == WINE_WS_PAUSED)
429 if ( snd_pcm_state(wwo->pcm) == SND_PCM_STATE_PAUSED )
431 err = snd_pcm_pause(wwo->pcm, 0);
432 if ( err < 0 )
433 ERR("pcm_pause failed: %s\n", snd_strerror(err));
435 wwo->state = WINE_WS_PLAYING;
437 SetEvent(ev);
438 break;
439 case WINE_WM_HEADER:
440 lpWaveHdr = (LPWAVEHDR)param;
442 /* insert buffer at the end of queue */
444 LPWAVEHDR* wh;
445 for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
446 *wh = lpWaveHdr;
448 if (!wwo->lpPlayPtr)
449 wodPlayer_BeginWaveHdr(wwo,lpWaveHdr);
450 if (wwo->state == WINE_WS_STOPPED)
451 wwo->state = WINE_WS_PLAYING;
452 break;
453 case WINE_WM_RESETTING:
454 wodPlayer_Reset(wwo,TRUE);
455 SetEvent(ev);
456 break;
457 case WINE_WM_BREAKLOOP:
458 if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL) {
459 /* ensure exit at end of current loop */
460 wwo->dwLoops = 1;
462 SetEvent(ev);
463 break;
464 case WINE_WM_CLOSING:
465 /* sanity check: this should not happen since the device must have been reset before */
466 if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
467 wwo->hThread = 0;
468 wwo->state = WINE_WS_CLOSED;
469 SetEvent(ev);
470 ExitThread(0);
471 /* shouldn't go here */
472 default:
473 FIXME("unknown message %d\n", msg);
474 break;
479 /**************************************************************************
480 * wodPlayer_FeedDSP [internal]
481 * Feed as much sound data as we can into the DSP and return the number of
482 * milliseconds before it will be necessary to feed the DSP again.
484 static DWORD wodPlayer_FeedDSP(WINE_WAVEDEV* wwo)
486 DWORD availInQ;
488 wodUpdatePlayedTotal(wwo, NULL);
489 availInQ = snd_pcm_avail_update(wwo->pcm);
491 /* no more room... no need to try to feed */
492 if (availInQ > 0) {
493 /* Feed from partial wavehdr */
494 if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0) {
495 wodPlayer_WriteMaxFrags(wwo, &availInQ);
498 /* Feed wavehdrs until we run out of wavehdrs or DSP space */
499 if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) {
500 do {
501 TRACE("Setting time to elapse for %p to %u\n",
502 wwo->lpPlayPtr, wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength);
503 /* note the value that dwPlayedTotal will return when this wave finishes playing */
504 wwo->lpPlayPtr->reserved = wwo->dwWrittenTotal + wwo->lpPlayPtr->dwBufferLength;
505 } while (wodPlayer_WriteMaxFrags(wwo, &availInQ) && wwo->lpPlayPtr && availInQ > 0);
509 return wodPlayer_DSPWait(wwo);
512 /**************************************************************************
513 * wodPlayer [internal]
515 static DWORD CALLBACK wodPlayer(LPVOID pmt)
517 WORD uDevID = (DWORD_PTR)pmt;
518 WINE_WAVEDEV* wwo = &WOutDev[uDevID];
519 DWORD dwNextFeedTime = INFINITE; /* Time before DSP needs feeding */
520 DWORD dwNextNotifyTime = INFINITE; /* Time before next wave completion */
521 DWORD dwSleepTime;
523 wwo->state = WINE_WS_STOPPED;
524 SetEvent(wwo->hStartUpEvent);
526 for (;;) {
527 /** Wait for the shortest time before an action is required. If there
528 * are no pending actions, wait forever for a command.
530 dwSleepTime = min(dwNextFeedTime, dwNextNotifyTime);
531 TRACE("waiting %ums (%u,%u)\n", dwSleepTime, dwNextFeedTime, dwNextNotifyTime);
532 ALSA_WaitRingMessage(&wwo->msgRing, dwSleepTime);
533 wodPlayer_ProcessMessages(wwo);
534 if (wwo->state == WINE_WS_PLAYING) {
535 dwNextFeedTime = wodPlayer_FeedDSP(wwo);
536 dwNextNotifyTime = wodPlayer_NotifyCompletions(wwo, FALSE);
537 if (dwNextFeedTime == INFINITE) {
538 /* FeedDSP ran out of data, but before giving up, */
539 /* check that a notification didn't give us more */
540 wodPlayer_ProcessMessages(wwo);
541 if (wwo->lpPlayPtr) {
542 TRACE("recovering\n");
543 dwNextFeedTime = wodPlayer_FeedDSP(wwo);
546 } else {
547 dwNextFeedTime = dwNextNotifyTime = INFINITE;
550 return 0;
553 /**************************************************************************
554 * wodGetDevCaps [internal]
556 static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize)
558 TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
560 if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
562 if (wDevID >= ALSA_WodNumDevs) {
563 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
564 return MMSYSERR_BADDEVICEID;
567 memcpy(lpCaps, &WOutDev[wDevID].outcaps, min(dwSize, sizeof(*lpCaps)));
568 return MMSYSERR_NOERROR;
571 /**************************************************************************
572 * wodOpen [internal]
574 static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
576 WINE_WAVEDEV* wwo;
577 snd_pcm_t * pcm = NULL;
578 snd_hctl_t * hctl = NULL;
579 snd_pcm_hw_params_t * hw_params;
580 snd_pcm_sw_params_t * sw_params;
581 snd_pcm_access_t access;
582 snd_pcm_format_t format = -1;
583 unsigned int rate;
584 unsigned int buffer_time = 120000;
585 unsigned int period_time = 22000;
586 snd_pcm_uframes_t buffer_size;
587 snd_pcm_uframes_t period_size;
588 int flags;
589 int err=0;
590 int dir=0;
591 DWORD retcode = 0;
593 TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
594 if (lpDesc == NULL) {
595 WARN("Invalid Parameter !\n");
596 return MMSYSERR_INVALPARAM;
598 if (wDevID >= ALSA_WodNumDevs) {
599 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
600 return MMSYSERR_BADDEVICEID;
603 /* only PCM format is supported so far... */
604 if (!ALSA_supportedFormat(lpDesc->lpFormat)) {
605 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
606 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
607 lpDesc->lpFormat->nSamplesPerSec);
608 return WAVERR_BADFORMAT;
611 if (dwFlags & WAVE_FORMAT_QUERY) {
612 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
613 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
614 lpDesc->lpFormat->nSamplesPerSec);
615 return MMSYSERR_NOERROR;
618 wwo = &WOutDev[wDevID];
620 if (wwo->pcm != NULL) {
621 WARN("%d already allocated\n", wDevID);
622 return MMSYSERR_ALLOCATED;
625 if (dwFlags & WAVE_DIRECTSOUND)
626 FIXME("Why are we called with DirectSound flag? It doesn't use MMSYSTEM any more\n");
627 /* not supported, ignore it */
628 dwFlags &= ~WAVE_DIRECTSOUND;
630 flags = SND_PCM_NONBLOCK;
632 if ( (err = snd_pcm_open(&pcm, wwo->pcmname, SND_PCM_STREAM_PLAYBACK, flags)) < 0)
634 ERR("Error open: %s\n", snd_strerror(err));
635 return MMSYSERR_NOTENABLED;
638 if (wwo->ctlname)
640 err = snd_hctl_open(&hctl, wwo->ctlname, 0);
641 if (err >= 0)
643 snd_hctl_load(hctl);
645 else
647 WARN("Could not open hctl for [%s]: %s\n", wwo->ctlname, snd_strerror(err));
648 hctl = NULL;
652 wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
654 wwo->waveDesc = *lpDesc;
655 ALSA_copyFormat(lpDesc->lpFormat, &wwo->format);
657 TRACE("Requested this format: %dx%dx%d %s\n",
658 wwo->format.Format.nSamplesPerSec,
659 wwo->format.Format.wBitsPerSample,
660 wwo->format.Format.nChannels,
661 ALSA_getFormat(wwo->format.Format.wFormatTag));
663 if (wwo->format.Format.wBitsPerSample == 0) {
664 WARN("Resetting zeroed wBitsPerSample\n");
665 wwo->format.Format.wBitsPerSample = 8 *
666 (wwo->format.Format.nAvgBytesPerSec /
667 wwo->format.Format.nSamplesPerSec) /
668 wwo->format.Format.nChannels;
671 #define EXIT_ON_ERROR(f,e,txt) do \
673 int err; \
674 if ( (err = (f) ) < 0) \
676 WARN(txt ": %s\n", snd_strerror(err)); \
677 retcode=e; \
678 goto errexit; \
680 } while(0)
682 sw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_sw_params_sizeof() );
683 hw_params = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, snd_pcm_hw_params_sizeof() );
684 if (!hw_params || !sw_params)
686 retcode = MMSYSERR_NOMEM;
687 goto errexit;
689 snd_pcm_hw_params_any(pcm, hw_params);
691 access = SND_PCM_ACCESS_MMAP_INTERLEAVED;
692 if ( ( err = snd_pcm_hw_params_set_access(pcm, hw_params, access ) ) < 0) {
693 WARN("mmap not available. switching to standard write.\n");
694 access = SND_PCM_ACCESS_RW_INTERLEAVED;
695 EXIT_ON_ERROR( snd_pcm_hw_params_set_access(pcm, hw_params, access ), MMSYSERR_INVALPARAM, "unable to set access for playback");
696 wwo->write = snd_pcm_writei;
698 else
699 wwo->write = snd_pcm_mmap_writei;
701 if ((err = snd_pcm_hw_params_set_channels(pcm, hw_params, wwo->format.Format.nChannels)) < 0) {
702 WARN("unable to set required channels: %d\n", wwo->format.Format.nChannels);
703 EXIT_ON_ERROR( snd_pcm_hw_params_set_channels(pcm, hw_params, wwo->format.Format.nChannels ), WAVERR_BADFORMAT, "unable to set required channels" );
706 if ((wwo->format.Format.wFormatTag == WAVE_FORMAT_PCM) ||
707 ((wwo->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
708 IsEqualGUID(&wwo->format.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) {
709 format = (wwo->format.Format.wBitsPerSample == 8) ? SND_PCM_FORMAT_U8 :
710 (wwo->format.Format.wBitsPerSample == 16) ? SND_PCM_FORMAT_S16_LE :
711 (wwo->format.Format.wBitsPerSample == 24) ? SND_PCM_FORMAT_S24_3LE :
712 (wwo->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_S32_LE : -1;
713 } else if ((wwo->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) &&
714 IsEqualGUID(&wwo->format.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)){
715 format = (wwo->format.Format.wBitsPerSample == 32) ? SND_PCM_FORMAT_FLOAT_LE : -1;
716 } else {
717 ERR("invalid format: %0x04x\n", wwo->format.Format.wFormatTag);
718 retcode = WAVERR_BADFORMAT;
719 goto errexit;
722 if ((err = snd_pcm_hw_params_set_format(pcm, hw_params, format)) < 0) {
723 WARN("unable to set required format: %s\n", snd_pcm_format_name(format));
724 EXIT_ON_ERROR( snd_pcm_hw_params_set_format(pcm, hw_params, format), WAVERR_BADFORMAT, "unable to set required format" );
727 rate = wwo->format.Format.nSamplesPerSec;
728 dir=0;
729 err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, &dir);
730 if (err < 0) {
731 WARN("Rate %d Hz not available for playback: %s\n", wwo->format.Format.nSamplesPerSec, snd_strerror(rate));
732 retcode = WAVERR_BADFORMAT;
733 goto errexit;
735 if (!ALSA_NearMatch(rate, wwo->format.Format.nSamplesPerSec)) {
736 WARN("Rate doesn't match (requested %d Hz, got %d Hz)\n", wwo->format.Format.nSamplesPerSec, rate);
737 retcode = WAVERR_BADFORMAT;
738 goto errexit;
741 TRACE("Got this format: %dx%dx%d %s\n",
742 wwo->format.Format.nSamplesPerSec,
743 wwo->format.Format.wBitsPerSample,
744 wwo->format.Format.nChannels,
745 ALSA_getFormat(wwo->format.Format.wFormatTag));
747 dir=0;
748 EXIT_ON_ERROR( snd_pcm_hw_params_set_buffer_time_near(pcm, hw_params, &buffer_time, &dir), MMSYSERR_INVALPARAM, "unable to set buffer time");
749 dir=0;
750 EXIT_ON_ERROR( snd_pcm_hw_params_set_period_time_near(pcm, hw_params, &period_time, &dir), MMSYSERR_INVALPARAM, "unable to set period time");
752 EXIT_ON_ERROR( snd_pcm_hw_params(pcm, hw_params), MMSYSERR_INVALPARAM, "unable to set hw params for playback");
754 err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir);
755 err = snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
757 snd_pcm_sw_params_current(pcm, sw_params);
759 EXIT_ON_ERROR( snd_pcm_sw_params_set_start_threshold(pcm, sw_params, 1), MMSYSERR_ERROR, "unable to set start threshold");
760 EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_size(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence size");
761 EXIT_ON_ERROR( snd_pcm_sw_params_set_avail_min(pcm, sw_params, period_size), MMSYSERR_ERROR, "unable to set avail min");
762 EXIT_ON_ERROR( snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, 0), MMSYSERR_ERROR, "unable to set silence threshold");
763 EXIT_ON_ERROR( snd_pcm_sw_params(pcm, sw_params), MMSYSERR_ERROR, "unable to set sw params for playback");
764 #undef EXIT_ON_ERROR
766 snd_pcm_prepare(pcm);
768 if (TRACE_ON(wave))
769 ALSA_TraceParameters(hw_params, sw_params, FALSE);
771 /* now, we can save all required data for later use... */
773 wwo->dwBufferSize = snd_pcm_frames_to_bytes(pcm, buffer_size);
774 wwo->lpQueuePtr = wwo->lpPlayPtr = wwo->lpLoopPtr = NULL;
775 wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
776 wwo->dwPartialOffset = 0;
778 ALSA_InitRingMessage(&wwo->msgRing);
780 wwo->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
781 wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)(DWORD_PTR)wDevID, 0, &(wwo->dwThreadID));
782 if (!wwo->hThread) {
783 ERR("Thread creation for the wodPlayer failed!\n");
784 CloseHandle(wwo->hStartUpEvent);
785 retcode = MMSYSERR_NOMEM;
786 goto errexit;
788 SetThreadPriority(wwo->hThread, THREAD_PRIORITY_TIME_CRITICAL);
789 WaitForSingleObject(wwo->hStartUpEvent, INFINITE);
790 CloseHandle(wwo->hStartUpEvent);
791 wwo->hStartUpEvent = NULL;
793 TRACE("handle=%p\n", pcm);
794 TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%u, nSamplesPerSec=%u, nChannels=%u nBlockAlign=%u!\n",
795 wwo->format.Format.wBitsPerSample, wwo->format.Format.nAvgBytesPerSec,
796 wwo->format.Format.nSamplesPerSec, wwo->format.Format.nChannels,
797 wwo->format.Format.nBlockAlign);
799 HeapFree( GetProcessHeap(), 0, sw_params );
800 wwo->hw_params = hw_params;
801 wwo->hctl = hctl;
802 wwo->pcm = pcm;
804 wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);
805 return MMSYSERR_NOERROR;
807 errexit:
808 if (pcm)
809 snd_pcm_close(pcm);
811 if (hctl)
813 snd_hctl_free(hctl);
814 snd_hctl_close(hctl);
817 HeapFree( GetProcessHeap(), 0, hw_params );
818 HeapFree( GetProcessHeap(), 0, sw_params );
819 if (wwo->msgRing.ring_buffer_size > 0)
820 ALSA_DestroyRingMessage(&wwo->msgRing);
822 return retcode;
826 /**************************************************************************
827 * wodClose [internal]
829 static DWORD wodClose(WORD wDevID)
831 WINE_WAVEDEV* wwo;
833 TRACE("(%u);\n", wDevID);
835 if (wDevID >= ALSA_WodNumDevs) {
836 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
837 return MMSYSERR_BADDEVICEID;
840 wwo = &WOutDev[wDevID];
841 if (wwo->pcm == NULL) {
842 WARN("Requested to close already closed device %d!\n", wDevID);
843 return MMSYSERR_BADDEVICEID;
846 if (wwo->lpQueuePtr) {
847 WARN("buffers still playing !\n");
848 return WAVERR_STILLPLAYING;
849 } else {
850 if (wwo->hThread) {
851 ALSA_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE);
853 ALSA_DestroyRingMessage(&wwo->msgRing);
855 HeapFree( GetProcessHeap(), 0, wwo->hw_params );
856 wwo->hw_params = NULL;
858 if (wwo->hctl)
860 snd_hctl_free(wwo->hctl);
861 snd_hctl_close(wwo->hctl);
862 wwo->hctl = NULL;
865 snd_pcm_close(wwo->pcm);
866 wwo->pcm = NULL;
868 wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
871 return MMSYSERR_NOERROR;
875 /**************************************************************************
876 * wodWrite [internal]
879 static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
881 TRACE("(%u, %p, %08X);\n", wDevID, lpWaveHdr, dwSize);
883 if (wDevID >= ALSA_WodNumDevs) {
884 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
885 return MMSYSERR_BADDEVICEID;
888 if (WOutDev[wDevID].pcm == NULL) {
889 WARN("Requested to write to closed device %d!\n", wDevID);
890 return MMSYSERR_BADDEVICEID;
893 if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
894 return WAVERR_UNPREPARED;
896 if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
897 return WAVERR_STILLPLAYING;
899 lpWaveHdr->dwFlags &= ~WHDR_DONE;
900 lpWaveHdr->dwFlags |= WHDR_INQUEUE;
901 lpWaveHdr->lpNext = 0;
903 ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_HEADER, (DWORD_PTR)lpWaveHdr, FALSE);
905 return MMSYSERR_NOERROR;
908 /**************************************************************************
909 * wodPause [internal]
911 static DWORD wodPause(WORD wDevID)
913 TRACE("(%u);!\n", wDevID);
915 if (wDevID >= ALSA_WodNumDevs) {
916 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
917 return MMSYSERR_BADDEVICEID;
920 if (WOutDev[wDevID].pcm == NULL) {
921 WARN("Requested to pause closed device %d!\n", wDevID);
922 return MMSYSERR_BADDEVICEID;
925 ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_PAUSING, 0, TRUE);
927 return MMSYSERR_NOERROR;
930 /**************************************************************************
931 * wodRestart [internal]
933 static DWORD wodRestart(WORD wDevID)
935 TRACE("(%u);\n", wDevID);
937 if (wDevID >= ALSA_WodNumDevs) {
938 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
939 return MMSYSERR_BADDEVICEID;
942 if (WOutDev[wDevID].pcm == NULL) {
943 WARN("Requested to restart closed device %d!\n", wDevID);
944 return MMSYSERR_BADDEVICEID;
947 if (WOutDev[wDevID].state == WINE_WS_PAUSED) {
948 ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESTARTING, 0, TRUE);
951 /* FIXME: is NotifyClient with WOM_DONE right ? (Comet Busters 1.3.3 needs this notification) */
952 /* FIXME: Myst crashes with this ... hmm -MM
953 return wodNotifyClient(wwo, WOM_DONE, 0L, 0L);
956 return MMSYSERR_NOERROR;
959 /**************************************************************************
960 * wodReset [internal]
962 static DWORD wodReset(WORD wDevID)
964 TRACE("(%u);\n", wDevID);
966 if (wDevID >= ALSA_WodNumDevs) {
967 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
968 return MMSYSERR_BADDEVICEID;
971 if (WOutDev[wDevID].pcm == NULL) {
972 WARN("Requested to reset closed device %d!\n", wDevID);
973 return MMSYSERR_BADDEVICEID;
976 ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_RESETTING, 0, TRUE);
978 return MMSYSERR_NOERROR;
981 /**************************************************************************
982 * wodGetPosition [internal]
984 static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
986 WINE_WAVEDEV* wwo;
988 TRACE("(%u, %p, %u);\n", wDevID, lpTime, uSize);
990 if (wDevID >= ALSA_WodNumDevs) {
991 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
992 return MMSYSERR_BADDEVICEID;
995 if (WOutDev[wDevID].pcm == NULL) {
996 WARN("Requested to get position of closed device %d!\n", wDevID);
997 return MMSYSERR_BADDEVICEID;
1000 if (lpTime == NULL) return MMSYSERR_INVALPARAM;
1002 wwo = &WOutDev[wDevID];
1003 return ALSA_bytes_to_mmtime(lpTime, wwo->dwPlayedTotal, &wwo->format);
1006 /**************************************************************************
1007 * wodBreakLoop [internal]
1009 static DWORD wodBreakLoop(WORD wDevID)
1011 TRACE("(%u);\n", wDevID);
1013 if (wDevID >= ALSA_WodNumDevs) {
1014 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
1015 return MMSYSERR_BADDEVICEID;
1018 if (WOutDev[wDevID].pcm == NULL) {
1019 WARN("Requested to breakloop of closed device %d!\n", wDevID);
1020 return MMSYSERR_BADDEVICEID;
1023 ALSA_AddRingMessage(&WOutDev[wDevID].msgRing, WINE_WM_BREAKLOOP, 0, TRUE);
1024 return MMSYSERR_NOERROR;
1027 /**************************************************************************
1028 * wodGetVolume [internal]
1030 static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol)
1032 WORD wleft, wright;
1033 WINE_WAVEDEV* wwo;
1034 int min, max;
1035 int left, right;
1036 DWORD rc;
1038 TRACE("(%u, %p);\n", wDevID, lpdwVol);
1039 if (wDevID >= ALSA_WodNumDevs) {
1040 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
1041 return MMSYSERR_BADDEVICEID;
1044 if (lpdwVol == NULL)
1045 return MMSYSERR_NOTENABLED;
1047 wwo = &WOutDev[wDevID];
1049 rc = ALSA_CheckSetVolume(wwo->hctl, &left, &right, &min, &max, NULL, NULL, NULL);
1050 if (rc == MMSYSERR_NOERROR)
1052 #define VOLUME_ALSA_TO_WIN(x) ( ( (((x)-min) * 65535) + (max-min)/2 ) /(max-min))
1053 wleft = VOLUME_ALSA_TO_WIN(left);
1054 wright = VOLUME_ALSA_TO_WIN(right);
1055 #undef VOLUME_ALSA_TO_WIN
1056 TRACE("left=%d,right=%d,converted to windows left %d, right %d\n", left, right, wleft, wright);
1057 *lpdwVol = MAKELONG( wleft, wright );
1059 else
1060 TRACE("CheckSetVolume failed; rc %d\n", rc);
1062 return rc;
1065 /**************************************************************************
1066 * wodSetVolume [internal]
1068 static DWORD wodSetVolume(WORD wDevID, DWORD dwParam)
1070 WORD wleft, wright;
1071 WINE_WAVEDEV* wwo;
1072 int min, max;
1073 int left, right;
1074 DWORD rc;
1076 TRACE("(%u, %08X);\n", wDevID, dwParam);
1077 if (wDevID >= ALSA_WodNumDevs) {
1078 TRACE("Asked for device %d, but only %d known!\n", wDevID, ALSA_WodNumDevs);
1079 return MMSYSERR_BADDEVICEID;
1082 wwo = &WOutDev[wDevID];
1084 rc = ALSA_CheckSetVolume(wwo->hctl, NULL, NULL, &min, &max, NULL, NULL, NULL);
1085 if (rc == MMSYSERR_NOERROR)
1087 wleft = LOWORD(dwParam);
1088 wright = HIWORD(dwParam);
1089 #define VOLUME_WIN_TO_ALSA(x) ( ( ( ((x) * (max-min)) + 32767) / 65535) + min )
1090 left = VOLUME_WIN_TO_ALSA(wleft);
1091 right = VOLUME_WIN_TO_ALSA(wright);
1092 #undef VOLUME_WIN_TO_ALSA
1093 rc = ALSA_CheckSetVolume(wwo->hctl, NULL, NULL, NULL, NULL, NULL, &left, &right);
1094 if (rc == MMSYSERR_NOERROR)
1095 TRACE("set volume: wleft=%d, wright=%d, converted to alsa left %d, right %d\n", wleft, wright, left, right);
1096 else
1097 TRACE("SetVolume failed; rc %d\n", rc);
1100 return rc;
1103 /**************************************************************************
1104 * wodGetNumDevs [internal]
1106 static DWORD wodGetNumDevs(void)
1108 return ALSA_WodNumDevs;
1111 /**************************************************************************
1112 * wodDevInterfaceSize [internal]
1114 static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
1116 TRACE("(%u, %p)\n", wDevID, dwParam1);
1118 *dwParam1 = MultiByteToWideChar(CP_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
1119 NULL, 0 ) * sizeof(WCHAR);
1120 return MMSYSERR_NOERROR;
1123 /**************************************************************************
1124 * wodDevInterface [internal]
1126 static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2)
1128 if (dwParam2 >= MultiByteToWideChar(CP_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
1129 NULL, 0 ) * sizeof(WCHAR))
1131 MultiByteToWideChar(CP_UNIXCP, 0, WOutDev[wDevID].interface_name, -1,
1132 dwParam1, dwParam2 / sizeof(WCHAR));
1133 return MMSYSERR_NOERROR;
1135 return MMSYSERR_INVALPARAM;
1138 /**************************************************************************
1139 * wodMessage (WINEALSA.@)
1141 DWORD WINAPI ALSA_wodMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
1142 DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1144 TRACE("(%u, %s, %08lX, %08lX, %08lX);\n",
1145 wDevID, ALSA_getMessage(wMsg), dwUser, dwParam1, dwParam2);
1147 switch (wMsg) {
1148 case DRVM_INIT:
1149 ALSA_WaveInit();
1150 case DRVM_EXIT:
1151 case DRVM_ENABLE:
1152 case DRVM_DISABLE:
1153 /* FIXME: Pretend this is supported */
1154 return 0;
1155 case WODM_OPEN: return wodOpen (wDevID, (LPWAVEOPENDESC)dwParam1, dwParam2);
1156 case WODM_CLOSE: return wodClose (wDevID);
1157 case WODM_GETDEVCAPS: return wodGetDevCaps (wDevID, (LPWAVEOUTCAPSW)dwParam1, dwParam2);
1158 case WODM_GETNUMDEVS: return wodGetNumDevs ();
1159 case WODM_GETPITCH: return MMSYSERR_NOTSUPPORTED;
1160 case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED;
1161 case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED;
1162 case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED;
1163 case WODM_WRITE: return wodWrite (wDevID, (LPWAVEHDR)dwParam1, dwParam2);
1164 case WODM_PAUSE: return wodPause (wDevID);
1165 case WODM_GETPOS: return wodGetPosition (wDevID, (LPMMTIME)dwParam1, dwParam2);
1166 case WODM_BREAKLOOP: return wodBreakLoop (wDevID);
1167 case WODM_PREPARE: return MMSYSERR_NOTSUPPORTED;
1168 case WODM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
1169 case WODM_GETVOLUME: return wodGetVolume (wDevID, (LPDWORD)dwParam1);
1170 case WODM_SETVOLUME: return wodSetVolume (wDevID, dwParam1);
1171 case WODM_RESTART: return wodRestart (wDevID);
1172 case WODM_RESET: return wodReset (wDevID);
1173 case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize (wDevID, (LPDWORD)dwParam1);
1174 case DRV_QUERYDEVICEINTERFACE: return wodDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2);
1175 case DRV_QUERYDSOUNDIFACE: return wodDsCreate (wDevID, (PIDSDRIVER*)dwParam1);
1176 case DRV_QUERYDSOUNDDESC: return wodDsDesc (wDevID, (PDSDRIVERDESC)dwParam1);
1178 default:
1179 FIXME("unknown message %d!\n", wMsg);
1181 return MMSYSERR_NOTSUPPORTED;