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(a)codeweavers.com
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, &factors);
+ 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, &factors);
+ 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, &factors);
+ 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, &factors);
+ volpan.dwTotalLeftAmpFactor=ampfactors & 0xffff;
+ volpan.dwTotalRightAmpFactor=ampfactors >> 16;
+ DSOUND_AmpFactorToVolPan(&volpan);
+ *pan = volpan.lPan;
return DS_OK;
}