1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
3 * Wine Wave mapper driver
5 * Copyright 1999,2001 Eric Pouech
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 * + better protection against evilish dwUser parameters
24 * + use asynchronous ACM conversion
25 * + don't use callback functions when none is required in open
26 * + the buffer sizes may not be accurate, so there may be some
27 * remaining bytes in src and dst buffers after ACM conversions...
28 * those should be taken care of...
40 #include "wine/unicode.h"
41 #include "wine/debug.h"
43 WINE_DEFAULT_DEBUG_CHANNEL(wavemap);
45 typedef struct tagWAVEMAPDATA {
46 struct tagWAVEMAPDATA* self;
57 HACMSTREAM hAcmStream;
58 /* needed data to filter callbacks. Only needed when hAcmStream is not 0 */
60 DWORD dwClientInstance;
62 /* ratio to compute position from a PCM playback to any format */
65 /* channel size of inner and outer */
66 DWORD nSamplesPerSecOuter;
67 DWORD nSamplesPerSecInner;
70 static BOOL WAVEMAP_IsData(WAVEMAPDATA* wm)
72 return (!IsBadReadPtr(wm, sizeof(WAVEMAPDATA)) && wm->self == wm);
75 /*======================================================================*
77 *======================================================================*/
79 static void CALLBACK wodCallback(HWAVEOUT hWave, UINT uMsg, DWORD dwInstance,
80 DWORD dwParam1, DWORD dwParam2)
82 WAVEMAPDATA* wom = (WAVEMAPDATA*)dwInstance;
84 TRACE("(%p %u %ld %lx %lx);\n", hWave, uMsg, dwInstance, dwParam1, dwParam2);
86 if (!WAVEMAP_IsData(wom)) {
91 if (hWave != wom->u.out.hInnerWave && uMsg != WOM_OPEN)
92 ERR("Shouldn't happen (%p %p)\n", hWave, wom->u.out.hInnerWave);
97 /* dwParam1 & dwParam2 are supposed to be 0, nothing to do */
100 if (wom->hAcmStream) {
101 LPWAVEHDR lpWaveHdrDst = (LPWAVEHDR)dwParam1;
102 PACMSTREAMHEADER ash = (PACMSTREAMHEADER)((LPSTR)lpWaveHdrDst - sizeof(ACMSTREAMHEADER));
103 LPWAVEHDR lpWaveHdrSrc = (LPWAVEHDR)ash->dwUser;
105 lpWaveHdrSrc->dwFlags &= ~WHDR_INQUEUE;
106 lpWaveHdrSrc->dwFlags |= WHDR_DONE;
107 dwParam1 = (DWORD)lpWaveHdrSrc;
111 ERR("Unknown msg %u\n", uMsg);
114 DriverCallback(wom->dwCallback, HIWORD(wom->dwFlags), (HDRVR)wom->u.out.hOuterWave,
115 uMsg, wom->dwClientInstance, dwParam1, dwParam2);
118 /******************************************************************
123 static DWORD wodOpenHelper(WAVEMAPDATA* wom, UINT idx,
124 LPWAVEOPENDESC lpDesc, LPWAVEFORMATEX lpwfx,
129 TRACE("(%p %04x %p %p %08lx)\n", wom, idx, lpDesc, lpwfx, dwFlags);
131 /* destination is always PCM, so the formulas below apply */
132 lpwfx->nBlockAlign = (lpwfx->nChannels * lpwfx->wBitsPerSample) / 8;
133 lpwfx->nAvgBytesPerSec = lpwfx->nSamplesPerSec * lpwfx->nBlockAlign;
134 if (dwFlags & WAVE_FORMAT_QUERY) {
135 ret = acmStreamOpen(NULL, 0, lpDesc->lpFormat, lpwfx, NULL, 0L, 0L, ACM_STREAMOPENF_QUERY);
137 ret = acmStreamOpen(&wom->hAcmStream, 0, lpDesc->lpFormat, lpwfx, NULL, 0L, 0L, 0L);
139 if (ret == MMSYSERR_NOERROR) {
140 ret = waveOutOpen(&wom->u.out.hInnerWave, idx, lpwfx, (DWORD)wodCallback,
141 (DWORD)wom, (dwFlags & ~CALLBACK_TYPEMASK) | CALLBACK_FUNCTION);
142 if (ret != MMSYSERR_NOERROR && !(dwFlags & WAVE_FORMAT_QUERY)) {
143 acmStreamClose(wom->hAcmStream, 0);
147 TRACE("ret = %08lx\n", ret);
151 static DWORD wodOpen(LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
155 WAVEMAPDATA* wom = HeapAlloc(GetProcessHeap(), 0, sizeof(WAVEMAPDATA));
158 TRACE("(%p %p %08lx)\n", lpdwUser, lpDesc, dwFlags);
162 return MMSYSERR_NOMEM;
165 ndhi = waveOutGetNumDevs();
166 if (dwFlags & WAVE_MAPPED) {
167 if (lpDesc->uMappedDeviceID >= ndhi) {
168 WARN("invalid parameter: dwFlags WAVE_MAPPED\n");
169 HeapFree(GetProcessHeap(), 0, wom);
170 return MMSYSERR_INVALPARAM;
172 ndlo = lpDesc->uMappedDeviceID;
174 dwFlags &= ~WAVE_MAPPED;
179 wom->dwCallback = lpDesc->dwCallback;
180 wom->dwFlags = dwFlags;
181 wom->dwClientInstance = lpDesc->dwInstance;
182 wom->u.out.hOuterWave = (HWAVEOUT)lpDesc->hWave;
183 wom->avgSpeedOuter = wom->avgSpeedInner = lpDesc->lpFormat->nAvgBytesPerSec;
184 wom->nSamplesPerSecOuter = wom->nSamplesPerSecInner = lpDesc->lpFormat->nSamplesPerSec;
186 for (i = ndlo; i < ndhi; i++) {
187 /* if no ACM stuff is involved, no need to handle callbacks at this
188 * level, this will be done transparently
190 if (waveOutOpen(&wom->u.out.hInnerWave, i, lpDesc->lpFormat, (DWORD)wodCallback,
191 (DWORD)wom, (dwFlags & ~CALLBACK_TYPEMASK) | CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT) == MMSYSERR_NOERROR) {
197 if ((dwFlags & WAVE_FORMAT_DIRECT) == 0 && lpDesc->lpFormat->wFormatTag == WAVE_FORMAT_PCM) {
200 wfx.wFormatTag = WAVE_FORMAT_PCM;
201 wfx.cbSize = 0; /* normally, this field is not used for PCM format, just in case */
202 /* try some ACM stuff */
204 #define TRY(sps,bps) wfx.nSamplesPerSec = (sps); wfx.wBitsPerSample = (bps); \
205 switch (res=wodOpenHelper(wom, i, lpDesc, &wfx, dwFlags | WAVE_FORMAT_DIRECT)) { \
206 case MMSYSERR_NOERROR: wom->avgSpeedInner = wfx.nAvgBytesPerSec; wom->nSamplesPerSecInner = wfx.nSamplesPerSec; goto found; \
207 case WAVERR_BADFORMAT: break; \
208 default: goto error; \
211 /* Our resampling algorithm is quite primitive so first try
212 * to just change the bit depth and number of channels
214 for (i = ndlo; i < ndhi; i++) {
215 wfx.nSamplesPerSec=lpDesc->lpFormat->nSamplesPerSec;
216 wfx.nChannels = lpDesc->lpFormat->nChannels;
217 TRY(wfx.nSamplesPerSec, 16);
218 TRY(wfx.nSamplesPerSec, 8);
220 TRY(wfx.nSamplesPerSec, 16);
221 TRY(wfx.nSamplesPerSec, 8);
224 for (i = ndlo; i < ndhi; i++) {
225 /* first try with same stereo/mono option as source */
226 wfx.nChannels = lpDesc->lpFormat->nChannels;
233 /* 2^3 => 1, 1^3 => 2, so if stereo, try mono (and the other way around) */
241 /* first try with same stereo/mono option as source */
242 wfx.nChannels = lpDesc->lpFormat->nChannels;
249 /* 2^3 => 1, 1^3 => 2, so if stereo, try mono (and the other way around) */
260 HeapFree(GetProcessHeap(), 0, wom);
261 WARN("ret = WAVERR_BADFORMAT\n");
262 return WAVERR_BADFORMAT;
265 if (dwFlags & WAVE_FORMAT_QUERY) {
267 HeapFree(GetProcessHeap(), 0, wom);
269 *lpdwUser = (DWORD)wom;
271 return MMSYSERR_NOERROR;
273 HeapFree(GetProcessHeap(), 0, wom);
274 if (res==ACMERR_NOTPOSSIBLE) {
275 WARN("ret = WAVERR_BADFORMAT\n");
276 return WAVERR_BADFORMAT;
278 WARN("ret = 0x%08lx\n", res);
282 static DWORD wodClose(WAVEMAPDATA* wom)
286 TRACE("(%p)\n", wom);
288 ret = waveOutClose(wom->u.out.hInnerWave);
289 if (ret == MMSYSERR_NOERROR) {
290 if (wom->hAcmStream) {
291 ret = acmStreamClose(wom->hAcmStream, 0);
293 if (ret == MMSYSERR_NOERROR) {
294 HeapFree(GetProcessHeap(), 0, wom);
300 static DWORD wodWrite(WAVEMAPDATA* wom, LPWAVEHDR lpWaveHdrSrc, DWORD dwParam2)
302 PACMSTREAMHEADER ash;
303 LPWAVEHDR lpWaveHdrDst;
305 TRACE("(%p %p %08lx)\n", wom, lpWaveHdrSrc, dwParam2);
307 if (!wom->hAcmStream) {
308 return waveOutWrite(wom->u.out.hInnerWave, lpWaveHdrSrc, dwParam2);
311 lpWaveHdrSrc->dwFlags |= WHDR_INQUEUE;
312 ash = (PACMSTREAMHEADER)lpWaveHdrSrc->reserved;
313 /* acmStreamConvert will actually check that the new size is less than initial size */
314 ash->cbSrcLength = lpWaveHdrSrc->dwBufferLength;
315 if (acmStreamConvert(wom->hAcmStream, ash, 0L) != MMSYSERR_NOERROR) {
316 WARN("acmStreamConvert failed\n");
317 return MMSYSERR_ERROR;
320 lpWaveHdrDst = (LPWAVEHDR)((LPSTR)ash + sizeof(ACMSTREAMHEADER));
321 if (ash->cbSrcLength > ash->cbSrcLengthUsed)
322 FIXME("Not all src buffer has been written, expect bogus sound\n");
323 else if (ash->cbSrcLength < ash->cbSrcLengthUsed)
324 ERR("CoDec has read more data than it is allowed to\n");
326 if (ash->cbDstLengthUsed == 0) {
327 /* something went wrong in decoding */
328 FIXME("Got 0 length\n");
329 return MMSYSERR_ERROR;
331 lpWaveHdrDst->dwBufferLength = ash->cbDstLengthUsed;
332 return waveOutWrite(wom->u.out.hInnerWave, lpWaveHdrDst, sizeof(*lpWaveHdrDst));
335 static DWORD wodPrepare(WAVEMAPDATA* wom, LPWAVEHDR lpWaveHdrSrc, DWORD dwParam2)
337 PACMSTREAMHEADER ash;
340 LPWAVEHDR lpWaveHdrDst;
342 TRACE("(%p %p %08lx)\n", wom, lpWaveHdrSrc, dwParam2);
344 if (!wom->hAcmStream)
345 return waveOutPrepareHeader(wom->u.out.hInnerWave, lpWaveHdrSrc, dwParam2);
347 if (acmStreamSize(wom->hAcmStream, lpWaveHdrSrc->dwBufferLength, &size, ACM_STREAMSIZEF_SOURCE) != MMSYSERR_NOERROR) {
348 WARN("acmStreamSize failed\n");
349 return MMSYSERR_ERROR;
352 ash = HeapAlloc(GetProcessHeap(), 0, sizeof(ACMSTREAMHEADER) + sizeof(WAVEHDR) + size);
355 return MMSYSERR_NOMEM;
358 ash->cbStruct = sizeof(*ash);
360 ash->dwUser = (DWORD)lpWaveHdrSrc;
361 ash->pbSrc = lpWaveHdrSrc->lpData;
362 ash->cbSrcLength = lpWaveHdrSrc->dwBufferLength;
363 /* ash->cbSrcLengthUsed */
364 ash->dwSrcUser = lpWaveHdrSrc->dwUser; /* FIXME ? */
365 ash->pbDst = (LPSTR)ash + sizeof(ACMSTREAMHEADER) + sizeof(WAVEHDR);
366 ash->cbDstLength = size;
367 /* ash->cbDstLengthUsed */
368 ash->dwDstUser = 0; /* FIXME ? */
369 dwRet = acmStreamPrepareHeader(wom->hAcmStream, ash, 0L);
370 if (dwRet != MMSYSERR_NOERROR) {
371 WARN("acmStreamPrepareHeader failed\n");
375 lpWaveHdrDst = (LPWAVEHDR)((LPSTR)ash + sizeof(ACMSTREAMHEADER));
376 lpWaveHdrDst->lpData = ash->pbDst;
377 lpWaveHdrDst->dwBufferLength = size; /* conversion is not done yet */
378 lpWaveHdrDst->dwFlags = 0;
379 lpWaveHdrDst->dwLoops = 0;
380 dwRet = waveOutPrepareHeader(wom->u.out.hInnerWave, lpWaveHdrDst, sizeof(*lpWaveHdrDst));
381 if (dwRet != MMSYSERR_NOERROR) {
382 WARN("waveOutPrepareHeader failed\n");
386 lpWaveHdrSrc->reserved = (DWORD)ash;
387 lpWaveHdrSrc->dwFlags = WHDR_PREPARED;
389 return MMSYSERR_NOERROR;
391 TRACE("=> (%ld)\n", dwRet);
392 HeapFree(GetProcessHeap(), 0, ash);
396 static DWORD wodUnprepare(WAVEMAPDATA* wom, LPWAVEHDR lpWaveHdrSrc, DWORD dwParam2)
398 PACMSTREAMHEADER ash;
399 LPWAVEHDR lpWaveHdrDst;
400 DWORD dwRet1, dwRet2;
402 TRACE("(%p %p %08lx)\n", wom, lpWaveHdrSrc, dwParam2);
404 if (!wom->hAcmStream) {
405 return waveOutUnprepareHeader(wom->u.out.hInnerWave, lpWaveHdrSrc, dwParam2);
407 ash = (PACMSTREAMHEADER)lpWaveHdrSrc->reserved;
408 dwRet1 = acmStreamUnprepareHeader(wom->hAcmStream, ash, 0L);
410 lpWaveHdrDst = (LPWAVEHDR)((LPSTR)ash + sizeof(ACMSTREAMHEADER));
411 dwRet2 = waveOutUnprepareHeader(wom->u.out.hInnerWave, lpWaveHdrDst, sizeof(*lpWaveHdrDst));
413 HeapFree(GetProcessHeap(), 0, ash);
415 lpWaveHdrSrc->dwFlags &= ~WHDR_PREPARED;
416 return (dwRet1 == MMSYSERR_NOERROR) ? dwRet2 : dwRet1;
419 static DWORD wodGetPosition(WAVEMAPDATA* wom, LPMMTIME lpTime, DWORD dwParam2)
423 TRACE("(%p %p %08lx)\n", wom, lpTime, dwParam2);
425 memcpy(&timepos, lpTime, sizeof(timepos));
427 /* For TIME_MS, we're going to recalculate using TIME_BYTES */
428 if (lpTime->wType == TIME_MS)
429 timepos.wType = TIME_BYTES;
431 /* This can change timepos.wType if the requested type is not supported */
432 val = waveOutGetPosition(wom->u.out.hInnerWave, &timepos, dwParam2);
434 if (timepos.wType == TIME_BYTES)
436 DWORD dwInnerSamplesPerOuter = wom->nSamplesPerSecInner / wom->nSamplesPerSecOuter;
437 if (dwInnerSamplesPerOuter > 0)
439 DWORD dwInnerBytesPerSample = wom->avgSpeedInner / wom->nSamplesPerSecInner;
440 DWORD dwInnerBytesPerOuterSample = dwInnerBytesPerSample * dwInnerSamplesPerOuter;
443 /* If we are up sampling (going from lower sample rate to higher),
444 ** we need to make a special accomodation for times when we've
445 ** written a partial output sample. This happens frequently
446 ** to us because we use msacm to do our up sampling, and it
447 ** will up sample on an unaligned basis.
448 ** For example, if you convert a 2 byte wide 8,000 'outer'
449 ** buffer to a 2 byte wide 48,000 inner device, you would
450 ** expect 2 bytes of input to produce 12 bytes of output.
451 ** Instead, msacm will produce 8 bytes of output.
452 ** But reporting our position as 1 byte of output is
453 ** nonsensical; the output buffer position needs to be
454 ** aligned on outer sample size, and aggressively rounded up.
456 remainder = timepos.u.cb % dwInnerBytesPerOuterSample;
459 timepos.u.cb -= remainder;
460 timepos.u.cb += dwInnerBytesPerOuterSample;
464 lpTime->u.cb = MulDiv(timepos.u.cb, wom->avgSpeedOuter, wom->avgSpeedInner);
466 /* Once we have the TIME_BYTES right, we can easily convert to TIME_MS */
467 if (lpTime->wType == TIME_MS)
468 lpTime->u.ms = MulDiv(lpTime->u.cb, 1000, wom->avgSpeedOuter);
470 lpTime->wType = TIME_BYTES;
472 else if (lpTime->wType == TIME_SAMPLES && timepos.wType == TIME_SAMPLES)
473 lpTime->u.sample = MulDiv(timepos.u.sample, wom->nSamplesPerSecOuter, wom->nSamplesPerSecInner);
475 /* other time types don't require conversion */
476 lpTime->u = timepos.u;
481 static DWORD wodGetDevCaps(UINT wDevID, WAVEMAPDATA* wom, LPWAVEOUTCAPSW lpWaveCaps, DWORD dwParam2)
483 static const WCHAR name[] = {'W','i','n','e',' ','w','a','v','e',' ','o','u','t',' ','m','a','p','p','e','r',0};
485 TRACE("(%04x %p %p %08lx)\n",wDevID, wom, lpWaveCaps, dwParam2);
487 /* if opened low driver, forward message */
488 if (WAVEMAP_IsData(wom))
489 return waveOutGetDevCapsW((UINT)wom->u.out.hInnerWave, lpWaveCaps, dwParam2);
490 /* else if no drivers, nothing to map so return bad device */
491 if (waveOutGetNumDevs() == 0) {
492 WARN("bad device id\n");
493 return MMSYSERR_BADDEVICEID;
495 /* otherwise, return caps of mapper itself */
496 if (wDevID == (UINT)-1 || wDevID == (UINT16)-1) {
500 woc.vDriverVersion = 0x0100;
501 lstrcpyW(woc.szPname, name);
503 WAVE_FORMAT_96M08 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16 |
504 WAVE_FORMAT_48M08 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 |
505 WAVE_FORMAT_4M08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S16 |
506 WAVE_FORMAT_2M08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 |
507 WAVE_FORMAT_1M08 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16;
509 woc.dwSupport = WAVECAPS_VOLUME | WAVECAPS_LRVOLUME;
510 memcpy(lpWaveCaps, &woc, min(dwParam2, sizeof(woc)));
512 return MMSYSERR_NOERROR;
514 ERR("This shouldn't happen\n");
515 return MMSYSERR_ERROR;
518 static DWORD wodGetVolume(UINT wDevID, WAVEMAPDATA* wom, LPDWORD lpVol)
520 TRACE("(%04x %p %p)\n",wDevID, wom, lpVol);
522 if (WAVEMAP_IsData(wom))
523 return waveOutGetVolume(wom->u.out.hInnerWave, lpVol);
524 return MMSYSERR_NOERROR;
527 static DWORD wodSetVolume(UINT wDevID, WAVEMAPDATA* wom, DWORD vol)
529 TRACE("(%04x %p %08lx)\n",wDevID, wom, vol);
531 if (WAVEMAP_IsData(wom))
532 return waveOutSetVolume(wom->u.out.hInnerWave, vol);
533 return MMSYSERR_NOERROR;
536 static DWORD wodPause(WAVEMAPDATA* wom)
540 return waveOutPause(wom->u.out.hInnerWave);
543 static DWORD wodRestart(WAVEMAPDATA* wom)
547 return waveOutRestart(wom->u.out.hInnerWave);
550 static DWORD wodReset(WAVEMAPDATA* wom)
554 return waveOutReset(wom->u.out.hInnerWave);
557 static DWORD wodBreakLoop(WAVEMAPDATA* wom)
561 return waveOutBreakLoop(wom->u.out.hInnerWave);
564 static DWORD wodMapperStatus(WAVEMAPDATA* wom, DWORD flags, LPVOID ptr)
567 DWORD ret = MMSYSERR_NOTSUPPORTED;
569 TRACE("(%p %08lx %p)\n",wom, flags, ptr);
572 case WAVEOUT_MAPPER_STATUS_DEVICE:
573 ret = waveOutGetID(wom->u.out.hInnerWave, &id);
576 case WAVEOUT_MAPPER_STATUS_MAPPED:
577 FIXME("Unsupported flag=%ld\n", flags);
578 *(LPDWORD)ptr = 0; /* FIXME ?? */
580 case WAVEOUT_MAPPER_STATUS_FORMAT:
581 FIXME("Unsupported flag=%ld\n", flags);
582 /* ptr points to a WAVEFORMATEX struct - before or after streaming ? */
586 FIXME("Unsupported flag=%ld\n", flags);
593 /**************************************************************************
594 * wodMessage (MSACM.@)
596 DWORD WINAPI WAVEMAP_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
597 DWORD dwParam1, DWORD dwParam2)
599 TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n",
600 wDevID, wMsg, dwUser, dwParam1, dwParam2);
607 /* FIXME: Pretend this is supported */
609 case WODM_OPEN: return wodOpen ((LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1,dwParam2);
610 case WODM_CLOSE: return wodClose ((WAVEMAPDATA*)dwUser);
611 case WODM_WRITE: return wodWrite ((WAVEMAPDATA*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
612 case WODM_PAUSE: return wodPause ((WAVEMAPDATA*)dwUser);
613 case WODM_GETPOS: return wodGetPosition ((WAVEMAPDATA*)dwUser, (LPMMTIME)dwParam1, dwParam2);
614 case WODM_BREAKLOOP: return wodBreakLoop ((WAVEMAPDATA*)dwUser);
615 case WODM_PREPARE: return wodPrepare ((WAVEMAPDATA*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
616 case WODM_UNPREPARE: return wodUnprepare ((WAVEMAPDATA*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
617 case WODM_GETDEVCAPS: return wodGetDevCaps (wDevID, (WAVEMAPDATA*)dwUser, (LPWAVEOUTCAPSW)dwParam1,dwParam2);
618 case WODM_GETNUMDEVS: return 1;
619 case WODM_GETPITCH: return MMSYSERR_NOTSUPPORTED;
620 case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED;
621 case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED;
622 case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED;
623 case WODM_GETVOLUME: return wodGetVolume (wDevID, (WAVEMAPDATA*)dwUser, (LPDWORD)dwParam1);
624 case WODM_SETVOLUME: return wodSetVolume (wDevID, (WAVEMAPDATA*)dwUser, dwParam1);
625 case WODM_RESTART: return wodRestart ((WAVEMAPDATA*)dwUser);
626 case WODM_RESET: return wodReset ((WAVEMAPDATA*)dwUser);
627 case WODM_MAPPER_STATUS: return wodMapperStatus ((WAVEMAPDATA*)dwUser, dwParam1, (LPVOID)dwParam2);
628 /* known but not supported */
629 case DRV_QUERYDEVICEINTERFACESIZE:
630 case DRV_QUERYDEVICEINTERFACE:
631 return MMSYSERR_NOTSUPPORTED;
633 FIXME("unknown message %d!\n", wMsg);
635 return MMSYSERR_NOTSUPPORTED;
638 /*======================================================================*
640 *======================================================================*/
642 static void CALLBACK widCallback(HWAVEIN hWave, UINT uMsg, DWORD dwInstance,
643 DWORD dwParam1, DWORD dwParam2)
645 WAVEMAPDATA* wim = (WAVEMAPDATA*)dwInstance;
647 TRACE("(%p %u %ld %lx %lx);\n", hWave, uMsg, dwInstance, dwParam1, dwParam2);
649 if (!WAVEMAP_IsData(wim)) {
654 if (hWave != wim->u.in.hInnerWave && uMsg != WIM_OPEN)
655 ERR("Shouldn't happen (%p %p)\n", hWave, wim->u.in.hInnerWave);
660 /* dwParam1 & dwParam2 are supposed to be 0, nothing to do */
663 if (wim->hAcmStream) {
664 LPWAVEHDR lpWaveHdrSrc = (LPWAVEHDR)dwParam1;
665 PACMSTREAMHEADER ash = (PACMSTREAMHEADER)((LPSTR)lpWaveHdrSrc - sizeof(ACMSTREAMHEADER));
666 LPWAVEHDR lpWaveHdrDst = (LPWAVEHDR)ash->dwUser;
668 /* convert data just gotten from waveIn into requested format */
669 if (acmStreamConvert(wim->hAcmStream, ash, 0L) != MMSYSERR_NOERROR) {
670 ERR("ACM conversion failed\n");
673 TRACE("Converted %ld bytes into %ld\n", ash->cbSrcLengthUsed, ash->cbDstLengthUsed);
675 /* and setup the wavehdr to return accordingly */
676 lpWaveHdrDst->dwFlags &= ~WHDR_INQUEUE;
677 lpWaveHdrDst->dwFlags |= WHDR_DONE;
678 lpWaveHdrDst->dwBytesRecorded = ash->cbDstLengthUsed;
679 dwParam1 = (DWORD)lpWaveHdrDst;
683 ERR("Unknown msg %u\n", uMsg);
686 DriverCallback(wim->dwCallback, HIWORD(wim->dwFlags), (HDRVR)wim->u.in.hOuterWave,
687 uMsg, wim->dwClientInstance, dwParam1, dwParam2);
690 static DWORD widOpenHelper(WAVEMAPDATA* wim, UINT idx,
691 LPWAVEOPENDESC lpDesc, LPWAVEFORMATEX lpwfx,
696 TRACE("(%p %04x %p %p %08lx)\n", wim, idx, lpDesc, lpwfx, dwFlags);
698 /* source is always PCM, so the formulas below apply */
699 lpwfx->nBlockAlign = (lpwfx->nChannels * lpwfx->wBitsPerSample) / 8;
700 lpwfx->nAvgBytesPerSec = lpwfx->nSamplesPerSec * lpwfx->nBlockAlign;
701 if (dwFlags & WAVE_FORMAT_QUERY) {
702 ret = acmStreamOpen(NULL, 0, lpwfx, lpDesc->lpFormat, NULL, 0L, 0L, ACM_STREAMOPENF_QUERY);
704 ret = acmStreamOpen(&wim->hAcmStream, 0, lpwfx, lpDesc->lpFormat, NULL, 0L, 0L, 0L);
706 if (ret == MMSYSERR_NOERROR) {
707 ret = waveInOpen(&wim->u.in.hInnerWave, idx, lpwfx, (DWORD)widCallback,
708 (DWORD)wim, (dwFlags & ~CALLBACK_TYPEMASK) | CALLBACK_FUNCTION);
709 if (ret != MMSYSERR_NOERROR && !(dwFlags & WAVE_FORMAT_QUERY)) {
710 acmStreamClose(wim->hAcmStream, 0);
714 TRACE("ret = %08lx\n", ret);
718 static DWORD widOpen(LPDWORD lpdwUser, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
722 WAVEMAPDATA* wim = HeapAlloc(GetProcessHeap(), 0, sizeof(WAVEMAPDATA));
725 TRACE("(%p %p %08lx)\n", lpdwUser, lpDesc, dwFlags);
729 return MMSYSERR_NOMEM;
733 wim->dwCallback = lpDesc->dwCallback;
734 wim->dwFlags = dwFlags;
735 wim->dwClientInstance = lpDesc->dwInstance;
736 wim->u.in.hOuterWave = (HWAVEIN)lpDesc->hWave;
738 ndhi = waveInGetNumDevs();
739 if (dwFlags & WAVE_MAPPED) {
740 if (lpDesc->uMappedDeviceID >= ndhi) return MMSYSERR_INVALPARAM;
741 ndlo = lpDesc->uMappedDeviceID;
743 dwFlags &= ~WAVE_MAPPED;
748 wim->avgSpeedOuter = wim->avgSpeedInner = lpDesc->lpFormat->nAvgBytesPerSec;
749 wim->nSamplesPerSecOuter = wim->nSamplesPerSecInner = lpDesc->lpFormat->nSamplesPerSec;
751 for (i = ndlo; i < ndhi; i++) {
752 if (waveInOpen(&wim->u.in.hInnerWave, i, lpDesc->lpFormat, (DWORD)widCallback,
753 (DWORD)wim, (dwFlags & ~CALLBACK_TYPEMASK) | CALLBACK_FUNCTION | WAVE_FORMAT_DIRECT) == MMSYSERR_NOERROR) {
759 if ((dwFlags & WAVE_FORMAT_DIRECT) == 0)
763 wfx.wFormatTag = WAVE_FORMAT_PCM;
764 wfx.cbSize = 0; /* normally, this field is not used for PCM format, just in case */
765 /* try some ACM stuff */
767 #define TRY(sps,bps) wfx.nSamplesPerSec = (sps); wfx.wBitsPerSample = (bps); \
768 switch (res=widOpenHelper(wim, i, lpDesc, &wfx, dwFlags | WAVE_FORMAT_DIRECT)) { \
769 case MMSYSERR_NOERROR: wim->avgSpeedInner = wfx.nAvgBytesPerSec; wim->nSamplesPerSecInner = wfx.nSamplesPerSec; goto found; \
770 case WAVERR_BADFORMAT: break; \
771 default: goto error; \
774 for (i = ndlo; i < ndhi; i++) {
775 wfx.nSamplesPerSec=lpDesc->lpFormat->nSamplesPerSec;
776 /* first try with same stereo/mono option as source */
777 wfx.nChannels = lpDesc->lpFormat->nChannels;
778 TRY(wfx.nSamplesPerSec, 16);
779 TRY(wfx.nSamplesPerSec, 8);
781 TRY(wfx.nSamplesPerSec, 16);
782 TRY(wfx.nSamplesPerSec, 8);
785 for (i = ndlo; i < ndhi; i++) {
786 wfx.nSamplesPerSec=lpDesc->lpFormat->nSamplesPerSec;
787 /* first try with same stereo/mono option as source */
788 wfx.nChannels = lpDesc->lpFormat->nChannels;
795 /* 2^3 => 1, 1^3 => 2, so if stereo, try mono (and the other way around) */
803 /* first try with same stereo/mono option as source */
804 wfx.nChannels = lpDesc->lpFormat->nChannels;
811 /* 2^3 => 1, 1^3 => 2, so if stereo, try mono (and the other way around) */
822 HeapFree(GetProcessHeap(), 0, wim);
823 WARN("ret = WAVERR_BADFORMAT\n");
824 return WAVERR_BADFORMAT;
826 if (dwFlags & WAVE_FORMAT_QUERY) {
828 HeapFree(GetProcessHeap(), 0, wim);
830 *lpdwUser = (DWORD)wim;
832 TRACE("Ok (stream=%08lx)\n", (DWORD)wim->hAcmStream);
833 return MMSYSERR_NOERROR;
835 HeapFree(GetProcessHeap(), 0, wim);
836 if (res==ACMERR_NOTPOSSIBLE) {
837 WARN("ret = WAVERR_BADFORMAT\n");
838 return WAVERR_BADFORMAT;
840 WARN("ret = 0x%08lx\n", res);
844 static DWORD widClose(WAVEMAPDATA* wim)
848 TRACE("(%p)\n", wim);
850 ret = waveInClose(wim->u.in.hInnerWave);
851 if (ret == MMSYSERR_NOERROR) {
852 if (wim->hAcmStream) {
853 ret = acmStreamClose(wim->hAcmStream, 0);
855 if (ret == MMSYSERR_NOERROR) {
856 HeapFree(GetProcessHeap(), 0, wim);
862 static DWORD widAddBuffer(WAVEMAPDATA* wim, LPWAVEHDR lpWaveHdrDst, DWORD dwParam2)
864 PACMSTREAMHEADER ash;
865 LPWAVEHDR lpWaveHdrSrc;
867 TRACE("(%p %p %08lx)\n", wim, lpWaveHdrDst, dwParam2);
869 if (!wim->hAcmStream) {
870 return waveInAddBuffer(wim->u.in.hInnerWave, lpWaveHdrDst, dwParam2);
873 lpWaveHdrDst->dwFlags |= WHDR_INQUEUE;
874 ash = (PACMSTREAMHEADER)lpWaveHdrDst->reserved;
876 lpWaveHdrSrc = (LPWAVEHDR)((LPSTR)ash + sizeof(ACMSTREAMHEADER));
877 return waveInAddBuffer(wim->u.in.hInnerWave, lpWaveHdrSrc, sizeof(*lpWaveHdrSrc));
880 static DWORD widPrepare(WAVEMAPDATA* wim, LPWAVEHDR lpWaveHdrDst, DWORD dwParam2)
882 PACMSTREAMHEADER ash;
885 LPWAVEHDR lpWaveHdrSrc;
887 TRACE("(%p %p %08lx)\n", wim, lpWaveHdrDst, dwParam2);
889 if (!wim->hAcmStream) {
890 return waveInPrepareHeader(wim->u.in.hInnerWave, lpWaveHdrDst, dwParam2);
892 if (acmStreamSize(wim->hAcmStream, lpWaveHdrDst->dwBufferLength, &size,
893 ACM_STREAMSIZEF_DESTINATION) != MMSYSERR_NOERROR) {
894 WARN("acmStreamSize failed\n");
895 return MMSYSERR_ERROR;
898 ash = HeapAlloc(GetProcessHeap(), 0, sizeof(ACMSTREAMHEADER) + sizeof(WAVEHDR) + size);
901 return MMSYSERR_NOMEM;
904 ash->cbStruct = sizeof(*ash);
906 ash->dwUser = (DWORD)lpWaveHdrDst;
907 ash->pbSrc = (LPSTR)ash + sizeof(ACMSTREAMHEADER) + sizeof(WAVEHDR);
908 ash->cbSrcLength = size;
909 /* ash->cbSrcLengthUsed */
910 ash->dwSrcUser = 0L; /* FIXME ? */
911 ash->pbDst = lpWaveHdrDst->lpData;
912 ash->cbDstLength = lpWaveHdrDst->dwBufferLength;
913 /* ash->cbDstLengthUsed */
914 ash->dwDstUser = lpWaveHdrDst->dwUser; /* FIXME ? */
915 dwRet = acmStreamPrepareHeader(wim->hAcmStream, ash, 0L);
916 if (dwRet != MMSYSERR_NOERROR) {
917 WARN("acmStreamPrepareHeader failed\n");
921 lpWaveHdrSrc = (LPWAVEHDR)((LPSTR)ash + sizeof(ACMSTREAMHEADER));
922 lpWaveHdrSrc->lpData = ash->pbSrc;
923 lpWaveHdrSrc->dwBufferLength = size; /* conversion is not done yet */
924 lpWaveHdrSrc->dwFlags = 0;
925 lpWaveHdrSrc->dwLoops = 0;
926 dwRet = waveInPrepareHeader(wim->u.in.hInnerWave, lpWaveHdrSrc, sizeof(*lpWaveHdrSrc));
927 if (dwRet != MMSYSERR_NOERROR) {
928 WARN("waveInPrepareHeader failed\n");
932 lpWaveHdrDst->reserved = (DWORD)ash;
933 lpWaveHdrDst->dwFlags = WHDR_PREPARED;
935 return MMSYSERR_NOERROR;
937 TRACE("=> (%ld)\n", dwRet);
938 HeapFree(GetProcessHeap(), 0, ash);
942 static DWORD widUnprepare(WAVEMAPDATA* wim, LPWAVEHDR lpWaveHdrDst, DWORD dwParam2)
944 PACMSTREAMHEADER ash;
945 LPWAVEHDR lpWaveHdrSrc;
946 DWORD dwRet1, dwRet2;
948 TRACE("(%p %p %08lx)\n", wim, lpWaveHdrDst, dwParam2);
950 if (!wim->hAcmStream) {
951 return waveInUnprepareHeader(wim->u.in.hInnerWave, lpWaveHdrDst, dwParam2);
953 ash = (PACMSTREAMHEADER)lpWaveHdrDst->reserved;
954 dwRet1 = acmStreamUnprepareHeader(wim->hAcmStream, ash, 0L);
956 lpWaveHdrSrc = (LPWAVEHDR)((LPSTR)ash + sizeof(ACMSTREAMHEADER));
957 dwRet2 = waveInUnprepareHeader(wim->u.in.hInnerWave, lpWaveHdrSrc, sizeof(*lpWaveHdrSrc));
959 HeapFree(GetProcessHeap(), 0, ash);
961 lpWaveHdrDst->dwFlags &= ~WHDR_PREPARED;
962 return (dwRet1 == MMSYSERR_NOERROR) ? dwRet2 : dwRet1;
965 static DWORD widGetPosition(WAVEMAPDATA* wim, LPMMTIME lpTime, DWORD dwParam2)
969 TRACE("(%p %p %08lx)\n", wim, lpTime, dwParam2);
971 memcpy(&timepos, lpTime, sizeof(timepos));
973 /* For TIME_MS, we're going to recalculate using TIME_BYTES */
974 if (lpTime->wType == TIME_MS)
975 timepos.wType = TIME_BYTES;
977 /* This can change timepos.wType if the requested type is not supported */
978 val = waveInGetPosition(wim->u.in.hInnerWave, &timepos, dwParam2);
980 if (timepos.wType == TIME_BYTES)
982 DWORD dwInnerSamplesPerOuter = wim->nSamplesPerSecInner / wim->nSamplesPerSecOuter;
983 if (dwInnerSamplesPerOuter > 0)
985 DWORD dwInnerBytesPerSample = wim->avgSpeedInner / wim->nSamplesPerSecInner;
986 DWORD dwInnerBytesPerOuterSample = dwInnerBytesPerSample * dwInnerSamplesPerOuter;
989 /* If we are up sampling (going from lower sample rate to higher),
990 ** we need to make a special accomodation for times when we've
991 ** written a partial output sample. This happens frequently
992 ** to us because we use msacm to do our up sampling, and it
993 ** will up sample on an unaligned basis.
994 ** For example, if you convert a 2 byte wide 8,000 'outer'
995 ** buffer to a 2 byte wide 48,000 inner device, you would
996 ** expect 2 bytes of input to produce 12 bytes of output.
997 ** Instead, msacm will produce 8 bytes of output.
998 ** But reporting our position as 1 byte of output is
999 ** nonsensical; the output buffer position needs to be
1000 ** aligned on outer sample size, and aggressively rounded up.
1002 remainder = timepos.u.cb % dwInnerBytesPerOuterSample;
1005 timepos.u.cb -= remainder;
1006 timepos.u.cb += dwInnerBytesPerOuterSample;
1010 lpTime->u.cb = MulDiv(timepos.u.cb, wim->avgSpeedOuter, wim->avgSpeedInner);
1012 /* Once we have the TIME_BYTES right, we can easily convert to TIME_MS */
1013 if (lpTime->wType == TIME_MS)
1014 lpTime->u.ms = MulDiv(lpTime->u.cb, 1000, wim->avgSpeedOuter);
1016 lpTime->wType = TIME_BYTES;
1018 else if (lpTime->wType == TIME_SAMPLES && timepos.wType == TIME_SAMPLES)
1019 lpTime->u.sample = MulDiv(timepos.u.sample, wim->nSamplesPerSecOuter, wim->nSamplesPerSecInner);
1021 /* other time types don't require conversion */
1022 lpTime->u = timepos.u;
1027 static DWORD widGetDevCaps(UINT wDevID, WAVEMAPDATA* wim, LPWAVEINCAPSW lpWaveCaps, DWORD dwParam2)
1029 TRACE("(%04x, %p %p %08lx)\n", wDevID, wim, lpWaveCaps, dwParam2);
1031 /* if opened low driver, forward message */
1032 if (WAVEMAP_IsData(wim))
1033 return waveInGetDevCapsW((UINT)wim->u.in.hInnerWave, lpWaveCaps, dwParam2);
1034 /* else if no drivers, nothing to map so return bad device */
1035 if (waveInGetNumDevs() == 0) {
1036 WARN("bad device id\n");
1037 return MMSYSERR_BADDEVICEID;
1039 /* otherwise, return caps of mapper itself */
1040 if (wDevID == (UINT)-1 || wDevID == (UINT16)-1) {
1042 static const WCHAR init[] = {'W','i','n','e',' ','w','a','v','e',' ','i','n',' ','m','a','p','p','e','r',0};
1045 wic.vDriverVersion = 0x0001;
1046 strcpyW(wic.szPname, init);
1048 WAVE_FORMAT_96M08 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16 |
1049 WAVE_FORMAT_48M08 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 |
1050 WAVE_FORMAT_4M08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S16 |
1051 WAVE_FORMAT_2M08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 |
1052 WAVE_FORMAT_1M08 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16;
1054 memcpy(lpWaveCaps, &wic, min(dwParam2, sizeof(wic)));
1056 return MMSYSERR_NOERROR;
1058 ERR("This shouldn't happen\n");
1059 return MMSYSERR_ERROR;
1062 static DWORD widStop(WAVEMAPDATA* wim)
1064 TRACE("(%p)\n", wim);
1066 return waveInStop(wim->u.in.hInnerWave);
1069 static DWORD widStart(WAVEMAPDATA* wim)
1071 TRACE("(%p)\n", wim);
1073 return waveInStart(wim->u.in.hInnerWave);
1076 static DWORD widReset(WAVEMAPDATA* wim)
1078 TRACE("(%p)\n", wim);
1080 return waveInReset(wim->u.in.hInnerWave);
1083 static DWORD widMapperStatus(WAVEMAPDATA* wim, DWORD flags, LPVOID ptr)
1086 DWORD ret = MMSYSERR_NOTSUPPORTED;
1088 TRACE("(%p %08lx %p)\n", wim, flags, ptr);
1091 case WAVEIN_MAPPER_STATUS_DEVICE:
1092 ret = waveInGetID(wim->u.in.hInnerWave, &id);
1095 case WAVEIN_MAPPER_STATUS_MAPPED:
1096 FIXME("Unsupported yet flag=%ld\n", flags);
1097 *(LPDWORD)ptr = 0; /* FIXME ?? */
1099 case WAVEIN_MAPPER_STATUS_FORMAT:
1100 FIXME("Unsupported flag=%ld\n", flags);
1101 /* ptr points to a WAVEFORMATEX struct - before or after streaming ? */
1102 *(LPDWORD)ptr = 0; /* FIXME ?? */
1105 FIXME("Unsupported flag=%ld\n", flags);
1112 /**************************************************************************
1113 * widMessage (MSACM.@)
1115 DWORD WINAPI WAVEMAP_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
1116 DWORD dwParam1, DWORD dwParam2)
1118 TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n",
1119 wDevID, wMsg, dwUser, dwParam1, dwParam2);
1126 /* FIXME: Pretend this is supported */
1129 case WIDM_OPEN: return widOpen ((LPDWORD)dwUser, (LPWAVEOPENDESC)dwParam1, dwParam2);
1130 case WIDM_CLOSE: return widClose ((WAVEMAPDATA*)dwUser);
1132 case WIDM_ADDBUFFER: return widAddBuffer ((WAVEMAPDATA*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
1133 case WIDM_PREPARE: return widPrepare ((WAVEMAPDATA*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
1134 case WIDM_UNPREPARE: return widUnprepare ((WAVEMAPDATA*)dwUser, (LPWAVEHDR)dwParam1, dwParam2);
1135 case WIDM_GETDEVCAPS: return widGetDevCaps (wDevID, (WAVEMAPDATA*)dwUser, (LPWAVEINCAPSW)dwParam1, dwParam2);
1136 case WIDM_GETNUMDEVS: return 1;
1137 case WIDM_GETPOS: return widGetPosition ((WAVEMAPDATA*)dwUser, (LPMMTIME)dwParam1, dwParam2);
1138 case WIDM_RESET: return widReset ((WAVEMAPDATA*)dwUser);
1139 case WIDM_START: return widStart ((WAVEMAPDATA*)dwUser);
1140 case WIDM_STOP: return widStop ((WAVEMAPDATA*)dwUser);
1141 case WIDM_MAPPER_STATUS: return widMapperStatus ((WAVEMAPDATA*)dwUser, dwParam1, (LPVOID)dwParam2);
1142 /* known but not supported */
1143 case DRV_QUERYDEVICEINTERFACESIZE:
1144 case DRV_QUERYDEVICEINTERFACE:
1145 return MMSYSERR_NOTSUPPORTED;
1147 FIXME("unknown message %u!\n", wMsg);
1149 return MMSYSERR_NOTSUPPORTED;
1152 /*======================================================================*
1154 *======================================================================*/
1156 static struct WINE_WAVEMAP* oss = NULL;
1158 /**************************************************************************
1159 * WAVEMAP_drvOpen [internal]
1161 static DWORD WAVEMAP_drvOpen(LPSTR str)
1163 TRACE("(%p)\n", str);
1168 /* I know, this is ugly, but who cares... */
1169 oss = (struct WINE_WAVEMAP*)1;
1173 /**************************************************************************
1174 * WAVEMAP_drvClose [internal]
1176 static DWORD WAVEMAP_drvClose(DWORD dwDevID)
1178 TRACE("(%08lx)\n", dwDevID);
1187 /**************************************************************************
1188 * DriverProc (MSACM.@)
1190 LONG CALLBACK WAVEMAP_DriverProc(DWORD dwDevID, HDRVR hDriv, DWORD wMsg,
1191 DWORD dwParam1, DWORD dwParam2)
1193 TRACE("(%08lX, %p, %08lX, %08lX, %08lX)\n",
1194 dwDevID, hDriv, wMsg, dwParam1, dwParam2);
1197 case DRV_LOAD: return 1;
1198 case DRV_FREE: return 1;
1199 case DRV_OPEN: return WAVEMAP_drvOpen((LPSTR)dwParam1);
1200 case DRV_CLOSE: return WAVEMAP_drvClose(dwDevID);
1201 case DRV_ENABLE: return 1;
1202 case DRV_DISABLE: return 1;
1203 case DRV_QUERYCONFIGURE: return 1;
1204 case DRV_CONFIGURE: MessageBoxA(0, "WAVEMAP MultiMedia Driver !", "Wave mapper Driver", MB_OK); return 1;
1205 case DRV_INSTALL: return DRVCNF_RESTART;
1206 case DRV_REMOVE: return DRVCNF_RESTART;
1208 return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);