msacm: Fix acmMetrics(ACM_METRIC_DRIVER_PRIORITY) return on error.
[wine/multimedia.git] / dlls / dsound / mixer.c
blobd5817c088ce9de6fdde470b9212e6428f5b90b6b
1 /* DirectSound
3 * Copyright 1998 Marcus Meissner
4 * Copyright 1998 Rob Riggs
5 * Copyright 2000-2002 TransGaming Technologies, Inc.
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
22 #include <assert.h>
23 #include <stdarg.h>
24 #include <math.h> /* Insomnia - pow() function */
26 #define NONAMELESSSTRUCT
27 #define NONAMELESSUNION
28 #include "windef.h"
29 #include "winbase.h"
30 #include "winuser.h"
31 #include "mmsystem.h"
32 #include "winreg.h"
33 #include "winternl.h"
34 #include "wine/debug.h"
35 #include "dsound.h"
36 #include "dsdriver.h"
37 #include "dsound_private.h"
39 WINE_DEFAULT_DEBUG_CHANNEL(dsound);
41 void DSOUND_RecalcVolPan(PDSVOLUMEPAN volpan)
43 double temp;
44 TRACE("(%p)\n",volpan);
46 TRACE("Vol=%ld Pan=%ld\n", volpan->lVolume, volpan->lPan);
47 /* the AmpFactors are expressed in 16.16 fixed point */
48 volpan->dwVolAmpFactor = (ULONG) (pow(2.0, volpan->lVolume / 600.0) * 0xffff);
49 /* FIXME: dwPan{Left|Right}AmpFactor */
51 /* FIXME: use calculated vol and pan ampfactors */
52 temp = (double) (volpan->lVolume - (volpan->lPan > 0 ? volpan->lPan : 0));
53 volpan->dwTotalLeftAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 0xffff);
54 temp = (double) (volpan->lVolume + (volpan->lPan < 0 ? volpan->lPan : 0));
55 volpan->dwTotalRightAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 0xffff);
57 TRACE("left = %lx, right = %lx\n", volpan->dwTotalLeftAmpFactor, volpan->dwTotalRightAmpFactor);
60 void DSOUND_AmpFactorToVolPan(PDSVOLUMEPAN volpan)
62 double left,right;
63 TRACE("(%p)\n",volpan);
65 TRACE("left=%lx, right=%lx\n",volpan->dwTotalLeftAmpFactor,volpan->dwTotalRightAmpFactor);
66 if (volpan->dwTotalLeftAmpFactor==0)
67 left=-10000;
68 else
69 left=600 * log(((double)volpan->dwTotalLeftAmpFactor) / 0xffff) / log(2);
70 if (volpan->dwTotalRightAmpFactor==0)
71 right=-10000;
72 else
73 right=600 * log(((double)volpan->dwTotalRightAmpFactor) / 0xffff) / log(2);
74 if (left<right)
76 volpan->lVolume=right;
77 volpan->dwVolAmpFactor=volpan->dwTotalRightAmpFactor;
79 else
81 volpan->lVolume=left;
82 volpan->dwVolAmpFactor=volpan->dwTotalLeftAmpFactor;
84 if (volpan->lVolume < -10000)
85 volpan->lVolume=-10000;
86 volpan->lPan=right-left;
87 if (volpan->lPan < -10000)
88 volpan->lPan=-10000;
90 TRACE("Vol=%ld Pan=%ld\n", volpan->lVolume, volpan->lPan);
93 void DSOUND_RecalcFormat(IDirectSoundBufferImpl *dsb)
95 TRACE("(%p)\n",dsb);
97 /* calculate the 10ms write lead */
98 dsb->writelead = (dsb->freq / 100) * dsb->pwfx->nBlockAlign;
101 void DSOUND_CheckEvent(IDirectSoundBufferImpl *dsb, int len)
103 int i;
104 DWORD offset;
105 LPDSBPOSITIONNOTIFY event;
106 TRACE("(%p,%d)\n",dsb,len);
108 if (dsb->nrofnotifies == 0)
109 return;
111 TRACE("(%p) buflen = %ld, playpos = %ld, len = %d\n",
112 dsb, dsb->buflen, dsb->playpos, len);
113 for (i = 0; i < dsb->nrofnotifies ; i++) {
114 event = dsb->notifies + i;
115 offset = event->dwOffset;
116 TRACE("checking %d, position %ld, event = %p\n",
117 i, offset, event->hEventNotify);
118 /* DSBPN_OFFSETSTOP has to be the last element. So this is */
119 /* OK. [Inside DirectX, p274] */
120 /* */
121 /* This also means we can't sort the entries by offset, */
122 /* because DSBPN_OFFSETSTOP == -1 */
123 if (offset == DSBPN_OFFSETSTOP) {
124 if (dsb->state == STATE_STOPPED) {
125 SetEvent(event->hEventNotify);
126 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
127 return;
128 } else
129 return;
131 if ((dsb->playpos + len) >= dsb->buflen) {
132 if ((offset < ((dsb->playpos + len) % dsb->buflen)) ||
133 (offset >= dsb->playpos)) {
134 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
135 SetEvent(event->hEventNotify);
137 } else {
138 if ((offset >= dsb->playpos) && (offset < (dsb->playpos + len))) {
139 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
140 SetEvent(event->hEventNotify);
146 /* WAV format info can be found at:
148 * http://www.cwi.nl/ftp/audio/AudioFormats.part2
149 * ftp://ftp.cwi.nl/pub/audio/RIFF-format
151 * Import points to remember:
152 * 8-bit WAV is unsigned
153 * 16-bit WAV is signed
155 /* Use the same formulas as pcmconverter.c */
156 static inline INT16 cvtU8toS16(BYTE b)
158 return (short)((b+(b << 8))-32768);
161 static inline BYTE cvtS16toU8(INT16 s)
163 return (s >> 8) ^ (unsigned char)0x80;
166 static inline void cp_fields(const IDirectSoundBufferImpl *dsb, BYTE *ibuf, BYTE *obuf )
168 DirectSoundDevice * device = dsb->device;
169 INT fl,fr;
171 if (dsb->pwfx->wBitsPerSample == 8) {
172 if (device->pwfx->wBitsPerSample == 8 &&
173 device->pwfx->nChannels == dsb->pwfx->nChannels) {
174 /* avoid needless 8->16->8 conversion */
175 *obuf=*ibuf;
176 if (dsb->pwfx->nChannels==2)
177 *(obuf+1)=*(ibuf+1);
178 return;
180 fl = cvtU8toS16(*ibuf);
181 fr = (dsb->pwfx->nChannels==2 ? cvtU8toS16(*(ibuf + 1)) : fl);
182 } else {
183 fl = *((INT16 *)ibuf);
184 fr = (dsb->pwfx->nChannels==2 ? *(((INT16 *)ibuf) + 1) : fl);
187 if (device->pwfx->nChannels == 2) {
188 if (device->pwfx->wBitsPerSample == 8) {
189 *obuf = cvtS16toU8(fl);
190 *(obuf + 1) = cvtS16toU8(fr);
191 return;
193 if (device->pwfx->wBitsPerSample == 16) {
194 *((INT16 *)obuf) = fl;
195 *(((INT16 *)obuf) + 1) = fr;
196 return;
199 if (device->pwfx->nChannels == 1) {
200 fl = (fl + fr) >> 1;
201 if (device->pwfx->wBitsPerSample == 8) {
202 *obuf = cvtS16toU8(fl);
203 return;
205 if (device->pwfx->wBitsPerSample == 16) {
206 *((INT16 *)obuf) = fl;
207 return;
212 /* Now with PerfectPitch (tm) technology */
213 static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
215 INT i, size, ipos, ilen;
216 BYTE *ibp, *obp;
217 INT iAdvance = dsb->pwfx->nBlockAlign;
218 INT oAdvance = dsb->device->pwfx->nBlockAlign;
220 ibp = dsb->buffer->memory + dsb->buf_mixpos;
221 obp = buf;
223 TRACE("(%p, %p, %p), buf_mixpos=%ld\n", dsb, ibp, obp, dsb->buf_mixpos);
224 /* Check for the best case */
225 if ((dsb->freq == dsb->device->pwfx->nSamplesPerSec) &&
226 (dsb->pwfx->wBitsPerSample == dsb->device->pwfx->wBitsPerSample) &&
227 (dsb->pwfx->nChannels == dsb->device->pwfx->nChannels)) {
228 INT bytesleft = dsb->buflen - dsb->buf_mixpos;
229 TRACE("(%p) Best case\n", dsb);
230 if (len <= bytesleft )
231 CopyMemory(obp, ibp, len);
232 else { /* wrap */
233 CopyMemory(obp, ibp, bytesleft);
234 CopyMemory(obp + bytesleft, dsb->buffer->memory, len - bytesleft);
236 return len;
239 /* Check for same sample rate */
240 if (dsb->freq == dsb->device->pwfx->nSamplesPerSec) {
241 TRACE("(%p) Same sample rate %ld = primary %ld\n", dsb,
242 dsb->freq, dsb->device->pwfx->nSamplesPerSec);
243 ilen = 0;
244 for (i = 0; i < len; i += oAdvance) {
245 cp_fields(dsb, ibp, obp );
246 ibp += iAdvance;
247 ilen += iAdvance;
248 obp += oAdvance;
249 if (ibp >= (BYTE *)(dsb->buffer->memory + dsb->buflen))
250 ibp = dsb->buffer->memory; /* wrap */
252 return (ilen);
255 /* Mix in different sample rates */
256 /* */
257 /* New PerfectPitch(tm) Technology (c) 1998 Rob Riggs */
258 /* Patent Pending :-] */
260 /* Patent enhancements (c) 2000 Ove KÃ¥ven,
261 * TransGaming Technologies Inc. */
263 /* FIXME("(%p) Adjusting frequency: %ld -> %ld (need optimization)\n",
264 dsb, dsb->freq, dsb->device->pwfx->nSamplesPerSec); */
266 size = len / oAdvance;
267 ilen = 0;
268 ipos = dsb->buf_mixpos;
269 for (i = 0; i < size; i++) {
270 cp_fields(dsb, (dsb->buffer->memory + ipos), obp);
271 obp += oAdvance;
272 dsb->freqAcc += dsb->freqAdjust;
273 if (dsb->freqAcc >= (1<<DSOUND_FREQSHIFT)) {
274 ULONG adv = (dsb->freqAcc>>DSOUND_FREQSHIFT) * iAdvance;
275 dsb->freqAcc &= (1<<DSOUND_FREQSHIFT)-1;
276 ipos += adv; ilen += adv;
277 ipos %= dsb->buflen;
280 return ilen;
283 static void DSOUND_MixerVol(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
285 INT i;
286 BYTE *bpc = buf;
287 INT16 *bps = (INT16 *) buf;
289 TRACE("(%p,%p,%d)\n",dsb,buf,len);
290 TRACE("left = %lx, right = %lx\n", dsb->cvolpan.dwTotalLeftAmpFactor,
291 dsb->cvolpan.dwTotalRightAmpFactor);
293 if ((!(dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) || (dsb->cvolpan.lPan == 0)) &&
294 (!(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) || (dsb->cvolpan.lVolume == 0)) &&
295 !(dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
296 return; /* Nothing to do */
298 /* If we end up with some bozo coder using panning or 3D sound */
299 /* with a mono primary buffer, it could sound very weird using */
300 /* this method. Oh well, tough patooties. */
302 switch (dsb->device->pwfx->wBitsPerSample) {
303 case 8:
304 /* 8-bit WAV is unsigned, but we need to operate */
305 /* on signed data for this to work properly */
306 switch (dsb->device->pwfx->nChannels) {
307 case 1:
308 for (i = 0; i < len; i++) {
309 INT val = *bpc - 128;
310 val = (val * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
311 *bpc = val + 128;
312 bpc++;
314 break;
315 case 2:
316 for (i = 0; i < len; i+=2) {
317 INT val = *bpc - 128;
318 val = (val * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
319 *bpc++ = val + 128;
320 val = *bpc - 128;
321 val = (val * dsb->cvolpan.dwTotalRightAmpFactor) >> 16;
322 *bpc = val + 128;
323 bpc++;
325 break;
326 default:
327 FIXME("doesn't support %d channels\n", dsb->device->pwfx->nChannels);
328 break;
330 break;
331 case 16:
332 /* 16-bit WAV is signed -- much better */
333 switch (dsb->device->pwfx->nChannels) {
334 case 1:
335 for (i = 0; i < len; i += 2) {
336 *bps = (*bps * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
337 bps++;
339 break;
340 case 2:
341 for (i = 0; i < len; i += 4) {
342 *bps = (*bps * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
343 bps++;
344 *bps = (*bps * dsb->cvolpan.dwTotalRightAmpFactor) >> 16;
345 bps++;
347 break;
348 default:
349 FIXME("doesn't support %d channels\n", dsb->device->pwfx->nChannels);
350 break;
352 break;
353 default:
354 FIXME("doesn't support %d bit samples\n", dsb->device->pwfx->wBitsPerSample);
355 break;
359 static LPBYTE DSOUND_tmpbuffer(DirectSoundDevice *device, DWORD len)
361 TRACE("(%p,%ld)\n", device, len);
363 if (len > device->tmp_buffer_len) {
364 if (device->tmp_buffer)
365 device->tmp_buffer = HeapReAlloc(GetProcessHeap(), 0, device->tmp_buffer, len);
366 else
367 device->tmp_buffer = HeapAlloc(GetProcessHeap(), 0, len);
369 device->tmp_buffer_len = len;
372 return device->tmp_buffer;
375 static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD fraglen)
377 INT i, len, ilen, field, todo;
378 BYTE *buf, *ibuf;
380 TRACE("(%p,%ld,%ld)\n",dsb,writepos,fraglen);
382 len = fraglen;
383 if (!(dsb->playflags & DSBPLAY_LOOPING)) {
384 int secondary_remainder = dsb->buflen - dsb->buf_mixpos;
385 int adjusted_remainder = MulDiv(dsb->device->pwfx->nAvgBytesPerSec, secondary_remainder, dsb->nAvgBytesPerSec);
386 assert(adjusted_remainder >= 0);
387 TRACE("secondary_remainder = %d, adjusted_remainder = %d, len = %d\n", secondary_remainder, adjusted_remainder, len);
388 if (adjusted_remainder < len) {
389 TRACE("clipping len to remainder of secondary buffer\n");
390 len = adjusted_remainder;
392 if (len == 0)
393 return 0;
396 if (len % dsb->device->pwfx->nBlockAlign) {
397 INT nBlockAlign = dsb->device->pwfx->nBlockAlign;
398 ERR("length not a multiple of block size, len = %d, block size = %d\n", len, nBlockAlign);
399 len = (len / nBlockAlign) * nBlockAlign; /* data alignment */
402 if ((buf = ibuf = DSOUND_tmpbuffer(dsb->device, len)) == NULL)
403 return 0;
405 TRACE("MixInBuffer (%p) len = %d, dest = %ld\n", dsb, len, writepos);
407 ilen = DSOUND_MixerNorm(dsb, ibuf, len);
408 if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
409 (dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) ||
410 (dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
411 DSOUND_MixerVol(dsb, ibuf, len);
413 if (dsb->device->pwfx->wBitsPerSample == 8) {
414 BYTE *obuf = dsb->device->buffer + writepos;
416 if ((writepos + len) <= dsb->device->buflen)
417 todo = len;
418 else
419 todo = dsb->device->buflen - writepos;
421 for (i = 0; i < todo; i++) {
422 /* 8-bit WAV is unsigned */
423 field = (*ibuf++ - 128);
424 field += (*obuf - 128);
425 if (field > 127) field = 127;
426 else if (field < -128) field = -128;
427 *obuf++ = field + 128;
430 if (todo < len) {
431 todo = len - todo;
432 obuf = dsb->device->buffer;
434 for (i = 0; i < todo; i++) {
435 /* 8-bit WAV is unsigned */
436 field = (*ibuf++ - 128);
437 field += (*obuf - 128);
438 if (field > 127) field = 127;
439 else if (field < -128) field = -128;
440 *obuf++ = field + 128;
443 } else {
444 INT16 *ibufs, *obufs;
446 ibufs = (INT16 *) ibuf;
447 obufs = (INT16 *)(dsb->device->buffer + writepos);
449 if ((writepos + len) <= dsb->device->buflen)
450 todo = len / 2;
451 else
452 todo = (dsb->device->buflen - writepos) / 2;
454 for (i = 0; i < todo; i++) {
455 /* 16-bit WAV is signed */
456 field = *ibufs++;
457 field += *obufs;
458 if (field > 32767) field = 32767;
459 else if (field < -32768) field = -32768;
460 *obufs++ = field;
463 if (todo < (len / 2)) {
464 todo = (len / 2) - todo;
465 obufs = (INT16 *)dsb->device->buffer;
467 for (i = 0; i < todo; i++) {
468 /* 16-bit WAV is signed */
469 field = *ibufs++;
470 field += *obufs;
471 if (field > 32767) field = 32767;
472 else if (field < -32768) field = -32768;
473 *obufs++ = field;
478 if (dsb->leadin && (dsb->startpos > dsb->buf_mixpos) && (dsb->startpos <= dsb->buf_mixpos + ilen)) {
479 /* HACK... leadin should be reset when the PLAY position reaches the startpos,
480 * not the MIX position... but if the sound buffer is bigger than our prebuffering
481 * (which must be the case for the streaming buffers that need this hack anyway)
482 * plus DS_HEL_MARGIN or equivalent, then this ought to work anyway. */
483 dsb->leadin = FALSE;
486 dsb->buf_mixpos += ilen;
488 if (dsb->buf_mixpos >= dsb->buflen) {
489 if (dsb->playflags & DSBPLAY_LOOPING) {
490 /* wrap */
491 dsb->buf_mixpos %= dsb->buflen;
492 if (dsb->leadin && (dsb->startpos <= dsb->buf_mixpos))
493 dsb->leadin = FALSE; /* HACK: see above */
494 } else if (dsb->buf_mixpos > dsb->buflen) {
495 ERR("Mixpos (%lu) past buflen (%lu), capping...\n", dsb->buf_mixpos, dsb->buflen);
496 dsb->buf_mixpos = dsb->buflen;
500 return len;
503 static void DSOUND_PhaseCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD len)
505 INT ilen, field;
506 UINT i, todo;
507 BYTE *buf, *ibuf;
509 TRACE("(%p,%ld,%ld)\n",dsb,writepos,len);
511 if (len % dsb->device->pwfx->nBlockAlign) {
512 INT nBlockAlign = dsb->device->pwfx->nBlockAlign;
513 ERR("length not a multiple of block size, len = %ld, block size = %d\n", len, nBlockAlign);
514 len = (len / nBlockAlign) * nBlockAlign; /* data alignment */
517 if ((buf = ibuf = DSOUND_tmpbuffer(dsb->device, len)) == NULL)
518 return;
520 TRACE("PhaseCancel (%p) len = %ld, dest = %ld\n", dsb, len, writepos);
522 ilen = DSOUND_MixerNorm(dsb, ibuf, len);
523 if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
524 (dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) ||
525 (dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
526 DSOUND_MixerVol(dsb, ibuf, len);
528 /* subtract instead of add, to phase out premixed data */
529 if (dsb->device->pwfx->wBitsPerSample == 8) {
530 BYTE *obuf = dsb->device->buffer + writepos;
532 if ((writepos + len) <= dsb->device->buflen)
533 todo = len;
534 else
535 todo = dsb->device->buflen - writepos;
537 for (i = 0; i < todo; i++) {
538 /* 8-bit WAV is unsigned */
539 field = (*obuf - 128);
540 field -= (*ibuf++ - 128);
541 if (field > 127) field = 127;
542 else if (field < -128) field = -128;
543 *obuf++ = field + 128;
546 if (todo < len) {
547 todo = len - todo;
548 obuf = dsb->device->buffer;
550 for (i = 0; i < todo; i++) {
551 /* 8-bit WAV is unsigned */
552 field = (*obuf - 128);
553 field -= (*ibuf++ - 128);
554 if (field > 127) field = 127;
555 else if (field < -128) field = -128;
556 *obuf++ = field + 128;
559 } else {
560 INT16 *ibufs, *obufs;
562 ibufs = (INT16 *) ibuf;
563 obufs = (INT16 *)(dsb->device->buffer + writepos);
565 if ((writepos + len) <= dsb->device->buflen)
566 todo = len / 2;
567 else
568 todo = (dsb->device->buflen - writepos) / 2;
570 for (i = 0; i < todo; i++) {
571 /* 16-bit WAV is signed */
572 field = *obufs;
573 field -= *ibufs++;
574 if (field > 32767) field = 32767;
575 else if (field < -32768) field = -32768;
576 *obufs++ = field;
579 if (todo < (len / 2)) {
580 todo = (len / 2) - todo;
581 obufs = (INT16 *)dsb->device->buffer;
583 for (i = 0; i < todo; i++) {
584 /* 16-bit WAV is signed */
585 field = *obufs;
586 field -= *ibufs++;
587 if (field > 32767) field = 32767;
588 else if (field < -32768) field = -32768;
589 *obufs++ = field;
595 static void DSOUND_MixCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, BOOL cancel)
597 DWORD size, flen, len, npos, nlen;
598 INT iAdvance = dsb->pwfx->nBlockAlign;
599 INT oAdvance = dsb->device->pwfx->nBlockAlign;
600 /* determine amount of premixed data to cancel */
601 DWORD primary_done =
602 ((dsb->primary_mixpos < writepos) ? dsb->device->buflen : 0) +
603 dsb->primary_mixpos - writepos;
605 TRACE("(%p, %ld), buf_mixpos=%ld\n", dsb, writepos, dsb->buf_mixpos);
607 /* backtrack the mix position */
608 size = primary_done / oAdvance;
609 flen = size * dsb->freqAdjust;
610 len = (flen >> DSOUND_FREQSHIFT) * iAdvance;
611 flen &= (1<<DSOUND_FREQSHIFT)-1;
612 while (dsb->freqAcc < flen) {
613 len += iAdvance;
614 dsb->freqAcc += 1<<DSOUND_FREQSHIFT;
616 len %= dsb->buflen;
617 npos = ((dsb->buf_mixpos < len) ? dsb->buflen : 0) +
618 dsb->buf_mixpos - len;
619 if (dsb->leadin && (dsb->startpos > npos) && (dsb->startpos <= npos + len)) {
620 /* stop backtracking at startpos */
621 npos = dsb->startpos;
622 len = ((dsb->buf_mixpos < npos) ? dsb->buflen : 0) +
623 dsb->buf_mixpos - npos;
624 flen = dsb->freqAcc;
625 nlen = len / dsb->pwfx->nBlockAlign;
626 nlen = ((nlen << DSOUND_FREQSHIFT) + flen) / dsb->freqAdjust;
627 nlen *= dsb->device->pwfx->nBlockAlign;
628 writepos =
629 ((dsb->primary_mixpos < nlen) ? dsb->device->buflen : 0) +
630 dsb->primary_mixpos - nlen;
633 dsb->freqAcc -= flen;
634 dsb->buf_mixpos = npos;
635 dsb->primary_mixpos = writepos;
637 TRACE("new buf_mixpos=%ld, primary_mixpos=%ld (len=%ld)\n",
638 dsb->buf_mixpos, dsb->primary_mixpos, len);
640 if (cancel) DSOUND_PhaseCancel(dsb, writepos, len);
643 void DSOUND_MixCancelAt(IDirectSoundBufferImpl *dsb, DWORD buf_writepos)
645 #if 0
646 DWORD i, size, flen, len, npos, nlen;
647 INT iAdvance = dsb->pwfx->nBlockAlign;
648 INT oAdvance = dsb->device->pwfx->nBlockAlign;
649 /* determine amount of premixed data to cancel */
650 DWORD buf_done =
651 ((dsb->buf_mixpos < buf_writepos) ? dsb->buflen : 0) +
652 dsb->buf_mixpos - buf_writepos;
653 #endif
655 WARN("(%p, %ld), buf_mixpos=%ld\n", dsb, buf_writepos, dsb->buf_mixpos);
656 /* since this is not implemented yet, just cancel *ALL* prebuffering for now
657 * (which is faster anyway when there's only a single secondary buffer) */
658 dsb->device->need_remix = TRUE;
661 void DSOUND_ForceRemix(IDirectSoundBufferImpl *dsb)
663 TRACE("(%p)\n",dsb);
664 EnterCriticalSection(&dsb->lock);
665 if (dsb->state == STATE_PLAYING)
666 dsb->device->need_remix = TRUE;
667 LeaveCriticalSection(&dsb->lock);
670 static DWORD DSOUND_MixOne(IDirectSoundBufferImpl *dsb, DWORD playpos, DWORD writepos, DWORD mixlen)
672 DWORD len, slen;
673 /* determine this buffer's write position */
674 DWORD buf_writepos = DSOUND_CalcPlayPosition(dsb, writepos, writepos);
675 /* determine how much already-mixed data exists */
676 DWORD buf_done =
677 ((dsb->buf_mixpos < buf_writepos) ? dsb->buflen : 0) +
678 dsb->buf_mixpos - buf_writepos;
679 DWORD primary_done =
680 ((dsb->primary_mixpos < writepos) ? dsb->device->buflen : 0) +
681 dsb->primary_mixpos - writepos;
682 DWORD adv_done =
683 ((dsb->device->mixpos < writepos) ? dsb->device->buflen : 0) +
684 dsb->device->mixpos - writepos;
685 DWORD played =
686 ((buf_writepos < dsb->playpos) ? dsb->buflen : 0) +
687 buf_writepos - dsb->playpos;
688 DWORD buf_left = dsb->buflen - buf_writepos;
689 int still_behind;
691 TRACE("(%p,%ld,%ld,%ld)\n",dsb,playpos,writepos,mixlen);
692 TRACE("buf_writepos=%ld, primary_writepos=%ld\n", buf_writepos, writepos);
693 TRACE("buf_done=%ld, primary_done=%ld\n", buf_done, primary_done);
694 TRACE("buf_mixpos=%ld, primary_mixpos=%ld, mixlen=%ld\n", dsb->buf_mixpos, dsb->primary_mixpos,
695 mixlen);
696 TRACE("looping=%ld, startpos=%ld, leadin=%ld\n", dsb->playflags, dsb->startpos, dsb->leadin);
698 /* check for notification positions */
699 if (dsb->dsbd.dwFlags & DSBCAPS_CTRLPOSITIONNOTIFY &&
700 dsb->state != STATE_STARTING) {
701 DSOUND_CheckEvent(dsb, played);
704 /* save write position for non-GETCURRENTPOSITION2... */
705 dsb->playpos = buf_writepos;
707 /* check whether CalcPlayPosition detected a mixing underrun */
708 if ((buf_done == 0) && (dsb->primary_mixpos != writepos)) {
709 /* it did, but did we have more to play? */
710 if ((dsb->playflags & DSBPLAY_LOOPING) ||
711 (dsb->buf_mixpos < dsb->buflen)) {
712 /* yes, have to recover */
713 ERR("underrun on sound buffer %p\n", dsb);
714 TRACE("recovering from underrun: primary_mixpos=%ld\n", writepos);
716 dsb->primary_mixpos = writepos;
717 primary_done = 0;
719 /* determine how far ahead we should mix */
720 if (((dsb->playflags & DSBPLAY_LOOPING) ||
721 (dsb->leadin && (dsb->probably_valid_to != 0))) &&
722 !(dsb->dsbd.dwFlags & DSBCAPS_STATIC)) {
723 /* if this is a streaming buffer, it typically means that
724 * we should defer mixing past probably_valid_to as long
725 * as we can, to avoid unnecessary remixing */
726 /* the heavy-looking calculations shouldn't be that bad,
727 * as any game isn't likely to be have more than 1 or 2
728 * streaming buffers in use at any time anyway... */
729 DWORD probably_valid_left =
730 (dsb->probably_valid_to == (DWORD)-1) ? dsb->buflen :
731 ((dsb->probably_valid_to < buf_writepos) ? dsb->buflen : 0) +
732 dsb->probably_valid_to - buf_writepos;
733 /* check for leadin condition */
734 if ((probably_valid_left == 0) &&
735 (dsb->probably_valid_to == dsb->startpos) &&
736 dsb->leadin)
737 probably_valid_left = dsb->buflen;
738 TRACE("streaming buffer probably_valid_to=%ld, probably_valid_left=%ld\n",
739 dsb->probably_valid_to, probably_valid_left);
740 /* check whether the app's time is already up */
741 if (probably_valid_left < dsb->writelead) {
742 WARN("probably_valid_to now within writelead, possible streaming underrun\n");
743 /* once we pass the point of no return,
744 * no reason to hold back anymore */
745 dsb->probably_valid_to = (DWORD)-1;
746 /* we just have to go ahead and mix what we have,
747 * there's no telling what the app is thinking anyway */
748 } else {
749 /* adjust for our frequency and our sample size */
750 probably_valid_left = MulDiv(probably_valid_left,
751 1 << DSOUND_FREQSHIFT,
752 dsb->pwfx->nBlockAlign * dsb->freqAdjust) *
753 dsb->device->pwfx->nBlockAlign;
754 /* check whether to clip mix_len */
755 if (probably_valid_left < mixlen) {
756 TRACE("clipping to probably_valid_left=%ld\n", probably_valid_left);
757 mixlen = probably_valid_left;
761 /* cut mixlen with what's already been mixed */
762 if (mixlen < primary_done) {
763 /* huh? and still CalcPlayPosition didn't
764 * detect an underrun? */
765 FIXME("problem with underrun detection (mixlen=%ld < primary_done=%ld)\n", mixlen, primary_done);
766 return 0;
768 len = mixlen - primary_done;
769 TRACE("remaining mixlen=%ld\n", len);
771 if (len < dsb->device->fraglen) {
772 /* smaller than a fragment, wait until it gets larger
773 * before we take the mixing overhead */
774 TRACE("mixlen not worth it, deferring mixing\n");
775 still_behind = 1;
776 goto post_mix;
779 /* ok, we know how much to mix, let's go */
780 still_behind = (adv_done > primary_done);
781 while (len) {
782 slen = dsb->device->buflen - dsb->primary_mixpos;
783 if (slen > len) slen = len;
784 slen = DSOUND_MixInBuffer(dsb, dsb->primary_mixpos, slen);
786 if ((dsb->primary_mixpos < dsb->device->mixpos) &&
787 (dsb->primary_mixpos + slen >= dsb->device->mixpos))
788 still_behind = FALSE;
790 dsb->primary_mixpos += slen; len -= slen;
791 dsb->primary_mixpos %= dsb->device->buflen;
793 if ((dsb->state == STATE_STOPPED) || !slen) break;
795 TRACE("new primary_mixpos=%ld, primary_advbase=%ld\n", dsb->primary_mixpos, dsb->device->mixpos);
796 TRACE("mixed data len=%ld, still_behind=%d\n", mixlen-len, still_behind);
798 post_mix:
799 /* check if buffer should be considered complete */
800 if (buf_left < dsb->writelead &&
801 !(dsb->playflags & DSBPLAY_LOOPING)) {
802 dsb->state = STATE_STOPPED;
803 dsb->playpos = 0;
804 dsb->last_playpos = 0;
805 dsb->buf_mixpos = 0;
806 dsb->leadin = FALSE;
807 dsb->need_remix = FALSE;
808 DSOUND_CheckEvent(dsb, buf_left);
811 /* return how far we think the primary buffer can
812 * advance its underrun detector...*/
813 if (still_behind) return 0;
814 if ((mixlen - len) < primary_done) return 0;
815 slen = ((dsb->primary_mixpos < dsb->device->mixpos) ?
816 dsb->device->buflen : 0) + dsb->primary_mixpos -
817 dsb->device->mixpos;
818 if (slen > mixlen) {
819 /* the primary_done and still_behind checks above should have worked */
820 FIXME("problem with advancement calculation (advlen=%ld > mixlen=%ld)\n", slen, mixlen);
821 slen = 0;
823 return slen;
826 static DWORD DSOUND_MixToPrimary(DirectSoundDevice *device, DWORD playpos, DWORD writepos, DWORD mixlen, BOOL recover)
828 INT i, len, maxlen = 0;
829 IDirectSoundBufferImpl *dsb;
831 TRACE("(%ld,%ld,%ld,%d)\n", playpos, writepos, mixlen, recover);
832 for (i = 0; i < device->nrofbuffers; i++) {
833 dsb = device->buffers[i];
835 if (dsb->buflen && dsb->state && !dsb->hwbuf) {
836 TRACE("Checking %p, mixlen=%ld\n", dsb, mixlen);
837 EnterCriticalSection(&(dsb->lock));
838 if (dsb->state == STATE_STOPPING) {
839 DSOUND_MixCancel(dsb, writepos, TRUE);
840 dsb->state = STATE_STOPPED;
841 DSOUND_CheckEvent(dsb, 0);
842 } else {
843 if ((dsb->state == STATE_STARTING) || recover) {
844 dsb->primary_mixpos = writepos;
845 dsb->cvolpan = dsb->volpan;
846 dsb->need_remix = FALSE;
848 else if (dsb->need_remix) {
849 DSOUND_MixCancel(dsb, writepos, TRUE);
850 dsb->cvolpan = dsb->volpan;
851 dsb->need_remix = FALSE;
853 len = DSOUND_MixOne(dsb, playpos, writepos, mixlen);
854 if (dsb->state == STATE_STARTING)
855 dsb->state = STATE_PLAYING;
856 maxlen = (len > maxlen) ? len : maxlen;
858 LeaveCriticalSection(&(dsb->lock));
862 return maxlen;
865 static void DSOUND_MixReset(DirectSoundDevice *device, DWORD writepos)
867 INT i;
868 IDirectSoundBufferImpl *dsb;
869 int nfiller;
871 TRACE("(%p,%ld)\n", device, writepos);
873 /* the sound of silence */
874 nfiller = device->pwfx->wBitsPerSample == 8 ? 128 : 0;
876 /* reset all buffer mix positions */
877 for (i = 0; i < device->nrofbuffers; i++) {
878 dsb = device->buffers[i];
880 if (dsb->buflen && dsb->state && !dsb->hwbuf) {
881 TRACE("Resetting %p\n", dsb);
882 EnterCriticalSection(&(dsb->lock));
883 if (dsb->state == STATE_STOPPING) {
884 dsb->state = STATE_STOPPED;
886 else if (dsb->state == STATE_STARTING) {
887 /* nothing */
888 } else {
889 DSOUND_MixCancel(dsb, writepos, FALSE);
890 dsb->cvolpan = dsb->volpan;
891 dsb->need_remix = FALSE;
893 LeaveCriticalSection(&(dsb->lock));
897 /* wipe out premixed data */
898 if (device->mixpos < writepos) {
899 FillMemory(device->buffer + writepos, device->buflen - writepos, nfiller);
900 FillMemory(device->buffer, device->mixpos, nfiller);
901 } else {
902 FillMemory(device->buffer + writepos, device->mixpos - writepos, nfiller);
905 /* reset primary mix position */
906 device->mixpos = writepos;
909 static void DSOUND_CheckReset(DirectSoundDevice *device, DWORD writepos)
911 TRACE("(%p,%ld)\n",device,writepos);
912 if (device->need_remix) {
913 DSOUND_MixReset(device, writepos);
914 device->need_remix = FALSE;
915 /* maximize Half-Life performance */
916 device->prebuf = ds_snd_queue_min;
917 device->precount = 0;
918 } else {
919 device->precount++;
920 if (device->precount >= 4) {
921 if (device->prebuf < ds_snd_queue_max)
922 device->prebuf++;
923 device->precount = 0;
926 TRACE("premix adjust: %d\n", device->prebuf);
929 void DSOUND_WaveQueue(DirectSoundDevice *device, DWORD mixq)
931 TRACE("(%p,%ld)\n", device, mixq);
932 if (mixq + device->pwqueue > ds_hel_queue) mixq = ds_hel_queue - device->pwqueue;
933 TRACE("queueing %ld buffers, starting at %d\n", mixq, device->pwwrite);
934 for (; mixq; mixq--) {
935 waveOutWrite(device->hwo, device->pwave[device->pwwrite], sizeof(WAVEHDR));
936 device->pwwrite++;
937 if (device->pwwrite >= DS_HEL_FRAGS) device->pwwrite = 0;
938 device->pwqueue++;
942 /* #define SYNC_CALLBACK */
944 void DSOUND_PerformMix(DirectSoundDevice *device)
946 int nfiller;
947 BOOL forced;
948 HRESULT hres;
950 TRACE("(%p)\n", device);
952 /* the sound of silence */
953 nfiller = device->pwfx->wBitsPerSample == 8 ? 128 : 0;
955 /* whether the primary is forced to play even without secondary buffers */
956 forced = ((device->state == STATE_PLAYING) || (device->state == STATE_STARTING));
958 if (device->priolevel != DSSCL_WRITEPRIMARY) {
959 BOOL paused = ((device->state == STATE_STOPPED) || (device->state == STATE_STARTING));
960 /* FIXME: document variables */
961 DWORD playpos, writepos, inq, maxq, frag;
962 if (device->hwbuf) {
963 hres = IDsDriverBuffer_GetPosition(device->hwbuf, &playpos, &writepos);
964 if (hres) {
965 WARN("IDsDriverBuffer_GetPosition failed\n");
966 return;
968 /* Well, we *could* do Just-In-Time mixing using the writepos,
969 * but that's a little bit ambitious and unnecessary... */
970 /* rather add our safety margin to the writepos, if we're playing */
971 if (!paused) {
972 writepos += device->writelead;
973 writepos %= device->buflen;
974 } else writepos = playpos;
975 } else {
976 playpos = device->pwplay * device->fraglen;
977 writepos = playpos;
978 if (!paused) {
979 writepos += ds_hel_margin * device->fraglen;
980 writepos %= device->buflen;
983 TRACE("primary playpos=%ld, writepos=%ld, clrpos=%ld, mixpos=%ld, buflen=%ld\n",
984 playpos,writepos,device->playpos,device->mixpos,device->buflen);
985 assert(device->playpos < device->buflen);
986 /* wipe out just-played sound data */
987 if (playpos < device->playpos) {
988 FillMemory(device->buffer + device->playpos, device->buflen - device->playpos, nfiller);
989 FillMemory(device->buffer, playpos, nfiller);
990 } else {
991 FillMemory(device->buffer + device->playpos, playpos - device->playpos, nfiller);
993 device->playpos = playpos;
995 EnterCriticalSection(&(device->mixlock));
997 /* reset mixing if necessary */
998 DSOUND_CheckReset(device, writepos);
1000 /* check how much prebuffering is left */
1001 inq = device->mixpos;
1002 if (inq < writepos)
1003 inq += device->buflen;
1004 inq -= writepos;
1006 /* find the maximum we can prebuffer */
1007 if (!paused) {
1008 maxq = playpos;
1009 if (maxq < writepos)
1010 maxq += device->buflen;
1011 maxq -= writepos;
1012 } else maxq = device->buflen;
1014 /* clip maxq to device->prebuf */
1015 frag = device->prebuf * device->fraglen;
1016 if (maxq > frag) maxq = frag;
1018 /* check for consistency */
1019 if (inq > maxq) {
1020 /* the playback position must have passed our last
1021 * mixed position, i.e. it's an underrun, or we have
1022 * nothing more to play */
1023 TRACE("reached end of mixed data (inq=%ld, maxq=%ld)\n", inq, maxq);
1024 inq = 0;
1025 /* stop the playback now, to allow buffers to refill */
1026 if (device->state == STATE_PLAYING) {
1027 device->state = STATE_STARTING;
1029 else if (device->state == STATE_STOPPING) {
1030 device->state = STATE_STOPPED;
1032 else {
1033 /* how can we have an underrun if we aren't playing? */
1034 WARN("unexpected primary state (%ld)\n", device->state);
1036 #ifdef SYNC_CALLBACK
1037 /* DSOUND_callback may need this lock */
1038 LeaveCriticalSection(&(device->mixlock));
1039 #endif
1040 if (DSOUND_PrimaryStop(device) != DS_OK)
1041 WARN("DSOUND_PrimaryStop failed\n");
1042 #ifdef SYNC_CALLBACK
1043 EnterCriticalSection(&(device->mixlock));
1044 #endif
1045 if (device->hwbuf) {
1046 /* the Stop is supposed to reset play position to beginning of buffer */
1047 /* unfortunately, OSS is not able to do so, so get current pointer */
1048 hres = IDsDriverBuffer_GetPosition(device->hwbuf, &playpos, NULL);
1049 if (hres) {
1050 LeaveCriticalSection(&(device->mixlock));
1051 WARN("IDsDriverBuffer_GetPosition failed\n");
1052 return;
1054 } else {
1055 playpos = device->pwplay * device->fraglen;
1057 writepos = playpos;
1058 device->playpos = playpos;
1059 device->mixpos = writepos;
1060 inq = 0;
1061 maxq = device->buflen;
1062 if (maxq > frag) maxq = frag;
1063 FillMemory(device->buffer, device->buflen, nfiller);
1064 paused = TRUE;
1067 /* do the mixing */
1068 frag = DSOUND_MixToPrimary(device, playpos, writepos, maxq, paused);
1069 if (forced) frag = maxq - inq;
1070 device->mixpos += frag;
1071 device->mixpos %= device->buflen;
1073 if (frag) {
1074 /* buffers have been filled, restart playback */
1075 if (device->state == STATE_STARTING) {
1076 device->state = STATE_PLAYING;
1078 else if (device->state == STATE_STOPPED) {
1079 /* the dsound is supposed to play if there's something to play
1080 * even if it is reported as stopped, so don't let this confuse you */
1081 device->state = STATE_STOPPING;
1083 LeaveCriticalSection(&(device->mixlock));
1084 if (paused) {
1085 if (DSOUND_PrimaryPlay(device) != DS_OK)
1086 WARN("DSOUND_PrimaryPlay failed\n");
1087 else
1088 TRACE("starting playback\n");
1091 else
1092 LeaveCriticalSection(&(device->mixlock));
1093 } else {
1094 /* in the DSSCL_WRITEPRIMARY mode, the app is totally in charge... */
1095 if (device->state == STATE_STARTING) {
1096 if (DSOUND_PrimaryPlay(device) != DS_OK)
1097 WARN("DSOUND_PrimaryPlay failed\n");
1098 else
1099 device->state = STATE_PLAYING;
1101 else if (device->state == STATE_STOPPING) {
1102 if (DSOUND_PrimaryStop(device) != DS_OK)
1103 WARN("DSOUND_PrimaryStop failed\n");
1104 else
1105 device->state = STATE_STOPPED;
1110 void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
1112 DirectSoundDevice * device = (DirectSoundDevice*)dwUser;
1113 DWORD start_time = GetTickCount();
1114 DWORD end_time;
1115 TRACE("(%d,%d,0x%lx,0x%lx,0x%lx)\n",timerID,msg,dwUser,dw1,dw2);
1116 TRACE("entering at %ld\n", start_time);
1118 if (DSOUND_renderer[device->drvdesc.dnDevNode] != device) {
1119 ERR("dsound died without killing us?\n");
1120 timeKillEvent(timerID);
1121 timeEndPeriod(DS_TIME_RES);
1122 return;
1125 RtlAcquireResourceShared(&(device->buffer_list_lock), TRUE);
1127 if (device->ref)
1128 DSOUND_PerformMix(device);
1130 RtlReleaseResource(&(device->buffer_list_lock));
1132 end_time = GetTickCount();
1133 TRACE("completed processing at %ld, duration = %ld\n", end_time, end_time - start_time);
1136 void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
1138 DirectSoundDevice * device = (DirectSoundDevice*)dwUser;
1139 TRACE("(%p,%x,%lx,%lx,%lx)\n",hwo,msg,dwUser,dw1,dw2);
1140 TRACE("entering at %ld, msg=%08x(%s)\n", GetTickCount(), msg,
1141 msg==MM_WOM_DONE ? "MM_WOM_DONE" : msg==MM_WOM_CLOSE ? "MM_WOM_CLOSE" :
1142 msg==MM_WOM_OPEN ? "MM_WOM_OPEN" : "UNKNOWN");
1143 if (msg == MM_WOM_DONE) {
1144 DWORD inq, mixq, fraglen, buflen, pwplay, playpos, mixpos;
1145 if (device->pwqueue == (DWORD)-1) {
1146 TRACE("completed due to reset\n");
1147 return;
1149 /* it could be a bad idea to enter critical section here... if there's lock contention,
1150 * the resulting scheduling delays might obstruct the winmm player thread */
1151 #ifdef SYNC_CALLBACK
1152 EnterCriticalSection(&(device->mixlock));
1153 #endif
1154 /* retrieve current values */
1155 fraglen = device->fraglen;
1156 buflen = device->buflen;
1157 pwplay = device->pwplay;
1158 playpos = pwplay * fraglen;
1159 mixpos = device->mixpos;
1160 /* check remaining mixed data */
1161 inq = ((mixpos < playpos) ? buflen : 0) + mixpos - playpos;
1162 mixq = inq / fraglen;
1163 if ((inq - (mixq * fraglen)) > 0) mixq++;
1164 /* complete the playing buffer */
1165 TRACE("done playing primary pos=%ld\n", playpos);
1166 pwplay++;
1167 if (pwplay >= DS_HEL_FRAGS) pwplay = 0;
1168 /* write new values */
1169 device->pwplay = pwplay;
1170 device->pwqueue--;
1171 /* queue new buffer if we have data for it */
1172 if (inq>1) DSOUND_WaveQueue(device, inq-1);
1173 #ifdef SYNC_CALLBACK
1174 LeaveCriticalSection(&(device->mixlock));
1175 #endif
1177 TRACE("completed\n");