Fix DirectSound's primary buffer Volume and Pan handling

Francois Gouget fgouget at codeweavers.com
Fri Jul 23 12:10:36 CDT 2004


This patch is identical to the one I sent to wine-devel:
http://www.winehq.org/hypermail/wine-devel/2004/07/0305.html

Noone objected so I guess the patch is good to go<g>.

---

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

See the patch for the hacked dsound.c test in my previous email.


The patch removes the primary buffer's 'volpan' variable and instead 
uses waveOutGetVolume() on dsound->hwo to get the current volume. This 
is then converted from the 'AmplificationFactor' format to the 
traditional DirectSound Volume/Pan format by DSOUND_AmpFactorToVolPan(). 
Settnig the volume goes through IDsDriverBuffer_SetVolumePan() or 
waveOutSetVolume() as before.

Also of note is that DSOUND_AmpFactorToVolPan() uses round(). It's 
straneg that there's no problem getting ceil() or floor() but round() is 
more tricky. On Linux _GNU_SOURCE needs to be defined before math.h is 
included in order to get it. I hope this will be ok on FreeBSD too. If 
not we'll figure out something.


Changelog:

  * dlls/dsound/dsound.c
    dlls/dsound/dsound_private.h
    dlls/dsound/mixer.c
    dlls/dsound/primary.c

    Francois Gouget <fgouget at codeweavers.com>
    Fix the volume and balance of primary buffers so it is the system's 
volume and balance, like on Windows.


-- 
Francois Gouget
fgouget at codeweavers.com

-------------- next part --------------
Index: dlls/dsound/dsound.c
===================================================================
RCS file: /var/cvs/wine/dlls/dsound/dsound.c,v
retrieving revision 1.7
diff -u -r1.7 dsound.c
--- dlls/dsound/dsound.c	22 Jul 2004 19:42:11 -0000	1.7
+++ dlls/dsound/dsound.c	22 Jul 2004 23:11:47 -0000
@@ -922,10 +922,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-patches mailing list