PR #275: Attenuate speaker (and 8-bit DAC) output sample after 0.25s of inactivity.

. Cherry-pick from 'master' of https://github.com/rmacri/AppleWin into master:
This commit is contained in:
tomcw 2017-12-20 20:09:43 +00:00
parent 355f5d0dd7
commit 713efcdcb1

View File

@ -109,6 +109,64 @@ static void DisplayBenchmarkResults ()
MB_ICONINFORMATION | MB_SETFOREGROUND);
}
//=============================================================================
//
// DC filtering V2 (Riccardo Macri May 2015) (GH#275)
//
// To prevent loud clicks on Window's sound buffer underruns and constant DC
// being sent out to amplifiers (some soundcards are DC coupled) which is
// not good for them, an attenuator slowly drops the speaker output
// to 0 after the speaker (or 8 bit DAC) has been idle for a couple hundred
// milliseconds.
//
// The approach works as follows:
// - SpkrToggle() is called when the speaker state is flipped by accessing $C030
// - This brings audio up to date then calls ResetDCFilter()
// - ResetDCFilter() sets a counter to a high value
// - every audio sample is processed by DCFilter() as follows:
// - if the counter is >= 32768, the speaker has been recently toggled
// and the samples are unaffected
// - if the counter is < 32768 but > 0, it is used to scale the
// sample to reduce +ve or -ve speaker states towards zero
// - In the two cases above, the counter is decremented
// - if the counter is zero, the speaker has been silent for a while
// and the output is 0 regardless of the speaker state.
//
// - the initial "high value" is chosen so 10000/44100 = about a
// quarter of a second of speaker inactivity is needed before attenuation
// begins.
//
// NOTE: The attenuation is not ever reducing the level of audio, just
// the DC offset at which the speaker has been left.
//
// This approach has zero impact on any speaker tones including PWM
// due to the samples being unchanged for at least 0.25 seconds after
// any speaker activity.
//
static UINT g_uDCFilterState = 0;
inline void ResetDCFilter(void)
{
// reset the attenuator with an additional 250ms of full gain
// (10000 samples) before it starts attenuating
g_uDCFilterState = 32768 + 10000;
}
inline short DCFilter(short sample_in)
{
if (g_uDCFilterState == 0) // no sound for a while, stay 0
return 0;
if (g_uDCFilterState >= 32768) // full gain after recent sound
{
g_uDCFilterState--;
return sample_in;
}
return (((int)sample_in) * (g_uDCFilterState--)) / 32768; // scale & divide by 32768 (NB. Don't ">>15" as undefined behaviour)
}
//=============================================================================
static void SetClksPerSpkrSample()
@ -283,7 +341,7 @@ static void UpdateRemainderBuffer(ULONG* pnCycleDiff)
nSampleMean /= (signed long) g_nRemainderBufferSize;
if(g_nBufferIdx < SPKR_SAMPLE_RATE-1)
g_pSpeakerBuffer[g_nBufferIdx++] = (short) nSampleMean;
g_pSpeakerBuffer[g_nBufferIdx++] = DCFilter( (short)nSampleMean );
}
}
}
@ -301,7 +359,7 @@ static void UpdateSpkr()
ULONG nCyclesRemaining = (ULONG) ((double)nCycleDiff - (double)nNumSamples * g_fClksPerSpkrSample);
while((nNumSamples--) && (g_nBufferIdx < SPKR_SAMPLE_RATE-1))
g_pSpeakerBuffer[g_nBufferIdx++] = g_nSpeakerData;
g_pSpeakerBuffer[g_nBufferIdx++] = DCFilter(g_nSpeakerData);
ReinitRemainderBuffer(nCyclesRemaining); // Partially fill 1Mhz sample buffer
}
@ -336,21 +394,16 @@ BYTE __stdcall SpkrToggle (WORD, WORD, BYTE, BYTE, ULONG nCyclesLeft)
UpdateSpkr();
if (g_bQuieterSpeaker)
{
// quieten the speaker if 8 bit DAC in use
if (g_nSpeakerData == (SPKR_DATA_INIT/4)) // NB. Don't shift -ve number right: undefined behaviour (MSDN says: implementation-dependent)
g_nSpeakerData = ~g_nSpeakerData;
else
g_nSpeakerData = SPKR_DATA_INIT/4; // NB. Don't shift -ve number right: undefined behaviour (MSDN says: implementation-dependent)
}
short speakerDriveLevel = SPKR_DATA_INIT;
if (g_bQuieterSpeaker) // quieten the speaker if 8 bit DAC in use
speakerDriveLevel /= 4; // NB. Don't shift -ve number right: undefined behaviour (MSDN says: implementation-dependent)
ResetDCFilter();
if (g_nSpeakerData == speakerDriveLevel)
g_nSpeakerData = ~speakerDriveLevel;
else
{
if (g_nSpeakerData == SPKR_DATA_INIT)
g_nSpeakerData = ~g_nSpeakerData;
else
g_nSpeakerData = SPKR_DATA_INIT;
}
g_nSpeakerData = speakerDriveLevel;
}
return MemReadFloatingBus(nCyclesLeft);
@ -575,7 +628,7 @@ static ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSa
if(dwBufferSize0)
{
wmemset((wchar_t*)pDSLockedBuffer0, (wchar_t)g_nSpeakerData, dwBufferSize0/sizeof(wchar_t));
wmemset((wchar_t*)pDSLockedBuffer0, (wchar_t)DCFilter(g_nSpeakerData), dwBufferSize0/sizeof(wchar_t));
#ifdef RIFF_SPKR
RiffPutSamples(pDSLockedBuffer0, dwBufferSize0/sizeof(short));
#endif
@ -583,7 +636,7 @@ static ULONG Spkr_SubmitWaveBuffer_FullSpeed(short* pSpeakerBuffer, ULONG nNumSa
if(pDSLockedBuffer1)
{
wmemset((wchar_t*)pDSLockedBuffer1, (wchar_t)g_nSpeakerData, dwBufferSize1/sizeof(wchar_t));
wmemset((wchar_t*)pDSLockedBuffer1, (wchar_t)DCFilter(g_nSpeakerData), dwBufferSize1/sizeof(wchar_t));
#ifdef RIFF_SPKR
RiffPutSamples(pDSLockedBuffer1, dwBufferSize1/sizeof(short));
#endif