diff --git a/.vscode/settings.json b/.vscode/settings.json index 768dc0b74..72a1af5f4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" } } diff --git a/src/common/SoundNull.hxx b/src/common/SoundNull.hxx index 0189153f6..390b30052 100644 --- a/src/common/SoundNull.hxx +++ b/src/common/SoundNull.hxx @@ -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; diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 34fe07b16..738d1e6c4 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -22,8 +22,6 @@ #include #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) - { - SDL_CloseAudio(); - myIsEnabled = myIsInitializedFlag = false; - } + if (!myIsInitializedFlag) return; + + SDL_CloseAudio(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -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(); - myOSystem.logMessage("SoundSDL2::close", 2); - } + 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(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(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(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 buffer = make_unique(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); + SDL_memset(stream, 0, len); // Write 'silence' } #endif // SOUND_SUPPORT diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index faa9a72b9..79fe47543 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -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 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); diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 0651cbae1..763fb78dc 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -89,7 +89,7 @@ Console::Console(OSystem& osystem, unique_ptr& cart, // Create subsystems for the console my6502 = make_unique(myOSystem.settings()); myRiot = make_unique(*this, myOSystem.settings()); - myTIA = make_unique(*this, myOSystem.sound(), myOSystem.settings()); + myTIA = make_unique(*this, myOSystem.settings()); myFrameManager = make_unique(); mySwitches = make_unique(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); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index a46afe114..89336b9b1 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -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 diff --git a/src/emucore/TIASnd.cxx b/src/emucore/TIASnd.cxx deleted file mode 100644 index ed8457e26..000000000 --- a/src/emucore/TIASnd.cxx +++ /dev/null @@ -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 -}; diff --git a/src/emucore/TIASnd.hxx b/src/emucore/TIASnd.hxx deleted file mode 100644 index 9a42d5ea2..000000000 --- a/src/emucore/TIASnd.hxx +++ /dev/null @@ -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 diff --git a/src/emucore/module.mk b/src/emucore/module.mk index aa6394214..f9a4d0903 100644 --- a/src/emucore/module.mk +++ b/src/emucore/module.mk @@ -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 diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 8deaf9c2f..ce5fb25ce 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -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); diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index bf2bcdecf..86e14e774 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -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; /**