push 5f793418e14616d83a4fb368b755a8821b49820b
[wine/hacks.git] / dlls / winepulse.drv / waveout.c
blobb531ee2953f6e633397145a917975cdf8f8bc439
1 /*
2 * Wine Driver for PulseAudio - WaveOut Functionality
3 * http://pulseaudio.org/
5 * Copyright 2009 Arthur Taylor <theycallhimart@gmail.com>
7 * Contains code from other wine multimedia drivers.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 #include "config.h"
26 #include <stdarg.h>
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "winnls.h"
33 #include "winerror.h"
34 #include "mmddk.h"
35 #include "mmreg.h"
37 #include <winepulse.h>
39 #include "wine/debug.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(wave);
43 #if HAVE_PULSEAUDIO
45 /* state diagram for waveOut writing:
47 * +---------+-------------+---------------+---------------------------------+
48 * | state | function | event | new state |
49 * +---------+-------------+---------------+---------------------------------+
50 * | | open() | | STOPPED |
51 * | PAUSED | write() | | PAUSED |
52 * | STOPPED | write() | <thrd create> | PLAYING |
53 * | PLAYING | write() | HEADER | PLAYING |
54 * | (other) | write() | <error> | |
55 * | (any) | pause() | PAUSING | PAUSED |
56 * | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) |
57 * | PAUSED | reset() | RESETTING | PAUSED |
58 * | (other) | reset() | RESETTING | STOPPED |
59 * | (any) | close() | CLOSING | CLOSED |
60 * +---------+-------------+---------------+---------------------------------+
64 * - It is currently unknown if pausing in a loop works the same as expected.
67 /*======================================================================*
68 * WAVE OUT specific PulseAudio Callbacks *
69 *======================================================================*/
71 /**************************************************************************
72 * WAVEOUT_StreamStartedCallback [internal]
74 * Called by the pulse mainloop whenever stream playback resumes after an
75 * underflow or an initial start. Requires libpulse >= 0.9.11
77 #if PA_API_VERSION >= 12
78 static void WAVEOUT_StreamStartedCallback(pa_stream *s, void *userdata) {
79 WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
80 assert(s && wwo);
82 TRACE("Audio flowing.\n");
84 if (wwo->buffer_attr.tlength == 0)
85 wwo->buffer_attr.tlength = (uint32_t) -1;
87 if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size) {
88 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE);
91 #endif
93 /**************************************************************************
94 * WAVEOUT_StreamRequestCallback
96 * Called by the pulse mainloop whenever it wants audio data.
98 static void WAVEOUT_StreamRequestCallback(pa_stream *s, size_t nbytes, void *userdata) {
99 WINE_WAVEINST *ww = (WINE_WAVEINST*)userdata;
101 TRACE("Asking to be fed %u bytes\n", nbytes);
103 /* Make sure that the player/recorder is running */
104 if (ww->hThread != INVALID_HANDLE_VALUE && ww->msgRing.messages) {
105 PULSE_AddRingMessage(&ww->msgRing, WINE_WM_FEED, (DWORD)nbytes, FALSE);
109 /**************************************************************************
110 * WAVEOUT_StreamTimingInfoUpdateCallback [internal]
112 * Called by the pulse mainloop whenever the timing info gets updated, we
113 * use this to send the started signal */
114 static void WAVEOUT_StreamTimingInfoUpdateCallback(pa_stream *s, void *userdata) {
115 WINE_WAVEINST *wwo = (WINE_WAVEINST*)userdata;
116 assert(s && wwo);
118 if (!wwo->is_releasing && wwo->timing_info && wwo->timing_info->playing) {
119 TRACE("Audio flowing.\n");
121 if (wwo->buffer_attr.tlength == 0)
122 wwo->buffer_attr.tlength = (uint32_t) -1;
124 if (wwo->hThread != INVALID_HANDLE_VALUE && wwo->msgRing.ring_buffer_size)
125 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_STARTING, 0, FALSE);
129 /**************************************************************************
130 * WAVEOUT_SinkInputInfoCallback [internal]
132 * Called by the pulse thread. Used for wodGetVolume.
134 static void WAVEOUT_SinkInputInfoCallback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
135 WINE_WAVEINST* wwo = (WINE_WAVEINST*)userdata;
136 if (!eol && i) {
137 for (wwo->volume.channels = 0; wwo->volume.channels != i->volume.channels; wwo->volume.channels++)
138 wwo->volume.values[wwo->volume.channels] = i->volume.values[wwo->volume.channels];
139 pa_threaded_mainloop_signal(PULSE_ml, 0);
143 /*======================================================================*
144 * "Low level" WAVE OUT implementation *
145 *======================================================================*/
147 /**************************************************************************
148 * wodPlayer_NotifyClient [internal]
150 static DWORD wodPlayer_NotifyClient(WINE_WAVEINST* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2) {
151 TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2);
153 switch (wMsg) {
154 case WOM_OPEN:
155 case WOM_CLOSE:
156 case WOM_DONE:
157 if (wwo->wFlags != DCB_NULL &&
158 !DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags, (HDRVR)wwo->waveDesc.hWave,
159 wMsg, wwo->waveDesc.dwInstance, dwParam1, dwParam2)) {
160 WARN("can't notify client !\n");
161 return MMSYSERR_ERROR;
163 break;
164 default:
165 FIXME("Unknown callback message %u\n", wMsg);
166 return MMSYSERR_INVALPARAM;
168 return MMSYSERR_NOERROR;
171 /**************************************************************************
172 * wodPlayer_BeginWaveHdr [internal]
174 * Makes the specified lpWaveHdr the currently playing wave header.
175 * If the specified wave header is a begin loop and we're not already in
176 * a loop, setup the loop.
178 static void wodPlayer_BeginWaveHdr(WINE_WAVEINST* wwo, LPWAVEHDR lpWaveHdr) {
179 wwo->lpPlayPtr = lpWaveHdr;
181 if (!lpWaveHdr) return;
183 if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) {
184 if (wwo->lpLoopPtr) {
185 WARN("Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
186 } else {
187 TRACE("Starting loop (%dx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);
188 wwo->lpLoopPtr = lpWaveHdr;
189 /* Windows does not touch WAVEHDR.dwLoops,
190 * so we need to make an internal copy */
191 wwo->dwLoops = lpWaveHdr->dwLoops;
194 wwo->dwPartialOffset = 0;
197 /**************************************************************************
198 * wodPlayer_PlayPtrNext [internal]
200 * Advance the play pointer to the next waveheader, looping if required.
202 static LPWAVEHDR wodPlayer_PlayPtrNext(WINE_WAVEINST* wwo) {
203 LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
205 wwo->dwPartialOffset = 0;
206 if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) {
207 /* We're at the end of a loop, loop if required */
208 if (--wwo->dwLoops > 0) {
209 wwo->lpPlayPtr = wwo->lpLoopPtr;
210 } else {
211 /* Handle overlapping loops correctly */
212 if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
213 FIXME("Correctly handled case ? (ending loop buffer also starts a new loop)\n");
214 /* shall we consider the END flag for the closing loop or for
215 * the opening one or for both ???
216 * code assumes for closing loop only
218 } else {
219 lpWaveHdr = lpWaveHdr->lpNext;
221 wwo->lpLoopPtr = NULL;
222 wodPlayer_BeginWaveHdr(wwo, lpWaveHdr);
224 } else {
225 /* We're not in a loop. Advance to the next wave header */
226 wodPlayer_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
229 return lpWaveHdr;
232 /**************************************************************************
233 * wodPlayer_CheckReleasing [internal]
235 * Check to make sure that playback has not stalled
237 static void wodPlayer_CheckReleasing(WINE_WAVEINST *wwo) {
238 LPWAVEHDR lpWaveHdr = wwo->lpQueuePtr;
240 pa_threaded_mainloop_lock(PULSE_ml);
241 if (!wwo->timing_info->playing && !wwo->is_releasing && lpWaveHdr && !wwo->lpPlayPtr && wwo->state == WINE_WS_PLAYING) {
243 /* Try and adjust the buffer attributes so there is less latency.
244 * Because of bugs this call does not work on older servers. Once
245 * new version of pulseaudio become ubiquitous we will drop support for
246 * versions before 0.9.15 because they have too many bugs.*/
247 if (wwo->buffer_attr.tlength == 0 &&
248 pa_context_get_server_protocol_version(PULSE_context) >= 15) {
249 wwo->buffer_attr.tlength = wwo->timing_info->write_index;
250 wwo->buffer_attr.prebuf = (uint32_t) -1;
251 wwo->buffer_attr.minreq = (uint32_t) -1;
252 wwo->buffer_attr.maxlength = (uint32_t) -1;
253 WARN("Asking for new buffer tlength of %ums (%u bytes)\n", (unsigned int)(pa_bytes_to_usec(wwo->buffer_attr.tlength, &wwo->sample_spec)/1000), wwo->buffer_attr.tlength);
254 pa_stream_set_buffer_attr(wwo->stream, &wwo->buffer_attr, PULSE_StreamSuccessCallback, wwo);
255 } else {
256 /* Fake playback start earlier, introducing unknown latency */
257 pa_gettimeofday(&wwo->started_releasing);
258 wwo->is_releasing = TRUE;
259 wwo->releasing_offset = wwo->lpQueuePtr->reserved;
260 WARN("Starting to release early.\n");
263 pa_threaded_mainloop_unlock(PULSE_ml);
266 /**************************************************************************
267 * wodPlayer_NotifyCompletions [internal]
269 * Notifies and remove from queue all wavehdrs which have been played to
270 * the speaker based on a reference time of (theoretical) playback start. If
271 * force is true, we notify all wavehdrs and remove them all from the queue
272 * even if they are unplayed or part of a loop. We return the time to wait
273 * until the next wavehdr needs to be freed, or INFINITE if there are no more
274 * wavehdrs. We use timevals rather than the stream position data that pulse
275 * gives us as realeasing needs to be constant and smooth for good application
276 * behaviour.
278 static DWORD wodPlayer_NotifyCompletions(WINE_WAVEINST* wwo, BOOL force) {
279 LPWAVEHDR lpWaveHdr = wwo->lpQueuePtr;
280 pa_usec_t time;
281 pa_usec_t wait;
283 time = pa_bytes_to_usec(wwo->releasing_offset, &wwo->sample_spec);
284 if (wwo->is_releasing)
285 time += pa_timeval_age(&wwo->started_releasing);
287 while (lpWaveHdr) {
288 if (!force) {
289 /* Start from lpQueuePtr and keep notifying until:
290 * - we hit an unwritten wavehdr
291 * - we hit the beginning of a running loop
292 * - we hit a wavehdr which hasn't finished playing
294 if (lpWaveHdr == wwo->lpPlayPtr) { TRACE("play %p\n", lpWaveHdr); return INFINITE; }
295 if (lpWaveHdr == wwo->lpLoopPtr) { TRACE("loop %p\n", lpWaveHdr); return INFINITE; }
297 /* See if this data has been played, and if not, return when it will have been */
298 wait = pa_bytes_to_usec(lpWaveHdr->reserved + lpWaveHdr->dwBufferLength, &wwo->sample_spec);
299 if (wait >= time) {
300 wait = ((wait - time) + 999) / 1000;
301 return wait ?: 1;
305 /* return the wavehdr */
306 wwo->lpQueuePtr = lpWaveHdr->lpNext;
307 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
308 lpWaveHdr->dwFlags |= WHDR_DONE;
310 wodPlayer_NotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0);
311 lpWaveHdr = wwo->lpQueuePtr;
313 /* No more wavehdrs */
314 TRACE("Empty queue\n");
315 return INFINITE;
318 /**************************************************************************
319 * wodPlayer_WriteMax [internal]
321 * Write either how much free space or how much data we have, depending on
322 * which is less
324 static int wodPlayer_WriteMax(WINE_WAVEINST *wwo, size_t *space) {
325 LPWAVEHDR lpWaveHdr = wwo->lpPlayPtr;
326 size_t toWrite = min(lpWaveHdr->dwBufferLength - wwo->dwPartialOffset, *space);
328 if (toWrite > 0 && pa_stream_write(wwo->stream, lpWaveHdr->lpData + wwo->dwPartialOffset, toWrite, NULL, 0, PA_SEEK_RELATIVE) >= 0)
329 TRACE("Writing wavehdr %p.%u[%u]\n", lpWaveHdr, wwo->dwPartialOffset, lpWaveHdr->dwBufferLength);
330 else
331 return 0;
333 /* Check to see if we wrote all of the wavehdr */
334 if ((wwo->dwPartialOffset += toWrite) >= lpWaveHdr->dwBufferLength)
335 wodPlayer_PlayPtrNext(wwo);
337 *space -= toWrite;
339 return toWrite;
342 /**************************************************************************
343 * wodPlayer_Feed [internal]
345 * Feed as much sound data as we can into pulse using wodPlayer_WriteMax.
346 * size_t space _must_ have come from either pa_stream_writable_size() or
347 * the value from a stream write callback, as if it isn't you run the risk
348 * of a buffer overflow in which audio data will be lost.
350 static void wodPlayer_Feed(WINE_WAVEINST* wwo, size_t space) {
352 /* No more room... no need to try to feed */
353 if (space == 0) return;
355 if (!wwo->stream || !PULSE_context ||
356 pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
357 pa_stream_get_state(wwo->stream) != PA_STREAM_READY)
358 return;
360 pa_threaded_mainloop_lock(PULSE_ml);
361 /* Feed from a partial wavehdr */
362 if (wwo->lpPlayPtr && wwo->dwPartialOffset != 0)
363 wodPlayer_WriteMax(wwo, &space);
365 /* Feed wavehdrs until we run out of wavehdrs or buffer space */
366 if (wwo->dwPartialOffset == 0 && wwo->lpPlayPtr) {
367 do {
368 wwo->lpPlayPtr->reserved = wwo->timing_info->write_index;
369 } while (wodPlayer_WriteMax(wwo, &space) > 0 && wwo->lpPlayPtr && space > 0);
371 pa_threaded_mainloop_unlock(PULSE_ml);
374 /**************************************************************************
375 * wodPlayer_Reset [internal]
377 * wodPlayer helper. Resets current output stream.
379 static void wodPlayer_Reset(WINE_WAVEINST* wwo) {
380 enum win_wm_message msg;
381 DWORD param;
382 HANDLE ev;
384 TRACE("(%p)\n", wwo);
386 /* remove any buffer */
387 wodPlayer_NotifyCompletions(wwo, TRUE);
389 wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
390 if (wwo->state != WINE_WS_PAUSED)
391 wwo->state = WINE_WS_STOPPED;
392 wwo->dwPartialOffset = 0;
394 if (!wwo->stream ||
395 !PULSE_context ||
396 pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
397 pa_stream_get_state(wwo->stream) != PA_STREAM_READY) {
398 return;
401 pa_threaded_mainloop_lock(PULSE_ml);
403 /* flush the output buffer of written data*/
404 PULSE_WaitForOperation(pa_stream_flush(wwo->stream, PULSE_StreamSuccessCallback, NULL));
406 /* Reset the written byte count as some data may have been flushed */
407 if (wwo->timing_info->write_index_corrupt)
408 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
409 wwo->releasing_offset = wwo->last_reset = wwo->timing_info->write_index;
410 if (wwo->is_releasing)
411 pa_gettimeofday(&wwo->started_releasing);
413 /* return all pending headers in queue */
414 EnterCriticalSection(&wwo->msgRing.msg_crst);
415 while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
416 if (msg != WINE_WM_HEADER) {
417 SetEvent(ev);
418 continue;
420 ((LPWAVEHDR)param)->dwFlags &= ~WHDR_INQUEUE;
421 ((LPWAVEHDR)param)->dwFlags |= WHDR_DONE;
422 wodPlayer_NotifyClient(wwo, WOM_DONE, param, 0);
424 PULSE_ResetRingMessage(&wwo->msgRing);
425 LeaveCriticalSection(&wwo->msgRing.msg_crst);
427 pa_threaded_mainloop_unlock(PULSE_ml);
430 /**************************************************************************
431 * wodPlayer_Underrun [internal]
433 * wodPlayer helper. Deal with a stream underrun.
435 static void wodPlayer_Underrun(WINE_WAVEINST* wwo) {
437 pa_threaded_mainloop_lock(PULSE_ml);
439 /* Ask for a timing update */
440 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
442 /* See if we recovered while the message was waiting in the queue */
443 if (wwo->timing_info->playing) {
444 TRACE("False alarm\n");
445 pa_threaded_mainloop_unlock(PULSE_ml);
446 return;
449 if (wwo->lpPlayPtr) {
450 size_t space;
452 TRACE("There is queued data. Trying to recover.\n");
454 /* Ask for a timing update */
455 if (wwo->timing_info->write_index_corrupt)
456 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
457 space = pa_stream_writable_size(wwo->stream);
458 wodPlayer_Feed(wwo, space);
460 WARN("Stream underrun!\n");
461 wwo->is_releasing = FALSE;
462 wwo->releasing_offset = wwo->timing_info->write_index;
463 pa_threaded_mainloop_unlock(PULSE_ml);
467 /**************************************************************************
468 * wodPlayer_ProcessMessages [internal]
470 static DWORD wodPlayer_ProcessMessages(WINE_WAVEINST* wwo) {
471 LPWAVEHDR lpWaveHdr;
472 enum win_wm_message msg;
473 DWORD param, msgcount = 0;
474 HANDLE ev;
476 while (PULSE_RetrieveRingMessage(&wwo->msgRing, &msg, &param, &ev)) {
477 TRACE("Received %s %x\n", PULSE_getCmdString(msg), param);
478 msgcount++;
480 switch (msg) {
481 case WINE_WM_PAUSING:
482 wwo->state = WINE_WS_PAUSED;
484 pa_threaded_mainloop_lock(PULSE_ml);
485 PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 1, PULSE_StreamSuccessCallback, wwo));
486 /* save how far we are, as releasing will restart from here */
487 if (wwo->is_releasing)
488 wwo->releasing_offset = wwo->timing_info->write_index;
489 wwo->is_releasing = FALSE;
490 pa_threaded_mainloop_unlock(PULSE_ml);
491 SetEvent(ev);
492 break;
494 case WINE_WM_RESTARTING:
495 if (wwo->state == WINE_WS_PAUSED) {
496 wwo->state = WINE_WS_PLAYING;
497 pa_threaded_mainloop_lock(PULSE_ml);
498 PULSE_WaitForOperation(pa_stream_cork(wwo->stream, 0, PULSE_StreamSuccessCallback, wwo));
499 /* If the serverside buffer was near full before pause, we need to
500 * have space to write soon, so force playback start */
501 PULSE_WaitForOperation(pa_stream_trigger(wwo->stream, PULSE_StreamSuccessCallback, wwo));
502 pa_threaded_mainloop_unlock(PULSE_ml);
504 SetEvent(ev);
505 break;
507 case WINE_WM_HEADER:
508 lpWaveHdr = (LPWAVEHDR)param;
509 /* insert buffer at the end of queue */
511 LPWAVEHDR* wh;
512 for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
513 *wh = lpWaveHdr;
516 if (!wwo->lpPlayPtr)
517 wodPlayer_BeginWaveHdr(wwo,lpWaveHdr);
518 if (wwo->state == WINE_WS_STOPPED)
519 wwo->state = WINE_WS_PLAYING;
521 wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream));
522 SetEvent(ev);
523 break;
525 case WINE_WM_RESETTING:
526 wodPlayer_Reset(wwo);
527 SetEvent(ev);
528 break;
530 case WINE_WM_BREAKLOOP:
531 if (wwo->state == WINE_WS_PLAYING && wwo->lpLoopPtr != NULL)
532 /* ensure exit at end of current loop */
533 wwo->dwLoops = 1;
534 SetEvent(ev);
535 break;
537 case WINE_WM_FEED: /* Sent by the pulse thread */
538 wodPlayer_Feed(wwo, pa_stream_writable_size(wwo->stream));
539 SetEvent(ev);
540 break;
542 case WINE_WM_XRUN: /* Sent by the pulse thread */
543 if ((DWORD)param == 1) {
544 ERR("Buffer overflow!\n");
545 pa_threaded_mainloop_lock(PULSE_ml);
546 PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, wwo));
547 wwo->is_releasing = FALSE;
548 pa_threaded_mainloop_unlock(PULSE_ml);
549 } else
550 wodPlayer_Underrun(wwo);
551 SetEvent(ev);
552 break;
554 case WINE_WM_STARTING: /* Sent by the pulse thread */
555 /* Start releasing wavehdrs if we haven't already */
556 if (!wwo->is_releasing) {
557 wwo->is_releasing = TRUE;
558 pa_gettimeofday(&wwo->started_releasing);
560 SetEvent(ev);
561 break;
563 case WINE_WM_CLOSING: /* If param = 1, close because of a failure */
564 wwo->hThread = NULL;
565 if ((DWORD)param == 1) {
566 /* If we are here, the stream has failed */
567 wwo->state = WINE_WS_FAILED;
568 SetEvent(ev);
569 PULSE_DestroyRingMessage(&wwo->msgRing);
570 wodPlayer_NotifyCompletions(wwo, TRUE);
571 wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L);
572 wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
573 pa_threaded_mainloop_lock(PULSE_ml);
574 pa_stream_disconnect(wwo->stream);
575 pa_threaded_mainloop_unlock(PULSE_ml);
576 TRACE("Thread exiting because of failure.\n");
577 ExitThread(1);
578 /* Stream instance will get de-refferenced upon close */
580 wwo->state = WINE_WS_CLOSED;
581 /* sanity check: this should not happen since the device must have been reset before */
582 if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
583 SetEvent(ev);
584 TRACE("Thread exiting.\n");
585 ExitThread(0);
586 /* shouldn't go here */
588 default:
589 FIXME("unknown message %d\n", msg);
590 break;
594 return msgcount;
597 /**************************************************************************
598 * wodPlayer [internal]
600 * The thread which is responsible for returning WaveHdrs via DriverCallback,
601 * the writing of queued WaveHdrs, and all pause / reset stream management.
603 static DWORD CALLBACK wodPlayer(LPVOID lpParam) {
604 WINE_WAVEINST *wwo = (WINE_WAVEINST*)lpParam;
605 DWORD dwSleepTime = INFINITE;
607 wwo->state = WINE_WS_STOPPED;
608 SetEvent(wwo->hStartUpEvent);
610 /* Wait for the shortest time before an action is required. If there are no
611 * pending actions, wait forever for a command. */
612 for (;;) {
613 TRACE("Waiting %u ms\n", dwSleepTime);
614 PULSE_WaitRingMessage(&wwo->msgRing, dwSleepTime);
616 /* If no messages were processed during the timeout it might be because audio
617 * is not flowing yet, so check. */
618 if (wodPlayer_ProcessMessages(wwo) == 0)
619 wodPlayer_CheckReleasing(wwo);
621 /* If there is audio playing, return headers and get next timeout */
622 if (wwo->state == WINE_WS_PLAYING)
623 dwSleepTime = wodPlayer_NotifyCompletions(wwo, FALSE);
624 else
625 dwSleepTime = INFINITE;
629 /**************************************************************************
630 * wodOpen [internal]
632 static DWORD wodOpen(WORD wDevID, LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) {
633 WINE_WAVEDEV *wdo;
634 WINE_WAVEINST *wwo = NULL;
635 DWORD ret = MMSYSERR_NOERROR;
636 pa_stream_flags_t stream_flags;
638 TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
639 if (lpDesc == NULL) {
640 WARN("Invalid Parameter !\n");
641 return MMSYSERR_INVALPARAM;
644 if (wDevID >= PULSE_WodNumDevs) {
645 WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
646 return MMSYSERR_BADDEVICEID;
648 wdo = &WOutDev[wDevID];
650 wwo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST));
651 if (!wwo) return MMSYSERR_NOMEM;
652 *lpdwUser = (DWORD)wwo;
654 /* check to see if format is supported and make pa_sample_spec struct */
655 if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwo->sample_spec)) {
656 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
657 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
658 lpDesc->lpFormat->nSamplesPerSec);
659 ret = WAVERR_BADFORMAT;
660 goto exit;
663 if (TRACE_ON(wave)) {
664 char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
665 pa_sample_spec_snprint(t, sizeof(t), &wwo->sample_spec);
666 TRACE("Sample spec '%s'\n", t);
669 if (dwFlags & WAVE_FORMAT_QUERY) {
670 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
671 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
672 lpDesc->lpFormat->nSamplesPerSec);
673 ret = MMSYSERR_NOERROR;
674 goto exit;
677 wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
678 wwo->waveDesc = *lpDesc;
679 PULSE_InitRingMessage(&wwo->msgRing);
681 wwo->stream = pa_stream_new(PULSE_context, "WaveOut", &wwo->sample_spec, NULL);
682 /* If server doesn't support sample_spec, we will error out here */
683 if (!wwo->stream) {
684 ret = WAVERR_BADFORMAT;
685 goto exit;
688 pa_stream_set_write_callback (wwo->stream, WAVEOUT_StreamRequestCallback, wwo);
689 pa_stream_set_state_callback (wwo->stream, PULSE_StreamStateCallback, wwo);
690 pa_stream_set_underflow_callback (wwo->stream, PULSE_StreamUnderflowCallback, wwo);
691 pa_stream_set_overflow_callback (wwo->stream, PULSE_StreamOverflowCallback, wwo);
692 pa_stream_set_moved_callback (wwo->stream, PULSE_StreamMovedCallback, wwo);
693 pa_stream_set_suspended_callback (wwo->stream, PULSE_StreamSuspendedCallback, wwo);
695 stream_flags = PA_STREAM_AUTO_TIMING_UPDATE;
697 #if PA_API_VERSION >= 12
698 if (pa_context_get_server_protocol_version(PULSE_context) >= 15) {
699 pa_stream_set_started_callback(wwo->stream, WAVEOUT_StreamStartedCallback, wwo);
700 stream_flags |= PA_STREAM_ADJUST_LATENCY;
701 } else
702 #endif
704 pa_stream_set_latency_update_callback(wwo->stream, WAVEOUT_StreamTimingInfoUpdateCallback, wwo);
707 TRACE("Connecting stream for playback on %s.\n", wdo->device_name);
708 pa_threaded_mainloop_lock(PULSE_ml);
709 pa_stream_connect_playback(wwo->stream, wdo->device_name, NULL, stream_flags, NULL, NULL);
711 for (;;) {
712 pa_context_state_t cstate = pa_context_get_state(PULSE_context);
713 pa_stream_state_t sstate = pa_stream_get_state(wwo->stream);
715 if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED ||
716 sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) {
717 ERR("Failed to connect stream context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
718 pa_threaded_mainloop_unlock(PULSE_ml);
719 ret = MMSYSERR_NODRIVER;
720 goto exit;
723 if (sstate == PA_STREAM_READY)
724 break;
726 pa_threaded_mainloop_wait(PULSE_ml);
728 TRACE("(%p)->stream connected for playback.\n", wwo);
730 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
732 wwo->timing_info = pa_stream_get_timing_info(wwo->stream);
733 assert(wwo->timing_info);
734 pa_threaded_mainloop_unlock(PULSE_ml);
736 wwo->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
737 wwo->hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)wwo, 0, &(wwo->dwThreadID));
738 if (wwo->hThread)
739 SetThreadPriority(wwo->hThread, THREAD_PRIORITY_TIME_CRITICAL);
740 else {
741 ERR("Thread creation for the wodPlayer failed!\n");
742 ret = MMSYSERR_NOMEM;
743 goto exit;
745 WaitForSingleObject(wwo->hStartUpEvent, INFINITE);
746 CloseHandle(wwo->hStartUpEvent);
747 wwo->hStartUpEvent = INVALID_HANDLE_VALUE;
750 return wodPlayer_NotifyClient (wwo, WOM_OPEN, 0L, 0L);
752 exit:
753 if (!wwo)
754 return ret;
756 if (wwo->hStartUpEvent != INVALID_HANDLE_VALUE)
757 CloseHandle(wwo->hStartUpEvent);
759 if (wwo->msgRing.ring_buffer_size > 0)
760 PULSE_DestroyRingMessage(&wwo->msgRing);
762 if (wwo->stream) {
763 if (pa_stream_get_state(wwo->stream) == PA_STREAM_READY)
764 pa_stream_disconnect(wwo->stream);
765 pa_stream_unref(wwo->stream);
766 wwo->stream = NULL;
768 HeapFree(GetProcessHeap(), 0, wwo);
770 return ret;
773 /**************************************************************************
774 * wodClose [internal]
776 static DWORD wodClose(WINE_WAVEINST *wwo) {
777 DWORD ret;
779 TRACE("(%p);\n", wwo);
780 if (!wwo) {
781 WARN("Stream instance invalid.\n");
782 return MMSYSERR_INVALHANDLE;
785 if (wwo->state != WINE_WS_FAILED) {
786 if (wwo->lpQueuePtr && wwo->lpPlayPtr) {
787 WARN("buffers still playing !\n");
788 return WAVERR_STILLPLAYING;
791 pa_threaded_mainloop_lock(PULSE_ml);
792 PULSE_WaitForOperation(pa_stream_drain(wwo->stream, PULSE_StreamSuccessCallback, NULL));
793 pa_stream_disconnect(wwo->stream);
794 pa_threaded_mainloop_unlock(PULSE_ml);
796 if (wwo->hThread != INVALID_HANDLE_VALUE)
797 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_CLOSING, 0, TRUE);
799 PULSE_DestroyRingMessage(&wwo->msgRing);
802 if (wwo->stream)
803 pa_stream_unref(wwo->stream);
804 ret = wodPlayer_NotifyClient(wwo, WOM_CLOSE, 0L, 0L);
806 HeapFree(GetProcessHeap(), 0, wwo);
808 return ret;
811 /**************************************************************************
812 * wodWrite [internal]
814 static DWORD wodWrite(WINE_WAVEINST *wwo, LPWAVEHDR lpWaveHdr, DWORD dwSize) {
815 if (!wwo || wwo->state == WINE_WS_FAILED) {
816 WARN("Stream instance invalid.\n");
817 return MMSYSERR_INVALHANDLE;
820 if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
821 return WAVERR_UNPREPARED;
823 if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
824 return WAVERR_STILLPLAYING;
826 lpWaveHdr->dwFlags &= ~WHDR_DONE;
827 lpWaveHdr->dwFlags |= WHDR_INQUEUE;
828 lpWaveHdr->lpNext = 0;
829 lpWaveHdr->reserved = 0;
831 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE);
832 return MMSYSERR_NOERROR;
835 /**************************************************************************
836 * wodPause [internal]
838 static DWORD wodPause(WINE_WAVEINST *wwo) {
839 if (!wwo || wwo->state == WINE_WS_FAILED) {
840 WARN("Stream instance invalid.\n");
841 return MMSYSERR_INVALHANDLE;
844 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_PAUSING, 0, TRUE);
845 return MMSYSERR_NOERROR;
848 /**************************************************************************
849 * wodGetPosition [internal]
851 static DWORD wodGetPosition(WINE_WAVEINST *wwo, LPMMTIME lpTime, DWORD uSize) {
852 pa_usec_t time, time_temp;
853 size_t bytes, bytes_temp;
855 if (!wwo || wwo->state == WINE_WS_FAILED) {
856 WARN("Stream instance invalid.\n");
857 return MMSYSERR_INVALHANDLE;
860 if (lpTime == NULL) return MMSYSERR_INVALPARAM;
862 pa_threaded_mainloop_lock(PULSE_ml);
863 if (wwo->timing_info->read_index_corrupt || wwo->timing_info->write_index_corrupt)
864 PULSE_WaitForOperation(pa_stream_update_timing_info(wwo->stream, PULSE_StreamSuccessCallback, wwo));
866 bytes = wwo->timing_info->read_index;
867 time = pa_bytes_to_usec(bytes, &wwo->sample_spec);
869 if (wwo->timing_info->playing) {
870 bytes += ((pa_timeval_age(&wwo->timing_info->timestamp) / 1000) * pa_bytes_per_second(&wwo->sample_spec)) / 1000;
871 bytes_temp = (time_temp = wwo->timing_info->sink_usec + wwo->timing_info->transport_usec)/1000 * pa_bytes_per_second(&wwo->sample_spec)/1000;
872 time += pa_timeval_age(&wwo->timing_info->timestamp);
873 } else {
874 time = 0;
875 time_temp = 0;
876 bytes_temp = 0;
879 pa_threaded_mainloop_unlock(PULSE_ml);
881 if (wwo->last_reset < bytes) {
882 time -= pa_bytes_to_usec(wwo->last_reset, &wwo->sample_spec);
883 bytes -= wwo->last_reset;
885 if (bytes > bytes_temp)
886 bytes -= bytes_temp;
887 else
888 bytes = 0;
890 if (time > time_temp)
891 time -= time_temp;
892 else
893 time = 0;
895 bytes -= bytes % pa_frame_size(&wwo->sample_spec);
896 time /= 1000; /* In milliseconds now */
898 switch (lpTime->wType) {
899 case TIME_SAMPLES:
900 lpTime->u.sample = bytes / pa_frame_size(&wwo->sample_spec);
901 TRACE("TIME_SAMPLES=%u\n", lpTime->u.sample);
902 break;
903 case TIME_MS:
904 lpTime->u.ms = time;
905 TRACE("TIME_MS=%u\n", lpTime->u.ms);
906 break;
907 case TIME_SMPTE:
908 lpTime->u.smpte.fps = 30;
909 lpTime->u.smpte.sec = time/1000;
910 lpTime->u.smpte.min = lpTime->u.smpte.sec / 60;
911 lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min;
912 lpTime->u.smpte.hour = lpTime->u.smpte.min / 60;
913 lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour;
914 lpTime->u.smpte.frame = time / lpTime->u.smpte.fps * 1000;
915 TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n",
916 lpTime->u.smpte.hour, lpTime->u.smpte.min,
917 lpTime->u.smpte.sec, lpTime->u.smpte.frame);
918 break;
919 default:
920 WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType);
921 lpTime->wType = TIME_BYTES;
922 /* fall through */
923 case TIME_BYTES:
924 lpTime->u.cb = bytes;
925 TRACE("TIME_BYTES=%u\n", lpTime->u.cb);
926 break;
928 return MMSYSERR_NOERROR;
930 /**************************************************************************
931 * wodBreakLoop [internal]
933 static DWORD wodBreakLoop(WINE_WAVEINST *wwo) {
934 if (!wwo || wwo->state == WINE_WS_FAILED) {
935 WARN("Stream instance invalid.\n");
936 return MMSYSERR_INVALHANDLE;
939 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_BREAKLOOP, 0, TRUE);
940 return MMSYSERR_NOERROR;
943 /**************************************************************************
944 * wodGetDevCaps [internal]
946 static DWORD wodGetDevCaps(DWORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize) {
947 TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
949 if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
951 if (wDevID >= PULSE_WodNumDevs) {
952 TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
953 return MMSYSERR_INVALHANDLE;
956 memcpy(lpCaps, &(WOutDev[wDevID].caps.out), min(dwSize, sizeof(*lpCaps)));
957 return MMSYSERR_NOERROR;
960 /**************************************************************************
961 * wodGetNumDevs [internal]
962 * Context-sanity check here, as if we respond with 0, WINE will move on
963 * to the next waveout driver.
965 static DWORD wodGetNumDevs() {
966 if (!PULSE_ml || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY)
967 return 0;
969 return PULSE_WodNumDevs;
972 /**************************************************************************
973 * wodGetVolume [internal]
975 static DWORD wodGetVolume(WINE_WAVEINST *wwo, LPDWORD lpdwVol) {
976 float value1, value2;
977 DWORD wleft, wright;
979 if (!wwo || wwo->state == WINE_WS_FAILED) {
980 WARN("Stream instance invalid.\n");
981 return MMSYSERR_INVALHANDLE;
984 TRACE("(%p, %p);\n", wwo, lpdwVol);
986 if (lpdwVol == NULL)
987 return MMSYSERR_NOTENABLED;
989 pa_threaded_mainloop_lock(PULSE_ml);
990 if (wwo->stream && PULSE_context && pa_context_get_state(PULSE_context) == PA_CONTEXT_READY &&
991 pa_stream_get_state(wwo->stream) == PA_STREAM_READY) {
992 PULSE_WaitForOperation(pa_context_get_sink_input_info(PULSE_context, pa_stream_get_index(wwo->stream), WAVEOUT_SinkInputInfoCallback, wwo));
994 pa_threaded_mainloop_unlock(PULSE_ml);
997 if (wwo->volume.channels == 2) {
998 value1 = pa_sw_volume_to_dB(wwo->volume.values[0]);
999 value2 = pa_sw_volume_to_dB(wwo->volume.values[1]);
1000 } else {
1001 value1 = pa_sw_volume_to_dB(pa_cvolume_avg(&wwo->volume));
1002 value2 = 0;
1005 if (value1 < -60)
1006 wleft = 0;
1007 else
1009 if (value2 < -60)
1010 wright = 0;
1011 else
1012 wright = 0xFFFFl - ((value2 / -60)*(float)0xFFFFl);
1014 if (wleft > 0xFFFFl)
1015 wleft = 0xFFFFl;
1016 if (wright > 0xFFFFl)
1017 wright = 0xFFFFl;
1019 *lpdwVol = (WORD)wleft + (WORD)(wright << 16);
1021 return MMSYSERR_NOERROR;
1024 /**************************************************************************
1025 * wodSetVolume [internal]
1027 static DWORD wodSetVolume(WINE_WAVEINST *wwo, DWORD dwParam1) {
1028 double value1, value2;
1030 TRACE("(%p, %08X);\n", wwo, dwParam1);
1031 if (!wwo || wwo->state == WINE_WS_FAILED) {
1032 WARN("Stream instance invalid.\n");
1033 return MMSYSERR_INVALHANDLE;
1036 /* waveOut volumes are /supposed/ to be logarithmic */
1037 value1 = LOWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - LOWORD(dwParam1))/0xFFFFl) * -60.0;
1038 value2 = HIWORD(dwParam1) == 0 ? PA_DECIBEL_MININFTY : ((float)(0xFFFFl - HIWORD(dwParam1))/0xFFFFl) * -60.0;
1040 if (wwo->sample_spec.channels == 2) {
1041 wwo->volume.channels = 2;
1042 wwo->volume.values[0] = pa_sw_volume_from_dB(value1);
1043 wwo->volume.values[1] = pa_sw_volume_from_dB(value2);
1044 } else {
1045 if (value1 != value2) FIXME("Non-stereo streams can't pan!\n");
1046 wwo->volume.channels = wwo->sample_spec.channels;
1047 pa_cvolume_set(&wwo->volume, wwo->volume.channels, pa_sw_volume_from_dB(max(value1, value2)));
1050 if (TRACE_ON(wave)) {
1051 char s[PA_CVOLUME_SNPRINT_MAX];
1052 pa_cvolume_snprint(s, PA_CVOLUME_SNPRINT_MAX, &wwo->volume);
1053 TRACE("%s\n", s);
1056 pa_threaded_mainloop_lock(PULSE_ml);
1057 if (!wwo->stream || !PULSE_context || pa_context_get_state(PULSE_context) != PA_CONTEXT_READY ||
1058 pa_stream_get_state(wwo->stream) != PA_STREAM_READY || !pa_cvolume_valid(&wwo->volume)) {
1059 pa_threaded_mainloop_unlock(PULSE_ml);
1060 return MMSYSERR_NOERROR;
1063 PULSE_WaitForOperation(pa_context_set_sink_input_volume(PULSE_context,
1064 pa_stream_get_index(wwo->stream), &wwo->volume,
1065 PULSE_ContextSuccessCallback, wwo));
1066 pa_threaded_mainloop_unlock(PULSE_ml);
1067 return MMSYSERR_NOERROR;
1070 /**************************************************************************
1071 * wodRestart [internal]
1073 static DWORD wodRestart(WINE_WAVEINST *wwo) {
1074 if (!wwo || wwo->state == WINE_WS_FAILED) {
1075 WARN("Stream instance invalid.\n");
1076 return MMSYSERR_INVALHANDLE;
1079 if (wwo->state == WINE_WS_PAUSED)
1080 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESTARTING, 0, TRUE);
1081 return MMSYSERR_NOERROR;
1084 /**************************************************************************
1085 * wodReset [internal]
1087 static DWORD wodReset(WINE_WAVEINST *wwo) {
1088 if (!wwo || wwo->state == WINE_WS_FAILED) {
1089 WARN("Stream instance invalid.\n");
1090 return MMSYSERR_INVALHANDLE;
1093 PULSE_AddRingMessage(&wwo->msgRing, WINE_WM_RESETTING, 0, TRUE);
1094 return MMSYSERR_NOERROR;
1097 /**************************************************************************
1098 * wodDevInterfaceSize [internal]
1100 static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) {
1102 *dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1, NULL, 0) * sizeof(WCHAR);
1103 return MMSYSERR_NOERROR;
1106 /**************************************************************************
1107 * wodDevInterface [internal]
1109 static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) {
1110 if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
1111 NULL, 0 ) * sizeof(WCHAR))
1113 MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].interface_name, -1,
1114 dwParam1, dwParam2 / sizeof(WCHAR));
1115 return MMSYSERR_NOERROR;
1117 return MMSYSERR_INVALPARAM;
1120 /**************************************************************************
1121 * wodMessage (WINEPULSE.@)
1123 DWORD WINAPI PULSE_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
1124 DWORD dwParam1, DWORD dwParam2) {
1126 TRACE("(%u, %s, %08X, %08X, %08X);\n",
1127 wDevID, PULSE_getMessage(wMsg), dwUser, dwParam1, dwParam2);
1129 switch (wMsg) {
1130 case DRVM_INIT:
1131 case DRVM_EXIT:
1132 case DRVM_ENABLE:
1133 case DRVM_DISABLE:
1134 /* FIXME: Pretend this is supported */
1135 return 0;
1136 case WODM_OPEN: return wodOpen (wDevID, (LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2);
1137 case WODM_CLOSE: return wodClose ((WINE_WAVEINST*)dwUser);
1138 case WODM_WRITE: return wodWrite ((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
1139 case WODM_PAUSE: return wodPause ((WINE_WAVEINST*)dwUser);
1140 case WODM_GETPOS: return wodGetPosition ((WINE_WAVEINST*)dwUser, (LPMMTIME)dwParam1, dwParam2);
1141 case WODM_BREAKLOOP: return wodBreakLoop ((WINE_WAVEINST*)dwUser);
1142 case WODM_PREPARE: return MMSYSERR_NOTSUPPORTED;
1143 case WODM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
1144 case WODM_GETDEVCAPS: return wodGetDevCaps (wDevID, (LPWAVEOUTCAPSW)dwParam1, dwParam2);
1145 case WODM_GETNUMDEVS: return wodGetNumDevs ();
1146 case WODM_GETPITCH: return MMSYSERR_NOTSUPPORTED;
1147 case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED;
1148 case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* support if theoretically possible */
1149 case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; /* since pulseaudio 0.9.8 */
1150 case WODM_GETVOLUME: return wodGetVolume ((WINE_WAVEINST*)dwUser, (LPDWORD)dwParam1);
1151 case WODM_SETVOLUME: return wodSetVolume ((WINE_WAVEINST*)dwUser, dwParam1);
1152 case WODM_RESTART: return wodRestart ((WINE_WAVEINST*)dwUser);
1153 case WODM_RESET: return wodReset ((WINE_WAVEINST*)dwUser);
1154 case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize (wDevID, (LPDWORD)dwParam1);
1155 case DRV_QUERYDEVICEINTERFACE: return wodDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2);
1156 case DRV_QUERYDSOUNDIFACE: return wodDsCreate (wDevID, (PIDSDRIVER*)dwParam1);
1157 case DRV_QUERYDSOUNDDESC: return wodDsDesc (wDevID, (PDSDRIVERDESC)dwParam1);
1158 default:
1159 FIXME("unknown message %d!\n", wMsg);
1161 return MMSYSERR_NOTSUPPORTED;
1164 #else /* !HAVE_PULSEAUDIO */
1166 /**************************************************************************
1167 * wodMessage (WINEPULSE.@)
1169 DWORD WINAPI PULSE_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
1170 DWORD dwParam1, DWORD dwParam2) {
1171 FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID, wMsg, dwUser,
1172 dwParam1, dwParam2);
1173 return MMSYSERR_NOTENABLED;
1176 #endif /* HAVE_PULSEAUDIO */