mirror of https://github.com/stella-emu/stella.git
OK, this is the first pass at adding the new TIA sound emulation
core to Stella, as outlined in a thread on AtariAge. It's currently not working 100% due to timing issues with the TIA class. Expect breakage over the next few months, as this code and the TIA code in general will be borked from time to time. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@3306 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
parent
c0095ca59a
commit
81e9480100
|
@ -37,8 +37,7 @@ class SoundNull : public Sound
|
|||
{
|
||||
public:
|
||||
/**
|
||||
Create a new sound object. The init method must be invoked before
|
||||
using the object.
|
||||
Create a new sound object with no functionality.
|
||||
*/
|
||||
SoundNull(OSystem& osystem) : Sound(osystem)
|
||||
{
|
||||
|
@ -49,40 +48,14 @@ class SoundNull : public Sound
|
|||
public:
|
||||
/**
|
||||
Enables/disables the sound subsystem.
|
||||
|
||||
@param enable Either true or false, to enable or disable the sound system
|
||||
@return Whether the sound system was enabled or disabled
|
||||
*/
|
||||
void setEnabled(bool enable) override { }
|
||||
|
||||
/**
|
||||
The system cycle counter is being adjusting by the specified amount. Any
|
||||
members using the system cycle counter should be adjusted as needed.
|
||||
|
||||
@param amount The amount the cycle counter is being adjusted by
|
||||
*/
|
||||
void adjustCycleCounter(Int32 amount) override { }
|
||||
|
||||
/**
|
||||
Sets the number of channels (mono or stereo sound).
|
||||
|
||||
@param channels The number of channels
|
||||
*/
|
||||
void setChannels(uInt32 channels) override { }
|
||||
|
||||
/**
|
||||
Sets the display framerate. Sound generation for NTSC and PAL games
|
||||
depends on the framerate, so we need to set it here.
|
||||
|
||||
@param framerate The base framerate depending on NTSC or PAL ROM
|
||||
*/
|
||||
void setFrameRate(float framerate) override { }
|
||||
void setEnabled(bool) override { }
|
||||
|
||||
/**
|
||||
Initializes the sound device. This must be called before any
|
||||
calls are made to derived methods.
|
||||
*/
|
||||
void open() override { }
|
||||
void open(bool) override { }
|
||||
|
||||
/**
|
||||
Should be called to close the sound device. Once called the sound
|
||||
|
@ -92,89 +65,25 @@ class SoundNull : public Sound
|
|||
|
||||
/**
|
||||
Set the mute state of the sound object. While muted no sound is played.
|
||||
|
||||
@param state Mutes sound if true, unmute if false
|
||||
*/
|
||||
void mute(bool state) override { }
|
||||
void mute(bool) override { }
|
||||
|
||||
/**
|
||||
Reset the sound device.
|
||||
*/
|
||||
void reset() { }
|
||||
|
||||
/**
|
||||
Sets the sound register to a given value.
|
||||
|
||||
@param addr The register address
|
||||
@param value The value to save into the register
|
||||
@param cycle The system cycle at which the register is being updated
|
||||
*/
|
||||
void set(uInt16 addr, uInt8 value, Int32 cycle) override { }
|
||||
|
||||
/**
|
||||
Sets the volume of the sound device to the specified level. The
|
||||
volume is given as a percentage from 0 to 100. Values outside
|
||||
this range indicate that the volume shouldn't be changed at all.
|
||||
|
||||
@param percent The new volume percentage level for the sound device
|
||||
*/
|
||||
void setVolume(Int32 percent) override { }
|
||||
void setVolume(uInt32) override { }
|
||||
|
||||
/**
|
||||
Adjusts the volume of the sound device based on the given direction.
|
||||
|
||||
@param direction Increase or decrease the current volume by a predefined
|
||||
amount based on the direction (1 = increase, -1 =decrease)
|
||||
*/
|
||||
void adjustVolume(Int8 direction) override { }
|
||||
|
||||
public:
|
||||
/**
|
||||
Saves the current state of this device to the given Serializer.
|
||||
|
||||
@param out The serializer device to save to.
|
||||
@return The result of the save. True on success, false on failure.
|
||||
*/
|
||||
bool save(Serializer& out) const
|
||||
{
|
||||
out.putString("TIASound");
|
||||
|
||||
for(int i = 0; i < 6; ++i)
|
||||
out.putByte(0);
|
||||
|
||||
// myLastRegisterSetCycle
|
||||
out.putInt(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
Loads the current state of this device from the given Serializer.
|
||||
|
||||
@param in The Serializer device to load from.
|
||||
@return The result of the load. True on success, false on failure.
|
||||
*/
|
||||
bool load(Serializer& in)
|
||||
{
|
||||
if(in.getString() != "TIASound")
|
||||
return false;
|
||||
|
||||
// Read sound registers and discard
|
||||
for(int i = 0; i < 6; ++i)
|
||||
in.getByte();
|
||||
|
||||
// myLastRegisterSetCycle
|
||||
in.getInt();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
Get a descriptor for this console class (used in error checking).
|
||||
|
||||
@return The name of the object
|
||||
*/
|
||||
string name() const { return "TIASound"; }
|
||||
void adjustVolume(Int8) override { }
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
#include <cmath>
|
||||
#include <SDL.h>
|
||||
|
||||
#include "TIASnd.hxx"
|
||||
#include "TIATables.hxx"
|
||||
#include "TIA.hxx"
|
||||
#include "FrameBuffer.hxx"
|
||||
#include "Settings.hxx"
|
||||
#include "System.hxx"
|
||||
|
@ -38,11 +37,6 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
|
|||
: Sound(osystem),
|
||||
myIsEnabled(false),
|
||||
myIsInitializedFlag(false),
|
||||
myLastRegisterSetCycle(0),
|
||||
myNumChannels(0),
|
||||
myFragmentSizeLogBase2(0),
|
||||
myFragmentSizeLogDiv1(0),
|
||||
myFragmentSizeLogDiv2(0),
|
||||
myIsMuted(true),
|
||||
myVolume(100)
|
||||
{
|
||||
|
@ -53,11 +47,12 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
|
|||
// This fixes a bug most prevalent with ATI video cards in Windows,
|
||||
// whereby sound stopped working after the first video change
|
||||
SDL_AudioSpec desired;
|
||||
SDL_memset(&desired, 0, sizeof(desired));
|
||||
desired.freq = myOSystem.settings().getInt("freq");
|
||||
desired.format = AUDIO_S16SYS;
|
||||
desired.channels = 2;
|
||||
desired.samples = myOSystem.settings().getInt("fragsize");
|
||||
desired.callback = callback;
|
||||
desired.callback = getSamples;
|
||||
desired.userdata = static_cast<void*>(this);
|
||||
|
||||
ostringstream buf;
|
||||
|
@ -69,24 +64,6 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
|
|||
return;
|
||||
}
|
||||
|
||||
// Make sure the sample buffer isn't to big (if it is the sound code
|
||||
// will not work so we'll need to disable the audio support)
|
||||
if((float(myHardwareSpec.samples) / float(myHardwareSpec.freq)) >= 0.25)
|
||||
{
|
||||
buf << "WARNING: Sound device doesn't support realtime audio! Make "
|
||||
<< "sure a sound" << endl
|
||||
<< " server isn't running. Audio is disabled." << endl;
|
||||
myOSystem.logMessage(buf.str(), 0);
|
||||
|
||||
SDL_CloseAudio();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-compute fragment-related variables as much as possible
|
||||
myFragmentSizeLogBase2 = log(myHardwareSpec.samples) / log(2.0);
|
||||
myFragmentSizeLogDiv1 = myFragmentSizeLogBase2 / 60.0;
|
||||
myFragmentSizeLogDiv2 = (myFragmentSizeLogBase2 - 1) / 60.0;
|
||||
|
||||
myIsInitializedFlag = true;
|
||||
SDL_PauseAudio(1);
|
||||
|
||||
|
@ -99,6 +76,7 @@ SoundSDL2::~SoundSDL2()
|
|||
// Close the SDL audio system if it's initialized
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
//FIXME SDL_PauseAudio(1);
|
||||
SDL_CloseAudio();
|
||||
myIsEnabled = myIsInitializedFlag = false;
|
||||
}
|
||||
|
@ -114,7 +92,7 @@ void SoundSDL2::setEnabled(bool state)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::open()
|
||||
void SoundSDL2::open(bool stereo)
|
||||
{
|
||||
myOSystem.logMessage("SoundSDL2::open started ...", 2);
|
||||
myIsEnabled = false;
|
||||
|
@ -126,9 +104,8 @@ void SoundSDL2::open()
|
|||
}
|
||||
|
||||
// Now initialize the TIASound object which will actually generate sound
|
||||
myTIASound.outputFrequency(myHardwareSpec.freq);
|
||||
const string& chanResult =
|
||||
myTIASound.channels(myHardwareSpec.channels, myNumChannels == 2);
|
||||
myOSystem.console().tia().sound().channels(myHardwareSpec.channels, stereo);
|
||||
|
||||
// Adjust volume to that defined in settings
|
||||
myVolume = myOSystem.settings().getInt("volume");
|
||||
|
@ -147,7 +124,7 @@ void SoundSDL2::open()
|
|||
|
||||
// And start the SDL sound subsystem ...
|
||||
myIsEnabled = true;
|
||||
mute(false);
|
||||
SDL_PauseAudio(0);//myIsMuted ? 1 : 0);
|
||||
|
||||
myOSystem.logMessage("SoundSDL2::open finished", 2);
|
||||
}
|
||||
|
@ -159,9 +136,8 @@ void SoundSDL2::close()
|
|||
{
|
||||
myIsEnabled = false;
|
||||
SDL_PauseAudio(1);
|
||||
myLastRegisterSetCycle = 0;
|
||||
myTIASound.reset();
|
||||
myRegWriteQueue.clear();
|
||||
if(myOSystem.hasConsole())
|
||||
myOSystem.console().tia().sound().reset();
|
||||
myOSystem.logMessage("SoundSDL2::close", 2);
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +148,10 @@ void SoundSDL2::mute(bool state)
|
|||
if(myIsInitializedFlag)
|
||||
{
|
||||
myIsMuted = state;
|
||||
SDL_PauseAudio(myIsMuted ? 1 : 0);
|
||||
if(myOSystem.hasConsole())
|
||||
myOSystem.console().tia().sound().volume(myIsMuted ? 0 : myVolume);
|
||||
|
||||
// SDL_PauseAudio(myIsMuted ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,23 +161,21 @@ void SoundSDL2::reset()
|
|||
if(myIsInitializedFlag)
|
||||
{
|
||||
SDL_PauseAudio(1);
|
||||
myLastRegisterSetCycle = 0;
|
||||
myTIASound.reset();
|
||||
myRegWriteQueue.clear();
|
||||
mute(myIsMuted);
|
||||
if(myOSystem.hasConsole())
|
||||
myOSystem.console().tia().sound().reset();
|
||||
// mute(myIsMuted);
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::setVolume(Int32 percent)
|
||||
void SoundSDL2::setVolume(uInt32 volume)
|
||||
{
|
||||
if(myIsInitializedFlag && (percent >= 0) && (percent <= 100))
|
||||
if(myIsInitializedFlag && (volume <= 100))
|
||||
{
|
||||
myOSystem.settings().setValue("volume", percent);
|
||||
SDL_LockAudio();
|
||||
myVolume = percent;
|
||||
myTIASound.volume(percent);
|
||||
SDL_UnlockAudio();
|
||||
myOSystem.settings().setValue("volume", volume);
|
||||
myVolume = volume;
|
||||
if(myOSystem.hasConsole())
|
||||
myOSystem.console().tia().sound().volume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,52 +206,25 @@ void SoundSDL2::adjustVolume(Int8 direction)
|
|||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::adjustCycleCounter(Int32 amount)
|
||||
void SoundSDL2::getSamples(void* udata, uInt8* stream, int len)
|
||||
{
|
||||
myLastRegisterSetCycle += amount;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::setChannels(uInt32 channels)
|
||||
{
|
||||
if(channels == 1 || channels == 2)
|
||||
myNumChannels = channels;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::setFrameRate(float framerate)
|
||||
{
|
||||
// Recalculate since frame rate has changed
|
||||
// FIXME - should we clear out the queue or adjust the values in it?
|
||||
myFragmentSizeLogDiv1 = myFragmentSizeLogBase2 / framerate;
|
||||
myFragmentSizeLogDiv2 = (myFragmentSizeLogBase2 - 1) / framerate;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::set(uInt16 addr, uInt8 value, Int32 cycle)
|
||||
{
|
||||
SDL_LockAudio();
|
||||
|
||||
// First, calculate how many seconds would have past since the last
|
||||
// register write on a real 2600
|
||||
double delta = double(cycle - myLastRegisterSetCycle) / 1193191.66666667;
|
||||
|
||||
// Now, adjust the time based on the frame rate the user has selected. For
|
||||
// the sound to "scale" correctly, we have to know the games real frame
|
||||
// rate (e.g., 50 or 60) and the currently emulated frame rate. We use these
|
||||
// values to "scale" the time before the register change occurs.
|
||||
RegWrite info;
|
||||
info.addr = addr;
|
||||
info.value = value;
|
||||
info.delta = delta;
|
||||
myRegWriteQueue.enqueue(info);
|
||||
|
||||
// Update last cycle counter to the current cycle
|
||||
myLastRegisterSetCycle = cycle;
|
||||
|
||||
SDL_UnlockAudio();
|
||||
SoundSDL2* sound = static_cast<SoundSDL2*>(udata);
|
||||
if(sound->myIsEnabled)
|
||||
{
|
||||
// The callback is requesting 8-bit data, but the TIA sound emulator
|
||||
// deals in 16-bit data
|
||||
// So, we need to convert the pointer and half the length
|
||||
uInt16* buffer = reinterpret_cast<uInt16*>(stream);
|
||||
Int32 left =
|
||||
sound->myOSystem.console().tia().sound().getSamples(buffer, uInt32(len) >> 1);
|
||||
if(left > 0) // Is silence required?
|
||||
SDL_memset(buffer, 0, uInt32(left) << 1);
|
||||
}
|
||||
else
|
||||
SDL_memset(stream, 0, len); // Write 'silence'
|
||||
}
|
||||
|
||||
#if 0
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::processFragment(Int16* stream, uInt32 length)
|
||||
{
|
||||
|
@ -355,166 +305,6 @@ void SoundSDL2::processFragment(Int16* stream, uInt32 length)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::callback(void* udata, uInt8* stream, int len)
|
||||
{
|
||||
SoundSDL2* sound = static_cast<SoundSDL2*>(udata);
|
||||
if(sound->myIsEnabled)
|
||||
{
|
||||
// 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(reinterpret_cast<Int16*>(stream), uInt32(len) >> 1);
|
||||
}
|
||||
else
|
||||
SDL_memset(stream, 0, len); // Write 'silence'
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool SoundSDL2::save(Serializer& out) const
|
||||
{
|
||||
try
|
||||
{
|
||||
out.putString(name());
|
||||
|
||||
// Only get the TIA sound registers if sound is enabled
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
out.putByte(myTIASound.get(TIARegister::AUDC0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDC1));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDF0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDF1));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDV0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDV1));
|
||||
}
|
||||
else
|
||||
for(int i = 0; i < 6; ++i)
|
||||
out.putByte(0);
|
||||
|
||||
out.putInt(myLastRegisterSetCycle);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
myOSystem.logMessage("ERROR: SoundSDL2::save", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool SoundSDL2::load(Serializer& in)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(in.getString() != name())
|
||||
return false;
|
||||
|
||||
// Only update the TIA sound registers if sound is enabled
|
||||
// Make sure to empty the queue of previous sound fragments
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
SDL_PauseAudio(1);
|
||||
myRegWriteQueue.clear();
|
||||
myTIASound.set(TIARegister::AUDC0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDC1, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDF0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDF1, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDV0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDV1, in.getByte());
|
||||
if(!myIsMuted) SDL_PauseAudio(0);
|
||||
}
|
||||
else
|
||||
for(int i = 0; i < 6; ++i)
|
||||
in.getByte();
|
||||
|
||||
myLastRegisterSetCycle = in.getInt();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
myOSystem.logMessage("ERROR: SoundSDL2::load", 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SoundSDL2::RegWriteQueue::RegWriteQueue(uInt32 capacity)
|
||||
: myBuffer(make_ptr<RegWrite[]>(capacity)),
|
||||
myCapacity(capacity),
|
||||
mySize(0),
|
||||
myHead(0),
|
||||
myTail(0)
|
||||
{
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::clear()
|
||||
{
|
||||
myHead = myTail = mySize = 0;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::dequeue()
|
||||
{
|
||||
if(mySize > 0)
|
||||
{
|
||||
myHead = (myHead + 1) % myCapacity;
|
||||
--mySize;
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
double SoundSDL2::RegWriteQueue::duration() const
|
||||
{
|
||||
double duration = 0.0;
|
||||
for(uInt32 i = 0; i < mySize; ++i)
|
||||
{
|
||||
duration += myBuffer[(myHead + i) % myCapacity].delta;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::enqueue(const RegWrite& info)
|
||||
{
|
||||
// If an attempt is made to enqueue more than the queue can hold then
|
||||
// we'll enlarge the queue's capacity.
|
||||
if(mySize == myCapacity)
|
||||
grow();
|
||||
|
||||
myBuffer[myTail] = info;
|
||||
myTail = (myTail + 1) % myCapacity;
|
||||
++mySize;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
SoundSDL2::RegWrite& SoundSDL2::RegWriteQueue::front() const
|
||||
{
|
||||
assert(mySize != 0);
|
||||
return myBuffer[myHead];
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt32 SoundSDL2::RegWriteQueue::size() const
|
||||
{
|
||||
return mySize;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void SoundSDL2::RegWriteQueue::grow()
|
||||
{
|
||||
unique_ptr<RegWrite[]> buffer = make_ptr<RegWrite[]>(myCapacity*2);
|
||||
for(uInt32 i = 0; i < mySize; ++i)
|
||||
buffer[i] = myBuffer[(myHead + i) % myCapacity];
|
||||
|
||||
myHead = 0;
|
||||
myTail = mySize;
|
||||
myCapacity *= 2;
|
||||
|
||||
myBuffer = std::move(buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // SOUND_SUPPORT
|
||||
|
|
|
@ -27,27 +27,22 @@ class OSystem;
|
|||
#include <SDL.h>
|
||||
|
||||
#include "bspf.hxx"
|
||||
#include "TIASnd.hxx"
|
||||
#include "Sound.hxx"
|
||||
|
||||
/**
|
||||
This class implements the sound API for SDL.
|
||||
This class implements the sound API for SDL2.
|
||||
|
||||
@author Stephen Anthony and Bradford W. Mott
|
||||
@author Stephen Anthony
|
||||
@version $Id$
|
||||
*/
|
||||
class SoundSDL2 : public Sound
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Create a new sound object. The init method must be invoked before
|
||||
using the object.
|
||||
Create a new sound object. The open() method must be invoked
|
||||
before using the object.
|
||||
*/
|
||||
SoundSDL2(OSystem& osystem);
|
||||
|
||||
/**
|
||||
Destructor
|
||||
*/
|
||||
virtual ~SoundSDL2();
|
||||
|
||||
public:
|
||||
|
@ -58,38 +53,13 @@ class SoundSDL2 : public Sound
|
|||
*/
|
||||
void setEnabled(bool state) override;
|
||||
|
||||
/**
|
||||
The system cycle counter is being adjusting by the specified amount. Any
|
||||
members using the system cycle counter should be adjusted as needed.
|
||||
|
||||
@param amount The amount the cycle counter is being adjusted by
|
||||
*/
|
||||
void adjustCycleCounter(Int32 amount) override;
|
||||
|
||||
/**
|
||||
Sets the number of channels (mono or stereo sound). Note that this
|
||||
determines how the emulation should 'mix' the channels of the TIA sound
|
||||
system (of which there are always two). It does not specify the actual
|
||||
number of hardware channels that SDL should use; it will always attempt
|
||||
to use two channels in hardware.
|
||||
|
||||
@param channels The number of channels
|
||||
*/
|
||||
void setChannels(uInt32 channels) override;
|
||||
|
||||
/**
|
||||
Sets the display framerate. Sound generation for NTSC and PAL games
|
||||
depends on the framerate, so we need to set it here.
|
||||
|
||||
@param framerate The base framerate depending on NTSC or PAL ROM
|
||||
*/
|
||||
void setFrameRate(float framerate) override;
|
||||
|
||||
/**
|
||||
Initializes the sound device. This must be called before any
|
||||
calls are made to derived methods.
|
||||
|
||||
@param stereo The number of channels (mono -> 1, stereo -> 2)
|
||||
*/
|
||||
void open() override;
|
||||
void open(bool stereo) override;
|
||||
|
||||
/**
|
||||
Should be called to close the sound device. Once called the sound
|
||||
|
@ -109,23 +79,14 @@ class SoundSDL2 : public Sound
|
|||
*/
|
||||
void reset() override;
|
||||
|
||||
/**
|
||||
Sets the sound register to a given value.
|
||||
|
||||
@param addr The register address
|
||||
@param value The value to save into the register
|
||||
@param cycle The system cycle at which the register is being updated
|
||||
*/
|
||||
void set(uInt16 addr, uInt8 value, Int32 cycle) override;
|
||||
|
||||
/**
|
||||
Sets the volume of the sound device to the specified level. The
|
||||
volume is given as a percentage from 0 to 100. Values outside
|
||||
this range indicate that the volume shouldn't be changed at all.
|
||||
|
||||
@param percent The new volume percentage level for the sound device
|
||||
@param volume The new volume percentage level for the sound device
|
||||
*/
|
||||
void setVolume(Int32 percent) override;
|
||||
void setVolume(uInt32 volume) override;
|
||||
|
||||
/**
|
||||
Adjusts the volume of the sound device based on the given direction.
|
||||
|
@ -135,143 +96,13 @@ class SoundSDL2 : public Sound
|
|||
*/
|
||||
void adjustVolume(Int8 direction) override;
|
||||
|
||||
public:
|
||||
/**
|
||||
Saves the current state of this device to the given Serializer.
|
||||
|
||||
@param out The serializer device to save to.
|
||||
@return The result of the save. True on success, false on failure.
|
||||
*/
|
||||
bool save(Serializer& out) const override;
|
||||
|
||||
/**
|
||||
Loads the current state of this device from the given Serializer.
|
||||
|
||||
@param in The Serializer device to load from.
|
||||
@return The result of the load. True on success, false on failure.
|
||||
*/
|
||||
bool load(Serializer& in) override;
|
||||
|
||||
/**
|
||||
Get a descriptor for this console class (used in error checking).
|
||||
|
||||
@return The name of the object
|
||||
*/
|
||||
string name() const override { return "TIASound"; }
|
||||
|
||||
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(Int16* stream, uInt32 length);
|
||||
|
||||
protected:
|
||||
// Struct to hold information regarding a TIA sound register write
|
||||
struct RegWrite
|
||||
{
|
||||
uInt16 addr;
|
||||
uInt8 value;
|
||||
double delta;
|
||||
};
|
||||
|
||||
/**
|
||||
A queue class used to hold TIA sound register writes before being
|
||||
processed while creating a sound fragment.
|
||||
*/
|
||||
class RegWriteQueue
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Create a new queue instance with the specified initial
|
||||
capacity. If the queue ever reaches its capacity then it will
|
||||
automatically increase its size.
|
||||
*/
|
||||
RegWriteQueue(uInt32 capacity = 512);
|
||||
|
||||
public:
|
||||
/**
|
||||
Clear any items stored in the queue.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
Dequeue the first object in the queue.
|
||||
*/
|
||||
void dequeue();
|
||||
|
||||
/**
|
||||
Return the duration of all the items in the queue.
|
||||
*/
|
||||
double duration() const;
|
||||
|
||||
/**
|
||||
Enqueue the specified object.
|
||||
*/
|
||||
void enqueue(const RegWrite& info);
|
||||
|
||||
/**
|
||||
Return the item at the front on the queue.
|
||||
|
||||
@return The item at the front of the queue.
|
||||
*/
|
||||
RegWrite& front() const;
|
||||
|
||||
/**
|
||||
Answers the number of items currently in the queue.
|
||||
|
||||
@return The number of items in the queue.
|
||||
*/
|
||||
uInt32 size() const;
|
||||
|
||||
private:
|
||||
// Increase the size of the queue
|
||||
void grow();
|
||||
|
||||
private:
|
||||
unique_ptr<RegWrite[]> myBuffer;
|
||||
uInt32 myCapacity;
|
||||
uInt32 mySize;
|
||||
uInt32 myHead;
|
||||
uInt32 myTail;
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
RegWriteQueue(const RegWriteQueue&) = delete;
|
||||
RegWriteQueue(RegWriteQueue&&) = delete;
|
||||
RegWriteQueue& operator=(const RegWriteQueue&) = delete;
|
||||
RegWriteQueue& operator=(RegWriteQueue&&) = delete;
|
||||
};
|
||||
|
||||
private:
|
||||
// TIASound emulation object
|
||||
TIASound myTIASound;
|
||||
|
||||
// Indicates if the sound subsystem is to be initialized
|
||||
bool myIsEnabled;
|
||||
|
||||
// Indicates if the sound device was successfully initialized
|
||||
bool myIsInitializedFlag;
|
||||
|
||||
// Indicates the cycle when a sound register was last set
|
||||
Int32 myLastRegisterSetCycle;
|
||||
|
||||
// Indicates the number of channels (mono or stereo)
|
||||
uInt32 myNumChannels;
|
||||
|
||||
// Log base 2 of the selected fragment size
|
||||
double myFragmentSizeLogBase2;
|
||||
|
||||
// The myFragmentSizeLogBase2 variable is used in only two places,
|
||||
// both of which involve an expensive division in the sound
|
||||
// processing callback
|
||||
// These are pre-computed to speed up the callback as much as possible
|
||||
double myFragmentSizeLogDiv1, myFragmentSizeLogDiv2;
|
||||
|
||||
// Indicates if the sound is currently muted
|
||||
bool myIsMuted;
|
||||
|
||||
|
@ -281,12 +112,9 @@ class SoundSDL2 : public Sound
|
|||
// Audio specification structure
|
||||
SDL_AudioSpec myHardwareSpec;
|
||||
|
||||
// Queue of TIA register writes
|
||||
RegWriteQueue myRegWriteQueue;
|
||||
|
||||
private:
|
||||
// Callback function invoked by the SDL Audio library when it needs data
|
||||
static void callback(void* udata, uInt8* stream, int len);
|
||||
static void getSamples(void* udata, uInt8* stream, int len);
|
||||
|
||||
// Following constructors and assignment operators not supported
|
||||
SoundSDL2() = delete;
|
||||
|
|
|
@ -430,9 +430,7 @@ void Console::initializeAudio()
|
|||
const string& sound = myProperties.get(Cartridge_Sound);
|
||||
|
||||
myOSystem.sound().close();
|
||||
myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1);
|
||||
myOSystem.sound().setFrameRate(myFramerate);
|
||||
myOSystem.sound().open();
|
||||
myOSystem.sound().open(sound == "STEREO");
|
||||
|
||||
// Make sure auto-frame calculation is only enabled when necessary
|
||||
myTIA->enableAutoFrame(framerate <= 0);
|
||||
|
@ -838,7 +836,6 @@ void Console::setFramerate(float framerate)
|
|||
{
|
||||
myFramerate = framerate;
|
||||
myOSystem.setFramerate(framerate);
|
||||
myOSystem.sound().setFrameRate(framerate);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
class OSystem;
|
||||
|
||||
#include "Serializable.hxx"
|
||||
#include "bspf.hxx"
|
||||
|
||||
/**
|
||||
|
@ -32,11 +31,11 @@ class OSystem;
|
|||
@author Stephen Anthony
|
||||
@version $Id$
|
||||
*/
|
||||
class Sound : public Serializable
|
||||
class Sound
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Create a new sound object. The init method must be invoked before
|
||||
Create a new sound object. The open method must be invoked before
|
||||
using the object.
|
||||
*/
|
||||
Sound(OSystem& osystem) : myOSystem(osystem) { }
|
||||
|
@ -50,34 +49,13 @@ class Sound : public Serializable
|
|||
*/
|
||||
virtual void setEnabled(bool enable) = 0;
|
||||
|
||||
/**
|
||||
The system cycle counter is being adjusting by the specified amount. Any
|
||||
members using the system cycle counter should be adjusted as needed.
|
||||
|
||||
@param amount The amount the cycle counter is being adjusted by
|
||||
*/
|
||||
virtual void adjustCycleCounter(Int32 amount) = 0;
|
||||
|
||||
/**
|
||||
Sets the number of channels (mono or stereo sound).
|
||||
|
||||
@param channels The number of channels
|
||||
*/
|
||||
virtual void setChannels(uInt32 channels) = 0;
|
||||
|
||||
/**
|
||||
Sets the display framerate. Sound generation for NTSC and PAL games
|
||||
depends on the framerate, so we need to set it here.
|
||||
|
||||
@param framerate The base framerate depending on NTSC or PAL ROM
|
||||
*/
|
||||
virtual void setFrameRate(float framerate) = 0;
|
||||
|
||||
/**
|
||||
Start the sound system, initializing it if necessary. This must be
|
||||
called before any calls are made to derived methods.
|
||||
|
||||
@param stereo The number of channels (mono -> 1, stereo -> 2)
|
||||
*/
|
||||
virtual void open() = 0;
|
||||
virtual void open(bool stereo) = 0;
|
||||
|
||||
/**
|
||||
Should be called to stop the sound system. Once called the sound
|
||||
|
@ -97,23 +75,14 @@ class Sound : public Serializable
|
|||
*/
|
||||
virtual void reset() = 0;
|
||||
|
||||
/**
|
||||
Sets the sound register to a given value.
|
||||
|
||||
@param addr The register address
|
||||
@param value The value to save into the register
|
||||
@param cycle The system cycle at which the register is being updated
|
||||
*/
|
||||
virtual void set(uInt16 addr, uInt8 value, Int32 cycle) = 0;
|
||||
|
||||
/**
|
||||
Sets the volume of the sound device to the specified level. The
|
||||
volume is given as a percentage from 0 to 100. Values outside
|
||||
this range indicate that the volume shouldn't be changed at all.
|
||||
|
||||
@param percent The new volume percentage level for the sound device
|
||||
@param volume The new volume percentage level for the sound device
|
||||
*/
|
||||
virtual void setVolume(Int32 percent) = 0;
|
||||
virtual void setVolume(uInt32 volume) = 0;
|
||||
|
||||
/**
|
||||
Adjusts the volume of the sound device based on the given direction.
|
||||
|
|
|
@ -21,26 +21,23 @@
|
|||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "bspf.hxx"
|
||||
|
||||
#ifdef DEBUGGER_SUPPORT
|
||||
#include "CartDebug.hxx"
|
||||
#endif
|
||||
|
||||
#include "Console.hxx"
|
||||
#include "Control.hxx"
|
||||
#include "Device.hxx"
|
||||
#include "M6502.hxx"
|
||||
#include "Settings.hxx"
|
||||
#include "Sound.hxx"
|
||||
#include "System.hxx"
|
||||
#include "TIATables.hxx"
|
||||
|
||||
#include "TIA.hxx"
|
||||
|
||||
#define HBLANK 68
|
||||
#define CLAMP_POS(reg) if(reg < 0) { reg += 160; } reg %= 160;
|
||||
|
||||
static Int32 START_SLINE = -1;
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
TIA::TIA(Console& console, Sound& sound, Settings& settings)
|
||||
: myConsole(console),
|
||||
|
@ -65,6 +62,7 @@ TIA::TIA(Console& console, Sound& sound, Settings& settings)
|
|||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIA::initialize()
|
||||
{
|
||||
START_SLINE = -1;
|
||||
myFramePointer = nullptr;
|
||||
myFramePointerOffset = myFramePointerClocks = myStopDisplayOffset = 0;
|
||||
|
||||
|
@ -214,9 +212,6 @@ void TIA::systemCyclesReset()
|
|||
// Get the current system cycle
|
||||
uInt32 cycles = mySystem->cycles();
|
||||
|
||||
// Adjust the sound cycle indicator
|
||||
mySound.adjustCycleCounter(-1 * cycles);
|
||||
|
||||
// Adjust the dump cycle
|
||||
myDumpDisabledCycle -= cycles;
|
||||
|
||||
|
@ -344,7 +339,7 @@ bool TIA::save(Serializer& out) const
|
|||
out.putInt(myPALFrameCounter);
|
||||
|
||||
// Save the sound sample stuff ...
|
||||
mySound.save(out);
|
||||
myTIASound.save(out);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
@ -448,7 +443,7 @@ bool TIA::load(Serializer& in)
|
|||
myPALFrameCounter = in.getInt();
|
||||
|
||||
// Load the sound sample stuff ...
|
||||
mySound.load(in);
|
||||
myTIASound.load(in);
|
||||
|
||||
// Reset TIA bits to be on
|
||||
enableBits(true);
|
||||
|
@ -540,7 +535,7 @@ inline void TIA::startFrame()
|
|||
// so that we can adjust the frame's starting clock by this amount. This
|
||||
// is necessary since some games position objects during VSYNC and the
|
||||
// TIA's internal counters are not reset by VSYNC.
|
||||
uInt32 clocks = ((mySystem->cycles() * 3) - myClockWhenFrameStarted) % 228;
|
||||
uInt32 clocks = clocksThisLine();
|
||||
|
||||
// Ask the system to reset the cycle count so it doesn't overflow
|
||||
mySystem->resetCycles();
|
||||
|
@ -587,6 +582,8 @@ inline void TIA::startFrame()
|
|||
myFrameCounter++;
|
||||
if(myScanlineCountForLastFrame >= 287)
|
||||
myPALFrameCounter++;
|
||||
|
||||
START_SLINE = -1;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -1230,6 +1227,16 @@ void TIA::updateFrame(Int32 clock)
|
|||
myHMOVEBlankEnabled = false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if(START_SLINE != scanlines())
|
||||
{
|
||||
cerr << " => scanline: " << scanlines() << endl;
|
||||
// cerr << "scanline: " << (line-1) << endl;
|
||||
myTIASound.queueSamples();
|
||||
START_SLINE = scanlines();
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO - this needs to be updated to actually do as the comment suggests
|
||||
#if 1
|
||||
// See if we're at the end of a scanline
|
||||
|
@ -1239,8 +1246,17 @@ void TIA::updateFrame(Int32 clock)
|
|||
// of the player has passed. However, for now we'll just reset at the
|
||||
// end of the scanline since the other way would be too slow.
|
||||
mySuppressP0 = mySuppressP1 = 0;
|
||||
|
||||
#if 1
|
||||
if(START_SLINE != scanlines())
|
||||
{
|
||||
cerr << " => scanline: " << scanlines() << endl;
|
||||
myTIASound.queueSamples();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1252,6 +1268,14 @@ inline void TIA::waitHorizontalSync()
|
|||
|
||||
if(cyclesToEndOfLine < 76)
|
||||
mySystem->incrementCycles(cyclesToEndOfLine);
|
||||
|
||||
#if 1
|
||||
{
|
||||
cerr << " => scanline(W): " << scanlines() << endl;
|
||||
myTIASound.queueSamples();
|
||||
START_SLINE = scanlines();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -1865,42 +1889,48 @@ bool TIA::poke(uInt16 addr, uInt8 value)
|
|||
case AUDC0: // Audio control 0
|
||||
{
|
||||
myAUDC0 = value & 0x0f;
|
||||
mySound.set(addr, value, mySystem->cycles());
|
||||
cerr << scanlines() << ": C0, " << hex << int(myAUDC0) << ", " << dec << ((clock - myClockWhenFrameStarted) % 228) << endl;
|
||||
myTIASound.writeAudC0(value, (clock - myClockWhenFrameStarted) % 228);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDC1: // Audio control 1
|
||||
{
|
||||
myAUDC1 = value & 0x0f;
|
||||
mySound.set(addr, value, mySystem->cycles());
|
||||
cerr << scanlines() << ": C1, " << hex << int(myAUDC1) << ", " << dec << ((clock - myClockWhenFrameStarted) % 228) << endl;
|
||||
myTIASound.writeAudC1(value, (clock - myClockWhenFrameStarted) % 228);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDF0: // Audio frequency 0
|
||||
{
|
||||
myAUDF0 = value & 0x1f;
|
||||
mySound.set(addr, value, mySystem->cycles());
|
||||
cerr << scanlines() << ": F0, " << hex << int(myAUDF0) << ", " << dec << ((clock - myClockWhenFrameStarted) % 228) << endl;
|
||||
myTIASound.writeAudF0(value, (clock - myClockWhenFrameStarted) % 228);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDF1: // Audio frequency 1
|
||||
{
|
||||
myAUDF1 = value & 0x1f;
|
||||
mySound.set(addr, value, mySystem->cycles());
|
||||
cerr << scanlines() << ": F1, " << hex << int(myAUDF1) << ", " << dec << ((clock - myClockWhenFrameStarted) % 228) << endl;
|
||||
myTIASound.writeAudF1(value, (clock - myClockWhenFrameStarted) % 228);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDV0: // Audio volume 0
|
||||
{
|
||||
myAUDV0 = value & 0x0f;
|
||||
mySound.set(addr, value, mySystem->cycles());
|
||||
cerr << scanlines() << ": V0, " << hex << int(myAUDV0) << ", " << dec << ((clock - myClockWhenFrameStarted) % 228) << endl;
|
||||
myTIASound.writeAudV0(value, (clock - myClockWhenFrameStarted) % 228);
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDV1: // Audio volume 1
|
||||
{
|
||||
myAUDV1 = value & 0x0f;
|
||||
mySound.set(addr, value, mySystem->cycles());
|
||||
cerr << scanlines() << ": V1, " << hex << int(myAUDV1) << ", " << dec << ((clock - myClockWhenFrameStarted) % 228) << endl;
|
||||
myTIASound.writeAudV1(value, (clock - myClockWhenFrameStarted) % 228);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ class Sound;
|
|||
#include "Device.hxx"
|
||||
#include "System.hxx"
|
||||
#include "TIATables.hxx"
|
||||
#include "TIASnd.hxx"
|
||||
|
||||
/**
|
||||
This class is a device that emulates the Television Interface Adaptor
|
||||
|
@ -96,6 +97,14 @@ class TIA : public Device
|
|||
*/
|
||||
void install(System& system, Device& device);
|
||||
|
||||
/**
|
||||
Get the TIA sound object associated with the TIA.
|
||||
|
||||
@param out The Serializer object to use
|
||||
@return False on any errors, else true
|
||||
*/
|
||||
TIASound& sound() { return myTIASound; }
|
||||
|
||||
/**
|
||||
Save the current state of this device to the given Serializer.
|
||||
|
||||
|
@ -407,6 +416,9 @@ class TIA : public Device
|
|||
// Settings object the TIA is associated with
|
||||
Settings& mySettings;
|
||||
|
||||
// TIASound emulation object
|
||||
TIASound myTIASound;
|
||||
|
||||
// Pointer to the current frame buffer
|
||||
unique_ptr<uInt8[]> myCurrentFrameBuffer;
|
||||
|
||||
|
|
|
@ -22,45 +22,58 @@
|
|||
#include "TIASnd.hxx"
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
TIASound::TIASound(Int32 outputFrequency)
|
||||
: myChannelMode(Hardware2Stereo),
|
||||
myOutputFrequency(outputFrequency),
|
||||
myOutputCounter(0),
|
||||
myVolumePercentage(100)
|
||||
TIASound::TIASound()
|
||||
: myChannelMode(Hardware2Mono),
|
||||
myHWVol(100)
|
||||
{
|
||||
// Build volume lookup table
|
||||
constexpr double ra = 1.0 / 30.0;
|
||||
constexpr double rb = 1.0 / 15.0;
|
||||
constexpr double rc = 1.0 / 7.5;
|
||||
constexpr double rd = 1.0 / 3.75;
|
||||
|
||||
memset(myVolLUT, 0, sizeof(uInt16)*256*101);
|
||||
for(int i = 1; i < 256; ++i)
|
||||
{
|
||||
double r2 = 0.0;
|
||||
|
||||
if(i & 0x01) r2 += ra;
|
||||
if(i & 0x02) r2 += rb;
|
||||
if(i & 0x04) r2 += rc;
|
||||
if(i & 0x08) r2 += rd;
|
||||
if(i & 0x10) r2 += ra;
|
||||
if(i & 0x20) r2 += rb;
|
||||
if(i & 0x40) r2 += rc;
|
||||
if(i & 0x80) r2 += rd;
|
||||
|
||||
r2 = 1.0 / r2;
|
||||
uInt16 vol = uInt16(32768.0 * (1.0 - r2 / (1.0 + r2)) + 0.5);
|
||||
|
||||
// Pre-calculate all possible volume levels
|
||||
for(int j = 0; j <= 100; ++j)
|
||||
myVolLUT[i][j] = vol * j / 100;
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::reset()
|
||||
{
|
||||
// Fill the polynomials
|
||||
polyInit(Bit4, 4, 4, 3);
|
||||
polyInit(Bit5, 5, 5, 3);
|
||||
polyInit(Bit9, 9, 9, 5);
|
||||
myAud0State.reset();
|
||||
myAud1State.reset();
|
||||
while(!mySamples.empty()) mySamples.pop();
|
||||
|
||||
// Initialize instance variables
|
||||
for(int chan = 0; chan <= 1; ++chan)
|
||||
for(int i = 0; i < 2; ++i)
|
||||
{
|
||||
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;
|
||||
myAudC0[i][0] = myAudC0[i][1] = 0;
|
||||
myAudC1[i][0] = myAudC1[i][1] = 0;
|
||||
myAudF0[i] = myAudF1[i] = 0;
|
||||
myAudV0[i] = myAudV1[i] = 0;
|
||||
}
|
||||
|
||||
myOutputCounter = 0;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::outputFrequency(Int32 freq)
|
||||
{
|
||||
myOutputFrequency = freq;
|
||||
myDeferredC0 = myDeferredC1 = myDeferredF0 = myDeferredF1 =
|
||||
myDeferredV0 = myDeferredV1 = 0xff;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
@ -73,318 +86,397 @@ string TIASound::channels(uInt32 hardware, bool stereo)
|
|||
|
||||
switch(myChannelMode)
|
||||
{
|
||||
case Hardware1: return "Hardware1";
|
||||
case Hardware2Mono: return "Hardware2Mono";
|
||||
case Hardware2Stereo: return "Hardware2Stereo";
|
||||
case Hardware1: return "Hardware1";
|
||||
default: return EmptyString;
|
||||
}
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::set(uInt16 address, uInt8 value)
|
||||
void TIASound::writeAudC0(uInt8 value, uInt32 clock)
|
||||
{
|
||||
int chan = ~address & 0x1;
|
||||
switch(address)
|
||||
{
|
||||
case TIARegister::AUDC0:
|
||||
case TIARegister::AUDC1:
|
||||
myAUDC[chan] = value & 0x0f;
|
||||
break;
|
||||
|
||||
case TIARegister::AUDF0:
|
||||
case TIARegister::AUDF1:
|
||||
myAUDF[chan] = value & 0x1f;
|
||||
break;
|
||||
|
||||
case TIARegister::AUDV0:
|
||||
case TIARegister::AUDV1:
|
||||
myAUDV[chan] = (value & 0x0f) << AUDV_SHIFT;
|
||||
break;
|
||||
|
||||
default:
|
||||
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;
|
||||
}
|
||||
value &= 0x0F;
|
||||
if(clock <= Cycle1Phase1) myAudC0[0][0] = value;
|
||||
if(clock <= Cycle1Phase2) myAudC0[0][1] = value;
|
||||
if(clock <= Cycle2Phase1) myAudC0[1][0] = value;
|
||||
if(clock <= Cycle2Phase2) myAudC0[1][1] = value;
|
||||
else // We missed both cycles, so defer write until next line
|
||||
myDeferredC0 = value;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
uInt8 TIASound::get(uInt16 address) const
|
||||
void TIASound::writeAudC1(uInt8 value, uInt32 clock)
|
||||
{
|
||||
switch(address)
|
||||
value &= 0x0F;
|
||||
if(clock <= Cycle1Phase1) myAudC1[0][0] = value;
|
||||
if(clock <= Cycle1Phase2) myAudC1[0][1] = value;
|
||||
if(clock <= Cycle2Phase1) myAudC1[1][0] = value;
|
||||
if(clock <= Cycle2Phase2) myAudC1[1][1] = value;
|
||||
else // We missed both cycles, so defer write until next line
|
||||
myDeferredC1 = value;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::writeAudF0(uInt8 value, uInt32 clock)
|
||||
{
|
||||
value &= 0x1F;
|
||||
if(clock <= Cycle1Phase1) myAudF0[0] = value;
|
||||
if(clock <= Cycle2Phase1) myAudF0[1] = value;
|
||||
else // We missed both cycles, so defer write until next line
|
||||
myDeferredF0 = value;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::writeAudF1(uInt8 value, uInt32 clock)
|
||||
{
|
||||
value &= 0x1F;
|
||||
if(clock <= Cycle1Phase1) myAudF1[0] = value;
|
||||
if(clock <= Cycle2Phase1) myAudF1[1] = value;
|
||||
else // We missed both cycles, so defer write until next line
|
||||
myDeferredF1 = value;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::writeAudV0(uInt8 value, uInt32 clock)
|
||||
{
|
||||
value &= 0x0F;
|
||||
if(clock <= Cycle1Phase2) myAudV0[0] = value;
|
||||
if(clock <= Cycle2Phase2) myAudV0[1] = value;
|
||||
else // We missed both cycles, so defer write until next line
|
||||
myDeferredV0 = value;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::writeAudV1(uInt8 value, uInt32 clock)
|
||||
{
|
||||
value &= 0x0F;
|
||||
if(clock <= Cycle1Phase2) myAudV1[0] = value;
|
||||
if(clock <= Cycle2Phase2) myAudV1[1] = value;
|
||||
else // We missed both cycles, so defer write until next line
|
||||
myDeferredV1 = value;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::queueSamples()
|
||||
{
|
||||
// Cycle 1
|
||||
bool aud0_c1 = updateAudioState(myAud0State, myAudF0[0], myAudC0[0]);
|
||||
bool aud1_c1 = updateAudioState(myAud1State, myAudF1[0], myAudC1[0]);
|
||||
|
||||
// Cycle 2
|
||||
bool aud0_c2 = updateAudioState(myAud0State, myAudF0[1], myAudC0[1]);
|
||||
bool aud1_c2 = updateAudioState(myAud1State, myAudF1[1], myAudC1[1]);
|
||||
|
||||
switch(myChannelMode)
|
||||
{
|
||||
case TIARegister::AUDC0: return myAUDC[0];
|
||||
case TIARegister::AUDC1: return myAUDC[1];
|
||||
case TIARegister::AUDF0: return myAUDF[0];
|
||||
case TIARegister::AUDF1: return myAUDF[1];
|
||||
case TIARegister::AUDV0: return myAUDV[0] >> AUDV_SHIFT;
|
||||
case TIARegister::AUDV1: return myAUDV[1] >> AUDV_SHIFT;
|
||||
default: return 0;
|
||||
case Hardware2Mono: // mono sampling with 2 hardware channels
|
||||
{
|
||||
uInt32 idx1 = 0;
|
||||
if(aud0_c1) idx1 |= myAudV0[0];
|
||||
if(aud1_c1) idx1 |= (myAudV1[0] << 4);
|
||||
uInt16 vol1 = myVolLUT[idx1][myHWVol];
|
||||
mySamples.push(vol1);
|
||||
mySamples.push(vol1);
|
||||
|
||||
uInt32 idx2 = 0;
|
||||
if(aud0_c2) idx2 |= myAudV0[1];
|
||||
if(aud1_c2) idx2 |= (myAudV1[1] << 4);
|
||||
uInt16 vol2 = myVolLUT[idx2][myHWVol];
|
||||
mySamples.push(vol2);
|
||||
mySamples.push(vol2);
|
||||
break;
|
||||
}
|
||||
case Hardware2Stereo: // stereo sampling with 2 hardware channels
|
||||
{
|
||||
mySamples.push(myVolLUT[aud0_c1 ? myAudV0[0] : 0][myHWVol]);
|
||||
mySamples.push(myVolLUT[aud1_c1 ? myAudV1[0] : 0][myHWVol]);
|
||||
|
||||
mySamples.push(myVolLUT[aud0_c2 ? myAudV0[1] : 0][myHWVol]);
|
||||
mySamples.push(myVolLUT[aud1_c2 ? myAudV1[1] : 0][myHWVol]);
|
||||
|
||||
break;
|
||||
}
|
||||
case Hardware1: // mono/stereo sampling with only 1 hardware channel
|
||||
{
|
||||
uInt32 idx1 = 0;
|
||||
if(aud0_c1) idx1 |= myAudV0[0];
|
||||
if(aud1_c1) idx1 |= (myAudV1[0] << 4);
|
||||
mySamples.push(myVolLUT[idx1][myHWVol]);
|
||||
|
||||
uInt32 idx2 = 0;
|
||||
if(aud0_c2) idx2 |= myAudV0[1];
|
||||
if(aud1_c2) idx2 |= (myAudV1[1] << 4);
|
||||
mySamples.push(myVolLUT[idx2][myHWVol]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// End of line, allow deferred updates
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
// AUDC0
|
||||
if(myDeferredC0 != 0xff) // write occurred after cycle2:phase2
|
||||
{
|
||||
myAudC0[0][0] = myAudC0[0][1] = myAudC0[1][0] = myAudC0[1][1] = myDeferredC0;
|
||||
myDeferredC0 = 0xff;
|
||||
}
|
||||
else // write occurred after cycle1:phase1
|
||||
myAudC0[0][0] = myAudC0[1][1];
|
||||
|
||||
// AUDC1
|
||||
if(myDeferredC1 != 0xff) // write occurred after cycle2:phase2
|
||||
{
|
||||
myAudC1[0][0] = myAudC1[0][1] = myAudC1[1][0] = myAudC1[1][1] = myDeferredC1;
|
||||
myDeferredC1 = 0xff;
|
||||
}
|
||||
else // write occurred after cycle1:phase1
|
||||
myAudC1[0][0] = myAudC1[1][1];
|
||||
|
||||
// AUDF0
|
||||
if(myDeferredF0 != 0xff) // write occurred after cycle2:phase2
|
||||
{
|
||||
myAudF0[0] = myAudF0[1] = myDeferredF0;
|
||||
myDeferredF0 = 0xff;
|
||||
}
|
||||
else // write occurred after cycle1:phase1
|
||||
myAudF0[0] = myAudF0[1];
|
||||
|
||||
// AUDF1
|
||||
if(myDeferredF1 != 0xff) // write occurred after cycle2:phase2
|
||||
{
|
||||
myAudF1[0] = myAudF1[1] = myDeferredF1;
|
||||
myDeferredF1 = 0xff;
|
||||
}
|
||||
else // write occurred after cycle1:phase1
|
||||
myAudF1[0] = myAudF1[1];
|
||||
|
||||
// AUDV0
|
||||
if(myDeferredV0 != 0xff) // write occurred after cycle2:phase2
|
||||
{
|
||||
myAudV0[0] = myAudV0[1] = myDeferredV0;
|
||||
myDeferredV0 = 0xff;
|
||||
}
|
||||
else // write occurred after cycle1:phase1
|
||||
myAudV0[0] = myAudV0[1];
|
||||
|
||||
// AUDV1
|
||||
if(myDeferredV1 != 0xff) // write occurred after cycle2:phase2
|
||||
{
|
||||
myAudV1[0] = myAudV1[1] = myDeferredV1;
|
||||
myDeferredV1 = 0xff;
|
||||
}
|
||||
else // write occurred after cycle1:phase1
|
||||
myAudV1[0] = myAudV1[1];
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::volume(uInt32 percent)
|
||||
{
|
||||
if(percent <= 100)
|
||||
myVolumePercentage = percent;
|
||||
myHWVol = std::min(percent, 100u);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::process(Int16* buffer, uInt32 samples)
|
||||
bool TIASound::updateAudioState(AudioState& state, uInt32 audf, uInt32* audc)
|
||||
{
|
||||
// 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];
|
||||
bool pulse_fb; // pulse counter LFSR feedback
|
||||
|
||||
// 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)
|
||||
// -- Logic updated on phase 1 of the audio clock --
|
||||
if(state.clk_en)
|
||||
{
|
||||
// Process channel 0
|
||||
if (div_n_cnt0 > 1)
|
||||
// Latch bit 4 of noise counter
|
||||
state.noise_cnt_4 = state.noise_cnt & 0x01;
|
||||
|
||||
// Latch pulse counter hold condition
|
||||
switch(audc[0] & 0x03)
|
||||
{
|
||||
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))
|
||||
{
|
||||
if (audc0 & 0x04) // Pure modified clock selected
|
||||
{
|
||||
if ((audc0 & 0x0f) == POLY5_DIV3) // POLY5 -> DIV3 mode
|
||||
{
|
||||
if ( Bit5[p5_0] != prev_bit5 )
|
||||
{
|
||||
myDiv3Cnt[0]--;
|
||||
if ( !myDiv3Cnt[0] )
|
||||
{
|
||||
myDiv3Cnt[0] = 3;
|
||||
v0 = v0 ? 0 : audv0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
|
||||
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 )
|
||||
{
|
||||
myDiv3Cnt[1]--;
|
||||
if ( ! myDiv3Cnt[1] )
|
||||
{
|
||||
myDiv3Cnt[1] = 3;
|
||||
v1 = v1 ? 0 : audv1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the output was set turn it off, else turn it on
|
||||
v1 = v1 ? 0 : audv1;
|
||||
}
|
||||
}
|
||||
else if (audc1 & 0x08) // Check for p5/p9
|
||||
{
|
||||
if (audc1 == POLY9) // Check for poly9
|
||||
{
|
||||
// Increase the poly9 counter
|
||||
myP9[1]++;
|
||||
if (myP9[1] == POLY9_SIZE)
|
||||
myP9[1] = 0;
|
||||
|
||||
v1 = Bit9[myP9[1]] ? audv1 : 0;
|
||||
}
|
||||
else if ( audc1 & 0x02 )
|
||||
{
|
||||
v1 = (v1 || audc1 & 0x01) ? 0 : audv1;
|
||||
}
|
||||
else // Must be poly5
|
||||
{
|
||||
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;
|
||||
|
||||
v1 = Bit4[myP4[1]] ? audv1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myOutputCounter += myOutputFrequency;
|
||||
|
||||
switch(myChannelMode)
|
||||
{
|
||||
case Hardware2Mono: // mono sampling with 2 hardware channels
|
||||
while((samples > 0) && (myOutputCounter >= 31400))
|
||||
{
|
||||
Int16 byte = v0 + v1;
|
||||
*(buffer++) = byte;
|
||||
*(buffer++) = byte;
|
||||
myOutputCounter -= 31400;
|
||||
samples--;
|
||||
}
|
||||
case 0x00:
|
||||
state.pulse_cnt_hold = false;
|
||||
break;
|
||||
|
||||
case Hardware2Stereo: // stereo sampling with 2 hardware channels
|
||||
while((samples > 0) && (myOutputCounter >= 31400))
|
||||
{
|
||||
*(buffer++) = v0;
|
||||
*(buffer++) = v1;
|
||||
myOutputCounter -= 31400;
|
||||
samples--;
|
||||
}
|
||||
case 0x01:
|
||||
state.pulse_cnt_hold = false;
|
||||
break;
|
||||
case 0x02:
|
||||
state.pulse_cnt_hold = ((state.noise_cnt & 0x1e) != 0x02);
|
||||
break;
|
||||
case 0x03:
|
||||
state.pulse_cnt_hold = !state.noise_cnt_4;
|
||||
break;
|
||||
}
|
||||
|
||||
case Hardware1: // mono/stereo sampling with only 1 hardware channel
|
||||
while((samples > 0) && (myOutputCounter >= 31400))
|
||||
{
|
||||
*(buffer++) = v0 + v1;
|
||||
myOutputCounter -= 31400;
|
||||
samples--;
|
||||
}
|
||||
// Latch noise counter LFSR feedback
|
||||
switch(audc[0] & 0x03)
|
||||
{
|
||||
case 0x00:
|
||||
state.noise_fb = ((state.pulse_cnt & 0x01) ^ (state.noise_cnt & 0x01)) |
|
||||
!((state.noise_cnt ? 1 : 0) | (state.pulse_cnt != 0x0a)) |
|
||||
!(audc[0] & 0x0c);
|
||||
break;
|
||||
default:
|
||||
state.noise_fb = (((state.noise_cnt & 0x04) ? 1 : 0) ^ (state.noise_cnt & 0x01)) |
|
||||
!state.noise_cnt;
|
||||
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;
|
||||
}
|
||||
// Set (or clear) audio clock enable
|
||||
state.clk_en = (state.div_cnt == audf);
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
void TIASound::polyInit(uInt8* poly, int size, int f0, int f1)
|
||||
{
|
||||
int mask = (1 << size) - 1, x = mask;
|
||||
// Increment clock divider counter
|
||||
if((state.div_cnt == audf) || (state.div_cnt == 31))
|
||||
state.div_cnt = 0;
|
||||
else
|
||||
state.div_cnt++;
|
||||
|
||||
for(int i = 0; i < mask; i++)
|
||||
// -- Logic updated on phase 2 of the audio clock --
|
||||
if(state.clk_en)
|
||||
{
|
||||
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) );
|
||||
// Evaluate pulse counter combinatorial logic
|
||||
switch(audc[1] >> 2)
|
||||
{
|
||||
case 0x00:
|
||||
pulse_fb = (((state.pulse_cnt & 0x02) ? 1 : 0) ^ (state.pulse_cnt & 0x01)) &
|
||||
(state.pulse_cnt != 0x0a) && (audc[1] & 0x03);
|
||||
break;
|
||||
case 0x01:
|
||||
pulse_fb = !(state.pulse_cnt & 0x08);
|
||||
break;
|
||||
case 0x02:
|
||||
pulse_fb = !state.noise_cnt_4;
|
||||
break;
|
||||
case 0x03:
|
||||
pulse_fb = !((state.pulse_cnt & 0x02) || !(state.pulse_cnt & 0x0e));
|
||||
break;
|
||||
}
|
||||
|
||||
// Increment noise counter
|
||||
state.noise_cnt >>= 1;
|
||||
if(state.noise_fb)
|
||||
state.noise_cnt |= 0x10;
|
||||
|
||||
// Increment pulse counter
|
||||
if(!state.pulse_cnt_hold)
|
||||
{
|
||||
state.pulse_cnt = (~(state.pulse_cnt >> 1) & 0x07);
|
||||
if(pulse_fb)
|
||||
state.pulse_cnt |= 0x08;
|
||||
}
|
||||
}
|
||||
|
||||
// Pulse generator output
|
||||
return (state.pulse_cnt & 0x01);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
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
|
||||
};
|
||||
bool TIASound::save(Serializer& out) const
|
||||
{
|
||||
#if 0
|
||||
try
|
||||
{
|
||||
out.putString(name());
|
||||
|
||||
// Only get the TIA sound registers if sound is enabled
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
out.putByte(myTIASound.get(TIARegister::AUDC0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDC1));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDF0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDF1));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDV0));
|
||||
out.putByte(myTIASound.get(TIARegister::AUDV1));
|
||||
}
|
||||
else
|
||||
for(int i = 0; i < 6; ++i)
|
||||
out.putByte(0);
|
||||
|
||||
out.putInt(myLastRegisterSetCycle);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
myOSystem.logMessage("ERROR: SoundSDL2::save", 0);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool TIASound::load(Serializer& in)
|
||||
{
|
||||
#if 0
|
||||
try
|
||||
{
|
||||
if(in.getString() != name())
|
||||
return false;
|
||||
|
||||
// Only update the TIA sound registers if sound is enabled
|
||||
// Make sure to empty the queue of previous sound fragments
|
||||
if(myIsInitializedFlag)
|
||||
{
|
||||
SDL_PauseAudio(1);
|
||||
myRegWriteQueue.clear();
|
||||
myTIASound.set(TIARegister::AUDC0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDC1, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDF0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDF1, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDV0, in.getByte());
|
||||
myTIASound.set(TIARegister::AUDV1, in.getByte());
|
||||
if(!myIsMuted) SDL_PauseAudio(0);
|
||||
}
|
||||
else
|
||||
for(int i = 0; i < 6; ++i)
|
||||
in.getByte();
|
||||
|
||||
myLastRegisterSetCycle = in.getInt();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
myOSystem.logMessage("ERROR: SoundSDL2::load", 0);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool TIASound::AudioState::save(Serializer& out) const
|
||||
{
|
||||
try
|
||||
{
|
||||
out.putBool(clk_en);
|
||||
out.putBool(noise_fb);
|
||||
out.putBool(noise_cnt_4);
|
||||
out.putBool(pulse_cnt_hold);
|
||||
out.putInt(div_cnt);
|
||||
out.putInt(noise_cnt);
|
||||
out.putInt(pulse_cnt);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
// FIXME myOSystem.logMessage("ERROR: TIASnd_state::save", 0);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
bool TIASound::AudioState::load(Serializer& in)
|
||||
{
|
||||
try
|
||||
{
|
||||
clk_en = in.getBool();
|
||||
noise_fb = in.getBool();
|
||||
noise_cnt_4 = in.getBool();
|
||||
pulse_cnt_hold = in.getBool();
|
||||
div_cnt = in.getInt();
|
||||
noise_cnt = in.getInt();
|
||||
pulse_cnt = in.getInt();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
// FIXME myOSystem.logMessage("ERROR: TIASnd_state::load", 0);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -17,40 +17,82 @@
|
|||
// $Id$
|
||||
//============================================================================
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Title: TIA Audio Generator
|
||||
//
|
||||
// Project - MMDC
|
||||
// Version - _VERSION
|
||||
// Author - Chris Brenner
|
||||
// Description - This code is translated more or less verbatim from my Verilog
|
||||
// code for my FPGA project. It provides core logic that can be used to achieve
|
||||
// cycle accurate emulation of the Atari 2600 TIA audio blocks.
|
||||
//
|
||||
// The core logic is broken up into two functions. The updateAudioState()
|
||||
// function contains the logic for the clock divider and pulse generator. It is
|
||||
// used to update the state of the audio logic, and should be called once for
|
||||
// every audio clock cycle for each channel.
|
||||
//
|
||||
// The queueSamples() function implements the volume control, and generates
|
||||
// audio samples. It depends on the state of the pulse generator of both audio
|
||||
// channels, so it internally calls the updateAudioState() function.
|
||||
//
|
||||
// The constructor is called on startup in order to initialize the audio logic
|
||||
// and volume control LUT.
|
||||
//
|
||||
// The accuracy of the emulation is dependent upon the accuracy of the data
|
||||
// contained in the AUDCx, AUDFx, and AUDVx registers. It is important that these
|
||||
// registers contain the most recent value written prior to the current clock
|
||||
// cycle and phase.
|
||||
//
|
||||
// The TIA audio clock is a 31.4 KHz two phase clock that occurs twice every
|
||||
// horizontal scan line. The mapping of the cycles and phases are as follows.
|
||||
// Cycle 1 - Phase 1: color clock 8
|
||||
// Cycle 1 - Phase 2: color clock 36
|
||||
// Cycle 2 - Phase 1: color clock 80
|
||||
// Cycle 2 - Phase 2: color clock 148
|
||||
//
|
||||
// Since software can change the value of the registers in between clock cycles
|
||||
// and phases, it's necessary to develop a mechanism for keeping these registers
|
||||
// up to date when doing bulk processing. One method would be to time stamp the
|
||||
// register writes, and queue them into a FIFO, but I leave this design decision
|
||||
// to the emulator developer. The phase requirements are listed here.
|
||||
// AUDC0, AUDC1: used by the pulse generator at both phases
|
||||
// AUDF0, AUDF1: used by the clock divider at phase 1
|
||||
// AUDV0, AUDV1: used by the volume control at phase 2
|
||||
//
|
||||
// In a real 2600, the volume control is analog, and is affected the instant when
|
||||
// AUDVx is written. However, since we generate audio samples at phase 2 of the
|
||||
// clock, the granularity of volume control updates is limited to our sample rate,
|
||||
// and changes that occur in between clocks result in only the last change prior
|
||||
// to phase 2 having an affect on the volume.
|
||||
//
|
||||
// @author Chris Brenner (original C implementation) and
|
||||
// Stephen Anthony (C++ conversion, integration into Stella)
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef TIASOUND_HXX
|
||||
#define TIASOUND_HXX
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "bspf.hxx"
|
||||
#include "Serializable.hxx"
|
||||
|
||||
/**
|
||||
This class implements a fairly accurate emulation of the TIA sound
|
||||
hardware. This class uses code/ideas from z26 and MESS.
|
||||
|
||||
Currently, the sound generation routines work at 31400Hz only.
|
||||
Resampling can be done by passing in a different output frequency.
|
||||
|
||||
@author Bradford W. Mott, Stephen Anthony, z26 and MESS teams
|
||||
@version $Id$
|
||||
*/
|
||||
class TIASound
|
||||
class TIASound : public Serializable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
Create a new TIA Sound object using the specified output frequency
|
||||
Create a new TIA Sound object.
|
||||
*/
|
||||
TIASound(Int32 outputFrequency = 31400);
|
||||
TIASound();
|
||||
|
||||
public:
|
||||
/**
|
||||
Reset the sound emulation to its power-on state
|
||||
Reset the sound emulation to its power-on state.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
Set the frequency output samples should be generated at
|
||||
*/
|
||||
void outputFrequency(Int32 freq);
|
||||
|
||||
/**
|
||||
Selects the number of audio channels per sample. There are two factors
|
||||
to consider: hardware capability and desired mixing.
|
||||
|
@ -63,117 +105,133 @@ class TIASound
|
|||
*/
|
||||
string channels(uInt32 hardware, bool stereo);
|
||||
|
||||
public:
|
||||
/**
|
||||
Sets the specified sound register to the given value
|
||||
Sets the specified sound register to the given value.
|
||||
|
||||
@param address Register address
|
||||
@param value Value to store in the register
|
||||
@param value Value to store in the register
|
||||
@param clock Colour clock at which the write occurred
|
||||
*/
|
||||
void set(uInt16 address, uInt8 value);
|
||||
|
||||
/**
|
||||
Gets the specified sound register's value
|
||||
|
||||
@param address Register address
|
||||
*/
|
||||
uInt8 get(uInt16 address) const;
|
||||
void writeAudC0(uInt8 value, uInt32 clock);
|
||||
void writeAudC1(uInt8 value, uInt32 clock);
|
||||
void writeAudF0(uInt8 value, uInt32 clock);
|
||||
void writeAudF1(uInt8 value, uInt32 clock);
|
||||
void writeAudV0(uInt8 value, uInt32 clock);
|
||||
void writeAudV1(uInt8 value, uInt32 clock);
|
||||
|
||||
/**
|
||||
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.
|
||||
The samples are stored in an internal queue, to be removed as
|
||||
necessary by getSamples() (invoked by the sound hardware callback).
|
||||
|
||||
@param buffer The location to store generated samples
|
||||
@param samples The number of samples to generate
|
||||
This method must be called once per scanline from the TIA class.
|
||||
*/
|
||||
void process(Int16* buffer, uInt32 samples);
|
||||
void queueSamples();
|
||||
|
||||
/**
|
||||
Move specified number of samples from the internal queue into the
|
||||
given buffer.
|
||||
|
||||
@param buffer The location to move generated samples
|
||||
@param samples The number of samples to move
|
||||
|
||||
@return The number of samples left to fill the buffer
|
||||
Should normally be 0, since we want to fill it completely
|
||||
*/
|
||||
Int32 getSamples(uInt16* buffer, uInt32 samples)
|
||||
{
|
||||
while(mySamples.size() > 0 && samples--)
|
||||
{
|
||||
*buffer++ = mySamples.front();
|
||||
mySamples.pop();
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
Set the volume of the samples created (0-100)
|
||||
*/
|
||||
void volume(uInt32 percent);
|
||||
|
||||
private:
|
||||
void polyInit(uInt8* poly, int size, int f0, int f1);
|
||||
/**
|
||||
Saves the current state of this device to the given Serializer.
|
||||
|
||||
@param out The serializer device to save to.
|
||||
@return The result of the save. True on success, false on failure.
|
||||
*/
|
||||
bool save(Serializer& out) const override;
|
||||
|
||||
/**
|
||||
Loads the current state of this device from the given Serializer.
|
||||
|
||||
@param in The Serializer device to load from.
|
||||
@return The result of the load. True on success, false on failure.
|
||||
*/
|
||||
bool load(Serializer& in) override;
|
||||
|
||||
/**
|
||||
Get a descriptor for this console class (used in error checking).
|
||||
|
||||
@return The name of the object
|
||||
*/
|
||||
string name() const override { return "TIASound"; }
|
||||
|
||||
private:
|
||||
// Definitions for AUDCx (15, 16)
|
||||
enum AUDCxRegister
|
||||
struct AudioState : public Serializable
|
||||
{
|
||||
SET_TO_1 = 0x00, // 0000
|
||||
POLY4 = 0x01, // 0001
|
||||
DIV31_POLY4 = 0x02, // 0010
|
||||
POLY5_POLY4 = 0x03, // 0011
|
||||
PURE1 = 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
|
||||
};
|
||||
AudioState() { reset(); }
|
||||
void reset()
|
||||
{
|
||||
clk_en = noise_fb = noise_cnt_4 = pulse_cnt_hold = false;
|
||||
div_cnt = 0; noise_cnt = pulse_cnt = 0;
|
||||
}
|
||||
bool save(Serializer& out) const override;
|
||||
bool load(Serializer& in) override;
|
||||
string name() const override { return "TIASound_AudioState"; }
|
||||
|
||||
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
|
||||
bool clk_en, noise_fb, noise_cnt_4, pulse_cnt_hold;
|
||||
uInt32 div_cnt, noise_cnt, pulse_cnt;
|
||||
};
|
||||
bool updateAudioState(AudioState& state, uInt32 audf, uInt32* audc);
|
||||
|
||||
enum ChannelMode {
|
||||
Hardware2Mono, // mono sampling with 2 hardware channels
|
||||
Hardware2Stereo, // stereo sampling with 2 hardware channels
|
||||
Hardware1 // mono/stereo sampling with only 1 hardware channel
|
||||
};
|
||||
|
||||
private:
|
||||
// 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)
|
||||
|
||||
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 myOutputCounter;
|
||||
uInt32 myVolumePercentage;
|
||||
|
||||
/*
|
||||
Initialize the bit patterns for the polynomials (at runtime).
|
||||
AudioState myAud0State; // storage for AUD0 state
|
||||
AudioState myAud1State; // storage for AUD1 state
|
||||
|
||||
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];
|
||||
uInt16 myVolLUT[256][101]; // storage for volume look-up table
|
||||
uInt32 myHWVol; // actual output volume to use
|
||||
|
||||
/*
|
||||
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];
|
||||
uInt32 myAudF0[2]; // audfx[0]: value at clock1-phase1
|
||||
uInt32 myAudF1[2]; // audfx[1]: value at clock2-phase1
|
||||
uInt32 myAudV0[2]; // audvx[0]: value at clock1-phase2
|
||||
uInt32 myAudV1[2]; // audvx[1]: value at clock2-phase2
|
||||
uInt32 myAudC0[2][2]; // audcx[0][0]: value at clock1-phase1,
|
||||
// audcx[0][1]: value at clock1:phase2
|
||||
uInt32 myAudC1[2][2]; // audcx[1][0]: value at clock2-phase1,
|
||||
// audcx[1][1]: value at clock2:phase2
|
||||
|
||||
// A value not equal to 0xff indicates that *both* cycles were missed
|
||||
// on the previous line, and must be updated on the next line
|
||||
uInt32 myDeferredC0, myDeferredC1;
|
||||
uInt32 myDeferredF0, myDeferredF1;
|
||||
uInt32 myDeferredV0, myDeferredV1;
|
||||
|
||||
// Contains the samples previously created by queueSamples()
|
||||
// This will be periodically emptied by getSamples()
|
||||
queue<uInt16> mySamples;
|
||||
|
||||
// The colour clock at which each cycle/phase ends
|
||||
// Any writes to sound registers that occur after a respective
|
||||
// cycle/phase are deferred until the next update interval
|
||||
static constexpr uInt32
|
||||
Cycle1Phase1 = 8, Cycle1Phase2 = 36, Cycle2Phase1 = 80, Cycle2Phase2 = 148;
|
||||
|
||||
private:
|
||||
// Following constructors and assignment operators not supported
|
||||
|
|
Loading…
Reference in New Issue