mirror of https://github.com/stella-emu/stella.git
Gut current audio code.
This commit is contained in:
parent
da473f4803
commit
e88751638b
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
};
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue