From e6044426ec2da63c77189e6cf9036880af8aa49d Mon Sep 17 00:00:00 2001 From: stephena Date: Fri, 4 Jan 2013 19:13:30 +0000 Subject: [PATCH] Updated sound system with ideas from z26 and MESS. To me at least, the sound is now much more accurate in "Space Rocks", and there aren't any regressions that I'm aware of. Removed 'clipvol' setting, since the sound is now 16-bit and clipping is no longer required. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@2578 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba --- Changes.txt | 16 +- src/common/SoundSDL.cxx | 24 +-- src/common/SoundSDL.hxx | 4 +- src/common/Version.hxx | 2 +- src/emucore/Settings.cxx | 1 - src/emucore/TIASnd.cxx | 444 ++++++++++++++++++++------------------- src/emucore/TIASnd.hxx | 114 ++++++---- src/gui/AudioDialog.cxx | 15 +- src/gui/AudioDialog.hxx | 1 - 9 files changed, 322 insertions(+), 299 deletions(-) diff --git a/Changes.txt b/Changes.txt index 2871655ef..4c623bdb8 100644 --- a/Changes.txt +++ b/Changes.txt @@ -12,6 +12,20 @@ Release History =========================================================================== +3.7.5 to 3.8: (January xx, 2013) + + * Huge changes to the sound system: + - The sound code now uses signed 16-bit samples instead of unsigned + 8-bit samples, making it more compatible with a wider variety of + systems. + - Improved sound output for several ROMs, including "Space Rocks" + (the 'heartbeat' sound can now be clearly heard). + - The 'volume clipping' option has been removed, since in 16-bit + mode it's no longer needed. + +-Have fun! + + 3.7.4 to 3.7.5: (December 22, 2012) * Improved regressions in PAL format autodetection, introduced in the @@ -22,8 +36,6 @@ * Snapshots generated by Stella now include more informative info, such as the build number, platform architecture, TV effects in use, etc. --Have fun! - 3.7.3 to 3.7.4: (October 31, 2012) diff --git a/src/common/SoundSDL.cxx b/src/common/SoundSDL.cxx index 4564436d1..a43c917a8 100644 --- a/src/common/SoundSDL.cxx +++ b/src/common/SoundSDL.cxx @@ -51,7 +51,7 @@ SoundSDL::SoundSDL(OSystem* osystem) // whereby sound stopped working after the first video change SDL_AudioSpec desired; desired.freq = myOSystem->settings().getInt("freq"); - desired.format = AUDIO_U8; + desired.format = AUDIO_S16; desired.channels = 2; desired.samples = myOSystem->settings().getInt("fragsize"); desired.callback = callback; @@ -131,9 +131,6 @@ void SoundSDL::open() const string& chanResult = myTIASound.channels(myHardwareSpec.channels, myNumChannels == 2); - bool clipvol = myOSystem->settings().getBool("clipvol"); - myTIASound.clipVolume(clipvol); - // Adjust volume to that defined in settings myVolume = myOSystem->settings().getInt("volume"); setVolume(myVolume); @@ -144,11 +141,8 @@ void SoundSDL::open() << " Volume: " << (int)myVolume << endl << " Frag size: " << (int)myHardwareSpec.samples << endl << " Frequency: " << (int)myHardwareSpec.freq << endl - << " Format: " << (int)myHardwareSpec.format << endl - << " TIA Freq: " << (int)tiafreq << endl << " Channels: " << (int)myHardwareSpec.channels << " (" << chanResult << ")" << endl - << " Clip volume: " << (clipvol ? "on" : "off") << endl << endl; myOSystem->logMessage(buf.str(), 1); @@ -262,7 +256,7 @@ void SoundSDL::set(uInt16 addr, uInt8 value, Int32 cycle) { SDL_LockAudio(); - // First, calulate how many seconds would have past since the last + // First, calculate how many seconds would have past since the last // register write on a real 2600 double delta = (((double)(cycle - myLastRegisterSetCycle)) / (1193191.66666667)); @@ -284,7 +278,7 @@ void SoundSDL::set(uInt16 addr, uInt8 value, Int32 cycle) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL::processFragment(uInt8* stream, Int32 length) +void SoundSDL::processFragment(Int16* stream, uInt32 length) { uInt32 channels = myHardwareSpec.channels; length = length / channels; @@ -311,7 +305,6 @@ void SoundSDL::processFragment(uInt8* stream, Int32 length) { // There are no more pending TIA sound register updates so we'll // use the current settings to finish filling the sound fragment -// myTIASound.process(stream + (uInt32)position, length - (uInt32)position); myTIASound.process(stream + ((uInt32)position * channels), length - (uInt32)position); @@ -341,9 +334,6 @@ void SoundSDL::processFragment(uInt8* stream, Int32 length) // Process the fragment upto the next TIA register write. We // round the count passed to process up if needed. double samples = (myHardwareSpec.freq * info.delta); -// myTIASound.process(stream + (uInt32)position, (uInt32)samples + -// (uInt32)(position + samples) - -// ((uInt32)position + (uInt32)samples)); myTIASound.process(stream + ((uInt32)position * channels), (uInt32)samples + (uInt32)(position + samples) - ((uInt32)position + (uInt32)samples)); @@ -359,7 +349,6 @@ void SoundSDL::processFragment(uInt8* stream, Int32 length) // The next register update occurs in the next fragment so finish // this fragment with the current TIA settings and reduce the register // update delay by the corresponding amount of time -// myTIASound.process(stream + (uInt32)position, length - (uInt32)position); myTIASound.process(stream + ((uInt32)position * channels), length - (uInt32)position); info.delta -= duration; @@ -374,7 +363,12 @@ void SoundSDL::callback(void* udata, uInt8* stream, int len) { SoundSDL* sound = (SoundSDL*)udata; if(sound->myIsEnabled) - sound->processFragment(stream, (Int32)len); + { + // The callback is requesting 8-bit (unsigned) data, but the TIA sound + // emulator deals in 16-bit (signed) data + // So, we need to convert the pointer and half the length + sound->processFragment((Int16*)stream, (uInt32)len / 2); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/SoundSDL.hxx b/src/common/SoundSDL.hxx index d67f4881f..307dbce97 100644 --- a/src/common/SoundSDL.hxx +++ b/src/common/SoundSDL.hxx @@ -162,11 +162,13 @@ class SoundSDL : public Sound protected: /** Invoked by the sound callback to process the next sound fragment. + The stream is 16-bits (even though the callback is 8-bits), since + the TIASnd class always generates signed 16-bit stereo samples. @param stream Pointer to the start of the fragment @param length Length of the fragment */ - void processFragment(uInt8* stream, Int32 length); + void processFragment(Int16* stream, uInt32 length); protected: // Struct to hold information regarding a TIA sound register write diff --git a/src/common/Version.hxx b/src/common/Version.hxx index a3188d22b..3cfe27c25 100644 --- a/src/common/Version.hxx +++ b/src/common/Version.hxx @@ -22,7 +22,7 @@ #include -#define STELLA_VERSION "3.7.5" +#define STELLA_VERSION "3.8_pre" #define STELLA_BUILD atoi("$Rev$" + 6) #endif diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 797c598a9..3cf99d906 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -81,7 +81,6 @@ Settings::Settings(OSystem* osystem) setInternal("freq", "31400"); setInternal("tiafreq", "31400"); setInternal("volume", "100"); - setInternal("clipvol", "true"); // Input event options setInternal("keymap", ""); diff --git a/src/emucore/TIASnd.cxx b/src/emucore/TIASnd.cxx index ccb6b7403..da09b01a0 100644 --- a/src/emucore/TIASnd.cxx +++ b/src/emucore/TIASnd.cxx @@ -26,8 +26,7 @@ TIASound::TIASound(Int32 outputFrequency, Int32 tiaFrequency) myOutputFrequency(outputFrequency), myTIAFrequency(tiaFrequency), myOutputCounter(0), - myVolumePercentage(100), - myVolumeClip(128) + myVolumePercentage(100) { reset(); } @@ -40,10 +39,26 @@ TIASound::~TIASound() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIASound::reset() { - myAUDC[0] = myAUDC[1] = myAUDF[0] = myAUDF[1] = myAUDV[0] = myAUDV[1] = 0; - myP4[0] = myP5[0] = myP4[1] = myP5[1] = 1; - myFreqDiv[0].set(0); - myFreqDiv[1].set(0); + // Fill the polynomials + polyInit(Bit4, 4, 4, 3); + polyInit(Bit5, 5, 5, 3); + polyInit(Bit9, 9, 9, 5); + + // Initialize instance variables + for(int chan = 0; chan <= 1; ++chan) + { + myVolume[chan] = 0; + myDivNCnt[chan] = 0; + myDivNMax[chan] = 0; + myDiv3Cnt[chan] = 3; + myAUDC[chan] = 0; + myAUDF[chan] = 0; + myAUDV[chan] = 0; + myP4[chan] = 0; + myP5[chan] = 0; + myP9[chan] = 0; + } + myOutputCounter = 0; } @@ -76,45 +91,61 @@ string TIASound::channels(uInt32 hardware, bool stereo) } } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::clipVolume(bool clip) -{ - myVolumeClip = clip ? 128 : 0; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIASound::set(uInt16 address, uInt8 value) { + int chan = ~address & 0x1; switch(address) { case 0x15: // AUDC0 - myAUDC[0] = value & 0x0f; - break; - case 0x16: // AUDC1 - myAUDC[1] = value & 0x0f; + myAUDC[chan] = value & 0x0f; break; case 0x17: // AUDF0 - myAUDF[0] = value & 0x1f; - myFreqDiv[0].set(myAUDF[0]); - break; - case 0x18: // AUDF1 - myAUDF[1] = value & 0x1f; - myFreqDiv[1].set(myAUDF[1]); + myAUDF[chan] = value & 0x1f; break; case 0x19: // AUDV0 - myAUDV[0] = value & 0x0f; - break; - case 0x1a: // AUDV1 - myAUDV[1] = value & 0x0f; + myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT; break; default: - break; + return; + } + + uInt16 newVal = 0; + + // An AUDC value of 0 is a special case + if (myAUDC[chan] == SET_TO_1 || myAUDC[chan] == POLY5_POLY5) + { + // Indicate the clock is zero so no processing will occur, + // and set the output to the selected volume + newVal = 0; + myVolume[chan] = (myAUDV[chan] * myVolumePercentage) / 100; + } + else + { + // Otherwise calculate the 'divide by N' value + newVal = myAUDF[chan] + 1; + + // If bits 2 & 3 are set, then multiply the 'div by n' count by 3 + if((myAUDC[chan] & DIV3_MASK) == DIV3_MASK && myAUDC[chan] != POLY5_DIV3) + newVal *= 3; + } + + // Only reset those channels that have changed + if(newVal != myDivNMax[chan]) + { + // Reset the divide by n counters + myDivNMax[chan] = newVal; + + // If the channel is now volume only or was volume only, + // reset the counter (otherwise let it complete the previous) + if ((myDivNCnt[chan] == 0) || (newVal == 0)) + myDivNCnt[chan] = newVal; } } @@ -136,10 +167,10 @@ uInt8 TIASound::get(uInt16 address) const return myAUDF[1]; case 0x19: // AUDV0 - return myAUDV[0]; + return myAUDV[0] >> AUDV_SHIFT; case 0x1a: // AUDV1 - return myAUDV[1]; + return myAUDV[1] >> AUDV_SHIFT; default: return 0; @@ -154,227 +185,176 @@ void TIASound::volume(uInt32 percent) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIASound::process(uInt8* buffer, uInt32 samples) +void TIASound::process(Int16* buffer, uInt32 samples) { - Int32 v0 = ((myAUDV[0] << 2) * myVolumePercentage) / 100; - Int32 v1 = ((myAUDV[1] << 2) * myVolumePercentage) / 100; + // Make temporary local copy + uInt8 audc0 = myAUDC[0], audc1 = myAUDC[1]; + uInt8 p5_0 = myP5[0], p5_1 = myP5[1]; + uInt8 div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1]; + Int16 v0 = myVolume[0], v1 = myVolume[1]; + + // Take external volume into account + Int16 audv0 = (myAUDV[0] * myVolumePercentage) / 100, + audv1 = (myAUDV[1] * myVolumePercentage) / 100; // Loop until the sample buffer is full while(samples > 0) { - // Process both sound channels - for(uInt32 c = 0; c < 2; ++c) + // Process channel 0 + if (div_n_cnt0 > 1) { - // Update P4 & P5 registers for channel if freq divider outputs a pulse - if((myFreqDiv[c].clock())) + div_n_cnt0--; + } + else if (div_n_cnt0 == 1) + { + int prev_bit5 = Bit5[p5_0]; + div_n_cnt0 = myDivNMax[0]; + + // The P5 counter has multiple uses, so we increment it here + p5_0++; + if (p5_0 == POLY5_SIZE) + p5_0 = 0; + + // Check clock modifier for clock tick + if ((audc0 & 0x02) == 0 || + ((audc0 & 0x01) == 0 && Div31[p5_0]) || + ((audc0 & 0x01) == 1 && Bit5[p5_0]) || + ((audc0 & 0x0f) == POLY5_DIV3 && Bit5[p5_0] != prev_bit5)) { - switch(myAUDC[c]) + if (audc0 & 0x04) // Pure modified clock selected { - case 0x00: // Set to 1 + if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode { - // Shift a 1 into the 4-bit register each clock - myP4[c] = (myP4[c] << 1) | 0x01; - break; - } - - case 0x01: // 4 bit poly - { - // Clock P4 as a standard 4-bit LSFR taps at bits 3 & 2 - myP4[c] = (myP4[c] & 0x0f) ? - ((myP4[c] << 1) | (((myP4[c] & 0x08) ? 1 : 0) ^ - ((myP4[c] & 0x04) ? 1 : 0))) : 1; - break; - } - - case 0x02: // div 31 -> 4 bit poly - { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; - - // This does the divide-by 31 with length 13:18 - if((myP5[c] & 0x0f) == 0x08) + if ( Bit5[p5_0] != prev_bit5 ) { - // Clock P4 as a standard 4-bit LSFR taps at bits 3 & 2 - myP4[c] = (myP4[c] & 0x0f) ? - ((myP4[c] << 1) | (((myP4[c] & 0x08) ? 1 : 0) ^ - ((myP4[c] & 0x04) ? 1 : 0))) : 1; + myDiv3Cnt[0]--; + if ( !myDiv3Cnt[0] ) + { + myDiv3Cnt[0] = 3; + v0 = v0 ? 0 : audv0; + } } - break; } - - case 0x03: // 5 bit poly -> 4 bit poly + else { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; + // If the output was set turn it off, else turn it on + v0 = v0 ? 0 : audv0; + } + } + else if (audc0 & 0x08) // Check for p5/p9 + { + if (audc0 == POLY9) // Check for poly9 + { + // Increase the poly9 counter + myP9[0]++; + if (myP9[0] == POLY9_SIZE) + myP9[0] = 0; - // P5 clocks the 4 bit poly - if(myP5[c] & 0x10) + v0 = Bit9[myP9[0]] ? audv0 : 0; + } + else if ( audc0 & 0x02 ) + { + v0 = (v0 || audc0 & 0x01) ? 0 : audv0; + } + else // Must be poly5 + { + v0 = Bit5[p5_0] ? audv0 : 0; + } + } + else // Poly4 is the only remaining option + { + // Increase the poly4 counter + myP4[0]++; + if (myP4[0] == POLY4_SIZE) + myP4[0] = 0; + + v0 = Bit4[myP4[0]] ? audv0 : 0; + } + } + } + + // Process channel 1 + if (div_n_cnt1 > 1) + { + div_n_cnt1--; + } + else if (div_n_cnt1 == 1) + { + int prev_bit5 = Bit5[p5_1]; + + div_n_cnt1 = myDivNMax[1]; + + // The P5 counter has multiple uses, so we increment it here + p5_1++; + if (p5_1 == POLY5_SIZE) + p5_1 = 0; + + // Check clock modifier for clock tick + if ((audc1 & 0x02) == 0 || + ((audc1 & 0x01) == 0 && Div31[p5_1]) || + ((audc1 & 0x01) == 1 && Bit5[p5_1]) || + ((audc1 & 0x0f) == POLY5_DIV3 && Bit5[p5_1] != prev_bit5)) + { + if (audc1 & 0x04) // Pure modified clock selected + { + if ((audc1 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode + { + if ( Bit5[p5_1] != prev_bit5 ) { - // Clock P4 as a standard 4-bit LSFR taps at bits 3 & 2 - myP4[c] = (myP4[c] & 0x0f) ? - ((myP4[c] << 1) | (((myP4[c] & 0x08) ? 1 : 0) ^ - ((myP4[c] & 0x04) ? 1 : 0))) : 1; + myDiv3Cnt[1]--; + if ( ! myDiv3Cnt[1] ) + { + myDiv3Cnt[1] = 3; + v1 = v1 ? 0 : audv1; + } } - break; } - - case 0x04: // div 2 + else { - // Clock P4 toggling the lower bit (divide by 2) - myP4[c] = (myP4[c] << 1) | ((myP4[c] & 0x01) ? 0 : 1); - break; + // If the output was set turn it off, else turn it on + v1 = v1 ? 0 : audv1; } - - case 0x05: // div 2 + } + else if (audc1 & 0x08) // Check for p5/p9 + { + if (audc1 == POLY9) // Check for poly9 { - // Clock P4 toggling the lower bit (divide by 2) - myP4[c] = (myP4[c] << 1) | ((myP4[c] & 0x01) ? 0 : 1); - break; - } + // Increase the poly9 counter + myP9[1]++; + if (myP9[1] == POLY9_SIZE) + myP9[1] = 0; - case 0x06: // div 31 -> div 2 + v1 = Bit9[myP9[1]] ? audv1 : 0; + } + else if ( audc1 & 0x02 ) { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; - - // This does the divide-by 31 with length 13:18 - if((myP5[c] & 0x0f) == 0x08) - { - // Clock P4 toggling the lower bit (divide by 2) - myP4[c] = (myP4[c] << 1) | ((myP4[c] & 0x01) ? 0 : 1); - } - break; + v1 = (v1 || audc1 & 0x01) ? 0 : audv1; } - - case 0x07: // 5 bit poly -> div 2 + else // Must be poly5 { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; - - // P5 clocks the 4 bit register - if(myP5[c] & 0x10) - { - // Clock P4 toggling the lower bit (divide by 2) - myP4[c] = (myP4[c] << 1) | ((myP4[c] & 0x01) ? 0 : 1); - } - break; + v1 = Bit5[p5_1] ? audv1 : 0; } + } + else // Poly4 is the only remaining option + { + // Increase the poly4 counter + myP4[1]++; + if (myP4[1] == POLY4_SIZE) + myP4[1] = 0; - case 0x08: // 9 bit poly - { - // Clock P5 & P4 as a standard 9-bit LSFR taps at 8 & 4 - myP5[c] = ((myP5[c] & 0x1f) || (myP4[c] & 0x0f)) ? - ((myP5[c] << 1) | (((myP4[c] & 0x08) ? 1 : 0) ^ - ((myP5[c] & 0x10) ? 1 : 0))) : 1; - myP4[c] = (myP4[c] << 1) | ((myP5[c] & 0x20) ? 1 : 0); - break; - } - - case 0x09: // 5 bit poly - { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; - - // Clock value out of P5 into P4 with no modification - myP4[c] = (myP4[c] << 1) | ((myP5[c] & 0x20) ? 1 : 0); - break; - } - - case 0x0a: // div 31 - { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; - - // This does the divide-by 31 with length 13:18 - if((myP5[c] & 0x0f) == 0x08) - { - // Feed bit 4 of P5 into P4 (this will toggle back and forth) - myP4[c] = (myP4[c] << 1) | ((myP5[c] & 0x10) ? 1 : 0); - } - break; - } - - case 0x0b: // Set last 4 bits to 1 - { - // A 1 is shifted into the 4-bit register each clock - myP4[c] = (myP4[c] << 1) | 0x01; - break; - } - - case 0x0c: // div 6 - { - // Use 4-bit register to generate sequence 000111000111 - myP4[c] = (~myP4[c] << 1) | - ((!(!(myP4[c] & 4) && ((myP4[c] & 7)))) ? 0 : 1); - break; - } - - case 0x0d: // div 6 - { - // Use 4-bit register to generate sequence 000111000111 - myP4[c] = (~myP4[c] << 1) | - ((!(!(myP4[c] & 4) && ((myP4[c] & 7)))) ? 0 : 1); - break; - } - - case 0x0e: // div 31 -> div 6 - { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; - - // This does the divide-by 31 with length 13:18 - if((myP5[c] & 0x0f) == 0x08) - { - // Use 4-bit register to generate sequence 000111000111 - myP4[c] = (~myP4[c] << 1) | - ((!(!(myP4[c] & 4) && ((myP4[c] & 7)))) ? 0 : 1); - } - break; - } - - case 0x0f: // poly 5 -> div 6 - { - // Clock P5 as a standard 5-bit LSFR taps at bits 4 & 2 - myP5[c] = (myP5[c] & 0x1f) ? - ((myP5[c] << 1) | (((myP5[c] & 0x10) ? 1 : 0) ^ - ((myP5[c] & 0x04) ? 1 : 0))) : 1; - - // Use poly 5 to clock 4-bit div register - if(myP5[c] & 0x10) - { - // Use 4-bit register to generate sequence 000111000111 - myP4[c] = (~myP4[c] << 1) | - ((!(!(myP4[c] & 4) && ((myP4[c] & 7)))) ? 0 : 1); - } - break; - } + v1 = Bit4[myP4[1]] ? audv1 : 0; } } } myOutputCounter += myOutputFrequency; - + switch(myChannelMode) { case Hardware2Mono: // mono sampling with 2 hardware channels while((samples > 0) && (myOutputCounter >= myTIAFrequency)) { - uInt8 byte = (((myP4[0] & 8) ? v0 : 0) + - ((myP4[1] & 8) ? v1 : 0)) + myVolumeClip; + Int16 byte = v0 + v1; *(buffer++) = byte; *(buffer++) = byte; myOutputCounter -= myTIAFrequency; @@ -385,8 +365,8 @@ void TIASound::process(uInt8* buffer, uInt32 samples) case Hardware2Stereo: // stereo sampling with 2 hardware channels while((samples > 0) && (myOutputCounter >= myTIAFrequency)) { - *(buffer++) = ((myP4[0] & 8) ? v0 : 0) + myVolumeClip; - *(buffer++) = ((myP4[1] & 8) ? v1 : 0) + myVolumeClip; + *(buffer++) = v0; + *(buffer++) = v1; myOutputCounter -= myTIAFrequency; samples--; } @@ -395,12 +375,40 @@ void TIASound::process(uInt8* buffer, uInt32 samples) case Hardware1: // mono/stereo sampling with only 1 hardware channel while((samples > 0) && (myOutputCounter >= myTIAFrequency)) { - *(buffer++) = (((myP4[0] & 8) ? v0 : 0) + - ((myP4[1] & 8) ? v1 : 0)) + myVolumeClip; + *(buffer++) = v0 + v1; myOutputCounter -= myTIAFrequency; samples--; } break; } } + + // Save for next round + myP5[0] = p5_0; + myP5[1] = p5_1; + myVolume[0] = v0; + myVolume[1] = v1; + myDivNCnt[0] = div_n_cnt0; + myDivNCnt[1] = div_n_cnt1; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TIASound::polyInit(uInt8* poly, int size, int f0, int f1) +{ + int mask = (1 << size) - 1, x = mask; + + for(int i = 0; i < mask; i++) + { + int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01; + int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01; + poly[i] = x & 1; + // calculate next bit + x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) ); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +const uInt8 TIASound::Div31[POLY5_SIZE] = { + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; diff --git a/src/emucore/TIASnd.hxx b/src/emucore/TIASnd.hxx index 7e81fc87b..c9fb5af85 100644 --- a/src/emucore/TIASnd.hxx +++ b/src/emucore/TIASnd.hxx @@ -24,9 +24,9 @@ /** This class implements a fairly accurate emulation of the TIA sound - hardware. + hardware. This class uses code/ideas from z26 and MESS. - @author Bradford W. Mott + @author Bradford W. Mott, Stephen Anthony, z26 and MESS teams @version $Id$ */ class TIASound @@ -70,11 +70,6 @@ class TIASound */ string channels(uInt32 hardware, bool stereo); - /** - Set volume clipping (decrease volume range by half to eliminate popping) - */ - void clipVolume(bool clip); - public: /** Sets the specified sound register to the given value @@ -99,7 +94,7 @@ class TIASound @param buffer The location to store generated samples @param samples The number of samples to generate */ - void process(uInt8* buffer, uInt32 samples); + void process(Int16* buffer, uInt32 samples); /** Set the volume of the samples created (0-100) @@ -107,59 +102,86 @@ class TIASound void volume(uInt32 percent); private: - /** - Frequency divider class which outputs 1 after "divide-by" clocks. This - is used to divide the main frequency by the values 1 to 32. - */ - class FreqDiv + void polyInit(uInt8* poly, int size, int f0, int f1); + + private: + // Definitions for AUDCx (15, 16) + enum AUDCxRegister { - public: - FreqDiv() - { - myDivideByValue = myCounter = 0; - } + SET_TO_1 = 0x00, // 0000 + POLY4 = 0x01, // 0001 + DIV31_POLY4 = 0x02, // 0010 + POLY5_POLY4 = 0x03, // 0011 + PURE = 0x04, // 0100 + PURE2 = 0x05, // 0101 + DIV31_PURE = 0x06, // 0110 + POLY5_2 = 0x07, // 0111 + POLY9 = 0x08, // 1000 + POLY5 = 0x09, // 1001 + DIV31_POLY5 = 0x0a, // 1010 + POLY5_POLY5 = 0x0b, // 1011 + DIV3_PURE = 0x0c, // 1100 + DIV3_PURE2 = 0x0d, // 1101 + DIV93_PURE = 0x0e, // 1110 + POLY5_DIV3 = 0x0f // 1111 + }; - void set(uInt32 divideBy) - { - myDivideByValue = divideBy; - } - - bool clock() - { - if(++myCounter > myDivideByValue) - { - myCounter = 0; - return true; - } - return false; - } - - private: - uInt32 myDivideByValue; - uInt32 myCounter; + enum { + POLY4_SIZE = 0x000f, + POLY5_SIZE = 0x001f, + POLY9_SIZE = 0x01ff, + DIV3_MASK = 0x0c, + AUDV_SHIFT = 10 // shift 2 positions for AUDV, + // then another 8 for 16-bit sound }; enum ChannelMode { - Hardware1, - Hardware2Mono, - Hardware2Stereo + Hardware2Mono, // mono sampling with 2 hardware channels + Hardware2Stereo, // stereo sampling with 2 hardware channels + Hardware1 // mono/stereo sampling with only 1 hardware channel }; private: - uInt8 myAUDC[2]; - uInt8 myAUDF[2]; - uInt8 myAUDV[2]; + // Structures to hold the 6 tia sound control bytes + uInt8 myAUDC[2]; // AUDCx (15, 16) + uInt8 myAUDF[2]; // AUDFx (17, 18) + Int16 myAUDV[2]; // AUDVx (19, 1A) - FreqDiv myFreqDiv[2]; // Frequency dividers - uInt8 myP4[2]; // 4-bit register LFSR (lower 4 bits used) - uInt8 myP5[2]; // 5-bit register LFSR (lower 5 bits used) + Int16 myVolume[2]; // Last output volume for each channel + + uInt8 myP4[2]; // Position pointer for the 4-bit POLY array + uInt8 myP5[2]; // Position pointer for the 5-bit POLY array + uInt16 myP9[2]; // Position pointer for the 9-bit POLY array + + uInt8 myDivNCnt[2]; // Divide by n counter. one for each channel + uInt8 myDivNMax[2]; // Divide by n maximum, one for each channel + uInt8 myDiv3Cnt[2]; // Div 3 counter, used for POLY5_DIV3 mode ChannelMode myChannelMode; Int32 myOutputFrequency; Int32 myTIAFrequency; Int32 myOutputCounter; uInt32 myVolumePercentage; - uInt8 myVolumeClip; + + /* + Initialize the bit patterns for the polynomials (at runtime). + + The 4bit and 5bit patterns are the identical ones used in the tia chip. + Though the patterns could be packed with 8 bits per byte, using only a + single bit per byte keeps the math simple, which is important for + efficient processing. + */ + uInt8 Bit4[POLY4_SIZE]; + uInt8 Bit5[POLY5_SIZE]; + uInt8 Bit9[POLY9_SIZE]; + + /* + The 'Div by 31' counter is treated as another polynomial because of + the way it operates. It does not have a 50% duty cycle, but instead + has a 13:18 ratio (of course, 13+18 = 31). This could also be + implemented by using counters. + */ + static const uInt8 Div31[POLY5_SIZE]; }; #endif diff --git a/src/gui/AudioDialog.cxx b/src/gui/AudioDialog.cxx index 1a41786f0..43e7dddbf 100644 --- a/src/gui/AudioDialog.cxx +++ b/src/gui/AudioDialog.cxx @@ -107,13 +107,8 @@ AudioDialog::AudioDialog(OSystem* osystem, DialogContainer* parent, wid.push_back(myTiaFreqPopup); ypos += lineHeight + 4; - // Clip volume - myClipVolumeCheckbox = new CheckboxWidget(this, font, xpos+10, ypos, - "Clip volume", 0); - wid.push_back(myClipVolumeCheckbox); - xpos += myClipVolumeCheckbox->getWidth() + 20; - // Enable sound + xpos = (_w - (font.getStringWidth("Enable sound") + 10)) / 2; mySoundEnableCheckbox = new CheckboxWidget(this, font, xpos, ypos, "Enable sound", kSoundEnableChanged); wid.push_back(mySoundEnableCheckbox); @@ -156,9 +151,6 @@ void AudioDialog::loadConfig() // TIA frequency myTiaFreqPopup->setSelected(instance().settings().getString("tiafreq"), "31400"); - // Clip volume - myClipVolumeCheckbox->setState(instance().settings().getBool("clipvol")); - // Enable sound bool b = instance().settings().getBool("sound"); mySoundEnableCheckbox->setState(b); @@ -185,9 +177,6 @@ void AudioDialog::saveConfig() // TIA frequency settings.setString("tiafreq", myTiaFreqPopup->getSelectedTag()); - // Enable/disable volume clipping (requires a restart to take effect) - settings.setBool("clipvol", myClipVolumeCheckbox->getState()); - // Enable/disable sound (requires a restart to take effect) instance().sound().setEnabled(mySoundEnableCheckbox->getState()); @@ -207,7 +196,6 @@ void AudioDialog::setDefaults() myFreqPopup->setSelected("31400", ""); myTiaFreqPopup->setSelected("31400", ""); - myClipVolumeCheckbox->setState(true); mySoundEnableCheckbox->setState(true); // Make sure that mutually-exclusive items are not enabled at the same time @@ -224,7 +212,6 @@ void AudioDialog::handleSoundEnableChange(bool active) myFragsizePopup->setEnabled(active); myFreqPopup->setEnabled(active); myTiaFreqPopup->setEnabled(active); - myClipVolumeCheckbox->setEnabled(active); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/AudioDialog.hxx b/src/gui/AudioDialog.hxx index 84c6d3bbe..bee3c1913 100644 --- a/src/gui/AudioDialog.hxx +++ b/src/gui/AudioDialog.hxx @@ -46,7 +46,6 @@ class AudioDialog : public Dialog PopUpWidget* myFragsizePopup; PopUpWidget* myFreqPopup; PopUpWidget* myTiaFreqPopup; - CheckboxWidget* myClipVolumeCheckbox; CheckboxWidget* mySoundEnableCheckbox; private: