Gut current audio code.

This commit is contained in:
Christian Speckner 2017-12-01 23:17:16 +01:00
parent da473f4803
commit e88751638b
11 changed files with 44 additions and 1186 deletions

16
.vscode/settings.json vendored
View File

@ -25,6 +25,20 @@
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"vector": "cpp"
"vector": "cpp",
"sstream": "cpp",
"__bit_reference": "cpp",
"__functional_base": "cpp",
"algorithm": "cpp",
"bitset": "cpp",
"chrono": "cpp",
"functional": "cpp",
"iterator": "cpp",
"limits": "cpp",
"locale": "cpp",
"memory": "cpp",
"ratio": "cpp",
"tuple": "cpp",
"type_traits": "cpp"
}
}

View File

@ -53,21 +53,6 @@ class SoundNull : public Sound
*/
void setEnabled(bool state) 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 { }
/**
Initializes the sound device. This must be called before any
calls are made to derived methods.
@ -92,15 +77,6 @@ class SoundNull : 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, uInt64 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
@ -118,54 +94,6 @@ class SoundNull : 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
{
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) override
{
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 override { return "TIASound"; }
private:
// Following constructors and assignment operators not supported
SoundNull() = delete;

View File

@ -22,8 +22,6 @@
#include <cmath>
#include "SDL_lib.hxx"
#include "TIASnd.hxx"
#include "TIATypes.hxx"
#include "FrameBuffer.hxx"
#include "Settings.hxx"
#include "System.hxx"
@ -34,14 +32,7 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::SoundSDL2(OSystem& osystem)
: Sound(osystem),
myIsEnabled(false),
myIsInitializedFlag(false),
myLastRegisterSetCycle(0),
myNumChannels(0),
myFragmentSizeLogBase2(0),
myFragmentSizeLogDiv1(0),
myFragmentSizeLogDiv2(0),
myIsMuted(true),
myVolume(100)
{
myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2);
@ -80,13 +71,9 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
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);
mute(true);
myOSystem.logMessage("SoundSDL2::SoundSDL2 initialized", 2);
}
@ -94,12 +81,9 @@ SoundSDL2::SoundSDL2(OSystem& osystem)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::~SoundSDL2()
{
// Close the SDL audio system if it's initialized
if(myIsInitializedFlag)
{
if (!myIsInitializedFlag) return;
SDL_CloseAudio();
myIsEnabled = myIsInitializedFlag = false;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -115,22 +99,16 @@ void SoundSDL2::setEnabled(bool state)
void SoundSDL2::open()
{
myOSystem.logMessage("SoundSDL2::open started ...", 2);
myIsEnabled = false;
mute(true);
if(!myIsInitializedFlag || !myOSystem.settings().getBool("sound"))
if(!myOSystem.settings().getBool("sound"))
{
myOSystem.logMessage("Sound disabled\n", 1);
return;
}
// Now initialize the TIASound object which will actually generate sound
myTIASound.outputFrequency(myHardwareSpec.freq);
const string& chanResult =
myTIASound.channels(myHardwareSpec.channels, myNumChannels == 2);
// Adjust volume to that defined in settings
myVolume = myOSystem.settings().getInt("volume");
setVolume(myVolume);
setVolume(myOSystem.settings().getInt("volume"));
// Show some info
ostringstream buf;
@ -139,12 +117,10 @@ void SoundSDL2::open()
<< " Frag size: " << uInt32(myHardwareSpec.samples) << endl
<< " Frequency: " << uInt32(myHardwareSpec.freq) << endl
<< " Channels: " << uInt32(myHardwareSpec.channels)
<< " (" << chanResult << ")" << endl
<< endl;
myOSystem.logMessage(buf.str(), 1);
// And start the SDL sound subsystem ...
myIsEnabled = true;
mute(false);
myOSystem.logMessage("SoundSDL2::open finished", 2);
@ -153,15 +129,11 @@ void SoundSDL2::open()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::close()
{
if(myIsInitializedFlag)
{
myIsEnabled = false;
SDL_PauseAudio(1);
myLastRegisterSetCycle = 0;
myTIASound.reset();
myRegWriteQueue.clear();
if(!myIsInitializedFlag) return;
mute(true);
myOSystem.logMessage("SoundSDL2::close", 2);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -169,23 +141,13 @@ void SoundSDL2::mute(bool state)
{
if(myIsInitializedFlag)
{
myIsMuted = state;
SDL_PauseAudio(myIsMuted ? 1 : 0);
SDL_PauseAudio(state ? 1 : 0);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::reset()
{
if(myIsInitializedFlag)
{
SDL_PauseAudio(1);
myLastRegisterSetCycle = 0;
myTIASound.reset();
myRegWriteQueue.clear();
mute(myIsMuted);
}
}
{}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::setVolume(Int32 percent)
@ -195,7 +157,6 @@ void SoundSDL2::setVolume(Int32 percent)
myOSystem.settings().setValue("volume", percent);
SDL_LockAudio();
myVolume = percent;
myTIASound.volume(percent);
SDL_UnlockAudio();
}
}
@ -226,286 +187,14 @@ void SoundSDL2::adjustVolume(Int8 direction)
myOSystem.frameBuffer().showMessage(message);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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, uInt64 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.
myRegWriteQueue.enqueue(addr, value, delta);
// Update last cycle counter to the current cycle
myLastRegisterSetCycle = cycle;
SDL_UnlockAudio();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::processFragment(Int16* stream, uInt32 length)
{
uInt32 channels = myHardwareSpec.channels;
length = length / channels;
// If there are excessive items on the queue then we'll remove some
if(myRegWriteQueue.duration() > myFragmentSizeLogDiv1)
{
double removed = 0.0;
while(removed < myFragmentSizeLogDiv2)
{
RegWrite& info = myRegWriteQueue.front();
removed += info.delta;
myTIASound.set(info.addr, info.value);
myRegWriteQueue.dequeue();
}
}
double position = 0.0;
double remaining = length;
while(remaining > 0.0)
{
if(myRegWriteQueue.size() == 0)
{
// There are no more pending TIA sound register updates so we'll
// use the current settings to finish filling the sound fragment
myTIASound.process(stream + (uInt32(position) * channels),
length - uInt32(position));
// Since we had to fill the fragment we'll reset the cycle counter
// to zero. NOTE: This isn't 100% correct, however, it'll do for
// now. We should really remember the overrun and remove it from
// the delta of the next write.
myLastRegisterSetCycle = 0;
break;
}
else
{
// There are pending TIA sound register updates so we need to
// update the sound buffer to the point of the next register update
RegWrite& info = myRegWriteQueue.front();
// How long will the remaining samples in the fragment take to play
double duration = remaining / myHardwareSpec.freq;
// Does the register update occur before the end of the fragment?
if(info.delta <= duration)
{
// If the register update time hasn't already passed then
// process samples upto the point where it should occur
if(info.delta > 0.0)
{
// Process the fragment upto the next TIA register write. We
// round the count passed to process up if needed.
double samples = (myHardwareSpec.freq * info.delta);
myTIASound.process(stream + (uInt32(position) * channels),
uInt32(samples) + uInt32(position + samples) -
(uInt32(position) + uInt32(samples)));
position += samples;
remaining -= samples;
}
myTIASound.set(info.addr, info.value);
myRegWriteQueue.dequeue();
}
else
{
// The next register update occurs in the next fragment so finish
// this fragment with the current TIA settings and reduce the register
// update delay by the corresponding amount of time
myTIASound.process(stream + (uInt32(position) * channels),
length - uInt32(position));
info.delta -= duration;
break;
}
}
}
}
{}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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.putLong(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.getLong();
}
catch(...)
{
myOSystem.logMessage("ERROR: SoundSDL2::load", 0);
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SoundSDL2::RegWriteQueue::RegWriteQueue(uInt32 capacity)
: myBuffer(make_unique<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(uInt16 addr, uInt8 value, double delta)
{
// 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();
RegWrite& reg = myBuffer[myTail];
reg.addr = addr;
reg.value = value;
reg.delta = delta;
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_unique<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 // SOUND_SUPPORT

View File

@ -25,7 +25,6 @@ class OSystem;
#include "SDL_lib.hxx"
#include "bspf.hxx"
#include "TIASnd.hxx"
#include "Sound.hxx"
/**
@ -51,28 +50,9 @@ class SoundSDL2 : public Sound
/**
Enables/disables the sound subsystem.
@param state True or false, to enable or disable the sound system
@param enable Either true or false, to enable or disable the sound system
*/
void setEnabled(bool state) 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;
void setEnabled(bool enable) override;
/**
Initializes the sound device. This must be called before any
@ -98,15 +78,6 @@ 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, uInt64 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
@ -124,30 +95,6 @@ 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.
@ -159,123 +106,16 @@ class SoundSDL2 : public Sound
*/
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;
RegWrite(uInt16 a = 0, uInt8 v = 0, double d = 0.0)
: addr(a), value(v), delta(d) { }
};
/**
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(uInt16 addr, uInt8 value, double delta);
/**
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
uInt64 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;
// Current volume as a percentage (0 - 100)
uInt32 myVolume;
// 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);

View File

@ -89,7 +89,7 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
// Create subsystems for the console
my6502 = make_unique<M6502>(myOSystem.settings());
myRiot = make_unique<M6532>(*this, myOSystem.settings());
myTIA = make_unique<TIA>(*this, myOSystem.sound(), myOSystem.settings());
myTIA = make_unique<TIA>(*this, myOSystem.settings());
myFrameManager = make_unique<FrameManager>();
mySwitches = make_unique<Switches>(myEvent, myProperties);
@ -562,11 +562,13 @@ void Console::initializeAudio()
// the commandline, but it can't be saved.
int framerate = myOSystem.settings().getInt("framerate");
if(framerate > 0) myFramerate = float(framerate);
const string& sound = myProperties.get(Cartridge_Sound);
myOSystem.sound().close();
myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1);
myOSystem.sound().setFrameRate(myFramerate);
// SND_TODO Communicate channels to TIA
// const string& sound = myProperties.get(Cartridge_Sound);
// myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1);
myOSystem.sound().open();
// Make sure auto-frame calculation is only enabled when necessary
@ -942,7 +944,7 @@ void Console::setFramerate(float framerate)
{
myFramerate = framerate;
myOSystem.setFramerate(framerate);
myOSystem.sound().setFrameRate(framerate);
// myOSystem.sound().setFrameRate(framerate);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -20,7 +20,6 @@
class OSystem;
#include "Serializable.hxx"
#include "bspf.hxx"
/**
@ -29,7 +28,7 @@ class OSystem;
@author Stephen Anthony
*/
class Sound : public Serializable
class Sound
{
public:
/**
@ -47,21 +46,6 @@ class Sound : public Serializable
*/
virtual void setEnabled(bool enable) = 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.
@ -86,15 +70,6 @@ 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, uInt64 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

View File

@ -1,388 +0,0 @@
//============================================================================
//
// 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-2017 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "System.hxx"
#include "TIATypes.hxx"
#include "TIASnd.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIASound::TIASound(Int32 outputFrequency)
: myChannelMode(Hardware2Stereo),
myOutputFrequency(outputFrequency),
myOutputCounter(0),
myVolumePercentage(100)
{
reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::reset()
{
// Fill the polynomials
polyInit(Bit4, 4, 4, 3);
polyInit(Bit5, 5, 5, 3);
polyInit(Bit9, 9, 9, 5);
// Initialize instance variables
for(int chan = 0; chan <= 1; ++chan)
{
myVolume[chan] = 0;
myDivNCnt[chan] = 0;
myDivNMax[chan] = 0;
myDiv3Cnt[chan] = 3;
myAUDC[chan] = 0;
myAUDF[chan] = 0;
myAUDV[chan] = 0;
myP4[chan] = 0;
myP5[chan] = 0;
myP9[chan] = 0;
}
myOutputCounter = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::outputFrequency(Int32 freq)
{
myOutputFrequency = freq;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string TIASound::channels(uInt32 hardware, bool stereo)
{
if(hardware == 1)
myChannelMode = Hardware1;
else
myChannelMode = stereo ? Hardware2Stereo : Hardware2Mono;
switch(myChannelMode)
{
case Hardware1: return "Hardware1";
case Hardware2Mono: return "Hardware2Mono";
case Hardware2Stereo: return "Hardware2Stereo";
}
return EmptyString;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::set(uInt16 address, uInt8 value)
{
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;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 TIASound::get(uInt16 address) const
{
switch(address)
{
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;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::volume(uInt32 percent)
{
if(percent <= 100)
myVolumePercentage = percent;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::process(Int16* buffer, uInt32 samples)
{
// Make temporary local copy
uInt8 audc0 = myAUDC[0], audc1 = myAUDC[1];
uInt8 p5_0 = myP5[0], p5_1 = myP5[1];
uInt8 div_n_cnt0 = myDivNCnt[0], div_n_cnt1 = myDivNCnt[1];
Int16 v0 = myVolume[0], v1 = myVolume[1];
// Take external volume into account
Int16 audv0 = (myAUDV[0] * myVolumePercentage) / 100,
audv1 = (myAUDV[1] * myVolumePercentage) / 100;
// Loop until the sample buffer is full
while(samples > 0)
{
// Process channel 0
if (div_n_cnt0 > 1)
{
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--;
}
break;
case Hardware2Stereo: // stereo sampling with 2 hardware channels
while((samples > 0) && (myOutputCounter >= 31400))
{
*(buffer++) = v0;
*(buffer++) = v1;
myOutputCounter -= 31400;
samples--;
}
break;
case Hardware1: // mono/stereo sampling with only 1 hardware channel
while((samples > 0) && (myOutputCounter >= 31400))
{
*(buffer++) = v0 + v1;
myOutputCounter -= 31400;
samples--;
}
break;
}
}
// Save for next round
myP5[0] = p5_0;
myP5[1] = p5_1;
myVolume[0] = v0;
myVolume[1] = v1;
myDivNCnt[0] = div_n_cnt0;
myDivNCnt[1] = div_n_cnt1;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASound::polyInit(uInt8* poly, int size, int f0, int f1)
{
int mask = (1 << size) - 1, x = mask;
for(int i = 0; i < mask; i++)
{
int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01;
int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01;
poly[i] = x & 1;
// calculate next bit
x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) );
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const uInt8 TIASound::Div31[POLY5_SIZE] = {
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

View File

@ -1,183 +0,0 @@
//============================================================================
//
// 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-2017 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef TIASOUND_HXX
#define TIASOUND_HXX
#include "bspf.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
*/
class TIASound
{
public:
/**
Create a new TIA Sound object using the specified output frequency
*/
TIASound(Int32 outputFrequency = 31400);
public:
/**
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.
@param hardware The number of channels supported by the sound system
@param stereo Whether to output the internal sound signals into 1
or 2 channels
@return Status of the channel configuration used
*/
string channels(uInt32 hardware, bool stereo);
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) const;
/**
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(Int16* buffer, uInt32 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);
private:
// Definitions for AUDCx (15, 16)
enum AUDCxRegister
{
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
};
enum {
POLY4_SIZE = 0x000f,
POLY5_SIZE = 0x001f,
POLY9_SIZE = 0x01ff,
DIV3_MASK = 0x0c,
AUDV_SHIFT = 10 // shift 2 positions for AUDV,
// then another 8 for 16-bit sound
};
enum ChannelMode {
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).
The 4bit and 5bit patterns are the identical ones used in the tia chip.
Though the patterns could be packed with 8 bits per byte, using only a
single bit per byte keeps the math simple, which is important for
efficient processing.
*/
uInt8 Bit4[POLY4_SIZE];
uInt8 Bit5[POLY5_SIZE];
uInt8 Bit9[POLY9_SIZE];
/*
The 'Div by 31' counter is treated as another polynomial because of
the way it operates. It does not have a 50% duty cycle, but instead
has a 13:18 ratio (of course, 13+18 = 31). This could also be
implemented by using counters.
*/
static const uInt8 Div31[POLY5_SIZE];
private:
// Following constructors and assignment operators not supported
TIASound(const TIASound&) = delete;
TIASound(TIASound&&) = delete;
TIASound& operator=(const TIASound&) = delete;
TIASound& operator=(TIASound&&) = delete;
};
#endif

View File

@ -76,7 +76,6 @@ MODULE_OBJS := \
src/emucore/Settings.o \
src/emucore/Switches.o \
src/emucore/System.o \
src/emucore/TIASnd.o \
src/emucore/TIASurface.o \
src/emucore/Thumbulator.o

View File

@ -65,9 +65,8 @@ enum ResxCounter: uInt8 {
static constexpr uInt8 resxLateHblankThreshold = 73;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIA::TIA(Console& console, Sound& sound, Settings& settings)
TIA::TIA(Console& console, Settings& settings)
: myConsole(console),
mySound(sound),
mySettings(settings),
myFrameManager(nullptr),
myPlayfield(~CollisionMask::playfield & 0x7FFF),
@ -163,7 +162,6 @@ void TIA::reset()
for (PaddleReader& paddleReader : myPaddleReaders)
paddleReader.reset(myTimestamp);
mySound.reset();
myDelayQueue.reset();
if (myFrameManager) myFrameManager->reset();
@ -225,8 +223,6 @@ bool TIA::save(Serializer& out) const
{
out.putString(name());
if(!mySound.save(out)) return false;
if(!myDelayQueue.save(out)) return false;
if(!myFrameManager->save(out)) return false;
@ -296,8 +292,6 @@ bool TIA::load(Serializer& in)
if(in.getString() != name())
return false;
if(!mySound.load(in)) return false;
if(!myDelayQueue.load(in)) return false;
if(!myFrameManager->load(in)) return false;
@ -519,33 +513,24 @@ bool TIA::poke(uInt16 address, uInt8 value)
break;
////////////////////////////////////////////////////////////
// FIXME - rework this when we add the new sound core
case AUDV0:
mySound.set(address, value, mySystem->cycles());
myShadowRegisters[address] = value;
break;
case AUDV1:
mySound.set(address, value, mySystem->cycles());
myShadowRegisters[address] = value;
break;
case AUDF0:
mySound.set(address, value, mySystem->cycles());
myShadowRegisters[address] = value;
break;
case AUDF1:
mySound.set(address, value, mySystem->cycles());
myShadowRegisters[address] = value;
break;
case AUDC0:
mySound.set(address, value, mySystem->cycles());
myShadowRegisters[address] = value;
break;
case AUDC1:
mySound.set(address, value, mySystem->cycles());
myShadowRegisters[address] = value;
break;
////////////////////////////////////////////////////////////
case HMOVE:
myDelayQueue.push(HMOVE, value, Delay::hmove);

View File

@ -20,7 +20,6 @@
#include "bspf.hxx"
#include "Console.hxx"
#include "Sound.hxx"
#include "Settings.hxx"
#include "Device.hxx"
#include "Serializer.hxx"
@ -97,10 +96,9 @@ class TIA : public Device
Create a new TIA for the specified console
@param console The console the TIA is associated with
@param sound The sound object the TIA is associated with
@param settings The settings object for this TIA device
*/
TIA(Console& console, Sound& sound, Settings& settings);
TIA(Console& console, Settings& settings);
virtual ~TIA() = default;
@ -585,7 +583,6 @@ class TIA : public Device
private:
Console& myConsole;
Sound& mySound;
Settings& mySettings;
/**