mirror of https://github.com/stella-emu/stella.git
Improved TIA sound emulation library which includes support for mono and
stereo output. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@764 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
parent
88464f6935
commit
28946ba589
|
@ -0,0 +1,373 @@
|
|||
//============================================================================
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// Copyright (c) 1995-2005 by Bradford W. Mott
|
||||
//
|
||||
// See the file "license" for information on usage and redistribution of
|
||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
//
|
||||
// $Id: TIASnd.cxx,v 1.1 2005-09-04 23:48:33 bwmott Exp $
|
||||
//============================================================================
|
||||
|
||||
#include "System.hxx"
|
||||
#include "TIASnd.hxx"
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
TIASound::TIASound(Int32 outputFrequency, uInt32 channels)
|
||||
: myOutputFrequency(outputFrequency),
|
||||
myChannels(channels),
|
||||
myOutputCounter(0),
|
||||
myVolumePercentage(100)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::outputFrequency(uInt32 freq)
|
||||
{
|
||||
myOutputFrequency = freq;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::channels(uInt32 number)
|
||||
{
|
||||
if(number == 2)
|
||||
myChannels = 2;
|
||||
else
|
||||
myChannels = 1;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt8 TIASound::get(uInt16 address)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
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
|
||||
while((samples > 0) && (myOutputCounter >= TIASoundFrequency))
|
||||
{
|
||||
*(buffer++) = (((myP4[0] & 8) ? v0 : 0) +
|
||||
((myP4[1] & 8) ? v1 : 0)) + 128;
|
||||
myOutputCounter -= TIASoundFrequency;
|
||||
samples--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle stereo sample generation
|
||||
while((samples > 0) && (myOutputCounter >= TIASoundFrequency))
|
||||
{
|
||||
*(buffer++) = ((myP4[0] & 8) ? v0 : 0) + 128;
|
||||
*(buffer++) = ((myP4[1] & 8) ? v1 : 0) + 128;
|
||||
myOutputCounter -= TIASoundFrequency;
|
||||
samples--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
//============================================================================
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// Copyright (c) 1995-2005 by Bradford W. Mott
|
||||
//
|
||||
// See the file "license" for information on usage and redistribution of
|
||||
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||||
//
|
||||
// $Id: TIASnd.hxx,v 1.1 2005-09-04 23:48:33 bwmott Exp $
|
||||
//============================================================================
|
||||
|
||||
#ifndef TIASOUND_HXX
|
||||
#define TIASOUND_HXX
|
||||
|
||||
#include "bspf.hxx"
|
||||
|
||||
/**
|
||||
This class implements a fairly accurate emulation of the TIA sound
|
||||
hardware.
|
||||
|
||||
@author Bradford W. Mott
|
||||
@version $Id: TIASnd.hxx,v 1.1 2005-09-04 23:48:33 bwmott Exp $
|
||||
*/
|
||||
class TIASound
|
||||
{
|
||||
public:
|
||||
static const int TIASoundFrequency = 31400;
|
||||
|
||||
public:
|
||||
/**
|
||||
Create a new TIA Sound object using the specified output frequency
|
||||
*/
|
||||
TIASound(Int32 outputFrequency = TIASoundFrequency, uInt32 channels = 1);
|
||||
|
||||
/**
|
||||
Destructor
|
||||
*/
|
||||
virtual ~TIASound();
|
||||
|
||||
public:
|
||||
/**
|
||||
Reset the sound emulation to its power-on state
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
Set the frequency output samples should be generated at
|
||||
*/
|
||||
void outputFrequency(uInt32 freq);
|
||||
|
||||
/**
|
||||
Selects the number of audio channels per sample (1 = mono, 2 = stereo)
|
||||
*/
|
||||
void channels(uInt32 number);
|
||||
|
||||
public:
|
||||
/**
|
||||
Sets the specified sound register to the given value
|
||||
|
||||
@param address Register address
|
||||
@param value Value to store in the register
|
||||
*/
|
||||
void set(uInt16 address, uInt8 value);
|
||||
|
||||
/**
|
||||
Gets the specified sound register's value
|
||||
|
||||
@param address Register address
|
||||
*/
|
||||
uInt8 get(uInt16 address);
|
||||
|
||||
/**
|
||||
Create sound samples based on the current sound register settings
|
||||
in the specified buffer. NOTE: If channels is set to stereo then
|
||||
the buffer will need to be twice as long as the number of samples.
|
||||
|
||||
@param buffer The location to store generated samples
|
||||
@param samples The number of samples to generate
|
||||
*/
|
||||
void process(uInt8* buffer, uInt32 samples);
|
||||
|
||||
/**
|
||||
Set the volume of the samples created (0-100)
|
||||
*/
|
||||
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
|
||||
{
|
||||
public:
|
||||
FreqDiv()
|
||||
{
|
||||
myDivideByValue = myCounter = 0;
|
||||
}
|
||||
|
||||
void set(uInt32 divideBy)
|
||||
{
|
||||
myDivideByValue = divideBy;
|
||||
}
|
||||
|
||||
bool clock()
|
||||
{
|
||||
if(++myCounter > myDivideByValue)
|
||||
{
|
||||
myCounter = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
uInt32 myDivideByValue;
|
||||
uInt32 myCounter;
|
||||
};
|
||||
|
||||
private:
|
||||
uInt8 myAUDC[2];
|
||||
uInt8 myAUDF[2];
|
||||
uInt8 myAUDV[2];
|
||||
|
||||
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)
|
||||
|
||||
Int32 myOutputFrequency;
|
||||
Int32 myOutputCounter;
|
||||
uInt32 myChannels;
|
||||
uInt32 myVolumePercentage;
|
||||
};
|
||||
#endif
|
Loading…
Reference in New Issue