dsound: Create a primary_pwfx separately from pwfx.
[wine] / dlls / dsound / mixer.c
1 /*                      DirectSound
2  *
3  * Copyright 1998 Marcus Meissner
4  * Copyright 1998 Rob Riggs
5  * Copyright 2000-2002 TransGaming Technologies, Inc.
6  * Copyright 2007 Peter Dons Tychsen
7  * Copyright 2007 Maarten Lankhorst
8  * Copyright 2011 Owen Rudge for CodeWeavers
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24
25 #include <assert.h>
26 #include <stdarg.h>
27 #include <math.h>       /* Insomnia - pow() function */
28
29 #define COBJMACROS
30 #define NONAMELESSSTRUCT
31 #define NONAMELESSUNION
32 #include "windef.h"
33 #include "winbase.h"
34 #include "mmsystem.h"
35 #include "wingdi.h"
36 #include "mmreg.h"
37 #include "winternl.h"
38 #include "wine/debug.h"
39 #include "dsound.h"
40 #include "ks.h"
41 #include "ksmedia.h"
42 #include "dsound_private.h"
43 #include "fir.h"
44
45 WINE_DEFAULT_DEBUG_CHANNEL(dsound);
46
47 void DSOUND_RecalcVolPan(PDSVOLUMEPAN volpan)
48 {
49         double temp;
50         TRACE("(%p)\n",volpan);
51
52         TRACE("Vol=%d Pan=%d\n", volpan->lVolume, volpan->lPan);
53         /* the AmpFactors are expressed in 16.16 fixed point */
54         volpan->dwVolAmpFactor = (ULONG) (pow(2.0, volpan->lVolume / 600.0) * 0xffff);
55         /* FIXME: dwPan{Left|Right}AmpFactor */
56
57         /* FIXME: use calculated vol and pan ampfactors */
58         temp = (double) (volpan->lVolume - (volpan->lPan > 0 ? volpan->lPan : 0));
59         volpan->dwTotalLeftAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 0xffff);
60         temp = (double) (volpan->lVolume + (volpan->lPan < 0 ? volpan->lPan : 0));
61         volpan->dwTotalRightAmpFactor = (ULONG) (pow(2.0, temp / 600.0) * 0xffff);
62
63         TRACE("left = %x, right = %x\n", volpan->dwTotalLeftAmpFactor, volpan->dwTotalRightAmpFactor);
64 }
65
66 void DSOUND_AmpFactorToVolPan(PDSVOLUMEPAN volpan)
67 {
68     double left,right;
69     TRACE("(%p)\n",volpan);
70
71     TRACE("left=%x, right=%x\n",volpan->dwTotalLeftAmpFactor,volpan->dwTotalRightAmpFactor);
72     if (volpan->dwTotalLeftAmpFactor==0)
73         left=-10000;
74     else
75         left=600 * log(((double)volpan->dwTotalLeftAmpFactor) / 0xffff) / log(2);
76     if (volpan->dwTotalRightAmpFactor==0)
77         right=-10000;
78     else
79         right=600 * log(((double)volpan->dwTotalRightAmpFactor) / 0xffff) / log(2);
80     if (left<right)
81     {
82         volpan->lVolume=right;
83         volpan->dwVolAmpFactor=volpan->dwTotalRightAmpFactor;
84     }
85     else
86     {
87         volpan->lVolume=left;
88         volpan->dwVolAmpFactor=volpan->dwTotalLeftAmpFactor;
89     }
90     if (volpan->lVolume < -10000)
91         volpan->lVolume=-10000;
92     volpan->lPan=right-left;
93     if (volpan->lPan < -10000)
94         volpan->lPan=-10000;
95
96     TRACE("Vol=%d Pan=%d\n", volpan->lVolume, volpan->lPan);
97 }
98
99 /**
100  * Recalculate the size for temporary buffer, and new writelead
101  * Should be called when one of the following things occur:
102  * - Primary buffer format is changed
103  * - This buffer format (frequency) is changed
104  */
105 void DSOUND_RecalcFormat(IDirectSoundBufferImpl *dsb)
106 {
107         DWORD ichannels = dsb->pwfx->nChannels;
108         DWORD ochannels = dsb->device->pwfx->nChannels;
109         WAVEFORMATEXTENSIBLE *pwfxe;
110         BOOL ieee = FALSE;
111
112         TRACE("(%p)\n",dsb);
113
114         pwfxe = (WAVEFORMATEXTENSIBLE *) dsb->pwfx;
115         dsb->freqAdjust = (float)dsb->freq / dsb->device->pwfx->nSamplesPerSec;
116
117         if ((pwfxe->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) || ((pwfxe->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE)
118             && (IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))))
119                 ieee = TRUE;
120
121         /**
122          * Recalculate FIR step and gain.
123          *
124          * firstep says how many points of the FIR exist per one
125          * sample in the secondary buffer. firgain specifies what
126          * to multiply the FIR output by in order to attenuate it correctly.
127          */
128         if (dsb->freqAdjust > 1.0f) {
129                 /**
130                  * Yes, round it a bit to make sure that the
131                  * linear interpolation factor never changes.
132                  */
133                 dsb->firstep = ceil(fir_step / dsb->freqAdjust);
134         } else {
135                 dsb->firstep = fir_step;
136         }
137         dsb->firgain = (float)dsb->firstep / fir_step;
138
139         /* calculate the 10ms write lead */
140         dsb->writelead = (dsb->freq / 100) * dsb->pwfx->nBlockAlign;
141
142         dsb->freqAcc = 0;
143
144         dsb->get_aux = ieee ? getbpp[4] : getbpp[dsb->pwfx->wBitsPerSample/8 - 1];
145         dsb->put_aux = putieee32;
146
147         dsb->get = dsb->get_aux;
148         dsb->put = dsb->put_aux;
149
150         if (ichannels == ochannels)
151         {
152                 dsb->mix_channels = ichannels;
153                 if (ichannels > 32) {
154                         FIXME("Copying %u channels is unsupported, limiting to first 32\n", ichannels);
155                         dsb->mix_channels = 32;
156                 }
157         }
158         else if (ichannels == 1)
159         {
160                 dsb->mix_channels = 1;
161                 dsb->put = put_mono2stereo;
162         }
163         else if (ochannels == 1)
164         {
165                 dsb->mix_channels = 1;
166                 dsb->get = get_mono;
167         }
168         else
169         {
170                 if (ichannels > 2)
171                         FIXME("Conversion from %u to %u channels is not implemented, falling back to stereo\n", ichannels, ochannels);
172                 dsb->mix_channels = 2;
173         }
174 }
175
176 /**
177  * Check for application callback requests for when the play position
178  * reaches certain points.
179  *
180  * The offsets that will be triggered will be those between the recorded
181  * "last played" position for the buffer (i.e. dsb->playpos) and "len" bytes
182  * beyond that position.
183  */
184 void DSOUND_CheckEvent(const IDirectSoundBufferImpl *dsb, DWORD playpos, int len)
185 {
186         int                     i;
187         DWORD                   offset;
188         LPDSBPOSITIONNOTIFY     event;
189         TRACE("(%p,%d)\n",dsb,len);
190
191         if (dsb->nrofnotifies == 0)
192                 return;
193
194         TRACE("(%p) buflen = %d, playpos = %d, len = %d\n",
195                 dsb, dsb->buflen, playpos, len);
196         for (i = 0; i < dsb->nrofnotifies ; i++) {
197                 event = dsb->notifies + i;
198                 offset = event->dwOffset;
199                 TRACE("checking %d, position %d, event = %p\n",
200                         i, offset, event->hEventNotify);
201                 /* DSBPN_OFFSETSTOP has to be the last element. So this is */
202                 /* OK. [Inside DirectX, p274] */
203                 /* Windows does not seem to enforce this, and some apps rely */
204                 /* on that, so we can't stop there. */
205                 /*  */
206                 /* This also means we can't sort the entries by offset, */
207                 /* because DSBPN_OFFSETSTOP == -1 */
208                 if (offset == DSBPN_OFFSETSTOP) {
209                         if (dsb->state == STATE_STOPPED) {
210                                 SetEvent(event->hEventNotify);
211                                 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
212                         }
213                         continue;
214                 }
215                 if ((playpos + len) >= dsb->buflen) {
216                         if ((offset < ((playpos + len) % dsb->buflen)) ||
217                             (offset >= playpos)) {
218                                 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
219                                 SetEvent(event->hEventNotify);
220                         }
221                 } else {
222                         if ((offset >= playpos) && (offset < (playpos + len))) {
223                                 TRACE("signalled event %p (%d)\n", event->hEventNotify, i);
224                                 SetEvent(event->hEventNotify);
225                         }
226                 }
227         }
228 }
229
230 static inline float get_current_sample(const IDirectSoundBufferImpl *dsb,
231         DWORD mixpos, DWORD channel)
232 {
233     if (mixpos >= dsb->buflen && !(dsb->playflags & DSBPLAY_LOOPING))
234         return 0.0f;
235     return dsb->get(dsb, mixpos % dsb->buflen, channel);
236 }
237
238 static UINT cp_fields_noresample(IDirectSoundBufferImpl *dsb, UINT count)
239 {
240     UINT istride = dsb->pwfx->nBlockAlign;
241     UINT ostride = dsb->device->pwfx->nChannels * sizeof(float);
242     DWORD channel, i;
243     for (i = 0; i < count; i++)
244         for (channel = 0; channel < dsb->mix_channels; channel++)
245             dsb->put(dsb, i * ostride, channel, get_current_sample(dsb,
246                     dsb->sec_mixpos + i * istride, channel));
247     return count;
248 }
249
250 static UINT cp_fields_resample(IDirectSoundBufferImpl *dsb, UINT count, float *freqAcc)
251 {
252     UINT i, channel;
253     UINT istride = dsb->pwfx->nBlockAlign;
254     UINT ostride = dsb->device->pwfx->nChannels * sizeof(float);
255
256     float freqAdjust = dsb->freqAdjust;
257     float freqAcc_start = *freqAcc;
258     float freqAcc_end = freqAcc_start + count * freqAdjust;
259     UINT dsbfirstep = dsb->firstep;
260     UINT channels = dsb->mix_channels;
261     UINT max_ipos = freqAcc_start + count * freqAdjust;
262
263     UINT fir_cachesize = (fir_len + dsbfirstep - 2) / dsbfirstep;
264     UINT required_input = max_ipos + fir_cachesize;
265
266     float* intermediate = HeapAlloc(GetProcessHeap(), 0,
267             sizeof(float) * required_input * channels);
268
269     float* fir_copy = HeapAlloc(GetProcessHeap(), 0,
270             sizeof(float) * fir_cachesize);
271
272     /* Important: this buffer MUST be non-interleaved
273      * if you want -msse3 to have any effect.
274      * This is good for CPU cache effects, too.
275      */
276     float* itmp = intermediate;
277     for (channel = 0; channel < channels; channel++)
278         for (i = 0; i < required_input; i++)
279             *(itmp++) = get_current_sample(dsb,
280                     dsb->sec_mixpos + i * istride, channel);
281
282     for(i = 0; i < count; ++i) {
283         float total_fir_steps = (freqAcc_start + i * freqAdjust) * dsbfirstep;
284         UINT int_fir_steps = total_fir_steps;
285         UINT ipos = int_fir_steps / dsbfirstep;
286
287         UINT idx = (ipos + 1) * dsbfirstep - int_fir_steps - 1;
288         float rem = int_fir_steps + 1.0 - total_fir_steps;
289
290         int fir_used = 0;
291         while (idx < fir_len - 1) {
292             fir_copy[fir_used++] = fir[idx] * (1.0 - rem) + fir[idx + 1] * rem;
293             idx += dsb->firstep;
294         }
295
296         assert(fir_used <= fir_cachesize);
297         assert(ipos + fir_used <= required_input);
298
299         for (channel = 0; channel < dsb->mix_channels; channel++) {
300             int j;
301             float sum = 0.0;
302             float* cache = &intermediate[channel * required_input + ipos];
303             for (j = 0; j < fir_used; j++)
304                 sum += fir_copy[j] * cache[j];
305             dsb->put(dsb, i * ostride, channel, sum * dsb->firgain);
306         }
307     }
308
309     freqAcc_end -= (int)freqAcc_end;
310     *freqAcc = freqAcc_end;
311
312     HeapFree(GetProcessHeap(), 0, fir_copy);
313     HeapFree(GetProcessHeap(), 0, intermediate);
314
315     return max_ipos;
316 }
317
318 static void cp_fields(IDirectSoundBufferImpl *dsb, UINT count, float *freqAcc)
319 {
320     DWORD ipos, adv;
321
322     if (dsb->freqAdjust == 1.0)
323         adv = cp_fields_noresample(dsb, count); /* *freqAcc is unmodified */
324     else
325         adv = cp_fields_resample(dsb, count, freqAcc);
326
327     ipos = dsb->sec_mixpos + adv * dsb->pwfx->nBlockAlign;
328     if (ipos >= dsb->buflen) {
329         if (dsb->playflags & DSBPLAY_LOOPING)
330             ipos %= dsb->buflen;
331         else {
332             ipos = 0;
333             dsb->state = STATE_STOPPED;
334         }
335     }
336
337     dsb->sec_mixpos = ipos;
338 }
339
340 /**
341  * Calculate the distance between two buffer offsets, taking wraparound
342  * into account.
343  */
344 static inline DWORD DSOUND_BufPtrDiff(DWORD buflen, DWORD ptr1, DWORD ptr2)
345 {
346 /* If these asserts fail, the problem is not here, but in the underlying code */
347         assert(ptr1 < buflen);
348         assert(ptr2 < buflen);
349         if (ptr1 >= ptr2) {
350                 return ptr1 - ptr2;
351         } else {
352                 return buflen + ptr1 - ptr2;
353         }
354 }
355 /**
356  * Mix at most the given amount of data into the allocated temporary buffer
357  * of the given secondary buffer, starting from the dsb's first currently
358  * unsampled frame (writepos), translating frequency (pitch), stereo/mono
359  * and bits-per-sample so that it is ideal for the primary buffer.
360  * Doesn't perform any mixing - this is a straight copy/convert operation.
361  *
362  * dsb = the secondary buffer
363  * writepos = Starting position of changed buffer
364  * len = number of bytes to resample from writepos
365  *
366  * NOTE: writepos + len <= buflen. When called by mixer, MixOne makes sure of this.
367  */
368 static void DSOUND_MixToTemporary(IDirectSoundBufferImpl *dsb, DWORD frames)
369 {
370         UINT size_bytes = frames * sizeof(float) * dsb->device->pwfx->nChannels;
371
372         if (dsb->device->tmp_buffer_len < size_bytes || !dsb->device->tmp_buffer)
373         {
374                 dsb->device->tmp_buffer_len = size_bytes;
375                 if (dsb->device->tmp_buffer)
376                         dsb->device->tmp_buffer = HeapReAlloc(GetProcessHeap(), 0, dsb->device->tmp_buffer, size_bytes);
377                 else
378                         dsb->device->tmp_buffer = HeapAlloc(GetProcessHeap(), 0, size_bytes);
379         }
380
381         cp_fields(dsb, frames, &dsb->freqAcc);
382 }
383
384 static void DSOUND_MixerVol(const IDirectSoundBufferImpl *dsb, INT frames)
385 {
386         INT     i;
387         float vLeft, vRight;
388         UINT channels = dsb->device->pwfx->nChannels, chan;
389
390         TRACE("(%p,%d)\n",dsb,frames);
391         TRACE("left = %x, right = %x\n", dsb->volpan.dwTotalLeftAmpFactor,
392                 dsb->volpan.dwTotalRightAmpFactor);
393
394         if ((!(dsb->dsbd.dwFlags & DSBCAPS_CTRLPAN) || (dsb->volpan.lPan == 0)) &&
395             (!(dsb->dsbd.dwFlags & DSBCAPS_CTRLVOLUME) || (dsb->volpan.lVolume == 0)) &&
396              !(dsb->dsbd.dwFlags & DSBCAPS_CTRL3D))
397                 return; /* Nothing to do */
398
399         if (channels != 1 && channels != 2)
400         {
401                 FIXME("There is no support for %u channels\n", channels);
402                 return;
403         }
404
405         vLeft = dsb->volpan.dwTotalLeftAmpFactor / ((float)0xFFFF);
406         vRight = dsb->volpan.dwTotalRightAmpFactor / ((float)0xFFFF);
407         for(i = 0; i < frames; ++i){
408                 for(chan = 0; chan < channels; ++chan){
409                         if(chan == 0)
410                                 dsb->device->tmp_buffer[i * channels + chan] *= vLeft;
411                         else
412                                 dsb->device->tmp_buffer[i * channels + chan] *= vRight;
413                 }
414         }
415 }
416
417 /**
418  * Mix (at most) the given number of bytes into the given position of the
419  * device buffer, from the secondary buffer "dsb" (starting at the current
420  * mix position for that buffer).
421  *
422  * Returns the number of bytes actually mixed into the device buffer. This
423  * will match fraglen unless the end of the secondary buffer is reached
424  * (and it is not looping).
425  *
426  * dsb  = the secondary buffer to mix from
427  * writepos = position (offset) in device buffer to write at
428  * fraglen = number of bytes to mix
429  */
430 static DWORD DSOUND_MixInBuffer(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD fraglen)
431 {
432         INT len = fraglen;
433         float *ibuf;
434         DWORD oldpos;
435         UINT frames = fraglen / dsb->device->pwfx->nBlockAlign;
436
437         TRACE("sec_mixpos=%d/%d\n", dsb->sec_mixpos, dsb->buflen);
438         TRACE("(%p,%d,%d)\n",dsb,writepos,fraglen);
439
440         if (len % dsb->device->pwfx->nBlockAlign) {
441                 INT nBlockAlign = dsb->device->pwfx->nBlockAlign;
442                 ERR("length not a multiple of block size, len = %d, block size = %d\n", len, nBlockAlign);
443                 len -= len % nBlockAlign; /* data alignment */
444         }
445
446         /* Resample buffer to temporary buffer specifically allocated for this purpose, if needed */
447         oldpos = dsb->sec_mixpos;
448
449         DSOUND_MixToTemporary(dsb, frames);
450         ibuf = dsb->device->tmp_buffer;
451
452         /* Apply volume if needed */
453         DSOUND_MixerVol(dsb, frames);
454
455         mixieee32(ibuf, dsb->device->mix_buffer, frames * dsb->device->pwfx->nChannels);
456
457         /* check for notification positions */
458         if (dsb->dsbd.dwFlags & DSBCAPS_CTRLPOSITIONNOTIFY &&
459             dsb->state != STATE_STARTING) {
460                 INT ilen = DSOUND_BufPtrDiff(dsb->buflen, dsb->sec_mixpos, oldpos);
461                 DSOUND_CheckEvent(dsb, oldpos, ilen);
462         }
463
464         return len;
465 }
466
467 /**
468  * Mix some frames from the given secondary buffer "dsb" into the device
469  * primary buffer.
470  *
471  * dsb = the secondary buffer
472  * playpos = the current play position in the device buffer (primary buffer)
473  * writepos = the current safe-to-write position in the device buffer
474  * mixlen = the maximum number of bytes in the primary buffer to mix, from the
475  *          current writepos.
476  *
477  * Returns: the number of bytes beyond the writepos that were mixed.
478  */
479 static DWORD DSOUND_MixOne(IDirectSoundBufferImpl *dsb, DWORD writepos, DWORD mixlen)
480 {
481         DWORD primary_done = 0;
482
483         TRACE("(%p,%d,%d)\n",dsb,writepos,mixlen);
484         TRACE("writepos=%d, mixlen=%d\n", writepos, mixlen);
485         TRACE("looping=%d, leadin=%d\n", dsb->playflags, dsb->leadin);
486
487         /* If leading in, only mix about 20 ms, and 'skip' mixing the rest, for more fluid pointer advancement */
488         /* FIXME: Is this needed? */
489         if (dsb->leadin && dsb->state == STATE_STARTING) {
490                 if (mixlen > 2 * dsb->device->fraglen) {
491                         primary_done = mixlen - 2 * dsb->device->fraglen;
492                         mixlen = 2 * dsb->device->fraglen;
493                         writepos += primary_done;
494                         dsb->sec_mixpos += (primary_done / dsb->device->pwfx->nBlockAlign) *
495                                 dsb->pwfx->nBlockAlign * dsb->freqAdjust;
496                 }
497         }
498
499         dsb->leadin = FALSE;
500
501         TRACE("mixlen (primary) = %i\n", mixlen);
502
503         /* First try to mix to the end of the buffer if possible
504          * Theoretically it would allow for better optimization
505         */
506         primary_done += DSOUND_MixInBuffer(dsb, writepos, mixlen);
507
508         TRACE("total mixed data=%d\n", primary_done);
509
510         /* Report back the total prebuffered amount for this buffer */
511         return primary_done;
512 }
513
514 /**
515  * For a DirectSoundDevice, go through all the currently playing buffers and
516  * mix them in to the device buffer.
517  *
518  * writepos = the current safe-to-write position in the primary buffer
519  * mixlen = the maximum amount to mix into the primary buffer
520  *          (beyond the current writepos)
521  * recover = true if the sound device may have been reset and the write
522  *           position in the device buffer changed
523  * all_stopped = reports back if all buffers have stopped
524  *
525  * Returns:  the length beyond the writepos that was mixed to.
526  */
527
528 static void DSOUND_MixToPrimary(const DirectSoundDevice *device, DWORD writepos, DWORD mixlen, BOOL recover, BOOL *all_stopped)
529 {
530         INT i;
531         IDirectSoundBufferImpl  *dsb;
532
533         /* unless we find a running buffer, all have stopped */
534         *all_stopped = TRUE;
535
536         TRACE("(%d,%d,%d)\n", writepos, mixlen, recover);
537         for (i = 0; i < device->nrofbuffers; i++) {
538                 dsb = device->buffers[i];
539
540                 TRACE("MixToPrimary for %p, state=%d\n", dsb, dsb->state);
541
542                 if (dsb->buflen && dsb->state) {
543                         TRACE("Checking %p, mixlen=%d\n", dsb, mixlen);
544                         RtlAcquireResourceShared(&dsb->lock, TRUE);
545                         /* if buffer is stopping it is stopped now */
546                         if (dsb->state == STATE_STOPPING) {
547                                 dsb->state = STATE_STOPPED;
548                                 DSOUND_CheckEvent(dsb, 0, 0);
549                         } else if (dsb->state != STATE_STOPPED) {
550
551                                 /* if the buffer was starting, it must be playing now */
552                                 if (dsb->state == STATE_STARTING)
553                                         dsb->state = STATE_PLAYING;
554
555                                 /* mix next buffer into the main buffer */
556                                 DSOUND_MixOne(dsb, writepos, mixlen);
557
558                                 *all_stopped = FALSE;
559                         }
560                         RtlReleaseResource(&dsb->lock);
561                 }
562         }
563 }
564
565 /**
566  * Add buffers to the emulated wave device system.
567  *
568  * device = The current dsound playback device
569  * force = If TRUE, the function will buffer up as many frags as possible,
570  *         even though and will ignore the actual state of the primary buffer.
571  *
572  * Returns:  None
573  */
574
575 static void DSOUND_WaveQueue(DirectSoundDevice *device, BOOL force)
576 {
577         DWORD prebuf_frames, prebuf_bytes, read_offs_bytes;
578         BYTE *buffer;
579         HRESULT hr;
580
581         TRACE("(%p)\n", device);
582
583         read_offs_bytes = (device->playing_offs_bytes + device->in_mmdev_bytes) % device->buflen;
584
585         TRACE("read_offs_bytes = %u, playing_offs_bytes = %u, in_mmdev_bytes: %u, prebuf = %u\n",
586                 read_offs_bytes, device->playing_offs_bytes, device->in_mmdev_bytes, device->prebuf);
587
588         if (!force)
589         {
590                 if(device->mixpos < device->playing_offs_bytes)
591                         prebuf_bytes = device->mixpos + device->buflen - device->playing_offs_bytes;
592                 else
593                         prebuf_bytes = device->mixpos - device->playing_offs_bytes;
594         }
595         else
596                 /* buffer the maximum amount of frags */
597                 prebuf_bytes = device->prebuf * device->fraglen;
598
599         /* limit to the queue we have left */
600         if(device->in_mmdev_bytes + prebuf_bytes > device->prebuf * device->fraglen)
601                 prebuf_bytes = device->prebuf * device->fraglen - device->in_mmdev_bytes;
602
603         TRACE("prebuf_bytes = %u\n", prebuf_bytes);
604
605         if(!prebuf_bytes)
606                 return;
607
608         device->in_mmdev_bytes += prebuf_bytes;
609
610         if(prebuf_bytes + read_offs_bytes > device->buflen){
611                 DWORD chunk_bytes = device->buflen - read_offs_bytes;
612                 prebuf_frames = chunk_bytes / device->pwfx->nBlockAlign;
613                 prebuf_bytes -= chunk_bytes;
614         }else{
615                 prebuf_frames = prebuf_bytes / device->pwfx->nBlockAlign;
616                 prebuf_bytes = 0;
617         }
618
619         hr = IAudioRenderClient_GetBuffer(device->render, prebuf_frames, &buffer);
620         if(FAILED(hr)){
621                 WARN("GetBuffer failed: %08x\n", hr);
622                 return;
623         }
624
625         memcpy(buffer, device->buffer + read_offs_bytes,
626                         prebuf_frames * device->pwfx->nBlockAlign);
627
628         hr = IAudioRenderClient_ReleaseBuffer(device->render, prebuf_frames, 0);
629         if(FAILED(hr)){
630                 WARN("ReleaseBuffer failed: %08x\n", hr);
631                 return;
632         }
633
634         /* check if anything wrapped */
635         if(prebuf_bytes > 0){
636                 prebuf_frames = prebuf_bytes / device->pwfx->nBlockAlign;
637
638                 hr = IAudioRenderClient_GetBuffer(device->render, prebuf_frames, &buffer);
639                 if(FAILED(hr)){
640                         WARN("GetBuffer failed: %08x\n", hr);
641                         return;
642                 }
643
644                 memcpy(buffer, device->buffer, prebuf_frames * device->pwfx->nBlockAlign);
645
646                 hr = IAudioRenderClient_ReleaseBuffer(device->render, prebuf_frames, 0);
647                 if(FAILED(hr)){
648                         WARN("ReleaseBuffer failed: %08x\n", hr);
649                         return;
650                 }
651         }
652
653         TRACE("in_mmdev_bytes now = %i\n", device->in_mmdev_bytes);
654 }
655
656 /**
657  * Perform mixing for a Direct Sound device. That is, go through all the
658  * secondary buffers (the sound bites currently playing) and mix them in
659  * to the primary buffer (the device buffer).
660  *
661  * The mixing procedure goes:
662  *
663  * secondary->buffer (secondary format)
664  *   =[Resample]=> device->tmp_buffer (float format)
665  *   =[Volume]=> device->tmp_buffer (float format)
666  *   =[Mix]=> device->mix_buffer (float format)
667  *   =[Reformat]=> device->buffer (device format)
668  */
669 static void DSOUND_PerformMix(DirectSoundDevice *device)
670 {
671         UINT32 pad, to_mix_frags, to_mix_bytes;
672         HRESULT hr;
673
674         TRACE("(%p)\n", device);
675
676         /* **** */
677         EnterCriticalSection(&device->mixlock);
678
679         hr = IAudioClient_GetCurrentPadding(device->client, &pad);
680         if(FAILED(hr)){
681                 WARN("GetCurrentPadding failed: %08x\n", hr);
682                 LeaveCriticalSection(&device->mixlock);
683                 return;
684         }
685
686         to_mix_frags = device->prebuf - (pad * device->pwfx->nBlockAlign + device->fraglen - 1) / device->fraglen;
687
688         to_mix_bytes = to_mix_frags * device->fraglen;
689
690         if(device->in_mmdev_bytes > 0){
691                 DWORD delta_bytes = min(to_mix_bytes, device->in_mmdev_bytes);
692                 device->in_mmdev_bytes -= delta_bytes;
693                 device->playing_offs_bytes += delta_bytes;
694                 device->playing_offs_bytes %= device->buflen;
695         }
696
697         if (device->priolevel != DSSCL_WRITEPRIMARY) {
698                 BOOL recover = FALSE, all_stopped = FALSE;
699                 DWORD playpos, writepos, writelead, maxq, prebuff_max, prebuff_left, size1, size2;
700                 LPVOID buf1, buf2;
701                 int nfiller;
702
703                 /* the sound of silence */
704                 nfiller = device->pwfx->wBitsPerSample == 8 ? 128 : 0;
705
706                 /* get the position in the primary buffer */
707                 if (DSOUND_PrimaryGetPosition(device, &playpos, &writepos) != 0){
708                         LeaveCriticalSection(&(device->mixlock));
709                         return;
710                 }
711
712                 TRACE("primary playpos=%d, writepos=%d, clrpos=%d, mixpos=%d, buflen=%d\n",
713                         playpos,writepos,device->playpos,device->mixpos,device->buflen);
714                 assert(device->playpos < device->buflen);
715
716                 /* calc maximum prebuff */
717                 prebuff_max = (device->prebuf * device->fraglen);
718
719                 /* check how close we are to an underrun. It occurs when the writepos overtakes the mixpos */
720                 prebuff_left = DSOUND_BufPtrDiff(device->buflen, device->mixpos, playpos);
721                 writelead = DSOUND_BufPtrDiff(device->buflen, writepos, playpos);
722
723                 /* check for underrun. underrun occurs when the write position passes the mix position
724                  * also wipe out just-played sound data */
725                 if((prebuff_left > prebuff_max) || (device->state == STATE_STOPPED) || (device->state == STATE_STARTING)){
726                         if (device->state == STATE_STOPPING || device->state == STATE_PLAYING)
727                                 WARN("Probable buffer underrun\n");
728                         else TRACE("Buffer starting or buffer underrun\n");
729
730                         /* recover mixing for all buffers */
731                         recover = TRUE;
732
733                         /* reset mix position to write position */
734                         device->mixpos = writepos;
735
736                         ZeroMemory(device->buffer, device->buflen);
737                 } else if (playpos < device->playpos) {
738                         buf1 = device->buffer + device->playpos;
739                         buf2 = device->buffer;
740                         size1 = device->buflen - device->playpos;
741                         size2 = playpos;
742                         FillMemory(buf1, size1, nfiller);
743                         if (playpos && (!buf2 || !size2))
744                                 FIXME("%d: (%d, %d)=>(%d, %d) There should be an additional buffer here!!\n", __LINE__, device->playpos, device->mixpos, playpos, writepos);
745                         FillMemory(buf2, size2, nfiller);
746                 } else {
747                         buf1 = device->buffer + device->playpos;
748                         buf2 = NULL;
749                         size1 = playpos - device->playpos;
750                         size2 = 0;
751                         FillMemory(buf1, size1, nfiller);
752                 }
753                 device->playpos = playpos;
754
755                 /* find the maximum we can prebuffer from current write position */
756                 maxq = (writelead < prebuff_max) ? (prebuff_max - writelead) : 0;
757
758                 TRACE("prebuff_left = %d, prebuff_max = %dx%d=%d, writelead=%d\n",
759                         prebuff_left, device->prebuf, device->fraglen, prebuff_max, writelead);
760
761                 ZeroMemory(device->mix_buffer, device->mix_buffer_len);
762
763                 /* do the mixing */
764                 DSOUND_MixToPrimary(device, writepos, maxq, recover, &all_stopped);
765
766                 if (maxq + writepos > device->buflen)
767                 {
768                         DWORD todo = device->buflen - writepos;
769                         DWORD offs_float = (todo / device->pwfx->nBlockAlign) * device->pwfx->nChannels;
770                         device->normfunction(device->mix_buffer, device->buffer + writepos, todo);
771                         device->normfunction(device->mix_buffer + offs_float, device->buffer, maxq - todo);
772                 }
773                 else
774                         device->normfunction(device->mix_buffer, device->buffer + writepos, maxq);
775
776                 /* update the mix position, taking wrap-around into account */
777                 device->mixpos = writepos + maxq;
778                 device->mixpos %= device->buflen;
779
780                 /* update prebuff left */
781                 prebuff_left = DSOUND_BufPtrDiff(device->buflen, device->mixpos, playpos);
782
783                 /* check if have a whole fragment */
784                 if (prebuff_left >= device->fraglen){
785
786                         /* update the wave queue */
787                         DSOUND_WaveQueue(device, FALSE);
788
789                         /* buffers are full. start playing if applicable */
790                         if(device->state == STATE_STARTING){
791                                 TRACE("started primary buffer\n");
792                                 if(DSOUND_PrimaryPlay(device) != DS_OK){
793                                         WARN("DSOUND_PrimaryPlay failed\n");
794                                 }
795                                 else{
796                                         /* we are playing now */
797                                         device->state = STATE_PLAYING;
798                                 }
799                         }
800
801                         /* buffers are full. start stopping if applicable */
802                         if(device->state == STATE_STOPPED){
803                                 TRACE("restarting primary buffer\n");
804                                 if(DSOUND_PrimaryPlay(device) != DS_OK){
805                                         WARN("DSOUND_PrimaryPlay failed\n");
806                                 }
807                                 else{
808                                         /* start stopping again. as soon as there is no more data, it will stop */
809                                         device->state = STATE_STOPPING;
810                                 }
811                         }
812                 }
813
814                 /* if device was stopping, its for sure stopped when all buffers have stopped */
815                 else if((all_stopped == TRUE) && (device->state == STATE_STOPPING)){
816                         TRACE("All buffers have stopped. Stopping primary buffer\n");
817                         device->state = STATE_STOPPED;
818
819                         /* stop the primary buffer now */
820                         DSOUND_PrimaryStop(device);
821                 }
822
823         } else {
824
825                 DSOUND_WaveQueue(device, TRUE);
826
827                 /* in the DSSCL_WRITEPRIMARY mode, the app is totally in charge... */
828                 if (device->state == STATE_STARTING) {
829                         if (DSOUND_PrimaryPlay(device) != DS_OK)
830                                 WARN("DSOUND_PrimaryPlay failed\n");
831                         else
832                                 device->state = STATE_PLAYING;
833                 }
834                 else if (device->state == STATE_STOPPING) {
835                         if (DSOUND_PrimaryStop(device) != DS_OK)
836                                 WARN("DSOUND_PrimaryStop failed\n");
837                         else
838                                 device->state = STATE_STOPPED;
839                 }
840         }
841
842         LeaveCriticalSection(&(device->mixlock));
843         /* **** */
844 }
845
846 void CALLBACK DSOUND_timer(UINT timerID, UINT msg, DWORD_PTR dwUser,
847                            DWORD_PTR dw1, DWORD_PTR dw2)
848 {
849         DirectSoundDevice * device = (DirectSoundDevice*)dwUser;
850         DWORD start_time =  GetTickCount();
851         DWORD end_time;
852         TRACE("(%d,%d,0x%lx,0x%lx,0x%lx)\n",timerID,msg,dwUser,dw1,dw2);
853         TRACE("entering at %d\n", start_time);
854
855         RtlAcquireResourceShared(&(device->buffer_list_lock), TRUE);
856
857         if (device->ref)
858                 DSOUND_PerformMix(device);
859
860         RtlReleaseResource(&(device->buffer_list_lock));
861
862         end_time = GetTickCount();
863         TRACE("completed processing at %d, duration = %d\n", end_time, end_time - start_time);
864 }