* 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, &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; }