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
[EMAIL PROTECTED]

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);
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;
 }
 

Reply via email to