- make parameter checking and error notification consistent
[wine] / dlls / dsound / buffer.c
1 /*                      DirectSound
2  *
3  * Copyright 1998 Marcus Meissner
4  * Copyright 1998 Rob Riggs
5  * Copyright 2000-2002 TransGaming Technologies, Inc.
6  *
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.
11  *
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.
16  *
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
20  */
21
22 #include "config.h"
23 #include <assert.h>
24 #include <stdio.h>
25 #include <sys/types.h>
26 #include <sys/fcntl.h>
27 #ifdef HAVE_UNISTD_H
28 # include <unistd.h>
29 #endif
30 #include <stdlib.h>
31 #include <string.h>
32 #include <math.h>       /* Insomnia - pow() function */
33
34 #include "windef.h"
35 #include "winbase.h"
36 #include "wingdi.h"
37 #include "winuser.h"
38 #include "winerror.h"
39 #include "mmsystem.h"
40 #include "winternl.h"
41 #include "mmddk.h"
42 #include "wine/windef16.h"
43 #include "wine/debug.h"
44 #include "dsound.h"
45 #include "dsdriver.h"
46 #include "dsound_private.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(dsound);
49
50 /*******************************************************************************
51  *              IDirectSoundNotify
52  */
53 static HRESULT WINAPI IDirectSoundNotifyImpl_QueryInterface(
54         LPDIRECTSOUNDNOTIFY iface,REFIID riid,LPVOID *ppobj
55 ) {
56         ICOM_THIS(IDirectSoundNotifyImpl,iface);
57         TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
58
59         if ( IsEqualGUID(riid, &IID_IUnknown) || 
60              IsEqualGUID(riid, &IID_IDirectSoundNotify) ||
61              IsEqualGUID(riid, &IID_IDirectSoundNotify8) ) {
62                 IDirectSoundNotify_AddRef(iface);
63                 *ppobj = This;
64                 return DS_OK;
65         }
66
67         FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
68
69         *ppobj = 0;
70
71         return E_NOINTERFACE;
72 }
73
74 static ULONG WINAPI IDirectSoundNotifyImpl_AddRef(LPDIRECTSOUNDNOTIFY iface) {
75         ICOM_THIS(IDirectSoundNotifyImpl,iface);
76         DWORD ref;
77
78         TRACE("(%p) ref was %ld\n", This, This->ref);
79
80         ref = InterlockedIncrement(&(This->ref));
81         return ref;
82 }
83
84 static ULONG WINAPI IDirectSoundNotifyImpl_Release(LPDIRECTSOUNDNOTIFY iface) {
85         ICOM_THIS(IDirectSoundNotifyImpl,iface);
86         DWORD ref;
87
88         TRACE("(%p) ref was %ld\n", This, This->ref);
89
90         ref = InterlockedDecrement(&(This->ref));
91         /* FIXME: A notification should be a part of a buffer rather than pointed 
92          * to from a buffer. Hence the -1 ref count */
93         if (ref == -1) {
94                 if (This->notifies != NULL)
95                         HeapFree(GetProcessHeap(), 0, This->notifies);
96
97                 HeapFree(GetProcessHeap(),0,This);
98                 return 0;
99         }
100         return ref;
101 }
102
103 static HRESULT WINAPI IDirectSoundNotifyImpl_SetNotificationPositions(
104         LPDIRECTSOUNDNOTIFY iface,DWORD howmuch,LPCDSBPOSITIONNOTIFY notify
105 ) {
106         ICOM_THIS(IDirectSoundNotifyImpl,iface);
107         TRACE("(%p,0x%08lx,%p)\n",This,howmuch,notify);
108
109         if (notify == NULL) {
110             WARN("invalid parameter: notify == NULL\n");
111             return DSERR_INVALIDPARAM;
112         }
113
114         if (TRACE_ON(dsound)) {
115             int i;
116             for (i=0;i<howmuch;i++)
117                 TRACE("notify at %ld to 0x%08lx\n",
118                     notify[i].dwOffset,(DWORD)notify[i].hEventNotify);
119         }
120
121         if (This->hwnotify) {
122             HRESULT hres;
123             hres = IDsDriverNotify_SetNotificationPositions(This->hwnotify, howmuch, notify);
124             if (hres != DS_OK)
125                     WARN("IDsDriverNotify_SetNotificationPositions failed\n");
126             return hres;
127         } else {
128             /* Make an internal copy of the caller-supplied array.
129              * Replace the existing copy if one is already present. */
130             This->notifies = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
131                 This->notifies, howmuch * sizeof(DSBPOSITIONNOTIFY));
132             if (This->notifies == NULL) {
133                     WARN("out of memory\n");
134                     return DSERR_OUTOFMEMORY;
135             }
136             memcpy(This->notifies, notify, howmuch * sizeof(DSBPOSITIONNOTIFY));
137             This->nrofnotifies = howmuch;
138         }
139
140         return S_OK;
141 }
142
143 ICOM_VTABLE(IDirectSoundNotify) dsnvt =
144 {
145         ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
146         IDirectSoundNotifyImpl_QueryInterface,
147         IDirectSoundNotifyImpl_AddRef,
148         IDirectSoundNotifyImpl_Release,
149         IDirectSoundNotifyImpl_SetNotificationPositions,
150 };
151
152 /*******************************************************************************
153  *              IDirectSoundBuffer
154  */
155
156 static HRESULT WINAPI IDirectSoundBufferImpl_SetFormat(
157         LPDIRECTSOUNDBUFFER8 iface,LPWAVEFORMATEX wfex
158 ) {
159         ICOM_THIS(IDirectSoundBufferImpl,iface);
160
161         TRACE("(%p,%p)\n",This,wfex);
162         /* This method is not available on secondary buffers */
163         WARN("invalid call\n");
164         return DSERR_INVALIDCALL;
165 }
166
167 static HRESULT WINAPI IDirectSoundBufferImpl_SetVolume(
168         LPDIRECTSOUNDBUFFER8 iface,LONG vol
169 ) {
170         ICOM_THIS(IDirectSoundBufferImpl,iface);
171         LONG oldVol;
172
173         TRACE("(%p,%ld)\n",This,vol);
174
175         if (!(This->dsbd.dwFlags & DSBCAPS_CTRLVOLUME)) {
176                 WARN("control unavailable: This->dsbd.dwFlags = 0x%08lx\n", This->dsbd.dwFlags);
177                 return DSERR_CONTROLUNAVAIL;
178         }
179
180         if ((vol > DSBVOLUME_MAX) || (vol < DSBVOLUME_MIN)) {
181                 WARN("invalid parameter: vol = %ld\n", vol);
182                 return DSERR_INVALIDPARAM;
183         }
184
185         /* **** */
186         EnterCriticalSection(&(This->lock));
187
188         if (This->dsbd.dwFlags & DSBCAPS_CTRL3D) {
189                 oldVol = This->ds3db->lVolume;
190                 This->ds3db->lVolume = vol;
191         } else {
192                 oldVol = This->volpan.lVolume;
193                 This->volpan.lVolume = vol;
194                 if (vol != oldVol) 
195                         DSOUND_RecalcVolPan(&(This->volpan));
196         }
197
198         if (vol != oldVol) {
199                 if (This->hwbuf) {
200                         HRESULT hres;
201                         hres = IDsDriverBuffer_SetVolumePan(This->hwbuf, &(This->volpan));
202                         if (hres != DS_OK)
203                                 WARN("IDsDriverBuffer_SetVolumePan failed\n");
204                 } else 
205                         DSOUND_ForceRemix(This);
206         }
207
208         LeaveCriticalSection(&(This->lock));
209         /* **** */
210
211         return DS_OK;
212 }
213
214 static HRESULT WINAPI IDirectSoundBufferImpl_GetVolume(
215         LPDIRECTSOUNDBUFFER8 iface,LPLONG vol
216 ) {
217         ICOM_THIS(IDirectSoundBufferImpl,iface);
218         TRACE("(%p,%p)\n",This,vol);
219
220         if (vol == NULL) {
221                 WARN("invalid parameter: vol == NULL\n");
222                 return DSERR_INVALIDPARAM;
223         }
224
225         if (This->dsbd.dwFlags & DSBCAPS_CTRL3D)
226                 *vol = This->ds3db->lVolume;
227         else
228                 *vol = This->volpan.lVolume;
229         return DS_OK;
230 }
231
232 static HRESULT WINAPI IDirectSoundBufferImpl_SetFrequency(
233         LPDIRECTSOUNDBUFFER8 iface,DWORD freq
234 ) {
235         ICOM_THIS(IDirectSoundBufferImpl,iface);
236         DWORD oldFreq;
237
238         TRACE("(%p,%ld)\n",This,freq);
239
240         if (!(This->dsbd.dwFlags & DSBCAPS_CTRLFREQUENCY)) {
241                 WARN("control unavailable\n");
242                 return DSERR_CONTROLUNAVAIL;
243         }
244
245         if (freq == DSBFREQUENCY_ORIGINAL)
246                 freq = This->wfx.nSamplesPerSec;
247
248         if ((freq < DSBFREQUENCY_MIN) || (freq > DSBFREQUENCY_MAX)) {
249                 WARN("invalid parameter: freq = %ld\n", freq);
250                 return DSERR_INVALIDPARAM;
251         }
252
253         /* **** */
254         EnterCriticalSection(&(This->lock));
255
256         oldFreq = This->freq;
257         This->freq = freq;
258         if (freq != oldFreq) {
259                 This->freqAdjust = (freq << DSOUND_FREQSHIFT) / This->dsound->wfx.nSamplesPerSec;
260                 This->nAvgBytesPerSec = freq * This->wfx.nBlockAlign;
261                 DSOUND_RecalcFormat(This);
262                 if (!This->hwbuf) 
263                         DSOUND_ForceRemix(This);
264         }
265
266         LeaveCriticalSection(&(This->lock));
267         /* **** */
268
269         return DS_OK;
270 }
271
272 static HRESULT WINAPI IDirectSoundBufferImpl_Play(
273         LPDIRECTSOUNDBUFFER8 iface,DWORD reserved1,DWORD reserved2,DWORD flags
274 ) {
275         HRESULT hres = DS_OK;
276         ICOM_THIS(IDirectSoundBufferImpl,iface);
277         TRACE("(%p,%08lx,%08lx,%08lx)\n",This,reserved1,reserved2,flags);
278
279         /* **** */
280         EnterCriticalSection(&(This->lock));
281
282         This->playflags = flags;
283         if (This->state == STATE_STOPPED) {
284                 This->leadin = TRUE;
285                 This->startpos = This->buf_mixpos;
286                 This->state = STATE_STARTING;
287         } else if (This->state == STATE_STOPPING)
288                 This->state = STATE_PLAYING;
289         if (This->hwbuf) {
290                 hres = IDsDriverBuffer_Play(This->hwbuf, 0, 0, This->playflags);
291                 if (hres != DS_OK)
292                         WARN("IDsDriverBuffer_Play failed\n");
293                 else
294                         This->state = STATE_PLAYING;
295         }
296
297         LeaveCriticalSection(&(This->lock));
298         /* **** */
299
300         return hres;
301 }
302
303 static HRESULT WINAPI IDirectSoundBufferImpl_Stop(LPDIRECTSOUNDBUFFER8 iface)
304 {
305         HRESULT hres = DS_OK;
306         ICOM_THIS(IDirectSoundBufferImpl,iface);
307         TRACE("(%p)\n",This);
308
309         /* **** */
310         EnterCriticalSection(&(This->lock));
311
312         if (This->state == STATE_PLAYING)
313                 This->state = STATE_STOPPING;
314         else if (This->state == STATE_STARTING)
315                 This->state = STATE_STOPPED;
316         if (This->hwbuf) {
317                 hres = IDsDriverBuffer_Stop(This->hwbuf);
318                 if (hres != DS_OK)
319                         WARN("IDsDriverBuffer_Stop failed\n");
320                 else
321                         This->state = STATE_STOPPED;
322         }
323         DSOUND_CheckEvent(This, 0);
324
325         LeaveCriticalSection(&(This->lock));
326         /* **** */
327
328         return hres;
329 }
330
331 static DWORD WINAPI IDirectSoundBufferImpl_AddRef(LPDIRECTSOUNDBUFFER8 iface) {
332         ICOM_THIS(IDirectSoundBufferImpl,iface);
333         DWORD ref;
334
335         TRACE("(%p) ref was %ld, thread is %04lx\n",This, This->ref, GetCurrentThreadId());
336
337         ref = InterlockedIncrement(&(This->ref));
338         if (!ref) {
339                 FIXME("thread-safety alert! AddRef-ing with a zero refcount!\n");
340         }
341         return ref;
342 }
343 static DWORD WINAPI IDirectSoundBufferImpl_Release(LPDIRECTSOUNDBUFFER8 iface) {
344         ICOM_THIS(IDirectSoundBufferImpl,iface);
345         int     i;
346         DWORD ref;
347
348         TRACE("(%p) ref was %ld, thread is %04lx\n",This, This->ref, GetCurrentThreadId());
349
350         ref = InterlockedDecrement(&(This->ref));
351         if (ref) return ref;
352
353         RtlAcquireResourceExclusive(&(This->dsound->lock), TRUE);
354         for (i=0;i<This->dsound->nrofbuffers;i++)
355                 if (This->dsound->buffers[i] == This)
356                         break;
357
358         if (i < This->dsound->nrofbuffers) {
359                 /* Put the last buffer of the list in the (now empty) position */
360                 This->dsound->buffers[i] = This->dsound->buffers[This->dsound->nrofbuffers - 1];
361                 This->dsound->nrofbuffers--;
362                 This->dsound->buffers = HeapReAlloc(GetProcessHeap(),0,This->dsound->buffers,sizeof(LPDIRECTSOUNDBUFFER8)*This->dsound->nrofbuffers);
363                 TRACE("buffer count is now %d\n", This->dsound->nrofbuffers);
364                 IDirectSound_Release((LPDIRECTSOUND)This->dsound);
365         }
366         RtlReleaseResource(&(This->dsound->lock));
367
368         DeleteCriticalSection(&(This->lock));
369         if (This->hwbuf) {
370                 IDsDriverBuffer_Release(This->hwbuf);
371                 if (This->dsound->drvdesc.dwFlags & DSDDESC_USESYSTEMMEMORY)
372                         HeapFree(GetProcessHeap(),0,This->buffer);
373         }
374         else if (!This->parent)
375                 HeapFree(GetProcessHeap(),0,This->buffer);
376         if (This->ds3db) {
377                 IDirectSound3DBuffer_Release((LPDIRECTSOUND3DBUFFER)This->ds3db);
378         }
379         if (This->dsound->listener) {
380                 if (IDirectSound3DListener_Release((LPDIRECTSOUND3DLISTENER)This->dsound->listener) == 0)
381                         This->dsound->listener = 0;
382         }
383         if (This->iks) {
384                 HeapFree(GetProcessHeap(), 0, This->iks);
385         }
386         if (This->parent)
387                 IDirectSoundBuffer8_Release((LPDIRECTSOUNDBUFFER8)This->parent);
388
389         HeapFree(GetProcessHeap(),0,This);
390
391         return 0;
392 }
393
394 DWORD DSOUND_CalcPlayPosition(IDirectSoundBufferImpl *This,
395                               DWORD state, DWORD pplay, DWORD pwrite, DWORD pmix, DWORD bmix)
396 {
397         DWORD bplay;
398
399         TRACE("primary playpos=%ld, mixpos=%ld\n", pplay, pmix);
400         TRACE("this mixpos=%ld, time=%ld\n", bmix, GetTickCount());
401
402         /* the actual primary play position (pplay) is always behind last mixed (pmix),
403          * unless the computer is too slow or something */
404         /* we need to know how far away we are from there */
405 #if 0 /* we'll never fill the primary entirely */
406         if (pmix == pplay) {
407                 if ((state == STATE_PLAYING) || (state == STATE_STOPPING)) {
408                         /* wow, the software mixer is really doing well,
409                          * seems the entire primary buffer is filled! */
410                         pmix += This->dsound->buflen;
411                 }
412                 /* else: the primary buffer is not playing, so probably empty */
413         }
414 #endif
415         if (pmix < pplay) pmix += This->dsound->buflen; /* wraparound */
416         pmix -= pplay;
417         /* detect buffer underrun */
418         if (pwrite < pplay) pwrite += This->dsound->buflen; /* wraparound */
419         pwrite -= pplay;
420         if (pmix > (ds_snd_queue_max * This->dsound->fraglen + pwrite + This->dsound->writelead)) {
421                 WARN("detected an underrun: primary queue was %ld\n",pmix);
422                 pmix = 0;
423         }
424         /* divide the offset by its sample size */
425         pmix /= This->dsound->wfx.nBlockAlign;
426         TRACE("primary back-samples=%ld\n",pmix);
427         /* adjust for our frequency */
428         pmix = (pmix * This->freqAdjust) >> DSOUND_FREQSHIFT;
429         /* multiply by our own sample size */
430         pmix *= This->wfx.nBlockAlign;
431         TRACE("this back-offset=%ld\n", pmix);
432         /* subtract from our last mixed position */
433         bplay = bmix;
434         while (bplay < pmix) bplay += This->buflen; /* wraparound */
435         bplay -= pmix;
436         if (This->leadin && ((bplay < This->startpos) || (bplay > bmix))) {
437                 /* seems we haven't started playing yet */
438                 TRACE("this still in lead-in phase\n");
439                 bplay = This->startpos;
440         }
441         /* return the result */
442         return bplay;
443 }
444
445 static HRESULT WINAPI IDirectSoundBufferImpl_GetCurrentPosition(
446         LPDIRECTSOUNDBUFFER8 iface,LPDWORD playpos,LPDWORD writepos
447 ) {
448         HRESULT hres;
449         ICOM_THIS(IDirectSoundBufferImpl,iface);
450         TRACE("(%p,%p,%p)\n",This,playpos,writepos);
451         if (This->hwbuf) {
452                 hres=IDsDriverBuffer_GetPosition(This->hwbuf,playpos,writepos);
453                 if (hres != DS_OK) {
454                     WARN("IDsDriverBuffer_GetPosition failed\n");
455                     return hres;
456                 }
457         }
458         else {
459                 if (playpos && (This->state != STATE_PLAYING)) {
460                         /* we haven't been merged into the primary buffer (yet) */
461                         *playpos = This->buf_mixpos;
462                 }
463                 else if (playpos) {
464                         DWORD pplay, pwrite, lplay, splay, pstate;
465                         /* let's get this exact; first, recursively call GetPosition on the primary */
466                         EnterCriticalSection(&(This->dsound->mixlock));
467                         if (DSOUND_PrimaryGetPosition(This->dsound, &pplay, &pwrite) != DS_OK)
468                                 WARN("DSOUND_PrimaryGetPosition failed\n");
469                         /* detect HEL mode underrun */
470                         pstate = This->dsound->state;
471                         if (!(This->dsound->hwbuf || This->dsound->pwqueue)) {
472                                 TRACE("detected an underrun\n");
473                                 /* pplay = ? */
474                                 if (pstate == STATE_PLAYING)
475                                         pstate = STATE_STARTING;
476                                 else if (pstate == STATE_STOPPING)
477                                         pstate = STATE_STOPPED;
478                         }
479                         /* get data for ourselves while we still have the lock */
480                         pstate &= This->state;
481                         lplay = This->primary_mixpos;
482                         splay = This->buf_mixpos;
483                         if ((This->dsbd.dwFlags & DSBCAPS_GETCURRENTPOSITION2) || This->dsound->hwbuf) {
484                                 /* calculate play position using this */
485                                 *playpos = DSOUND_CalcPlayPosition(This, pstate, pplay, pwrite, lplay, splay);
486                         } else {
487                                 /* (unless the app isn't using GETCURRENTPOSITION2) */
488                                 /* don't know exactly how this should be handled...
489                                  * the docs says that play cursor is reported as directly
490                                  * behind write cursor, hmm... */
491                                 /* let's just do what might work for Half-Life */
492                                 DWORD wp;
493                                 wp = (This->dsound->pwplay + ds_hel_margin) * This->dsound->fraglen;
494                                 while (wp >= This->dsound->buflen)
495                                         wp -= This->dsound->buflen;
496                                 *playpos = DSOUND_CalcPlayPosition(This, pstate, wp, pwrite, lplay, splay);
497                         }
498                         LeaveCriticalSection(&(This->dsound->mixlock));
499                 }
500                 if (writepos) *writepos = This->buf_mixpos;
501         }
502         if (writepos) {
503                 if (This->state != STATE_STOPPED)
504                         /* apply the documented 10ms lead to writepos */
505                         *writepos += This->writelead;
506                 while (*writepos >= This->buflen) *writepos -= This->buflen;
507         }
508         if (playpos) This->last_playpos = *playpos;
509         TRACE("playpos = %ld, writepos = %ld (%p, time=%ld)\n", playpos?*playpos:0, writepos?*writepos:0, This, GetTickCount());
510         return DS_OK;
511 }
512
513 static HRESULT WINAPI IDirectSoundBufferImpl_GetStatus(
514         LPDIRECTSOUNDBUFFER8 iface,LPDWORD status
515 ) {
516         ICOM_THIS(IDirectSoundBufferImpl,iface);
517         TRACE("(%p,%p), thread is %04lx\n",This,status,GetCurrentThreadId());
518
519         if (status == NULL) {
520                 WARN("invalid parameter: status = NULL\n");
521                 return DSERR_INVALIDPARAM;
522         }
523
524         *status = 0;
525         if ((This->state == STATE_STARTING) || (This->state == STATE_PLAYING)) {
526                 *status |= DSBSTATUS_PLAYING;
527                 if (This->playflags & DSBPLAY_LOOPING)
528                         *status |= DSBSTATUS_LOOPING;
529         }
530
531         TRACE("status=%lx\n", *status);
532         return DS_OK;
533 }
534
535
536 static HRESULT WINAPI IDirectSoundBufferImpl_GetFormat(
537         LPDIRECTSOUNDBUFFER8 iface,LPWAVEFORMATEX lpwf,DWORD wfsize,LPDWORD wfwritten
538 ) {
539         ICOM_THIS(IDirectSoundBufferImpl,iface);
540         TRACE("(%p,%p,%ld,%p)\n",This,lpwf,wfsize,wfwritten);
541
542         if (wfsize>sizeof(This->wfx))
543                 wfsize = sizeof(This->wfx);
544         if (lpwf) {     /* NULL is valid */
545                 memcpy(lpwf,&(This->wfx),wfsize);
546                 if (wfwritten)
547                         *wfwritten = wfsize;
548         } else {
549                 if (wfwritten)
550                         *wfwritten = sizeof(This->wfx);
551                 else {
552                         WARN("invalid parameter: wfwritten == NULL\n");
553                         return DSERR_INVALIDPARAM;
554                 }
555         }
556
557         return DS_OK;
558 }
559
560 static HRESULT WINAPI IDirectSoundBufferImpl_Lock(
561         LPDIRECTSOUNDBUFFER8 iface,DWORD writecursor,DWORD writebytes,LPVOID lplpaudioptr1,LPDWORD audiobytes1,LPVOID lplpaudioptr2,LPDWORD audiobytes2,DWORD flags
562 ) {
563         HRESULT hres = DS_OK;
564         ICOM_THIS(IDirectSoundBufferImpl,iface);
565
566         TRACE("(%p,%ld,%ld,%p,%p,%p,%p,0x%08lx) at %ld\n",
567                 This,
568                 writecursor,
569                 writebytes,
570                 lplpaudioptr1,
571                 audiobytes1,
572                 lplpaudioptr2,
573                 audiobytes2,
574                 flags,
575                 GetTickCount()
576         );
577
578         if (flags & DSBLOCK_FROMWRITECURSOR) {
579                 DWORD writepos;
580                 /* GetCurrentPosition does too much magic to duplicate here */
581                 hres = IDirectSoundBufferImpl_GetCurrentPosition(iface, NULL, &writepos);
582                 if (hres != DS_OK) {
583                         WARN("IDirectSoundBufferImpl_GetCurrentPosition failed\n");
584                         return hres;
585                 }
586                 writecursor += writepos;
587         }
588         while (writecursor >= This->buflen)
589                 writecursor -= This->buflen;
590         if (flags & DSBLOCK_ENTIREBUFFER)
591                 writebytes = This->buflen;
592         if (writebytes > This->buflen)
593                 writebytes = This->buflen;
594
595         assert(audiobytes1!=audiobytes2);
596         assert(lplpaudioptr1!=lplpaudioptr2);
597
598         EnterCriticalSection(&(This->lock));
599
600         if ((writebytes == This->buflen) &&
601             ((This->state == STATE_STARTING) ||
602              (This->state == STATE_PLAYING)))
603                 /* some games, like Half-Life, try to be clever (not) and
604                  * keep one secondary buffer, and mix sounds into it itself,
605                  * locking the entire buffer every time... so we can just forget
606                  * about tracking the last-written-to-position... */
607                 This->probably_valid_to = (DWORD)-1;
608         else
609                 This->probably_valid_to = writecursor;
610
611         if (!(This->dsound->drvdesc.dwFlags & DSDDESC_DONTNEEDSECONDARYLOCK) && This->hwbuf) {
612                 hres = IDsDriverBuffer_Lock(This->hwbuf,
613                                      lplpaudioptr1, audiobytes1,
614                                      lplpaudioptr2, audiobytes2,
615                                      writecursor, writebytes,
616                                      0);
617                 if (hres != DS_OK) {
618                         WARN("IDsDriverBuffer_Lock failed\n");
619                         LeaveCriticalSection(&(This->lock));
620                         return hres;
621                 }
622         } else {
623                 BOOL remix = FALSE;
624                 if (writecursor+writebytes <= This->buflen) {
625                         *(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor;
626                         *audiobytes1 = writebytes;
627                         if (lplpaudioptr2)
628                                 *(LPBYTE*)lplpaudioptr2 = NULL;
629                         if (audiobytes2)
630                                 *audiobytes2 = 0;
631                         TRACE("->%ld.0\n",writebytes);
632                 } else {
633                         *(LPBYTE*)lplpaudioptr1 = This->buffer+writecursor;
634                         *audiobytes1 = This->buflen-writecursor;
635                         if (lplpaudioptr2)
636                                 *(LPBYTE*)lplpaudioptr2 = This->buffer;
637                         if (audiobytes2)
638                                 *audiobytes2 = writebytes-(This->buflen-writecursor);
639                         TRACE("->%ld.%ld\n",*audiobytes1,audiobytes2?*audiobytes2:0);
640                 }
641                 if (This->state == STATE_PLAYING) {
642                         /* if the segment between playpos and buf_mixpos is touched,
643                          * we need to cancel some mixing */
644                         /* we'll assume that the app always calls GetCurrentPosition before
645                          * locking a playing buffer, so that last_playpos is up-to-date */
646                         if (This->buf_mixpos >= This->last_playpos) {
647                                 if (This->buf_mixpos > writecursor &&
648                                     This->last_playpos < writecursor+writebytes)
649                                         remix = TRUE;
650                         }
651                         else {
652                                 if (This->buf_mixpos > writecursor ||
653                                     This->last_playpos < writecursor+writebytes)
654                                         remix = TRUE;
655                         }
656                         if (remix) {
657                                 TRACE("locking prebuffered region, ouch\n");
658                                 DSOUND_MixCancelAt(This, writecursor);
659                         }
660                 }
661         }
662
663         LeaveCriticalSection(&(This->lock));
664         return DS_OK;
665 }
666
667 static HRESULT WINAPI IDirectSoundBufferImpl_SetCurrentPosition(
668         LPDIRECTSOUNDBUFFER8 iface,DWORD newpos
669 ) {
670         HRESULT hres = DS_OK;
671         ICOM_THIS(IDirectSoundBufferImpl,iface);
672         TRACE("(%p,%ld)\n",This,newpos);
673
674         /* **** */
675         EnterCriticalSection(&(This->lock));
676
677         while (newpos >= This->buflen)
678                 newpos -= This->buflen;
679         This->buf_mixpos = newpos;
680         if (This->hwbuf) {
681                 hres = IDsDriverBuffer_SetPosition(This->hwbuf, This->buf_mixpos);
682                 if (hres != DS_OK)
683                         WARN("IDsDriverBuffer_SetPosition failed\n");
684         }
685
686         LeaveCriticalSection(&(This->lock));
687         /* **** */
688
689         return hres;
690 }
691
692 static HRESULT WINAPI IDirectSoundBufferImpl_SetPan(
693         LPDIRECTSOUNDBUFFER8 iface,LONG pan
694 ) {
695         HRESULT hres = DS_OK;
696         ICOM_THIS(IDirectSoundBufferImpl,iface);
697         LONG oldPan;
698
699         TRACE("(%p,%ld)\n",This,pan);
700
701         if ((pan > DSBPAN_RIGHT) || (pan < DSBPAN_LEFT)) {
702                 WARN("invalid parameter: pan = %ld\n", pan);
703                 return DSERR_INVALIDPARAM;
704         }
705
706         /* You cannot use both pan and 3D controls */
707         if (!(This->dsbd.dwFlags & DSBCAPS_CTRLPAN) ||
708             (This->dsbd.dwFlags & DSBCAPS_CTRL3D)) {
709                 WARN("control unavailable\n");
710                 return DSERR_CONTROLUNAVAIL;
711         }
712
713         /* **** */
714         EnterCriticalSection(&(This->lock));
715
716         oldPan = This->volpan.lPan;
717         This->volpan.lPan = pan;
718
719         if (pan != oldPan) {
720                 DSOUND_RecalcVolPan(&(This->volpan));
721
722                 if (This->hwbuf) {
723                         hres = IDsDriverBuffer_SetVolumePan(This->hwbuf, &(This->volpan));
724                         if (hres != DS_OK)
725                                 WARN("IDsDriverBuffer_SetVolumePan failed\n");
726                 } else 
727                         DSOUND_ForceRemix(This);
728         }
729
730         LeaveCriticalSection(&(This->lock));
731         /* **** */
732
733         return hres;
734 }
735
736 static HRESULT WINAPI IDirectSoundBufferImpl_GetPan(
737         LPDIRECTSOUNDBUFFER8 iface,LPLONG pan
738 ) {
739         ICOM_THIS(IDirectSoundBufferImpl,iface);
740         TRACE("(%p,%p)\n",This,pan);
741
742         if (pan == NULL) {
743                 WARN("invalid parameter: pan = NULL\n");
744                 return DSERR_INVALIDPARAM;
745         }
746
747         *pan = This->volpan.lPan;
748
749         return DS_OK;
750 }
751
752 static HRESULT WINAPI IDirectSoundBufferImpl_Unlock(
753         LPDIRECTSOUNDBUFFER8 iface,LPVOID p1,DWORD x1,LPVOID p2,DWORD x2
754 ) {
755         ICOM_THIS(IDirectSoundBufferImpl,iface);
756         DWORD probably_valid_to;
757
758         TRACE("(%p,%p,%ld,%p,%ld)\n", This,p1,x1,p2,x2);
759
760 #if 0
761         /* Preprocess 3D buffers... */
762
763         /* This is highly experimental and liable to break things */
764         if (This->dsbd.dwFlags & DSBCAPS_CTRL3D)
765                 DSOUND_Create3DBuffer(This);
766 #endif
767
768         if (!(This->dsound->drvdesc.dwFlags & DSDDESC_DONTNEEDSECONDARYLOCK) && This->hwbuf) {
769                 HRESULT hres;
770                 hres = IDsDriverBuffer_Unlock(This->hwbuf, p1, x1, p2, x2);
771                 if (hres != DS_OK) {
772                         WARN("IDsDriverBuffer_Unlock failed\n");
773                         return hres;
774                 }
775         }
776
777         if (p2) probably_valid_to = (((LPBYTE)p2)-This->buffer) + x2;
778         else probably_valid_to = (((LPBYTE)p1)-This->buffer) + x1;
779         while (probably_valid_to >= This->buflen)
780                 probably_valid_to -= This->buflen;
781         if ((probably_valid_to == 0) && ((x1+x2) == This->buflen) &&
782             ((This->state == STATE_STARTING) ||
783              (This->state == STATE_PLAYING)))
784                 /* see IDirectSoundBufferImpl_Lock */
785                 probably_valid_to = (DWORD)-1;
786         This->probably_valid_to = probably_valid_to;
787
788         return DS_OK;
789 }
790
791 static HRESULT WINAPI IDirectSoundBufferImpl_Restore(
792         LPDIRECTSOUNDBUFFER8 iface
793 ) {
794         ICOM_THIS(IDirectSoundBufferImpl,iface);
795         FIXME("(%p):stub\n",This);
796         return DS_OK;
797 }
798
799 static HRESULT WINAPI IDirectSoundBufferImpl_GetFrequency(
800         LPDIRECTSOUNDBUFFER8 iface,LPDWORD freq
801 ) {
802         ICOM_THIS(IDirectSoundBufferImpl,iface);
803         TRACE("(%p,%p)\n",This,freq);
804
805         if (freq == NULL) {
806                 WARN("invalid parameter: freq = NULL\n");
807                 return DSERR_INVALIDPARAM;
808         }
809
810         *freq = This->freq;
811         TRACE("-> %ld\n", *freq);
812
813         return DS_OK;
814 }
815
816 static HRESULT WINAPI IDirectSoundBufferImpl_SetFX(
817         LPDIRECTSOUNDBUFFER8 iface,DWORD dwEffectsCount,LPDSEFFECTDESC pDSFXDesc,LPDWORD pdwResultCodes
818 ) {
819         ICOM_THIS(IDirectSoundBufferImpl,iface);
820         DWORD u;
821
822         FIXME("(%p,%lu,%p,%p): stub\n",This,dwEffectsCount,pDSFXDesc,pdwResultCodes);
823
824         if (pdwResultCodes)
825                 for (u=0; u<dwEffectsCount; u++) pdwResultCodes[u] = DSFXR_UNKNOWN;
826
827         WARN("control unavailable\n");
828         return DSERR_CONTROLUNAVAIL;
829 }
830
831 static HRESULT WINAPI IDirectSoundBufferImpl_AcquireResources(
832         LPDIRECTSOUNDBUFFER8 iface,DWORD dwFlags,DWORD dwEffectsCount,LPDWORD pdwResultCodes
833 ) {
834         ICOM_THIS(IDirectSoundBufferImpl,iface);
835         DWORD u;
836
837         FIXME("(%p,%08lu,%lu,%p): stub\n",This,dwFlags,dwEffectsCount,pdwResultCodes);
838
839         if (pdwResultCodes)
840                 for (u=0; u<dwEffectsCount; u++) pdwResultCodes[u] = DSFXR_UNKNOWN;
841
842         WARN("control unavailable\n");
843         return DSERR_CONTROLUNAVAIL;
844 }
845
846 static HRESULT WINAPI IDirectSoundBufferImpl_GetObjectInPath(
847         LPDIRECTSOUNDBUFFER8 iface,REFGUID rguidObject,DWORD dwIndex,REFGUID rguidInterface,LPVOID* ppObject
848 ) {
849         ICOM_THIS(IDirectSoundBufferImpl,iface);
850
851         FIXME("(%p,%s,%lu,%s,%p): stub\n",This,debugstr_guid(rguidObject),dwIndex,debugstr_guid(rguidInterface),ppObject);
852
853         WARN("control unavailable\n");
854         return DSERR_CONTROLUNAVAIL;
855 }
856
857 static HRESULT WINAPI IDirectSoundBufferImpl_Initialize(
858         LPDIRECTSOUNDBUFFER8 iface,LPDIRECTSOUND8 dsound,LPDSBUFFERDESC dbsd
859 ) {
860         ICOM_THIS(IDirectSoundBufferImpl,iface);
861         FIXME("(%p,%p,%p):stub\n",This,dsound,dbsd);
862         DPRINTF("Re-Init!!!\n");
863         WARN("already initialized\n");
864         return DSERR_ALREADYINITIALIZED;
865 }
866
867 static HRESULT WINAPI IDirectSoundBufferImpl_GetCaps(
868         LPDIRECTSOUNDBUFFER8 iface,LPDSBCAPS caps
869 ) {
870         ICOM_THIS(IDirectSoundBufferImpl,iface);
871         TRACE("(%p)->(%p)\n",This,caps);
872
873         if (caps == NULL) {
874                 WARN("invalid parameter: caps == NULL\n");
875                 return DSERR_INVALIDPARAM;
876         }
877
878         if (caps->dwSize < sizeof(*caps)) {
879                 WARN("invalid parameter: caps->dwSize = %ld < %d\n",caps->dwSize, sizeof(*caps));
880                 return DSERR_INVALIDPARAM;
881         }
882
883         caps->dwFlags = This->dsbd.dwFlags;
884         if (This->hwbuf) caps->dwFlags |= DSBCAPS_LOCHARDWARE;
885         else caps->dwFlags |= DSBCAPS_LOCSOFTWARE;
886
887         caps->dwBufferBytes = This->buflen;
888
889         /* This value represents the speed of the "unlock" command.
890            As unlock is quite fast (it does not do anything), I put
891            4096 ko/s = 4 Mo / s */
892         /* FIXME: hwbuf speed */
893         caps->dwUnlockTransferRate = 4096;
894         caps->dwPlayCpuOverhead = 0;
895
896         return DS_OK;
897 }
898
899 static HRESULT WINAPI IDirectSoundBufferImpl_QueryInterface(
900         LPDIRECTSOUNDBUFFER8 iface,REFIID riid,LPVOID *ppobj
901 ) {
902         ICOM_THIS(IDirectSoundBufferImpl,iface);
903
904         TRACE("(%p,%s,%p)\n",This,debugstr_guid(riid),ppobj);
905
906         if ( IsEqualGUID( &IID_IDirectSoundNotify, riid ) ||
907              IsEqualGUID( &IID_IDirectSoundNotify8, riid ) ) {
908                 if (!This->notify) {
909                         This->notify = (IDirectSoundNotifyImpl*)HeapAlloc(GetProcessHeap(),
910                                 HEAP_ZERO_MEMORY,sizeof(*This->notify));
911                         if (This->notify) {
912                                 This->notify->ref = 0;  /* release when ref == -1 */
913                                 This->notify->lpVtbl = &dsnvt;
914                         }
915                 }
916                 if (This->notify) {
917                         IDirectSoundNotify_AddRef((LPDIRECTSOUNDNOTIFY)This->notify);
918                         *ppobj = (LPVOID)This->notify;
919                         return S_OK;
920                 }
921                 *ppobj = NULL;
922                 WARN("IID_IDirectSoundNotify\n");
923                 return E_FAIL;
924         }
925
926         if ( IsEqualGUID( &IID_IDirectSound3DBuffer, riid ) ) {
927                 if (!This->ds3db)
928                         IDirectSound3DBufferImpl_Create(This, &This->ds3db);
929                 *ppobj = This->ds3db;
930                 if (*ppobj) {
931                         IDirectSound3DBuffer_AddRef((LPDIRECTSOUND3DBUFFER)*ppobj);
932                         return S_OK;
933                 }
934                 *ppobj = NULL;
935                 WARN("IID_IDirectSound3DBuffer\n");
936                 return E_FAIL;
937         }
938
939         if ( IsEqualGUID( &IID_IDirectSound3DListener, riid ) ) {
940                 ERR("app requested IDirectSound3DListener on secondary buffer\n");
941                 *ppobj = NULL;
942                 return E_FAIL;
943         }
944
945         if ( IsEqualGUID( &IID_IKsPropertySet, riid ) ) {
946                 if (!This->iks)
947                         IKsPropertySetImpl_Create(This, &This->iks);
948                 *ppobj = This->iks;
949                 if (*ppobj) {
950                         IKsPropertySet_AddRef((LPKSPROPERTYSET)*ppobj);
951                         return S_OK;
952                 }
953                 *ppobj = NULL;
954                 WARN("IID_IKsPropertySet\n");
955                 return E_FAIL;
956         }
957
958         if ( IsEqualGUID( &IID_IDirectSoundBuffer8, riid ) ) {
959             IDirectSoundBuffer8_AddRef(iface);
960             *ppobj = This;
961             return NO_ERROR;
962         }
963
964         FIXME( "Unknown IID %s\n", debugstr_guid( riid ) );
965
966         *ppobj = NULL;
967
968         return E_NOINTERFACE;
969 }
970
971 static ICOM_VTABLE(IDirectSoundBuffer8) dsbvt =
972 {
973         ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
974         IDirectSoundBufferImpl_QueryInterface,
975         IDirectSoundBufferImpl_AddRef,
976         IDirectSoundBufferImpl_Release,
977         IDirectSoundBufferImpl_GetCaps,
978         IDirectSoundBufferImpl_GetCurrentPosition,
979         IDirectSoundBufferImpl_GetFormat,
980         IDirectSoundBufferImpl_GetVolume,
981         IDirectSoundBufferImpl_GetPan,
982         IDirectSoundBufferImpl_GetFrequency,
983         IDirectSoundBufferImpl_GetStatus,
984         IDirectSoundBufferImpl_Initialize,
985         IDirectSoundBufferImpl_Lock,
986         IDirectSoundBufferImpl_Play,
987         IDirectSoundBufferImpl_SetCurrentPosition,
988         IDirectSoundBufferImpl_SetFormat,
989         IDirectSoundBufferImpl_SetVolume,
990         IDirectSoundBufferImpl_SetPan,
991         IDirectSoundBufferImpl_SetFrequency,
992         IDirectSoundBufferImpl_Stop,
993         IDirectSoundBufferImpl_Unlock,
994         IDirectSoundBufferImpl_Restore,
995         IDirectSoundBufferImpl_SetFX,
996         IDirectSoundBufferImpl_AcquireResources,
997         IDirectSoundBufferImpl_GetObjectInPath
998 };
999
1000 HRESULT WINAPI SecondaryBuffer_Create(
1001         IDirectSoundImpl *This,
1002         IDirectSoundBufferImpl **pdsb,
1003         LPDSBUFFERDESC dsbd)
1004 {
1005         IDirectSoundBufferImpl *dsb;
1006         LPWAVEFORMATEX wfex = dsbd->lpwfxFormat;
1007         HRESULT err = DS_OK;
1008         DWORD capf = 0;
1009         int use_hw;
1010
1011         if (dsbd->dwBufferBytes < DSBSIZE_MIN || dsbd->dwBufferBytes > DSBSIZE_MAX) {
1012                 ERR("invalid parameter: dsbd->dwBufferBytes = %ld\n", dsbd->dwBufferBytes);
1013                 *pdsb = NULL;
1014                 return DSERR_INVALIDPARAM; /* FIXME: which error? */
1015         }
1016
1017         dsb = (IDirectSoundBufferImpl*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(*dsb));
1018
1019         if (dsb == 0) {
1020                 WARN("out of memory\n");
1021                 *pdsb = NULL;
1022                 return DSERR_OUTOFMEMORY;
1023         }
1024         dsb->ref = 1;
1025         dsb->dsound = This;
1026         dsb->parent = NULL;
1027         dsb->lpVtbl = &dsbvt;
1028
1029         memcpy(&dsb->dsbd, dsbd, sizeof(*dsbd));
1030         if (wfex)
1031                 memcpy(&dsb->wfx, wfex, sizeof(dsb->wfx));
1032
1033         TRACE("Created buffer at %p\n", dsb);
1034
1035         dsb->buflen = dsbd->dwBufferBytes;
1036         dsb->freq = dsbd->lpwfxFormat->nSamplesPerSec;
1037
1038         /* Check necessary hardware mixing capabilities */
1039         if (wfex->nChannels==2) capf |= DSCAPS_SECONDARYSTEREO;
1040         else capf |= DSCAPS_SECONDARYMONO;
1041         if (wfex->wBitsPerSample==16) capf |= DSCAPS_SECONDARY16BIT;
1042         else capf |= DSCAPS_SECONDARY8BIT;
1043         use_hw = (This->drvcaps.dwFlags & capf) == capf;
1044
1045         /* FIXME: check hardware sample rate mixing capabilities */
1046         /* FIXME: check app hints for software/hardware buffer (STATIC, LOCHARDWARE, etc) */
1047         /* FIXME: check whether any hardware buffers are left */
1048         /* FIXME: handle DSDHEAP_CREATEHEAP for hardware buffers */
1049
1050         /* Allocate system memory if applicable */
1051         if ((This->drvdesc.dwFlags & DSDDESC_USESYSTEMMEMORY) || !use_hw) {
1052                 dsb->buffer = (LPBYTE)HeapAlloc(GetProcessHeap(),0,dsb->buflen);
1053                 if (dsb->buffer == NULL) {
1054                         WARN("out of memory\n");
1055                         HeapFree(GetProcessHeap(),0,dsb);
1056                         *pdsb = NULL;
1057                         return DSERR_OUTOFMEMORY;
1058                 }
1059         }
1060
1061         /* Allocate the hardware buffer */
1062         if (use_hw) {
1063                 err = IDsDriver_CreateSoundBuffer(This->driver,wfex,dsbd->dwFlags,0,
1064                                                   &(dsb->buflen),&(dsb->buffer),
1065                                                   (LPVOID*)&(dsb->hwbuf));
1066                 if (err != DS_OK) {
1067                         WARN("IDsDriver_CreateSoundBuffer failed\n");
1068                         if (dsb->buffer)
1069                                 HeapFree(GetProcessHeap(),0,dsb->buffer);
1070                         HeapFree(GetProcessHeap(),0,dsb);
1071                         *pdsb = NULL;
1072                         return err;
1073                 }
1074         }
1075
1076         /* calculate fragment size and write lead */
1077         DSOUND_RecalcFormat(dsb);
1078
1079         /* It's not necessary to initialize values to zero since */
1080         /* we allocated this structure with HEAP_ZERO_MEMORY... */
1081         dsb->playpos = 0;
1082         dsb->buf_mixpos = 0;
1083         dsb->state = STATE_STOPPED;
1084
1085         dsb->freqAdjust = (dsb->freq << DSOUND_FREQSHIFT) /
1086                 This->wfx.nSamplesPerSec;
1087         dsb->nAvgBytesPerSec = dsb->freq *
1088                 dsbd->lpwfxFormat->nBlockAlign;
1089
1090         if (dsbd->dwFlags & DSBCAPS_CTRL3D) {
1091                 err = IDirectSound3DBufferImpl_Create(dsb, &dsb->ds3db);
1092                 if (err != DS_OK) {
1093                         WARN("IDirectSound3DBufferImpl_Create failed\n");
1094                         if (dsb->buffer)
1095                                 HeapFree(GetProcessHeap(),0,dsb->buffer);
1096                         HeapFree(GetProcessHeap(),0,dsb);
1097                         dsb = NULL;
1098                         return err;
1099                 }
1100         } else
1101                 DSOUND_RecalcVolPan(&(dsb->volpan));
1102
1103         InitializeCriticalSection(&(dsb->lock));
1104
1105         /* register buffer */
1106         RtlAcquireResourceExclusive(&(This->lock), TRUE);
1107         if (!(dsbd->dwFlags & DSBCAPS_PRIMARYBUFFER)) {
1108                 IDirectSoundBufferImpl **newbuffers = (IDirectSoundBufferImpl**)HeapReAlloc(GetProcessHeap(),0,This->buffers,sizeof(IDirectSoundBufferImpl*)*(This->nrofbuffers+1));
1109                 if (newbuffers) {
1110                         This->buffers = newbuffers;
1111                         This->buffers[This->nrofbuffers] = dsb;
1112                         This->nrofbuffers++;
1113                         TRACE("buffer count is now %d\n", This->nrofbuffers);
1114                 } else {
1115                         ERR("out of memory for buffer list! Current buffer count is %d\n", This->nrofbuffers);
1116                         if (dsb->buffer)
1117                                 HeapFree(GetProcessHeap(),0,dsb->buffer);
1118                         DeleteCriticalSection(&(dsb->lock));
1119                         RtlReleaseResource(&(This->lock));
1120                         HeapFree(GetProcessHeap(),0,dsb);
1121                         dsb = NULL;
1122                         return DSERR_OUTOFMEMORY;
1123                 }
1124         }
1125         RtlReleaseResource(&(This->lock));
1126         IDirectSound8_AddRef((LPDIRECTSOUND8)This);
1127         *pdsb = dsb;
1128         return S_OK;
1129 }