configure: Changes from running autconf after previous patch.
[wine/hacks.git] / dlls / winepulse.drv / wavein.c
blob8aea53896482a1f6bc8608bed19074780a549d34
1 /*
2 * Wine Driver for PulseAudio - WaveIn 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 "mmddk.h"
35 #include <winepulse.h>
37 #include "wine/debug.h"
39 WINE_DEFAULT_DEBUG_CHANNEL(wave);
41 #if HAVE_PULSEAUDIO
43 /*======================================================================*
44 * WAVE IN specific PulseAudio Callbacks *
45 *======================================================================*/
47 /**************************************************************************
48 * widNotifyClient [internal]
50 static DWORD widNotifyClient(WINE_WAVEINST* wwi, WORD wMsg, DWORD dwParam1, DWORD dwParam2) {
51 TRACE("wMsg = 0x%04x dwParm1 = %04X dwParam2 = %04X\n", wMsg, dwParam1, dwParam2);
53 switch (wMsg) {
54 case WIM_OPEN:
55 case WIM_CLOSE:
56 case WIM_DATA:
57 if (wwi->wFlags != DCB_NULL &&
58 !DriverCallback(wwi->waveDesc.dwCallback, wwi->wFlags, (HDRVR)wwi->waveDesc.hWave,
59 wMsg, wwi->waveDesc.dwInstance, dwParam1, dwParam2)) {
60 WARN("can't notify client !\n");
61 return MMSYSERR_ERROR;
63 break;
64 default:
65 FIXME("Unknown callback message %u\n", wMsg);
66 return MMSYSERR_INVALPARAM;
68 return MMSYSERR_NOERROR;
71 /**************************************************************************
72 * widRecorder_CopyData [internal]
74 * Copys data from the fragments pulse returns to queued buffers.
76 static void widRecorder_CopyData(WINE_WAVEINST *wwi) {
77 LPWAVEHDR lpWaveHdr = wwi->lpQueuePtr;
78 size_t bytes_avail;
80 /* Get this value once and trust it. Note that the total available is made
81 * of one _or more_ fragments. These fragments will probably not align with
82 * the wavehdr buffer sizes. */
83 pa_threaded_mainloop_lock(PULSE_ml);
84 bytes_avail = pa_stream_readable_size(wwi->stream);
85 pa_threaded_mainloop_unlock(PULSE_ml);
87 if (bytes_avail == -1) {
88 ERR("pa_stream_readable_size() returned -1, record stream has failed.\n");
89 return;
92 /* If there is an already peeked buffer, add it to the total */
93 if (wwi->buffer)
94 bytes_avail += wwi->buffer_length - wwi->buffer_read_offset;
96 for (;bytes_avail && lpWaveHdr; lpWaveHdr = wwi->lpQueuePtr) {
97 size_t peek_avail;
99 if (!wwi->buffer) {
100 pa_threaded_mainloop_lock(PULSE_ml);
101 pa_stream_peek(wwi->stream, &wwi->buffer, &wwi->buffer_length);
102 pa_threaded_mainloop_unlock(PULSE_ml);
103 wwi->buffer_read_offset = 0;
105 if (!wwi->buffer || !wwi->buffer_length) {
106 WARN("pa_stream_peek failed\n");
107 break;
111 peek_avail = min(wwi->buffer_length - wwi->buffer_read_offset,
112 lpWaveHdr->dwBufferLength - lpWaveHdr->dwBytesRecorded);
114 memcpy(lpWaveHdr->lpData + lpWaveHdr->dwBytesRecorded,
115 (PBYTE)wwi->buffer + wwi->buffer_read_offset,
116 peek_avail);
118 wwi->buffer_read_offset += peek_avail;
119 lpWaveHdr->dwBytesRecorded += peek_avail;
120 bytes_avail -= peek_avail;
122 if (lpWaveHdr->dwBytesRecorded == lpWaveHdr->dwBufferLength) {
123 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
124 lpWaveHdr->dwFlags |= WHDR_DONE;
125 wwi->lpQueuePtr = lpWaveHdr->lpNext;
126 widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
129 if (wwi->buffer_read_offset == wwi->buffer_length) {
130 pa_threaded_mainloop_lock(PULSE_ml);
131 pa_stream_drop(wwi->stream);
132 wwi->buffer = NULL;
133 pa_threaded_mainloop_unlock(PULSE_ml);
135 } /* for(bytes_avail && lpWaveHdr) */
137 return;
140 static void widRecorder_ProcessMessages(WINE_WAVEINST* wwi) {
141 LPWAVEHDR lpWaveHdr;
142 enum win_wm_message msg;
143 DWORD param;
144 HANDLE ev;
147 while (PULSE_RetrieveRingMessage(&wwi->msgRing, &msg, &param, &ev)) {
148 TRACE("Received %s %x\n", PULSE_getCmdString(msg), param);
150 switch (msg) {
151 case WINE_WM_FEED:
152 /* Spin the loop in widRecorder */
153 SetEvent(ev);
154 break;
156 case WINE_WM_STARTING:
157 wwi->dwLastReset = wwi->timing_info->read_index;
158 pa_threaded_mainloop_lock(PULSE_ml);
159 PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 0, PULSE_StreamSuccessCallback, NULL));
160 pa_threaded_mainloop_unlock(PULSE_ml);
161 wwi->state = WINE_WS_PLAYING;
162 SetEvent(ev);
163 break;
165 case WINE_WM_HEADER:
166 lpWaveHdr = (LPWAVEHDR)param;
167 lpWaveHdr->lpNext = 0;
168 /* insert buffer at the end of queue */
170 LPWAVEHDR *wh;
171 for (wh = &(wwi->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
172 *wh = lpWaveHdr;
174 break;
176 case WINE_WM_STOPPING:
177 if (wwi->state != WINE_WS_STOPPED) {
178 wwi->state = WINE_WS_STOPPED;
179 pa_threaded_mainloop_lock(PULSE_ml);
180 PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL));
181 if (wwi->buffer) {
182 pa_stream_drop(wwi->stream);
183 wwi->buffer = NULL;
185 pa_threaded_mainloop_unlock(PULSE_ml);
187 /* return only the current buffer to app */
188 if ((lpWaveHdr = wwi->lpQueuePtr)) {
189 LPWAVEHDR lpNext = lpWaveHdr->lpNext;
190 TRACE("stop %p %p\n", lpWaveHdr, lpWaveHdr->lpNext);
191 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
192 lpWaveHdr->dwFlags |= WHDR_DONE;
193 wwi->lpQueuePtr = lpNext;
194 widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
197 SetEvent(ev);
198 break;
200 case WINE_WM_RESETTING:
201 if (wwi->state != WINE_WS_STOPPED) {
202 wwi->state = WINE_WS_STOPPED;
203 pa_threaded_mainloop_lock(PULSE_ml);
204 PULSE_WaitForOperation(pa_stream_cork(wwi->stream, 1, PULSE_StreamSuccessCallback, NULL));
205 if (wwi->buffer) {
206 pa_stream_drop(wwi->stream);
207 wwi->buffer = NULL;
209 pa_threaded_mainloop_unlock(PULSE_ml);
212 /* return all the buffers to the app */
213 lpWaveHdr = wwi->lpPlayPtr ? wwi->lpPlayPtr : wwi->lpQueuePtr;
214 for (; lpWaveHdr; lpWaveHdr = wwi->lpQueuePtr) {
215 lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
216 lpWaveHdr->dwFlags |= WHDR_DONE;
217 wwi->lpQueuePtr = lpWaveHdr->lpNext;
218 widNotifyClient(wwi, WIM_DATA, (DWORD)lpWaveHdr, 0);
220 SetEvent(ev);
221 break;
223 case WINE_WM_CLOSING:
224 wwi->hThread = 0;
225 wwi->state = WINE_WS_CLOSED;
226 SetEvent(ev);
227 ExitThread(0);
228 /* shouldn't go here */
230 default:
231 FIXME("unknown message %d\n", msg);
232 break;
237 /**************************************************************************
238 * widRecorder [internal]
240 static DWORD CALLBACK widRecorder(LPVOID lpParam) {
241 WINE_WAVEINST *wwi = (WINE_WAVEINST*)lpParam;
243 wwi->state = WINE_WS_STOPPED;
244 SetEvent(wwi->hStartUpEvent);
246 for (;;) {
247 PULSE_WaitRingMessage(&wwi->msgRing, INFINITE);
248 widRecorder_ProcessMessages(wwi);
249 if (wwi->state == WINE_WS_PLAYING && wwi->lpQueuePtr)
250 widRecorder_CopyData(wwi);
253 return 0;
256 /**************************************************************************
257 * widOpen [internal]
259 static DWORD widOpen(WORD wDevID, DWORD_PTR *lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags) {
260 WINE_WAVEDEV *wdi;
261 WINE_WAVEINST *wwi = NULL;
262 DWORD ret = MMSYSERR_NOERROR;
264 TRACE("(%u, %p, %08X);\n", wDevID, lpDesc, dwFlags);
265 if (lpDesc == NULL) {
266 WARN("Invalid Parameter !\n");
267 return MMSYSERR_INVALPARAM;
270 if (wDevID >= PULSE_WidNumDevs) {
271 TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs);
272 return MMSYSERR_BADDEVICEID;
274 wdi = &WInDev[wDevID];
276 wwi = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_WAVEINST));
277 if (!wwi) return MMSYSERR_NOMEM;
278 *lpdwUser = (DWORD_PTR)wwi;
280 /* check to see if format is supported and make pa_sample_spec struct */
281 if (!PULSE_SetupFormat(lpDesc->lpFormat, &wwi->sample_spec)) {
282 WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
283 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
284 lpDesc->lpFormat->nSamplesPerSec);
285 ret = WAVERR_BADFORMAT;
286 goto exit;
289 if (TRACE_ON(wave)) {
290 char t[PA_SAMPLE_SPEC_SNPRINT_MAX];
291 pa_sample_spec_snprint(t, sizeof(t), &wwi->sample_spec);
292 TRACE("Sample spec '%s'\n", t);
295 if (dwFlags & WAVE_FORMAT_QUERY) {
296 TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%d !\n",
297 lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
298 lpDesc->lpFormat->nSamplesPerSec);
299 ret = MMSYSERR_NOERROR;
300 goto exit;
303 wwi->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
304 wwi->waveDesc = *lpDesc;
305 PULSE_InitRingMessage(&wwi->msgRing);
307 wwi->stream = pa_stream_new(PULSE_context, "WaveIn", &wwi->sample_spec, NULL);
308 if (!wwi->stream) {
309 ret = WAVERR_BADFORMAT;
310 goto exit;
313 pa_stream_set_state_callback(wwi->stream, PULSE_StreamStateCallback, wwi);
314 pa_stream_set_read_callback (wwi->stream, PULSE_StreamRequestCallback, wwi);
316 wwi->buffer_attr.maxlength = (uint32_t)-1;
317 wwi->buffer_attr.fragsize = pa_bytes_per_second(&wwi->sample_spec) / 100;
319 pa_threaded_mainloop_lock(PULSE_ml);
320 TRACE("Asking to open %s for recording.\n", wdi->device_name);
321 pa_stream_connect_record(wwi->stream, wdi->device_name, &wwi->buffer_attr,
322 PA_STREAM_START_CORKED |
323 PA_STREAM_AUTO_TIMING_UPDATE);
325 for (;;) {
326 pa_context_state_t cstate = pa_context_get_state(PULSE_context);
327 pa_stream_state_t sstate = pa_stream_get_state(wwi->stream);
329 if (cstate == PA_CONTEXT_FAILED || cstate == PA_CONTEXT_TERMINATED ||
330 sstate == PA_STREAM_FAILED || sstate == PA_STREAM_TERMINATED) {
331 ERR("Failed to connect context object: %s\n", pa_strerror(pa_context_errno(PULSE_context)));
332 ret = MMSYSERR_NODRIVER;
333 pa_threaded_mainloop_unlock(PULSE_ml);
334 goto exit;
337 if (sstate == PA_STREAM_READY)
338 break;
340 pa_threaded_mainloop_wait(PULSE_ml);
342 TRACE("(%p)->stream connected for recording.\n", wwi);
344 PULSE_WaitForOperation(pa_stream_update_timing_info(wwi->stream, PULSE_StreamSuccessCallback, wwi));
346 wwi->timing_info = pa_stream_get_timing_info(wwi->stream);
347 assert(wwi->timing_info);
348 pa_threaded_mainloop_unlock(PULSE_ml);
350 wwi->hStartUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
351 wwi->hThread = CreateThread(NULL, 0, widRecorder, (LPVOID)wwi, 0, &(wwi->dwThreadID));
352 if (wwi->hThread)
353 SetThreadPriority(wwi->hThread, THREAD_PRIORITY_TIME_CRITICAL);
354 else {
355 ERR("Thread creation for the widRecorder failed!\n");
356 ret = MMSYSERR_NOMEM;
357 goto exit;
359 WaitForSingleObject(wwi->hStartUpEvent, INFINITE);
360 CloseHandle(wwi->hStartUpEvent);
361 wwi->hStartUpEvent = INVALID_HANDLE_VALUE;
363 return widNotifyClient(wwi, WIM_OPEN, 0L, 0L);
365 exit:
366 if (!wwi)
367 return ret;
369 if (wwi->hStartUpEvent != INVALID_HANDLE_VALUE)
370 CloseHandle(wwi->hStartUpEvent);
372 if (wwi->msgRing.ring_buffer_size > 0)
373 PULSE_DestroyRingMessage(&wwi->msgRing);
375 if (wwi->stream) {
376 if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY)
377 pa_stream_disconnect(wwi->stream);
378 pa_stream_unref(wwi->stream);
380 HeapFree(GetProcessHeap(), 0, wwi);
382 return ret;
384 /**************************************************************************
385 * widClose [internal]
387 static DWORD widClose(WORD wDevID, WINE_WAVEINST *wwi) {
388 DWORD ret;
390 TRACE("(%u, %p);\n", wDevID, wwi);
391 if (wDevID >= PULSE_WidNumDevs) {
392 WARN("Asked for device %d, but only %d known!\n", wDevID, PULSE_WodNumDevs);
393 return MMSYSERR_INVALHANDLE;
394 } else if (!wwi) {
395 WARN("Stream instance invalid.\n");
396 return MMSYSERR_INVALHANDLE;
399 if (wwi->state != WINE_WS_FAILED) {
400 if (wwi->lpQueuePtr) {
401 WARN("buffers recording recording !\n");
402 return WAVERR_STILLPLAYING;
405 pa_threaded_mainloop_lock(PULSE_ml);
406 if (pa_stream_get_state(wwi->stream) == PA_STREAM_READY)
407 pa_stream_drop(wwi->stream);
408 pa_stream_disconnect(wwi->stream);
409 pa_threaded_mainloop_unlock(PULSE_ml);
411 if (wwi->hThread != INVALID_HANDLE_VALUE)
412 PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_CLOSING, 0, TRUE);
414 PULSE_DestroyRingMessage(&wwi->msgRing);
416 ret = widNotifyClient(wwi, WIM_CLOSE, 0L, 0L);
418 pa_stream_unref(wwi->stream);
419 TRACE("Deallocating record instance.\n");
420 HeapFree(GetProcessHeap(), 0, wwi);
421 return ret;
424 /**************************************************************************
425 * widAddBuffer [internal]
428 static DWORD widAddBuffer(WINE_WAVEINST* wwi, LPWAVEHDR lpWaveHdr, DWORD dwSize) {
429 TRACE("(%p, %p, %08X);\n", wwi, lpWaveHdr, dwSize);
431 if (!wwi || wwi->state == WINE_WS_FAILED) {
432 WARN("Stream instance invalid.\n");
433 return MMSYSERR_INVALHANDLE;
436 if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
437 return WAVERR_UNPREPARED;
439 if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
440 return WAVERR_STILLPLAYING;
442 lpWaveHdr->dwFlags &= ~WHDR_DONE;
443 lpWaveHdr->dwFlags |= WHDR_INQUEUE;
444 lpWaveHdr->dwBytesRecorded = 0;
445 lpWaveHdr->lpNext = 0;
447 PULSE_AddRingMessage(&wwi->msgRing, WINE_WM_HEADER, (DWORD)lpWaveHdr, FALSE);
449 return MMSYSERR_NOERROR;
452 /**************************************************************************
453 * widRecorderMessage [internal]
455 static DWORD widRecorderMessage(WINE_WAVEINST *wwi, enum win_wm_message message) {
456 if (!wwi || wwi->state == WINE_WS_FAILED) {
457 WARN("Stream instance invalid.\n");
458 return MMSYSERR_INVALHANDLE;
461 PULSE_AddRingMessage(&wwi->msgRing, message, 0, TRUE);
462 return MMSYSERR_NOERROR;
465 /**************************************************************************
466 * widGetPosition [internal]
468 static DWORD widGetPosition(WINE_WAVEINST *wwi, LPMMTIME lpTime, DWORD uSize) {
470 if (!wwi || wwi->state == WINE_WS_FAILED) {
471 WARN("Stream instance invalid.\n");
472 return MMSYSERR_INVALHANDLE;
475 if (lpTime == NULL) return MMSYSERR_INVALPARAM;
477 return PULSE_UsecToMMTime(pa_bytes_to_usec(wwi->timing_info->read_index - wwi->dwLastReset, &wwi->sample_spec), lpTime, &wwi->sample_spec);
480 /**************************************************************************
481 * widGetDevCaps [internal]
483 static DWORD widGetDevCaps(DWORD wDevID, LPWAVEINCAPSW lpCaps, DWORD dwSize) {
484 TRACE("(%u, %p, %u);\n", wDevID, lpCaps, dwSize);
486 if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
488 if (wDevID >= PULSE_WidNumDevs) {
489 TRACE("Asked for device %d, but only %d known!\n", wDevID, PULSE_WidNumDevs);
490 return MMSYSERR_INVALHANDLE;
493 memcpy(lpCaps, &(WInDev[wDevID].caps.in), min(dwSize, sizeof(*lpCaps)));
494 return MMSYSERR_NOERROR;
497 /**************************************************************************
498 * widGetNumDevs [internal]
499 * Context-sanity check here, as if we respond with 0, WINE will move on
500 * to the next wavein driver.
502 static DWORD widGetNumDevs(void) {
503 if (pa_context_get_state(PULSE_context) != PA_CONTEXT_READY)
504 return 0;
506 return PULSE_WidNumDevs;
509 /**************************************************************************
510 * widDevInterfaceSize [internal]
512 static DWORD widDevInterfaceSize(UINT wDevID, LPDWORD dwParam1) {
513 TRACE("(%u, %p)\n", wDevID, dwParam1);
515 *dwParam1 = MultiByteToWideChar(CP_UTF8, 0, WInDev[wDevID].interface_name, -1,
516 NULL, 0 ) * sizeof(WCHAR);
517 return MMSYSERR_NOERROR;
520 /**************************************************************************
521 * widDevInterface [internal]
523 static DWORD widDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2) {
524 if (dwParam2 >= MultiByteToWideChar(CP_UTF8, 0, WInDev[wDevID].interface_name, -1,
525 NULL, 0 ) * sizeof(WCHAR))
527 MultiByteToWideChar(CP_UTF8, 0, WInDev[wDevID].interface_name, -1,
528 dwParam1, dwParam2 / sizeof(WCHAR));
529 return MMSYSERR_NOERROR;
531 return MMSYSERR_INVALPARAM;
534 /**************************************************************************
535 * widDsDesc [internal]
537 DWORD widDsDesc(UINT wDevID, PDSDRIVERDESC desc)
539 *desc = WInDev[wDevID].ds_desc;
540 return MMSYSERR_NOERROR;
543 /**************************************************************************
544 * widMessage (WINEPULSE.@)
546 DWORD WINAPI PULSE_widMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
547 DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
549 switch (wMsg) {
550 case DRVM_INIT:
551 case DRVM_EXIT:
552 case DRVM_ENABLE:
553 case DRVM_DISABLE:
554 /* FIXME: Pretend this is supported */
555 return 0;
556 case WIDM_OPEN: return widOpen (wDevID, (DWORD_PTR*)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2);
557 case WIDM_CLOSE: return widClose (wDevID, (WINE_WAVEINST*)dwUser);
558 case WIDM_ADDBUFFER: return widAddBuffer ((WINE_WAVEINST*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
559 case WIDM_PREPARE: return MMSYSERR_NOTSUPPORTED;
560 case WIDM_UNPREPARE: return MMSYSERR_NOTSUPPORTED;
561 case WIDM_GETDEVCAPS: return widGetDevCaps(wDevID, (LPWAVEINCAPSW)dwParam1, dwParam2);
562 case WIDM_GETNUMDEVS: return widGetNumDevs();
563 case WIDM_GETPOS: return widGetPosition ((WINE_WAVEINST*)dwUser, (LPMMTIME)dwParam1, dwParam2);
564 case WIDM_RESET: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_RESETTING);
565 case WIDM_START: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STARTING);
566 case WIDM_STOP: return widRecorderMessage((WINE_WAVEINST*)dwUser, WINE_WM_STOPPING);
567 case DRV_QUERYDEVICEINTERFACESIZE: return widDevInterfaceSize(wDevID, (LPDWORD)dwParam1);
568 case DRV_QUERYDEVICEINTERFACE: return widDevInterface(wDevID, (PWCHAR)dwParam1, dwParam2);
569 case DRV_QUERYDSOUNDIFACE: return MMSYSERR_NOTSUPPORTED; /* Use emulation, as there is no advantage */
570 case DRV_QUERYDSOUNDDESC: return widDsDesc(wDevID, (PDSDRIVERDESC)dwParam1);
571 default:
572 FIXME("unknown message %d!\n", wMsg);
574 return MMSYSERR_NOTSUPPORTED;
577 #else /* HAVE_PULSEAUDIO */
579 /**************************************************************************
580 * widMessage (WINEPULSE.@)
582 DWORD WINAPI PULSE_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
583 DWORD dwParam1, DWORD dwParam2) {
584 FIXME("(%u, %04X, %08X, %08X, %08X):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
585 return MMSYSERR_NOTENABLED;
588 #endif /* HAVE_PULSEAUDIO */