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
26 #include <sys/types.h>
27 #include <sys/fcntl.h>
33 #include <math.h> /* Insomnia - pow() function */
44 #include "wine/windef16.h"
45 #include "wine/debug.h"
48 #include "dsound_private.h"
50 WINE_DEFAULT_DEBUG_CHANNEL(dsound);
52 void DSOUND_RecalcVolPan(PDSVOLUMEPAN volpan)
55 TRACE("(%p)\n",volpan);
57 TRACE("Vol=%ld Pan=%ld\n", volpan->lVolume, volpan->lPan);
58 /* the AmpFactors are expressed in 16.16 fixed point */
59 volpan->dwVolAmpFactor = (ULONG) (pow(2.0, volpan->lVolume / 600.0) * 0xffff);
60 /* FIXME: dwPan{Left|Right}AmpFactor */
62 /* FIXME: use calculated vol and pan ampfactors */
63 temp = (double) (volpan->lVolume - (volpan->lPan > 0 ? volpan->lPan : 0));
64 volpan->dwTotalLeftAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 0xffff);
65 temp = (double) (volpan->lVolume + (volpan->lPan < 0 ? volpan->lPan : 0));
66 volpan->dwTotalRightAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 0xffff);
68 TRACE("left = %lx, right = %lx\n", volpan->dwTotalLeftAmpFactor, volpan->dwTotalRightAmpFactor);
71 void DSOUND_AmpFactorToVolPan(PDSVOLUMEPAN volpan)
74 TRACE("(%p)\n",volpan);
76 TRACE("left=%lx, right=%lx\n",volpan->dwTotalLeftAmpFactor,volpan->dwTotalRightAmpFactor);
77 if (volpan->dwTotalLeftAmpFactor==0)
80 left=600 * log(((double)volpan->dwTotalLeftAmpFactor) / 0xffff) / log(2);
81 if (volpan->dwTotalRightAmpFactor==0)
84 right=600 * log(((double)volpan->dwTotalRightAmpFactor) / 0xffff) / log(2);
87 volpan->lVolume=right;
88 volpan->dwVolAmpFactor=volpan->dwTotalRightAmpFactor;
93 volpan->dwVolAmpFactor=volpan->dwTotalLeftAmpFactor;
95 if (volpan->lVolume < -10000)
96 volpan->lVolume=-10000;
97 volpan->lPan=right-left;
98 if (volpan->lPan < -10000)
101 TRACE("Vol=%ld Pan=%ld\n", volpan->lVolume, volpan->lPan);
104 void DSOUND_RecalcFormat(IDirectSoundBufferImpl *dsb)
109 sw = dsb->wfx.nChannels * (dsb->wfx.wBitsPerSample / 8);
110 /* calculate the 10ms write lead */
111 dsb->writelead = (dsb->freq / 100) * sw;
114 void DSOUND_CheckEvent(IDirectSoundBufferImpl *dsb, int len)
118 LPDSBPOSITIONNOTIFY event;
119 TRACE("(%p,%d)\n",dsb,len);
121 if (dsb->nrofnotifies == 0)
124 TRACE("(%p) buflen = %ld, playpos = %ld, len = %d\n",
125 dsb, dsb->buflen, dsb->playpos, len);
126 for (i = 0; i < dsb->nrofnotifies ; i++) {
127 event = dsb->notifies + i;
128 offset = event->dwOffset;
129 TRACE("checking %d, position %ld, event = %p\n",
130 i, offset, event->hEventNotify);
131 /* DSBPN_OFFSETSTOP has to be the last element. So this is */
132 /* OK. [Inside DirectX, p274] */
134 /* This also means we can't sort the entries by offset, */
135 /* because DSBPN_OFFSETSTOP == -1 */
136 if (offset == DSBPN_OFFSETSTOP) {
137 if (dsb->state == STATE_STOPPED) {
138 SetEvent(event->hEventNotify);
139 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
144 if ((dsb->playpos + len) >= dsb->buflen) {
145 if ((offset < ((dsb->playpos + len) % dsb->buflen)) ||
146 (offset >= dsb->playpos)) {
147 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
148 SetEvent(event->hEventNotify);
151 if ((offset >= dsb->playpos) && (offset < (dsb->playpos + len))) {
152 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
153 SetEvent(event->hEventNotify);
159 /* WAV format info can be found at:
161 * http://www.cwi.nl/ftp/audio/AudioFormats.part2
162 * ftp://ftp.cwi.nl/pub/audio/RIFF-format
164 * Import points to remember:
165 * 8-bit WAV is unsigned
166 * 16-bit WAV is signed
168 /* Use the same formulas as pcmconverter.c */
169 static inline INT16 cvtU8toS16(BYTE b)
171 return (short)((b+(b << 8))-32768);
174 static inline BYTE cvtS16toU8(INT16 s)
176 return (s >> 8) ^ (unsigned char)0x80;
179 static inline void cp_fields(const IDirectSoundBufferImpl *dsb, BYTE *ibuf, BYTE *obuf )
183 if (dsb->wfx.wBitsPerSample == 8) {
184 if (dsb->dsound->wfx.wBitsPerSample == 8 &&
185 dsb->dsound->wfx.nChannels == dsb->wfx.nChannels) {
186 /* avoid needless 8->16->8 conversion */
188 if (dsb->wfx.nChannels==2)
192 fl = cvtU8toS16(*ibuf);
193 fr = (dsb->wfx.nChannels==2 ? cvtU8toS16(*(ibuf + 1)) : fl);
195 fl = *((INT16 *)ibuf);
196 fr = (dsb->wfx.nChannels==2 ? *(((INT16 *)ibuf) + 1) : fl);
199 if (dsb->dsound->wfx.nChannels == 2) {
200 if (dsb->dsound->wfx.wBitsPerSample == 8) {
201 *obuf = cvtS16toU8(fl);
202 *(obuf + 1) = cvtS16toU8(fr);
205 if (dsb->dsound->wfx.wBitsPerSample == 16) {
206 *((INT16 *)obuf) = fl;
207 *(((INT16 *)obuf) + 1) = fr;
211 if (dsb->dsound->wfx.nChannels == 1) {
213 if (dsb->dsound->wfx.wBitsPerSample == 8) {
214 *obuf = cvtS16toU8(fl);
217 if (dsb->dsound->wfx.wBitsPerSample == 16) {
218 *((INT16 *)obuf) = fl;
224 /* Now with PerfectPitch (tm) technology */
225 static INT DSOUND_MixerNorm(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
227 INT i, size, ipos, ilen;
229 INT iAdvance = dsb->wfx.nBlockAlign;
230 INT oAdvance = dsb->dsound->wfx.nBlockAlign;
232 ibp = dsb->buffer->memory + dsb->buf_mixpos;
235 TRACE("(%p, %p, %p), buf_mixpos=%ld\n", dsb, ibp, obp, dsb->buf_mixpos);
236 /* Check for the best case */
237 if ((dsb->freq == dsb->dsound->wfx.nSamplesPerSec) &&
238 (dsb->wfx.wBitsPerSample == dsb->dsound->wfx.wBitsPerSample) &&
239 (dsb->wfx.nChannels == dsb->dsound->wfx.nChannels)) {
240 DWORD bytesleft = dsb->buflen - dsb->buf_mixpos;
241 TRACE("(%p) Best case\n", dsb);
242 if (len <= bytesleft )
243 memcpy(obp, ibp, len);
245 memcpy(obp, ibp, bytesleft );
246 memcpy(obp + bytesleft, dsb->buffer->memory, len - bytesleft);
251 /* Check for same sample rate */
252 if (dsb->freq == dsb->dsound->wfx.nSamplesPerSec) {
253 TRACE("(%p) Same sample rate %ld = primary %ld\n", dsb,
254 dsb->freq, dsb->dsound->wfx.nSamplesPerSec);
256 for (i = 0; i < len; i += oAdvance) {
257 cp_fields(dsb, ibp, obp );
261 if (ibp >= (BYTE *)(dsb->buffer->memory + dsb->buflen))
262 ibp = dsb->buffer->memory; /* wrap */
267 /* Mix in different sample rates */
269 /* New PerfectPitch(tm) Technology (c) 1998 Rob Riggs */
270 /* Patent Pending :-] */
272 /* Patent enhancements (c) 2000 Ove KÃ¥ven,
273 * TransGaming Technologies Inc. */
275 /* FIXME("(%p) Adjusting frequency: %ld -> %ld (need optimization)\n",
276 dsb, dsb->freq, dsb->dsound->wfx.nSamplesPerSec); */
278 size = len / oAdvance;
280 ipos = dsb->buf_mixpos;
281 for (i = 0; i < size; i++) {
282 cp_fields(dsb, (dsb->buffer->memory + ipos), obp);
284 dsb->freqAcc += dsb->freqAdjust;
285 if (dsb->freqAcc >= (1<<DSOUND_FREQSHIFT)) {
286 ULONG adv = (dsb->freqAcc>>DSOUND_FREQSHIFT) * iAdvance;
287 dsb->freqAcc &= (1<<DSOUND_FREQSHIFT)-1;
288 ipos += adv; ilen += adv;
289 while (ipos >= dsb->buflen)
296 static void DSOUND_MixerVol(IDirectSoundBufferImpl *dsb, BYTE *buf, INT len)
300 INT16 *bps = (INT16 *) buf;
302 TRACE("(%p,%p,%d)\n",dsb,buf,len);
303 TRACE("left = %lx, right = %lx\n", dsb->cvolpan.dwTotalLeftAmpFactor,
304 dsb->cvolpan.dwTotalRightAmpFactor);
306 if ((!(dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) || (dsb->cvolpan.lPan == 0)) &&
307 (!(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) || (dsb->cvolpan.lVolume == 0)) &&
308 !(dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
309 return; /* Nothing to do */
311 /* If we end up with some bozo coder using panning or 3D sound */
312 /* with a mono primary buffer, it could sound very weird using */
313 /* this method. Oh well, tough patooties. */
315 switch (dsb->dsound->wfx.wBitsPerSample) {
317 /* 8-bit WAV is unsigned, but we need to operate */
318 /* on signed data for this to work properly */
319 switch (dsb->dsound->wfx.nChannels) {
321 for (i = 0; i < len; i++) {
322 INT val = *bpc - 128;
323 val = (val * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
329 for (i = 0; i < len; i+=2) {
330 INT val = *bpc - 128;
331 val = (val * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
334 val = (val * dsb->cvolpan.dwTotalRightAmpFactor) >> 16;
340 FIXME("doesn't support %d channels\n", dsb->dsound->wfx.nChannels);
345 /* 16-bit WAV is signed -- much better */
346 switch (dsb->dsound->wfx.nChannels) {
348 for (i = 0; i < len; i += 2) {
349 *bps = (*bps * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
354 for (i = 0; i < len; i += 4) {
355 *bps = (*bps * dsb->cvolpan.dwTotalLeftAmpFactor) >> 16;
357 *bps = (*bps * dsb->cvolpan.dwTotalRightAmpFactor) >> 16;
362 FIXME("doesn't support %d channels\n", dsb->dsound->wfx.nChannels);
367 FIXME("doesn't support %d bit samples\n", dsb->dsound->wfx.wBitsPerSample);
372 static void *tmp_buffer;
373 static size_t tmp_buffer_len = 0;
375 static void *DSOUND_tmpbuffer(size_t len)
377 if (len>tmp_buffer_len) {
378 void *new_buffer = realloc(tmp_buffer, len);
380 tmp_buffer = new_buffer;
381 tmp_buffer_len = len;
388 static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD fraglen)
390 INT i, len, ilen, temp, field, nBlockAlign;
391 INT advance = dsb->dsound->wfx.wBitsPerSample >> 3;
392 BYTE *buf, *ibuf, *obuf;
393 INT16 *ibufs, *obufs;
395 TRACE("(%p,%ld,%ld)\n",dsb,writepos,fraglen);
398 if (!(dsb->playflags & DSBPLAY_LOOPING)) {
399 temp = MulDiv(dsb->dsound->wfx.nAvgBytesPerSec, dsb->buflen,
400 dsb->nAvgBytesPerSec) -
401 MulDiv(dsb->dsound->wfx.nAvgBytesPerSec, dsb->buf_mixpos,
402 dsb->nAvgBytesPerSec);
403 len = (len > temp) ? temp : len;
405 nBlockAlign = dsb->dsound->wfx.nBlockAlign;
406 len = len / nBlockAlign * nBlockAlign; /* data alignment */
409 /* This should only happen if we aren't looping and temp < nBlockAlign */
413 /* Been seeing segfaults in malloc() for some reason... */
414 TRACE("allocating buffer (size = %d)\n", len);
415 if ((buf = ibuf = (BYTE *) DSOUND_tmpbuffer(len)) == NULL)
418 TRACE("MixInBuffer (%p) len = %d, dest = %ld\n", dsb, len, writepos);
420 ilen = DSOUND_MixerNorm(dsb, ibuf, len);
421 if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
422 (dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) ||
423 (dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
424 DSOUND_MixerVol(dsb, ibuf, len);
426 obuf = dsb->dsound->buffer + writepos;
427 for (i = 0; i < len; i += advance) {
428 obufs = (INT16 *) obuf;
429 ibufs = (INT16 *) ibuf;
430 if (dsb->dsound->wfx.wBitsPerSample == 8) {
431 /* 8-bit WAV is unsigned */
432 field = (*ibuf - 128);
433 field += (*obuf - 128);
434 field = field > 127 ? 127 : field;
435 field = field < -128 ? -128 : field;
438 /* 16-bit WAV is signed */
441 field = field > 32767 ? 32767 : field;
442 field = field < -32768 ? -32768 : field;
447 if (obuf >= (BYTE *)(dsb->dsound->buffer + dsb->dsound->buflen))
448 obuf = dsb->dsound->buffer;
452 if (dsb->leadin && (dsb->startpos > dsb->buf_mixpos) && (dsb->startpos <= dsb->buf_mixpos + ilen)) {
453 /* HACK... leadin should be reset when the PLAY position reaches the startpos,
454 * not the MIX position... but if the sound buffer is bigger than our prebuffering
455 * (which must be the case for the streaming buffers that need this hack anyway)
456 * plus DS_HEL_MARGIN or equivalent, then this ought to work anyway. */
460 dsb->buf_mixpos += ilen;
462 if (dsb->buf_mixpos >= dsb->buflen) {
463 if (dsb->playflags & DSBPLAY_LOOPING) {
465 while (dsb->buf_mixpos >= dsb->buflen)
466 dsb->buf_mixpos -= dsb->buflen;
467 if (dsb->leadin && (dsb->startpos <= dsb->buf_mixpos))
468 dsb->leadin = FALSE; /* HACK: see above */
475 static void DSOUND_PhaseCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD len)
477 INT i, ilen, field, nBlockAlign;
478 INT advance = dsb->dsound->wfx.wBitsPerSample >> 3;
479 BYTE *buf, *ibuf, *obuf;
480 INT16 *ibufs, *obufs;
481 TRACE("(%p,%ld,%ld)\n",dsb,writepos,len);
483 nBlockAlign = dsb->dsound->wfx.nBlockAlign;
484 len = len / nBlockAlign * nBlockAlign; /* data alignment */
486 TRACE("allocating buffer (size = %ld)\n", len);
487 if ((buf = ibuf = (BYTE *) DSOUND_tmpbuffer(len)) == NULL)
490 TRACE("PhaseCancel (%p) len = %ld, dest = %ld\n", dsb, len, writepos);
492 ilen = DSOUND_MixerNorm(dsb, ibuf, len);
493 if ((dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
494 (dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) ||
495 (dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
496 DSOUND_MixerVol(dsb, ibuf, len);
498 /* subtract instead of add, to phase out premixed data */
499 obuf = dsb->dsound->buffer + writepos;
500 for (i = 0; i < len; i += advance) {
501 obufs = (INT16 *) obuf;
502 ibufs = (INT16 *) ibuf;
503 if (dsb->dsound->wfx.wBitsPerSample == 8) {
504 /* 8-bit WAV is unsigned */
505 field = (*ibuf - 128);
506 field -= (*obuf - 128);
507 field = field > 127 ? 127 : field;
508 field = field < -128 ? -128 : field;
511 /* 16-bit WAV is signed */
514 field = field > 32767 ? 32767 : field;
515 field = field < -32768 ? -32768 : field;
520 if (obuf >= (BYTE *)(dsb->dsound->buffer + dsb->dsound->buflen))
521 obuf = dsb->dsound->buffer;
526 static void DSOUND_MixCancel(IDirectSoundBufferImpl *dsb, DWORD writepos, BOOL cancel)
528 DWORD size, flen, len, npos, nlen;
529 INT iAdvance = dsb->wfx.nBlockAlign;
530 INT oAdvance = dsb->dsound->wfx.nBlockAlign;
531 /* determine amount of premixed data to cancel */
533 ((dsb->primary_mixpos < writepos) ? dsb->dsound->buflen : 0) +
534 dsb->primary_mixpos - writepos;
536 TRACE("(%p, %ld), buf_mixpos=%ld\n", dsb, writepos, dsb->buf_mixpos);
538 /* backtrack the mix position */
539 size = primary_done / oAdvance;
540 flen = size * dsb->freqAdjust;
541 len = (flen >> DSOUND_FREQSHIFT) * iAdvance;
542 flen &= (1<<DSOUND_FREQSHIFT)-1;
543 while (dsb->freqAcc < flen) {
545 dsb->freqAcc += 1<<DSOUND_FREQSHIFT;
548 npos = ((dsb->buf_mixpos < len) ? dsb->buflen : 0) +
549 dsb->buf_mixpos - len;
550 if (dsb->leadin && (dsb->startpos > npos) && (dsb->startpos <= npos + len)) {
551 /* stop backtracking at startpos */
552 npos = dsb->startpos;
553 len = ((dsb->buf_mixpos < npos) ? dsb->buflen : 0) +
554 dsb->buf_mixpos - npos;
556 nlen = len / dsb->wfx.nBlockAlign;
557 nlen = ((nlen << DSOUND_FREQSHIFT) + flen) / dsb->freqAdjust;
558 nlen *= dsb->dsound->wfx.nBlockAlign;
560 ((dsb->primary_mixpos < nlen) ? dsb->dsound->buflen : 0) +
561 dsb->primary_mixpos - nlen;
564 dsb->freqAcc -= flen;
565 dsb->buf_mixpos = npos;
566 dsb->primary_mixpos = writepos;
568 TRACE("new buf_mixpos=%ld, primary_mixpos=%ld (len=%ld)\n",
569 dsb->buf_mixpos, dsb->primary_mixpos, len);
571 if (cancel) DSOUND_PhaseCancel(dsb, writepos, len);
574 void DSOUND_MixCancelAt(IDirectSoundBufferImpl *dsb, DWORD buf_writepos)
577 DWORD i, size, flen, len, npos, nlen;
578 INT iAdvance = dsb->wfx.nBlockAlign;
579 INT oAdvance = dsb->dsound->wfx.nBlockAlign;
580 /* determine amount of premixed data to cancel */
582 ((dsb->buf_mixpos < buf_writepos) ? dsb->buflen : 0) +
583 dsb->buf_mixpos - buf_writepos;
586 WARN("(%p, %ld), buf_mixpos=%ld\n", dsb, buf_writepos, dsb->buf_mixpos);
587 /* since this is not implemented yet, just cancel *ALL* prebuffering for now
588 * (which is faster anyway when there's only a single secondary buffer) */
589 dsb->dsound->need_remix = TRUE;
592 void DSOUND_ForceRemix(IDirectSoundBufferImpl *dsb)
595 EnterCriticalSection(&dsb->lock);
596 if (dsb->state == STATE_PLAYING) {
597 #if 0 /* this may not be quite reliable yet */
598 dsb->need_remix = TRUE;
600 dsb->dsound->need_remix = TRUE;
603 LeaveCriticalSection(&dsb->lock);
606 static DWORD DSOUND_MixOne(IDirectSoundBufferImpl *dsb, DWORD playpos, DWORD writepos, DWORD mixlen)
609 /* determine this buffer's write position */
610 DWORD buf_writepos = DSOUND_CalcPlayPosition(dsb, dsb->state & dsb->dsound->state, writepos,
611 writepos, dsb->primary_mixpos, dsb->buf_mixpos);
612 /* determine how much already-mixed data exists */
614 ((dsb->buf_mixpos < buf_writepos) ? dsb->buflen : 0) +
615 dsb->buf_mixpos - buf_writepos;
617 ((dsb->primary_mixpos < writepos) ? dsb->dsound->buflen : 0) +
618 dsb->primary_mixpos - writepos;
620 ((dsb->dsound->mixpos < writepos) ? dsb->dsound->buflen : 0) +
621 dsb->dsound->mixpos - writepos;
623 ((buf_writepos < dsb->playpos) ? dsb->buflen : 0) +
624 buf_writepos - dsb->playpos;
625 DWORD buf_left = dsb->buflen - buf_writepos;
628 TRACE("(%p,%ld,%ld,%ld)\n",dsb,playpos,writepos,mixlen);
629 TRACE("buf_writepos=%ld, primary_writepos=%ld\n", buf_writepos, writepos);
630 TRACE("buf_done=%ld, primary_done=%ld\n", buf_done, primary_done);
631 TRACE("buf_mixpos=%ld, primary_mixpos=%ld, mixlen=%ld\n", dsb->buf_mixpos, dsb->primary_mixpos,
633 TRACE("looping=%ld, startpos=%ld, leadin=%ld\n", dsb->playflags, dsb->startpos, dsb->leadin);
635 /* check for notification positions */
636 if (dsb->dsbd.dwFlags & DSBCAPS_CTRLPOSITIONNOTIFY &&
637 dsb->state != STATE_STARTING) {
638 DSOUND_CheckEvent(dsb, played);
641 /* save write position for non-GETCURRENTPOSITION2... */
642 dsb->playpos = buf_writepos;
644 /* check whether CalcPlayPosition detected a mixing underrun */
645 if ((buf_done == 0) && (dsb->primary_mixpos != writepos)) {
646 /* it did, but did we have more to play? */
647 if ((dsb->playflags & DSBPLAY_LOOPING) ||
648 (dsb->buf_mixpos < dsb->buflen)) {
649 /* yes, have to recover */
650 ERR("underrun on sound buffer %p\n", dsb);
651 TRACE("recovering from underrun: primary_mixpos=%ld\n", writepos);
653 dsb->primary_mixpos = writepos;
656 /* determine how far ahead we should mix */
657 if (((dsb->playflags & DSBPLAY_LOOPING) ||
658 (dsb->leadin && (dsb->probably_valid_to != 0))) &&
659 !(dsb->dsbd.dwFlags & DSBCAPS_STATIC)) {
660 /* if this is a streaming buffer, it typically means that
661 * we should defer mixing past probably_valid_to as long
662 * as we can, to avoid unnecessary remixing */
663 /* the heavy-looking calculations shouldn't be that bad,
664 * as any game isn't likely to be have more than 1 or 2
665 * streaming buffers in use at any time anyway... */
666 DWORD probably_valid_left =
667 (dsb->probably_valid_to == (DWORD)-1) ? dsb->buflen :
668 ((dsb->probably_valid_to < buf_writepos) ? dsb->buflen : 0) +
669 dsb->probably_valid_to - buf_writepos;
670 /* check for leadin condition */
671 if ((probably_valid_left == 0) &&
672 (dsb->probably_valid_to == dsb->startpos) &&
674 probably_valid_left = dsb->buflen;
675 TRACE("streaming buffer probably_valid_to=%ld, probably_valid_left=%ld\n",
676 dsb->probably_valid_to, probably_valid_left);
677 /* check whether the app's time is already up */
678 if (probably_valid_left < dsb->writelead) {
679 WARN("probably_valid_to now within writelead, possible streaming underrun\n");
680 /* once we pass the point of no return,
681 * no reason to hold back anymore */
682 dsb->probably_valid_to = (DWORD)-1;
683 /* we just have to go ahead and mix what we have,
684 * there's no telling what the app is thinking anyway */
686 /* adjust for our frequency and our sample size */
687 probably_valid_left = MulDiv(probably_valid_left,
688 1 << DSOUND_FREQSHIFT,
689 dsb->wfx.nBlockAlign * dsb->freqAdjust) *
690 dsb->dsound->wfx.nBlockAlign;
691 /* check whether to clip mix_len */
692 if (probably_valid_left < mixlen) {
693 TRACE("clipping to probably_valid_left=%ld\n", probably_valid_left);
694 mixlen = probably_valid_left;
698 /* cut mixlen with what's already been mixed */
699 if (mixlen < primary_done) {
700 /* huh? and still CalcPlayPosition didn't
701 * detect an underrun? */
702 FIXME("problem with underrun detection (mixlen=%ld < primary_done=%ld)\n", mixlen, primary_done);
705 len = mixlen - primary_done;
706 TRACE("remaining mixlen=%ld\n", len);
708 if (len < dsb->dsound->fraglen) {
709 /* smaller than a fragment, wait until it gets larger
710 * before we take the mixing overhead */
711 TRACE("mixlen not worth it, deferring mixing\n");
716 /* ok, we know how much to mix, let's go */
717 still_behind = (adv_done > primary_done);
719 slen = dsb->dsound->buflen - dsb->primary_mixpos;
720 if (slen > len) slen = len;
721 slen = DSOUND_MixInBuffer(dsb, dsb->primary_mixpos, slen);
723 if ((dsb->primary_mixpos < dsb->dsound->mixpos) &&
724 (dsb->primary_mixpos + slen >= dsb->dsound->mixpos))
725 still_behind = FALSE;
727 dsb->primary_mixpos += slen; len -= slen;
728 while (dsb->primary_mixpos >= dsb->dsound->buflen)
729 dsb->primary_mixpos -= dsb->dsound->buflen;
731 if ((dsb->state == STATE_STOPPED) || !slen) break;
733 TRACE("new primary_mixpos=%ld, primary_advbase=%ld\n", dsb->primary_mixpos, dsb->dsound->mixpos);
734 TRACE("mixed data len=%ld, still_behind=%d\n", mixlen-len, still_behind);
737 /* check if buffer should be considered complete */
738 if (buf_left < dsb->writelead &&
739 !(dsb->playflags & DSBPLAY_LOOPING)) {
740 dsb->state = STATE_STOPPED;
742 dsb->last_playpos = 0;
745 DSOUND_CheckEvent(dsb, buf_left);
748 /* return how far we think the primary buffer can
749 * advance its underrun detector...*/
750 if (still_behind) return 0;
751 if ((mixlen - len) < primary_done) return 0;
752 slen = ((dsb->primary_mixpos < dsb->dsound->mixpos) ?
753 dsb->dsound->buflen : 0) + dsb->primary_mixpos -
756 /* the primary_done and still_behind checks above should have worked */
757 FIXME("problem with advancement calculation (advlen=%ld > mixlen=%ld)\n", slen, mixlen);
763 static DWORD DSOUND_MixToPrimary(IDirectSoundImpl *dsound, DWORD playpos, DWORD writepos, DWORD mixlen, BOOL recover)
765 INT i, len, maxlen = 0;
766 IDirectSoundBufferImpl *dsb;
768 TRACE("(%ld,%ld,%ld,%d)\n", playpos, writepos, mixlen, recover);
769 for (i = dsound->nrofbuffers - 1; i >= 0; i--) {
770 dsb = dsound->buffers[i];
772 if (!dsb || !dsb->lpVtbl)
774 if (dsb->buflen && dsb->state && !dsb->hwbuf) {
775 TRACE("Checking %p, mixlen=%ld\n", dsb, mixlen);
776 EnterCriticalSection(&(dsb->lock));
777 if (dsb->state == STATE_STOPPING) {
778 DSOUND_MixCancel(dsb, writepos, TRUE);
779 dsb->state = STATE_STOPPED;
780 DSOUND_CheckEvent(dsb, 0);
782 if ((dsb->state == STATE_STARTING) || recover) {
783 dsb->primary_mixpos = writepos;
784 memcpy(&dsb->cvolpan, &dsb->volpan, sizeof(dsb->cvolpan));
785 dsb->need_remix = FALSE;
787 else if (dsb->need_remix) {
788 DSOUND_MixCancel(dsb, writepos, TRUE);
789 memcpy(&dsb->cvolpan, &dsb->volpan, sizeof(dsb->cvolpan));
790 dsb->need_remix = FALSE;
792 len = DSOUND_MixOne(dsb, playpos, writepos, mixlen);
793 if (dsb->state == STATE_STARTING)
794 dsb->state = STATE_PLAYING;
795 maxlen = (len > maxlen) ? len : maxlen;
797 LeaveCriticalSection(&(dsb->lock));
804 static void DSOUND_MixReset(IDirectSoundImpl *dsound, DWORD writepos)
807 IDirectSoundBufferImpl *dsb;
810 TRACE("(%ld)\n", writepos);
812 /* the sound of silence */
813 nfiller = dsound->wfx.wBitsPerSample == 8 ? 128 : 0;
815 /* reset all buffer mix positions */
816 for (i = dsound->nrofbuffers - 1; i >= 0; i--) {
817 dsb = dsound->buffers[i];
819 if (!dsb || !dsb->lpVtbl)
821 if (dsb->buflen && dsb->state && !dsb->hwbuf) {
822 TRACE("Resetting %p\n", dsb);
823 EnterCriticalSection(&(dsb->lock));
824 if (dsb->state == STATE_STOPPING) {
825 dsb->state = STATE_STOPPED;
827 else if (dsb->state == STATE_STARTING) {
830 DSOUND_MixCancel(dsb, writepos, FALSE);
831 memcpy(&dsb->cvolpan, &dsb->volpan, sizeof(dsb->cvolpan));
832 dsb->need_remix = FALSE;
834 LeaveCriticalSection(&(dsb->lock));
838 /* wipe out premixed data */
839 if (dsound->mixpos < writepos) {
840 memset(dsound->buffer + writepos, nfiller, dsound->buflen - writepos);
841 memset(dsound->buffer, nfiller, dsound->mixpos);
843 memset(dsound->buffer + writepos, nfiller, dsound->mixpos - writepos);
846 /* reset primary mix position */
847 dsound->mixpos = writepos;
850 static void DSOUND_CheckReset(IDirectSoundImpl *dsound, DWORD writepos)
852 TRACE("(%p,%ld)\n",dsound,writepos);
853 if (dsound->need_remix) {
854 DSOUND_MixReset(dsound, writepos);
855 dsound->need_remix = FALSE;
856 /* maximize Half-Life performance */
857 dsound->prebuf = ds_snd_queue_min;
858 dsound->precount = 0;
861 if (dsound->precount >= 4) {
862 if (dsound->prebuf < ds_snd_queue_max)
864 dsound->precount = 0;
867 TRACE("premix adjust: %d\n", dsound->prebuf);
870 void DSOUND_WaveQueue(IDirectSoundImpl *dsound, DWORD mixq)
872 TRACE("(%p,%ld)\n",dsound,mixq);
873 if (mixq + dsound->pwqueue > ds_hel_queue) mixq = ds_hel_queue - dsound->pwqueue;
874 TRACE("queueing %ld buffers, starting at %d\n", mixq, dsound->pwwrite);
875 for (; mixq; mixq--) {
876 waveOutWrite(dsound->hwo, dsound->pwave[dsound->pwwrite], sizeof(WAVEHDR));
878 if (dsound->pwwrite >= DS_HEL_FRAGS) dsound->pwwrite = 0;
883 /* #define SYNC_CALLBACK */
885 void DSOUND_PerformMix(IDirectSoundImpl *dsound)
893 /* the sound of silence */
894 nfiller = dsound->wfx.wBitsPerSample == 8 ? 128 : 0;
896 /* whether the primary is forced to play even without secondary buffers */
897 forced = ((dsound->state == STATE_PLAYING) || (dsound->state == STATE_STARTING));
899 if (dsound->priolevel != DSSCL_WRITEPRIMARY) {
900 BOOL paused = ((dsound->state == STATE_STOPPED) || (dsound->state == STATE_STARTING));
901 /* FIXME: document variables */
902 DWORD playpos, writepos, inq, maxq, frag;
904 hres = IDsDriverBuffer_GetPosition(dsound->hwbuf, &playpos, &writepos);
906 WARN("IDsDriverBuffer_GetPosition failed\n");
909 /* Well, we *could* do Just-In-Time mixing using the writepos,
910 * but that's a little bit ambitious and unnecessary... */
911 /* rather add our safety margin to the writepos, if we're playing */
913 writepos += dsound->writelead;
914 while (writepos >= dsound->buflen)
915 writepos -= dsound->buflen;
916 } else writepos = playpos;
918 playpos = dsound->pwplay * dsound->fraglen;
921 writepos += ds_hel_margin * dsound->fraglen;
922 while (writepos >= dsound->buflen)
923 writepos -= dsound->buflen;
926 TRACE("primary playpos=%ld, writepos=%ld, clrpos=%ld, mixpos=%ld, buflen=%ld\n",
927 playpos,writepos,dsound->playpos,dsound->mixpos,dsound->buflen);
928 assert(dsound->playpos < dsound->buflen);
929 /* wipe out just-played sound data */
930 if (playpos < dsound->playpos) {
931 memset(dsound->buffer + dsound->playpos, nfiller, dsound->buflen - dsound->playpos);
932 memset(dsound->buffer, nfiller, playpos);
934 memset(dsound->buffer + dsound->playpos, nfiller, playpos - dsound->playpos);
936 dsound->playpos = playpos;
938 EnterCriticalSection(&(dsound->mixlock));
940 /* reset mixing if necessary */
941 DSOUND_CheckReset(dsound, writepos);
943 /* check how much prebuffering is left */
944 inq = dsound->mixpos;
946 inq += dsound->buflen;
949 /* find the maximum we can prebuffer */
953 maxq += dsound->buflen;
955 } else maxq = dsound->buflen;
957 /* clip maxq to dsound->prebuf */
958 frag = dsound->prebuf * dsound->fraglen;
959 if (maxq > frag) maxq = frag;
961 /* check for consistency */
963 /* the playback position must have passed our last
964 * mixed position, i.e. it's an underrun, or we have
965 * nothing more to play */
966 TRACE("reached end of mixed data (inq=%ld, maxq=%ld)\n", inq, maxq);
968 /* stop the playback now, to allow buffers to refill */
969 if (dsound->state == STATE_PLAYING) {
970 dsound->state = STATE_STARTING;
972 else if (dsound->state == STATE_STOPPING) {
973 dsound->state = STATE_STOPPED;
976 /* how can we have an underrun if we aren't playing? */
977 WARN("unexpected primary state (%ld)\n", dsound->state);
980 /* DSOUND_callback may need this lock */
981 LeaveCriticalSection(&(dsound->mixlock));
983 if (DSOUND_PrimaryStop(dsound) != DS_OK)
984 WARN("DSOUND_PrimaryStop failed\n");
986 EnterCriticalSection(&(dsound->mixlock));
989 /* the Stop is supposed to reset play position to beginning of buffer */
990 /* unfortunately, OSS is not able to do so, so get current pointer */
991 hres = IDsDriverBuffer_GetPosition(dsound->hwbuf, &playpos, NULL);
993 LeaveCriticalSection(&(dsound->mixlock));
994 WARN("IDsDriverBuffer_GetPosition failed\n");
998 playpos = dsound->pwplay * dsound->fraglen;
1001 dsound->playpos = playpos;
1002 dsound->mixpos = writepos;
1004 maxq = dsound->buflen;
1005 if (maxq > frag) maxq = frag;
1006 memset(dsound->buffer, nfiller, dsound->buflen);
1011 frag = DSOUND_MixToPrimary(dsound, playpos, writepos, maxq, paused);
1012 if (forced) frag = maxq - inq;
1013 dsound->mixpos += frag;
1014 while (dsound->mixpos >= dsound->buflen)
1015 dsound->mixpos -= dsound->buflen;
1018 /* buffers have been filled, restart playback */
1019 if (dsound->state == STATE_STARTING) {
1020 dsound->state = STATE_PLAYING;
1022 else if (dsound->state == STATE_STOPPED) {
1023 /* the dsound is supposed to play if there's something to play
1024 * even if it is reported as stopped, so don't let this confuse you */
1025 dsound->state = STATE_STOPPING;
1027 LeaveCriticalSection(&(dsound->mixlock));
1029 if (DSOUND_PrimaryPlay(dsound) != DS_OK)
1030 WARN("DSOUND_PrimaryPlay failed\n");
1032 TRACE("starting playback\n");
1036 LeaveCriticalSection(&(dsound->mixlock));
1038 /* in the DSSCL_WRITEPRIMARY mode, the app is totally in charge... */
1039 if (dsound->state == STATE_STARTING) {
1040 if (DSOUND_PrimaryPlay(dsound) != DS_OK)
1041 WARN("DSOUND_PrimaryPlay failed\n");
1043 dsound->state = STATE_PLAYING;
1045 else if (dsound->state == STATE_STOPPING) {
1046 if (DSOUND_PrimaryStop(dsound) != DS_OK)
1047 WARN("DSOUND_PrimaryStop failed\n");
1049 dsound->state = STATE_STOPPED;
1054 void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
1056 IDirectSoundImpl* This = (IDirectSoundImpl*)dwUser;
1057 TRACE("(%d,%d,0x%lx,0x%lx,0x%lx)\n",timerID,msg,dwUser,dw1,dw2);
1058 TRACE("entering at %ld\n", GetTickCount());
1060 if (dsound != This) {
1061 ERR("dsound died without killing us?\n");
1062 timeKillEvent(timerID);
1063 timeEndPeriod(DS_TIME_RES);
1067 RtlAcquireResourceShared(&(This->lock), TRUE);
1070 DSOUND_PerformMix(This);
1073 RtlReleaseResource(&(This->lock));
1075 TRACE("completed processing at %ld\n", GetTickCount());
1078 void CALLBACK DSOUND_callback(HWAVEOUT hwo, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2)
1080 IDirectSoundImpl* This = (IDirectSoundImpl*)dwUser;
1081 TRACE("(%p,%x,%lx,%lx,%lx)\n",hwo,msg,dwUser,dw1,dw2);
1082 TRACE("entering at %ld, msg=%08x(%s)\n", GetTickCount(), msg,
1083 msg==MM_WOM_DONE ? "MM_WOM_DONE" : msg==MM_WOM_CLOSE ? "MM_WOM_CLOSE" :
1084 msg==MM_WOM_OPEN ? "MM_WOM_OPEN" : "UNKNOWN");
1085 if (msg == MM_WOM_DONE) {
1086 DWORD inq, mixq, fraglen, buflen, pwplay, playpos, mixpos;
1087 if (This->pwqueue == (DWORD)-1) {
1088 TRACE("completed due to reset\n");
1091 /* it could be a bad idea to enter critical section here... if there's lock contention,
1092 * the resulting scheduling delays might obstruct the winmm player thread */
1093 #ifdef SYNC_CALLBACK
1094 EnterCriticalSection(&(This->mixlock));
1096 /* retrieve current values */
1097 fraglen = This->fraglen;
1098 buflen = This->buflen;
1099 pwplay = This->pwplay;
1100 playpos = pwplay * fraglen;
1101 mixpos = This->mixpos;
1102 /* check remaining mixed data */
1103 inq = ((mixpos < playpos) ? buflen : 0) + mixpos - playpos;
1104 mixq = inq / fraglen;
1105 if ((inq - (mixq * fraglen)) > 0) mixq++;
1106 /* complete the playing buffer */
1107 TRACE("done playing primary pos=%ld\n", playpos);
1109 if (pwplay >= DS_HEL_FRAGS) pwplay = 0;
1110 /* write new values */
1111 This->pwplay = pwplay;
1113 /* queue new buffer if we have data for it */
1114 if (inq>1) DSOUND_WaveQueue(This, inq-1);
1115 #ifdef SYNC_CALLBACK
1116 LeaveCriticalSection(&(This->mixlock));
1119 TRACE("completed\n");