Fix DirectSound's primary buffer Volume and Pan handling

Francois Gouget fgouget at codeweavers.com
Wed Jul 21 11:13:11 CDT 2004


Currently the way DirectSound handles Volume and Pan is wrong:
  * whenever a primary buffer is created (e.g. in DirectSoundCreate()) 
we initialize an internal variable that sets the volume to the max and 
pan to the center.
  * we also call IDsDriverBuffer_SetVolumePan() or waveOutSetVolume() to 
set the system volume to that value. On my machine this has the same 
effect as
    amixer set PCM 100%
  * we ignore changes in the volume or balance made outside of 
DirectSound. So if the user does:
    amixer set PCM 50%
    And then the application calls GetVolume() it will still get 0 (i.e. 
max volume)

This is not what happens on Windows. On Windows primary buffers don't 
have their internal volume. Instead SetVolume()/SetPan() and 
GetVolume()/GetPan() query directly the underlying volume.

I hacked the dsound.c test to verify this. Basically, now it does the 
following:
  * it calls GetVolume() and GetPan() and prints the results
  * it sleeps for 20 seconds
  * during these 20 seconds, start Windows' mixer control, and change 
the 'Wave Sound' (translated from French) volume and/or balance.
  * the test then calls GetVolume() and GetPan() and prints the results 
again

Running the test we can see that the second time DierctSound returns the 
new volume:

dsound.c:771:Primary volume=-566 pan=0
dsound.c:776:Primary volume=-566 pan=0
--- the test sleeps here while we change the volume ---
dsound.c:782:Primary volume=-1438 pan=0


I attached the dsound.c test patch so this can be cross-checked. You 
will note that I also played with the secondary buffers to check the 
effect changing the volume on a secondary buffer would have on the 
system volume: it has none but Wine already does the right thing (a bit 
surprisingly).
(for secondary buffers SetVolume() has a call to 
IDsDriverBuffer_SetVolumePan() but that branch is not taken so we do a 
DSOUND_ForceRemix() instead. I'm not sure if we should ever go through 
this IDsDriverBuffer_SetVolumePan() branch)

I also patched DirectSound to fix the problem. The one thing that 
bothers me is that I did not find the equivalent of an 
IDsDriverBuffer_GetVolumePan(). I.e. we can use the driver to set the 
volume but not to query it. That's pretty strange. I wonder how 
DirectSound gets the system volume on Windows.

In Wine what I did is simply use waveOutGetVolume() on dsound->hwo. Then 
I pass this through DSOUND_AmpFactorToVolPan() to convert these 
'AmpFactors' into the regular Vol/Pan used by DirectSound.

Comment and improvement suggestions welcome.
If nobody objects I'll submit this to wine-patches tomorrow.

-- 
Francois Gouget
fgouget at codeweavers.com

-------------- next part --------------
Index: dlls/dsound/tests/dsound.c
===================================================================
RCS file: /var/cvs/wine/dlls/dsound/tests/dsound.c,v
retrieving revision 1.30
diff -u -r1.30 dsound.c
--- dlls/dsound/tests/dsound.c	21 Jul 2004 03:23:13 -0000	1.30
+++ dlls/dsound/tests/dsound.c	21 Jul 2004 12:27:48 -0000
@@ -745,11 +745,11 @@
     primary=NULL;
     ZeroMemory(&bufdesc, sizeof(bufdesc));
     bufdesc.dwSize=sizeof(bufdesc);
-    bufdesc.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_CTRLVOLUME;
+    bufdesc.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLPAN;
     rc=IDirectSound_CreateSoundBuffer(dso,&bufdesc,&primary,NULL);
     ok(rc==DS_OK && primary!=NULL,"CreateSoundBuffer failed to create a primary buffer: 0x%lx\n",rc);
     if (rc==DS_OK && primary!=NULL) {
-        LONG vol;
+        LONG vol,pan;
 
         /* Try to create a second primary buffer */
         /* DSOUND: Error: The primary buffer already exists.  Any changes made to the buffer description will be ignored. */
@@ -765,7 +765,21 @@
         ok(rc!=DS_OK,"IDirectSound_DuplicateSoundBuffer primary buffer should have failed 0x%lx\n",rc);
 
         rc=IDirectSoundBuffer_GetVolume(primary,&vol);
-        ok(rc==DS_OK,"GetVolume failed: 0x%lx\n",rc);
+        ok(rc==DS_OK,"Primary GetVolume failed: %s\n",DXGetErrorString9(rc));
+        rc=IDirectSoundBuffer_GetPan(primary,&pan);
+        ok(rc==DS_OK,"Primary GetPan failed: %s\n",DXGetErrorString9(rc));
+        trace("Primary volume=%ld pan=%ld\n",vol,pan);
+        rc=IDirectSoundBuffer_SetVolume(primary,vol);
+        ok(rc==DS_OK,"Primary SetVolume failed: %s\n",DXGetErrorString9(rc));
+        rc=IDirectSoundBuffer_SetPan(primary,pan);
+        ok(rc==DS_OK,"Primary SetPan failed: %s\n",DXGetErrorString9(rc));
+        trace("Primary volume=%ld pan=%ld\n",vol,pan);
+        Sleep(20000);
+        rc=IDirectSoundBuffer_GetVolume(primary,&vol);
+        ok(rc==DS_OK,"Primary GetVolume failed: %s\n",DXGetErrorString9(rc));
+        rc=IDirectSoundBuffer_GetPan(primary,&pan);
+        ok(rc==DS_OK,"Primary GetPan failed: %s\n",DXGetErrorString9(rc));
+        trace("Primary volume=%ld pan=%ld\n",vol,pan);
 
         if (winetest_interactive)
         {
@@ -828,7 +842,7 @@
 
     ZeroMemory(&bufdesc, sizeof(bufdesc));
     bufdesc.dwSize=sizeof(bufdesc);
-    bufdesc.dwFlags=DSBCAPS_PRIMARYBUFFER;
+    bufdesc.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLPAN;
     rc=IDirectSound_CreateSoundBuffer(dso,&bufdesc,&primary,NULL);
     ok(rc==DS_OK && primary!=NULL,"CreateSoundBuffer failed to create a primary buffer 0x%lx\n", rc);
 
@@ -838,7 +852,7 @@
             secondary=NULL;
             ZeroMemory(&bufdesc, sizeof(bufdesc));
             bufdesc.dwSize=sizeof(bufdesc);
-            bufdesc.dwFlags=DSBCAPS_GETCURRENTPOSITION2;
+            bufdesc.dwFlags=DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_CTRLVOLUME|DSBCAPS_CTRLPAN;
             bufdesc.dwBufferBytes=wfx.nAvgBytesPerSec*BUFFER_LEN/1000;
             bufdesc.lpwfxFormat=&wfx;
             trace("  Testing a secondary buffer at %ldx%dx%d\n",
@@ -847,6 +861,27 @@
             ok(rc==DS_OK && secondary!=NULL,"CreateSoundBuffer failed to create a secondary buffer 0x%lx\n",rc);
 
             if (rc==DS_OK && secondary!=NULL) {
+                LONG vol,pan;
+
+                rc=IDirectSoundBuffer_GetVolume(secondary,&vol);
+                ok(rc==DS_OK,"GetVolume(secondary) failed: %s\n",DXGetErrorString9(rc));
+                rc=IDirectSoundBuffer_GetPan(secondary,&pan);
+                ok(rc==DS_OK,"GetPan(secondary) failed: %s\n",DXGetErrorString9(rc));
+                trace("Secondary volume=%ld pan=%ld\n",vol,pan);
+                vol=-1800;
+                rc=IDirectSoundBuffer_SetVolume(secondary,vol);
+                ok(rc==DS_OK,"SetVolume(secondary) failed: %s\n",DXGetErrorString9(rc));
+                rc=IDirectSoundBuffer_GetVolume(secondary,&vol);
+                ok(rc==DS_OK,"GetVolume(secondary) failed: %s\n",DXGetErrorString9(rc));
+                rc=IDirectSoundBuffer_GetPan(secondary,&pan);
+                ok(rc==DS_OK,"GetPan(secondary) failed: %s\n",DXGetErrorString9(rc));
+                trace("Secondary volume=%ld pan=%ld\n",vol,pan);
+                rc=IDirectSoundBuffer_GetVolume(primary,&vol);
+                ok(rc==DS_OK,"Primary GetVolume failed: %s\n",DXGetErrorString9(rc));
+                rc=IDirectSoundBuffer_GetPan(primary,&pan);
+                ok(rc==DS_OK,"Primary GetPan failed: %s\n",DXGetErrorString9(rc));
+                trace("Primary volume=%ld pan=%ld\n",vol,pan);
+
                 test_buffer(dso,secondary,0,FALSE,0,FALSE,0,winetest_interactive,1.0,0,NULL,0,0);
 
                 ref=IDirectSoundBuffer_Release(secondary);
-------------- next part --------------
Index: dlls/dsound/dsound.c
===================================================================
RCS file: /var/cvs/wine/dlls/dsound/dsound.c,v
retrieving revision 1.6
diff -u -r1.6 dsound.c
--- dlls/dsound/dsound.c	19 Jul 2004 20:06:22 -0000	1.6
+++ dlls/dsound/dsound.c	21 Jul 2004 09:34:19 -0000
@@ -892,10 +892,6 @@
         pDS->drvcaps.dwPrimaryBuffers = 1;
     }
 
-    pDS->volpan.lVolume = 0;
-    pDS->volpan.lPan = 0;
-    DSOUND_RecalcVolPan(&(pDS->volpan));
-
     InitializeCriticalSection(&(pDS->mixlock));
     RtlInitializeResource(&(pDS->lock));
 
Index: dlls/dsound/dsound_private.h
===================================================================
RCS file: /var/cvs/wine/dlls/dsound/dsound_private.h,v
retrieving revision 1.19
diff -u -r1.19 dsound_private.h
--- dlls/dsound/dsound_private.h	21 Jul 2004 03:23:13 -0000	1.19
+++ dlls/dsound/dsound_private.h	21 Jul 2004 12:27:47 -0000
@@ -91,7 +91,6 @@
     IDirectSoundBufferImpl**    buffers;
     RTL_RWLOCK                  lock;
     CRITICAL_SECTION            mixlock;
-    DSVOLUMEPAN                 volpan;
     PrimaryBufferImpl*          primary;
     DSBUFFERDESC                dsbd;
     DWORD                       speaker_config;
@@ -450,6 +449,7 @@
 extern IClassFactoryImpl DSOUND_FULLDUPLEX_CF;
 
 void DSOUND_RecalcVolPan(PDSVOLUMEPAN volpan);
+void DSOUND_AmpFactorToVolPan(PDSVOLUMEPAN volpan);
 void DSOUND_RecalcFormat(IDirectSoundBufferImpl *dsb);
 
 /* primary.c */
Index: dlls/dsound/mixer.c
===================================================================
RCS file: /var/cvs/wine/dlls/dsound/mixer.c,v
retrieving revision 1.20
diff -u -r1.20 mixer.c
--- dlls/dsound/mixer.c	12 Jan 2004 21:02:22 -0000	1.20
+++ dlls/dsound/mixer.c	21 Jul 2004 11:21:55 -0000
@@ -20,6 +20,7 @@
  */
 
 #include "config.h"
+#define _GNU_SOURCE     /* for round() in math.h */
 #include <assert.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -54,6 +55,7 @@
 	double temp;
 	TRACE("(%p)\n",volpan);
 
+	TRACE("Vol=%ld Pan=%ld\n", volpan->lVolume, volpan->lPan);
 	/* the AmpFactors are expressed in 16.16 fixed point */
 	volpan->dwVolAmpFactor = (ULONG) (pow(2.0, volpan->lVolume / 600.0) * 0xffff);
 	/* FIXME: dwPan{Left|Right}AmpFactor */
@@ -67,6 +69,39 @@
 	TRACE("left = %lx, right = %lx\n", volpan->dwTotalLeftAmpFactor, volpan->dwTotalRightAmpFactor);
 }
 
+void DSOUND_AmpFactorToVolPan(PDSVOLUMEPAN volpan)
+{
+    double left,right;
+    TRACE("(%p)\n",volpan);
+
+    TRACE("left=%lx, right=%lx\n",volpan->dwTotalLeftAmpFactor,volpan->dwTotalRightAmpFactor);
+    if (volpan->dwTotalLeftAmpFactor==0)
+        left=-10000;
+    else
+        left=600 * log(((double)volpan->dwTotalLeftAmpFactor) / 0xffff) / log(2);
+    if (volpan->dwTotalRightAmpFactor==0)
+        right=-10000;
+    else
+        right=600 * log(((double)volpan->dwTotalRightAmpFactor) / 0xffff) / log(2);
+    if (left<right)
+    {
+        volpan->lVolume=round(right);
+        volpan->dwVolAmpFactor=volpan->dwTotalRightAmpFactor;
+    }
+    else
+    {
+        volpan->lVolume=round(left);
+        volpan->dwVolAmpFactor=volpan->dwTotalLeftAmpFactor;
+    }
+    if (volpan->lVolume < -10000)
+        volpan->lVolume=-10000;
+    volpan->lPan=round(right-left);
+    if (volpan->lPan < -10000)
+        volpan->lPan=-10000;
+
+    TRACE("Vol=%ld Pan=%ld\n", volpan->lVolume, volpan->lPan);
+}
+
 void DSOUND_RecalcFormat(IDirectSoundBufferImpl *dsb)
 {
 	DWORD sw;
Index: dlls/dsound/primary.c
===================================================================
RCS file: /var/cvs/wine/dlls/dsound/primary.c,v
retrieving revision 1.25
diff -u -r1.25 primary.c
--- dlls/dsound/primary.c	13 Jul 2004 23:35:09 -0000	1.25
+++ dlls/dsound/primary.c	21 Jul 2004 11:06:06 -0000
@@ -74,8 +74,6 @@
 	HRESULT err = DS_OK;
 	TRACE("(%p)\n",This);
 
-	DSOUND_RecalcVolPan(&(This->volpan));
-
 	/* are we using waveOut stuff? */
 	if (!This->driver) {
 		LPBYTE newbuf;
@@ -135,11 +133,6 @@
 		}
 		if ((err == DS_OK) && (merr != DS_OK))
 			err = merr;
-
-		if (!err) {
-			DWORD vol = (This->volpan.dwTotalLeftAmpFactor & 0xffff) | (This->volpan.dwTotalRightAmpFactor << 16);
-			err = mmErr(waveOutSetVolume(This->hwo, vol));
-		}
 	} else {
 		if (!This->hwbuf) {
 			err = IDsDriver_CreateSoundBuffer(This->driver,&(This->wfx),
@@ -154,7 +147,6 @@
 			if (dsound->state == STATE_PLAYING) dsound->state = STATE_STARTING;
 			else if (dsound->state == STATE_STOPPING) dsound->state = STATE_STOPPED;
 		}
-		err = IDsDriverBuffer_SetVolumePan(This->hwbuf, &(This->volpan));
 	}
 
 	return err;
@@ -458,7 +450,8 @@
 ) {
 	ICOM_THIS(PrimaryBufferImpl,iface);
 	IDirectSoundImpl* dsound = This->dsound;
-	LONG oldVol;
+	DWORD ampfactors;
+	DSVOLUMEPAN volpan;
 
 	TRACE("(%p,%ld)\n",This,vol);
 
@@ -475,24 +468,26 @@
 	/* **** */
 	EnterCriticalSection(&(dsound->mixlock));
 
-	oldVol = dsound->volpan.lVolume;
-	dsound->volpan.lVolume = vol;
-	DSOUND_RecalcVolPan(&dsound->volpan);
-
-	if (vol != oldVol) {
-		if (dsound->hwbuf) {
-			HRESULT hres;
-			hres = IDsDriverBuffer_SetVolumePan(dsound->hwbuf, &(dsound->volpan));
-			if (hres != DS_OK) {
-				LeaveCriticalSection(&(dsound->mixlock));
-				WARN("IDsDriverBuffer_SetVolumePan failed\n");
-				return hres;
-			}
-		} else {
-			DWORD vol = (dsound->volpan.dwTotalLeftAmpFactor & 0xffff) | (dsound->volpan.dwTotalRightAmpFactor << 16);
-			waveOutSetVolume(dsound->hwo, vol);
-		}
-	}
+        waveOutGetVolume(dsound->hwo, &ampfactors);
+        volpan.dwTotalLeftAmpFactor=ampfactors & 0xffff;
+        volpan.dwTotalRightAmpFactor=ampfactors >> 16;
+        DSOUND_AmpFactorToVolPan(&volpan);
+        if (vol != volpan.lVolume) {
+            volpan.lVolume=vol;
+            DSOUND_RecalcVolPan(&volpan);
+            if (dsound->hwbuf) {
+                HRESULT hres;
+                hres = IDsDriverBuffer_SetVolumePan(dsound->hwbuf, &volpan);
+                if (hres != DS_OK) {
+                    LeaveCriticalSection(&(dsound->mixlock));
+                    WARN("IDsDriverBuffer_SetVolumePan failed\n");
+                    return hres;
+                }
+            } else {
+                ampfactors = (volpan.dwTotalLeftAmpFactor & 0xffff) | (volpan.dwTotalRightAmpFactor << 16);
+                waveOutSetVolume(dsound->hwo, ampfactors);
+            }
+        }
 
 	LeaveCriticalSection(&(dsound->mixlock));
 	/* **** */
@@ -504,6 +499,8 @@
 	LPDIRECTSOUNDBUFFER8 iface,LPLONG vol
 ) {
 	ICOM_THIS(PrimaryBufferImpl,iface);
+	DWORD ampfactors;
+	DSVOLUMEPAN volpan;
 	TRACE("(%p,%p)\n",This,vol);
 
 	if (!(This->dsound->dsbd.dwFlags & DSBCAPS_CTRLVOLUME)) {
@@ -516,7 +513,11 @@
 		return DSERR_INVALIDPARAM;
 	}
 
-	*vol = This->dsound->volpan.lVolume;
+	waveOutGetVolume(dsound->hwo, &ampfactors);
+	volpan.dwTotalLeftAmpFactor=ampfactors & 0xffff;
+	volpan.dwTotalRightAmpFactor=ampfactors >> 16;
+	DSOUND_AmpFactorToVolPan(&volpan);
+	*vol = volpan.lVolume;
 	return DS_OK;
 }
 
@@ -769,7 +770,8 @@
 ) {
 	ICOM_THIS(PrimaryBufferImpl,iface);
 	IDirectSoundImpl* dsound = This->dsound;
-	LONG oldPan;
+	DWORD ampfactors;
+	DSVOLUMEPAN volpan;
 
 	TRACE("(%p,%ld)\n",This,pan);
 
@@ -786,25 +788,27 @@
 	/* **** */
 	EnterCriticalSection(&(dsound->mixlock));
 
-	oldPan = dsound->volpan.lPan;
-	dsound->volpan.lPan = pan;
-	DSOUND_RecalcVolPan(&dsound->volpan);
-
-	if (pan != oldPan) {
-		if (dsound->hwbuf) {
-			HRESULT hres;
-			hres = IDsDriverBuffer_SetVolumePan(dsound->hwbuf, &(dsound->volpan));
-			if (hres != DS_OK) {
-				LeaveCriticalSection(&(dsound->mixlock));
-				WARN("IDsDriverBuffer_SetVolumePan failed\n");
-				return hres;
-			}
-		}
-		else {
-			DWORD vol = (dsound->volpan.dwTotalLeftAmpFactor & 0xffff) | (dsound->volpan.dwTotalRightAmpFactor << 16);
-			waveOutSetVolume(dsound->hwo, vol);
-		}
-	}
+        waveOutGetVolume(dsound->hwo, &ampfactors);
+        volpan.dwTotalLeftAmpFactor=ampfactors & 0xffff;
+        volpan.dwTotalRightAmpFactor=ampfactors >> 16;
+        DSOUND_AmpFactorToVolPan(&volpan);
+        if (pan != volpan.lPan) {
+            volpan.lPan=pan;
+            DSOUND_RecalcVolPan(&volpan);
+            if (dsound->hwbuf) {
+                HRESULT hres;
+                hres = IDsDriverBuffer_SetVolumePan(dsound->hwbuf, &volpan);
+                if (hres != DS_OK) {
+                    LeaveCriticalSection(&(dsound->mixlock));
+                    WARN("IDsDriverBuffer_SetVolumePan failed\n");
+                    return hres;
+                }
+            }
+            else {
+                ampfactors = (volpan.dwTotalLeftAmpFactor & 0xffff) | (volpan.dwTotalRightAmpFactor << 16);
+                waveOutSetVolume(dsound->hwo, ampfactors);
+            }
+        }
 
 	LeaveCriticalSection(&(dsound->mixlock));
 	/* **** */
@@ -816,6 +820,8 @@
 	LPDIRECTSOUNDBUFFER8 iface,LPLONG pan
 ) {
 	ICOM_THIS(PrimaryBufferImpl,iface);
+	DWORD ampfactors;
+	DSVOLUMEPAN volpan;
 	TRACE("(%p,%p)\n",This,pan);
 
 	if (!(This->dsound->dsbd.dwFlags & DSBCAPS_CTRLPAN)) {
@@ -828,8 +834,11 @@
 		return DSERR_INVALIDPARAM;
 	}
 
-	*pan = This->dsound->volpan.lPan;
-
+	waveOutGetVolume(dsound->hwo, &ampfactors);
+	volpan.dwTotalLeftAmpFactor=ampfactors & 0xffff;
+	volpan.dwTotalRightAmpFactor=ampfactors >> 16;
+	DSOUND_AmpFactorToVolPan(&volpan);
+	*pan = volpan.lPan;
 	return DS_OK;
 }
 


More information about the wine-devel mailing list