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
This commit is contained in:
stephena 2013-01-04 19:13:30 +00:00
parent 41fedc4b2f
commit e6044426ec
9 changed files with 322 additions and 299 deletions

View File

@ -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)

View File

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

View File

@ -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

View File

@ -22,7 +22,7 @@
#include <cstdlib>
#define STELLA_VERSION "3.7.5"
#define STELLA_VERSION "3.8_pre"
#define STELLA_BUILD atoi("$Rev$" + 6)
#endif

View File

@ -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", "");

View File

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

View File

@ -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

View File

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

View File

@ -46,7 +46,6 @@ class AudioDialog : public Dialog
PopUpWidget* myFragsizePopup;
PopUpWidget* myFreqPopup;
PopUpWidget* myTiaFreqPopup;
CheckboxWidget* myClipVolumeCheckbox;
CheckboxWidget* mySoundEnableCheckbox;
private: