2005-09-04 23:48:33 +00:00
|
|
|
//============================================================================
|
|
|
|
//
|
|
|
|
// SSSS tt lll lll
|
|
|
|
// SS SS tt ll ll
|
|
|
|
// SS tttttt eeee ll ll aaaa
|
|
|
|
// SSSS tt ee ee ll ll aa
|
|
|
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
|
|
// SS SS tt ee ll ll aa aa
|
|
|
|
// SSSS ttt eeeee llll llll aaaaa
|
|
|
|
//
|
2010-01-10 02:58:28 +00:00
|
|
|
// Copyright (c) 1995-2010 by Bradford W. Mott
|
2005-09-04 23:48:33 +00:00
|
|
|
//
|
2010-01-10 03:23:32 +00:00
|
|
|
// See the file "License.txt" for information on usage and redistribution of
|
2005-09-04 23:48:33 +00:00
|
|
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
|
|
//
|
2009-05-13 13:55:40 +00:00
|
|
|
// $Id$
|
2005-09-05 01:12:56 +00:00
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
#include "System.hxx"
|
|
|
|
#include "TIASnd.hxx"
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2006-01-06 00:31:56 +00:00
|
|
|
TIASound::TIASound(Int32 outputFrequency, Int32 tiaFrequency, uInt32 channels)
|
|
|
|
: myOutputFrequency(outputFrequency),
|
|
|
|
myTIAFrequency(tiaFrequency),
|
|
|
|
myChannels(channels),
|
|
|
|
myOutputCounter(0),
|
|
|
|
myVolumePercentage(100),
|
|
|
|
myVolumeClip(128)
|
2005-09-05 01:12:56 +00:00
|
|
|
{
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
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);
|
|
|
|
myOutputCounter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2006-01-06 00:31:56 +00:00
|
|
|
void TIASound::outputFrequency(Int32 freq)
|
2005-09-05 01:12:56 +00:00
|
|
|
{
|
|
|
|
myOutputFrequency = freq;
|
|
|
|
}
|
|
|
|
|
2006-01-06 00:31:56 +00:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
void TIASound::tiaFrequency(Int32 freq)
|
|
|
|
{
|
|
|
|
myTIAFrequency = freq;
|
|
|
|
}
|
|
|
|
|
2005-09-05 01:12:56 +00:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
void TIASound::channels(uInt32 number)
|
|
|
|
{
|
2006-01-06 00:31:56 +00:00
|
|
|
myChannels = number == 2 ? 2 : 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
void TIASound::clipVolume(bool clip)
|
|
|
|
{
|
|
|
|
myVolumeClip = clip ? 128 : 0;
|
2005-09-05 01:12:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
void TIASound::set(uInt16 address, uInt8 value)
|
|
|
|
{
|
|
|
|
switch(address)
|
|
|
|
{
|
|
|
|
case 0x15: // AUDC0
|
|
|
|
myAUDC[0] = value & 0x0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x16: // AUDC1
|
|
|
|
myAUDC[1] = 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]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x19: // AUDV0
|
|
|
|
myAUDV[0] = value & 0x0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x1a: // AUDV1
|
|
|
|
myAUDV[1] = value & 0x0f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
OK, this looks like a huge update, but it's only because of some Serializer
class reworking. Serializer class now handles read/write of state from
files as well as in-memory streams. As a result, Deserializer class has
been removed.
Added state rewinding to the debugger. For now, this is limited to 100
levels of undo, with a new state generated each time a step/trace/frame/
scanline advance is performed. The undo level is 'rolling', in that it
remembers the last 100 levels (so you lose the oldest states when you
start adding more than 100). For now, this is tied to the 'Alt-r' key
in the debugger. Still TODO is add a button for it, and clean up some
TIA output issues when rewinding.
Added support for 6K version of Supercharger ROMs (this fixes issues
with the 6K version of Cubis).
Cleaned up the Serializable infrastructure, making sure that all
classes that need to implement it actually do so now.
Fixed issue with editable widgets in the UI, where pressing Enter
on the keypad wasn't actually being registered.
git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@1849 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
2009-08-05 16:05:34 +00:00
|
|
|
uInt8 TIASound::get(uInt16 address) const
|
2005-09-05 01:12:56 +00:00
|
|
|
{
|
|
|
|
switch(address)
|
|
|
|
{
|
|
|
|
case 0x15: // AUDC0
|
|
|
|
return myAUDC[0];
|
|
|
|
|
|
|
|
case 0x16: // AUDC1
|
|
|
|
return myAUDC[1];
|
|
|
|
|
|
|
|
case 0x17: // AUDF0
|
|
|
|
return myAUDF[0];
|
|
|
|
|
|
|
|
case 0x18: // AUDF1
|
|
|
|
return myAUDF[1];
|
|
|
|
|
|
|
|
case 0x19: // AUDV0
|
|
|
|
return myAUDV[0];
|
|
|
|
|
|
|
|
case 0x1a: // AUDV1
|
|
|
|
return myAUDV[1];
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
void TIASound::volume(uInt32 percent)
|
|
|
|
{
|
|
|
|
if((percent >= 0) && (percent <= 100))
|
|
|
|
myVolumePercentage = percent;
|
|
|
|
}
|
|
|
|
|
2005-09-04 23:48:33 +00:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2005-09-05 01:12:56 +00:00
|
|
|
void TIASound::process(uInt8* buffer, uInt32 samples)
|
|
|
|
{
|
|
|
|
Int32 v0 = ((myAUDV[0] << 2) * myVolumePercentage) / 100;
|
|
|
|
Int32 v1 = ((myAUDV[1] << 2) * myVolumePercentage) / 100;
|
|
|
|
|
|
|
|
// Loop until the sample buffer is full
|
|
|
|
while(samples > 0)
|
|
|
|
{
|
|
|
|
// Process both sound channels
|
|
|
|
for(uInt32 c = 0; c < 2; ++c)
|
|
|
|
{
|
|
|
|
// Update P4 & P5 registers for channel if freq divider outputs a pulse
|
|
|
|
if((myFreqDiv[c].clock()))
|
|
|
|
{
|
|
|
|
switch(myAUDC[c])
|
|
|
|
{
|
|
|
|
case 0x00: // Set to 1
|
|
|
|
{
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
// 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 0x03: // 5 bit poly -> 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;
|
|
|
|
|
|
|
|
// P5 clocks the 4 bit poly
|
|
|
|
if(myP5[c] & 0x10)
|
|
|
|
{
|
|
|
|
// 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 0x04: // div 2
|
|
|
|
{
|
|
|
|
// Clock P4 toggling the lower bit (divide by 2)
|
|
|
|
myP4[c] = (myP4[c] << 1) | ((myP4[c] & 0x01) ? 0 : 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 0x05: // div 2
|
|
|
|
{
|
|
|
|
// Clock P4 toggling the lower bit (divide by 2)
|
|
|
|
myP4[c] = (myP4[c] << 1) | ((myP4[c] & 0x01) ? 0 : 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 0x06: // div 31 -> div 2
|
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 0x07: // 5 bit poly -> div 2
|
|
|
|
{
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
myOutputCounter += myOutputFrequency;
|
|
|
|
|
|
|
|
if(myChannels == 1)
|
|
|
|
{
|
|
|
|
// Handle mono sample generation
|
2006-01-06 00:31:56 +00:00
|
|
|
while((samples > 0) && (myOutputCounter >= myTIAFrequency))
|
2005-09-05 01:12:56 +00:00
|
|
|
{
|
|
|
|
*(buffer++) = (((myP4[0] & 8) ? v0 : 0) +
|
2006-01-06 00:31:56 +00:00
|
|
|
((myP4[1] & 8) ? v1 : 0)) + myVolumeClip;
|
|
|
|
myOutputCounter -= myTIAFrequency;
|
2005-09-05 01:12:56 +00:00
|
|
|
samples--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Handle stereo sample generation
|
2006-01-06 00:31:56 +00:00
|
|
|
while((samples > 0) && (myOutputCounter >= myTIAFrequency))
|
2005-09-05 01:12:56 +00:00
|
|
|
{
|
2006-01-06 00:31:56 +00:00
|
|
|
*(buffer++) = ((myP4[0] & 8) ? v0 : 0) + myVolumeClip;
|
|
|
|
*(buffer++) = ((myP4[1] & 8) ? v1 : 0) + myVolumeClip;
|
|
|
|
myOutputCounter -= myTIAFrequency;
|
2005-09-05 01:12:56 +00:00
|
|
|
samples--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|