From e88751638b7fda4db11c7a606e50dd8f140d7ca8 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Fri, 1 Dec 2017 23:17:16 +0100 Subject: [PATCH 01/77] Gut current audio code. --- .vscode/settings.json | 16 +- src/common/SoundNull.hxx | 72 -------- src/common/SoundSDL2.cxx | 345 ++-------------------------------- src/common/SoundSDL2.hxx | 164 +---------------- src/emucore/Console.cxx | 12 +- src/emucore/Sound.hxx | 27 +-- src/emucore/TIASnd.cxx | 388 --------------------------------------- src/emucore/TIASnd.hxx | 183 ------------------ src/emucore/module.mk | 1 - src/emucore/tia/TIA.cxx | 17 +- src/emucore/tia/TIA.hxx | 5 +- 11 files changed, 44 insertions(+), 1186 deletions(-) delete mode 100644 src/emucore/TIASnd.cxx delete mode 100644 src/emucore/TIASnd.hxx 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; /** From 5736f93fcd79de6fbb8fdc04fa4ddf6b74050a1f Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 4 Dec 2017 00:25:03 +0100 Subject: [PATCH 02/77] Add two "grace lines" of black when autodetecting ystart. --- src/emucore/Console.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 763fb78dc..38e69324f 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -69,6 +69,10 @@ #include "Console.hxx" +namespace { + constexpr uInt8 YSTART_EXTRA = 2; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Console::Console(OSystem& osystem, unique_ptr& cart, const Properties& props) @@ -245,7 +249,7 @@ void Console::autodetectYStart() myTIA->setFrameManager(myFrameManager.get()); - myAutodetectedYstart = ystartDetector.detectedYStart(); + myAutodetectedYstart = ystartDetector.detectedYStart() - YSTART_EXTRA; // Don't forget to reset the SC progress bars again myOSystem.settings().setValue("fastscbios", fastscbios); From be91e6ff21c5b2da024dcd637974a07d10b427be Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 17 Jan 2018 19:28:49 +0100 Subject: [PATCH 03/77] Threadsafe fragment queue. --- src/common/AudioQueue.cxx | 133 +++++++++++++++++++++++++++++++++++ src/common/AudioQueue.hxx | 141 ++++++++++++++++++++++++++++++++++++++ src/common/module.mk | 3 +- 3 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/common/AudioQueue.cxx create mode 100644 src/common/AudioQueue.hxx diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx new file mode 100644 index 000000000..0f12f4244 --- /dev/null +++ b/src/common/AudioQueue.cxx @@ -0,0 +1,133 @@ +//============================================================================ +// +// 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-2018 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 "AudioQueue.hxx" + +using std::mutex; +using std::lock_guard; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate) + : myFragmentSize(fragmentSize), + myIsStereo(isStereo), + myFragmentQueue(capacity), + myAllFragments(capacity + 2), + mySize(0), + myNextFragment(0) +{ + const uInt8 sampleSize = myIsStereo ? 2 : 1; + + myFragmentBuffer = new Int16[myFragmentSize * sampleSize * (capacity + 2)]; + + for (uInt8 i = 0; i < capacity; i++) + myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer + i * sampleSize * myFragmentSize; + + myAllFragments[capacity] = myFirstFragmentForEnqueue = + myFragmentBuffer + capacity * sampleSize * myFragmentSize; + + myAllFragments[capacity + 1] = myFirstFragmentForDequeue = + myFragmentBuffer + (capacity + 1) * sampleSize * myFragmentSize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioQueue::~AudioQueue() +{ + delete[]myFragmentBuffer; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt8 AudioQueue::capacity() const +{ + return myFragmentQueue.size(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt8 AudioQueue::size() +{ + lock_guard guard(myMutex); + + return mySize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AudioQueue::isStereo() const +{ + return myIsStereo; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioQueue::fragmentSize() const +{ + return myFragmentSize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt16 AudioQueue::sampleRate() const +{ + return mySampleRate; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int16* AudioQueue::enqueue(Int16* fragment) +{ + lock_guard guard(myMutex); + + Int16* newFragment; + + if (!fragment) { + if (!myFirstFragmentForEnqueue) throw runtime_error("enqueue called empty"); + + newFragment = myFirstFragmentForEnqueue; + myFirstFragmentForEnqueue = 0; + + return newFragment; + } + + const uInt8 capacity = myFragmentQueue.size(); + const uInt8 fragmentIndex = (myNextFragment + mySize) % capacity; + + newFragment = myFragmentQueue.at(fragmentIndex); + myFragmentQueue.at(fragmentIndex) = fragment; + + if (mySize < capacity) mySize++; + else myNextFragment = (myNextFragment + 1) % capacity; + + return newFragment; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int16* AudioQueue::dequeue(Int16* fragment) +{ + lock_guard guard(myMutex); + + if (mySize == 0) return 0; + + if (!fragment) { + if (!myFirstFragmentForDequeue) throw runtime_error("dequeue called empty"); + + fragment = myFirstFragmentForDequeue; + myFirstFragmentForDequeue = 0; + } + + Int16* nextFragment = myFragmentQueue.at(myNextFragment); + myFragmentQueue.at(myNextFragment) = fragment; + + mySize--; + myNextFragment = (myNextFragment + 1) % myFragmentQueue.size(); + + return nextFragment; +} diff --git a/src/common/AudioQueue.hxx b/src/common/AudioQueue.hxx new file mode 100644 index 000000000..ec1637dd8 --- /dev/null +++ b/src/common/AudioQueue.hxx @@ -0,0 +1,141 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 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 AUDIO_QUEUE_HXX +#define AUDIO_QUEUE_HXX + +#include + +#include "bspf.hxx" + +/** + This class implements a an audio queue that acts both like a ring buffer + and a pool of audio fragments. The TIA emulation core fills a fragment + with samples and then returns it to the queue, receiving a new fragment + in return. The sound driver removes fragments for playback from the + queue and returns the used fragment in this process. + + The queue needs to be threadsafe as the (SDL) audio driver runs on a + separate thread. Samples are stored as signed 16 bit integers + (platform endian). + */ +class AudioQueue +{ + public: + + /** + Create a new AudioQueue. + + @param fragmentSize The size (in stereo / mono samples) of each fragment + @param capacaity The number of fragments that can be queued before wrapping. + @param isStereo Whether samples are stereo or mono. + @param sampleRate The sample rate. This is not used, but can be queried. + */ + AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate); + + /** + We need a destructor to deallocate the individual fragment buffers. + */ + ~AudioQueue(); + + /** + Capacity getter. + */ + uInt8 capacity() const; + + /** + Size getter. + */ + uInt8 size(); + + /** + Stereo / mono getter. + */ + bool isStereo() const; + + /** + Fragment size getter. + */ + uInt32 fragmentSize() const; + + /** + Sample rate getter. + */ + uInt16 sampleRate() const; + + /** + Enqueue a new fragment and get a new fragmen to fill. + + @param fragment The returned fragment. This must be empty on the first call (when + there is nothing to return) + */ + Int16* enqueue(Int16* fragment = 0); + + /** + * Dequeue a fragment for playback and return the played fragment. This may + * return 0 if there is no queued fragment to return (in this case, the returned + * fragment is not enqueued and must be passed in the next invocation). + * + * @param fragment The returned fragment. This must be empty on the first call (when + * there is nothing to return). + */ + Int16* dequeue(Int16* fragment = 0); + + private: + + // The size of an individual fragment (in stereo / mono samples) + uInt32 myFragmentSize; + + // Are we using stereo samples? + bool myIsStereo; + + // The sample rate + uInt16 mySampleRate; + + // The fragment queue + vector myFragmentQueue; + + // All fragments, including the two fragments that are in circulation. + vector myAllFragments; + + // We allocate a consecutive slice of memory for the fragments. + Int16* myFragmentBuffer; + + // The nubmer if queued fragments + uInt8 mySize; + + // The next fragment. + uInt8 myNextFragment; + + // We need a mutex for thread safety. + std::mutex myMutex; + + // The first (empty) enqueue call returns this fragment. + Int16* myFirstFragmentForEnqueue; + // The first (empty) dequeue call replaces the returned fragment with this fragment. + Int16* myFirstFragmentForDequeue; + + private: + + AudioQueue() = delete; + AudioQueue(const AudioQueue&) = delete; + AudioQueue(AudioQueue&&) = delete; + AudioQueue& operator=(const AudioQueue&) = delete; + AudioQueue& operator=(AudioQueue&&) = delete; +}; + +#endif // AUDIO_QUEUE_HXX diff --git a/src/common/module.mk b/src/common/module.mk index 37690f7dd..beea041b5 100644 --- a/src/common/module.mk +++ b/src/common/module.mk @@ -12,7 +12,8 @@ MODULE_OBJS := \ src/common/MouseControl.o \ src/common/RewindManager.o \ src/common/StateManager.o \ - src/common/ZipHandler.o + src/common/ZipHandler.o \ + src/common/AudioQueue.o MODULE_DIRS += \ src/common From f1b5421c17eda811c824453d3097b09c42ac3e1d Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Tue, 23 Jan 2018 13:41:14 +0100 Subject: [PATCH 04/77] Introduce and wire audio emulation core. --- src/emucore/Console.cxx | 35 +++++++++++++++++++++++++++++++++++ src/emucore/Console.hxx | 9 +++++++++ src/emucore/tia/Audio.cxx | 28 ++++++++++++++++++++++++++++ src/emucore/tia/Audio.hxx | 39 +++++++++++++++++++++++++++++++++++++++ src/emucore/tia/TIA.cxx | 7 +++++++ src/emucore/tia/TIA.hxx | 15 +++++++++++++-- src/emucore/tia/module.mk | 3 ++- 7 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 src/emucore/tia/Audio.cxx create mode 100644 src/emucore/tia/Audio.hxx diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 9db5a7946..b56d8fc56 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -55,6 +55,7 @@ #include "Version.hxx" #include "TIAConstants.hxx" #include "FrameLayout.hxx" +#include "AudioQueue.hxx" #include "frame-manager/FrameManager.hxx" #include "frame-manager/FrameLayoutDetector.hxx" #include "frame-manager/YStartDetector.hxx" @@ -71,6 +72,8 @@ namespace { constexpr uInt8 YSTART_EXTRA = 2; + constexpr uInt8 AUDIO_QUEUE_CAPACITY_FRAGMENTS = 20; + constexpr uInt8 AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT = 1; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -179,6 +182,9 @@ Console::Console(OSystem& osystem, unique_ptr& cart, myConsoleTiming = ConsoleTiming::secam; } + createAudioQueue(); + myTIA->setAudioQueue(myAudioQueue.get()); + bool joyallow4 = myOSystem.settings().getBool("joyallow4"); myOSystem.eventHandler().allowAllDirections(joyallow4); @@ -727,6 +733,35 @@ void Console::setTIAProperties() myTIA->setHeight(height); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Console::createAudioQueue() +{ + uInt32 fragmentSize, sampleRate; + + switch (myConsoleTiming) { + case ConsoleTiming::ntsc: + fragmentSize = 262 * AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT; + sampleRate = 2 * 262 * 60; + break; + + case ConsoleTiming::pal: + case ConsoleTiming::secam: + fragmentSize = 312 * AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT; + sampleRate = 2 * 312 * 50; + break; + + default: + throw runtime_error("invalid console timing"); + } + + myAudioQueue = make_unique( + fragmentSize, + AUDIO_QUEUE_CAPACITY_FRAGMENTS, + myProperties.get(Cartridge_Sound) == "STEREO", + sampleRate + ); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Console::setControllers(const string& rommd5) { diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index 8f527828d..7e67e70dd 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -27,6 +27,7 @@ class M6532; class Cartridge; class CompuMate; class Debugger; +class AudioQueue; #include "bspf.hxx" #include "Control.hxx" @@ -322,6 +323,11 @@ class Console : public Serializable */ void setTIAProperties(); + /** + Create the audio queue + */ + void createAudioQueue(); + /** Adds the left and right controllers to the console. */ @@ -381,6 +387,9 @@ class Console : public Serializable // The frame manager instance that is used during emulation. unique_ptr myFrameManager; + // The audio fragment queue that connects TIA and audio driver + unique_ptr myAudioQueue; + // Pointer to the Cartridge (the debugger needs it) unique_ptr myCart; diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx new file mode 100644 index 000000000..9f697b332 --- /dev/null +++ b/src/emucore/tia/Audio.cxx @@ -0,0 +1,28 @@ +//============================================================================ +// +// 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-2018 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 "Audio.hxx" +#include "AudioQueue.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Audio::Audio() {} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::setAudioQueue(AudioQueue* queue) +{ + myAudioQueue = queue; +} diff --git a/src/emucore/tia/Audio.hxx b/src/emucore/tia/Audio.hxx new file mode 100644 index 000000000..d309c0f76 --- /dev/null +++ b/src/emucore/tia/Audio.hxx @@ -0,0 +1,39 @@ +//============================================================================ +// +// 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-2018 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 TIA_AUDIO_HXX +#define TIA_AUDIO_HXX + +class AudioQueue; + +class Audio { + public: + Audio(); + + void setAudioQueue(AudioQueue *queue); + + private: + AudioQueue* myAudioQueue; + + private: + Audio(const Audio&) = delete; + Audio(Audio&&) = delete; + Audio& operator=(const Audio&) = delete; + Audio& operator=(Audio&&) = delete; +}; + +#endif // TIA_AUDIO_HXX diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index c8ab43d40..ae9c9666a 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -23,6 +23,7 @@ #include "DelayQueueIteratorImpl.hxx" #include "TIAConstants.hxx" #include "frame-manager/FrameManager.hxx" +#include "AudioQueue.hxx" #ifdef DEBUGGER_SUPPORT #include "CartDebug.hxx" @@ -115,6 +116,12 @@ void TIA::setFrameManager(AbstractFrameManager *frameManager) myFrameManager->setJitterFactor(myJitterFactor); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TIA::setAudioQueue(AudioQueue* queue) +{ + myAudio.setAudioQueue(queue); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::clearFrameManager() { diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 968b927df..da7f29c10 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -28,6 +28,7 @@ #include "DelayQueueIterator.hxx" #include "frame-manager/AbstractFrameManager.hxx" #include "FrameLayout.hxx" +#include "Audio.hxx" #include "Background.hxx" #include "Playfield.hxx" #include "Missile.hxx" @@ -39,6 +40,8 @@ #include "Control.hxx" #include "System.hxx" +class AudioQueue; + /** This class is a device that emulates the Television Interface Adaptor found in the Atari 2600 and 7800 consoles. The Television Interface @@ -103,12 +106,18 @@ class TIA : public Device public: /** - * Configure the frame manager. + Configure the frame manager. */ void setFrameManager(AbstractFrameManager *frameManager); /** - * Clear the configured frame manager and deteach the lifecycle callbacks. + Set the audio queue. This needs to be dynamic as the queue is created after + the timing has been determined. + */ + void setAudioQueue(AudioQueue *audioQueue); + + /** + Clear the configured frame manager and deteach the lifecycle callbacks. */ void clearFrameManager(); @@ -638,6 +647,8 @@ class TIA : public Device Player myPlayer1; Ball myBall; + Audio myAudio; + /** * The paddle readout circuits. */ diff --git a/src/emucore/tia/module.mk b/src/emucore/tia/module.mk index ef2adef45..569dfc35e 100644 --- a/src/emucore/tia/module.mk +++ b/src/emucore/tia/module.mk @@ -9,7 +9,8 @@ MODULE_OBJS := \ src/emucore/tia/Ball.o \ src/emucore/tia/Background.o \ src/emucore/tia/LatchedInput.o \ - src/emucore/tia/PaddleReader.o + src/emucore/tia/PaddleReader.o \ + src/emucore/tia/Audio.o MODULE_DIRS += \ src/emucore/tia From 4ec553785f73d05b343c8d72c0daf19e2f0b7fdf Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 24 Jan 2018 22:20:44 +0100 Subject: [PATCH 05/77] Implement and connect audio emulation. --- src/emucore/tia/Audio.cxx | 88 ++++++++++++++++++- src/emucore/tia/Audio.hxx | 28 +++++- src/emucore/tia/AudioChannel.cxx | 143 +++++++++++++++++++++++++++++++ src/emucore/tia/AudioChannel.hxx | 61 +++++++++++++ src/emucore/tia/TIA.cxx | 13 +++ src/emucore/tia/module.mk | 3 +- 6 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 src/emucore/tia/AudioChannel.cxx create mode 100644 src/emucore/tia/AudioChannel.hxx diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx index 9f697b332..b776447bc 100644 --- a/src/emucore/tia/Audio.cxx +++ b/src/emucore/tia/Audio.cxx @@ -18,11 +18,97 @@ #include "Audio.hxx" #include "AudioQueue.hxx" +#include + +namespace { + constexpr double R_MAX = 30.; + constexpr double R = 1.; + + Int16 mixingTableEntry(uInt8 v, uInt8 vMax) + { + return floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v))); + } +} + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Audio::Audio() {} +Audio::Audio() + : myAudioQueue(0), + myCurrentFragment(0) +{ + for (uInt8 i = 0; i <= 0x1e; i++) myMixingTableSum[i]= mixingTableEntry(i, 0x1e); + for (uInt8 i = 0; i <= 0x0f; i++) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f); + + reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::reset() +{ + myCounter = 0; + mySampleIndex = 0; + + myChannel0.reset(); + myChannel1.reset(); +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Audio::setAudioQueue(AudioQueue* queue) { myAudioQueue = queue; + + myCurrentFragment = myAudioQueue->enqueue(); + mySampleIndex = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::tick() +{ switch (myCounter) { + case 9: + case 81: + myChannel0.phase0(); + myChannel1.phase0(); + + break; + + case 37: + case 149: + phase1(); + break; + } + + if (++myCounter == 228) myCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Audio::phase1() +{ + uInt8 sample0 = myChannel0.phase1(); + uInt8 sample1 = myChannel1.phase1(); + + if (!myAudioQueue) return; + + if (myAudioQueue->isStereo()) { + myCurrentFragment[2*mySampleIndex] = myMixingTableIndividual[sample0]; + myCurrentFragment[2*mySampleIndex + 1] = myMixingTableIndividual[sample1]; + } else { + myCurrentFragment[mySampleIndex] = myMixingTableSum[sample0 + sample1]; + } + + if (++mySampleIndex == myAudioQueue->fragmentSize()) { + mySampleIndex = 0; + myCurrentFragment = myAudioQueue->enqueue(myCurrentFragment); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel0() +{ + return myChannel0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel& Audio::channel1() +{ + return myChannel1; } diff --git a/src/emucore/tia/Audio.hxx b/src/emucore/tia/Audio.hxx index d309c0f76..595b53765 100644 --- a/src/emucore/tia/Audio.hxx +++ b/src/emucore/tia/Audio.hxx @@ -18,17 +18,43 @@ #ifndef TIA_AUDIO_HXX #define TIA_AUDIO_HXX +#include "bspf.hxx" +#include "AudioChannel.hxx" + class AudioQueue; -class Audio { +class Audio +{ public: Audio(); + void reset(); + void setAudioQueue(AudioQueue *queue); + void tick(); + + AudioChannel& channel0(); + + AudioChannel& channel1(); + + private: + void phase1(); + private: AudioQueue* myAudioQueue; + uInt8 myCounter; + + AudioChannel myChannel0; + AudioChannel myChannel1; + + Int16 myMixingTableSum[0x1e + 1]; + Int16 myMixingTableIndividual[0x0f + 1]; + + Int16* myCurrentFragment; + uInt32 mySampleIndex; + private: Audio(const Audio&) = delete; Audio(Audio&&) = delete; diff --git a/src/emucore/tia/AudioChannel.cxx b/src/emucore/tia/AudioChannel.cxx new file mode 100644 index 000000000..b7e13fa30 --- /dev/null +++ b/src/emucore/tia/AudioChannel.cxx @@ -0,0 +1,143 @@ +//============================================================================ +// +// 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-2018 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 "AudioChannel.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioChannel::AudioChannel() +{ + reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::reset() +{ + myAudc = myAudv = myAudf = 0; + myClockEnable = myNoiseFeedback = myNoiseCounterBit4 = myPulseCounterHold = false; + myDivCounter = myPulseCounter = myNoiseCounter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::phase0() +{ + if (myClockEnable) { + myNoiseCounterBit4 = myNoiseCounter & 0x01; + + switch (myAudc & 0x03) { + case 0x00: + case 0x01: + myPulseCounterHold = false; + break; + + case 0x02: + myPulseCounterHold = (myNoiseCounter & 0x1e) != 0x02; + break; + + case 0x03: + myPulseCounterHold = myNoiseCounterBit4; + break; + } + + switch (myAudc & 0x03) { + case 0x00: + myNoiseFeedback = + ((myPulseCounter ^ myNoiseCounter) & 0x01) || + !(myNoiseCounter || (myPulseCounter != 0x0a)) || + !(myAudc & 0x0c); + + break; + + default: + myNoiseFeedback = + ((myNoiseCounter & 0x04 ? 1 : 0) ^ (myNoiseCounter & 0x01)) || + myNoiseCounter == 0; + + break; + } + } + + myClockEnable = myDivCounter == myAudf; + + if (myDivCounter == myAudf || myDivCounter == 0x1f) { + myDivCounter = 0; + } else { + myDivCounter++; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt8 AudioChannel::phase1() +{ + bool pulseFeedback = false; + + if (myClockEnable) { + switch (myAudc >> 2) { + case 0x00: + pulseFeedback = + ((myPulseCounter & 0x02 ? 1 : 0) ^ (myPulseCounter & 0x01)) && + (myPulseCounter != 0x0a) && + (myAudc & 0x03); + + break; + + case 0x01: + pulseFeedback = !(myPulseCounter & 0x08); + break; + + case 0x02: + pulseFeedback = !myNoiseCounterBit4; + break; + + case 0x03: + pulseFeedback = !((myPulseCounter & 0x02) || !(myPulseCounter & 0x0e)); + break; + } + + myNoiseCounter >>= 1; + if (myNoiseFeedback) { + myNoiseCounter |= 0x10; + } + + if (!myPulseCounterHold) { + myPulseCounter = ~(myPulseCounter >> 1) & 0x07; + + if (pulseFeedback) { + myPulseCounter |= 0x08; + } + } + } + + return (myPulseCounter & 0x01) * myAudv; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audc(uInt8 value) +{ + myAudc = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audv(uInt8 value) +{ + myAudv = value & 0x0f; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioChannel::audf(uInt8 value) +{ + myAudc = value & 0x1f; +} diff --git a/src/emucore/tia/AudioChannel.hxx b/src/emucore/tia/AudioChannel.hxx new file mode 100644 index 000000000..de596edbd --- /dev/null +++ b/src/emucore/tia/AudioChannel.hxx @@ -0,0 +1,61 @@ +//============================================================================ +// +// 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-2018 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 TIA_AUDIO_CHANNEL_HXX +#define TIA_AUDIO_CHANNEL_HXX + +#include "bspf.hxx" + +class AudioChannel +{ + public: + AudioChannel(); + + void reset(); + + void phase0(); + + uInt8 phase1(); + + void audc(uInt8 value); + + void audf(uInt8 value); + + void audv(uInt8 value); + + private: + uInt8 myAudc; + uInt8 myAudv; + uInt8 myAudf; + + bool myClockEnable; + bool myNoiseFeedback; + bool myNoiseCounterBit4; + bool myPulseCounterHold; + + uInt8 myDivCounter; + uInt8 myPulseCounter; + uInt8 myNoiseCounter; + + private: + AudioChannel(const AudioChannel&); + AudioChannel(AudioChannel&&); + AudioChannel& operator=(const AudioChannel&); + AudioChannel& operator=(AudioChannel&&); +}; + +#endif // TIA_AUDIO_CHANNEL_HXX diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index ae9c9666a..641d1d4c9 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -521,21 +521,32 @@ bool TIA::poke(uInt16 address, uInt8 value) break; case AUDV0: + myAudio.channel0().audv(value); myShadowRegisters[address] = value; break; + case AUDV1: + myAudio.channel1().audv(value); myShadowRegisters[address] = value; break; + case AUDF0: + myAudio.channel0().audf(value); myShadowRegisters[address] = value; break; + case AUDF1: + myAudio.channel1().audf(value); myShadowRegisters[address] = value; break; + case AUDC0: + myAudio.channel0().audc(value); myShadowRegisters[address] = value; break; + case AUDC1: + myAudio.channel1().audc(value); myShadowRegisters[address] = value; break; @@ -1179,6 +1190,8 @@ void TIA::cycle(uInt32 colorClocks) if (++myHctr >= 228) nextLine(); + myAudio.tick(); + myTimestamp++; } } diff --git a/src/emucore/tia/module.mk b/src/emucore/tia/module.mk index 569dfc35e..d6302e805 100644 --- a/src/emucore/tia/module.mk +++ b/src/emucore/tia/module.mk @@ -10,7 +10,8 @@ MODULE_OBJS := \ src/emucore/tia/Background.o \ src/emucore/tia/LatchedInput.o \ src/emucore/tia/PaddleReader.o \ - src/emucore/tia/Audio.o + src/emucore/tia/Audio.o \ + src/emucore/tia/AudioChannel.o MODULE_DIRS += \ src/emucore/tia From 8198f6ccaf9e2899f615a9dfacbf2a06f8193f20 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 25 Jan 2018 20:48:34 +0100 Subject: [PATCH 06/77] Crackling and screeching.... but it is correlated with the TIA :) --- src/common/SoundSDL2.cxx | 78 +++++++++++++++++++++++++++++++++++++--- src/common/SoundSDL2.hxx | 10 +++++- src/emucore/Console.cxx | 2 +- src/emucore/Sound.hxx | 3 +- 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 4c5b638c8..e8d27e92e 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -28,12 +28,15 @@ #include "OSystem.hxx" #include "Console.hxx" #include "SoundSDL2.hxx" +#include "AudioQueue.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL2::SoundSDL2(OSystem& osystem) : Sound(osystem), myIsInitializedFlag(false), - myVolume(100) + myVolume(100), + myAudioQueue(0), + myCurrentFragment(0) { myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); @@ -96,7 +99,7 @@ void SoundSDL2::setEnabled(bool state) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL2::open() +void SoundSDL2::open(AudioQueue* audioQueue) { myOSystem.logMessage("SoundSDL2::open started ...", 2); mute(true); @@ -107,6 +110,12 @@ void SoundSDL2::open() return; } + myAudioQueue = audioQueue; + myUnderrun = true; + myCurrentFragment = 0; + myTimeIndex = 0; + myFragmentIndex = 0; + // Adjust volume to that defined in settings setVolume(myOSystem.settings().getInt("volume")); @@ -131,6 +140,9 @@ void SoundSDL2::close() { if(!myIsInitializedFlag) return; + myAudioQueue = 0; + myCurrentFragment = 0; + mute(true); myOSystem.logMessage("SoundSDL2::close", 2); @@ -189,12 +201,70 @@ void SoundSDL2::adjustVolume(Int8 direction) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::processFragment(Int16* stream, uInt32 length) -{} +{ + if (myUnderrun && myAudioQueue->size() > 1) { + myUnderrun = false; + myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); + myFragmentIndex = 0; + } + + if (!myCurrentFragment) { + memset(stream, 0, 2 * length); + return; + } + + bool isStereoTIA = myAudioQueue->isStereo(); + bool isStereo = myHardwareSpec.channels == 2; + uInt32 sampleRateTIA = myAudioQueue->sampleRate(); + uInt32 sampleRate = myHardwareSpec.freq; + uInt32 fragmentSize = myAudioQueue->fragmentSize(); + uInt32 outputSamples = isStereo ? (length >> 1) : length; + + for (uInt32 i = 0; i < outputSamples; i++) { + myTimeIndex += sampleRateTIA; + + if (myTimeIndex > sampleRate) { + myFragmentIndex += myTimeIndex / sampleRate; + myTimeIndex %= sampleRate; + } + + if (myFragmentIndex >= fragmentSize) { + myFragmentIndex %= fragmentSize; + + Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment); + if (nextFragment) + myCurrentFragment = nextFragment; + else + myUnderrun = true; + } + + if (isStereo) { + if (isStereoTIA) { + stream[2*i] = myCurrentFragment[2*myFragmentIndex]; + stream[2*i + 1] = myCurrentFragment[2*myFragmentIndex + 1]; + } else { + stream[2*i] = stream[2*i + 1] = myCurrentFragment[myFragmentIndex]; + } + } else { + if (isStereoTIA) { + stream[i] = + ((myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2)); + } else { + stream[i] = myCurrentFragment[myFragmentIndex]; + } + } + } +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::callback(void* udata, uInt8* stream, int len) { - SDL_memset(stream, 0, len); // Write 'silence' + SoundSDL2* self = static_cast(udata); + + if (self->myAudioQueue) + self->processFragment(reinterpret_cast(stream), len >> 1); + else + SDL_memset(stream, 0, len); } #endif // SOUND_SUPPORT diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index ba40b67c1..b216b5a41 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -21,6 +21,7 @@ #define SOUND_SDL2_HXX class OSystem; +class AudioQueue; #include "SDL_lib.hxx" @@ -58,7 +59,7 @@ class SoundSDL2 : public Sound Initializes the sound device. This must be called before any calls are made to derived methods. */ - void open() override; + void open(AudioQueue* audioQueue) override; /** Should be called to close the sound device. Once called the sound @@ -116,6 +117,13 @@ class SoundSDL2 : public Sound // Audio specification structure SDL_AudioSpec myHardwareSpec; + AudioQueue* myAudioQueue; + + Int16* myCurrentFragment; + uInt32 myTimeIndex; + uInt32 myFragmentIndex; + bool myUnderrun; + 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 b56d8fc56..a420cb15f 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -580,7 +580,7 @@ void Console::initializeAudio() // const string& sound = myProperties.get(Cartridge_Sound); // myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1); - myOSystem.sound().open(); + myOSystem.sound().open(myAudioQueue.get()); // Make sure auto-frame calculation is only enabled when necessary myTIA->enableAutoFrame(framerate <= 0); diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index 904c0ea19..3aa4726e4 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -19,6 +19,7 @@ #define SOUND_HXX class OSystem; +class AudioQueue; #include "bspf.hxx" @@ -50,7 +51,7 @@ class Sound Start the sound system, initializing it if necessary. This must be called before any calls are made to derived methods. */ - virtual void open() = 0; + virtual void open(AudioQueue* audioQueue) = 0; /** Should be called to stop the sound system. Once called the sound From 4528b9067af1bc7bae6fa46d330bde56247bfb3f Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 25 Jan 2018 22:48:00 +0100 Subject: [PATCH 07/77] Assorted fixes -> works (module timing glitches). --- src/common/AudioQueue.cxx | 1 + src/common/SoundSDL2.cxx | 17 +++++++++-------- src/emucore/tia/AudioChannel.cxx | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 0f12f4244..6156f37f6 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -24,6 +24,7 @@ using std::lock_guard; AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate) : myFragmentSize(fragmentSize), myIsStereo(isStereo), + mySampleRate(sampleRate), myFragmentQueue(capacity), myAllFragments(capacity + 2), mySize(0), diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index e8d27e92e..f111a86a4 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -45,7 +45,8 @@ SoundSDL2::SoundSDL2(OSystem& osystem) // This fixes a bug most prevalent with ATI video cards in Windows, // whereby sound stopped working after the first video change SDL_AudioSpec desired; - desired.freq = myOSystem.settings().getInt("freq"); + // desired.freq = myOSystem.settings().getInt("freq"); + desired.freq = 48000; desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = myOSystem.settings().getInt("fragsize"); @@ -213,17 +214,17 @@ void SoundSDL2::processFragment(Int16* stream, uInt32 length) return; } - bool isStereoTIA = myAudioQueue->isStereo(); - bool isStereo = myHardwareSpec.channels == 2; - uInt32 sampleRateTIA = myAudioQueue->sampleRate(); - uInt32 sampleRate = myHardwareSpec.freq; - uInt32 fragmentSize = myAudioQueue->fragmentSize(); - uInt32 outputSamples = isStereo ? (length >> 1) : length; + const bool isStereoTIA = myAudioQueue->isStereo(); + const bool isStereo = myHardwareSpec.channels == 2; + const uInt32 sampleRateTIA = myAudioQueue->sampleRate(); + const uInt32 sampleRate = myHardwareSpec.freq; + const uInt32 fragmentSize = myAudioQueue->fragmentSize(); + const uInt32 outputSamples = isStereo ? (length >> 1) : length; for (uInt32 i = 0; i < outputSamples; i++) { myTimeIndex += sampleRateTIA; - if (myTimeIndex > sampleRate) { + if (myTimeIndex >= sampleRate) { myFragmentIndex += myTimeIndex / sampleRate; myTimeIndex %= sampleRate; } diff --git a/src/emucore/tia/AudioChannel.cxx b/src/emucore/tia/AudioChannel.cxx index b7e13fa30..a9fe49a5c 100644 --- a/src/emucore/tia/AudioChannel.cxx +++ b/src/emucore/tia/AudioChannel.cxx @@ -139,5 +139,5 @@ void AudioChannel::audv(uInt8 value) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioChannel::audf(uInt8 value) { - myAudc = value & 0x1f; + myAudf = value & 0x1f; } From 6b984a8563fada8f884b76d0640c08eac555f361 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 28 Jan 2018 00:27:25 +0100 Subject: [PATCH 08/77] Avoid race condition in pause, switch to shared_ptr for simpler semantics. --- src/common/SoundSDL2.cxx | 5 +++-- src/common/SoundSDL2.hxx | 4 ++-- src/emucore/Console.cxx | 6 +++--- src/emucore/Console.hxx | 2 +- src/emucore/Sound.hxx | 2 +- src/emucore/tia/Audio.cxx | 2 +- src/emucore/tia/Audio.hxx | 4 ++-- src/emucore/tia/TIA.cxx | 2 +- src/emucore/tia/TIA.hxx | 2 +- 9 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index f111a86a4..818ac96a5 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -100,7 +100,7 @@ void SoundSDL2::setEnabled(bool state) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL2::open(AudioQueue* audioQueue) +void SoundSDL2::open(shared_ptr audioQueue) { myOSystem.logMessage("SoundSDL2::open started ...", 2); mute(true); @@ -141,10 +141,11 @@ void SoundSDL2::close() { if(!myIsInitializedFlag) return; + mute(true); + myAudioQueue = 0; myCurrentFragment = 0; - mute(true); myOSystem.logMessage("SoundSDL2::close", 2); } diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index b216b5a41..a058eaca8 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -59,7 +59,7 @@ class SoundSDL2 : public Sound Initializes the sound device. This must be called before any calls are made to derived methods. */ - void open(AudioQueue* audioQueue) override; + void open(shared_ptr audioQueue) override; /** Should be called to close the sound device. Once called the sound @@ -117,7 +117,7 @@ class SoundSDL2 : public Sound // Audio specification structure SDL_AudioSpec myHardwareSpec; - AudioQueue* myAudioQueue; + shared_ptr myAudioQueue; Int16* myCurrentFragment; uInt32 myTimeIndex; diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index a420cb15f..ffc714452 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -183,7 +183,7 @@ Console::Console(OSystem& osystem, unique_ptr& cart, } createAudioQueue(); - myTIA->setAudioQueue(myAudioQueue.get()); + myTIA->setAudioQueue(myAudioQueue); bool joyallow4 = myOSystem.settings().getBool("joyallow4"); myOSystem.eventHandler().allowAllDirections(joyallow4); @@ -580,7 +580,7 @@ void Console::initializeAudio() // const string& sound = myProperties.get(Cartridge_Sound); // myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1); - myOSystem.sound().open(myAudioQueue.get()); + myOSystem.sound().open(myAudioQueue); // Make sure auto-frame calculation is only enabled when necessary myTIA->enableAutoFrame(framerate <= 0); @@ -754,7 +754,7 @@ void Console::createAudioQueue() throw runtime_error("invalid console timing"); } - myAudioQueue = make_unique( + myAudioQueue = make_shared( fragmentSize, AUDIO_QUEUE_CAPACITY_FRAGMENTS, myProperties.get(Cartridge_Sound) == "STEREO", diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index 7e67e70dd..880911e0a 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -388,7 +388,7 @@ class Console : public Serializable unique_ptr myFrameManager; // The audio fragment queue that connects TIA and audio driver - unique_ptr myAudioQueue; + shared_ptr myAudioQueue; // Pointer to the Cartridge (the debugger needs it) unique_ptr myCart; diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index 3aa4726e4..67b3de1af 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -51,7 +51,7 @@ class Sound Start the sound system, initializing it if necessary. This must be called before any calls are made to derived methods. */ - virtual void open(AudioQueue* audioQueue) = 0; + virtual void open(shared_ptr audioQueue) = 0; /** Should be called to stop the sound system. Once called the sound diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx index b776447bc..3397af214 100644 --- a/src/emucore/tia/Audio.cxx +++ b/src/emucore/tia/Audio.cxx @@ -53,7 +53,7 @@ void Audio::reset() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Audio::setAudioQueue(AudioQueue* queue) +void Audio::setAudioQueue(shared_ptr queue) { myAudioQueue = queue; diff --git a/src/emucore/tia/Audio.hxx b/src/emucore/tia/Audio.hxx index 595b53765..b4a59dfc4 100644 --- a/src/emucore/tia/Audio.hxx +++ b/src/emucore/tia/Audio.hxx @@ -30,7 +30,7 @@ class Audio void reset(); - void setAudioQueue(AudioQueue *queue); + void setAudioQueue(shared_ptr queue); void tick(); @@ -42,7 +42,7 @@ class Audio void phase1(); private: - AudioQueue* myAudioQueue; + shared_ptr myAudioQueue; uInt8 myCounter; diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 641d1d4c9..f7cbe949a 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -117,7 +117,7 @@ void TIA::setFrameManager(AbstractFrameManager *frameManager) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIA::setAudioQueue(AudioQueue* queue) +void TIA::setAudioQueue(shared_ptr queue) { myAudio.setAudioQueue(queue); } diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index da7f29c10..3c0f0b2cb 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -114,7 +114,7 @@ class TIA : public Device Set the audio queue. This needs to be dynamic as the queue is created after the timing has been determined. */ - void setAudioQueue(AudioQueue *audioQueue); + void setAudioQueue(shared_ptr audioQueue); /** Clear the configured frame manager and deteach the lifecycle callbacks. From 8c4faf122bf88e30be4fc772e9760e529f4945a0 Mon Sep 17 00:00:00 2001 From: thrust26 Date: Sun, 28 Jan 2018 16:34:52 +0100 Subject: [PATCH 09/77] Updated project files for VS --- src/windows/Stella.vcxproj | 8 ++++++-- src/windows/Stella.vcxproj.filters | 24 ++++++++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index c9225f627..021ec6dcb 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -228,6 +228,7 @@ + @@ -325,6 +326,8 @@ + + @@ -407,7 +410,6 @@ - @@ -508,6 +510,7 @@ + @@ -620,6 +623,8 @@ + + @@ -726,7 +731,6 @@ - diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index 7997369dd..f43f77d34 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -237,9 +237,6 @@ Source Files\emucore - - Source Files\emucore - Source Files\cheat @@ -891,6 +888,15 @@ Source Files\emucore\tia + + Source Files\emucore + + + Source Files\emucore\tia + + + Source Files\emucore\tia + @@ -1103,9 +1109,6 @@ Header Files\emucore - - Header Files\emucore - Header Files\debugger @@ -1823,6 +1826,15 @@ Header Files\emucore\tia + + Header Files\emucore + + + Header Files\emucore\tia + + + Header Files\emucore\tia + From ae88a5b6b6ccb7d8f3184b7c72c422ebff0fc4a5 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 28 Jan 2018 23:23:14 +0100 Subject: [PATCH 10/77] Fix crash on audio settings change. --- .vscode/settings.json | 3 ++- src/common/AudioQueue.cxx | 12 ++++++++++++ src/common/AudioQueue.hxx | 18 ++++++++++++------ src/common/SoundSDL2.cxx | 1 + 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f8fcb6720..0975eeb40 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,6 +40,7 @@ "ratio": "cpp", "tuple": "cpp", "type_traits": "cpp", - "stdexcept": "cpp" + "stdexcept": "cpp", + "fstream": "cpp" } } diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 6156f37f6..8865a49bf 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -132,3 +132,15 @@ Int16* AudioQueue::dequeue(Int16* fragment) return nextFragment; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioQueue::closeSink(Int16* fragment) +{ + lock_guard guard(myMutex); + + if (myFirstFragmentForDequeue && fragment) + throw new runtime_error("attempt to return unknown buffer on closeSink"); + + if (!myFirstFragmentForDequeue) + myFirstFragmentForDequeue = fragment; +} diff --git a/src/common/AudioQueue.hxx b/src/common/AudioQueue.hxx index ec1637dd8..6b25d9bd6 100644 --- a/src/common/AudioQueue.hxx +++ b/src/common/AudioQueue.hxx @@ -86,15 +86,21 @@ class AudioQueue Int16* enqueue(Int16* fragment = 0); /** - * Dequeue a fragment for playback and return the played fragment. This may - * return 0 if there is no queued fragment to return (in this case, the returned - * fragment is not enqueued and must be passed in the next invocation). - * - * @param fragment The returned fragment. This must be empty on the first call (when - * there is nothing to return). + Dequeue a fragment for playback and return the played fragment. This may + return 0 if there is no queued fragment to return (in this case, the returned + fragment is not enqueued and must be passed in the next invocation). + + @param fragment The returned fragment. This must be empty on the first call (when + there is nothing to return). */ Int16* dequeue(Int16* fragment = 0); + /** + Return the currently playing fragment without drawing a new one. This is called + if the sink is closed and prepares the queue to be reopened. + */ + void closeSink(Int16* fragment); + private: // The size of an individual fragment (in stereo / mono samples) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 818ac96a5..32ef5ec90 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -143,6 +143,7 @@ void SoundSDL2::close() mute(true); + if (myAudioQueue) myAudioQueue->closeSink(myCurrentFragment); myAudioQueue = 0; myCurrentFragment = 0; From c5aad2ae7aa5f0b860b481da3ebb0cc304682ffb Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 28 Jan 2018 23:50:02 +0100 Subject: [PATCH 11/77] Volume adjustment. --- src/common/SoundSDL2.cxx | 26 +++++++++++++++++++------- src/common/SoundSDL2.hxx | 1 + 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 32ef5ec90..243a498a1 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -30,11 +30,19 @@ #include "SoundSDL2.hxx" #include "AudioQueue.hxx" +namespace { + inline Int16 applyVolume(Int16 sample, Int32 volumeFactor) + { + return static_cast(static_cast(sample) * volumeFactor / 0xffff); + } +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL2::SoundSDL2(OSystem& osystem) : Sound(osystem), myIsInitializedFlag(false), myVolume(100), + myVolumeFactor(0xffff), myAudioQueue(0), myCurrentFragment(0) { @@ -170,8 +178,10 @@ void SoundSDL2::setVolume(Int32 percent) if(myIsInitializedFlag && (percent >= 0) && (percent <= 100)) { myOSystem.settings().setValue("volume", percent); - SDL_LockAudio(); myVolume = percent; + + SDL_LockAudio(); + myVolumeFactor = static_cast(floor(static_cast(0xffff) * static_cast(myVolume) / 100.)); SDL_UnlockAudio(); } } @@ -243,17 +253,19 @@ void SoundSDL2::processFragment(Int16* stream, uInt32 length) if (isStereo) { if (isStereoTIA) { - stream[2*i] = myCurrentFragment[2*myFragmentIndex]; - stream[2*i + 1] = myCurrentFragment[2*myFragmentIndex + 1]; + stream[2*i] = applyVolume(myCurrentFragment[2*myFragmentIndex], myVolumeFactor); + stream[2*i + 1] = applyVolume(myCurrentFragment[2*myFragmentIndex + 1], myVolumeFactor); } else { - stream[2*i] = stream[2*i + 1] = myCurrentFragment[myFragmentIndex]; + stream[2*i] = stream[2*i + 1] = applyVolume(myCurrentFragment[myFragmentIndex], myVolumeFactor); } } else { if (isStereoTIA) { - stream[i] = - ((myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2)); + stream[i] = applyVolume( + (myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2), + myVolumeFactor + ); } else { - stream[i] = myCurrentFragment[myFragmentIndex]; + stream[i] = applyVolume(myCurrentFragment[myFragmentIndex], myVolumeFactor); } } } diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index a058eaca8..da141959d 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -113,6 +113,7 @@ class SoundSDL2 : public Sound // Current volume as a percentage (0 - 100) uInt32 myVolume; + Int32 myVolumeFactor; // Audio specification structure SDL_AudioSpec myHardwareSpec; From 13eefbb542c006c748979134cd820eab5b826caf Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 28 Jan 2018 23:50:50 +0100 Subject: [PATCH 12/77] Make compiler happy. --- src/emucore/tia/Audio.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx index 3397af214..0e6d4b03b 100644 --- a/src/emucore/tia/Audio.cxx +++ b/src/emucore/tia/Audio.cxx @@ -26,7 +26,9 @@ namespace { Int16 mixingTableEntry(uInt8 v, uInt8 vMax) { - return floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v))); + return static_cast( + floor(0x7fff * double(v) / double(vMax) * (R_MAX + R * double(vMax)) / (R_MAX + R * double(v))) + ); } } From f87316bfdbb8c2b638be260ccf6423a06e61d635 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 29 Jan 2018 00:08:50 +0100 Subject: [PATCH 13/77] Dynamically adjust fragment buffer size based on fragmen size and period. --- src/common/SoundSDL2.cxx | 11 +++++++++-- src/common/SoundSDL2.hxx | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 243a498a1..f6923daea 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -44,7 +44,8 @@ SoundSDL2::SoundSDL2(OSystem& osystem) myVolume(100), myVolumeFactor(0xffff), myAudioQueue(0), - myCurrentFragment(0) + myCurrentFragment(0), + myFragmentBufferSize(0) { myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); @@ -124,6 +125,12 @@ void SoundSDL2::open(shared_ptr audioQueue) myCurrentFragment = 0; myTimeIndex = 0; myFragmentIndex = 0; + myFragmentBufferSize = static_cast( + ceil( + 1.5 * static_cast(myHardwareSpec.samples) / static_cast(myAudioQueue->fragmentSize()) + * static_cast(myAudioQueue->sampleRate()) / static_cast(myHardwareSpec.freq) + ) + ); // Adjust volume to that defined in settings setVolume(myOSystem.settings().getInt("volume")); @@ -215,7 +222,7 @@ void SoundSDL2::adjustVolume(Int8 direction) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::processFragment(Int16* stream, uInt32 length) { - if (myUnderrun && myAudioQueue->size() > 1) { + if (myUnderrun && myAudioQueue->size() > myFragmentBufferSize) { myUnderrun = false; myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); myFragmentIndex = 0; diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index da141959d..49217a0ed 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -123,6 +123,7 @@ class SoundSDL2 : public Sound Int16* myCurrentFragment; uInt32 myTimeIndex; uInt32 myFragmentIndex; + uInt32 myFragmentBufferSize; bool myUnderrun; private: From 97a483b124719a39bd372f2f2995a7e501f67788 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 29 Jan 2018 00:22:43 +0100 Subject: [PATCH 14/77] Update XCode project. --- src/macosx/stella.xcodeproj/project.pbxproj | 36 ++++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index 0358d89a8..4df9ee437 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -104,7 +104,6 @@ 2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20F9FF08C603EC00A73076 /* TogglePixelWidget.hxx */; }; 2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D20FA0108C603EC00A73076 /* ToggleWidget.hxx */; }; 2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D6CC10408C811A600B8F642 /* TiaZoomWidget.hxx */; }; - 2D91746509BA90380026E9FF /* TIASnd.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2DE7242E08CE910900C889A8 /* TIASnd.hxx */; }; 2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D2331900900B5EF00613B1F /* AudioWidget.hxx */; }; 2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF5F096E269100A518FE /* EventMappingWidget.hxx */; }; 2D91746A09BA90380026E9FF /* InputDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = 2D05FF61096E269100A518FE /* InputDialog.hxx */; }; @@ -202,7 +201,6 @@ 2D91750309BA90380026E9FF /* TogglePixelWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20F9FE08C603EC00A73076 /* TogglePixelWidget.cxx */; }; 2D91750409BA90380026E9FF /* ToggleWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D20FA0008C603EC00A73076 /* ToggleWidget.cxx */; }; 2D91750609BA90380026E9FF /* TiaZoomWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D6CC10308C811A600B8F642 /* TiaZoomWidget.cxx */; }; - 2D91750709BA90380026E9FF /* TIASnd.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2DE7242D08CE910900C889A8 /* TIASnd.cxx */; }; 2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D23318F0900B5EF00613B1F /* AudioWidget.cxx */; }; 2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF5E096E269100A518FE /* EventMappingWidget.cxx */; }; 2D91750C09BA90380026E9FF /* InputDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2D05FF60096E269100A518FE /* InputDialog.cxx */; }; @@ -595,7 +593,6 @@ DCF3A6FD1DFC75E3008A8AF3 /* Playfield.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */; }; DCF3A6FE1DFC75E3008A8AF3 /* TIA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */; }; DCF3A6FF1DFC75E3008A8AF3 /* TIA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */; }; - DCF3A7021DFC76BC008A8AF3 /* TIATypes.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */; }; DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467B40F93993B00B25D7A /* SoundNull.hxx */; }; DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF467BC0F9399F500B25D7A /* Version.hxx */; }; DCF467C20F939A1400B25D7A /* CartEF.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF467BE0F939A1400B25D7A /* CartEF.cxx */; }; @@ -620,6 +617,12 @@ E0306E111F93E916003DDD52 /* JitterEmulation.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */; }; E0406FB61F81A85400A82AE0 /* AbstractFrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */; }; E0406FB81F81A85400A82AE0 /* FrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */; }; + E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4139201E901C004A3391 /* AudioQueue.hxx */; }; + E09F413C201E901D004A3391 /* AudioQueue.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413A201E901D004A3391 /* AudioQueue.cxx */; }; + E09F4141201E9050004A3391 /* Audio.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F413D201E904F004A3391 /* Audio.hxx */; }; + E09F4142201E9050004A3391 /* Audio.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413E201E904F004A3391 /* Audio.cxx */; }; + E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413F201E904F004A3391 /* AudioChannel.cxx */; }; + E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4140201E904F004A3391 /* AudioChannel.hxx */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -867,8 +870,6 @@ 2DE2DF8D0627AE34006BEC99 /* Sound.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Sound.hxx; sourceTree = ""; }; 2DE2DF8E0627AE34006BEC99 /* Switches.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = Switches.cxx; sourceTree = ""; }; 2DE2DF8F0627AE34006BEC99 /* Switches.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = Switches.hxx; sourceTree = ""; }; - 2DE7242D08CE910900C889A8 /* TIASnd.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = TIASnd.cxx; sourceTree = ""; }; - 2DE7242E08CE910900C889A8 /* TIASnd.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = TIASnd.hxx; sourceTree = ""; }; 2DEB3D4C0629BD24007EBBD3 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; 2DEF21F808BC033500B246B4 /* CheckListWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CheckListWidget.cxx; sourceTree = ""; }; 2DEF21F908BC033500B246B4 /* CheckListWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.h; path = CheckListWidget.hxx; sourceTree = ""; }; @@ -1264,7 +1265,6 @@ DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Playfield.hxx; sourceTree = ""; }; DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TIA.cxx; sourceTree = ""; }; DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIA.hxx; sourceTree = ""; }; - DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TIATypes.hxx; sourceTree = ""; }; DCF467B40F93993B00B25D7A /* SoundNull.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SoundNull.hxx; sourceTree = ""; }; DCF467BC0F9399F500B25D7A /* Version.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Version.hxx; sourceTree = ""; }; DCF467BE0F939A1400B25D7A /* CartEF.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartEF.cxx; sourceTree = ""; }; @@ -1287,6 +1287,12 @@ E0306E091F93E915003DDD52 /* JitterEmulation.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JitterEmulation.cxx; sourceTree = ""; }; E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameLayoutDetector.cxx; sourceTree = ""; }; E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = JitterEmulation.hxx; sourceTree = ""; }; + E09F4139201E901C004A3391 /* AudioQueue.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioQueue.hxx; sourceTree = ""; }; + E09F413A201E901D004A3391 /* AudioQueue.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioQueue.cxx; sourceTree = ""; }; + E09F413D201E904F004A3391 /* Audio.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Audio.hxx; sourceTree = ""; }; + E09F413E201E904F004A3391 /* Audio.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cxx; sourceTree = ""; }; + E09F413F201E904F004A3391 /* AudioChannel.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioChannel.cxx; sourceTree = ""; }; + E09F4140201E904F004A3391 /* AudioChannel.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioChannel.hxx; sourceTree = ""; }; E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AbstractFrameManager.cxx; sourceTree = ""; }; E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = ""; }; F5A47A9D01A0482F01D3D55B /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = SOURCE_ROOT; }; @@ -1564,6 +1570,8 @@ 2D6050C5089876F300C6DE89 /* common */ = { isa = PBXGroup; children = ( + E09F413A201E901D004A3391 /* AudioQueue.cxx */, + E09F4139201E901C004A3391 /* AudioQueue.hxx */, DC79F81017A88D9E00288B91 /* Base.cxx */, DC79F81117A88D9E00288B91 /* Base.hxx */, DCC527D810B9DA6A005E1287 /* bspf.hxx */, @@ -1796,11 +1804,8 @@ DCD2839612E39F1200A808DC /* Thumbulator.cxx */, DCD2839712E39F1200A808DC /* Thumbulator.hxx */, DCE903E31DF5DCD10080A7F3 /* tia */, - 2DE7242D08CE910900C889A8 /* TIASnd.cxx */, - 2DE7242E08CE910900C889A8 /* TIASnd.hxx */, DC2AADAC194F389C0026C7A4 /* TIASurface.cxx */, DC2AADAD194F389C0026C7A4 /* TIASurface.hxx */, - DCF3A7011DFC76BC008A8AF3 /* TIATypes.hxx */, DC1B2EC21E50036100F62837 /* TrakBall.hxx */, ); path = emucore; @@ -2049,6 +2054,10 @@ DCE903E31DF5DCD10080A7F3 /* tia */ = { isa = PBXGroup; children = ( + E09F413E201E904F004A3391 /* Audio.cxx */, + E09F413D201E904F004A3391 /* Audio.hxx */, + E09F413F201E904F004A3391 /* AudioChannel.cxx */, + E09F4140201E904F004A3391 /* AudioChannel.hxx */, DCF3A6CD1DFC75E3008A8AF3 /* Background.cxx */, DCF3A6CE1DFC75E3008A8AF3 /* Background.hxx */, DCF3A6CF1DFC75E3008A8AF3 /* Ball.cxx */, @@ -2103,6 +2112,7 @@ buildActionMask = 2147483647; files = ( 2D9173CB09BA90380026E9FF /* SDLMain.h in Headers */, + E09F4141201E9050004A3391 /* Audio.hxx in Headers */, 2D9173CC09BA90380026E9FF /* Booster.hxx in Headers */, 2D9173CD09BA90380026E9FF /* Cart.hxx in Headers */, 2D9173CE09BA90380026E9FF /* Cart2K.hxx in Headers */, @@ -2150,6 +2160,7 @@ 2D9173EE09BA90380026E9FF /* Serializer.hxx in Headers */, 2D9173EF09BA90380026E9FF /* Sound.hxx in Headers */, 2D9173F009BA90380026E9FF /* Switches.hxx in Headers */, + E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */, 2D9173F909BA90380026E9FF /* EventHandler.hxx in Headers */, 2D9173FA09BA90380026E9FF /* FrameBuffer.hxx in Headers */, 2D9173FB09BA90380026E9FF /* Settings.hxx in Headers */, @@ -2165,6 +2176,7 @@ 2D91740309BA90380026E9FF /* Command.hxx in Headers */, DC3EE85B1E2C0E6D00905161 /* deflate.h in Headers */, 2D91740409BA90380026E9FF /* Dialog.hxx in Headers */, + E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */, 2D91740509BA90380026E9FF /* DialogContainer.hxx in Headers */, DC6D39881A3CE65000171E71 /* CartWDWidget.hxx in Headers */, 2D91740609BA90380026E9FF /* GameInfoDialog.hxx in Headers */, @@ -2245,7 +2257,6 @@ 2D91746109BA90380026E9FF /* TogglePixelWidget.hxx in Headers */, 2D91746209BA90380026E9FF /* ToggleWidget.hxx in Headers */, 2D91746409BA90380026E9FF /* TiaZoomWidget.hxx in Headers */, - 2D91746509BA90380026E9FF /* TIASnd.hxx in Headers */, 2D91746609BA90380026E9FF /* AudioWidget.hxx in Headers */, 2D91746909BA90380026E9FF /* EventMappingWidget.hxx in Headers */, 2D91746A09BA90380026E9FF /* InputDialog.hxx in Headers */, @@ -2285,7 +2296,6 @@ DC932D450F278A5200FEFEFC /* Serializable.hxx in Headers */, DC932D460F278A5200FEFEFC /* SerialPort.hxx in Headers */, DC9EA8880F729A36000452B5 /* KidVid.hxx in Headers */, - DCF3A7021DFC76BC008A8AF3 /* TIATypes.hxx in Headers */, DCF467B80F93993B00B25D7A /* SoundNull.hxx in Headers */, DCBDDE9F1D6A5F2F009DF1E9 /* Cart3EPlus.hxx in Headers */, DCF467BD0F9399F500B25D7A /* Version.hxx in Headers */, @@ -2552,6 +2562,7 @@ 2D91748F09BA90380026E9FF /* Keyboard.cxx in Sources */, 2D91749009BA90380026E9FF /* M6532.cxx in Sources */, 2D91749109BA90380026E9FF /* MD5.cxx in Sources */, + E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */, DC44019E1F1A5D01008C08F6 /* ColorWidget.cxx in Sources */, 2D91749309BA90380026E9FF /* Paddles.cxx in Sources */, 2D91749409BA90380026E9FF /* Props.cxx in Sources */, @@ -2641,7 +2652,7 @@ DC71EA9D1FDA06D2008827CB /* CartE78K.cxx in Sources */, DC73BD851915E5B1003FAFAD /* FBSurfaceSDL2.cxx in Sources */, DCDDEAC41F5DBF0400C67366 /* RewindManager.cxx in Sources */, - 2D91750709BA90380026E9FF /* TIASnd.cxx in Sources */, + E09F413C201E901D004A3391 /* AudioQueue.cxx in Sources */, DC71EA9F1FDA06D2008827CB /* CartMNetwork.cxx in Sources */, 2D91750809BA90380026E9FF /* AudioWidget.cxx in Sources */, 2D91750B09BA90380026E9FF /* EventMappingWidget.cxx in Sources */, @@ -2725,6 +2736,7 @@ DC8C1BAD14B25DE7006440EE /* CartCM.cxx in Sources */, DCDDEAC61F5DBF0400C67366 /* StateManager.cxx in Sources */, DC8C1BAF14B25DE7006440EE /* CompuMate.cxx in Sources */, + E09F4142201E9050004A3391 /* Audio.cxx in Sources */, DC8C1BB114B25DE7006440EE /* MindLink.cxx in Sources */, DCCF47DF14B60DEE00814FAB /* JoystickWidget.cxx in Sources */, DCCF49B714B7544A00814FAB /* PaddleWidget.cxx in Sources */, From 025adc59aac8ab9e360463c1c0259ef7494d993f Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 29 Jan 2018 22:25:23 +0100 Subject: [PATCH 15/77] Merge fallout. --- src/windows/Stella.vcxproj.filters | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index 1cbe3f7bb..50126d528 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -888,7 +888,6 @@ Source Files\emucore\tia -<<<<<<< HEAD Source Files\emucore @@ -897,10 +896,8 @@ Source Files\emucore\tia -======= Source Files\gui ->>>>>>> master @@ -1831,7 +1828,6 @@ Header Files\emucore\tia -<<<<<<< HEAD Header Files\emucore @@ -1840,10 +1836,8 @@ Header Files\emucore\tia -======= Header Files\gui ->>>>>>> master @@ -1857,4 +1851,4 @@ Resource Files - \ No newline at end of file + From d70b0d8c4070fdb9afecaffa1b0833050412853c Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 29 Jan 2018 22:44:06 +0100 Subject: [PATCH 16/77] Audio state serialization. --- src/emucore/tia/Audio.cxx | 48 ++++++++++++++++++++++++ src/emucore/tia/Audio.hxx | 10 ++++- src/emucore/tia/AudioChannel.cxx | 64 ++++++++++++++++++++++++++++++++ src/emucore/tia/AudioChannel.hxx | 10 ++++- src/emucore/tia/TIA.cxx | 2 + 5 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx index 0e6d4b03b..e2620879c 100644 --- a/src/emucore/tia/Audio.cxx +++ b/src/emucore/tia/Audio.cxx @@ -114,3 +114,51 @@ AudioChannel& Audio::channel1() { return myChannel1; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string Audio::name() const +{ + return "TIA_Audio"; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Audio::save(Serializer& out) const +{ + try + { + out.putString(name()); + + out.putInt(myCounter); + + if (!myChannel0.save(out)) return false; + if (!myChannel1.save(out)) return false; + } + catch(...) + { + cerr << "ERROR: TIA_Audio::save" << endl; + return false; + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Audio::load(Serializer& in) +{ + try + { + if (in.getString() != name()) return false; + + myCounter = in.getInt(); + + if (!myChannel0.load(in)) return false; + if (!myChannel1.load(in)) return false; + } + catch(...) + { + cerr << "ERROR: TIA_Audio::load" << endl; + return false; + } + + return true; +} diff --git a/src/emucore/tia/Audio.hxx b/src/emucore/tia/Audio.hxx index b4a59dfc4..968c30a9a 100644 --- a/src/emucore/tia/Audio.hxx +++ b/src/emucore/tia/Audio.hxx @@ -20,10 +20,11 @@ #include "bspf.hxx" #include "AudioChannel.hxx" +#include "Serializable.hxx" class AudioQueue; -class Audio +class Audio : public Serializable { public: Audio(); @@ -38,6 +39,13 @@ class Audio AudioChannel& channel1(); + /** + Serializable methods (see that class for more information). + */ + bool save(Serializer& out) const override; + bool load(Serializer& in) override; + string name() const override; + private: void phase1(); diff --git a/src/emucore/tia/AudioChannel.cxx b/src/emucore/tia/AudioChannel.cxx index a9fe49a5c..81b16d020 100644 --- a/src/emucore/tia/AudioChannel.cxx +++ b/src/emucore/tia/AudioChannel.cxx @@ -141,3 +141,67 @@ void AudioChannel::audf(uInt8 value) { myAudf = value & 0x1f; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string AudioChannel::name() const +{ + return "TIA_AudioChannel"; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AudioChannel::save(Serializer& out) const +{ + try + { + out.putString(name()); + + out.putInt(myAudc); + out.putInt(myAudv); + out.putInt(myAudf); + + out.putBool(myClockEnable); + out.putBool(myNoiseFeedback); + out.putBool(myNoiseCounterBit4); + out.putBool(myPulseCounterHold); + + out.putInt(myDivCounter); + out.putInt(myPulseCounter); + out.putInt(myNoiseCounter); + } + catch(...) + { + cerr << "ERROR: TIA_AudioChannel::save" << endl; + return false; + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AudioChannel::load(Serializer& in) +{ + try + { + if (in.getString() != name()) return false; + + myAudc = in.getInt(); + myAudv = in.getInt(); + myAudf = in.getInt(); + + myClockEnable = in.getBool(); + myNoiseFeedback = in.getBool(); + myNoiseCounterBit4 = in.getBool(); + myPulseCounterHold = in.getBool(); + + myDivCounter = in.getInt(); + myPulseCounter = in.getInt(); + myNoiseCounter = in.getInt(); + } + catch(...) + { + cerr << "ERROR: TIA_AudioChannel::load" << endl; + return false; + } + + return true; +} diff --git a/src/emucore/tia/AudioChannel.hxx b/src/emucore/tia/AudioChannel.hxx index de596edbd..f72372a86 100644 --- a/src/emucore/tia/AudioChannel.hxx +++ b/src/emucore/tia/AudioChannel.hxx @@ -19,8 +19,9 @@ #define TIA_AUDIO_CHANNEL_HXX #include "bspf.hxx" +#include "Serializable.hxx" -class AudioChannel +class AudioChannel : public Serializable { public: AudioChannel(); @@ -37,6 +38,13 @@ class AudioChannel void audv(uInt8 value); + /** + Serializable methods (see that class for more information). + */ + bool save(Serializer& out) const override; + bool load(Serializer& in) override; + string name() const override; + private: uInt8 myAudc; uInt8 myAudv; diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index bc79b5ede..53edcb0aa 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -240,6 +240,7 @@ bool TIA::save(Serializer& out) const if(!myPlayer0.save(out)) return false; if(!myPlayer1.save(out)) return false; if(!myBall.save(out)) return false; + if(!myAudio.save(out)) return false; for (const PaddleReader& paddleReader : myPaddleReaders) if(!paddleReader.save(out)) return false; @@ -309,6 +310,7 @@ bool TIA::load(Serializer& in) if(!myPlayer0.load(in)) return false; if(!myPlayer1.load(in)) return false; if(!myBall.load(in)) return false; + if(!myAudio.load(in)) return false; for (PaddleReader& paddleReader : myPaddleReaders) if(!paddleReader.load(in)) return false; From 7f83e776b234a1579ac2336e413242d43a7b92b4 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 31 Jan 2018 19:29:37 +0100 Subject: [PATCH 17/77] Keep book of the number of cycles spent during emulation. --- src/emucore/FrameBuffer.cxx | 10 +++++++--- src/emucore/FrameBuffer.hxx | 5 +++-- src/emucore/tia/PaddleReader.cxx | 12 ++++++------ src/emucore/tia/PaddleReader.hxx | 12 ++++++------ src/emucore/tia/TIA.cxx | 7 ++++++- src/emucore/tia/TIA.hxx | 8 +++----- 6 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 1554ddd94..ab62f93df 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -261,12 +261,14 @@ FBInitStatus FrameBuffer::createDisplay(const string& title, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::update() +Int64 FrameBuffer::update() { // Determine which mode we are in (from the EventHandler) // Take care of S_EMULATE mode here, otherwise let the GUI // figure out what to draw + Int64 cycles = -1; + invalidate(); switch(myOSystem.eventHandler().state()) { @@ -275,7 +277,7 @@ void FrameBuffer::update() // Run the console for one frame // Note that the debugger can cause a breakpoint to occur, which changes // the EventHandler state 'behind our back' - we need to check for that - myOSystem.console().tia().update(); + cycles = myOSystem.console().tia().update(); #ifdef DEBUGGER_SUPPORT if(myOSystem.eventHandler().state() != EventHandlerState::EMULATION) break; #endif @@ -344,7 +346,7 @@ void FrameBuffer::update() } case EventHandlerState::NONE: - return; + return -1; } // Draw any pending messages @@ -353,6 +355,8 @@ void FrameBuffer::update() // Do any post-frame stuff postFrameUpdate(); + + return cycles; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index eb834815e..7f5a3ce03 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -113,9 +113,10 @@ class FrameBuffer /** Updates the display, which depending on the current mode could mean - drawing the TIA, any pending menus, etc. + drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles + spent during emulation, or -1 if not applicable. */ - void update(); + Int64 update(); /** Shows a message onscreen. diff --git a/src/emucore/tia/PaddleReader.cxx b/src/emucore/tia/PaddleReader.cxx index cdb6c9437..8ae0ba201 100644 --- a/src/emucore/tia/PaddleReader.cxx +++ b/src/emucore/tia/PaddleReader.cxx @@ -26,7 +26,7 @@ PaddleReader::PaddleReader() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PaddleReader::reset(double timestamp) +void PaddleReader::reset(uInt64 timestamp) { myU = 0; myIsDumped = false; @@ -38,7 +38,7 @@ void PaddleReader::reset(double timestamp) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PaddleReader::vblank(uInt8 value, double timestamp) +void PaddleReader::vblank(uInt8 value, uInt64 timestamp) { bool oldIsDumped = myIsDumped; @@ -53,7 +53,7 @@ void PaddleReader::vblank(uInt8 value, double timestamp) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 PaddleReader::inpt(double timestamp) +uInt8 PaddleReader::inpt(uInt64 timestamp) { updateCharge(timestamp); @@ -63,7 +63,7 @@ uInt8 PaddleReader::inpt(double timestamp) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PaddleReader::update(double value, double timestamp, ConsoleTiming consoleTiming) +void PaddleReader::update(double value, uInt64 timestamp, ConsoleTiming consoleTiming) { if (consoleTiming != myConsoleTiming) { setConsoleTiming(consoleTiming); @@ -94,13 +94,13 @@ void PaddleReader::setConsoleTiming(ConsoleTiming consoleTiming) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PaddleReader::updateCharge(double timestamp) +void PaddleReader::updateCharge(uInt64 timestamp) { if (myIsDumped) return; if (myValue >= 0) myU = USUPP * (1 - (1 - myU / USUPP) * - exp(-(timestamp - myTimestamp) / (myValue * RPOT + R0) / C / myClockFreq)); + exp(-static_cast(timestamp - myTimestamp) / (myValue * RPOT + R0) / C / myClockFreq)); myTimestamp = timestamp; } diff --git a/src/emucore/tia/PaddleReader.hxx b/src/emucore/tia/PaddleReader.hxx index 77ee3bdf3..544d1d7ee 100644 --- a/src/emucore/tia/PaddleReader.hxx +++ b/src/emucore/tia/PaddleReader.hxx @@ -30,14 +30,14 @@ class PaddleReader : public Serializable public: - void reset(double timestamp); + void reset(uInt64 timestamp); - void vblank(uInt8 value, double timestamp); + void vblank(uInt8 value, uInt64 timestamp); bool vblankDumped() const { return myIsDumped; } - uInt8 inpt(double timestamp); + uInt8 inpt(uInt64 timestamp); - void update(double value, double timestamp, ConsoleTiming consoleTiming); + void update(double value, uInt64 timestamp, ConsoleTiming consoleTiming); /** Serializable methods (see that class for more information). @@ -50,7 +50,7 @@ class PaddleReader : public Serializable void setConsoleTiming(ConsoleTiming timing); - void updateCharge(double timestamp); + void updateCharge(uInt64 timestamp); private: @@ -58,7 +58,7 @@ class PaddleReader : public Serializable double myU; double myValue; - double myTimestamp; + uInt64 myTimestamp; ConsoleTiming myConsoleTiming; double myClockFreq; diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 53edcb0aa..3d8363a68 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -809,9 +809,14 @@ bool TIA::loadDisplay(Serializer& in) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIA::update() +uInt64 TIA::update() { + uInt64 timestampOld = myTimestamp; + mySystem->m6502().execute(25000); + + updateEmulation(); + return (myTimestamp - timestampOld) / 3; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 3c0f0b2cb..583087327 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -199,7 +199,7 @@ class TIA : public Device desired frame rate to update the TIA. Invoking this method will update the graphics buffer and generate the corresponding audio samples. */ - void update(); + uInt64 update(); /** Returns a pointer to the internal frame buffer. @@ -748,11 +748,9 @@ class TIA : public Device uInt8 myColorHBlank; /** - * The total number of color clocks since emulation started. This is a - * double a) to avoid overflows and b) as it will enter floating point - * expressions in the paddle readout simulation anyway. + * The total number of color clocks since emulation started. */ - double myTimestamp; + uInt64 myTimestamp; /** * The "shadow registers" track the last written register value for the From 396dd637af284142404a65e4ef7fdb2a21df8630 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 31 Jan 2018 20:44:39 +0100 Subject: [PATCH 18/77] Rework dispatch loop. --- .vscode/settings.json | 3 +- src/emucore/Console.cxx | 6 ++++ src/emucore/Console.hxx | 2 +- src/emucore/FrameBuffer.cxx | 33 ++--------------- src/emucore/FrameBuffer.hxx | 4 --- src/emucore/OSystem.cxx | 70 +++++++++++++++---------------------- src/emucore/tia/TIA.hxx | 2 ++ 7 files changed, 43 insertions(+), 77 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0975eeb40..a414c6bf2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -41,6 +41,7 @@ "tuple": "cpp", "type_traits": "cpp", "stdexcept": "cpp", - "fstream": "cpp" + "fstream": "cpp", + "__locale": "cpp" } } diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index ffc714452..7b08d4383 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -987,6 +987,12 @@ void Console::setFramerate(float framerate) // myOSystem.sound().setFrameRate(framerate); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +float Console::getFramerate() const +{ + return myTIA->frameRate(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Console::toggleTIABit(TIABit bit, const string& bitname, bool show) const { diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index 880911e0a..cd6daab86 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -272,7 +272,7 @@ class Console : public Serializable Returns the framerate based on a number of factors (whether 'framerate' is set, what display format is in use, etc) */ - float getFramerate() const { return myFramerate; } + float getFramerate() const; /** Toggles the TIA bit specified in the method name. diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index ab62f93df..1876559a9 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -47,10 +47,7 @@ FrameBuffer::FrameBuffer(OSystem& osystem) myInitializedCount(0), myPausedCount(0), myStatsEnabled(false), - myLastFrameRate(60), - myCurrentModeList(nullptr), - myTotalTime(0), - myTotalFrames(0) + myCurrentModeList(nullptr) { } @@ -290,8 +287,7 @@ Int64 FrameBuffer::update() // Show frame statistics if(myStatsMsg.enabled) drawFrameStats(); - else - myLastFrameRate = myOSystem.console().getFramerate(); + myLastScanlines = myOSystem.console().tia().scanlinesLastFrame(); myPausedCount = 0; break; // EventHandlerState::EMULATION @@ -404,30 +400,7 @@ void FrameBuffer::drawFrameStats() myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor); xPos += font().getStringWidth(msg); - // draw framerate - float frameRate; - /*if(myOSystem.settings().getInt("framerate") == 0) - { - // if 'Auto' is selected, draw the calculated framerate - frameRate = myOSystem.console().getFramerate(); - } - else*/ - { - // if 'Auto' is not selected, draw the effective framerate - const TimingInfo& ti = myOSystem.timingInfo(); - if(ti.totalFrames - myTotalFrames >= myLastFrameRate) - { - frameRate = 1000000.0 * (ti.totalFrames - myTotalFrames) / (ti.totalTime - myTotalTime); - if(frameRate > myOSystem.console().getFramerate() + 1) - frameRate = 1; - myTotalFrames = ti.totalFrames; - myTotalTime = ti.totalTime; - } - else - frameRate = myLastFrameRate; - } - myLastFrameRate = frameRate; - std::snprintf(msg, 30, " @ %5.2ffps", frameRate); + std::snprintf(msg, 30, " @ %5.2ffps", myOSystem.console().getFramerate()); myStatsMsg.surface->drawString(font(), msg, xPos, YPOS, myStatsMsg.w, myStatsMsg.color, TextAlign::Left, 0, true, kBGColor); diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index 7f5a3ce03..a23003fd2 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -517,7 +517,6 @@ class FrameBuffer Message myStatsMsg; bool myStatsEnabled; uInt32 myLastScanlines; - float myLastFrameRate; bool myGrabMouse; @@ -538,9 +537,6 @@ class FrameBuffer // Holds UI palette data (standard and classic colours) static uInt32 ourGUIColors[3][kNumColors-256]; - uInt64 myTotalTime; - uInt64 myTotalFrames; - private: // Following constructors and assignment operators not supported FrameBuffer() = delete; diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 2a5704382..9b9206cff 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -35,6 +35,8 @@ #include "CheatManager.hxx" #endif +#include + #include "FSNode.hxx" #include "MD5.hxx" #include "Cart.hxx" @@ -58,6 +60,8 @@ #include "OSystem.hxx" +using namespace std::chrono; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OSystem::OSystem() : myLauncherUsed(false), @@ -615,51 +619,35 @@ uInt64 OSystem::getTicks() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void OSystem::mainLoop() { - if(mySettings->getString("timing") == "sleep") - { - // Sleep-based wait: good for CPU, bad for graphical sync - for(;;) - { - myTimingInfo.start = getTicks(); - myEventHandler->poll(myTimingInfo.start); - if(myQuitLoop) break; // Exit if the user wants to quit - myFrameBuffer->update(); - myTimingInfo.current = getTicks(); - myTimingInfo.virt += myTimePerFrame; + // Sleep-based wait: good for CPU, bad for graphical sync + bool busyWait = mySettings->getString("timing") != "sleep"; + time_point virtualTime = high_resolution_clock::now(); - // Timestamps may periodically go out of sync, particularly on systems - // that can have 'negative time' (ie, when the time seems to go backwards) - // This normally results in having a very large delay time, so we check - // for that and reset the timers when appropriate - if((myTimingInfo.virt - myTimingInfo.current) > (myTimePerFrame << 1)) - { - myTimingInfo.current = myTimingInfo.virt = getTicks(); + for(;;) + { + myEventHandler->poll(myTimingInfo.start); + if(myQuitLoop) break; // Exit if the user wants to quit + + Int64 cycles = myFrameBuffer->update(); + duration timeslice ( + (cycles >= 0) ? + static_cast(cycles) / static_cast(76 * ((myConsole->timing() == ConsoleTiming::ntsc) ? (262 * 60) : (312 * 50))) : + 1. / 30. + ); + + virtualTime += duration_cast(timeslice); + time_point now = high_resolution_clock::now(); + + if (duration_cast>(now - virtualTime).count() > 0.5) + virtualTime = now; + else if (virtualTime > now) { + if (busyWait && cycles >= 0) { + while (high_resolution_clock::now() < virtualTime); } - - if(myTimingInfo.current < myTimingInfo.virt) - SDL_Delay(uInt32(myTimingInfo.virt - myTimingInfo.current) / 1000); - - myTimingInfo.totalTime += (getTicks() - myTimingInfo.start); - myTimingInfo.totalFrames++; + else std::this_thread::sleep_until(virtualTime); } - } - else - { - // Busy-wait: bad for CPU, good for graphical sync - for(;;) - { - myTimingInfo.start = getTicks(); - myEventHandler->poll(myTimingInfo.start); - if(myQuitLoop) break; // Exit if the user wants to quit - myFrameBuffer->update(); - myTimingInfo.virt += myTimePerFrame; - while(getTicks() < myTimingInfo.virt) - ; // busy-wait - - myTimingInfo.totalTime += (getTicks() - myTimingInfo.start); - myTimingInfo.totalFrames++; - } + myTimingInfo.totalFrames++; } // Cleanup time diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 583087327..7cbf94872 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -237,6 +237,8 @@ class TIA : public Device */ void enableAutoFrame(bool enabled) { myAutoFrameEnabled = enabled; } + float frameRate() const { return myFrameManager ? myFrameManager->frameRate() : 0; } + /** Enables/disables color-loss for PAL modes only. From cb89d09c7fcd6f36d570eee609875130fb9ae369 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 3 Feb 2018 01:01:02 +0100 Subject: [PATCH 19/77] Refactoring: remove framerate from OSystem and Console. --- src/common/SoundSDL2.cxx | 2 +- src/emucore/Console.cxx | 42 +---------------------------- src/emucore/Console.hxx | 15 +++-------- src/emucore/FrameBuffer.cxx | 3 +-- src/emucore/OSystem.cxx | 54 +++++-------------------------------- src/emucore/OSystem.hxx | 49 ++------------------------------- src/emucore/tia/TIA.cxx | 10 ------- src/emucore/tia/TIA.hxx | 13 --------- src/gui/VideoDialog.cxx | 4 +-- 9 files changed, 16 insertions(+), 176 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index f6923daea..cefcdfa67 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -222,7 +222,7 @@ void SoundSDL2::adjustVolume(Int8 direction) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::processFragment(Int16* stream, uInt32 length) { - if (myUnderrun && myAudioQueue->size() > myFragmentBufferSize) { + if (myUnderrun && myAudioQueue->size() > 0) { myUnderrun = false; myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); myFragmentIndex = 0; diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 7b08d4383..b8306b54b 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -72,7 +72,7 @@ namespace { constexpr uInt8 YSTART_EXTRA = 2; - constexpr uInt8 AUDIO_QUEUE_CAPACITY_FRAGMENTS = 20; + constexpr uInt8 AUDIO_QUEUE_CAPACITY_FRAGMENTS = 30; constexpr uInt8 AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT = 1; } @@ -84,7 +84,6 @@ Console::Console(OSystem& osystem, unique_ptr& cart, myProperties(props), myCart(std::move(cart)), myDisplayFormat(""), // Unknown TV format @ start - myFramerate(0.0), // Unknown framerate @ start myCurrentFormat(0), // Unknown format @ start, myAutodetectedYstart(0), myUserPaletteDefined(false), @@ -149,7 +148,6 @@ Console::Console(OSystem& osystem, unique_ptr& cart, // Note that this can be overridden if a format is forced // For example, if a PAL ROM is forced to be NTSC, it will use NTSC-like // properties (60Hz, 262 scanlines, etc), but likely result in flicker - // The TIA will self-adjust the framerate if necessary setTIAProperties(); if(myDisplayFormat == "NTSC") { @@ -551,39 +549,14 @@ FBInitStatus Console::initializeVideo(bool full) } setPalette(myOSystem.settings().getString("palette")); - // Set the correct framerate based on the format of the ROM - // This can be overridden by changing the framerate in the - // VideoDialog box or on the commandline, but it can't be saved - // (ie, framerate is now determined based on number of scanlines). - int framerate = myOSystem.settings().getInt("framerate"); - if(framerate > 0) myFramerate = float(framerate); - myOSystem.setFramerate(myFramerate); - - // Make sure auto-frame calculation is only enabled when necessary - myTIA->enableAutoFrame(framerate <= 0); - return fbstatus; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Console::initializeAudio() { - // Initialize the sound interface. - // The # of channels can be overridden in the AudioDialog box or on - // the commandline, but it can't be saved. - int framerate = myOSystem.settings().getInt("framerate"); - if(framerate > 0) myFramerate = float(framerate); - myOSystem.sound().close(); - - // SND_TODO Communicate channels to TIA - // const string& sound = myProperties.get(Cartridge_Sound); - // myOSystem.sound().setChannels(sound == "STEREO" ? 2 : 1); - myOSystem.sound().open(myAudioQueue); - - // Make sure auto-frame calculation is only enabled when necessary - myTIA->enableAutoFrame(framerate <= 0); } /* Original frying research and code by Fred Quimby. @@ -713,16 +686,11 @@ void Console::setTIAProperties() myDisplayFormat == "SECAM60") { // Assume we've got ~262 scanlines (NTSC-like format) - myFramerate = 60.0; - myConsoleInfo.InitialFrameRate = "60"; myTIA->setLayout(FrameLayout::ntsc); } else { // Assume we've got ~312 scanlines (PAL-like format) - myFramerate = 50.0; - myConsoleInfo.InitialFrameRate = "50"; - // PAL ROMs normally need at least 250 lines if (height != 0) height = std::max(height, 250u); @@ -979,14 +947,6 @@ void Console::generateColorLossPalette() } } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void Console::setFramerate(float framerate) -{ - myFramerate = framerate; - myOSystem.setFramerate(framerate); - // myOSystem.sound().setFrameRate(framerate); -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - float Console::getFramerate() const { diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index cd6daab86..d3b1082f8 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -50,7 +50,6 @@ struct ConsoleInfo string Control0; string Control1; string DisplayFormat; - string InitialFrameRate; }; /** @@ -263,14 +262,7 @@ class Console : public Serializable void changeHeight(int direction); /** - Sets the framerate of the console, which in turn communicates - this to all applicable subsystems. - */ - void setFramerate(float framerate); - - /** - Returns the framerate based on a number of factors - (whether 'framerate' is set, what display format is in use, etc) + Returns the current framerate. */ float getFramerate() const; @@ -405,9 +397,6 @@ class Console : public Serializable // The currently defined display format (NTSC/PAL/SECAM) string myDisplayFormat; - // The currently defined display framerate - float myFramerate; - // Display format currently in use uInt32 myCurrentFormat; @@ -424,6 +413,8 @@ class Console : public Serializable // Contains timing information for this console ConsoleTiming myConsoleTiming; + uInt32 myFramerate; + // Table of RGB values for NTSC, PAL and SECAM static uInt32 ourNTSCPalette[256]; static uInt32 ourPALPalette[256]; diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 1876559a9..3f84a39d7 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -721,8 +721,7 @@ void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight) myDesktopSize.w, myDesktopSize.h); // Aspect ratio - bool ntsc = myOSystem.console().about().InitialFrameRate == "60"; - uInt32 aspect = myOSystem.settings().getInt(ntsc ? + uInt32 aspect = myOSystem.settings().getInt(myOSystem.console().timing() == ConsoleTiming::ntsc ? "tia.aspectn" : "tia.aspectp"); // Figure our the smallest zoom level we can use diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 9b9206cff..26ba9ed9d 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -17,11 +17,6 @@ #include -#include -#ifdef HAVE_GETTIMEOFDAY - #include -#endif - #include "bspf.hxx" #include "MediaFactory.hxx" @@ -67,9 +62,6 @@ OSystem::OSystem() : myLauncherUsed(false), myQuitLoop(false) { - // Calculate startup time - myMillisAtStart = uInt32(time(nullptr) * 1000); - // Get built-in features #ifdef SOUND_SUPPORT myFeatures += "Sound "; @@ -237,16 +229,6 @@ void OSystem::setConfigFile(const string& file) myConfigFile = FilesystemNode(file).getPath(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void OSystem::setFramerate(float framerate) -{ - if(framerate > 0.0) - { - myDisplayFrameRate = framerate; - myTimePerFrame = uInt32(1000000.0 / myDisplayFrameRate); - } -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FBInitStatus OSystem::createFrameBuffer() { @@ -362,9 +344,6 @@ string OSystem::createConsole(const FilesystemNode& rom, const string& md5sum, << getROMInfo(*myConsole) << endl; logMessage(buf.str(), 1); - // Update the timing info for a new console run - resetLoopTiming(); - myFrameBuffer->setCursorState(); // Also check if certain virtual buttons should be held down @@ -404,8 +383,6 @@ bool OSystem::createLauncher(const string& startdir) myLauncher->reStack(); myFrameBuffer->setCursorState(); - setFramerate(30); - resetLoopTiming(); status = true; } else @@ -575,15 +552,6 @@ string OSystem::getROMInfo(const Console& console) return buf.str(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void OSystem::resetLoopTiming() -{ - myTimingInfo.start = myTimingInfo.virt = getTicks(); - myTimingInfo.current = 0; - myTimingInfo.totalTime = 0; - myTimingInfo.totalFrames = 0; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void OSystem::validatePath(string& path, const string& setting, const string& defaultpath) @@ -601,19 +569,13 @@ void OSystem::validatePath(string& path, const string& setting, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt64 OSystem::getTicks() const { -#ifdef HAVE_GETTIMEOFDAY - // Gettimeofday natively refers to the UNIX epoch (a set time in the past) - timeval now; - gettimeofday(&now, nullptr); + return duration_cast > >(system_clock::now().time_since_epoch()).count(); +} - return uInt64(now.tv_sec) * 1000000 + now.tv_usec; -#else - // We use SDL_GetTicks, but add in the time when the application was - // initialized. This is necessary, since SDL_GetTicks only measures how - // long SDL has been running, which can be the same between multiple runs - // of the application. - return uInt64(SDL_GetTicks() + myMillisAtStart) * 1000; -#endif +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +float OSystem::frameRate() const +{ + return myConsole ? myConsole->getFramerate() : 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -625,7 +587,7 @@ void OSystem::mainLoop() for(;;) { - myEventHandler->poll(myTimingInfo.start); + myEventHandler->poll(getTicks()); if(myQuitLoop) break; // Exit if the user wants to quit Int64 cycles = myFrameBuffer->update(); @@ -646,8 +608,6 @@ void OSystem::mainLoop() } else std::this_thread::sleep_until(virtualTime); } - - myTimingInfo.totalFrames++; } // Cleanup time diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 24d690e59..04a733481 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -44,14 +44,6 @@ class VideoDialog; #include "EventHandlerConstants.hxx" #include "bspf.hxx" -struct TimingInfo { - uInt64 start; - uInt64 current; - uInt64 virt; - uInt64 totalTime; - uInt64 totalFrames; -}; - /** This class provides an interface for accessing operating system specific functions. It also comprises an overall parent object, to which all the @@ -207,26 +199,11 @@ class OSystem CheatManager& cheat() const { return *myCheatManager; } #endif - /** - Set the framerate for the video system. It's placed in this class since - the mainLoop() method is defined here. - - @param framerate The video framerate to use - */ - virtual void setFramerate(float framerate); - /** Set all config file paths for the OSystem. */ void setConfigPaths(); - /** - Get the current framerate for the video system. - - @return The video framerate currently in use - */ - float frameRate() const { return myDisplayFrameRate; } - /** Return the default full/complete directory name for storing data. */ @@ -377,12 +354,6 @@ class OSystem */ const string& logMessages() const { return myLogMessages; } - /** - Return timing information (start time of console, current - number of frames rendered, etc. - */ - const TimingInfo& timingInfo() const { return myTimingInfo; } - public: ////////////////////////////////////////////////////////////////////// // The following methods are system-specific and can be overrided in @@ -400,6 +371,8 @@ class OSystem */ virtual uInt64 getTicks() const; + float frameRate() const; + /** This method runs the main loop. Since different platforms may use different timing methods and/or algorithms, this method can @@ -492,15 +465,6 @@ class OSystem // The list of log messages string myLogMessages; - // Number of times per second to iterate through the main loop - float myDisplayFrameRate; - - // Time per frame for a video update, based on the current framerate - uInt32 myTimePerFrame; - - // The time (in milliseconds) from the UNIX epoch when the application starts - uInt32 myMillisAtStart; - // Indicates whether to stop the main loop bool myQuitLoop; @@ -523,9 +487,6 @@ class OSystem string myFeatures; string myBuildInfo; - // Indicates whether the main processing loop should proceed - TimingInfo myTimingInfo; - private: /** Creates the various framebuffers/renderers available in this system. @@ -577,12 +538,6 @@ class OSystem */ string getROMInfo(const Console& console); - /** - Initializes the timing so that the mainloop is reset to its - initial values. - */ - void resetLoopTiming(); - /** Validate the directory name, and create it if necessary. Also, update the settings with the new name. For now, validation diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 3d8363a68..e58e4dcc5 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -144,7 +144,6 @@ void TIA::reset() myCollisionMask = 0; myLinesSinceChange = 0; myCollisionUpdateRequired = false; - myAutoFrameEnabled = false; myColorLossEnabled = myColorLossActive = false; myColorHBlank = 0; myLastCycle = 0; @@ -190,7 +189,6 @@ void TIA::reset() void TIA::frameReset() { memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight); - myAutoFrameEnabled = mySettings.getInt("framerate") <= 0; enableColorLoss(mySettings.getBool("dev.settings") ? "dev.colorloss" : "plr.colorloss"); } @@ -277,8 +275,6 @@ bool TIA::save(Serializer& out) const out.putDouble(myTimestamp); - out.putBool(myAutoFrameEnabled); - out.putByteArray(myShadowRegisters, 64); out.putLong(myCyclesAtFrameStart); @@ -347,8 +343,6 @@ bool TIA::load(Serializer& in) myTimestamp = in.getDouble(); - myAutoFrameEnabled = in.getBool(); - in.getByteArray(myShadowRegisters, 64); myCyclesAtFrameStart = in.getLong(); @@ -1157,10 +1151,6 @@ void TIA::onFrameComplete() const Int32 missingScanlines = myFrameManager->missingScanlines(); if (missingScanlines > 0) memset(myFramebuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160); - - // Recalculate framerate, attempting to auto-correct for scanline 'jumps' - if(myAutoFrameEnabled) - myConsole.setFramerate(myFrameManager->frameRate()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 7cbf94872..9ab8350c6 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -229,14 +229,6 @@ class TIA : public Device */ ConsoleTiming consoleTiming() const { return myConsole.timing(); } - /** - Enables/disables auto-frame calculation. If enabled, the TIA - re-adjusts the framerate at regular intervals. - - @param enabled Whether to enable or disable all auto-frame calculation - */ - void enableAutoFrame(bool enabled) { myAutoFrameEnabled = enabled; } - float frameRate() const { return myFrameManager ? myFrameManager->frameRate() : 0; } /** @@ -760,11 +752,6 @@ class TIA : public Device */ uInt8 myShadowRegisters[64]; - /** - * Automatic framerate correction based on number of scanlines. - */ - bool myAutoFrameEnabled; - /** * Indicates if color loss should be enabled or disabled. Color loss * occurs on PAL-like systems when the previous frame contains an odd diff --git a/src/gui/VideoDialog.cxx b/src/gui/VideoDialog.cxx index 007e663ec..0ef087592 100644 --- a/src/gui/VideoDialog.cxx +++ b/src/gui/VideoDialog.cxx @@ -410,9 +410,7 @@ void VideoDialog::saveConfig() instance().settings().setValue("framerate", f); if(instance().hasConsole()) { - // Make sure auto-frame calculation is only enabled when necessary - instance().console().tia().enableAutoFrame(f <= 0); - instance().console().setFramerate(float(f)); + // instance().console().setFramerate(float(f)); } // Fullscreen From 803b85343a0e705bc2fbde85335028a3114cafde Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 3 Feb 2018 01:23:19 +0100 Subject: [PATCH 20/77] Tuning. --- src/common/SoundSDL2.cxx | 12 ++++++++++++ src/common/SoundSDL2.hxx | 4 ++++ src/emucore/Console.cxx | 5 +++-- src/emucore/OSystem.cxx | 2 +- src/emucore/Sound.hxx | 10 ++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index cefcdfa67..1116d6b48 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -219,6 +219,18 @@ void SoundSDL2::adjustVolume(Int8 direction) myOSystem.frameBuffer().showMessage(message); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 SoundSDL2::getFragmentSize() const +{ + return myHardwareSpec.size; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 SoundSDL2::getSampleRate() const +{ + return myHardwareSpec.freq; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::processFragment(Int16* stream, uInt32 length) { diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 49217a0ed..873ab6032 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -96,6 +96,10 @@ class SoundSDL2 : public Sound */ void adjustVolume(Int8 direction) override; + uInt32 getFragmentSize() const override; + + uInt32 getSampleRate() const override; + protected: /** Invoked by the sound callback to process the next sound fragment. diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index b8306b54b..bece24a71 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -72,7 +72,6 @@ namespace { constexpr uInt8 YSTART_EXTRA = 2; - constexpr uInt8 AUDIO_QUEUE_CAPACITY_FRAGMENTS = 30; constexpr uInt8 AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT = 1; } @@ -722,9 +721,11 @@ void Console::createAudioQueue() throw runtime_error("invalid console timing"); } + uInt32 queueSize = (2 * myOSystem.sound().getFragmentSize() * sampleRate) / (myOSystem.sound().getSampleRate() * fragmentSize); + myAudioQueue = make_shared( fragmentSize, - AUDIO_QUEUE_CAPACITY_FRAGMENTS, + queueSize > 0 ? queueSize : 1, myProperties.get(Cartridge_Sound) == "STEREO", sampleRate ); diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 26ba9ed9d..93d21928e 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -600,7 +600,7 @@ void OSystem::mainLoop() virtualTime += duration_cast(timeslice); time_point now = high_resolution_clock::now(); - if (duration_cast>(now - virtualTime).count() > 0.5) + if (duration_cast>(now - virtualTime).count() > 0) virtualTime = now; else if (virtualTime > now) { if (busyWait && cycles >= 0) { diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index 67b3de1af..a8c895f64 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -66,6 +66,16 @@ class Sound */ virtual void mute(bool state) = 0; + /** + Get the fragment size. + */ + virtual uInt32 getFragmentSize() const = 0; + + /** + Get the sample rate. + */ + virtual uInt32 getSampleRate() const = 0; + /** Reset the sound device. */ From c25e7a6b35e56ec875dadc2f724463c17b1e2762 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 12 Feb 2018 23:55:14 +0100 Subject: [PATCH 21/77] Compile fix. --- src/gui/Dialog.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/Dialog.cxx b/src/gui/Dialog.cxx index 8bba62a5f..073748367 100644 --- a/src/gui/Dialog.cxx +++ b/src/gui/Dialog.cxx @@ -791,7 +791,7 @@ bool Dialog::getResizableBounds(uInt32& w, uInt32& h) const { const GUI::Rect& r = instance().frameBuffer().imageRect(); - bool ntsc = instance().console().about().InitialFrameRate == "60"; + bool ntsc = instance().console().timing() == ConsoleTiming::ntsc; uInt32 aspect = instance().settings().getInt(ntsc ?"tia.aspectn" : "tia.aspectp"); if(r.width() <= FrameBuffer::kFBMinW || r.height() <= FrameBuffer::kFBMinH) From fbbfa222a0076b937b0988244ae4e4259b3285bb Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Tue, 13 Feb 2018 23:08:20 +0100 Subject: [PATCH 22/77] Fix a detail in audio block simulation -> doctor sounds correct --- src/emucore/tia/AudioChannel.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emucore/tia/AudioChannel.cxx b/src/emucore/tia/AudioChannel.cxx index 81b16d020..0d344c846 100644 --- a/src/emucore/tia/AudioChannel.cxx +++ b/src/emucore/tia/AudioChannel.cxx @@ -48,7 +48,7 @@ void AudioChannel::phase0() break; case 0x03: - myPulseCounterHold = myNoiseCounterBit4; + myPulseCounterHold = !myNoiseCounterBit4; break; } From 9079d77de08cbdcf0bbfd2dfe7fad7f21d974bd3 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 2 May 2018 21:30:38 +0200 Subject: [PATCH 23/77] Cosmetic changes, add audio todo. --- TODO_audio.md | 22 ++++++++++++++++++++++ src/common/AudioQueue.cxx | 2 +- src/emucore/Console.cxx | 10 ++++++---- src/emucore/tia/Audio.cxx | 5 ++++- 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 TODO_audio.md diff --git a/TODO_audio.md b/TODO_audio.md new file mode 100644 index 000000000..d5e9481c1 --- /dev/null +++ b/TODO_audio.md @@ -0,0 +1,22 @@ +# Debugging + + * Log and check how queue fills + +# Bugs + + * Fix timeslice size + +# Refactoring + + * Move timing related constants and logic to separate class + +# Verify + + * Verify that the base unit for chrono is seconds + * Verify that FPS are still measured correctly + +# Missing features + + * Reimplement target FPS mode + * Implement Lanzcos resampling + * Fixup OpenGL sync diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 8865a49bf..e05407245 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -47,7 +47,7 @@ AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt1 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AudioQueue::~AudioQueue() { - delete[]myFragmentBuffer; + delete[] myFragmentBuffer; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index bece24a71..d42138580 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -179,9 +179,6 @@ Console::Console(OSystem& osystem, unique_ptr& cart, myConsoleTiming = ConsoleTiming::secam; } - createAudioQueue(); - myTIA->setAudioQueue(myAudioQueue); - bool joyallow4 = myOSystem.settings().getBool("joyallow4"); myOSystem.eventHandler().allowAllDirections(joyallow4); @@ -555,6 +552,10 @@ FBInitStatus Console::initializeVideo(bool full) void Console::initializeAudio() { myOSystem.sound().close(); + + createAudioQueue(); + myTIA->setAudioQueue(myAudioQueue); + myOSystem.sound().open(myAudioQueue); } @@ -721,7 +722,8 @@ void Console::createAudioQueue() throw runtime_error("invalid console timing"); } - uInt32 queueSize = (2 * myOSystem.sound().getFragmentSize() * sampleRate) / (myOSystem.sound().getSampleRate() * fragmentSize); + uInt32 queueSize = + (2 * myOSystem.sound().getFragmentSize() * sampleRate) / (fragmentSize * myOSystem.sound().getSampleRate()); myAudioQueue = make_shared( fragmentSize, diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx index e2620879c..19e06ac2f 100644 --- a/src/emucore/tia/Audio.cxx +++ b/src/emucore/tia/Audio.cxx @@ -38,7 +38,7 @@ Audio::Audio() : myAudioQueue(0), myCurrentFragment(0) { - for (uInt8 i = 0; i <= 0x1e; i++) myMixingTableSum[i]= mixingTableEntry(i, 0x1e); + for (uInt8 i = 0; i <= 0x1e; i++) myMixingTableSum[i] = mixingTableEntry(i, 0x1e); for (uInt8 i = 0; i <= 0x0f; i++) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f); reset(); @@ -130,6 +130,9 @@ bool Audio::save(Serializer& out) const out.putInt(myCounter); + // The queue starts out pristine after loading, so we don't need to save + // any other parts of our state + if (!myChannel0.save(out)) return false; if (!myChannel1.save(out)) return false; } From d2c930886bfe25649e50bf705927401b10f2f0e9 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 5 May 2018 00:47:48 +0200 Subject: [PATCH 24/77] Sanitize and match emulation timing -> no more perceivable audio latency -> fewer underruns --- TODO_audio.md | 19 ++---- src/common/AudioQueue.cxx | 8 +-- src/common/AudioQueue.hxx | 10 ++-- src/common/SoundNull.hxx | 4 +- src/common/SoundSDL2.cxx | 11 +++- src/common/SoundSDL2.hxx | 5 +- src/debugger/Debugger.cxx | 2 +- src/emucore/Console.cxx | 33 +++------- src/emucore/Console.hxx | 10 +++- src/emucore/EmulationTiming.cxx | 103 ++++++++++++++++++++++++++++++++ src/emucore/EmulationTiming.hxx | 57 ++++++++++++++++++ src/emucore/FrameBuffer.cxx | 4 +- src/emucore/FrameBuffer.hxx | 2 +- src/emucore/M6502.cxx | 8 ++- src/emucore/M6502.hxx | 8 ++- src/emucore/OSystem.cxx | 19 ++++-- src/emucore/Sound.hxx | 3 +- src/emucore/module.mk | 1 + src/emucore/tia/TIA.cxx | 4 +- src/emucore/tia/TIA.hxx | 2 +- 20 files changed, 240 insertions(+), 73 deletions(-) create mode 100644 src/emucore/EmulationTiming.cxx create mode 100644 src/emucore/EmulationTiming.hxx diff --git a/TODO_audio.md b/TODO_audio.md index d5e9481c1..4e9d3a4f8 100644 --- a/TODO_audio.md +++ b/TODO_audio.md @@ -1,22 +1,13 @@ -# Debugging - - * Log and check how queue fills - -# Bugs - - * Fix timeslice size - -# Refactoring - - * Move timing related constants and logic to separate class - # Verify - * Verify that the base unit for chrono is seconds * Verify that FPS are still measured correctly # Missing features * Reimplement target FPS mode * Implement Lanzcos resampling - * Fixup OpenGL sync + * Fixup OpenGL sync, ensure that FB only rerenders after a frame has been generated + +# Cleanup + + * Document EmulationTiming diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index e05407245..6075e9a33 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -21,7 +21,7 @@ using std::mutex; using std::lock_guard; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate) +AudioQueue::AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate) : myFragmentSize(fragmentSize), myIsStereo(isStereo), mySampleRate(sampleRate), @@ -34,7 +34,7 @@ AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt1 myFragmentBuffer = new Int16[myFragmentSize * sampleSize * (capacity + 2)]; - for (uInt8 i = 0; i < capacity; i++) + for (uInt32 i = 0; i < capacity; i++) myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer + i * sampleSize * myFragmentSize; myAllFragments[capacity] = myFirstFragmentForEnqueue = @@ -51,13 +51,13 @@ AudioQueue::~AudioQueue() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 AudioQueue::capacity() const +uInt32 AudioQueue::capacity() const { return myFragmentQueue.size(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 AudioQueue::size() +uInt32 AudioQueue::size() { lock_guard guard(myMutex); diff --git a/src/common/AudioQueue.hxx b/src/common/AudioQueue.hxx index 6b25d9bd6..66901ac09 100644 --- a/src/common/AudioQueue.hxx +++ b/src/common/AudioQueue.hxx @@ -45,7 +45,7 @@ class AudioQueue @param isStereo Whether samples are stereo or mono. @param sampleRate The sample rate. This is not used, but can be queried. */ - AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate); + AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate); /** We need a destructor to deallocate the individual fragment buffers. @@ -55,12 +55,12 @@ class AudioQueue /** Capacity getter. */ - uInt8 capacity() const; + uInt32 capacity() const; /** Size getter. */ - uInt8 size(); + uInt32 size(); /** Stereo / mono getter. @@ -122,10 +122,10 @@ class AudioQueue Int16* myFragmentBuffer; // The nubmer if queued fragments - uInt8 mySize; + uInt32 mySize; // The next fragment. - uInt8 myNextFragment; + uInt32 myNextFragment; // We need a mutex for thread safety. std::mutex myMutex; diff --git a/src/common/SoundNull.hxx b/src/common/SoundNull.hxx index 76962a19c..21560c589 100644 --- a/src/common/SoundNull.hxx +++ b/src/common/SoundNull.hxx @@ -21,6 +21,8 @@ #include "bspf.hxx" #include "Sound.hxx" #include "OSystem.hxx" +#include "AudioQueue.hxx" +#include "EmulationTiming.hxx" /** This class implements a Null sound object, where-by sound generation @@ -57,7 +59,7 @@ class SoundNull : public Sound Initializes the sound device. This must be called before any calls are made to derived methods. */ - void open() override { } + void open(shared_ptr, EmulationTiming*) override { } /** Should be called to close the sound device. Once called the sound diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 0d33e3628..9bf8902ba 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -29,6 +29,7 @@ #include "Console.hxx" #include "SoundSDL2.hxx" #include "AudioQueue.hxx" +#include "EmulationTiming.hxx" namespace { inline Int16 applyVolume(Int16 sample, Int32 volumeFactor) @@ -116,8 +117,10 @@ void SoundSDL2::setEnabled(bool state) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL2::open(shared_ptr audioQueue) +void SoundSDL2::open(shared_ptr audioQueue, EmulationTiming* emulationTiming) { + this->emulationTiming = emulationTiming; + myOSystem.logMessage("SoundSDL2::open started ...", 2); mute(true); @@ -241,7 +244,7 @@ uInt32 SoundSDL2::getSampleRate() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::processFragment(Int16* stream, uInt32 length) { - if (myUnderrun && myAudioQueue->size() > 0) { + if (myUnderrun && myAudioQueue->size() > emulationTiming->prebufferFragmentCount()) { myUnderrun = false; myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); myFragmentIndex = 0; @@ -273,8 +276,10 @@ void SoundSDL2::processFragment(Int16* stream, uInt32 length) Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment); if (nextFragment) myCurrentFragment = nextFragment; - else + else { myUnderrun = true; + (cout << "audio underrun!\n").flush(); + } } if (isStereo) { diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 873ab6032..12a878640 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -22,6 +22,7 @@ class OSystem; class AudioQueue; +class EmulationTiming; #include "SDL_lib.hxx" @@ -59,7 +60,7 @@ class SoundSDL2 : public Sound Initializes the sound device. This must be called before any calls are made to derived methods. */ - void open(shared_ptr audioQueue) override; + void open(shared_ptr audioQueue, EmulationTiming* emulationTiming) override; /** Should be called to close the sound device. Once called the sound @@ -124,6 +125,8 @@ class SoundSDL2 : public Sound shared_ptr myAudioQueue; + EmulationTiming* emulationTiming; + Int16* myCurrentFragment; uInt32 myTimeIndex; uInt32 myFragmentIndex; diff --git a/src/debugger/Debugger.cxx b/src/debugger/Debugger.cxx index 65b440469..731cebb82 100644 --- a/src/debugger/Debugger.cxx +++ b/src/debugger/Debugger.cxx @@ -513,7 +513,7 @@ void Debugger::nextFrame(int frames) unlockSystem(); while(frames) { - myOSystem.console().tia().update(); + myOSystem.console().tia().update(myOSystem.console().emulationTiming().maxCyclesPerTimeslice()); --frames; } lockSystem(); diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index d42138580..1901561a3 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -72,7 +72,6 @@ namespace { constexpr uInt8 YSTART_EXTRA = 2; - constexpr uInt8 AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT = 1; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -358,6 +357,7 @@ void Console::toggleFormat(int direction) setTIAProperties(); myTIA->frameReset(); initializeVideo(); // takes care of refreshing the screen + initializeAudio(); // ensure that audio synthesis is set up to match emulation speed myOSystem.frameBuffer().showMessage(message); @@ -556,7 +556,7 @@ void Console::initializeAudio() createAudioQueue(); myTIA->setAudioQueue(myAudioQueue); - myOSystem.sound().open(myAudioQueue); + myOSystem.sound().open(myAudioQueue, &myEmulationTiming); } /* Original frying research and code by Fred Quimby. @@ -699,37 +699,18 @@ void Console::setTIAProperties() myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart); myTIA->setHeight(height); + + myEmulationTiming.updateFrameLayout(myTIA->frameLayout()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Console::createAudioQueue() { - uInt32 fragmentSize, sampleRate; - - switch (myConsoleTiming) { - case ConsoleTiming::ntsc: - fragmentSize = 262 * AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT; - sampleRate = 2 * 262 * 60; - break; - - case ConsoleTiming::pal: - case ConsoleTiming::secam: - fragmentSize = 312 * AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT; - sampleRate = 2 * 312 * 50; - break; - - default: - throw runtime_error("invalid console timing"); - } - - uInt32 queueSize = - (2 * myOSystem.sound().getFragmentSize() * sampleRate) / (fragmentSize * myOSystem.sound().getSampleRate()); - myAudioQueue = make_shared( - fragmentSize, - queueSize > 0 ? queueSize : 1, + myEmulationTiming.audioFragmentSize(), + myEmulationTiming.audioQueueCapacity(myOSystem.sound().getSampleRate(), myOSystem.sound().getFragmentSize()), myProperties.get(Cartridge_Sound) == "STEREO", - sampleRate + myEmulationTiming.audioSampleRate() ); } diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index cd5608ccb..b10740467 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -37,6 +37,7 @@ class AudioQueue; #include "Serializable.hxx" #include "EventHandlerConstants.hxx" #include "NTSCFilter.hxx" +#include "EmulationTiming.hxx" #include "frame-manager/AbstractFrameManager.hxx" /** @@ -190,6 +191,11 @@ class Console : public Serializable */ void stateChanged(EventHandlerState state); + /** + Retrieve emulation timing provider. + */ + EmulationTiming& emulationTiming() { return myEmulationTiming; } + public: /** Toggle between NTSC/PAL/SECAM (and variants) display format. @@ -416,7 +422,9 @@ class Console : public Serializable // Contains timing information for this console ConsoleTiming myConsoleTiming; - uInt32 myFramerate; + // Emulation timing provider. This ties together the timing of the core emulation loop + // and the audio synthesis parameters + EmulationTiming myEmulationTiming; // Table of RGB values for NTSC, PAL and SECAM static uInt32 ourNTSCPalette[256]; diff --git a/src/emucore/EmulationTiming.cxx b/src/emucore/EmulationTiming.cxx new file mode 100644 index 000000000..ccbf6aa46 --- /dev/null +++ b/src/emucore/EmulationTiming.cxx @@ -0,0 +1,103 @@ +//============================================================================ +// +// 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-2018 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 "EmulationTiming.hxx" + +namespace { + constexpr uInt32 AUDIO_HALF_FRAMES_PER_FRAGMENT = 1; + constexpr uInt32 QUEUE_CAPACITY_SAFETY_FACTOR = 2; + constexpr uInt32 PREBUFFER_FRAGMENT_COUNT = 4; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +EmulationTiming::EmulationTiming(FrameLayout frameLayout) : frameLayout(frameLayout) {} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationTiming::updateFrameLayout(FrameLayout frameLayout) { + this->frameLayout = frameLayout; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::maxCyclesPerTimeslice() const { + return (3 * cyclesPerFrame()) / 2; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::minCyclesPerTimeslice() const { + return cyclesPerFrame() / 2; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::linesPerFrame() const { + switch (frameLayout) { + case FrameLayout::ntsc: + return 262; + + case FrameLayout::pal: + return 312; + + default: + throw runtime_error("invalid frame layout"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::cyclesPerFrame() const { + return 76 * linesPerFrame(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::framesPerSecond() const { + switch (frameLayout) { + case FrameLayout::ntsc: + return 60; + + case FrameLayout::pal: + return 50; + + default: + throw runtime_error("invalid frame layout"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::cyclesPerSecond() const { + return cyclesPerFrame() * framesPerSecond(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::audioFragmentSize() const { + return AUDIO_HALF_FRAMES_PER_FRAGMENT * linesPerFrame(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::audioSampleRate() const { + return 2 * linesPerFrame() * framesPerSecond(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::audioQueueCapacity(uInt32 playbackRate, uInt32 playbackFragmentSize) const { + uInt32 capacity = (playbackFragmentSize * audioSampleRate()) / (audioFragmentSize() * playbackRate) + 1; + uInt32 minCapacity = (maxCyclesPerTimeslice() * audioSampleRate()) / (audioFragmentSize() * cyclesPerSecond()) + 1; + + return std::max(prebufferFragmentCount() + 1, QUEUE_CAPACITY_SAFETY_FACTOR * std::max(capacity, minCapacity)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::prebufferFragmentCount() const { + return PREBUFFER_FRAGMENT_COUNT; +} diff --git a/src/emucore/EmulationTiming.hxx b/src/emucore/EmulationTiming.hxx new file mode 100644 index 000000000..b82cd9118 --- /dev/null +++ b/src/emucore/EmulationTiming.hxx @@ -0,0 +1,57 @@ +//============================================================================ +// +// 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-2018 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 EMULATION_TIMING_HXX +#define EMULATION_TIMING_HXX + +#include "bspf.hxx" +#include "FrameLayout.hxx" + +class EmulationTiming { + public: + + EmulationTiming(FrameLayout frameLayout = FrameLayout::ntsc); + + void updateFrameLayout(FrameLayout frameLayout); + + uInt32 maxCyclesPerTimeslice() const; + + uInt32 minCyclesPerTimeslice() const; + + uInt32 linesPerFrame() const; + + uInt32 cyclesPerFrame() const; + + uInt32 framesPerSecond() const; + + uInt32 cyclesPerSecond() const; + + uInt32 audioFragmentSize() const; + + uInt32 audioSampleRate() const; + + uInt32 audioQueueCapacity(uInt32 playbackRate, uInt32 playbackFragmentSize) const; + + uInt32 prebufferFragmentCount() const; + + private: + + FrameLayout frameLayout; + +}; + +#endif // EMULATION_TIMING_HXX diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 4fa8d950c..4db75a9c4 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -258,7 +258,7 @@ FBInitStatus FrameBuffer::createDisplay(const string& title, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int64 FrameBuffer::update() +Int64 FrameBuffer::update(uInt32 maxCycles) { // Determine which mode we are in (from the EventHandler) // Take care of S_EMULATE mode here, otherwise let the GUI @@ -274,7 +274,7 @@ Int64 FrameBuffer::update() // Run the console for one frame // Note that the debugger can cause a breakpoint to occur, which changes // the EventHandler state 'behind our back' - we need to check for that - cycles = myOSystem.console().tia().update(); + cycles = myOSystem.console().tia().update(maxCycles); #ifdef DEBUGGER_SUPPORT if(myOSystem.eventHandler().state() != EventHandlerState::EMULATION) break; #endif diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index 29df6d441..b1806a963 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -116,7 +116,7 @@ class FrameBuffer drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles spent during emulation, or -1 if not applicable. */ - Int64 update(); + Int64 update(uInt32 maxCycles = 50000); /** Shows a message onscreen. diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index 19cf07ed8..e8066aa5c 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -229,7 +229,7 @@ bool M6502::execute(uInt32 number) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -inline bool M6502::_execute(uInt32 number) +inline bool M6502::_execute(uInt32 cycles) { // Clear all of the execution status bits except for the fatal error bit myExecutionStatus &= FatalErrorBit; @@ -239,10 +239,12 @@ inline bool M6502::_execute(uInt32 number) M6532& riot = mySystem->m6532(); #endif + uInt32 currentCycles = 0; + // Loop until execution is stopped or a fatal error occurs for(;;) { - for(; !myExecutionStatus && (number != 0); --number) + for(; !myExecutionStatus && (currentCycles < cycles * SYSTEM_CYCLES_PER_CPU); currentCycles += SYSTEM_CYCLES_PER_CPU) { #ifdef DEBUGGER_SUPPORT if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag) @@ -329,7 +331,7 @@ inline bool M6502::_execute(uInt32 number) } // See if we've executed the specified number of instructions - if(number == 0) + if (currentCycles >= cycles * SYSTEM_CYCLES_PER_CPU) { // Yes, so answer that everything finished fine return true; diff --git a/src/emucore/M6502.hxx b/src/emucore/M6502.hxx index c0512d658..5b96c7342 100644 --- a/src/emucore/M6502.hxx +++ b/src/emucore/M6502.hxx @@ -110,10 +110,12 @@ class M6502 : public Serializable is executed, someone stops execution, or an error occurs. Answers true iff execution stops normally. - @param number Indicates the number of instructions to execute + @param number Indicates the number of cycles to execute. Not that the actual + granularity of the CPU is instructions, so this is only accurate up to + a couple of cycles @return true iff execution stops normally */ - bool execute(uInt32 number); + bool execute(uInt32 cycles); /** Tell the processor to stop executing instructions. Invoking this @@ -318,7 +320,7 @@ class M6502 : public Serializable This is the actual dispatch function that does the grunt work. M6502::execute wraps it and makes sure that any pending halt is processed before returning. */ - bool _execute(uInt32 number); + bool _execute(uInt32 cycles); #ifdef DEBUGGER_SUPPORT /** diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index f9b2ec853..d6c9b1760 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -646,10 +646,21 @@ void OSystem::mainLoop() myEventHandler->poll(getTicks()); if(myQuitLoop) break; // Exit if the user wants to quit - Int64 cycles = myFrameBuffer->update(); + Int64 totalCycles = 0; + const Int64 minCycles = myConsole ? myConsole->emulationTiming().minCyclesPerTimeslice() : 50000; + const Int64 maxCycles = myConsole ? myConsole->emulationTiming().maxCyclesPerTimeslice() : 0; + const uInt32 cyclesPerSecond = myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1; + + do { + Int64 cycles = myFrameBuffer->update(totalCycles > 0 ? minCycles - totalCycles : maxCycles); + if (cycles < 0) break; + + totalCycles += cycles; + } while (totalCycles < minCycles); + duration timeslice ( - (cycles >= 0) ? - static_cast(cycles) / static_cast(76 * ((myConsole->timing() == ConsoleTiming::ntsc) ? (262 * 60) : (312 * 50))) : + (totalCycles > 0) ? + static_cast(totalCycles) / static_cast(cyclesPerSecond) : 1. / 30. ); @@ -659,7 +670,7 @@ void OSystem::mainLoop() if (duration_cast>(now - virtualTime).count() > 0) virtualTime = now; else if (virtualTime > now) { - if (busyWait && cycles >= 0) { + if (busyWait && totalCycles > 0) { while (high_resolution_clock::now() < virtualTime); } else std::this_thread::sleep_until(virtualTime); diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index a8c895f64..763926460 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -20,6 +20,7 @@ class OSystem; class AudioQueue; +class EmulationTiming; #include "bspf.hxx" @@ -51,7 +52,7 @@ class Sound Start the sound system, initializing it if necessary. This must be called before any calls are made to derived methods. */ - virtual void open(shared_ptr audioQueue) = 0; + virtual void open(shared_ptr, EmulationTiming*) = 0; /** Should be called to stop the sound system. Once called the sound diff --git a/src/emucore/module.mk b/src/emucore/module.mk index 3c909aa48..37325597e 100644 --- a/src/emucore/module.mk +++ b/src/emucore/module.mk @@ -53,6 +53,7 @@ MODULE_OBJS := \ src/emucore/Control.o \ src/emucore/Driving.o \ src/emucore/EventHandler.o \ + src/emucore/EmulationTiming.o \ src/emucore/FrameBuffer.o \ src/emucore/FBSurface.o \ src/emucore/FSNode.o \ diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 77cf0df49..84dbdf76e 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -805,11 +805,11 @@ bool TIA::loadDisplay(Serializer& in) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt64 TIA::update() +uInt64 TIA::update(uInt32 maxCycles) { uInt64 timestampOld = myTimestamp; - mySystem->m6502().execute(25000); + mySystem->m6502().execute(maxCycles); updateEmulation(); return (myTimestamp - timestampOld) / 3; diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 9dd86ff56..03b1161b8 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -199,7 +199,7 @@ class TIA : public Device desired frame rate to update the TIA. Invoking this method will update the graphics buffer and generate the corresponding audio samples. */ - uInt64 update(); + uInt64 update(uInt32 maxCycles = 50000); /** Returns a pointer to the internal frame buffer. From a58db7e62d4cafc2bded9744106200604dd9b7b4 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 5 May 2018 01:08:09 +0200 Subject: [PATCH 25/77] Make sure that all audio samples are generated during each timeslice. --- src/emucore/M6502.cxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index e8066aa5c..6005fcaad 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -218,12 +218,13 @@ bool M6502::execute(uInt32 number) // the halt to take effect). This is safe because as we know that the next cycle will be a read // cycle anyway. handleHalt(); +#endif // Make sure that the hardware state matches the current system clock. This is necessary - // to maintain a consistent state for the debugger after stepping. + // to maintain a consistent state for the debugger after stepping and to make sure + // that audio samples are generated for the whole timeslice. mySystem->tia().updateEmulation(); mySystem->m6532().updateEmulation(); -#endif return status; } From 6cc8a22978176b73d0a70ac3c1279939397decfd Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 6 May 2018 23:45:21 +0200 Subject: [PATCH 26/77] More timing tuning, coding style. --- src/emucore/Console.cxx | 5 ++- src/emucore/EmulationTiming.cxx | 63 +++++++++++++++++++++++---------- src/emucore/EmulationTiming.hxx | 12 +++++-- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 1901561a3..66b084603 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -553,6 +553,9 @@ void Console::initializeAudio() { myOSystem.sound().close(); + myEmulationTiming.updatePlaybackPeriod(myOSystem.sound().getSampleRate()); + myEmulationTiming.updatePlaybackPeriod(myOSystem.sound().getFragmentSize()); + createAudioQueue(); myTIA->setAudioQueue(myAudioQueue); @@ -708,7 +711,7 @@ void Console::createAudioQueue() { myAudioQueue = make_shared( myEmulationTiming.audioFragmentSize(), - myEmulationTiming.audioQueueCapacity(myOSystem.sound().getSampleRate(), myOSystem.sound().getFragmentSize()), + myEmulationTiming.audioQueueCapacity(), myProperties.get(Cartridge_Sound) == "STEREO", myEmulationTiming.audioSampleRate() ); diff --git a/src/emucore/EmulationTiming.cxx b/src/emucore/EmulationTiming.cxx index ccbf6aa46..c812bb36b 100644 --- a/src/emucore/EmulationTiming.cxx +++ b/src/emucore/EmulationTiming.cxx @@ -24,26 +24,46 @@ namespace { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -EmulationTiming::EmulationTiming(FrameLayout frameLayout) : frameLayout(frameLayout) {} +EmulationTiming::EmulationTiming(FrameLayout frameLayout) : + myFrameLayout(frameLayout), + myPlaybackRate(44100), + myPlaybackPeriod(512) +{} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EmulationTiming::updateFrameLayout(FrameLayout frameLayout) { - this->frameLayout = frameLayout; +void EmulationTiming::updateFrameLayout(FrameLayout frameLayout) +{ + myFrameLayout = frameLayout; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::maxCyclesPerTimeslice() const { - return (3 * cyclesPerFrame()) / 2; +void EmulationTiming::updatePlaybackRate(uInt32 playbackRate) +{ + myPlaybackRate = playbackRate; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::minCyclesPerTimeslice() const { +void EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod) +{ + myPlaybackPeriod = playbackPeriod; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::maxCyclesPerTimeslice() const +{ + return 2 * cyclesPerFrame(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 EmulationTiming::minCyclesPerTimeslice() const +{ return cyclesPerFrame() / 2; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::linesPerFrame() const { - switch (frameLayout) { +uInt32 EmulationTiming::linesPerFrame() const +{ + switch (myFrameLayout) { case FrameLayout::ntsc: return 262; @@ -56,13 +76,15 @@ uInt32 EmulationTiming::linesPerFrame() const { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::cyclesPerFrame() const { +uInt32 EmulationTiming::cyclesPerFrame() const +{ return 76 * linesPerFrame(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::framesPerSecond() const { - switch (frameLayout) { +uInt32 EmulationTiming::framesPerSecond() const +{ + switch (myFrameLayout) { case FrameLayout::ntsc: return 60; @@ -75,29 +97,34 @@ uInt32 EmulationTiming::framesPerSecond() const { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::cyclesPerSecond() const { +uInt32 EmulationTiming::cyclesPerSecond() const +{ return cyclesPerFrame() * framesPerSecond(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::audioFragmentSize() const { +uInt32 EmulationTiming::audioFragmentSize() const +{ return AUDIO_HALF_FRAMES_PER_FRAGMENT * linesPerFrame(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::audioSampleRate() const { +uInt32 EmulationTiming::audioSampleRate() const +{ return 2 * linesPerFrame() * framesPerSecond(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::audioQueueCapacity(uInt32 playbackRate, uInt32 playbackFragmentSize) const { - uInt32 capacity = (playbackFragmentSize * audioSampleRate()) / (audioFragmentSize() * playbackRate) + 1; +uInt32 EmulationTiming::audioQueueCapacity() const +{ + uInt32 capacity = (myPlaybackPeriod * audioSampleRate()) / (audioFragmentSize() * myPlaybackRate) + 1; uInt32 minCapacity = (maxCyclesPerTimeslice() * audioSampleRate()) / (audioFragmentSize() * cyclesPerSecond()) + 1; return std::max(prebufferFragmentCount() + 1, QUEUE_CAPACITY_SAFETY_FACTOR * std::max(capacity, minCapacity)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 EmulationTiming::prebufferFragmentCount() const { - return PREBUFFER_FRAGMENT_COUNT; +uInt32 EmulationTiming::prebufferFragmentCount() const +{ + return (myPlaybackPeriod * audioSampleRate()) / (audioFragmentSize() * myPlaybackRate) + PREBUFFER_FRAGMENT_COUNT; } diff --git a/src/emucore/EmulationTiming.hxx b/src/emucore/EmulationTiming.hxx index b82cd9118..a18acc6b0 100644 --- a/src/emucore/EmulationTiming.hxx +++ b/src/emucore/EmulationTiming.hxx @@ -28,6 +28,10 @@ class EmulationTiming { void updateFrameLayout(FrameLayout frameLayout); + void updatePlaybackRate(uInt32 playbackRate); + + void updatePlaybackPeriod(uInt32 period); + uInt32 maxCyclesPerTimeslice() const; uInt32 minCyclesPerTimeslice() const; @@ -44,13 +48,17 @@ class EmulationTiming { uInt32 audioSampleRate() const; - uInt32 audioQueueCapacity(uInt32 playbackRate, uInt32 playbackFragmentSize) const; + uInt32 audioQueueCapacity() const; uInt32 prebufferFragmentCount() const; private: - FrameLayout frameLayout; + FrameLayout myFrameLayout; + + uInt32 myPlaybackRate; + + uInt32 myPlaybackPeriod; }; From 068dcad4a4d0ba6e81d8bad71f5360b1216be381 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 6 May 2018 23:50:52 +0200 Subject: [PATCH 27/77] Remove hardcoded limit on sample rate. --- src/common/SoundSDL2.cxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 9bf8902ba..84e2f7d62 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -62,8 +62,7 @@ SoundSDL2::SoundSDL2(OSystem& osystem) // This fixes a bug most prevalent with ATI video cards in Windows, // whereby sound stopped working after the first video change SDL_AudioSpec desired; - // desired.freq = myOSystem.settings().getInt("freq"); - desired.freq = 48000; + desired.freq = myOSystem.settings().getInt("freq"); desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = myOSystem.settings().getInt("fragsize"); From f3b7245ce4d00ca864b3871077af9a276614ee0c Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 6 May 2018 23:51:43 +0200 Subject: [PATCH 28/77] FPS measurement seems to be OK. --- TODO_audio.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/TODO_audio.md b/TODO_audio.md index 4e9d3a4f8..6df170ff8 100644 --- a/TODO_audio.md +++ b/TODO_audio.md @@ -1,7 +1,3 @@ -# Verify - - * Verify that FPS are still measured correctly - # Missing features * Reimplement target FPS mode From ec83fdd158083d38e7591193ad5f60452b0f6534 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 7 May 2018 00:06:53 +0200 Subject: [PATCH 29/77] Update XCode build --- src/macosx/stella.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index 9766500da..2ba4e8fdf 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -622,6 +622,8 @@ E0306E0F1F93E916003DDD52 /* JitterEmulation.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E091F93E915003DDD52 /* JitterEmulation.cxx */; }; E0306E101F93E916003DDD52 /* FrameLayoutDetector.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */; }; E0306E111F93E916003DDD52 /* JitterEmulation.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */; }; + E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */; }; + E034A5EF209FB25D00C89E9E /* EmulationTiming.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */; }; E0406FB61F81A85400A82AE0 /* AbstractFrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */; }; E0406FB81F81A85400A82AE0 /* FrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */; }; E09F413B201E901D004A3391 /* AudioQueue.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4139201E901C004A3391 /* AudioQueue.hxx */; }; @@ -1301,6 +1303,8 @@ E0306E091F93E915003DDD52 /* JitterEmulation.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JitterEmulation.cxx; sourceTree = ""; }; E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameLayoutDetector.cxx; sourceTree = ""; }; E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = JitterEmulation.hxx; sourceTree = ""; }; + E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmulationTiming.cxx; sourceTree = ""; }; + E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EmulationTiming.hxx; sourceTree = ""; }; E09F4139201E901C004A3391 /* AudioQueue.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioQueue.hxx; sourceTree = ""; }; E09F413A201E901D004A3391 /* AudioQueue.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioQueue.cxx; sourceTree = ""; }; E09F413D201E904F004A3391 /* Audio.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Audio.hxx; sourceTree = ""; }; @@ -1658,6 +1662,8 @@ 2D6050CC0898776500C6DE89 /* emucore */ = { isa = PBXGroup; children = ( + E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */, + E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */, DC1B2EBE1E50036100F62837 /* AmigaMouse.hxx */, DC1B2EC01E50036100F62837 /* AtariMouse.hxx */, DC487FB40DA5350900E12499 /* AtariVox.cxx */, @@ -2139,6 +2145,7 @@ 2D9173CE09BA90380026E9FF /* Cart2K.hxx in Headers */, 2D9173CF09BA90380026E9FF /* Cart3F.hxx in Headers */, DC3EE86D1E2C0E6D00905161 /* zlib.h in Headers */, + E034A5EF209FB25D00C89E9E /* EmulationTiming.hxx in Headers */, DC3EE86A1E2C0E6D00905161 /* trees.h in Headers */, 2D9173D009BA90380026E9FF /* Cart4K.hxx in Headers */, 2D9173D109BA90380026E9FF /* CartAR.hxx in Headers */, @@ -2561,6 +2568,7 @@ DC3EE8671E2C0E6D00905161 /* inftrees.c in Sources */, 2D91747609BA90380026E9FF /* Cart.cxx in Sources */, 2D91747709BA90380026E9FF /* Cart2K.cxx in Sources */, + E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */, 2D91747809BA90380026E9FF /* Cart3F.cxx in Sources */, 2D91747909BA90380026E9FF /* Cart4K.cxx in Sources */, 2D91747A09BA90380026E9FF /* CartAR.cxx in Sources */, From efbc261128d5b18358f1d2223f3a04e5deec3ffe Mon Sep 17 00:00:00 2001 From: thrust26 Date: Tue, 8 May 2018 16:05:01 +0200 Subject: [PATCH 30/77] fixed missed end tag --- src/windows/Stella.vcxproj.filters | 1 + 1 file changed, 1 insertion(+) diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index f2c059592..2acabdb55 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -893,6 +893,7 @@ Source Files\emucore\tia + Source Files\gui From c1679d6883ee2691b0cf88112aa23af8e4d651aa Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Tue, 8 May 2018 20:00:10 -0230 Subject: [PATCH 31/77] Fixed VS project file; recreated it from the one in master. --- src/windows/Stella.vcxproj | 2 ++ src/windows/Stella.vcxproj.filters | 43 +++++++++++++++++------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index 623958114..0d8270a8f 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -324,6 +324,7 @@ + @@ -623,6 +624,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index 2acabdb55..b5fc3627f 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -885,15 +885,6 @@ Source Files\emucore\tia - - Source Files\emucore - - - Source Files\emucore\tia - - - Source Files\emucore\tia - Source Files\gui @@ -906,6 +897,18 @@ Source Files + + Source Files\emucore\tia + + + Source Files\emucore\tia + + + Source Files + + + Source Files\emucore + @@ -1835,14 +1838,6 @@ Header Files\emucore\tia - - Header Files\emucore - - - Header Files\emucore\tia - - - Header Files\emucore\tia Header Files\gui @@ -1855,6 +1850,18 @@ Header Files + + Header Files\emucore\tia + + + Header Files\emucore\tia + + + Header Files + + + Header Files\emucore + @@ -1867,4 +1874,4 @@ Resource Files - + \ No newline at end of file From 3bac41dd4647241db27af3975005d4e0d798daa3 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 9 May 2018 00:38:01 +0200 Subject: [PATCH 32/77] Factor out resampling. --- .vscode/settings.json | 3 +- Makefile | 3 +- src/common/SoundSDL2.cxx | 85 ++++++++------------------ src/common/SoundSDL2.hxx | 10 ++- src/common/audio/Resampler.hxx | 79 ++++++++++++++++++++++++ src/common/audio/SimpleResampler.cxx | 91 ++++++++++++++++++++++++++++ src/common/audio/SimpleResampler.hxx | 50 +++++++++++++++ src/common/audio/module.mk | 10 +++ src/emucore/EmulationTiming.hxx | 7 +++ 9 files changed, 273 insertions(+), 65 deletions(-) create mode 100644 src/common/audio/Resampler.hxx create mode 100644 src/common/audio/SimpleResampler.cxx create mode 100644 src/common/audio/SimpleResampler.hxx create mode 100644 src/common/audio/module.mk diff --git a/.vscode/settings.json b/.vscode/settings.json index a414c6bf2..790c9dc4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,6 +42,7 @@ "type_traits": "cpp", "stdexcept": "cpp", "fstream": "cpp", - "__locale": "cpp" + "__locale": "cpp", + "__string": "cpp" } } diff --git a/Makefile b/Makefile index 35a1e3606..004612643 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,8 @@ MODULES += \ src/emucore/tia/frame-manager \ src/gui \ src/common \ - src/common/tv_filters + src/common/tv_filters \ + src/common/audio ###################################################################### # The build rules follow - normally you should have no need to diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 84e2f7d62..df43df023 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -30,6 +30,7 @@ #include "SoundSDL2.hxx" #include "AudioQueue.hxx" #include "EmulationTiming.hxx" +#include "audio/SimpleResampler.hxx" namespace { inline Int16 applyVolume(Int16 sample, Int32 volumeFactor) @@ -45,8 +46,7 @@ SoundSDL2::SoundSDL2(OSystem& osystem) myVolume(100), myVolumeFactor(0xffff), myAudioQueue(0), - myCurrentFragment(0), - myFragmentBufferSize(0) + myCurrentFragment(0) { myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); @@ -132,14 +132,6 @@ void SoundSDL2::open(shared_ptr audioQueue, EmulationTiming* emulati myAudioQueue = audioQueue; myUnderrun = true; myCurrentFragment = 0; - myTimeIndex = 0; - myFragmentIndex = 0; - myFragmentBufferSize = static_cast( - ceil( - 1.5 * static_cast(myHardwareSpec.samples) / static_cast(myAudioQueue->fragmentSize()) - * static_cast(myAudioQueue->sampleRate()) / static_cast(myHardwareSpec.freq) - ) - ); // Adjust volume to that defined in settings setVolume(myOSystem.settings().getInt("volume")); @@ -154,6 +146,8 @@ void SoundSDL2::open(shared_ptr audioQueue, EmulationTiming* emulati << endl; myOSystem.logMessage(buf.str(), 1); + initResampler(); + // And start the SDL sound subsystem ... mute(false); @@ -243,62 +237,33 @@ uInt32 SoundSDL2::getSampleRate() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::processFragment(Int16* stream, uInt32 length) { - if (myUnderrun && myAudioQueue->size() > emulationTiming->prebufferFragmentCount()) { - myUnderrun = false; - myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); - myFragmentIndex = 0; - } + myResampler->fillFragment(stream, length); - if (!myCurrentFragment) { - memset(stream, 0, 2 * length); - return; - } + for (uInt32 i = 0; i < length; i++) stream[i] = applyVolume(stream[i], myVolumeFactor); +} - const bool isStereoTIA = myAudioQueue->isStereo(); - const bool isStereo = myHardwareSpec.channels == 2; - const uInt32 sampleRateTIA = myAudioQueue->sampleRate(); - const uInt32 sampleRate = myHardwareSpec.freq; - const uInt32 fragmentSize = myAudioQueue->fragmentSize(); - const uInt32 outputSamples = isStereo ? (length >> 1) : length; +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL2::initResampler() +{ + Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* { + Int16* nextFragment = 0; - for (uInt32 i = 0; i < outputSamples; i++) { - myTimeIndex += sampleRateTIA; + if (myUnderrun) + nextFragment = myAudioQueue->size() > emulationTiming->prebufferFragmentCount() ? myAudioQueue->dequeue(myCurrentFragment) : 0; + else + nextFragment = myAudioQueue->dequeue(myCurrentFragment); - if (myTimeIndex >= sampleRate) { - myFragmentIndex += myTimeIndex / sampleRate; - myTimeIndex %= sampleRate; - } + myUnderrun = nextFragment == 0; + if (nextFragment) myCurrentFragment = nextFragment; - if (myFragmentIndex >= fragmentSize) { - myFragmentIndex %= fragmentSize; + return nextFragment; + }; - Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment); - if (nextFragment) - myCurrentFragment = nextFragment; - else { - myUnderrun = true; - (cout << "audio underrun!\n").flush(); - } - } - - if (isStereo) { - if (isStereoTIA) { - stream[2*i] = applyVolume(myCurrentFragment[2*myFragmentIndex], myVolumeFactor); - stream[2*i + 1] = applyVolume(myCurrentFragment[2*myFragmentIndex + 1], myVolumeFactor); - } else { - stream[2*i] = stream[2*i + 1] = applyVolume(myCurrentFragment[myFragmentIndex], myVolumeFactor); - } - } else { - if (isStereoTIA) { - stream[i] = applyVolume( - (myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2), - myVolumeFactor - ); - } else { - stream[i] = applyVolume(myCurrentFragment[myFragmentIndex], myVolumeFactor); - } - } - } + myResampler = make_unique( + Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()), + Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1), + nextFragmentCallback + ); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 12a878640..9874c1197 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -28,6 +28,7 @@ class EmulationTiming; #include "bspf.hxx" #include "Sound.hxx" +#include "audio/Resampler.hxx" /** This class implements the sound API for SDL. @@ -112,6 +113,10 @@ class SoundSDL2 : public Sound */ void processFragment(Int16* stream, uInt32 length); + private: + + void initResampler(); + private: // Indicates if the sound device was successfully initialized bool myIsInitializedFlag; @@ -128,11 +133,10 @@ class SoundSDL2 : public Sound EmulationTiming* emulationTiming; Int16* myCurrentFragment; - uInt32 myTimeIndex; - uInt32 myFragmentIndex; - uInt32 myFragmentBufferSize; bool myUnderrun; + unique_ptr myResampler; + 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/common/audio/Resampler.hxx b/src/common/audio/Resampler.hxx new file mode 100644 index 000000000..2926272a4 --- /dev/null +++ b/src/common/audio/Resampler.hxx @@ -0,0 +1,79 @@ +//============================================================================ +// +// 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-2018 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 RESAMPLER_HXX +#define RESAMPLER_HXX + +#include + +#include "bspf.hxx" + +class Resampler { + public: + + using NextFragmentCallback = std::function; + + class Format { + public: + + Format(uInt32 sampleRate, uInt32 fragmentSize, bool stereo) : + sampleRate(sampleRate), + fragmentSize(fragmentSize), + stereo(stereo) + {} + + public: + + uInt32 sampleRate; + uInt32 fragmentSize; + bool stereo; + + private: + + Format() = delete; + }; + + public: + + Resampler(Format formatFrom, Format formatTo, NextFragmentCallback nextFragmentCallback) : + myFormatFrom(formatFrom), + myFormatTo(formatTo), + myNextFragmentCallback(nextFragmentCallback) + {} + + virtual void fillFragment(Int16* fragment, uInt32 length) = 0; + + virtual ~Resampler() {} + + protected: + + Format myFormatFrom; + Format myFormatTo; + + NextFragmentCallback myNextFragmentCallback; + + private: + + Resampler() = delete; + Resampler(const Resampler&) = delete; + Resampler(Resampler&&) = delete; + Resampler& operator=(const Resampler&) = delete; + Resampler& operator=(Resampler&&) = delete; + +}; + +#endif // RESAMPLER_HXX diff --git a/src/common/audio/SimpleResampler.cxx b/src/common/audio/SimpleResampler.cxx new file mode 100644 index 000000000..de3b6e535 --- /dev/null +++ b/src/common/audio/SimpleResampler.cxx @@ -0,0 +1,91 @@ +//============================================================================ +// +// 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-2018 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 "SimpleResampler.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SimpleResampler::SimpleResampler( + Resampler::Format formatFrom, + Resampler::Format formatTo, + Resampler::NextFragmentCallback nextFragmentCallback) +: + Resampler(formatFrom, formatTo, nextFragmentCallback), + myCurrentFragment(0), + myTimeIndex(0), + myFragmentIndex(0), + myIsUnderrun(true) +{} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SimpleResampler::fillFragment(Int16* fragment, uInt32 length) +{ + if (myIsUnderrun) { + Int16* nextFragment = myNextFragmentCallback(); + + if (nextFragment) { + myCurrentFragment = nextFragment; + myFragmentIndex = 0; + myIsUnderrun = false; + } + } + + if (!myCurrentFragment) { + memset(fragment, 0, 2 * length); + return; + } + + const uInt32 outputSamples = myFormatTo.stereo ? (length >> 1) : length; + + // For the following math, remember that myTimeIndex = time * myFormatFrom.sampleRate * myFormatTo.sampleRate + for (uInt32 i = 0; i < outputSamples; i++) { + // time += 1 / myFormatTo.sampleRate + myTimeIndex += myFormatFrom.sampleRate; + + // time >= 1 / myFormatFrom.sampleRate + if (myTimeIndex >= myFormatTo.sampleRate) { + // myFragmentIndex += time * myFormatFrom.sampleRate + myFragmentIndex += myTimeIndex / myFormatTo.sampleRate; + myTimeIndex %= myFormatTo.sampleRate; + } + + if (myFragmentIndex >= myFormatFrom.fragmentSize) { + myFragmentIndex %= myFormatFrom.fragmentSize; + + Int16* nextFragment = myNextFragmentCallback(); + if (nextFragment) + myCurrentFragment = nextFragment; + else { + (cerr << "audio buffer underrun\n").flush(); + myIsUnderrun = true; + } + } + + if (myFormatTo.stereo) { + if (myFormatFrom.stereo) { + fragment[2*i] = myCurrentFragment[2*myFragmentIndex]; + fragment[2*i + 1] = myCurrentFragment[2*myFragmentIndex + 1]; + } + else + fragment[2*i] = fragment[2*i + 1] = myCurrentFragment[myFragmentIndex]; + } else { + if (myFormatFrom.stereo) + fragment[i] = (myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2); + else + fragment[i] = myCurrentFragment[myFragmentIndex]; + } + } +} diff --git a/src/common/audio/SimpleResampler.hxx b/src/common/audio/SimpleResampler.hxx new file mode 100644 index 000000000..a9b155226 --- /dev/null +++ b/src/common/audio/SimpleResampler.hxx @@ -0,0 +1,50 @@ +//============================================================================ +// +// 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-2018 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 SIMPLE_RESAMPLER_HXX +#define SIMPLE_RESAMPLER_HXX + +#include "Resampler.hxx" + +class SimpleResampler : public Resampler { + public: + SimpleResampler( + Resampler::Format formatFrom, + Resampler::Format formatTo, + Resampler::NextFragmentCallback NextFragmentCallback + ); + + virtual void fillFragment(Int16* fragment, uInt32 length); + + private: + + Int16* myCurrentFragment; + uInt32 myTimeIndex; + uInt32 myFragmentIndex; + bool myIsUnderrun; + + private: + + SimpleResampler() = delete; + SimpleResampler(const SimpleResampler&) = delete; + SimpleResampler(SimpleResampler&&) = delete; + SimpleResampler& operator=(const SimpleResampler&) = delete; + SimpleResampler& operator=(const SimpleResampler&&) = delete; + +}; + +#endif // SIMPLE_RESAMPLER_HXX diff --git a/src/common/audio/module.mk b/src/common/audio/module.mk new file mode 100644 index 000000000..890196f3c --- /dev/null +++ b/src/common/audio/module.mk @@ -0,0 +1,10 @@ +MODULE := src/common/audio + +MODULE_OBJS := \ + src/common/audio/SimpleResampler.o + +MODULE_DIRS += \ + src/emucore/tia + +# Include common rules +include $(srcdir)/common.rules diff --git a/src/emucore/EmulationTiming.hxx b/src/emucore/EmulationTiming.hxx index a18acc6b0..163df9088 100644 --- a/src/emucore/EmulationTiming.hxx +++ b/src/emucore/EmulationTiming.hxx @@ -60,6 +60,13 @@ class EmulationTiming { uInt32 myPlaybackPeriod; + private: + + EmulationTiming(const EmulationTiming&) = delete; + EmulationTiming(EmulationTiming&&) = delete; + EmulationTiming& operator=(const EmulationTiming&) = delete; + EmulationTiming& operator=(EmulationTiming&&) = delete; + }; #endif // EMULATION_TIMING_HXX From 2c38757faaebaae818bb50bf351ef84bcfaffc3b Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Tue, 8 May 2018 20:17:48 -0230 Subject: [PATCH 33/77] Updated VS project file for resampler classes. --- src/windows/Stella.vcxproj | 3 +++ src/windows/Stella.vcxproj.filters | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index 0d8270a8f..9534109df 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -229,6 +229,7 @@ + @@ -515,6 +516,8 @@ + + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index b5fc3627f..e08979da0 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -55,6 +55,12 @@ {ffa3642d-aa8a-43a5-8ac5-acd8878dd091} + + {49d8ea64-20c1-45f1-9dc9-b39c17d7cabd} + + + {000e4a6b-8cd6-43db-8253-8255c7efa706} + @@ -909,6 +915,9 @@ Source Files\emucore + + Source Files\audio + @@ -1862,6 +1871,12 @@ Header Files\emucore + + Header Files\audio + + + Header Files\audio + From cea1c011be6bbdb13051d273128e95dfe5123e56 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Tue, 8 May 2018 20:34:46 -0230 Subject: [PATCH 34/77] Updated Xcode project for resampler classes. --- src/macosx/stella.xcodeproj/project.pbxproj | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index 2ba4e8fdf..4acb90526 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -509,6 +509,9 @@ DCC527D610B9DA19005E1287 /* System.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCC527CE10B9DA19005E1287 /* System.cxx */; }; DCC527D710B9DA19005E1287 /* System.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527CF10B9DA19005E1287 /* System.hxx */; }; DCC527DB10B9DA6A005E1287 /* bspf.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC527D810B9DA6A005E1287 /* bspf.hxx */; }; + DCC6A4B120A2622500863C59 /* Resampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC6A4AE20A2622500863C59 /* Resampler.hxx */; }; + DCC6A4B220A2622500863C59 /* SimpleResampler.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */; }; + DCC6A4B320A2622500863C59 /* SimpleResampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */; }; DCCA26B31FA64D5E000EE4D8 /* AbstractFrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B11FA64D5E000EE4D8 /* AbstractFrameManager.hxx */; }; DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */; }; DCCF47DE14B60DEE00814FAB /* ControllerWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCCF47DB14B60DEE00814FAB /* ControllerWidget.hxx */; }; @@ -1190,6 +1193,9 @@ DCC527CE10B9DA19005E1287 /* System.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = System.cxx; sourceTree = ""; }; DCC527CF10B9DA19005E1287 /* System.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = System.hxx; sourceTree = ""; }; DCC527D810B9DA6A005E1287 /* bspf.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = bspf.hxx; sourceTree = ""; }; + DCC6A4AE20A2622500863C59 /* Resampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Resampler.hxx; path = audio/Resampler.hxx; sourceTree = ""; }; + DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SimpleResampler.cxx; path = audio/SimpleResampler.cxx; sourceTree = ""; }; + DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = SimpleResampler.hxx; path = audio/SimpleResampler.hxx; sourceTree = ""; }; DCCA26B11FA64D5E000EE4D8 /* AbstractFrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AbstractFrameManager.hxx; sourceTree = ""; }; DCCA26B21FA64D5E000EE4D8 /* FrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameManager.hxx; sourceTree = ""; }; DCCF47DB14B60DEE00814FAB /* ControllerWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ControllerWidget.hxx; sourceTree = ""; }; @@ -1588,6 +1594,7 @@ 2D6050C5089876F300C6DE89 /* common */ = { isa = PBXGroup; children = ( + DCC6A4AD20A2620D00863C59 /* audio */, E09F413A201E901D004A3391 /* AudioQueue.cxx */, E09F4139201E901C004A3391 /* AudioQueue.hxx */, DC79F81017A88D9E00288B91 /* Base.cxx */, @@ -1662,8 +1669,6 @@ 2D6050CC0898776500C6DE89 /* emucore */ = { isa = PBXGroup; children = ( - E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */, - E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */, DC1B2EBE1E50036100F62837 /* AmigaMouse.hxx */, DC1B2EC01E50036100F62837 /* AtariMouse.hxx */, DC487FB40DA5350900E12499 /* AtariVox.cxx */, @@ -1772,6 +1777,8 @@ DCC527C910B9DA19005E1287 /* Device.hxx */, 2DE2DF3E0627AE07006BEC99 /* Driving.cxx */, 2DE2DF3F0627AE07006BEC99 /* Driving.hxx */, + E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */, + E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */, 2DE2DF410627AE07006BEC99 /* Event.hxx */, 2D733D6E062895B2006265D9 /* EventHandler.cxx */, 2D733D6F062895B2006265D9 /* EventHandler.hxx */, @@ -2031,6 +2038,16 @@ path = tv_filters; sourceTree = ""; }; + DCC6A4AD20A2620D00863C59 /* audio */ = { + isa = PBXGroup; + children = ( + DCC6A4AE20A2622500863C59 /* Resampler.hxx */, + DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */, + DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */, + ); + name = audio; + sourceTree = ""; + }; DCCC0C9109C3541E0088BFF1 /* cheat */ = { isa = PBXGroup; children = ( @@ -2265,6 +2282,7 @@ 2D91745509BA90380026E9FF /* CpuWidget.hxx in Headers */, 2D91745609BA90380026E9FF /* DataGridOpsWidget.hxx in Headers */, 2D91745709BA90380026E9FF /* DataGridWidget.hxx in Headers */, + DCC6A4B120A2622500863C59 /* Resampler.hxx in Headers */, DCF3A6EC1DFC75E3008A8AF3 /* DelayQueue.hxx in Headers */, DCA078341F8C1B04008EFEE5 /* LinkedObjectPool.hxx in Headers */, 2D91745809BA90380026E9FF /* DebuggerDialog.hxx in Headers */, @@ -2303,6 +2321,7 @@ DC8078EB0B4BD697005E9305 /* UIDialog.hxx in Headers */, DCEECE570B5E5E540021D754 /* Cart0840.hxx in Headers */, DCE3BBFA0C95CEDC00A671DF /* RomInfoWidget.hxx in Headers */, + DCC6A4B320A2622500863C59 /* SimpleResampler.hxx in Headers */, DC6DC91F205DB879004A5FC3 /* PhysicalJoystick.hxx in Headers */, DC0984860D3985160073C852 /* CartSB.hxx in Headers */, DCEC585E1E945175002F0246 /* DelayQueueIterator.hxx in Headers */, @@ -2794,6 +2813,7 @@ DC3EE8601E2C0E6D00905161 /* gzwrite.c in Sources */, DCAAE5D71715887B0080BB82 /* Cart4KWidget.cxx in Sources */, DCDA03B01A2009BB00711920 /* CartWD.cxx in Sources */, + DCC6A4B220A2622500863C59 /* SimpleResampler.cxx in Sources */, DCAAE5D91715887B0080BB82 /* Cart0840Widget.cxx in Sources */, DCAAE5DB1715887B0080BB82 /* CartCVWidget.cxx in Sources */, DCAAE5DE1715887B0080BB82 /* CartEFSCWidget.cxx in Sources */, From b329c7ff5f13c1613b1d39d5c5e38463c30a1d82 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 10 May 2018 23:53:30 +0200 Subject: [PATCH 35/77] Switch to F32 samples, make volume scale more linear in sound.. --- src/common/SoundSDL2.cxx | 24 +++++------------------- src/common/SoundSDL2.hxx | 4 ++-- src/common/audio/Resampler.hxx | 2 +- src/common/audio/SimpleResampler.cxx | 25 +++++++++++++++---------- src/common/audio/SimpleResampler.hxx | 2 +- 5 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index df43df023..e3443c15d 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -32,13 +32,6 @@ #include "EmulationTiming.hxx" #include "audio/SimpleResampler.hxx" -namespace { - inline Int16 applyVolume(Int16 sample, Int32 volumeFactor) - { - return static_cast(static_cast(sample) * volumeFactor / 0xffff); - } -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL2::SoundSDL2(OSystem& osystem) : Sound(osystem), @@ -50,20 +43,13 @@ SoundSDL2::SoundSDL2(OSystem& osystem) { myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); -#ifdef BSPF_WINDOWS - // TODO - remove the following code once we convert to the new sound - // core, and use 32-bit floating point samples and do - // our own resampling - SDL_setenv("SDL_AUDIODRIVER", "directsound", true); -#endif - // The sound system is opened only once per program run, to eliminate // issues with opening and closing it multiple times // This fixes a bug most prevalent with ATI video cards in Windows, // whereby sound stopped working after the first video change SDL_AudioSpec desired; desired.freq = myOSystem.settings().getInt("freq"); - desired.format = AUDIO_S16SYS; + desired.format = AUDIO_F32SYS; desired.channels = 2; desired.samples = myOSystem.settings().getInt("fragsize"); desired.callback = callback; @@ -191,7 +177,7 @@ void SoundSDL2::setVolume(Int32 percent) myVolume = percent; SDL_LockAudio(); - myVolumeFactor = static_cast(floor(static_cast(0xffff) * static_cast(myVolume) / 100.)); + myVolumeFactor = std::pow(static_cast(percent) / 100.f, 2.f); SDL_UnlockAudio(); } } @@ -235,11 +221,11 @@ uInt32 SoundSDL2::getSampleRate() const } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL2::processFragment(Int16* stream, uInt32 length) +void SoundSDL2::processFragment(float* stream, uInt32 length) { myResampler->fillFragment(stream, length); - for (uInt32 i = 0; i < length; i++) stream[i] = applyVolume(stream[i], myVolumeFactor); + for (uInt32 i = 0; i < length; i++) stream[i] = stream[i] * myVolumeFactor; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -272,7 +258,7 @@ void SoundSDL2::callback(void* udata, uInt8* stream, int len) SoundSDL2* self = static_cast(udata); if (self->myAudioQueue) - self->processFragment(reinterpret_cast(stream), len >> 1); + self->processFragment(reinterpret_cast(stream), len >> 2); else SDL_memset(stream, 0, len); } diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 9874c1197..9b4922ade 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -111,7 +111,7 @@ class SoundSDL2 : public Sound @param stream Pointer to the start of the fragment @param length Length of the fragment */ - void processFragment(Int16* stream, uInt32 length); + void processFragment(float* stream, uInt32 length); private: @@ -123,7 +123,7 @@ class SoundSDL2 : public Sound // Current volume as a percentage (0 - 100) uInt32 myVolume; - Int32 myVolumeFactor; + float myVolumeFactor; // Audio specification structure SDL_AudioSpec myHardwareSpec; diff --git a/src/common/audio/Resampler.hxx b/src/common/audio/Resampler.hxx index 2926272a4..9fb7a8611 100644 --- a/src/common/audio/Resampler.hxx +++ b/src/common/audio/Resampler.hxx @@ -55,7 +55,7 @@ class Resampler { myNextFragmentCallback(nextFragmentCallback) {} - virtual void fillFragment(Int16* fragment, uInt32 length) = 0; + virtual void fillFragment(float* fragment, uInt32 length) = 0; virtual ~Resampler() {} diff --git a/src/common/audio/SimpleResampler.cxx b/src/common/audio/SimpleResampler.cxx index de3b6e535..76005222f 100644 --- a/src/common/audio/SimpleResampler.cxx +++ b/src/common/audio/SimpleResampler.cxx @@ -31,7 +31,7 @@ SimpleResampler::SimpleResampler( {} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SimpleResampler::fillFragment(Int16* fragment, uInt32 length) +void SimpleResampler::fillFragment(float* fragment, uInt32 length) { if (myIsUnderrun) { Int16* nextFragment = myNextFragmentCallback(); @@ -44,7 +44,7 @@ void SimpleResampler::fillFragment(Int16* fragment, uInt32 length) } if (!myCurrentFragment) { - memset(fragment, 0, 2 * length); + memset(fragment, 0, sizeof(float) * length); return; } @@ -74,18 +74,23 @@ void SimpleResampler::fillFragment(Int16* fragment, uInt32 length) } } - if (myFormatTo.stereo) { - if (myFormatFrom.stereo) { - fragment[2*i] = myCurrentFragment[2*myFragmentIndex]; - fragment[2*i + 1] = myCurrentFragment[2*myFragmentIndex + 1]; + if (myFormatFrom.stereo) { + float sampleL = static_cast(myCurrentFragment[2*myFragmentIndex]) / static_cast(0x7fff); + float sampleR = static_cast(myCurrentFragment[2*myFragmentIndex + 1]) / static_cast(0x7fff); + + if (myFormatTo.stereo) { + fragment[2*i] = sampleL; + fragment[2*i + 1] = sampleR; } else - fragment[2*i] = fragment[2*i + 1] = myCurrentFragment[myFragmentIndex]; + fragment[i] = (sampleL + sampleR) / 2.f; } else { - if (myFormatFrom.stereo) - fragment[i] = (myCurrentFragment[2*myFragmentIndex] / 2) + (myCurrentFragment[2*myFragmentIndex + 1] / 2); + float sample = static_cast(myCurrentFragment[myFragmentIndex] / static_cast(0x7fff)); + + if (myFormatTo.stereo) + fragment[2*i] = fragment[2*i + 1] = sample; else - fragment[i] = myCurrentFragment[myFragmentIndex]; + fragment[i] = sample; } } } diff --git a/src/common/audio/SimpleResampler.hxx b/src/common/audio/SimpleResampler.hxx index a9b155226..87f684e1e 100644 --- a/src/common/audio/SimpleResampler.hxx +++ b/src/common/audio/SimpleResampler.hxx @@ -28,7 +28,7 @@ class SimpleResampler : public Resampler { Resampler::NextFragmentCallback NextFragmentCallback ); - virtual void fillFragment(Int16* fragment, uInt32 length); + virtual void fillFragment(float* fragment, uInt32 length); private: From 1b0fb381d09e29c335ec6eb690260831a68a2701 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Fri, 11 May 2018 23:52:00 +0200 Subject: [PATCH 36/77] Lanczos resampling. --- src/common/SoundSDL2.cxx | 6 +- src/common/audio/ConvolutionBuffer.cxx | 50 ++++++ src/common/audio/ConvolutionBuffer.hxx | 52 ++++++ src/common/audio/LanczosResampler.cxx | 211 +++++++++++++++++++++++++ src/common/audio/LanczosResampler.hxx | 64 ++++++++ src/common/audio/SimpleResampler.cxx | 38 ++--- src/common/audio/SimpleResampler.hxx | 1 + src/common/audio/module.mk | 4 +- 8 files changed, 404 insertions(+), 22 deletions(-) create mode 100644 src/common/audio/ConvolutionBuffer.cxx create mode 100644 src/common/audio/ConvolutionBuffer.hxx create mode 100644 src/common/audio/LanczosResampler.cxx create mode 100644 src/common/audio/LanczosResampler.hxx diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index e3443c15d..a7bbd416d 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -31,6 +31,7 @@ #include "AudioQueue.hxx" #include "EmulationTiming.hxx" #include "audio/SimpleResampler.hxx" +#include "audio/LanczosResampler.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL2::SoundSDL2(OSystem& osystem) @@ -245,10 +246,11 @@ void SoundSDL2::initResampler() return nextFragment; }; - myResampler = make_unique( + myResampler = make_unique( Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()), Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1), - nextFragmentCallback + nextFragmentCallback, + 2 ); } diff --git a/src/common/audio/ConvolutionBuffer.cxx b/src/common/audio/ConvolutionBuffer.cxx new file mode 100644 index 000000000..0badf2d5e --- /dev/null +++ b/src/common/audio/ConvolutionBuffer.cxx @@ -0,0 +1,50 @@ +//============================================================================ +// +// 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-2018 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 "ConvolutionBuffer.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +ConvolutionBuffer::ConvolutionBuffer(uInt32 size) : myFirstIndex(0), mySize(size) +{ + myData = new float[mySize]; + memset(myData, 0, mySize * sizeof(float)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +ConvolutionBuffer::~ConvolutionBuffer() +{ + delete[] myData; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void ConvolutionBuffer::shift(float nextValue) +{ + myFirstIndex = (myFirstIndex + 1) % mySize; + myData[(myFirstIndex + mySize - 1) % mySize] = nextValue; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +float ConvolutionBuffer::convoluteWith(float* kernel) const +{ + float result = 0.; + + for (uInt32 i = 0; i < mySize; i++) { + result += kernel[i] * myData[(myFirstIndex + i) % mySize]; + } + + return result; +} diff --git a/src/common/audio/ConvolutionBuffer.hxx b/src/common/audio/ConvolutionBuffer.hxx new file mode 100644 index 000000000..bf85ace41 --- /dev/null +++ b/src/common/audio/ConvolutionBuffer.hxx @@ -0,0 +1,52 @@ +//============================================================================ +// +// 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-2018 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 CONVOLUTION_BUFFER_HXX +#define CONVOLUTION_BUFFER_HXX + +#include "bspf.hxx" + +class ConvolutionBuffer { + public: + + ConvolutionBuffer(uInt32 size); + + ~ConvolutionBuffer(); + + void shift(float nextValue); + + float convoluteWith(float* kernel) const; + + private: + + float* myData; + + uInt32 myFirstIndex; + + uInt32 mySize; + + private: + + ConvolutionBuffer() = delete; + ConvolutionBuffer(const ConvolutionBuffer&) = delete; + ConvolutionBuffer(ConvolutionBuffer&&) = delete; + ConvolutionBuffer& operator=(const ConvolutionBuffer&) = delete; + ConvolutionBuffer& operator=(ConvolutionBuffer&&) = delete; + +}; + +#endif // CONVOLUTION_BUFFER_HXX diff --git a/src/common/audio/LanczosResampler.cxx b/src/common/audio/LanczosResampler.cxx new file mode 100644 index 000000000..dec22c07a --- /dev/null +++ b/src/common/audio/LanczosResampler.cxx @@ -0,0 +1,211 @@ +//============================================================================ +// +// 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-2018 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 + +#include "LanczosResampler.hxx" + +namespace { + + constexpr float CLIPPING_FACTOR = 0.75; + + uInt32 reducedDenominator(uInt32 n, uInt32 d) + { + for (uInt32 i = std::min(n ,d); i > 1; i--) { + if ((n % i == 0) && (d % i == 0)) { + n /= i; + d /= i; + i = std::min(n ,d); + } + } + + return d; + } + + float sinc(float x) + { + // We calculate the sinc with double precision in order to compensate for precision loss + // around zero + return x == 0.f ? 1 : static_cast( + sin(M_PI * static_cast(x)) / M_PI / static_cast(x) + ); + } + + double lanczosKernel(float x, uInt32 a) { + return sinc(x) * sinc(x / static_cast(a)); + } + +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +LanczosResampler::LanczosResampler( + Resampler::Format formatFrom, + Resampler::Format formatTo, + Resampler::NextFragmentCallback nextFragmentCallback, + uInt32 kernelParameter) +: + Resampler(formatFrom, formatTo, nextFragmentCallback), + // In order to find the number of kernels we need to precompute, we need to find N minimal such that + // + // N / formatTo.sampleRate = M / formatFrom.sampleRate + // + // with integral N and M. Equivalently, we have + // + // formatFrom.sampleRate / formatTo.sampleRate = M / N + // + // -> we find N from fully reducing the fraction. + myPrecomputedKernelCount(reducedDenominator(formatFrom.sampleRate, formatTo.sampleRate)), + myKernelSize(2 * kernelParameter), + myCurrentKernelIndex(0), + myKernelParameter(kernelParameter), + myBuffer(0), + myBufferL(0), + myBufferR(0), + myCurrentFragment(0), + myFragmentIndex(0), + myIsUnderrun(true), + myTimeIndex(0) +{ + myPrecomputedKernels = new float[myPrecomputedKernelCount * myKernelSize]; + + if (myFormatFrom.stereo) { + myBufferL = new ConvolutionBuffer(myKernelSize); + myBufferR = new ConvolutionBuffer(myKernelSize); + } + else + myBuffer = new ConvolutionBuffer(myKernelSize); + + precomputeKernels(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +LanczosResampler::~LanczosResampler() { + delete[] myPrecomputedKernels; + + if (myBuffer) delete myBuffer; + if (myBufferL) delete myBufferL; + if (myBufferR) delete myBufferR; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void LanczosResampler::precomputeKernels() +{ + // timeIndex = time * formatFrom.sampleRate * formatTo.sampleRAte + uInt32 timeIndex = 0; + + for (uInt32 i = 0; i < myPrecomputedKernelCount; i++) { + float* kernel = myPrecomputedKernels + myKernelSize * i; + float center = + static_cast(timeIndex) / static_cast(myFormatTo.sampleRate); + + for (uInt32 j = 0; j < 2 * myKernelParameter; j++) { + kernel[j] = lanczosKernel( + center - static_cast(j) + static_cast(myKernelParameter) - 1.f, myKernelParameter + ) * CLIPPING_FACTOR; + } + + // Next step: time += 1 / formatTo.sampleRate + // + // By construction, we limit x in the kernal evaluation to 0 .. 1 / formatFrom.sampleRate + // + // time = N / formatFrom.sampleRate + delta + // + // with max integral N and delta, then time = delta -> modulus + timeIndex = (timeIndex + myFormatFrom.sampleRate) % myFormatTo.sampleRate; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void LanczosResampler::fillFragment(float* fragment, uInt32 length) +{ + if (myIsUnderrun) { + Int16* nextFragment = myNextFragmentCallback(); + + if (nextFragment) { + myCurrentFragment = nextFragment; + myFragmentIndex = 0; + myIsUnderrun = false; + } + } + + if (!myCurrentFragment) { + memset(fragment, 0, sizeof(float) * length); + return; + } + + const uInt32 outputSamples = myFormatTo.stereo ? (length >> 1) : length; + + for (uInt32 i = 0; i < outputSamples; i++) { + float* kernel = myPrecomputedKernels + (myCurrentKernelIndex * myKernelSize); + myCurrentKernelIndex = (myCurrentKernelIndex + 1) % myPrecomputedKernelCount; + + if (myFormatFrom.stereo) { + float sampleL = myBufferL->convoluteWith(kernel); + float sampleR = myBufferR->convoluteWith(kernel); + + if (myFormatTo.stereo) { + fragment[2*i] = sampleL; + fragment[2*i + 1] = sampleR; + } + else + fragment[i] = (sampleL + sampleR) / 2.f; + } else { + float sample = myBuffer->convoluteWith(kernel); + + if (myFormatTo.stereo) + fragment[2*i] = fragment[2*i + 1] = sample; + else + fragment[i] = sample; + } + + myTimeIndex += myFormatFrom.sampleRate; + + uInt32 samplesToShift = myTimeIndex / myFormatTo.sampleRate; + if (samplesToShift == 0) continue; + + myTimeIndex %= myFormatTo.sampleRate; + shiftSamples(samplesToShift); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +inline void LanczosResampler::shiftSamples(uInt32 samplesToShift) +{ + while (samplesToShift-- > 0) { + if (myFormatFrom.stereo) { + myBufferL->shift(myCurrentFragment[2*myFragmentIndex] / static_cast(0x7fff)); + myBufferR->shift(myCurrentFragment[2*myFragmentIndex + 1] / static_cast(0x7fff)); + } + else + myBuffer->shift(myCurrentFragment[myFragmentIndex] / static_cast(0x7fff)); + + myFragmentIndex++; + + if (myFragmentIndex >= myFormatFrom.fragmentSize) { + myFragmentIndex %= myFormatFrom.fragmentSize; + + Int16* nextFragment = myNextFragmentCallback(); + if (nextFragment) { + myCurrentFragment = nextFragment; + myIsUnderrun = false; + } else { + (cerr << "audio buffer underrun\n").flush(); + myIsUnderrun = true; + } + } + } +} diff --git a/src/common/audio/LanczosResampler.hxx b/src/common/audio/LanczosResampler.hxx new file mode 100644 index 000000000..07b5956ef --- /dev/null +++ b/src/common/audio/LanczosResampler.hxx @@ -0,0 +1,64 @@ +//============================================================================ +// +// 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-2018 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 LANCZOS_RESAMPLER_HXX +#define LANCZOS_RESAMPLER_HXX + +#include "bspf.hxx" +#include "Resampler.hxx" +#include "ConvolutionBuffer.hxx" + +class LanczosResampler : public Resampler { + public: + LanczosResampler( + Resampler::Format formatFrom, + Resampler::Format formatTo, + Resampler::NextFragmentCallback nextFragmentCallback, + uInt32 kernelParameter + ); + + virtual void fillFragment(float* fragment, uInt32 length); + + virtual ~LanczosResampler(); + + private: + + void precomputeKernels(); + + void shiftSamples(uInt32 samplesToShift); + + private: + + uInt32 myPrecomputedKernelCount; + uInt32 myKernelSize; + float* myPrecomputedKernels; + uInt32 myCurrentKernelIndex; + + uInt32 myKernelParameter; + + ConvolutionBuffer* myBuffer; + ConvolutionBuffer* myBufferL; + ConvolutionBuffer* myBufferR; + + Int16* myCurrentFragment; + uInt32 myFragmentIndex; + bool myIsUnderrun; + + uInt32 myTimeIndex; +}; + +#endif // LANCZOS_RESAMPLER_HXX diff --git a/src/common/audio/SimpleResampler.cxx b/src/common/audio/SimpleResampler.cxx index 76005222f..6785886d8 100644 --- a/src/common/audio/SimpleResampler.cxx +++ b/src/common/audio/SimpleResampler.cxx @@ -52,6 +52,25 @@ void SimpleResampler::fillFragment(float* fragment, uInt32 length) // For the following math, remember that myTimeIndex = time * myFormatFrom.sampleRate * myFormatTo.sampleRate for (uInt32 i = 0; i < outputSamples; i++) { + if (myFormatFrom.stereo) { + float sampleL = static_cast(myCurrentFragment[2*myFragmentIndex]) / static_cast(0x7fff); + float sampleR = static_cast(myCurrentFragment[2*myFragmentIndex + 1]) / static_cast(0x7fff); + + if (myFormatTo.stereo) { + fragment[2*i] = sampleL; + fragment[2*i + 1] = sampleR; + } + else + fragment[i] = (sampleL + sampleR) / 2.f; + } else { + float sample = static_cast(myCurrentFragment[myFragmentIndex] / static_cast(0x7fff)); + + if (myFormatTo.stereo) + fragment[2*i] = fragment[2*i + 1] = sample; + else + fragment[i] = sample; + } + // time += 1 / myFormatTo.sampleRate myTimeIndex += myFormatFrom.sampleRate; @@ -73,24 +92,5 @@ void SimpleResampler::fillFragment(float* fragment, uInt32 length) myIsUnderrun = true; } } - - if (myFormatFrom.stereo) { - float sampleL = static_cast(myCurrentFragment[2*myFragmentIndex]) / static_cast(0x7fff); - float sampleR = static_cast(myCurrentFragment[2*myFragmentIndex + 1]) / static_cast(0x7fff); - - if (myFormatTo.stereo) { - fragment[2*i] = sampleL; - fragment[2*i + 1] = sampleR; - } - else - fragment[i] = (sampleL + sampleR) / 2.f; - } else { - float sample = static_cast(myCurrentFragment[myFragmentIndex] / static_cast(0x7fff)); - - if (myFormatTo.stereo) - fragment[2*i] = fragment[2*i + 1] = sample; - else - fragment[i] = sample; - } } } diff --git a/src/common/audio/SimpleResampler.hxx b/src/common/audio/SimpleResampler.hxx index 87f684e1e..8495ba7a7 100644 --- a/src/common/audio/SimpleResampler.hxx +++ b/src/common/audio/SimpleResampler.hxx @@ -18,6 +18,7 @@ #ifndef SIMPLE_RESAMPLER_HXX #define SIMPLE_RESAMPLER_HXX +#include "bspf.hxx" #include "Resampler.hxx" class SimpleResampler : public Resampler { diff --git a/src/common/audio/module.mk b/src/common/audio/module.mk index 890196f3c..67ae01063 100644 --- a/src/common/audio/module.mk +++ b/src/common/audio/module.mk @@ -1,7 +1,9 @@ MODULE := src/common/audio MODULE_OBJS := \ - src/common/audio/SimpleResampler.o + src/common/audio/SimpleResampler.o \ + src/common/audio/ConvolutionBuffer.o \ + src/common/audio/LanczosResampler.o MODULE_DIRS += \ src/emucore/tia From 2da0ffa2f519e42e0b0156b3c4fdccfa55889706 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 12 May 2018 00:15:29 +0200 Subject: [PATCH 37/77] Rework available sampling rates (44100, 44800, 96000), add resampling.quality parameter. --- TODO_audio.md | 2 +- src/common/SoundSDL2.cxx | 30 ++++++++++++++++++++++++------ src/emucore/Settings.cxx | 18 +++++++++++------- src/gui/AudioDialog.cxx | 4 +--- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/TODO_audio.md b/TODO_audio.md index 6df170ff8..3f5c5ccbe 100644 --- a/TODO_audio.md +++ b/TODO_audio.md @@ -1,9 +1,9 @@ # Missing features * Reimplement target FPS mode - * Implement Lanzcos resampling * Fixup OpenGL sync, ensure that FB only rerenders after a frame has been generated # Cleanup + * Remove or turn sterr output into log messages * Document EmulationTiming diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index a7bbd416d..2f42f5f85 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -246,12 +246,30 @@ void SoundSDL2::initResampler() return nextFragment; }; - myResampler = make_unique( - Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()), - Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1), - nextFragmentCallback, - 2 - ); + Resampler::Format formatFrom = + Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()); + Resampler::Format formatTo = + Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1); + + int quality = myOSystem.settings().getInt("resampling.quality"); + + switch (quality) { + case 1: + myResampler = make_unique(formatFrom, formatTo, nextFragmentCallback); + (cerr << "resampling quality 1: using nearest neighbor resampling\n").flush(); + break; + + default: + case 2: + (cerr << "resampling quality 2: using nearest Lanczos resampling, a = 2\n").flush(); + myResampler = make_unique(formatFrom, formatTo, nextFragmentCallback, 2); + break; + + case 3: + (cerr << "resampling quality 3: using nearest Lanczos resampling, a = 3\n").flush(); + myResampler = make_unique(formatFrom, formatTo, nextFragmentCallback, 3); + break; + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index b54c366ed..99c223bfb 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -71,8 +71,9 @@ Settings::Settings(OSystem& osystem) // Sound options setInternal("sound", "true"); setInternal("fragsize", "512"); - setInternal("freq", "31400"); + setInternal("freq", "44100"); setInternal("volume", "100"); + setInternal("resampling.quality", "2"); // Input event options setInternal("keymap", ""); @@ -362,8 +363,10 @@ void Settings::validate() i = getInt("volume"); if(i < 0 || i > 100) setInternal("volume", "100"); i = getInt("freq"); - if(!(i == 11025 || i == 22050 || i == 31400 || i == 44100 || i == 48000)) - setInternal("freq", "31400"); + if(!(i == 44100 || i == 48000 || i == 96000)) + setInternal("freq", "44100"); + i = getInt("resampling.quality"); + if (i < 1 || i > 3) setInternal("resampling.quality", 2); #endif i = getInt("joydeadzone"); @@ -443,10 +446,11 @@ void Settings::usage() const << " -uimessages <1|0> Show onscreen UI messages for different events\n" << endl #ifdef SOUND_SUPPORT - << " -sound <1|0> Enable sound generation\n" - << " -fragsize The size of sound fragments (must be a power of two)\n" - << " -freq Set sound sample output frequency (11025|22050|31400|44100|48000)\n" - << " -volume Set the volume (0 - 100)\n" + << " -sound <1|0> Enable sound generation\n" + << " -fragsize The size of sound fragments (must be a power of two)\n" + << " -freq Set sound sample output frequency (44100|48000|96000)\n" + << " -resampling.quality Resampling quality (1 -3), default: 2\n" + << " -volume Set the volume (0 - 100)\n" << endl #endif << " -tia.zoom Use the specified zoom level (windowed mode) for TIA image\n" diff --git a/src/gui/AudioDialog.cxx b/src/gui/AudioDialog.cxx index 74c15668f..1eb93c36a 100644 --- a/src/gui/AudioDialog.cxx +++ b/src/gui/AudioDialog.cxx @@ -85,11 +85,9 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, // Output frequency items.clear(); - VarList::push_back(items, "11025 Hz", "11025"); - VarList::push_back(items, "22050 Hz", "22050"); - VarList::push_back(items, "31400 Hz", "31400"); VarList::push_back(items, "44100 Hz", "44100"); VarList::push_back(items, "48000 Hz", "48000"); + VarList::push_back(items, "96000 Hz", "96000"); myFreqPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, items, "Frequency (*) ", lwidth); From 0c67bff9393fb782003c636dfa90ae656b6d2bea Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 12 May 2018 00:18:09 +0200 Subject: [PATCH 38/77] Tuning, TODO --- TODO_audio.md | 1 + src/emucore/EmulationTiming.cxx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/TODO_audio.md b/TODO_audio.md index 3f5c5ccbe..3781ec26f 100644 --- a/TODO_audio.md +++ b/TODO_audio.md @@ -2,6 +2,7 @@ * Reimplement target FPS mode * Fixup OpenGL sync, ensure that FB only rerenders after a frame has been generated + * Add GUI for new audio parameters (prebuffer fragment count, resampling quality) # Cleanup diff --git a/src/emucore/EmulationTiming.cxx b/src/emucore/EmulationTiming.cxx index c812bb36b..4708c65e6 100644 --- a/src/emucore/EmulationTiming.cxx +++ b/src/emucore/EmulationTiming.cxx @@ -20,7 +20,7 @@ namespace { constexpr uInt32 AUDIO_HALF_FRAMES_PER_FRAGMENT = 1; constexpr uInt32 QUEUE_CAPACITY_SAFETY_FACTOR = 2; - constexpr uInt32 PREBUFFER_FRAGMENT_COUNT = 4; + constexpr uInt32 PREBUFFER_FRAGMENT_COUNT = 2; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From ce2d650df4fa28dd835cacd6b058c6b6bddc9ee3 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 12 May 2018 00:24:02 +0200 Subject: [PATCH 39/77] Update XCode project. --- src/macosx/stella.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index 4acb90526..aceca62ce 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -635,6 +635,10 @@ E09F4142201E9050004A3391 /* Audio.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413E201E904F004A3391 /* Audio.cxx */; }; E09F4143201E9050004A3391 /* AudioChannel.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E09F413F201E904F004A3391 /* AudioChannel.cxx */; }; E09F4144201E9050004A3391 /* AudioChannel.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E09F4140201E904F004A3391 /* AudioChannel.hxx */; }; + E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */; }; + E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */; }; + E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */; }; + E0DCD3AA20A64E96000B614E /* ConvolutionBuffer.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -1317,6 +1321,10 @@ E09F413E201E904F004A3391 /* Audio.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cxx; sourceTree = ""; }; E09F413F201E904F004A3391 /* AudioChannel.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioChannel.cxx; sourceTree = ""; }; E09F4140201E904F004A3391 /* AudioChannel.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioChannel.hxx; sourceTree = ""; }; + E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = LanczosResampler.hxx; path = audio/LanczosResampler.hxx; sourceTree = ""; }; + E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LanczosResampler.cxx; path = audio/LanczosResampler.cxx; sourceTree = ""; }; + E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ConvolutionBuffer.hxx; path = audio/ConvolutionBuffer.hxx; sourceTree = ""; }; + E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ConvolutionBuffer.cxx; path = audio/ConvolutionBuffer.cxx; sourceTree = ""; }; E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AbstractFrameManager.cxx; sourceTree = ""; }; E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = ""; }; F5A47A9D01A0482F01D3D55B /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = SOURCE_ROOT; }; @@ -2041,6 +2049,10 @@ DCC6A4AD20A2620D00863C59 /* audio */ = { isa = PBXGroup; children = ( + E0DCD3A620A64E96000B614E /* ConvolutionBuffer.cxx */, + E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */, + E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */, + E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */, DCC6A4AE20A2622500863C59 /* Resampler.hxx */, DCC6A4AF20A2622500863C59 /* SimpleResampler.cxx */, DCC6A4B020A2622500863C59 /* SimpleResampler.hxx */, @@ -2259,6 +2271,7 @@ DC3EE86F1E2C0E6D00905161 /* zutil.h in Headers */, 2D91742509BA90380026E9FF /* EditTextWidget.hxx in Headers */, DCB87E581A104C1E00BF2A3B /* MediaFactory.hxx in Headers */, + E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */, 2D91742809BA90380026E9FF /* PackedBitArray.hxx in Headers */, 2D91742909BA90380026E9FF /* TIADebug.hxx in Headers */, 2D91742A09BA90380026E9FF /* YaccParser.hxx in Headers */, @@ -2335,6 +2348,7 @@ DC4AC6F40DC8DAEF00CD3AD2 /* SaveKey.hxx in Headers */, DC173F770E2CAC1E00320F94 /* ContextMenu.hxx in Headers */, DC0DF86A0F0DAAF500B0F1F3 /* GlobalPropsDialog.hxx in Headers */, + E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */, DC5D2C520F117CFD004D1660 /* Rect.hxx in Headers */, DC71EAA81FDA070D008827CB /* CartMNetworkWidget.hxx in Headers */, DC5D2C530F117CFD004D1660 /* StellaFont.hxx in Headers */, @@ -2596,6 +2610,7 @@ DCF3A6FC1DFC75E3008A8AF3 /* Playfield.cxx in Sources */, 2D91747C09BA90380026E9FF /* CartDPC.cxx in Sources */, 2D91747D09BA90380026E9FF /* CartE0.cxx in Sources */, + E0DCD3AA20A64E96000B614E /* ConvolutionBuffer.cxx in Sources */, 2D91747E09BA90380026E9FF /* CartE7.cxx in Sources */, DC9616321F817830008A2206 /* PointingDeviceWidget.cxx in Sources */, 2D91747F09BA90380026E9FF /* CartF4.cxx in Sources */, @@ -2842,6 +2857,7 @@ DC676A4D1729A0B000E4E73D /* CartDPCWidget.cxx in Sources */, DC676A4F1729A0B000E4E73D /* CartE0Widget.cxx in Sources */, DC676A511729A0B000E4E73D /* CartE7Widget.cxx in Sources */, + E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */, DC676A531729A0B000E4E73D /* CartFA2Widget.cxx in Sources */, DC676A551729A0B000E4E73D /* CartFEWidget.cxx in Sources */, DC676A591729A0B000E4E73D /* CartSBWidget.cxx in Sources */, From 33db8a8b039407a70fa7901844a676fe0bfe9a3b Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 12 May 2018 00:44:31 +0200 Subject: [PATCH 40/77] Comments. --- src/common/audio/LanczosResampler.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/audio/LanczosResampler.cxx b/src/common/audio/LanczosResampler.cxx index dec22c07a..f5467a9e6 100644 --- a/src/common/audio/LanczosResampler.cxx +++ b/src/common/audio/LanczosResampler.cxx @@ -109,6 +109,7 @@ void LanczosResampler::precomputeKernels() for (uInt32 i = 0; i < myPrecomputedKernelCount; i++) { float* kernel = myPrecomputedKernels + myKernelSize * i; + // The kernel is normalized such to be evaluate on time * formatFrom.sampleRate float center = static_cast(timeIndex) / static_cast(myFormatTo.sampleRate); @@ -120,7 +121,8 @@ void LanczosResampler::precomputeKernels() // Next step: time += 1 / formatTo.sampleRate // - // By construction, we limit x in the kernal evaluation to 0 .. 1 / formatFrom.sampleRate + // By construction, we limit the argument during kernel evaluation to 0 .. 1, which + // corresponds to 0 .. 1 / formatFrom.sampleRate for the time // // time = N / formatFrom.sampleRate + delta // From d624140829ec39327af083a49e5c49410034104c Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Fri, 11 May 2018 20:32:18 -0230 Subject: [PATCH 41/77] Updated VS project for resampling work, and fixed minor compile error in Windows. --- src/common/audio/LanczosResampler.cxx | 3 +++ src/windows/Stella.vcxproj | 4 ++++ src/windows/Stella.vcxproj.filters | 12 ++++++++++++ 3 files changed, 19 insertions(+) diff --git a/src/common/audio/LanczosResampler.cxx b/src/common/audio/LanczosResampler.cxx index f5467a9e6..07ee8b064 100644 --- a/src/common/audio/LanczosResampler.cxx +++ b/src/common/audio/LanczosResampler.cxx @@ -16,6 +16,9 @@ //============================================================================ #include +#ifndef M_PI + #define M_PI 3.14159265358979323846f +#endif #include "LanczosResampler.hxx" diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index 9534109df..d94440b24 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -229,6 +229,8 @@ + + @@ -516,6 +518,8 @@ + + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index e08979da0..1f095df8a 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -918,6 +918,12 @@ Source Files\audio + + Source Files\audio + + + Source Files\audio + @@ -1877,6 +1883,12 @@ Header Files\audio + + Header Files\audio + + + Header Files\audio + From 741515a52035aaf623ac6c48f524877d7f4d4ab2 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Fri, 11 May 2018 21:01:40 -0230 Subject: [PATCH 42/77] Fix minor compile warnings generated by clang: - mostly change pointer -> 0 to use 'nullptr' - some commenting and formatting fixes --- src/common/AudioQueue.cxx | 6 ++-- src/common/AudioQueue.hxx | 40 +++++++++++++-------------- src/common/SoundSDL2.cxx | 26 +++++++++-------- src/common/SoundSDL2.hxx | 4 +-- src/common/audio/LanczosResampler.cxx | 8 +++--- src/common/audio/LanczosResampler.hxx | 3 +- src/common/audio/Resampler.hxx | 8 +++--- src/common/audio/SimpleResampler.cxx | 14 +++++----- src/common/audio/SimpleResampler.hxx | 3 +- src/emucore/M6502.hxx | 2 +- src/emucore/tia/Audio.cxx | 7 +++-- 11 files changed, 63 insertions(+), 58 deletions(-) diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 6075e9a33..3e5f1e0fc 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -93,7 +93,7 @@ Int16* AudioQueue::enqueue(Int16* fragment) if (!myFirstFragmentForEnqueue) throw runtime_error("enqueue called empty"); newFragment = myFirstFragmentForEnqueue; - myFirstFragmentForEnqueue = 0; + myFirstFragmentForEnqueue = nullptr; return newFragment; } @@ -115,13 +115,13 @@ Int16* AudioQueue::dequeue(Int16* fragment) { lock_guard guard(myMutex); - if (mySize == 0) return 0; + if (mySize == 0) return nullptr; if (!fragment) { if (!myFirstFragmentForDequeue) throw runtime_error("dequeue called empty"); fragment = myFirstFragmentForDequeue; - myFirstFragmentForDequeue = 0; + myFirstFragmentForDequeue = nullptr; } Int16* nextFragment = myFragmentQueue.at(myNextFragment); diff --git a/src/common/AudioQueue.hxx b/src/common/AudioQueue.hxx index 66901ac09..7d64c010b 100644 --- a/src/common/AudioQueue.hxx +++ b/src/common/AudioQueue.hxx @@ -23,16 +23,16 @@ #include "bspf.hxx" /** - This class implements a an audio queue that acts both like a ring buffer - and a pool of audio fragments. The TIA emulation core fills a fragment - with samples and then returns it to the queue, receiving a new fragment - in return. The sound driver removes fragments for playback from the - queue and returns the used fragment in this process. + This class implements a an audio queue that acts both like a ring buffer + and a pool of audio fragments. The TIA emulation core fills a fragment + with samples and then returns it to the queue, receiving a new fragment + in return. The sound driver removes fragments for playback from the + queue and returns the used fragment in this process. - The queue needs to be threadsafe as the (SDL) audio driver runs on a - separate thread. Samples are stored as signed 16 bit integers - (platform endian). - */ + The queue needs to be threadsafe as the (SDL) audio driver runs on a + separate thread. Samples are stored as signed 16 bit integers + (platform endian). +*/ class AudioQueue { public: @@ -40,10 +40,10 @@ class AudioQueue /** Create a new AudioQueue. - @param fragmentSize The size (in stereo / mono samples) of each fragment - @param capacaity The number of fragments that can be queued before wrapping. - @param isStereo Whether samples are stereo or mono. - @param sampleRate The sample rate. This is not used, but can be queried. + @param fragmentSize The size (in stereo / mono samples) of each fragment + @param capacity The number of fragments that can be queued before wrapping. + @param isStereo Whether samples are stereo or mono. + @param sampleRate The sample rate. This is not used, but can be queried. */ AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate); @@ -83,17 +83,17 @@ class AudioQueue @param fragment The returned fragment. This must be empty on the first call (when there is nothing to return) */ - Int16* enqueue(Int16* fragment = 0); + Int16* enqueue(Int16* fragment = nullptr); /** - Dequeue a fragment for playback and return the played fragment. This may - return 0 if there is no queued fragment to return (in this case, the returned - fragment is not enqueued and must be passed in the next invocation). + Dequeue a fragment for playback and return the played fragment. This may + return 0 if there is no queued fragment to return (in this case, the returned + fragment is not enqueued and must be passed in the next invocation). - @param fragment The returned fragment. This must be empty on the first call (when - there is nothing to return). + @param fragment The returned fragment. This must be empty on the first call (when + there is nothing to return). */ - Int16* dequeue(Int16* fragment = 0); + Int16* dequeue(Int16* fragment = nullptr); /** Return the currently playing fragment without drawing a new one. This is called diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 2f42f5f85..a7f87caf9 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -39,8 +39,7 @@ SoundSDL2::SoundSDL2(OSystem& osystem) myIsInitializedFlag(false), myVolume(100), myVolumeFactor(0xffff), - myAudioQueue(0), - myCurrentFragment(0) + myCurrentFragment(nullptr) { myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); @@ -99,13 +98,14 @@ void SoundSDL2::setEnabled(bool state) myOSystem.settings().setValue("sound", state); myOSystem.logMessage(state ? "SoundSDL2::setEnabled(true)" : - "SoundSDL2::setEnabled(false)", 2); + "SoundSDL2::setEnabled(false)", 2); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL2::open(shared_ptr audioQueue, EmulationTiming* emulationTiming) +void SoundSDL2::open(shared_ptr audioQueue, + EmulationTiming* emulationTiming) { - this->emulationTiming = emulationTiming; + myEmulationTiming = emulationTiming; myOSystem.logMessage("SoundSDL2::open started ...", 2); mute(true); @@ -118,7 +118,7 @@ void SoundSDL2::open(shared_ptr audioQueue, EmulationTiming* emulati myAudioQueue = audioQueue; myUnderrun = true; - myCurrentFragment = 0; + myCurrentFragment = nullptr; // Adjust volume to that defined in settings setVolume(myOSystem.settings().getInt("volume")); @@ -149,8 +149,8 @@ void SoundSDL2::close() mute(true); if (myAudioQueue) myAudioQueue->closeSink(myCurrentFragment); - myAudioQueue = 0; - myCurrentFragment = 0; + myAudioQueue.reset(); + myCurrentFragment = nullptr; myOSystem.logMessage("SoundSDL2::close", 2); @@ -167,7 +167,8 @@ void SoundSDL2::mute(bool state) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::reset() -{} +{ +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::setVolume(Int32 percent) @@ -233,14 +234,15 @@ void SoundSDL2::processFragment(float* stream, uInt32 length) void SoundSDL2::initResampler() { Resampler::NextFragmentCallback nextFragmentCallback = [this] () -> Int16* { - Int16* nextFragment = 0; + Int16* nextFragment = nullptr; if (myUnderrun) - nextFragment = myAudioQueue->size() > emulationTiming->prebufferFragmentCount() ? myAudioQueue->dequeue(myCurrentFragment) : 0; + nextFragment = myAudioQueue->size() > myEmulationTiming->prebufferFragmentCount() ? + myAudioQueue->dequeue(myCurrentFragment) : nullptr; else nextFragment = myAudioQueue->dequeue(myCurrentFragment); - myUnderrun = nextFragment == 0; + myUnderrun = nextFragment == nullptr; if (nextFragment) myCurrentFragment = nextFragment; return nextFragment; diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 9b4922ade..38156f79a 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -33,7 +33,7 @@ class EmulationTiming; /** This class implements the sound API for SDL. - @author Stephen Anthony and Bradford W. Mott + @author Stephen Anthony and Christian Speckner (DirtyHairy) */ class SoundSDL2 : public Sound { @@ -130,7 +130,7 @@ class SoundSDL2 : public Sound shared_ptr myAudioQueue; - EmulationTiming* emulationTiming; + EmulationTiming* myEmulationTiming; Int16* myCurrentFragment; bool myUnderrun; diff --git a/src/common/audio/LanczosResampler.cxx b/src/common/audio/LanczosResampler.cxx index 07ee8b064..0bce3e1ac 100644 --- a/src/common/audio/LanczosResampler.cxx +++ b/src/common/audio/LanczosResampler.cxx @@ -75,10 +75,10 @@ LanczosResampler::LanczosResampler( myKernelSize(2 * kernelParameter), myCurrentKernelIndex(0), myKernelParameter(kernelParameter), - myBuffer(0), - myBufferL(0), - myBufferR(0), - myCurrentFragment(0), + myBuffer(nullptr), + myBufferL(nullptr), + myBufferR(nullptr), + myCurrentFragment(nullptr), myFragmentIndex(0), myIsUnderrun(true), myTimeIndex(0) diff --git a/src/common/audio/LanczosResampler.hxx b/src/common/audio/LanczosResampler.hxx index 07b5956ef..2ea5a6c04 100644 --- a/src/common/audio/LanczosResampler.hxx +++ b/src/common/audio/LanczosResampler.hxx @@ -22,7 +22,8 @@ #include "Resampler.hxx" #include "ConvolutionBuffer.hxx" -class LanczosResampler : public Resampler { +class LanczosResampler : public Resampler +{ public: LanczosResampler( Resampler::Format formatFrom, diff --git a/src/common/audio/Resampler.hxx b/src/common/audio/Resampler.hxx index 9fb7a8611..cd39818e4 100644 --- a/src/common/audio/Resampler.hxx +++ b/src/common/audio/Resampler.hxx @@ -30,10 +30,10 @@ class Resampler { class Format { public: - Format(uInt32 sampleRate, uInt32 fragmentSize, bool stereo) : - sampleRate(sampleRate), - fragmentSize(fragmentSize), - stereo(stereo) + Format(uInt32 f_sampleRate, uInt32 f_fragmentSize, bool f_stereo) : + sampleRate(f_sampleRate), + fragmentSize(f_fragmentSize), + stereo(f_stereo) {} public: diff --git a/src/common/audio/SimpleResampler.cxx b/src/common/audio/SimpleResampler.cxx index 6785886d8..5e7b4ce65 100644 --- a/src/common/audio/SimpleResampler.cxx +++ b/src/common/audio/SimpleResampler.cxx @@ -22,13 +22,13 @@ SimpleResampler::SimpleResampler( Resampler::Format formatFrom, Resampler::Format formatTo, Resampler::NextFragmentCallback nextFragmentCallback) -: - Resampler(formatFrom, formatTo, nextFragmentCallback), - myCurrentFragment(0), - myTimeIndex(0), - myFragmentIndex(0), - myIsUnderrun(true) -{} + : Resampler(formatFrom, formatTo, nextFragmentCallback), + myCurrentFragment(nullptr), + myTimeIndex(0), + myFragmentIndex(0), + myIsUnderrun(true) +{ +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SimpleResampler::fillFragment(float* fragment, uInt32 length) diff --git a/src/common/audio/SimpleResampler.hxx b/src/common/audio/SimpleResampler.hxx index 8495ba7a7..92de9e05f 100644 --- a/src/common/audio/SimpleResampler.hxx +++ b/src/common/audio/SimpleResampler.hxx @@ -21,7 +21,8 @@ #include "bspf.hxx" #include "Resampler.hxx" -class SimpleResampler : public Resampler { +class SimpleResampler : public Resampler +{ public: SimpleResampler( Resampler::Format formatFrom, diff --git a/src/emucore/M6502.hxx b/src/emucore/M6502.hxx index 5b96c7342..2dad3de57 100644 --- a/src/emucore/M6502.hxx +++ b/src/emucore/M6502.hxx @@ -110,7 +110,7 @@ class M6502 : public Serializable is executed, someone stops execution, or an error occurs. Answers true iff execution stops normally. - @param number Indicates the number of cycles to execute. Not that the actual + @param cycles Indicates the number of cycles to execute. Not that the actual granularity of the CPU is instructions, so this is only accurate up to a couple of cycles @return true iff execution stops normally diff --git a/src/emucore/tia/Audio.cxx b/src/emucore/tia/Audio.cxx index 19e06ac2f..74658f8c5 100644 --- a/src/emucore/tia/Audio.cxx +++ b/src/emucore/tia/Audio.cxx @@ -35,8 +35,8 @@ namespace { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Audio::Audio() - : myAudioQueue(0), - myCurrentFragment(0) + : myAudioQueue(nullptr), + myCurrentFragment(nullptr) { for (uInt8 i = 0; i <= 0x1e; i++) myMixingTableSum[i] = mixingTableEntry(i, 0x1e); for (uInt8 i = 0; i <= 0x0f; i++) myMixingTableIndividual[i] = mixingTableEntry(i, 0x0f); @@ -65,7 +65,8 @@ void Audio::setAudioQueue(shared_ptr queue) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Audio::tick() -{ switch (myCounter) { +{ + switch (myCounter) { case 9: case 81: myChannel0.phase0(); From 4c7ad7a0b65555ba1570756e3afc1e6b4d7c44f9 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Fri, 11 May 2018 21:26:22 -0230 Subject: [PATCH 43/77] Replace 'new' calls with unique_ptr. - @DirtHairy, you can revert this if you like, but unless there is some issue, I'd rather use smart pointers with auto-deallocation. --- src/common/AudioQueue.cxx | 14 ++++--------- src/common/AudioQueue.hxx | 7 +------ src/common/audio/ConvolutionBuffer.cxx | 14 +++++-------- src/common/audio/ConvolutionBuffer.hxx | 7 +++---- src/common/audio/LanczosResampler.cxx | 27 ++++++++------------------ src/common/audio/LanczosResampler.hxx | 10 +++++----- 6 files changed, 26 insertions(+), 53 deletions(-) diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 3e5f1e0fc..bc6964fe3 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -32,22 +32,16 @@ AudioQueue::AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt { const uInt8 sampleSize = myIsStereo ? 2 : 1; - myFragmentBuffer = new Int16[myFragmentSize * sampleSize * (capacity + 2)]; + myFragmentBuffer = make_unique(myFragmentSize * sampleSize * (capacity + 2)); for (uInt32 i = 0; i < capacity; i++) - myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer + i * sampleSize * myFragmentSize; + myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer.get() + i * sampleSize * myFragmentSize; myAllFragments[capacity] = myFirstFragmentForEnqueue = - myFragmentBuffer + capacity * sampleSize * myFragmentSize; + myFragmentBuffer.get() + capacity * sampleSize * myFragmentSize; myAllFragments[capacity + 1] = myFirstFragmentForDequeue = - myFragmentBuffer + (capacity + 1) * sampleSize * myFragmentSize; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -AudioQueue::~AudioQueue() -{ - delete[] myFragmentBuffer; + myFragmentBuffer.get() + (capacity + 1) * sampleSize * myFragmentSize; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/AudioQueue.hxx b/src/common/AudioQueue.hxx index 7d64c010b..e0d7c4868 100644 --- a/src/common/AudioQueue.hxx +++ b/src/common/AudioQueue.hxx @@ -47,11 +47,6 @@ class AudioQueue */ AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate); - /** - We need a destructor to deallocate the individual fragment buffers. - */ - ~AudioQueue(); - /** Capacity getter. */ @@ -119,7 +114,7 @@ class AudioQueue vector myAllFragments; // We allocate a consecutive slice of memory for the fragments. - Int16* myFragmentBuffer; + unique_ptr myFragmentBuffer; // The nubmer if queued fragments uInt32 mySize; diff --git a/src/common/audio/ConvolutionBuffer.cxx b/src/common/audio/ConvolutionBuffer.cxx index 0badf2d5e..c0513ad45 100644 --- a/src/common/audio/ConvolutionBuffer.cxx +++ b/src/common/audio/ConvolutionBuffer.cxx @@ -18,16 +18,12 @@ #include "ConvolutionBuffer.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -ConvolutionBuffer::ConvolutionBuffer(uInt32 size) : myFirstIndex(0), mySize(size) +ConvolutionBuffer::ConvolutionBuffer(uInt32 size) + : myFirstIndex(0), + mySize(size) { - myData = new float[mySize]; - memset(myData, 0, mySize * sizeof(float)); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -ConvolutionBuffer::~ConvolutionBuffer() -{ - delete[] myData; + myData = make_unique(mySize); + memset(myData.get(), 0, mySize * sizeof(float)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/audio/ConvolutionBuffer.hxx b/src/common/audio/ConvolutionBuffer.hxx index bf85ace41..d0a71ad3b 100644 --- a/src/common/audio/ConvolutionBuffer.hxx +++ b/src/common/audio/ConvolutionBuffer.hxx @@ -20,20 +20,19 @@ #include "bspf.hxx" -class ConvolutionBuffer { +class ConvolutionBuffer +{ public: ConvolutionBuffer(uInt32 size); - ~ConvolutionBuffer(); - void shift(float nextValue); float convoluteWith(float* kernel) const; private: - float* myData; + unique_ptr myData; uInt32 myFirstIndex; diff --git a/src/common/audio/LanczosResampler.cxx b/src/common/audio/LanczosResampler.cxx index 0bce3e1ac..56fa9aac1 100644 --- a/src/common/audio/LanczosResampler.cxx +++ b/src/common/audio/LanczosResampler.cxx @@ -75,35 +75,24 @@ LanczosResampler::LanczosResampler( myKernelSize(2 * kernelParameter), myCurrentKernelIndex(0), myKernelParameter(kernelParameter), - myBuffer(nullptr), - myBufferL(nullptr), - myBufferR(nullptr), myCurrentFragment(nullptr), myFragmentIndex(0), myIsUnderrun(true), myTimeIndex(0) { - myPrecomputedKernels = new float[myPrecomputedKernelCount * myKernelSize]; + myPrecomputedKernels = make_unique(myPrecomputedKernelCount * myKernelSize); - if (myFormatFrom.stereo) { - myBufferL = new ConvolutionBuffer(myKernelSize); - myBufferR = new ConvolutionBuffer(myKernelSize); + if (myFormatFrom.stereo) + { + myBufferL = make_unique(myKernelSize); + myBufferR = make_unique(myKernelSize); } else - myBuffer = new ConvolutionBuffer(myKernelSize); + myBuffer = make_unique(myKernelSize); precomputeKernels(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -LanczosResampler::~LanczosResampler() { - delete[] myPrecomputedKernels; - - if (myBuffer) delete myBuffer; - if (myBufferL) delete myBufferL; - if (myBufferR) delete myBufferR; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void LanczosResampler::precomputeKernels() { @@ -111,7 +100,7 @@ void LanczosResampler::precomputeKernels() uInt32 timeIndex = 0; for (uInt32 i = 0; i < myPrecomputedKernelCount; i++) { - float* kernel = myPrecomputedKernels + myKernelSize * i; + float* kernel = myPrecomputedKernels.get() + myKernelSize * i; // The kernel is normalized such to be evaluate on time * formatFrom.sampleRate float center = static_cast(timeIndex) / static_cast(myFormatTo.sampleRate); @@ -155,7 +144,7 @@ void LanczosResampler::fillFragment(float* fragment, uInt32 length) const uInt32 outputSamples = myFormatTo.stereo ? (length >> 1) : length; for (uInt32 i = 0; i < outputSamples; i++) { - float* kernel = myPrecomputedKernels + (myCurrentKernelIndex * myKernelSize); + float* kernel = myPrecomputedKernels.get() + (myCurrentKernelIndex * myKernelSize); myCurrentKernelIndex = (myCurrentKernelIndex + 1) % myPrecomputedKernelCount; if (myFormatFrom.stereo) { diff --git a/src/common/audio/LanczosResampler.hxx b/src/common/audio/LanczosResampler.hxx index 2ea5a6c04..0f92f48d5 100644 --- a/src/common/audio/LanczosResampler.hxx +++ b/src/common/audio/LanczosResampler.hxx @@ -34,7 +34,7 @@ class LanczosResampler : public Resampler virtual void fillFragment(float* fragment, uInt32 length); - virtual ~LanczosResampler(); + virtual ~LanczosResampler() = default; private: @@ -46,14 +46,14 @@ class LanczosResampler : public Resampler uInt32 myPrecomputedKernelCount; uInt32 myKernelSize; - float* myPrecomputedKernels; uInt32 myCurrentKernelIndex; + unique_ptr myPrecomputedKernels; uInt32 myKernelParameter; - ConvolutionBuffer* myBuffer; - ConvolutionBuffer* myBufferL; - ConvolutionBuffer* myBufferR; + unique_ptr myBuffer; + unique_ptr myBufferL; + unique_ptr myBufferR; Int16* myCurrentFragment; uInt32 myFragmentIndex; From 7391c55f9efe117546521c8510675d763ee0957f Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 12 May 2018 13:32:31 +0200 Subject: [PATCH 44/77] Documentation, minor optimization. --- src/common/audio/ConvolutionBuffer.cxx | 2 +- src/common/audio/LanczosResampler.cxx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/audio/ConvolutionBuffer.cxx b/src/common/audio/ConvolutionBuffer.cxx index c0513ad45..983b06209 100644 --- a/src/common/audio/ConvolutionBuffer.cxx +++ b/src/common/audio/ConvolutionBuffer.cxx @@ -29,8 +29,8 @@ ConvolutionBuffer::ConvolutionBuffer(uInt32 size) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ConvolutionBuffer::shift(float nextValue) { + myData[myFirstIndex] = nextValue; myFirstIndex = (myFirstIndex + 1) % mySize; - myData[(myFirstIndex + mySize - 1) % mySize] = nextValue; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/audio/LanczosResampler.cxx b/src/common/audio/LanczosResampler.cxx index 56fa9aac1..25ecd82ec 100644 --- a/src/common/audio/LanczosResampler.cxx +++ b/src/common/audio/LanczosResampler.cxx @@ -114,11 +114,14 @@ void LanczosResampler::precomputeKernels() // Next step: time += 1 / formatTo.sampleRate // // By construction, we limit the argument during kernel evaluation to 0 .. 1, which - // corresponds to 0 .. 1 / formatFrom.sampleRate for the time + // corresponds to 0 .. 1 / formatFrom.sampleRate for time. To implement this, we decompose + // time as follows: // // time = N / formatFrom.sampleRate + delta + // timeIndex = N * formatTo.sampleRate + delta * formatTo.sampleRate * formatFrom.sampleRate // - // with max integral N and delta, then time = delta -> modulus + // with N integral and delta < 0. From this, it follows that we replace + // time with delta, i.e. take the modulus of timeIndex. timeIndex = (timeIndex + myFormatFrom.sampleRate) % myFormatTo.sampleRate; } } From f01553bdc713472035ec742c30aae683152c893c Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 12 May 2018 16:21:58 +0200 Subject: [PATCH 45/77] More audio queue tuning -> less lag. --- src/emucore/EmulationTiming.cxx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/emucore/EmulationTiming.cxx b/src/emucore/EmulationTiming.cxx index 4708c65e6..7f850d148 100644 --- a/src/emucore/EmulationTiming.cxx +++ b/src/emucore/EmulationTiming.cxx @@ -19,8 +19,13 @@ namespace { constexpr uInt32 AUDIO_HALF_FRAMES_PER_FRAGMENT = 1; - constexpr uInt32 QUEUE_CAPACITY_SAFETY_FACTOR = 2; - constexpr uInt32 PREBUFFER_FRAGMENT_COUNT = 2; + constexpr uInt32 QUEUE_CAPACITY_EXTRA_FRAGMENTS = 1; + constexpr uInt32 PREBUFFER_EXTRA_FRAGMENT_COUNT = 2; + + uInt32 discreteDivCeil(uInt32 n, uInt32 d) + { + return n / d + ((n % d == 0) ? 0 : 1); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -117,14 +122,13 @@ uInt32 EmulationTiming::audioSampleRate() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::audioQueueCapacity() const { - uInt32 capacity = (myPlaybackPeriod * audioSampleRate()) / (audioFragmentSize() * myPlaybackRate) + 1; - uInt32 minCapacity = (maxCyclesPerTimeslice() * audioSampleRate()) / (audioFragmentSize() * cyclesPerSecond()) + 1; + uInt32 minCapacity = discreteDivCeil(maxCyclesPerTimeslice() * audioSampleRate(), audioFragmentSize() * cyclesPerSecond()); - return std::max(prebufferFragmentCount() + 1, QUEUE_CAPACITY_SAFETY_FACTOR * std::max(capacity, minCapacity)); + return std::max(prebufferFragmentCount(), minCapacity) + QUEUE_CAPACITY_EXTRA_FRAGMENTS; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::prebufferFragmentCount() const { - return (myPlaybackPeriod * audioSampleRate()) / (audioFragmentSize() * myPlaybackRate) + PREBUFFER_FRAGMENT_COUNT; + return discreteDivCeil(myPlaybackPeriod * audioSampleRate(), audioFragmentSize() * myPlaybackRate) + PREBUFFER_EXTRA_FRAGMENT_COUNT; } From c3766021d8e542e57e6dc4f68e2b446853d0ef04 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 12 May 2018 16:52:54 +0200 Subject: [PATCH 46/77] Add a log message for audio buffer overflow messages. --- src/common/AudioQueue.cxx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index bc6964fe3..fb80a26ce 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -99,7 +99,10 @@ Int16* AudioQueue::enqueue(Int16* fragment) myFragmentQueue.at(fragmentIndex) = fragment; if (mySize < capacity) mySize++; - else myNextFragment = (myNextFragment + 1) % capacity; + else { + myNextFragment = (myNextFragment + 1) % capacity; + (cerr << "audio buffer overflow\n").flush(); + } return newFragment; } From dc77f5d5f1373e34310d48308833673eda9e7808 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Sat, 12 May 2018 17:04:33 -0230 Subject: [PATCH 47/77] Variables declared as uInt64 should be serialized as such. --- src/common/StateManager.cxx | 2 +- src/emucore/tia/PaddleReader.cxx | 4 ++-- src/emucore/tia/TIA.cxx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/StateManager.cxx b/src/common/StateManager.cxx index 768d4c928..66a62bf29 100644 --- a/src/common/StateManager.cxx +++ b/src/common/StateManager.cxx @@ -27,7 +27,7 @@ #include "StateManager.hxx" -#define STATE_HEADER "05010000state" +#define STATE_HEADER "05019000state" // #define MOVIE_HEADER "03030000movie" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/tia/PaddleReader.cxx b/src/emucore/tia/PaddleReader.cxx index 8ae0ba201..dbc3d48aa 100644 --- a/src/emucore/tia/PaddleReader.cxx +++ b/src/emucore/tia/PaddleReader.cxx @@ -116,7 +116,7 @@ bool PaddleReader::save(Serializer& out) const out.putDouble(myU); out.putDouble(myValue); - out.putDouble(myTimestamp); + out.putLong(myTimestamp); out.putInt(int(myConsoleTiming)); out.putDouble(myClockFreq); @@ -144,7 +144,7 @@ bool PaddleReader::load(Serializer& in) myU = in.getDouble(); myValue = in.getDouble(); - myTimestamp = in.getDouble(); + myTimestamp = in.getLong(); myConsoleTiming = ConsoleTiming(in.getInt()); myClockFreq = in.getDouble(); diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 84dbdf76e..c07abedae 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -275,7 +275,7 @@ bool TIA::save(Serializer& out) const out.putByte(myColorHBlank); - out.putDouble(myTimestamp); + out.putLong(myTimestamp); out.putByteArray(myShadowRegisters, 64); @@ -343,7 +343,7 @@ bool TIA::load(Serializer& in) myColorHBlank = in.getByte(); - myTimestamp = in.getDouble(); + myTimestamp = in.getLong(); in.getByteArray(myShadowRegisters, 64); From cf8f76a0e1a319ba20810d60c7489f343f8010c7 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Sat, 12 May 2018 22:33:15 -0230 Subject: [PATCH 48/77] Fix [[nodiscard]] warning in VS (applies only to C++17 mode). --- src/debugger/DebuggerParser.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx index 53c4596e3..3640e4584 100644 --- a/src/debugger/DebuggerParser.cxx +++ b/src/debugger/DebuggerParser.cxx @@ -414,7 +414,7 @@ bool DebuggerParser::validateArgs(int cmd) { if(required) { - commandResult.str(); + void(commandResult.str()); outputCommandError("missing required argument(s)", cmd); return false; // needed args. didn't get 'em. } @@ -513,13 +513,13 @@ cerr << "curCount = " << curCount << endl if(curCount < argRequiredCount) { - commandResult.str(); + void(commandResult.str()); outputCommandError("missing required argument(s)", cmd); return false; } else if(argCount > curCount) { - commandResult.str(); + void(commandResult.str()); outputCommandError("too many arguments", cmd); return false; } From adbde4e4db606fff1586156862bb04602d448216 Mon Sep 17 00:00:00 2001 From: thrust26 Date: Tue, 15 May 2018 20:51:29 +0200 Subject: [PATCH 49/77] preliminary audio dialog changes --- src/emucore/Settings.cxx | 1 + src/gui/AudioDialog.cxx | 72 +++++++++++++++++++++++++++++++++++----- src/gui/AudioDialog.hxx | 10 ++++-- 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 99c223bfb..81433eef2 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -70,6 +70,7 @@ Settings::Settings(OSystem& osystem) // Sound options setInternal("sound", "true"); + setInternal("aud.mode", "balanced"); setInternal("fragsize", "512"); setInternal("freq", "44100"); setInternal("volume", "100"); diff --git a/src/gui/AudioDialog.cxx b/src/gui/AudioDialog.cxx index 1eb93c36a..cf5773bd3 100644 --- a/src/gui/AudioDialog.cxx +++ b/src/gui/AudioDialog.cxx @@ -44,14 +44,14 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, fontWidth = font.getMaxCharWidth(), fontHeight = font.getFontHeight(); int xpos, ypos; - int lwidth = font.getStringWidth("Sample Size (*) "), + int lwidth = font.getStringWidth("Resampling quality "), pwidth = font.getStringWidth("512 bytes"); WidgetArray wid; VariantList items; // Set real dimensions _w = 35 * fontWidth + HBORDER * 2; - _h = 7 * (lineHeight + 4) + VBORDER + _th; + _h = 11 * (lineHeight + 4) + VBORDER + _th; xpos = HBORDER; ypos = VBORDER + _th; @@ -64,13 +64,26 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, xpos += INDENT; // Volume - myVolumeSlider = new SliderWidget(this, font, xpos, ypos, 11 * fontWidth + 5, lineHeight, - "Volume ", lwidth, 0, 4 * fontWidth, "%"); + myVolumeSlider = new SliderWidget(this, font, xpos, ypos, + "Volume ", 0, 0, 4 * fontWidth, "%"); myVolumeSlider->setMinValue(1); myVolumeSlider->setMaxValue(100); wid.push_back(myVolumeSlider); ypos += lineHeight + 4; + // + VarList::push_back(items, "Minimal Lag", "minlag"); + VarList::push_back(items, "Balanced", "balanced"); + VarList::push_back(items, "Max. Quality", "maxquality"); + VarList::push_back(items, "Custom", "Custom"); + myModePopup = new PopUpWidget(this, font, xpos, ypos, + font.getStringWidth("Max. Quality"), lineHeight, + items, "Mode (*) ", 0, kModeChanged); + wid.push_back(myModePopup); + ypos += lineHeight + 4; + xpos += INDENT; + // Fragment size + items.clear(); VarList::push_back(items, "128 bytes", "128"); VarList::push_back(items, "256 bytes", "256"); VarList::push_back(items, "512 bytes", "512"); @@ -92,13 +105,39 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, pwidth, lineHeight, items, "Frequency (*) ", lwidth); wid.push_back(myFreqPopup); + ypos += lineHeight + 4; + + + // Resampling quality + items.clear(); + VarList::push_back(items, "Low", "low"); + VarList::push_back(items, "Medium", "medium"); + VarList::push_back(items, "High", "high"); + myResamplingPopup = new PopUpWidget(this, font, xpos, ypos, + pwidth, lineHeight, + items, "Resampling quality ", lwidth); + wid.push_back(myResamplingPopup); + ypos += lineHeight + 4; + + // Param 1 + myHeadroomSlider = new SliderWidget(this, font, xpos, ypos, + "Headroom "); + myHeadroomSlider->setMinValue(1); myHeadroomSlider->setMaxValue(10); + wid.push_back(myHeadroomSlider); + ypos += lineHeight + 4; + + // Param 2 + myBufferSizeSlider = new SliderWidget(this, font, xpos, ypos, + "Buffer size "); + myBufferSizeSlider->setMinValue(1); myBufferSizeSlider->setMaxValue(10); + wid.push_back(myBufferSizeSlider); // Add message concerning usage ypos = _h - fontHeight * 2 - 24; const GUI::Font& infofont = instance().frameBuffer().infoFont(); - new StaticTextWidget(this, infofont, HBORDER, ypos, + new StaticTextWidget(this, infofont, HBORDER, ypos, "(*) Requires application restart");/* , font.getStringWidth("(*) Requires application restart"), fontHeight, - "(*) Requires application restart", TextAlign::Left); + "(*) Requires application restart", TextAlign::Left);*/ // Add Defaults, OK and Cancel buttons addDefaultsOKCancelBGroup(wid, font); @@ -170,10 +209,23 @@ void AudioDialog::setDefaults() void AudioDialog::handleSoundEnableChange(bool active) { myVolumeSlider->setEnabled(active); - myFragsizePopup->setEnabled(active); - myFreqPopup->setEnabled(active); + myModePopup->setEnabled(active); + handleModeChange(active); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioDialog::handleModeChange(bool active) +{ + bool userMode = active && "Custom" == myModePopup->getSelectedName(); + + myFragsizePopup->setEnabled(userMode); + myFreqPopup->setEnabled(userMode); + myResamplingPopup->setEnabled(userMode); + myHeadroomSlider->setEnabled(userMode); + myBufferSizeSlider->setEnabled(userMode); +} + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioDialog::handleCommand(CommandSender* sender, int cmd, int data, int id) @@ -193,6 +245,10 @@ void AudioDialog::handleCommand(CommandSender* sender, int cmd, handleSoundEnableChange(data == 1); break; + case kModeChanged: + handleModeChange(true); + break; + default: Dialog::handleCommand(sender, cmd, data, 0); break; diff --git a/src/gui/AudioDialog.hxx b/src/gui/AudioDialog.hxx index 1ca83be00..91ff75df6 100644 --- a/src/gui/AudioDialog.hxx +++ b/src/gui/AudioDialog.hxx @@ -41,17 +41,23 @@ class AudioDialog : public Dialog void setDefaults() override; void handleSoundEnableChange(bool active); + void handleModeChange(bool active); void handleCommand(CommandSender* sender, int cmd, int data, int id) override; private: enum { - kSoundEnableChanged = 'ADse' + kSoundEnableChanged = 'ADse', + kModeChanged = 'ADmc' }; + CheckboxWidget* mySoundEnableCheckbox; SliderWidget* myVolumeSlider; + PopUpWidget* myModePopup; PopUpWidget* myFragsizePopup; PopUpWidget* myFreqPopup; - CheckboxWidget* mySoundEnableCheckbox; + PopUpWidget* myResamplingPopup; + SliderWidget* myHeadroomSlider; + SliderWidget* myBufferSizeSlider; private: // Following constructors and assignment operators not supported From 639b6af1e9ffb40c17e1abfa5e9d966827955773 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 16 May 2018 13:09:29 +0200 Subject: [PATCH 50/77] Fix missing audio reset. --- src/emucore/tia/TIA.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index c07abedae..84f2bd8d1 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -164,6 +164,8 @@ void TIA::reset() myInput0.reset(); myInput1.reset(); + myAudio.reset(); + myTimestamp = 0; for (PaddleReader& paddleReader : myPaddleReaders) paddleReader.reset(myTimestamp); From ed6eae6a67e0c8e63de037435e8ae6ccf20d8ddb Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 17 May 2018 22:56:07 +0200 Subject: [PATCH 51/77] Rerender only if there is actual change. --- src/emucore/FrameBuffer.cxx | 19 ++------------- src/emucore/FrameBuffer.hxx | 2 +- src/emucore/OSystem.cxx | 46 +++++++++++++++++++++++++------------ src/emucore/tia/TIA.cxx | 24 +++++++++++++------ src/emucore/tia/TIA.hxx | 17 ++++++++++++++ 5 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 4db75a9c4..63ec56137 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -258,30 +258,17 @@ FBInitStatus FrameBuffer::createDisplay(const string& title, } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Int64 FrameBuffer::update(uInt32 maxCycles) +void FrameBuffer::update() { // Determine which mode we are in (from the EventHandler) // Take care of S_EMULATE mode here, otherwise let the GUI // figure out what to draw - Int64 cycles = -1; - invalidate(); switch(myOSystem.eventHandler().state()) { case EventHandlerState::EMULATION: { - // Run the console for one frame - // Note that the debugger can cause a breakpoint to occur, which changes - // the EventHandler state 'behind our back' - we need to check for that - cycles = myOSystem.console().tia().update(maxCycles); - #ifdef DEBUGGER_SUPPORT - if(myOSystem.eventHandler().state() != EventHandlerState::EMULATION) break; - #endif - if(myOSystem.eventHandler().frying()) - myOSystem.console().fry(); - - // And update the screen myTIASurface->render(); // Show frame statistics @@ -342,7 +329,7 @@ Int64 FrameBuffer::update(uInt32 maxCycles) } case EventHandlerState::NONE: - return -1; + return; } // Draw any pending messages @@ -351,8 +338,6 @@ Int64 FrameBuffer::update(uInt32 maxCycles) // Do any post-frame stuff postFrameUpdate(); - - return cycles; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index b1806a963..c6533eca4 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -116,7 +116,7 @@ class FrameBuffer drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles spent during emulation, or -1 if not applicable. */ - Int64 update(uInt32 maxCycles = 50000); + void update(); /** Shows a message onscreen. diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index d6c9b1760..e0126e4aa 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -52,6 +52,7 @@ #include "SerialPort.hxx" #include "StateManager.hxx" #include "Version.hxx" +#include "TIA.hxx" #include "OSystem.hxx" @@ -646,23 +647,38 @@ void OSystem::mainLoop() myEventHandler->poll(getTicks()); if(myQuitLoop) break; // Exit if the user wants to quit - Int64 totalCycles = 0; - const Int64 minCycles = myConsole ? myConsole->emulationTiming().minCyclesPerTimeslice() : 50000; - const Int64 maxCycles = myConsole ? myConsole->emulationTiming().maxCyclesPerTimeslice() : 0; - const uInt32 cyclesPerSecond = myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1; + double timesliceSeconds; - do { - Int64 cycles = myFrameBuffer->update(totalCycles > 0 ? minCycles - totalCycles : maxCycles); - if (cycles < 0) break; + if (myEventHandler->state() == EventHandlerState::EMULATION) { + Int64 totalCycles = 0; + const Int64 minCycles = myConsole ? myConsole->emulationTiming().minCyclesPerTimeslice() : 50000; + const Int64 maxCycles = myConsole ? myConsole->emulationTiming().maxCyclesPerTimeslice() : 0; + const uInt32 cyclesPerSecond = myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1; - totalCycles += cycles; - } while (totalCycles < minCycles); + do { + Int64 cycles = myConsole ? myConsole->tia().update(totalCycles > 0 ? minCycles - totalCycles : maxCycles) : 0; - duration timeslice ( - (totalCycles > 0) ? - static_cast(totalCycles) / static_cast(cyclesPerSecond) : - 1. / 30. - ); + totalCycles += cycles; + } while (myConsole && totalCycles < minCycles && myEventHandler->state() == EventHandlerState::EMULATION); + + if (myEventHandler->state() == EventHandlerState::EMULATION && myEventHandler->frying()) + myConsole->fry(); + + timesliceSeconds = static_cast(totalCycles) / static_cast(cyclesPerSecond); + } + else + timesliceSeconds = 1. / 30.; + + if (myEventHandler->state() == EventHandlerState::EMULATION) { + if (myConsole && myConsole->tia().newFramePending()) { + myFrameBuffer->update(); + myConsole->tia().clearNewFramePending(); + } + } + else + myFrameBuffer->update(); + + duration timeslice(timesliceSeconds); virtualTime += duration_cast(timeslice); time_point now = high_resolution_clock::now(); @@ -670,7 +686,7 @@ void OSystem::mainLoop() if (duration_cast>(now - virtualTime).count() > 0) virtualTime = now; else if (virtualTime > now) { - if (busyWait && totalCycles > 0) { + if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) { while (high_resolution_clock::now() < virtualTime); } else std::this_thread::sleep_until(virtualTime); diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 84f2bd8d1..d3722bc16 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -180,6 +180,8 @@ void TIA::reset() frameReset(); // Recalculate the size of the display } + myNewFramePending = false; + // Must be done last, after all other items have reset enableFixedColors(mySettings.getBool(mySettings.getBool("dev.settings") ? "dev.debugcolors" : "plr.debugcolors")); setFixedColorPalette(mySettings.getString("tia.dbgcolors")); @@ -192,6 +194,7 @@ void TIA::reset() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::frameReset() { + memset(myBackBuffer, 0, 160 * TIAConstants::frameBufferHeight); memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight); enableColorLoss(mySettings.getBool("dev.settings") ? "dev.colorloss" : "plr.colorloss"); } @@ -778,7 +781,9 @@ bool TIA::saveDisplay(Serializer& out) const { try { - out.putByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight); + out.putByteArray(myFramebuffer, 160* TIAConstants::frameBufferHeight); + out.putByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight); + out.putBool(myNewFramePending); } catch(...) { @@ -796,6 +801,8 @@ bool TIA::loadDisplay(Serializer& in) { // Reset frame buffer pointer and data in.getByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight); + in.getByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight); + myNewFramePending = in.getBool(); } catch(...) { @@ -1149,12 +1156,15 @@ void TIA::onFrameComplete() myCyclesAtFrameStart = mySystem->cycles(); if (myXAtRenderingStart > 0) - memset(myFramebuffer, 0, myXAtRenderingStart); + memset(myBackBuffer, 0, myXAtRenderingStart); // Blank out any extra lines not drawn this frame const Int32 missingScanlines = myFrameManager->missingScanlines(); if (missingScanlines > 0) - memset(myFramebuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160); + memset(myBackBuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160); + + memcpy(&myFramebuffer, &myBackBuffer, 160 * TIAConstants::frameBufferHeight); + myNewFramePending = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1265,7 +1275,7 @@ void TIA::applyRsync() myHctrDelta = 225 - myHctr; if (myFrameManager->isRendering()) - memset(myFramebuffer + myFrameManager->getY() * 160 + x, 0, 160 - x); + memset(myBackBuffer + myFrameManager->getY() * 160 + x, 0, 160 - x); myHctr = 225; } @@ -1304,7 +1314,7 @@ void TIA::cloneLastLine() if (!myFrameManager->isRendering() || y == 0) return; - uInt8* buffer = myFramebuffer; + uInt8* buffer = myBackBuffer; memcpy(buffer + y * 160, buffer + (y-1) * 160, 160); } @@ -1377,7 +1387,7 @@ void TIA::renderPixel(uInt32 x, uInt32 y) } } - myFramebuffer[y * 160 + x] = color; + myBackBuffer[y * 160 + x] = color; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1403,7 +1413,7 @@ void TIA::flushLineCache() void TIA::clearHmoveComb() { if (myFrameManager->isRendering() && myHstate == HState::blank) - memset(myFramebuffer + myFrameManager->getY() * 160, myColorHBlank, 8); + memset(myBackBuffer + myFrameManager->getY() * 160, myColorHBlank, 8); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index 03b1161b8..d0eced062 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -201,6 +201,16 @@ class TIA : public Device */ uInt64 update(uInt32 maxCycles = 50000); + /** + Did we generate a new frame? + */ + bool newFramePending() { return myNewFramePending; } + + /** + Clear the flag + */ + void clearNewFramePending() { myNewFramePending = false; } + /** Returns a pointer to the internal frame buffer. */ @@ -657,6 +667,13 @@ class TIA : public Device // Pointer to the internal color-index-based frame buffer uInt8 myFramebuffer[160 * TIAConstants::frameBufferHeight]; + // The frame is rendered to the backbuffer and only copied to the framebuffer + // upon completion + uInt8 myBackBuffer[160 * TIAConstants::frameBufferHeight]; + + // Did we emit a frame? + bool myNewFramePending; + /** * Setting this to true injects random values into undefined reads. */ From ae0faaabfc4372e96e2d5dbfeded59e67c4b2a1d Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Tue, 22 May 2018 00:18:07 +0200 Subject: [PATCH 52/77] Fix cycle counting in CPU. --- src/emucore/M6502.cxx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index 6005fcaad..04f740f36 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -240,12 +240,13 @@ inline bool M6502::_execute(uInt32 cycles) M6532& riot = mySystem->m6532(); #endif - uInt32 currentCycles = 0; + uInt64 previousCycles = mySystem->cycles(); + uInt64 currentCycles = 0; // Loop until execution is stopped or a fatal error occurs for(;;) { - for(; !myExecutionStatus && (currentCycles < cycles * SYSTEM_CYCLES_PER_CPU); currentCycles += SYSTEM_CYCLES_PER_CPU) + while (!myExecutionStatus && currentCycles < cycles * SYSTEM_CYCLES_PER_CPU) { #ifdef DEBUGGER_SUPPORT if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag) @@ -297,6 +298,8 @@ inline bool M6502::_execute(uInt32 cycles) myExecutionStatus |= FatalErrorBit; } + currentCycles = (mySystem->cycles() - previousCycles); + #ifdef DEBUGGER_SUPPORT if(myStepStateByInstruction) { From a14cf8d07791fa3828cf5e0f6626960e8340dbd6 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 24 May 2018 00:13:43 +0200 Subject: [PATCH 53/77] Refactoring: start debugger from dispatch loop. --- src/emucore/DispatchResult.cxx | 55 ++++++++++++++++++++++++++++ src/emucore/DispatchResult.hxx | 67 ++++++++++++++++++++++++++++++++++ src/emucore/FrameBuffer.cxx | 37 +++++++++++++------ src/emucore/FrameBuffer.hxx | 5 +++ src/emucore/M6502.cxx | 54 +++++++++++++++------------ src/emucore/M6502.hxx | 13 +++---- src/emucore/OSystem.cxx | 60 ++++++++++++++++++------------ src/emucore/OSystem.hxx | 2 + src/emucore/module.mk | 1 + src/emucore/tia/TIA.cxx | 14 +++++-- src/emucore/tia/TIA.hxx | 5 ++- 11 files changed, 243 insertions(+), 70 deletions(-) create mode 100644 src/emucore/DispatchResult.cxx create mode 100644 src/emucore/DispatchResult.hxx diff --git a/src/emucore/DispatchResult.cxx b/src/emucore/DispatchResult.cxx new file mode 100644 index 000000000..e6ca383c9 --- /dev/null +++ b/src/emucore/DispatchResult.cxx @@ -0,0 +1,55 @@ +//============================================================================ +// +// MM MM 6666 555555 0000 2222 +// MMMM MMMM 66 66 55 00 00 22 22 +// MM MMM MM 66 55 00 00 22 +// MM M MM 66666 55555 00 00 22222 -- "A 6502 Microprocessor Emulator" +// MM MM 66 66 55 00 00 22 +// MM MM 66 66 55 55 00 00 22 +// MM MM 6666 5555 0000 222222 +// +// Copyright (c) 1995-2018 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 "DispatchResult.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DispatchResult::assertStatus(Status status) const +{ + if (myStatus != status) throw runtime_error("invalid status for operation"); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool DispatchResult::isSuccess() const +{ + return myStatus == Status::debugger || myStatus == Status::ok; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DispatchResult::setOk(uInt32 cycles) +{ + myStatus = Status::ok; + myCycles = cycles; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DispatchResult::setDebugger(uInt32 cycles, const string& message, int address, bool wasReadTrap) +{ + myStatus = Status::debugger; + myCycles = cycles; + myMessage = message; + myAddress = address; + myWasReadTrap = wasReadTrap; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DispatchResult::setFatal(uInt32 cycles) +{ + myCycles = cycles; + + myStatus = Status::fatal; +} diff --git a/src/emucore/DispatchResult.hxx b/src/emucore/DispatchResult.hxx new file mode 100644 index 000000000..ea9c2982c --- /dev/null +++ b/src/emucore/DispatchResult.hxx @@ -0,0 +1,67 @@ +//============================================================================ +// +// MM MM 6666 555555 0000 2222 +// MMMM MMMM 66 66 55 00 00 22 22 +// MM MMM MM 66 55 00 00 22 +// MM M MM 66666 55555 00 00 22222 -- "A 6502 Microprocessor Emulator" +// MM MM 66 66 55 00 00 22 +// MM MM 66 66 55 55 00 00 22 +// MM MM 6666 5555 0000 222222 +// +// Copyright (c) 1995-2018 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 DISPATCH_RESULT_HXX +#define DISPATCH_RESULT_HXX + +#include "bspf.hxx" + +class DispatchResult +{ + public: + enum class Status { invalid, ok, debugger, fatal }; + + public: + + DispatchResult() : myStatus(Status::invalid) {} + + Status getStatus() const { return myStatus; } + + uInt32 getCycles() const { return myCycles; } + + const string& getMessage() const { assertStatus(Status::debugger); return myMessage; } + + uInt16 getAddress() const { assertStatus(Status::debugger); return myAddress; } + + bool wasReadTrap() const { assertStatus(Status::debugger); return myWasReadTrap; } + + bool isSuccess() const; + + void setOk(uInt32 cycles); + + void setDebugger(uInt32 cycles, const string& message = "", int address = -1, bool wasReadTrap = -1); + + void setFatal(uInt32 cycles); + + private: + + void assertStatus(Status status) const; + + private: + + Status myStatus; + + uInt32 myCycles; + + string myMessage; + + int myAddress; + + bool myWasReadTrap; +}; + +#endif // DISPATCH_RESULT_HXX diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 63ec56137..196552c0f 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -268,17 +268,8 @@ void FrameBuffer::update() switch(myOSystem.eventHandler().state()) { case EventHandlerState::EMULATION: - { - myTIASurface->render(); - - // Show frame statistics - if(myStatsMsg.enabled) - drawFrameStats(); - - myLastScanlines = myOSystem.console().tia().scanlinesLastFrame(); - myPausedCount = 0; - break; // EventHandlerState::EMULATION - } + // Do nothing; emulation mode is handled separately (see below) + break; case EventHandlerState::PAUSE: { @@ -340,6 +331,30 @@ void FrameBuffer::update() postFrameUpdate(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBuffer::updateInEmulationMode() +{ + // Determine which mode we are in (from the EventHandler) + // Take care of S_EMULATE mode here, otherwise let the GUI + // figure out what to draw + + myTIASurface->render(); + + // Show frame statistics + if(myStatsMsg.enabled) + drawFrameStats(); + + myLastScanlines = myOSystem.console().tia().scanlinesLastFrame(); + myPausedCount = 0; + + // Draw any pending messages + if(myMsg.enabled) + drawMessage(); + + // Do any post-frame stuff + postFrameUpdate(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void FrameBuffer::showMessage(const string& message, MessagePosition position, bool force) diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index c6533eca4..a001409b0 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -118,6 +118,11 @@ class FrameBuffer */ void update(); + /** + There is a dedicated update method for emulation mode. + */ + void updateInEmulationMode(); + /** Shows a message onscreen. diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index 04f740f36..5323e4e03 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -47,6 +47,7 @@ #include "System.hxx" #include "M6502.hxx" +#include "DispatchResult.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - M6502::M6502(const Settings& settings) @@ -208,9 +209,9 @@ inline void M6502::handleHalt() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool M6502::execute(uInt32 number) +void M6502::execute(uInt32 number, DispatchResult& result) { - const bool status = _execute(number); + _execute(number, result); #ifdef DEBUGGER_SUPPORT // Debugger hack: this ensures that stepping a "STA WSYNC" will actually end at the @@ -225,12 +226,20 @@ bool M6502::execute(uInt32 number) // that audio samples are generated for the whole timeslice. mySystem->tia().updateEmulation(); mySystem->m6532().updateEmulation(); - - return status; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -inline bool M6502::_execute(uInt32 cycles) +bool M6502::execute(uInt32 number) +{ + DispatchResult result; + + _execute(number, result); + + return result.isSuccess(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +inline void M6502::_execute(uInt32 cycles, DispatchResult& result) { // Clear all of the execution status bits except for the fatal error bit myExecutionStatus &= FatalErrorBit; @@ -241,7 +250,7 @@ inline bool M6502::_execute(uInt32 cycles) #endif uInt64 previousCycles = mySystem->cycles(); - uInt64 currentCycles = 0; + uInt32 currentCycles = 0; // Loop until execution is stopped or a fatal error occurs for(;;) @@ -254,18 +263,23 @@ inline bool M6502::_execute(uInt32 cycles) bool read = myJustHitReadTrapFlag; myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; - if (startDebugger(myHitTrapInfo.message, myHitTrapInfo.address, read)) return true; + result.setDebugger(currentCycles, myHitTrapInfo.message, myHitTrapInfo.address, read); + return; } - if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC) && startDebugger("BP: ", PC)) - return true; + if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC)) { + result.setDebugger(currentCycles, "BP: ", PC); + return; + } int cond = evalCondBreaks(); if(cond > -1) { stringstream msg; msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond]; - if (startDebugger(msg.str())) return true; + + result.setDebugger(currentCycles, msg.str()); + return; } cond = evalCondSaveStates(); @@ -324,21 +338,24 @@ inline bool M6502::_execute(uInt32 cycles) if(myExecutionStatus & StopExecutionBit) { // Yes, so answer that everything finished fine - return true; + result.setOk(currentCycles); + return; } // See if a fatal error has occured if(myExecutionStatus & FatalErrorBit) { // Yes, so answer that something when wrong - return false; + result.setFatal(currentCycles + icycles); + return; } // See if we've executed the specified number of instructions if (currentCycles >= cycles * SYSTEM_CYCLES_PER_CPU) { // Yes, so answer that everything finished fine - return true; + result.setOk(currentCycles); + return; } } } @@ -612,15 +629,4 @@ void M6502::updateStepStateByInstruction() myTrapConds.size(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool M6502::startDebugger(const string& message, int address, bool read) -{ - handleHalt(); - - mySystem->tia().updateEmulation(); - mySystem->m6532().updateEmulation(); - - return myDebugger->start(message, address, read); -} - #endif // DEBUGGER_SUPPORT diff --git a/src/emucore/M6502.hxx b/src/emucore/M6502.hxx index 2dad3de57..26185e6ab 100644 --- a/src/emucore/M6502.hxx +++ b/src/emucore/M6502.hxx @@ -22,6 +22,7 @@ class Settings; class System; +class DispatchResult; #ifdef DEBUGGER_SUPPORT class Debugger; @@ -113,8 +114,10 @@ class M6502 : public Serializable @param cycles Indicates the number of cycles to execute. Not that the actual granularity of the CPU is instructions, so this is only accurate up to a couple of cycles - @return true iff execution stops normally + @param result A DispatchResult object that will transport the result */ + void execute(uInt32 cycles, DispatchResult& result); + bool execute(uInt32 cycles); /** @@ -320,7 +323,7 @@ class M6502 : public Serializable This is the actual dispatch function that does the grunt work. M6502::execute wraps it and makes sure that any pending halt is processed before returning. */ - bool _execute(uInt32 cycles); + void _execute(uInt32 cycles, DispatchResult& result); #ifdef DEBUGGER_SUPPORT /** @@ -328,12 +331,6 @@ class M6502 : public Serializable with the CPU and update the flag accordingly. */ void updateStepStateByInstruction(); - - /** - Make sure that the current hardware state is up to date (TIA & RIOT) and dispatch - debugger. - */ - bool startDebugger(const string& message = "", int address = -1, bool read = true); #endif // DEBUGGER_SUPPORT private: diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index e0126e4aa..76659b57b 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -53,6 +53,7 @@ #include "StateManager.hxx" #include "Version.hxx" #include "TIA.hxx" +#include "DispatchResult.hxx" #include "OSystem.hxx" @@ -635,6 +636,30 @@ float OSystem::frameRate() const return myConsole ? myConsole->getFramerate() : 0; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +double OSystem::dispatchEmulation(uInt32 cyclesPerSecond) +{ + if (!myConsole) return 0.; + + Int64 totalCycles = 0; + const Int64 minCycles = myConsole->emulationTiming().minCyclesPerTimeslice(); + const Int64 maxCycles = myConsole->emulationTiming().maxCyclesPerTimeslice(); + DispatchResult dispatchResult; + + do { + myConsole->tia().update(dispatchResult, totalCycles > 0 ? minCycles - totalCycles : maxCycles); + + totalCycles += dispatchResult.getCycles(); + } while (totalCycles < minCycles && dispatchResult.getStatus() == DispatchResult::Status::ok); + + if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start(); + + if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying()) + myConsole->fry(); + + return static_cast(totalCycles) / static_cast(cyclesPerSecond); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void OSystem::mainLoop() { @@ -650,40 +675,29 @@ void OSystem::mainLoop() double timesliceSeconds; if (myEventHandler->state() == EventHandlerState::EMULATION) { - Int64 totalCycles = 0; - const Int64 minCycles = myConsole ? myConsole->emulationTiming().minCyclesPerTimeslice() : 50000; - const Int64 maxCycles = myConsole ? myConsole->emulationTiming().maxCyclesPerTimeslice() : 0; - const uInt32 cyclesPerSecond = myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1; + timesliceSeconds = dispatchEmulation(myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1); - do { - Int64 cycles = myConsole ? myConsole->tia().update(totalCycles > 0 ? minCycles - totalCycles : maxCycles) : 0; - - totalCycles += cycles; - } while (myConsole && totalCycles < minCycles && myEventHandler->state() == EventHandlerState::EMULATION); - - if (myEventHandler->state() == EventHandlerState::EMULATION && myEventHandler->frying()) - myConsole->fry(); - - timesliceSeconds = static_cast(totalCycles) / static_cast(cyclesPerSecond); - } - else - timesliceSeconds = 1. / 30.; - - if (myEventHandler->state() == EventHandlerState::EMULATION) { if (myConsole && myConsole->tia().newFramePending()) { - myFrameBuffer->update(); + myFrameBuffer->updateInEmulationMode(); myConsole->tia().clearNewFramePending(); } - } - else + } else { + timesliceSeconds = 1. / 30.; myFrameBuffer->update(); + } duration timeslice(timesliceSeconds); virtualTime += duration_cast(timeslice); time_point now = high_resolution_clock::now(); + double maxLag = myConsole + ? ( + static_cast(myConsole->emulationTiming().cyclesPerFrame()) / + static_cast(myConsole->emulationTiming().cyclesPerSecond()) + ) + : 0; - if (duration_cast>(now - virtualTime).count() > 0) + if (duration_cast>(now - virtualTime).count() > maxLag) virtualTime = now; else if (virtualTime > now) { if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) { diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 37b275bde..0b9a3521c 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -563,6 +563,8 @@ class OSystem void validatePath(string& path, const string& setting, const string& defaultpath); + double dispatchEmulation(uInt32 cyclesPerSecond); + // Following constructors and assignment operators not supported OSystem(const OSystem&) = delete; OSystem(OSystem&&) = delete; diff --git a/src/emucore/module.mk b/src/emucore/module.mk index 37325597e..36dfa1345 100644 --- a/src/emucore/module.mk +++ b/src/emucore/module.mk @@ -51,6 +51,7 @@ MODULE_OBJS := \ src/emucore/CompuMate.o \ src/emucore/Console.o \ src/emucore/Control.o \ + src/emucore/DispatchResult.o \ src/emucore/Driving.o \ src/emucore/EventHandler.o \ src/emucore/EmulationTiming.o \ diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index d3722bc16..177a1da29 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -24,6 +24,7 @@ #include "TIAConstants.hxx" #include "frame-manager/FrameManager.hxx" #include "AudioQueue.hxx" +#include "DispatchResult.hxx" #ifdef DEBUGGER_SUPPORT #include "CartDebug.hxx" @@ -814,14 +815,21 @@ bool TIA::loadDisplay(Serializer& in) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt64 TIA::update(uInt32 maxCycles) +void TIA::update(DispatchResult& result, uInt32 maxCycles) { uInt64 timestampOld = myTimestamp; - mySystem->m6502().execute(maxCycles); + mySystem->m6502().execute(maxCycles, result); updateEmulation(); - return (myTimestamp - timestampOld) / 3; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TIA::update(uInt32 maxCycles) +{ + DispatchResult dispatchResult; + + update(dispatchResult, maxCycles); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index d0eced062..d96dae916 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -41,6 +41,7 @@ #include "System.hxx" class AudioQueue; +class DispatchResult; /** This class is a device that emulates the Television Interface Adaptor @@ -199,7 +200,9 @@ class TIA : public Device desired frame rate to update the TIA. Invoking this method will update the graphics buffer and generate the corresponding audio samples. */ - uInt64 update(uInt32 maxCycles = 50000); + void update(DispatchResult& result, uInt32 maxCycles = 50000); + + void update(uInt32 maxCycles = 50000); /** Did we generate a new frame? From 77f149eab925de7b86a3b9b6d6b5127104e08440 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 24 May 2018 00:19:49 +0200 Subject: [PATCH 54/77] Fix stepping after breakpoint. --- src/emucore/M6502.cxx | 47 +++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/emucore/M6502.cxx b/src/emucore/M6502.cxx index 5323e4e03..cefd24d83 100644 --- a/src/emucore/M6502.cxx +++ b/src/emucore/M6502.cxx @@ -258,31 +258,34 @@ inline void M6502::_execute(uInt32 cycles, DispatchResult& result) while (!myExecutionStatus && currentCycles < cycles * SYSTEM_CYCLES_PER_CPU) { #ifdef DEBUGGER_SUPPORT - if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag) - { - bool read = myJustHitReadTrapFlag; - myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; + // Don't break if we haven't actually executed anything yet + if (currentCycles > 0) { + if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag) + { + bool read = myJustHitReadTrapFlag; + myJustHitReadTrapFlag = myJustHitWriteTrapFlag = false; - result.setDebugger(currentCycles, myHitTrapInfo.message, myHitTrapInfo.address, read); - return; + result.setDebugger(currentCycles, myHitTrapInfo.message, myHitTrapInfo.address, read); + return; + } + + if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC)) { + result.setDebugger(currentCycles, "BP: ", PC); + return; + } + + int cond = evalCondBreaks(); + if(cond > -1) + { + stringstream msg; + msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond]; + + result.setDebugger(currentCycles, msg.str()); + return; + } } - if(myBreakPoints.isInitialized() && myBreakPoints.isSet(PC)) { - result.setDebugger(currentCycles, "BP: ", PC); - return; - } - - int cond = evalCondBreaks(); - if(cond > -1) - { - stringstream msg; - msg << "CBP[" << Common::Base::HEX2 << cond << "]: " << myCondBreakNames[cond]; - - result.setDebugger(currentCycles, msg.str()); - return; - } - - cond = evalCondSaveStates(); + int cond = evalCondSaveStates(); if(cond > -1) { stringstream msg; From a9985cc6b81cf3796dbdb8809ec3f573cbd6319e Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Thu, 24 May 2018 21:01:38 -0230 Subject: [PATCH 55/77] Updated OSX project file for new dispatch code, and fixed a minor warning. --- src/common/AudioQueue.cxx | 2 +- src/macosx/stella.xcodeproj/project.pbxproj | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index fb80a26ce..845c63032 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -47,7 +47,7 @@ AudioQueue::AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 AudioQueue::capacity() const { - return myFragmentQueue.size(); + return uInt32(myFragmentQueue.size()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index aceca62ce..2370196c3 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -565,6 +565,8 @@ DCDE17FB17724E5D00EB1AC6 /* ConfigPathDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F717724E5D00EB1AC6 /* ConfigPathDialog.hxx */; }; DCDE17FC17724E5D00EB1AC6 /* SnapshotDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */; }; DCDE17FD17724E5D00EB1AC6 /* SnapshotDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDE17F917724E5D00EB1AC6 /* SnapshotDialog.hxx */; }; + DCDFF08120B781B0001227C0 /* DispatchResult.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */; }; + DCDFF08220B781B0001227C0 /* DispatchResult.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCDFF08020B781B0001227C0 /* DispatchResult.hxx */; }; DCE395DB16CB0B2B008DB1E5 /* FSNodePOSIX.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395DA16CB0B2B008DB1E5 /* FSNodePOSIX.hxx */; }; DCE395EF16CB0B5F008DB1E5 /* FSNodeFactory.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */; }; DCE395F016CB0B5F008DB1E5 /* FSNodeZIP.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCE395EB16CB0B5F008DB1E5 /* FSNodeZIP.cxx */; }; @@ -1253,6 +1255,8 @@ DCDE17F717724E5D00EB1AC6 /* ConfigPathDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConfigPathDialog.hxx; sourceTree = ""; }; DCDE17F817724E5D00EB1AC6 /* SnapshotDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SnapshotDialog.cxx; sourceTree = ""; }; DCDE17F917724E5D00EB1AC6 /* SnapshotDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SnapshotDialog.hxx; sourceTree = ""; }; + DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DispatchResult.cxx; sourceTree = ""; }; + DCDFF08020B781B0001227C0 /* DispatchResult.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DispatchResult.hxx; sourceTree = ""; }; DCE395DA16CB0B2B008DB1E5 /* FSNodePOSIX.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FSNodePOSIX.hxx; sourceTree = ""; }; DCE395EA16CB0B5F008DB1E5 /* FSNodeFactory.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FSNodeFactory.hxx; sourceTree = ""; }; DCE395EB16CB0B5F008DB1E5 /* FSNodeZIP.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FSNodeZIP.cxx; sourceTree = ""; }; @@ -1783,6 +1787,8 @@ 2DE2DF3B0627AE07006BEC99 /* Control.hxx */, DC932D3F0F278A5200FEFEFC /* DefProps.hxx */, DCC527C910B9DA19005E1287 /* Device.hxx */, + DCDFF07F20B781B0001227C0 /* DispatchResult.cxx */, + DCDFF08020B781B0001227C0 /* DispatchResult.hxx */, 2DE2DF3E0627AE07006BEC99 /* Driving.cxx */, 2DE2DF3F0627AE07006BEC99 /* Driving.hxx */, E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */, @@ -2436,6 +2442,7 @@ DC6DC921205DB879004A5FC3 /* PJoystickHandler.hxx in Headers */, DCAAE5DF1715887B0080BB82 /* CartEFSCWidget.hxx in Headers */, DCAAE5E11715887B0080BB82 /* CartEFWidget.hxx in Headers */, + DCDFF08220B781B0001227C0 /* DispatchResult.hxx in Headers */, DCAAE5E31715887B0080BB82 /* CartF0Widget.hxx in Headers */, DCAAE5E51715887B0080BB82 /* CartF4SCWidget.hxx in Headers */, DCAAE5E71715887B0080BB82 /* CartF4Widget.hxx in Headers */, @@ -2703,6 +2710,7 @@ 2D9174FA09BA90380026E9FF /* DebuggerDialog.cxx in Sources */, DCF3A6E91DFC75E3008A8AF3 /* Ball.cxx in Sources */, 2D9174FB09BA90380026E9FF /* PromptWidget.cxx in Sources */, + DCDFF08120B781B0001227C0 /* DispatchResult.cxx in Sources */, 2D9174FC09BA90380026E9FF /* RamWidget.cxx in Sources */, DC2AADAE194F389C0026C7A4 /* CartDASH.cxx in Sources */, 2D9174FD09BA90380026E9FF /* RomListWidget.cxx in Sources */, From f7a9a12c23759a5cd53ee667d3ab76cdff100d44 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Thu, 24 May 2018 21:32:32 -0230 Subject: [PATCH 56/77] Updated VS project for recent dispatch class changes. Bumped version # for precise-sound branch, since I'm getting tired of switching between branches and forgetting which binary I'm currently running. --- src/common/Version.hxx | 2 +- src/windows/Stella.vcxproj | 2 ++ src/windows/Stella.vcxproj.filters | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/Version.hxx b/src/common/Version.hxx index ac15e5f7b..ef52325c7 100644 --- a/src/common/Version.hxx +++ b/src/common/Version.hxx @@ -18,7 +18,7 @@ #ifndef VERSION_HXX #define VERSION_HXX -#define STELLA_VERSION "5.2_pre" +#define STELLA_VERSION "5.2_soundtest-1" #define STELLA_BUILD "4138" #endif diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index d94440b24..b5de19a15 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -327,6 +327,7 @@ + @@ -631,6 +632,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index 1f095df8a..f6dae28e3 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -924,6 +924,9 @@ Source Files\audio + + Source Files\emucore + @@ -1889,6 +1892,9 @@ Header Files\audio + + Header Files\emucore + From a5ce457b728fe1c8ce01cd055997b64662f99200 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 27 May 2018 00:26:07 +0200 Subject: [PATCH 57/77] Implement triple buffering in TIA. --- src/debugger/gui/TiaOutputWidget.cxx | 4 +++- src/emucore/DispatchResult.hxx | 2 +- src/emucore/OSystem.cxx | 2 +- src/emucore/TIASurface.cxx | 5 +++-- src/emucore/TIASurface.hxx | 7 +++---- src/emucore/tia/TIA.cxx | 15 ++++++++++++--- src/emucore/tia/TIA.hxx | 13 ++++++++++--- 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/debugger/gui/TiaOutputWidget.cxx b/src/debugger/gui/TiaOutputWidget.cxx index 384fa8d04..033c7caa7 100644 --- a/src/debugger/gui/TiaOutputWidget.cxx +++ b/src/debugger/gui/TiaOutputWidget.cxx @@ -168,6 +168,8 @@ void TiaOutputWidget::drawWidget(bool hilite) uInt32 scanx, scany, scanoffset; bool visible = instance().console().tia().electronBeamPos(scanx, scany); scanoffset = width * scany + scanx; + uInt8* tiaOutputBuffer = instance().console().tia().outputBuffer(); + TIASurface& tiaSurface(instance().frameBuffer().tiaSurface()); for(uInt32 y = 0, i = 0; y < height; ++y) { @@ -175,7 +177,7 @@ void TiaOutputWidget::drawWidget(bool hilite) for(uInt32 x = 0; x < width; ++x, ++i) { uInt8 shift = i >= scanoffset ? 1 : 0; - uInt32 pixel = instance().frameBuffer().tiaSurface().pixel(i, shift); + uInt32 pixel = tiaSurface.mapIndexedPixel(tiaOutputBuffer[i], shift); *line_ptr++ = pixel; *line_ptr++ = pixel; } diff --git a/src/emucore/DispatchResult.hxx b/src/emucore/DispatchResult.hxx index ea9c2982c..4bb1da098 100644 --- a/src/emucore/DispatchResult.hxx +++ b/src/emucore/DispatchResult.hxx @@ -43,7 +43,7 @@ class DispatchResult void setOk(uInt32 cycles); - void setDebugger(uInt32 cycles, const string& message = "", int address = -1, bool wasReadTrap = -1); + void setDebugger(uInt32 cycles, const string& message = "", int address = -1, bool wasReadTrap = false); void setFatal(uInt32 cycles); diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 76659b57b..5fbb81b2b 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -678,8 +678,8 @@ void OSystem::mainLoop() timesliceSeconds = dispatchEmulation(myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1); if (myConsole && myConsole->tia().newFramePending()) { + myConsole->tia().renderToFrameBuffer(); myFrameBuffer->updateInEmulationMode(); - myConsole->tia().clearNewFramePending(); } } else { timesliceSeconds = 1. / 30.; diff --git a/src/emucore/TIASurface.cxx b/src/emucore/TIASurface.cxx index d074bb2f3..55ca4de96 100644 --- a/src/emucore/TIASurface.cxx +++ b/src/emucore/TIASurface.cxx @@ -138,11 +138,12 @@ const FBSurface& TIASurface::baseSurface(GUI::Rect& rect) const } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 TIASurface::pixel(uInt32 idx, uInt8 shift) +uInt32 TIASurface::mapIndexedPixel(uInt8 indexedColor, uInt8 shift) { - return myPalette[*(myTIA->frameBuffer() + idx) | shift]; + return myPalette[indexedColor | shift]; } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIASurface::setNTSC(NTSCFilter::Preset preset, bool show) { diff --git a/src/emucore/TIASurface.hxx b/src/emucore/TIASurface.hxx index 258462508..d7ae17291 100644 --- a/src/emucore/TIASurface.hxx +++ b/src/emucore/TIASurface.hxx @@ -73,10 +73,9 @@ class TIASurface const FBSurface& baseSurface(GUI::Rect& rect) const; /** - Get the TIA pixel associated with the given TIA buffer index, - shifting by the given offset (for greyscale values). - */ - uInt32 pixel(uInt32 idx, uInt8 shift = 0); + Use the palette to map a single indexed pixel color. This is used by the TIA output widget. + */ + uInt32 mapIndexedPixel(uInt8 indexedColor, uInt8 shift = 0); /** Get the NTSCFilter object associated with the framebuffer diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index 177a1da29..fd9cdbb77 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -196,6 +196,7 @@ void TIA::reset() void TIA::frameReset() { memset(myBackBuffer, 0, 160 * TIAConstants::frameBufferHeight); + memset(myFrontBuffer, 0, 160 * TIAConstants::frameBufferHeight); memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight); enableColorLoss(mySettings.getBool("dev.settings") ? "dev.colorloss" : "plr.colorloss"); } @@ -784,6 +785,7 @@ bool TIA::saveDisplay(Serializer& out) const { out.putByteArray(myFramebuffer, 160* TIAConstants::frameBufferHeight); out.putByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight); + out.putByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight); out.putBool(myNewFramePending); } catch(...) @@ -803,6 +805,7 @@ bool TIA::loadDisplay(Serializer& in) // Reset frame buffer pointer and data in.getByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight); in.getByteArray(myBackBuffer, 160 * TIAConstants::frameBufferHeight); + in.getByteArray(myFrontBuffer, 160 * TIAConstants::frameBufferHeight); myNewFramePending = in.getBool(); } catch(...) @@ -817,13 +820,19 @@ bool TIA::loadDisplay(Serializer& in) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::update(DispatchResult& result, uInt32 maxCycles) { - uInt64 timestampOld = myTimestamp; - mySystem->m6502().execute(maxCycles, result); updateEmulation(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TIA::renderToFrameBuffer() +{ + if (!myNewFramePending) return; + + memcpy(myFramebuffer, myFrontBuffer, 160 * TIAConstants::frameBufferHeight); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::update(uInt32 maxCycles) { @@ -1171,7 +1180,7 @@ void TIA::onFrameComplete() if (missingScanlines > 0) memset(myBackBuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160); - memcpy(&myFramebuffer, &myBackBuffer, 160 * TIAConstants::frameBufferHeight); + memcpy(myFrontBuffer, myBackBuffer, 160 * TIAConstants::frameBufferHeight); myNewFramePending = true; } diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index d96dae916..f4b5ba869 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -210,9 +210,15 @@ class TIA : public Device bool newFramePending() { return myNewFramePending; } /** - Clear the flag - */ - void clearNewFramePending() { myNewFramePending = false; } + Render the pending frame to the framebuffer and clear the flag. + */ + void renderToFrameBuffer(); + + /** + Return the buffer that holds the currently drawing TIA frame + (the TIA output widget needs this). + */ + uInt8* outputBuffer() { return myBackBuffer; } /** Returns a pointer to the internal frame buffer. @@ -673,6 +679,7 @@ class TIA : public Device // The frame is rendered to the backbuffer and only copied to the framebuffer // upon completion uInt8 myBackBuffer[160 * TIAConstants::frameBufferHeight]; + uInt8 myFrontBuffer[160 * TIAConstants::frameBufferHeight]; // Did we emit a frame? bool myNewFramePending; From 1eee879e296dce3a73627c5084e54bbbfcba52f3 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Sun, 27 May 2018 16:41:42 -0230 Subject: [PATCH 58/77] Make sure onscreen messages are shown, even when a console doesn't exist. --- src/emucore/FrameBuffer.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 196552c0f..d4170066c 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -366,6 +366,7 @@ void FrameBuffer::showMessage(const string& message, MessagePosition position, // Precompute the message coordinates myMsg.text = message; myMsg.counter = uInt32(myOSystem.frameRate()) << 1; // Show message for 2 seconds + if(myMsg.counter == 0) myMsg.counter = 60; myMsg.color = kBtnTextColor; myMsg.w = font().getStringWidth(myMsg.text) + 10; From 9e4dbd6a3a55c15fd038b4a523e3ee0275c33eb9 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Sun, 27 May 2018 18:53:57 -0230 Subject: [PATCH 59/77] Fixed dirty buffer in TIA mode when switching screenmodes. --- src/common/FrameBufferSDL2.cxx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/common/FrameBufferSDL2.cxx b/src/common/FrameBufferSDL2.cxx index d9338eeb9..a2ddba56e 100644 --- a/src/common/FrameBufferSDL2.cxx +++ b/src/common/FrameBufferSDL2.cxx @@ -138,10 +138,6 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode) // Always recreate renderer (some systems need this) if(myRenderer) { - // Always clear the (double-buffered) renderer surface - SDL_RenderClear(myRenderer); - SDL_RenderPresent(myRenderer); - SDL_RenderClear(myRenderer); SDL_DestroyRenderer(myRenderer); myRenderer = nullptr; } @@ -228,6 +224,12 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode) myOSystem.logMessage(msg, 0); return false; } + + // Always clear the (double-buffered) renderer surface + SDL_RenderClear(myRenderer); + SDL_RenderPresent(myRenderer); + SDL_RenderClear(myRenderer); + SDL_RendererInfo renderinfo; if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0) myOSystem.settings().setValue("video", renderinfo.name); @@ -235,6 +237,7 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode) return true; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void FrameBufferSDL2::setTitle(const string& title) { myScreenTitle = title; From afb1e1d1e1bc5afdb79860d2e33af3abc761303c Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 7 Jun 2018 20:43:56 +0200 Subject: [PATCH 60/77] Emulation worker. Currently untested and disconnected. --- .vscode/settings.json | 11 +- src/emucore/EmulationWorker.cxx | 256 ++++++++++++++++++++++++++++++++ src/emucore/EmulationWorker.hxx | 92 ++++++++++++ src/emucore/module.mk | 1 + 4 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 src/emucore/EmulationWorker.cxx create mode 100644 src/emucore/EmulationWorker.hxx diff --git a/.vscode/settings.json b/.vscode/settings.json index 790c9dc4d..a067b748d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -43,6 +43,15 @@ "stdexcept": "cpp", "fstream": "cpp", "__locale": "cpp", - "__string": "cpp" + "__string": "cpp", + "__config": "cpp", + "__nullptr": "cpp", + "cstddef": "cpp", + "exception": "cpp", + "initializer_list": "cpp", + "new": "cpp", + "typeinfo": "cpp", + "__mutex_base": "cpp", + "mutex": "cpp" } } diff --git a/src/emucore/EmulationWorker.cxx b/src/emucore/EmulationWorker.cxx new file mode 100644 index 000000000..504877ee1 --- /dev/null +++ b/src/emucore/EmulationWorker.cxx @@ -0,0 +1,256 @@ +//============================================================================ +// +// 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-2018 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 + +#include "EmulationWorker.hxx" +#include "DispatchResult.hxx" +#include "TIA.hxx" + +using namespace std::chrono; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +EmulationWorker::EmulationWorker(TIA& tia) : myTia(tia) +{ + std::mutex mutex; + std::unique_lock lock(mutex); + std::condition_variable threadInitialized; + + myThread = std::thread( + &EmulationWorker::threadMain, this, &threadInitialized, &mutex + ); + + // Wait until the thread has acquired myMutex and moved on + while (myState == State::initializing) threadInitialized.wait(lock); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +EmulationWorker::~EmulationWorker() +{ + // This has to run in a block in order to release the mutex before joining + { + std::unique_lock lock(myMutex); + + if (myState != State::exception) { + myPendingSignal = Signal::quit; + mySignalCondition.notify_one(); + } + } + + myThread.join(); + + handlePossibleException(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::handlePossibleException() +{ + if (myState == State::exception && myPendingException) { + std::exception_ptr ex = myPendingException; + // Make sure that the exception is not thrown a second time (destructor!!!) + myPendingException = nullptr; + + std::rethrow_exception(ex); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult) +{ + // Optimization: run this in a block to unlock the mutex before notifying the thread; + // otherwise, the thread would immediatelly block again until we have released the mutex, + // sacrificing a timeslice + { + // Aquire the mutex -> wait until the thread is suspended + std::unique_lock lock(myMutex); + + // Pass on possible exceptions + handlePossibleException(); + + // NB: The thread does not suspend execution in State::initialized + if (myState != State::waitingForResume) + throw runtime_error("start called on running or dead worker"); + + // Store the parameters for emulation + myCyclesPerSecond = cyclesPerSecond; + myMaxCycles = maxCycles; + myMinCycles = minCycles; + myDispatchResult = dispatchResult; + + // Set the signal... + myPendingSignal = Signal::resume; + } + + // ... and wakeup the thread + mySignalCondition.notify_one(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::stop() +{ + // See EmulationWorker::start for the gory details + { + std::unique_lock lock(myMutex); + handlePossibleException(); + + // If the worker has stopped on its own, we return + if (myState == State::waitingForResume) return; + + // NB: The thread does not suspend execution in State::initialized or State::running + if (myState != State::waitingForStop) + throw runtime_error("stop called on a dead worker"); + + myPendingSignal = Signal::stop; + } + + mySignalCondition.notify_one(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex) +{ + std::unique_lock lock(myMutex); + + try { + // Optimization: release the lock before notifying our parent -> saves a timeslice + { + // Wait until our parent releases the lock and sleeps + std::lock_guard guard(*initializationMutex); + + // Update the state... + myState = State::initialized; + } + + // ... and wake up our parent to notifiy that we have initialized. From this point, the + // parent can safely assume that we are running while the mutex is locked. + initializedCondition->notify_one(); + + while (myPendingSignal != Signal::quit) handleWakeup(lock); + } + catch (...) { + myPendingException = std::current_exception(); + myState = State::exception; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::handleWakeup(std::unique_lock& lock) +{ + switch (myState) { + case State::initialized: + myState = State::waitingForResume; + mySignalCondition.wait(lock); + break; + + case State::waitingForResume: + handleWakeupFromWaitingForResume(lock); + break; + + case State::waitingForStop: + handleWakeupFromWaitingForStop(lock); + break; + + default: + throw runtime_error("wakeup in invalid worker state"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::handleWakeupFromWaitingForResume(std::unique_lock& lock) +{ + switch (myPendingSignal) { + case Signal::resume: + myPendingSignal = Signal::none; + dispatchEmulation(lock); + break; + + case Signal::none: + mySignalCondition.wait(lock); + break; + + case Signal::quit: + break; + + default: + throw runtime_error("invalid signal while waiting for resume"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::handleWakeupFromWaitingForStop(std::unique_lock& lock) +{ + switch (myPendingSignal) { + case Signal::stop: + myState = State::waitingForResume; + myPendingSignal = Signal::none; + + mySignalCondition.wait(lock); + break; + + case Signal::none: + if (myWakeupPoint <= high_resolution_clock::now()) + mySignalCondition.wait_until(lock, myWakeupPoint); + else { + myState = State::waitingForResume; + mySignalCondition.wait(lock); + } + + break; + + case Signal::quit: + break; + + default: + throw runtime_error("invalid signal while waiting for stop"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::dispatchEmulation(std::unique_lock& lock) +{ + myState = State::running; + + time_point now = high_resolution_clock::now(); + uInt64 totalCycles = 0; + + do { + myTia.update(*myDispatchResult, totalCycles > 0 ? myMinCycles - totalCycles : myMaxCycles); + } while (totalCycles < myMinCycles && myDispatchResult->getStatus() == DispatchResult::Status::ok); + + if (myDispatchResult->getStatus() == DispatchResult::Status::ok) { + // If emulation finished successfully, we can go for another round + duration timesliceSeconds(static_cast(totalCycles) / static_cast(myCyclesPerSecond)); + myWakeupPoint = now + duration_cast(timesliceSeconds); + + myState = State::waitingForStop; + + if (myWakeupPoint > high_resolution_clock::now()) + // If we can keep up with the emulation, we sleep + mySignalCondition.wait_until(lock, myWakeupPoint); + else { + // If we are already lagging behind, we briefly relinquish control over the mutex + // and yield to scheduler, to make sure that the main thread has a chance to stop us + lock.release(); + std::this_thread::yield(); + lock.lock(); + } + } else { + // If execution trapped, we stop immediatelly. + myState = State::waitingForResume; + mySignalCondition.wait(lock); + } +} diff --git a/src/emucore/EmulationWorker.hxx b/src/emucore/EmulationWorker.hxx new file mode 100644 index 000000000..227081973 --- /dev/null +++ b/src/emucore/EmulationWorker.hxx @@ -0,0 +1,92 @@ +//============================================================================ +// +// 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-2018 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 EMULATION_WORKER_HXX +#define EMULATION_WORKER_HXX + +#include +#include +#include +#include +#include +#include + +#include "bspf.hxx" + +class TIA; +class DispatchResult; + +class EmulationWorker +{ + public: + enum class State { + initializing, initialized, waitingForResume, running, waitingForStop, exception + }; + + enum class Signal { + resume, stop, quit, none + }; + + public: + + EmulationWorker(TIA& tia); + + ~EmulationWorker(); + + void handlePossibleException(); + + void start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult); + + void stop(); + + private: + + void handleException(); + + // Passing references into a thread is awkward and requires std::ref -> use pointers here + void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex); + + void handleWakeup(std::unique_lock& lock); + + void handleWakeupFromWaitingForResume(std::unique_lock& lock); + + void handleWakeupFromWaitingForStop(std::unique_lock& lock); + + void dispatchEmulation(std::unique_lock& lock); + + private: + + TIA& myTia; + + std::thread myThread; + + std::condition_variable mySignalCondition; + std::mutex myMutex; + std::exception_ptr myPendingException; + Signal myPendingSignal; + // The initial access to myState is not synchronized -> make this atomic + std::atomic myState; + + uInt32 myCyclesPerSecond; + uInt32 myMaxCycles; + uInt32 myMinCycles; + DispatchResult* myDispatchResult; + + std::chrono::time_point myWakeupPoint; +}; + +#endif // EMULATION_WORKER_HXX diff --git a/src/emucore/module.mk b/src/emucore/module.mk index 36dfa1345..57b8fb133 100644 --- a/src/emucore/module.mk +++ b/src/emucore/module.mk @@ -55,6 +55,7 @@ MODULE_OBJS := \ src/emucore/Driving.o \ src/emucore/EventHandler.o \ src/emucore/EmulationTiming.o \ + src/emucore/EmulationWorker.o \ src/emucore/FrameBuffer.o \ src/emucore/FBSurface.o \ src/emucore/FSNode.o \ From 773a0cf906d382a443f28a95b061351dd9bb7f60 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 7 Jun 2018 20:55:33 +0200 Subject: [PATCH 61/77] Cleanup. --- src/emucore/EmulationWorker.hxx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/emucore/EmulationWorker.hxx b/src/emucore/EmulationWorker.hxx index 227081973..d436c892a 100644 --- a/src/emucore/EmulationWorker.hxx +++ b/src/emucore/EmulationWorker.hxx @@ -47,15 +47,13 @@ class EmulationWorker ~EmulationWorker(); - void handlePossibleException(); - void start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult); void stop(); private: - void handleException(); + void handlePossibleException(); // Passing references into a thread is awkward and requires std::ref -> use pointers here void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex); From d8732c93788a2fe466d6fd090501ffc091bbb35e Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 7 Jun 2018 20:59:15 +0200 Subject: [PATCH 62/77] Uups, fix evident error. --- src/emucore/EmulationWorker.cxx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/emucore/EmulationWorker.cxx b/src/emucore/EmulationWorker.cxx index 504877ee1..26c23e15d 100644 --- a/src/emucore/EmulationWorker.cxx +++ b/src/emucore/EmulationWorker.cxx @@ -203,11 +203,9 @@ void EmulationWorker::handleWakeupFromWaitingForStop(std::unique_lock Date: Thu, 7 Jun 2018 21:02:08 +0200 Subject: [PATCH 63/77] Scheduling fixes. --- src/emucore/EmulationWorker.cxx | 12 ++++++------ src/emucore/EmulationWorker.hxx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/emucore/EmulationWorker.cxx b/src/emucore/EmulationWorker.cxx index 26c23e15d..9304e11b4 100644 --- a/src/emucore/EmulationWorker.cxx +++ b/src/emucore/EmulationWorker.cxx @@ -175,6 +175,7 @@ void EmulationWorker::handleWakeupFromWaitingForResume(std::unique_lock& lock) { myState = State::running; - time_point now = high_resolution_clock::now(); uInt64 totalCycles = 0; do { @@ -232,13 +232,13 @@ void EmulationWorker::dispatchEmulation(std::unique_lock& lock) if (myDispatchResult->getStatus() == DispatchResult::Status::ok) { // If emulation finished successfully, we can go for another round duration timesliceSeconds(static_cast(totalCycles) / static_cast(myCyclesPerSecond)); - myWakeupPoint = now + duration_cast(timesliceSeconds); + myVirtualTime += duration_cast(timesliceSeconds); myState = State::waitingForStop; - if (myWakeupPoint > high_resolution_clock::now()) + if (myVirtualTime > high_resolution_clock::now()) // If we can keep up with the emulation, we sleep - mySignalCondition.wait_until(lock, myWakeupPoint); + mySignalCondition.wait_until(lock, myVirtualTime); else { // If we are already lagging behind, we briefly relinquish control over the mutex // and yield to scheduler, to make sure that the main thread has a chance to stop us diff --git a/src/emucore/EmulationWorker.hxx b/src/emucore/EmulationWorker.hxx index d436c892a..e2226d6e9 100644 --- a/src/emucore/EmulationWorker.hxx +++ b/src/emucore/EmulationWorker.hxx @@ -84,7 +84,7 @@ class EmulationWorker uInt32 myMinCycles; DispatchResult* myDispatchResult; - std::chrono::time_point myWakeupPoint; + std::chrono::time_point myVirtualTime; }; #endif // EMULATION_WORKER_HXX From 8edc597189360a73f5026b7ca95734345333c3b5 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Thu, 7 Jun 2018 23:38:14 +0200 Subject: [PATCH 64/77] Hook and fix up EmulationWorker -> threading works, pick'n'pile is happy. --- .vscode/settings.json | 3 +- src/common/AudioQueue.cxx | 2 +- src/emucore/EmulationWorker.cxx | 166 +++++++++++++++++++------------- src/emucore/EmulationWorker.hxx | 35 +++++-- src/emucore/OSystem.cxx | 39 ++++---- src/emucore/OSystem.hxx | 3 +- 6 files changed, 155 insertions(+), 93 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a067b748d..effb7653b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,6 +52,7 @@ "new": "cpp", "typeinfo": "cpp", "__mutex_base": "cpp", - "mutex": "cpp" + "mutex": "cpp", + "condition_variable": "cpp" } } diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 845c63032..237059816 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -101,7 +101,7 @@ Int16* AudioQueue::enqueue(Int16* fragment) if (mySize < capacity) mySize++; else { myNextFragment = (myNextFragment + 1) % capacity; - (cerr << "audio buffer overflow\n").flush(); + //(cerr << "audio buffer overflow\n").flush(); } return newFragment; diff --git a/src/emucore/EmulationWorker.cxx b/src/emucore/EmulationWorker.cxx index 9304e11b4..728ddd5c8 100644 --- a/src/emucore/EmulationWorker.cxx +++ b/src/emucore/EmulationWorker.cxx @@ -24,7 +24,7 @@ using namespace std::chrono; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -EmulationWorker::EmulationWorker(TIA& tia) : myTia(tia) +EmulationWorker::EmulationWorker() : myPendingSignal(Signal::none), myState(State::initializing) { std::mutex mutex; std::unique_lock lock(mutex); @@ -34,7 +34,7 @@ EmulationWorker::EmulationWorker(TIA& tia) : myTia(tia) &EmulationWorker::threadMain, this, &threadInitialized, &mutex ); - // Wait until the thread has acquired myMutex and moved on + // Wait until the thread has acquired myWakeupMutex and moved on while (myState == State::initializing) threadInitialized.wait(lock); } @@ -43,11 +43,11 @@ EmulationWorker::~EmulationWorker() { // This has to run in a block in order to release the mutex before joining { - std::unique_lock lock(myMutex); + std::unique_lock lock(myWakeupMutex); if (myState != State::exception) { - myPendingSignal = Signal::quit; - mySignalCondition.notify_one(); + signalQuit(); + myWakeupCondition.notify_one(); } } @@ -69,75 +69,73 @@ void EmulationWorker::handlePossibleException() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EmulationWorker::start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult) +void EmulationWorker::start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult, TIA* tia) { - // Optimization: run this in a block to unlock the mutex before notifying the thread; - // otherwise, the thread would immediatelly block again until we have released the mutex, - // sacrificing a timeslice - { - // Aquire the mutex -> wait until the thread is suspended - std::unique_lock lock(myMutex); + waitForSignalClear(); - // Pass on possible exceptions - handlePossibleException(); + // Aquire the mutex -> wait until the thread is suspended + std::unique_lock lock(myWakeupMutex); - // NB: The thread does not suspend execution in State::initialized - if (myState != State::waitingForResume) - throw runtime_error("start called on running or dead worker"); + // Pass on possible exceptions + handlePossibleException(); - // Store the parameters for emulation - myCyclesPerSecond = cyclesPerSecond; - myMaxCycles = maxCycles; - myMinCycles = minCycles; - myDispatchResult = dispatchResult; + // NB: The thread does not suspend execution in State::initialized + if (myState != State::waitingForResume) + fatal("start called on running or dead worker"); - // Set the signal... - myPendingSignal = Signal::resume; - } + // Store the parameters for emulation + myTia = tia; + myCyclesPerSecond = cyclesPerSecond; + myMaxCycles = maxCycles; + myMinCycles = minCycles; + myDispatchResult = dispatchResult; + + // Set the signal... + myPendingSignal = Signal::resume; // ... and wakeup the thread - mySignalCondition.notify_one(); + myWakeupCondition.notify_one(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EmulationWorker::stop() +uInt64 EmulationWorker::stop() { - // See EmulationWorker::start for the gory details - { - std::unique_lock lock(myMutex); - handlePossibleException(); + waitForSignalClear(); - // If the worker has stopped on its own, we return - if (myState == State::waitingForResume) return; + std::unique_lock lock(myWakeupMutex); + handlePossibleException(); - // NB: The thread does not suspend execution in State::initialized or State::running - if (myState != State::waitingForStop) - throw runtime_error("stop called on a dead worker"); + // If the worker has stopped on its own, we return + if (myState == State::waitingForResume) return 0; - myPendingSignal = Signal::stop; - } + // NB: The thread does not suspend execution in State::initialized or State::running + if (myState != State::waitingForStop) + fatal("stop called on a dead worker"); - mySignalCondition.notify_one(); + myPendingSignal = Signal::stop; + + myWakeupCondition.notify_one(); + + return myTotalCycles; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EmulationWorker::threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex) { - std::unique_lock lock(myMutex); + std::unique_lock lock(myWakeupMutex); try { - // Optimization: release the lock before notifying our parent -> saves a timeslice { // Wait until our parent releases the lock and sleeps std::lock_guard guard(*initializationMutex); // Update the state... myState = State::initialized; - } - // ... and wake up our parent to notifiy that we have initialized. From this point, the - // parent can safely assume that we are running while the mutex is locked. - initializedCondition->notify_one(); + // ... and wake up our parent to notifiy that we have initialized. From this point, the + // parent can safely assume that we are running while the mutex is locked. + initializedCondition->notify_one(); + } while (myPendingSignal != Signal::quit) handleWakeup(lock); } @@ -153,7 +151,7 @@ void EmulationWorker::handleWakeup(std::unique_lock& lock) switch (myState) { case State::initialized: myState = State::waitingForResume; - mySignalCondition.wait(lock); + myWakeupCondition.wait(lock); break; case State::waitingForResume: @@ -165,7 +163,7 @@ void EmulationWorker::handleWakeup(std::unique_lock& lock) break; default: - throw runtime_error("wakeup in invalid worker state"); + fatal("wakeup in invalid worker state"); } } @@ -174,20 +172,21 @@ void EmulationWorker::handleWakeupFromWaitingForResume(std::unique_lock& lock) uInt64 totalCycles = 0; do { - myTia.update(*myDispatchResult, totalCycles > 0 ? myMinCycles - totalCycles : myMaxCycles); + myTia->update(*myDispatchResult, totalCycles > 0 ? myMinCycles - totalCycles : myMaxCycles); + totalCycles += myDispatchResult->getCycles(); } while (totalCycles < myMinCycles && myDispatchResult->getStatus() == DispatchResult::Status::ok); + myTotalCycles += totalCycles; + + bool continueEmulating = false; + if (myDispatchResult->getStatus() == DispatchResult::Status::ok) { // If emulation finished successfully, we can go for another round duration timesliceSeconds(static_cast(totalCycles) / static_cast(myCyclesPerSecond)); myVirtualTime += duration_cast(timesliceSeconds); myState = State::waitingForStop; + continueEmulating = myVirtualTime > high_resolution_clock::now(); + } - if (myVirtualTime > high_resolution_clock::now()) - // If we can keep up with the emulation, we sleep - mySignalCondition.wait_until(lock, myVirtualTime); - else { - // If we are already lagging behind, we briefly relinquish control over the mutex - // and yield to scheduler, to make sure that the main thread has a chance to stop us - lock.release(); - std::this_thread::yield(); - lock.lock(); - } + if (continueEmulating) { + myState = State::waitingForStop; + myWakeupCondition.wait_until(lock, myVirtualTime); } else { - // If execution trapped, we stop immediatelly. myState = State::waitingForResume; - mySignalCondition.wait(lock); + myWakeupCondition.wait(lock); } } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::clearSignal() +{ + std::unique_lock lock(mySignalChangeMutex); + myPendingSignal = Signal::none; + + mySignalChangeCondition.notify_one(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::signalQuit() +{ + std::unique_lock lock(mySignalChangeMutex); + myPendingSignal = Signal::quit; + + mySignalChangeCondition.notify_one(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::waitForSignalClear() +{ + std::unique_lock lock(mySignalChangeMutex); + + while (myPendingSignal != Signal::none && myPendingSignal != Signal::quit) + mySignalChangeCondition.wait(lock); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationWorker::fatal(string message) +{ + (cerr << "FATAL in emulation worker: " << message << std::endl).flush(); + throw runtime_error(message); +} diff --git a/src/emucore/EmulationWorker.hxx b/src/emucore/EmulationWorker.hxx index e2226d6e9..c1fbacfa8 100644 --- a/src/emucore/EmulationWorker.hxx +++ b/src/emucore/EmulationWorker.hxx @@ -43,13 +43,13 @@ class EmulationWorker public: - EmulationWorker(TIA& tia); + EmulationWorker(); ~EmulationWorker(); - void start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult); + void start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult, TIA* tia); - void stop(); + uInt64 stop(); private: @@ -58,6 +58,12 @@ class EmulationWorker // Passing references into a thread is awkward and requires std::ref -> use pointers here void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex); + void clearSignal(); + + void signalQuit(); + + void waitForSignalClear(); + void handleWakeup(std::unique_lock& lock); void handleWakeupFromWaitingForResume(std::unique_lock& lock); @@ -66,14 +72,20 @@ class EmulationWorker void dispatchEmulation(std::unique_lock& lock); + void fatal(string message); + private: - TIA& myTia; + TIA* myTia; std::thread myThread; - std::condition_variable mySignalCondition; - std::mutex myMutex; + std::condition_variable myWakeupCondition; + std::mutex myWakeupMutex; + + std::condition_variable mySignalChangeCondition; + std::mutex mySignalChangeMutex; + std::exception_ptr myPendingException; Signal myPendingSignal; // The initial access to myState is not synchronized -> make this atomic @@ -84,7 +96,18 @@ class EmulationWorker uInt32 myMinCycles; DispatchResult* myDispatchResult; + uInt64 myTotalCycles; std::chrono::time_point myVirtualTime; + + private: + + EmulationWorker(const EmulationWorker&) = delete; + + EmulationWorker(EmulationWorker&&) = delete; + + EmulationWorker& operator=(const EmulationWorker&) = delete; + + EmulationWorker& operator=(EmulationWorker&&) = delete; }; #endif // EMULATION_WORKER_HXX diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 5fbb81b2b..65b9c09ac 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -54,6 +54,7 @@ #include "Version.hxx" #include "TIA.hxx" #include "DispatchResult.hxx" +#include "EmulationWorker.hxx" #include "OSystem.hxx" @@ -637,27 +638,35 @@ float OSystem::frameRate() const } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -double OSystem::dispatchEmulation(uInt32 cyclesPerSecond) +double OSystem::dispatchEmulation(EmulationWorker& emulationWorker) { if (!myConsole) return 0.; - Int64 totalCycles = 0; - const Int64 minCycles = myConsole->emulationTiming().minCyclesPerTimeslice(); - const Int64 maxCycles = myConsole->emulationTiming().maxCyclesPerTimeslice(); + TIA& tia(myConsole->tia()); + EmulationTiming& timing(myConsole->emulationTiming()); DispatchResult dispatchResult; - do { - myConsole->tia().update(dispatchResult, totalCycles > 0 ? minCycles - totalCycles : maxCycles); + bool framePending = tia.newFramePending(); + if (framePending) tia.renderToFrameBuffer(); - totalCycles += dispatchResult.getCycles(); - } while (totalCycles < minCycles && dispatchResult.getStatus() == DispatchResult::Status::ok); + emulationWorker.start( + timing.cyclesPerSecond(), + timing.maxCyclesPerTimeslice(), + timing.minCyclesPerTimeslice(), + &dispatchResult, + &tia + ); + + if (framePending) myFrameBuffer->updateInEmulationMode(); + + uInt64 totalCycles = emulationWorker.stop(); if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start(); if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying()) myConsole->fry(); - return static_cast(totalCycles) / static_cast(cyclesPerSecond); + return static_cast(totalCycles) / static_cast(timing.cyclesPerSecond()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -666,6 +675,7 @@ void OSystem::mainLoop() // Sleep-based wait: good for CPU, bad for graphical sync bool busyWait = mySettings->getString("timing") != "sleep"; time_point virtualTime = high_resolution_clock::now(); + EmulationWorker emulationWorker; for(;;) { @@ -674,14 +684,9 @@ void OSystem::mainLoop() double timesliceSeconds; - if (myEventHandler->state() == EventHandlerState::EMULATION) { - timesliceSeconds = dispatchEmulation(myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1); - - if (myConsole && myConsole->tia().newFramePending()) { - myConsole->tia().renderToFrameBuffer(); - myFrameBuffer->updateInEmulationMode(); - } - } else { + if (myEventHandler->state() == EventHandlerState::EMULATION) + timesliceSeconds = dispatchEmulation(emulationWorker); + else { timesliceSeconds = 1. / 30.; myFrameBuffer->update(); } diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 0b9a3521c..85d23b9d6 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -38,6 +38,7 @@ class Settings; class Sound; class StateManager; class VideoDialog; +class EmulationWorker; #include "FSNode.hxx" #include "FrameBufferConstants.hxx" @@ -563,7 +564,7 @@ class OSystem void validatePath(string& path, const string& setting, const string& defaultpath); - double dispatchEmulation(uInt32 cyclesPerSecond); + double dispatchEmulation(EmulationWorker& emulationWorker); // Following constructors and assignment operators not supported OSystem(const OSystem&) = delete; From b955113f837ad2a5bb962b1b867607ee2db1e608 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Thu, 7 Jun 2018 19:44:13 -0230 Subject: [PATCH 65/77] Updated VS project file for EmulationWorker class. --- src/windows/Stella.vcxproj | 2 ++ src/windows/Stella.vcxproj.filters | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index b5de19a15..9de68a454 100644 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -329,6 +329,7 @@ + @@ -634,6 +635,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index f6dae28e3..4c27c863a 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -927,6 +927,9 @@ Source Files\emucore + + Source Files\emucore + @@ -1895,6 +1898,9 @@ Header Files\emucore + + Header Files\emucore + From 6cb9efac28e68a81d67fafe693aff91007d931da Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Thu, 7 Jun 2018 19:56:36 -0230 Subject: [PATCH 66/77] Updated OSX project file for EmulationWorker class. Fixed minor compile warning, and made a method const. --- src/emucore/PropsSet.hxx | 2 +- src/macosx/stella.xcodeproj/project.pbxproj | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/emucore/PropsSet.hxx b/src/emucore/PropsSet.hxx index 187110f63..9fc2cf927 100644 --- a/src/emucore/PropsSet.hxx +++ b/src/emucore/PropsSet.hxx @@ -115,7 +115,7 @@ class PropertiesSet /** Return the size of the myExternalProps list */ - uInt32 size() { return myExternalProps.size(); } + uInt64 size() const { return myExternalProps.size(); } private: using PropsList = std::map; diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index 2370196c3..aa63a229a 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -617,6 +617,8 @@ DCF7B0DF10A762FC007A2870 /* CartFA.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF7B0DB10A762FC007A2870 /* CartFA.cxx */; }; DCF7B0E010A762FC007A2870 /* CartFA.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF7B0DC10A762FC007A2870 /* CartFA.hxx */; }; DCFB9FAC1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFB9FAB1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx */; }; + DCFCDE7220C9E66500915CBE /* EmulationWorker.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */; }; + DCFCDE7320C9E66500915CBE /* EmulationWorker.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */; }; DCFF14CD18B0260300A20364 /* EventHandlerSDL2.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFF14CB18B0260300A20364 /* EventHandlerSDL2.cxx */; }; DCFF14CE18B0260300A20364 /* EventHandlerSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */; }; DCFFE59D12100E1400DFA000 /* ComboDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */; }; @@ -1307,6 +1309,8 @@ DCF7B0DB10A762FC007A2870 /* CartFA.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartFA.cxx; sourceTree = ""; }; DCF7B0DC10A762FC007A2870 /* CartFA.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartFA.hxx; sourceTree = ""; }; DCFB9FAB1ECA2609004FD69B /* DelayQueueIteratorImpl.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueIteratorImpl.hxx; sourceTree = ""; }; + DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EmulationWorker.cxx; sourceTree = ""; }; + DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EmulationWorker.hxx; sourceTree = ""; }; DCFF14CB18B0260300A20364 /* EventHandlerSDL2.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EventHandlerSDL2.cxx; sourceTree = ""; }; DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EventHandlerSDL2.hxx; sourceTree = ""; }; DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ComboDialog.cxx; sourceTree = ""; }; @@ -1793,6 +1797,8 @@ 2DE2DF3F0627AE07006BEC99 /* Driving.hxx */, E034A5EC209FB25C00C89E9E /* EmulationTiming.cxx */, E034A5ED209FB25C00C89E9E /* EmulationTiming.hxx */, + DCFCDE7020C9E66500915CBE /* EmulationWorker.cxx */, + DCFCDE7120C9E66500915CBE /* EmulationWorker.hxx */, 2DE2DF410627AE07006BEC99 /* Event.hxx */, 2D733D6E062895B2006265D9 /* EventHandler.cxx */, 2D733D6F062895B2006265D9 /* EventHandler.hxx */, @@ -2406,6 +2412,7 @@ DC74D6A2138D4D7E00F05C5C /* StringParser.hxx in Headers */, DC2874071F8F2278004BF21A /* TrapArray.hxx in Headers */, DCCA26B41FA64D5E000EE4D8 /* FrameManager.hxx in Headers */, + DCFCDE7320C9E66500915CBE /* EmulationWorker.hxx in Headers */, DC6C726313CDEA0A008A5975 /* LoggerDialog.hxx in Headers */, DC8C1BAE14B25DE7006440EE /* CartCM.hxx in Headers */, DCF3A6FB1DFC75E3008A8AF3 /* Player.hxx in Headers */, @@ -2621,6 +2628,7 @@ 2D91747E09BA90380026E9FF /* CartE7.cxx in Sources */, DC9616321F817830008A2206 /* PointingDeviceWidget.cxx in Sources */, 2D91747F09BA90380026E9FF /* CartF4.cxx in Sources */, + DCFCDE7220C9E66500915CBE /* EmulationWorker.cxx in Sources */, 2D91748009BA90380026E9FF /* CartF4SC.cxx in Sources */, 2D91748109BA90380026E9FF /* CartF6.cxx in Sources */, 2D91748209BA90380026E9FF /* CartF6SC.cxx in Sources */, From 8781889a7fef6b55f29fe911889c21f28ad7d907 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 9 Jun 2018 00:30:33 +0200 Subject: [PATCH 67/77] Documentation, cleaup, fix race in frame stats. --- src/common/AudioQueue.cxx | 2 +- src/emucore/Console.cxx | 2 +- src/emucore/EmulationWorker.cxx | 123 ++++++++++++++++++++++---------- src/emucore/EmulationWorker.hxx | 42 +++++++++-- src/emucore/FrameBuffer.cxx | 6 +- src/emucore/tia/TIA.cxx | 20 ++++++ src/emucore/tia/TIA.hxx | 15 ++++ 7 files changed, 161 insertions(+), 49 deletions(-) diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 237059816..845c63032 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -101,7 +101,7 @@ Int16* AudioQueue::enqueue(Int16* fragment) if (mySize < capacity) mySize++; else { myNextFragment = (myNextFragment + 1) % capacity; - //(cerr << "audio buffer overflow\n").flush(); + (cerr << "audio buffer overflow\n").flush(); } return newFragment; diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 66b084603..376548e02 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -937,7 +937,7 @@ void Console::generateColorLossPalette() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - float Console::getFramerate() const { - return myTIA->frameRate(); + return myTIA->frameBufferFrameRate(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/EmulationWorker.cxx b/src/emucore/EmulationWorker.cxx index 728ddd5c8..05dc1f650 100644 --- a/src/emucore/EmulationWorker.cxx +++ b/src/emucore/EmulationWorker.cxx @@ -34,7 +34,7 @@ EmulationWorker::EmulationWorker() : myPendingSignal(Signal::none), myState(Stat &EmulationWorker::threadMain, this, &threadInitialized, &mutex ); - // Wait until the thread has acquired myWakeupMutex and moved on + // Wait until the thread has acquired myThreadIsRunningMutex and moved on while (myState == State::initializing) threadInitialized.wait(lock); } @@ -43,7 +43,7 @@ EmulationWorker::~EmulationWorker() { // This has to run in a block in order to release the mutex before joining { - std::unique_lock lock(myWakeupMutex); + std::unique_lock lock(myThreadIsRunningMutex); if (myState != State::exception) { signalQuit(); @@ -71,27 +71,36 @@ void EmulationWorker::handlePossibleException() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EmulationWorker::start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult, TIA* tia) { - waitForSignalClear(); + // Wait until any pending signal has been processed + waitUntilPendingSignalHasProcessed(); - // Aquire the mutex -> wait until the thread is suspended - std::unique_lock lock(myWakeupMutex); + // Run in a block to release the mutex before notifying; this avoids an unecessary + // block that will waste a timeslice + { + // Aquire the mutex -> wait until the thread is suspended + std::unique_lock lock(myThreadIsRunningMutex); - // Pass on possible exceptions - handlePossibleException(); + // Pass on possible exceptions + handlePossibleException(); - // NB: The thread does not suspend execution in State::initialized - if (myState != State::waitingForResume) - fatal("start called on running or dead worker"); + // Make sure that we don't overwrite the exit condition. + // This case is hypothetical and cannot happen, but handling it does not hurt, either + if (myPendingSignal == Signal::quit) return; - // Store the parameters for emulation - myTia = tia; - myCyclesPerSecond = cyclesPerSecond; - myMaxCycles = maxCycles; - myMinCycles = minCycles; - myDispatchResult = dispatchResult; + // NB: The thread does not suspend execution in State::initialized + if (myState != State::waitingForResume) + fatal("start called on running or dead worker"); - // Set the signal... - myPendingSignal = Signal::resume; + // Store the parameters for emulation + myTia = tia; + myCyclesPerSecond = cyclesPerSecond; + myMaxCycles = maxCycles; + myMinCycles = minCycles; + myDispatchResult = dispatchResult; + + // Raise the signal... + myPendingSignal = Signal::resume; + } // ... and wakeup the thread myWakeupCondition.notify_one(); @@ -100,29 +109,40 @@ void EmulationWorker::start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 min // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt64 EmulationWorker::stop() { - waitForSignalClear(); + // See EmulationWorker::start above for the gory details + waitUntilPendingSignalHasProcessed(); - std::unique_lock lock(myWakeupMutex); - handlePossibleException(); + uInt64 totalCycles; + { + std::unique_lock lock(myThreadIsRunningMutex); - // If the worker has stopped on its own, we return - if (myState == State::waitingForResume) return 0; + // Paranoia: make sure that we don't doublecount an emulation timeslice + totalCycles = myTotalCycles; + myTotalCycles = 0; - // NB: The thread does not suspend execution in State::initialized or State::running - if (myState != State::waitingForStop) - fatal("stop called on a dead worker"); + handlePossibleException(); - myPendingSignal = Signal::stop; + if (myPendingSignal == Signal::quit) return totalCycles; + + // If the worker has stopped on its own, we return + if (myState == State::waitingForResume) return totalCycles; + + // NB: The thread does not suspend execution in State::initialized or State::running + if (myState != State::waitingForStop) + fatal("stop called on a dead worker"); + + myPendingSignal = Signal::stop; + } myWakeupCondition.notify_one(); - return myTotalCycles; + return totalCycles; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EmulationWorker::threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex) { - std::unique_lock lock(myWakeupMutex); + std::unique_lock lock(myThreadIsRunningMutex); try { { @@ -137,11 +157,18 @@ void EmulationWorker::threadMain(std::condition_variable* initializedCondition, initializedCondition->notify_one(); } + // Loop until we have an exit condition while (myPendingSignal != Signal::quit) handleWakeup(lock); } catch (...) { + // Store away the exception and the state accordingly myPendingException = std::current_exception(); myState = State::exception; + + // Raising the exit condition is consistent and makes shure that the main thread + // will not deadlock if an exception is raised while it is waiting for a signal + // to be processed. + signalQuit(); } } @@ -150,6 +177,7 @@ void EmulationWorker::handleWakeup(std::unique_lock& lock) { switch (myState) { case State::initialized: + // Enter waitingForResume and sleep after initialization myState = State::waitingForResume; myWakeupCondition.wait(lock); break; @@ -172,13 +200,19 @@ void EmulationWorker::handleWakeupFromWaitingForResume(std::unique_lock go for another emulation timeslice dispatchEmulation(lock); else + // Wakeup was spurious, reenter sleep myWakeupCondition.wait_until(lock, myVirtualTime); break; @@ -220,6 +259,7 @@ void EmulationWorker::handleWakeupFromWaitingForStop(std::unique_lock& lock) { + // Technically, we could do without State::running, but it is cleaner and might be useful in the future myState = State::running; uInt64 totalCycles = 0; @@ -234,18 +274,22 @@ void EmulationWorker::dispatchEmulation(std::unique_lock& lock) bool continueEmulating = false; if (myDispatchResult->getStatus() == DispatchResult::Status::ok) { - // If emulation finished successfully, we can go for another round + // If emulation finished successfully, we are free to go for another round duration timesliceSeconds(static_cast(totalCycles) / static_cast(myCyclesPerSecond)); myVirtualTime += duration_cast(timesliceSeconds); - myState = State::waitingForStop; + // If we aren't fast enough to keep up with the emulation, we stop immediatelly to avoid + // starving the system for processing time --- emulation will stutter anyway. continueEmulating = myVirtualTime > high_resolution_clock::now(); } if (continueEmulating) { + // If we are free to continue emulating, we sleep until either the timeslice has passed or we + // have been signalled from the main thread myState = State::waitingForStop; myWakeupCondition.wait_until(lock, myVirtualTime); } else { + // If can't continue, we just stop and wait to be signalled myState = State::waitingForResume; myWakeupCondition.wait(lock); } @@ -254,8 +298,10 @@ void EmulationWorker::dispatchEmulation(std::unique_lock& lock) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EmulationWorker::clearSignal() { - std::unique_lock lock(mySignalChangeMutex); - myPendingSignal = Signal::none; + { + std::unique_lock lock(mySignalChangeMutex); + myPendingSignal = Signal::none; + } mySignalChangeCondition.notify_one(); } @@ -263,17 +309,20 @@ void EmulationWorker::clearSignal() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void EmulationWorker::signalQuit() { - std::unique_lock lock(mySignalChangeMutex); - myPendingSignal = Signal::quit; + { + std::unique_lock lock(mySignalChangeMutex); + myPendingSignal = Signal::quit; + } mySignalChangeCondition.notify_one(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EmulationWorker::waitForSignalClear() +void EmulationWorker::waitUntilPendingSignalHasProcessed() { std::unique_lock lock(mySignalChangeMutex); + // White until there is no pending signal (or the exit condition has been raised) while (myPendingSignal != Signal::none && myPendingSignal != Signal::quit) mySignalChangeCondition.wait(lock); } diff --git a/src/emucore/EmulationWorker.hxx b/src/emucore/EmulationWorker.hxx index c1fbacfa8..57fbaa3e9 100644 --- a/src/emucore/EmulationWorker.hxx +++ b/src/emucore/EmulationWorker.hxx @@ -15,6 +15,26 @@ // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ +/* + * This class is the core of stella's real time scheduling. Scheduling is a two step + * process that is shared between the main loop in OSystem and this class. + * + * In emulation mode (as opposed to debugger, menu, etc.), each iteration of the main loop + * instructs the emulation worker to start emulation on a separate thread and then proceeds + * to render the last frame produced by the TIA (if any). After the frame has been rendered, + * the worker is stopped, and the main thread sleeps until the time allotted to the emulation + * timeslice (as calculated from the 6507 cycles that have passed) has been reached. After + * that, it iterates. + * + * The emulation worker contains its own microscheduling. After emulating a timeslice, it sleeps + * until either the allotted time is up or it has been signalled to stop. If the time is up + * without the signal, the worker will emulate another timeslice, etc. + * + * In combination, the scheduling in the main loop and the microscheduling in the worker + * ensure that the emulation continues to run even if rendering blocks, ensuring the real + * time scheduling required for cycle exact audio to work. + */ + #ifndef EMULATION_WORKER_HXX #define EMULATION_WORKER_HXX @@ -47,8 +67,14 @@ class EmulationWorker ~EmulationWorker(); + /** + Wake up the worker and start emulation with the specified parameters. + */ void start(uInt32 cyclesPerSecond, uInt32 maxCycles, uInt32 minCycles, DispatchResult* dispatchResult, TIA* tia); + /** + Stop emulation and return the number of 6507 cycles emulated. + */ uInt64 stop(); private: @@ -58,12 +84,6 @@ class EmulationWorker // Passing references into a thread is awkward and requires std::ref -> use pointers here void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex); - void clearSignal(); - - void signalQuit(); - - void waitForSignalClear(); - void handleWakeup(std::unique_lock& lock); void handleWakeupFromWaitingForResume(std::unique_lock& lock); @@ -72,6 +92,12 @@ class EmulationWorker void dispatchEmulation(std::unique_lock& lock); + void clearSignal(); + + void signalQuit(); + + void waitUntilPendingSignalHasProcessed(); + void fatal(string message); private: @@ -80,8 +106,10 @@ class EmulationWorker std::thread myThread; + // Condition variable for waking up the thread std::condition_variable myWakeupCondition; - std::mutex myWakeupMutex; + // THe thread is running while this mutex is locked + std::mutex myThreadIsRunningMutex; std::condition_variable mySignalChangeCondition; std::mutex mySignalChangeMutex; diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index d4170066c..29257e880 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -344,7 +344,7 @@ void FrameBuffer::updateInEmulationMode() if(myStatsMsg.enabled) drawFrameStats(); - myLastScanlines = myOSystem.console().tia().scanlinesLastFrame(); + myLastScanlines = myOSystem.console().tia().frameBufferScanlinesLastFrame(); myPausedCount = 0; // Draw any pending messages @@ -389,9 +389,9 @@ void FrameBuffer::drawFrameStats() myStatsMsg.surface->invalidate(); // draw scanlines - color = myOSystem.console().tia().scanlinesLastFrame() != myLastScanlines ? + color = myOSystem.console().tia().frameBufferScanlinesLastFrame() != myLastScanlines ? uInt32(kDbgColorRed) : myStatsMsg.color; - std::snprintf(msg, 30, "%3u", myOSystem.console().tia().scanlinesLastFrame()); + std::snprintf(msg, 30, "%3u", myOSystem.console().tia().frameBufferScanlinesLastFrame()); myStatsMsg.surface->drawString(font(), msg, xPos, YPOS, myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor); xPos += font().getStringWidth(msg); diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index fd9cdbb77..8bafaeee7 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -181,6 +181,9 @@ void TIA::reset() frameReset(); // Recalculate the size of the display } + myFrontBufferFrameRate = myFrameBufferFrameRate = 0; + myFrontBufferScanlines = myFrameBufferScanlines = 0; + myNewFramePending = false; // Must be done last, after all other items have reset @@ -287,6 +290,11 @@ bool TIA::save(Serializer& out) const out.putByteArray(myShadowRegisters, 64); out.putLong(myCyclesAtFrameStart); + + out.putInt(myFrameBufferScanlines); + out.putInt(myFrontBufferScanlines); + out.putDouble(myFrameBufferFrameRate); + out.putDouble(myFrontBufferFrameRate); } catch(...) { @@ -355,6 +363,11 @@ bool TIA::load(Serializer& in) in.getByteArray(myShadowRegisters, 64); myCyclesAtFrameStart = in.getLong(); + + myFrameBufferScanlines = in.getInt(); + myFrontBufferScanlines = in.getInt(); + myFrameBufferFrameRate = in.getDouble(); + myFrontBufferFrameRate = in.getDouble(); } catch(...) { @@ -831,6 +844,9 @@ void TIA::renderToFrameBuffer() if (!myNewFramePending) return; memcpy(myFramebuffer, myFrontBuffer, 160 * TIAConstants::frameBufferHeight); + + myFrameBufferFrameRate = myFrontBufferFrameRate; + myFrameBufferScanlines = myFrontBufferScanlines; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1181,6 +1197,10 @@ void TIA::onFrameComplete() memset(myBackBuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160); memcpy(myFrontBuffer, myBackBuffer, 160 * TIAConstants::frameBufferHeight); + + myFrontBufferFrameRate = frameRate(); + myFrontBufferScanlines = scanlinesLastFrame(); + myNewFramePending = true; } diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index f4b5ba869..95c53d8ec 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -250,6 +250,11 @@ class TIA : public Device float frameRate() const { return myFrameManager ? myFrameManager->frameRate() : 0; } + /** + The same, but for the frame in the frame buffer. + */ + float frameBufferFrameRate() const { return myFrameBufferFrameRate; } + /** Enables/disables color-loss for PAL modes only. @@ -295,6 +300,11 @@ class TIA : public Device */ uInt32 scanlinesLastFrame() const { return myFrameManager->scanlinesLastFrame(); } + /** + The same, but for the frame in the frame buffer. + */ + uInt32 frameBufferScanlinesLastFrame() const { return myFrameBufferScanlines; } + /** Answers the total system cycles from the start of the emulation. */ @@ -681,6 +691,11 @@ class TIA : public Device uInt8 myBackBuffer[160 * TIAConstants::frameBufferHeight]; uInt8 myFrontBuffer[160 * TIAConstants::frameBufferHeight]; + // We snapshot frame statistics when the back buffer is copied to the front buffer + // and when the front buffer is copied to the frame buffer + uInt32 myFrontBufferScanlines, myFrameBufferScanlines; + float myFrontBufferFrameRate, myFrameBufferFrameRate; + // Did we emit a frame? bool myNewFramePending; From 0fbd8757838cd9a735939f247356e63f942acbca Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 9 Jun 2018 23:16:59 +0200 Subject: [PATCH 68/77] Documentation. --- src/emucore/EmulationWorker.hxx | 104 +++++++++++++++++++++++++++----- src/emucore/OSystem.cxx | 20 +++++- 2 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/emucore/EmulationWorker.hxx b/src/emucore/EmulationWorker.hxx index 57fbaa3e9..bacde5801 100644 --- a/src/emucore/EmulationWorker.hxx +++ b/src/emucore/EmulationWorker.hxx @@ -16,8 +16,8 @@ //============================================================================ /* - * This class is the core of stella's real time scheduling. Scheduling is a two step - * process that is shared between the main loop in OSystem and this class. + * This class is the core of stella's scheduling. Scheduling is a two step process + * that is shared between the main loop in OSystem and this class. * * In emulation mode (as opposed to debugger, menu, etc.), each iteration of the main loop * instructs the emulation worker to start emulation on a separate thread and then proceeds @@ -26,9 +26,9 @@ * timeslice (as calculated from the 6507 cycles that have passed) has been reached. After * that, it iterates. * - * The emulation worker contains its own microscheduling. After emulating a timeslice, it sleeps + * The emulation worker does its own microscheduling. After emulating a timeslice, it sleeps * until either the allotted time is up or it has been signalled to stop. If the time is up - * without the signal, the worker will emulate another timeslice, etc. + * without being signalled, the worker will emulate another timeslice, etc. * * In combination, the scheduling in the main loop and the microscheduling in the worker * ensure that the emulation continues to run even if rendering blocks, ensuring the real @@ -52,19 +52,16 @@ class DispatchResult; class EmulationWorker { - public: - enum class State { - initializing, initialized, waitingForResume, running, waitingForStop, exception - }; - - enum class Signal { - resume, stop, quit, none - }; - public: + /** + The constructor starts the worker thread and waits until it has initialized. + */ EmulationWorker(); + /** + The destructor signals quit to the worker and joins. + */ ~EmulationWorker(); /** @@ -79,52 +76,127 @@ class EmulationWorker private: + /** + Check whether an exception occurred on the thread and rethrow if appicable. + */ void handlePossibleException(); - // Passing references into a thread is awkward and requires std::ref -> use pointers here + /** + The main thread entry point. + Passing references into a thread is awkward and requires std::ref -> use pointers here + */ void threadMain(std::condition_variable* initializedCondition, std::mutex* initializationMutex); + /** + Handle thread wakeup after sleep depending on the thread state. + */ void handleWakeup(std::unique_lock& lock); + /** + Handle wakeup while sleeping and waiting to be resumed. + */ void handleWakeupFromWaitingForResume(std::unique_lock& lock); + /** + Handle wakeup while sleeping and waiting to be stopped (or for the timeslice + to expire). + */ void handleWakeupFromWaitingForStop(std::unique_lock& lock); + /** + Run the emulation, adjust the thread state according to the result and sleep. + */ void dispatchEmulation(std::unique_lock& lock); + /** + Clear any pending signal and wake up the main thread (if it is waiting for the signal + to be cleared). + */ void clearSignal(); + /** + * Signal quit and wake up the main thread if applicable. + */ void signalQuit(); + /** + Wait and sleep until a pending signal has been processed (or quit sigmalled). + This is called from the main thread. + */ void waitUntilPendingSignalHasProcessed(); + /** + Log a fatal error to cerr and throw a runtime exception. + */ void fatal(string message); private: + /** + Thread state. + */ + enum class State { + // Initial state + initializing, + // Thread has initialized. From the point, myThreadIsRunningMutex is locked if and only if + // the thread is running. + initialized, + // Sleeping and waiting for emulation to be resumed + waitingForResume, + // Running and emulating + running, + // Sleeping and waiting for emulation to be stopped + waitingForStop, + // An exception occurred and the thread has terminated (or is terminating) + exception + }; - TIA* myTia; + /** + Thread behavior is controlled by signals that are raised prior to waking up the thread. + */ + enum class Signal { + // Resume emulation + resume, + // Stop emulation + stop, + // Quit (either during destruction or after an exception) + quit, + // No pending signal + none + }; + private: + + // Worker thread std::thread myThread; // Condition variable for waking up the thread std::condition_variable myWakeupCondition; - // THe thread is running while this mutex is locked + // The thread is running if and only if while this mutex is locked std::mutex myThreadIsRunningMutex; + // Condition variable to signal changes to the pending signal std::condition_variable mySignalChangeCondition; + // This mutex guards reading and writing the pending signal. std::mutex mySignalChangeMutex; + // Any exception on the worker thread is saved here to be rethrown on the main thread. std::exception_ptr myPendingException; + + // Any pending signal (or Signal::none) Signal myPendingSignal; // The initial access to myState is not synchronized -> make this atomic std::atomic myState; + // Emulation parameters + TIA* myTia; uInt32 myCyclesPerSecond; uInt32 myMaxCycles; uInt32 myMinCycles; DispatchResult* myDispatchResult; + // Total number of cycles during this emulation run uInt64 myTotalCycles; + // 6507 time std::chrono::time_point myVirtualTime; private: diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 65b9c09ac..87bb00905 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -646,9 +646,14 @@ double OSystem::dispatchEmulation(EmulationWorker& emulationWorker) EmulationTiming& timing(myConsole->emulationTiming()); DispatchResult dispatchResult; + // Check whether we have a frame pending for rendering... bool framePending = tia.newFramePending(); + // ... and copy it to the frame buffer. It is important to do this before + // the worker is started to avoid racing. if (framePending) tia.renderToFrameBuffer(); + // Start emulation on a dedicated thread. It will do its own scheduling to sync 6507 and real time + // and will run until we stop the worker. emulationWorker.start( timing.cyclesPerSecond(), timing.maxCyclesPerTimeslice(), @@ -657,15 +662,21 @@ double OSystem::dispatchEmulation(EmulationWorker& emulationWorker) &tia ); + // Render the frame. This may block, but emulation will continue to run on the worker, so the + // audio pipeline is kept fed :) if (framePending) myFrameBuffer->updateInEmulationMode(); + // Stop the worker and wait until it has finished uInt64 totalCycles = emulationWorker.stop(); + // Break or trap? -> start debugger if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start(); + // Handle frying if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying()) myConsole->fry(); + // Return the 6507 time used in seconds return static_cast(totalCycles) / static_cast(timing.cyclesPerSecond()); } @@ -674,7 +685,9 @@ void OSystem::mainLoop() { // Sleep-based wait: good for CPU, bad for graphical sync bool busyWait = mySettings->getString("timing") != "sleep"; + // 6507 time time_point virtualTime = high_resolution_clock::now(); + // The emulation worker EmulationWorker emulationWorker; for(;;) @@ -685,16 +698,19 @@ void OSystem::mainLoop() double timesliceSeconds; if (myEventHandler->state() == EventHandlerState::EMULATION) + // Dispatch emulation and render frame (if applicable) timesliceSeconds = dispatchEmulation(emulationWorker); else { + // Render the GUI with 30 Hz in all other modes timesliceSeconds = 1. / 30.; myFrameBuffer->update(); } duration timeslice(timesliceSeconds); - virtualTime += duration_cast(timeslice); time_point now = high_resolution_clock::now(); + + // We allow 6507 time to lag behind by one frame max double maxLag = myConsole ? ( static_cast(myConsole->emulationTiming().cyclesPerFrame()) / @@ -703,8 +719,10 @@ void OSystem::mainLoop() : 0; if (duration_cast>(now - virtualTime).count() > maxLag) + // If 6507 time is lagging behind more than one frame we reset it to real time virtualTime = now; else if (virtualTime > now) { + // Wait until we have caught up with 6507 time if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) { while (high_resolution_clock::now() < virtualTime); } From 0f2551509949b9169a4d37f3cf460e77ea44d687 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 9 Jun 2018 23:18:13 +0200 Subject: [PATCH 69/77] Update TODO. --- TODO_audio.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO_audio.md b/TODO_audio.md index 3781ec26f..48179f2a0 100644 --- a/TODO_audio.md +++ b/TODO_audio.md @@ -1,7 +1,6 @@ # Missing features * Reimplement target FPS mode - * Fixup OpenGL sync, ensure that FB only rerenders after a frame has been generated * Add GUI for new audio parameters (prebuffer fragment count, resampling quality) # Cleanup From d127865deebcfe8785e5533adc7312351c58d57e Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Tue, 19 Jun 2018 21:37:18 +0200 Subject: [PATCH 70/77] Remove a leftover from refactoring. --- src/emucore/tia/frame-manager/FrameManager.cxx | 13 +------------ src/emucore/tia/frame-manager/FrameManager.hxx | 3 --- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/emucore/tia/frame-manager/FrameManager.cxx b/src/emucore/tia/frame-manager/FrameManager.cxx index 18b1a54c1..c2010e326 100644 --- a/src/emucore/tia/frame-manager/FrameManager.cxx +++ b/src/emucore/tia/frame-manager/FrameManager.cxx @@ -47,9 +47,7 @@ FrameManager::FrameManager() myHeight(0), myFixedHeight(0), myYStart(0), - myJitterEnabled(false), - myStableFrameLines(-1), - myStableFrameHeightCountdown(0) + myJitterEnabled(false) { onLayoutChange(); } @@ -63,9 +61,6 @@ void FrameManager::onReset() myVsyncLines = 0; myY = 0; - myStableFrameLines = -1; - myStableFrameHeightCountdown = 0; - myJitterEmulation.reset(); } @@ -230,9 +225,6 @@ bool FrameManager::onSave(Serializer& out) const out.putBool(myJitterEnabled); - out.putInt(myStableFrameLines); - out.putInt(myStableFrameHeightCountdown); - return true; } @@ -257,8 +249,5 @@ bool FrameManager::onLoad(Serializer& in) myJitterEnabled = in.getBool(); - myStableFrameLines = in.getInt(); - myStableFrameHeightCountdown = in.getInt(); - return true; } diff --git a/src/emucore/tia/frame-manager/FrameManager.hxx b/src/emucore/tia/frame-manager/FrameManager.hxx index ee9297824..2f6a156af 100644 --- a/src/emucore/tia/frame-manager/FrameManager.hxx +++ b/src/emucore/tia/frame-manager/FrameManager.hxx @@ -100,9 +100,6 @@ class FrameManager: public AbstractFrameManager { bool myJitterEnabled; - Int32 myStableFrameLines; - uInt8 myStableFrameHeightCountdown; - JitterEmulation myJitterEmulation; private: From ef5261689a9cd87bc583e5f79eef8de3d0c509ae Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 23 Jun 2018 00:58:28 +0200 Subject: [PATCH 71/77] Major audio settings overhaul. --- .vscode/settings.json | 7 +- src/common/AudioSettings.cxx | 262 ++++++++++++++++++++++++++++++++ src/common/AudioSettings.hxx | 122 +++++++++++++++ src/common/MediaFactory.hxx | 6 +- src/common/SoundNull.hxx | 2 +- src/common/SoundSDL2.cxx | 38 ++--- src/common/SoundSDL2.hxx | 7 +- src/common/module.mk | 3 +- src/emucore/Console.cxx | 17 ++- src/emucore/Console.hxx | 8 +- src/emucore/EmulationTiming.cxx | 33 +++- src/emucore/EmulationTiming.hxx | 13 +- src/emucore/OSystem.cxx | 7 +- src/emucore/OSystem.hxx | 4 + src/emucore/Settings.cxx | 36 ++--- src/emucore/Sound.hxx | 2 +- src/windows/SettingsWINDOWS.cxx | 4 +- 17 files changed, 505 insertions(+), 66 deletions(-) create mode 100644 src/common/AudioSettings.cxx create mode 100644 src/common/AudioSettings.hxx diff --git a/.vscode/settings.json b/.vscode/settings.json index effb7653b..d271851f7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,6 +53,11 @@ "typeinfo": "cpp", "__mutex_base": "cpp", "mutex": "cpp", - "condition_variable": "cpp" + "condition_variable": "cpp", + "*.ins": "cpp", + "cstring": "cpp", + "iostream": "cpp", + "cstdint": "cpp", + "ostream": "cpp" } } diff --git a/src/common/AudioSettings.cxx b/src/common/AudioSettings.cxx new file mode 100644 index 000000000..bdc2e2ead --- /dev/null +++ b/src/common/AudioSettings.cxx @@ -0,0 +1,262 @@ +//============================================================================ +// +// 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-2018 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 "AudioSettings.hxx" +#include "Settings.hxx" + +namespace { + uInt32 convertInt(int x, int defaultValue) + { + return x <= defaultValue ? defaultValue : x; + } + + AudioSettings::Preset normalizedPreset(int numericPreset) + { + return ( + numericPreset >= static_cast(AudioSettings::Preset::custom) && + numericPreset <= static_cast(AudioSettings::Preset::veryHighQualityVeryLowLag) + ) ? static_cast(numericPreset) : AudioSettings::DEFAULT_PRESET; + } + + AudioSettings::ResamplingQuality normalizeResamplingQuality(int numericResamplingQuality) + { + return ( + numericResamplingQuality >= static_cast(AudioSettings::ResamplingQuality::nearestNeightbour) && + numericResamplingQuality <= static_cast(AudioSettings::ResamplingQuality::lanczos_3) + ) ? static_cast(numericResamplingQuality) : AudioSettings::DEFAULT_RESAMPLING_QUALITY; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioSettings::AudioSettings(Settings* settings) + : mySettings(settings) +{ + setPreset(normalizedPreset(mySettings->getInt(SETTING_PRESET))); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::normalize(Settings& settings) +{ + int settingPreset = settings.getInt(SETTING_PRESET); + Preset preset = normalizedPreset(settingPreset); + if (static_cast(preset) != settingPreset) settings.setValue(SETTING_PRESET, static_cast(DEFAULT_PRESET)); + + switch (settings.getInt(SETTING_SAMPLE_RATE)) { + case 44100: + case 48000: + case 96000: + break; + + default: + settings.setValue(SETTING_SAMPLE_RATE, DEFAULT_SAMPLE_RATE); + break; + } + + switch (settings.getInt(SETTING_FRAGMENT_SIZE)) { + case 128: + case 256: + case 512: + case 1024: + case 2048: + case 4096: + break; + + default: + settings.setValue(SETTING_FRAGMENT_SIZE, DEFAULT_FRAGMENT_SIZE); + break; + } + + int settingBufferSize = settings.getInt(SETTING_BUFFER_SIZE); + if (settingBufferSize < 0 || settingBufferSize > 20) settings.setValue(SETTING_BUFFER_SIZE, DEFAULT_BUFFER_SIZE); + + int settingHeadroom = settings.getInt(SETTING_HEADROOM); + if (settingHeadroom < 0 || settingHeadroom > 20) settings.setValue(SETTING_HEADROOM, DEFAULT_HEADROOM); + + int settingResamplingQuality = settings.getInt(SETTING_RESAMPLING_QUALITY); + ResamplingQuality resamplingQuality = normalizeResamplingQuality(settingResamplingQuality); + if (static_cast(resamplingQuality) != settingResamplingQuality) + settings.setValue(SETTING_RESAMPLING_QUALITY, static_cast(DEFAULT_RESAMPLING_QUALITY)); + + int settingVolume = settings.getInt(SETTING_VOLUME); + if (settingVolume < 0 || settingVolume > 100) settings.setValue(SETTING_VOLUME, DEFAULT_VOLUME); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioSettings::Preset AudioSettings::preset() +{ + updatePresetFromSettings(); + return myPreset; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioSettings::sampleRate() +{ + updatePresetFromSettings(); + return customSettings() ? convertInt(mySettings->getInt(SETTING_SAMPLE_RATE), DEFAULT_SAMPLE_RATE) : myPresetSampleRate; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioSettings::fragmentSize() +{ + updatePresetFromSettings(); + return customSettings() ? convertInt(mySettings->getInt(SETTING_FRAGMENT_SIZE), DEFAULT_FRAGMENT_SIZE) : myPresetFragmentSize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioSettings::bufferSize() +{ + updatePresetFromSettings(); + // 0 is a valid value -> keep it + return customSettings() ? convertInt(mySettings->getInt(SETTING_BUFFER_SIZE), 0) : myPresetBufferSize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioSettings::headroom() +{ + updatePresetFromSettings(); + // 0 is a valid value -> keep it + return customSettings() ? convertInt(mySettings->getInt(SETTING_HEADROOM), 0) : myPresetHeadroom; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioSettings::ResamplingQuality AudioSettings::resamplingQuality() +{ + updatePresetFromSettings(); + return customSettings() ? normalizeResamplingQuality(mySettings->getInt(SETTING_RESAMPLING_QUALITY)) : myPresetResamplingQuality; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioSettings::volume() const +{ + // 0 is a valid value -> keep it + return convertInt(mySettings->getInt(SETTING_VOLUME), 0); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AudioSettings::enabled() const +{ + return mySettings->getBool(SETTING_ENABLED); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setPreset(AudioSettings::Preset preset) +{ + if (preset == myPreset) return; + myPreset = preset; + + switch (myPreset) { + case Preset::custom: + break; + + case Preset::lowQualityMediumLag: + myPresetSampleRate = 44100; + myPresetFragmentSize = 1024; + myPresetBufferSize = 4; + myPresetHeadroom = 3; + myPresetResamplingQuality = ResamplingQuality::nearestNeightbour; + break; + + case Preset::highQualityMediumLag: + myPresetSampleRate = 44100; + myPresetFragmentSize = 1024; + myPresetBufferSize = 4; + myPresetHeadroom = 3; + myPresetResamplingQuality = ResamplingQuality::lanczos_2; + break; + + case Preset::highQualityLowLag: + myPresetSampleRate = 48000; + myPresetFragmentSize = 512; + myPresetBufferSize = 2; + myPresetHeadroom = 1; + myPresetResamplingQuality = ResamplingQuality::lanczos_2; + break; + + case Preset::veryHighQualityVeryLowLag: + myPresetSampleRate = 96000; + myPresetFragmentSize = 128; + myPresetBufferSize = 0; + myPresetHeadroom = 0; + myPresetResamplingQuality = ResamplingQuality::lanczos_3; + break; + + default: + throw runtime_error("invalid preset"); + } + + mySettings->setValue(SETTING_PRESET, static_cast(myPreset)); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setSampleRate(uInt32 sampleRate) +{ + mySettings->setValue(SETTING_SAMPLE_RATE, sampleRate); + normalize(*mySettings); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setFragmentSize(uInt32 fragmentSize) +{ + mySettings->setValue(SETTING_FRAGMENT_SIZE, fragmentSize); + normalize(*mySettings); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setBufferSize(uInt32 bufferSize) +{ + mySettings->setValue(SETTING_BUFFER_SIZE, bufferSize); + normalize(*mySettings); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setHeadroom(uInt32 headroom) +{ + mySettings->setValue(SETTING_HEADROOM, headroom); + normalize(*mySettings); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setResamplingQuality(AudioSettings::ResamplingQuality resamplingQuality) +{ + mySettings->setValue(SETTING_RESAMPLING_QUALITY, static_cast(resamplingQuality)); + normalize(*mySettings); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setVolume(uInt32 volume) +{ + mySettings->setValue(SETTING_VOLUME, volume); + normalize(*mySettings); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setEnabled(bool isEnabled) +{ + mySettings->setValue(SETTING_ENABLED, isEnabled); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AudioSettings::customSettings() const +{ + return myPreset == Preset::custom; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::updatePresetFromSettings() +{ + setPreset(normalizedPreset(mySettings->getInt(SETTING_PRESET))); +} diff --git a/src/common/AudioSettings.hxx b/src/common/AudioSettings.hxx new file mode 100644 index 000000000..3435a344d --- /dev/null +++ b/src/common/AudioSettings.hxx @@ -0,0 +1,122 @@ +//============================================================================ +// +// 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-2018 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 AUDIO_PARAMTERS_HXX +#define AUDIO_PARAMTERS_HXX + +#include "bspf.hxx" + +class Settings; + +class AudioSettings +{ + public: + + enum class Preset { + custom = 1, + lowQualityMediumLag = 2, + highQualityMediumLag = 3, + highQualityLowLag = 4, + veryHighQualityVeryLowLag = 5 + }; + + enum class ResamplingQuality { + nearestNeightbour = 1, + lanczos_2 = 2, + lanczos_3 = 3 + }; + + static constexpr const char* SETTING_PRESET = "audio.preset"; + static constexpr const char* SETTING_SAMPLE_RATE = "audio.sample_rate"; + static constexpr const char* SETTING_FRAGMENT_SIZE = "audio.fragment_size"; + static constexpr const char* SETTING_BUFFER_SIZE = "audio.buffer_size"; + static constexpr const char* SETTING_HEADROOM = "audio.headroom"; + static constexpr const char* SETTING_RESAMPLING_QUALITY = "audio.resampling_quality"; + static constexpr const char* SETTING_VOLUME = "audio.volume"; + static constexpr const char* SETTING_ENABLED = "audio.enabled"; + + static constexpr Preset DEFAULT_PRESET = Preset::highQualityMediumLag; + static constexpr uInt32 DEFAULT_SAMPLE_RATE = 44100; + static constexpr uInt32 DEFAULT_FRAGMENT_SIZE = 512; + static constexpr uInt32 DEFAULT_BUFFER_SIZE = 3; + static constexpr uInt32 DEFAULT_HEADROOM = 2; + static constexpr ResamplingQuality DEFAULT_RESAMPLING_QUALITY = ResamplingQuality::lanczos_2; + static constexpr uInt32 DEFAULT_VOLUME = 80; + static constexpr bool DEFAULT_ENABLED = true; + + public: + + AudioSettings() = default; + + AudioSettings(Settings* mySettings); + + static void initialize(Settings& settings); + + static void normalize(Settings& settings); + + Preset preset(); + + uInt32 sampleRate(); + + uInt32 fragmentSize(); + + uInt32 bufferSize(); + + uInt32 headroom(); + + ResamplingQuality resamplingQuality(); + + uInt32 volume() const; + + bool enabled() const; + + void setPreset(Preset preset); + + void setSampleRate(uInt32 sampleRate); + + void setFragmentSize(uInt32 fragmentSize); + + void setBufferSize(uInt32 bufferSize); + + void setHeadroom(uInt32 headroom); + + void setResamplingQuality(ResamplingQuality resamplingQuality); + + void setVolume(uInt32 volume); + + void setEnabled(bool isEnabled); + + private: + + bool customSettings() const; + + void updatePresetFromSettings(); + + private: + + Settings* mySettings; + + Preset myPreset; + + uInt32 myPresetSampleRate; + uInt32 myPresetFragmentSize; + uInt32 myPresetBufferSize; + uInt32 myPresetHeadroom; + ResamplingQuality myPresetResamplingQuality; +}; + +#endif // AUDIO_PARAMTERS_HXX diff --git a/src/common/MediaFactory.hxx b/src/common/MediaFactory.hxx index 1338b1ccd..708413967 100644 --- a/src/common/MediaFactory.hxx +++ b/src/common/MediaFactory.hxx @@ -50,6 +50,8 @@ #include "SoundNull.hxx" #endif +class AudioSettings; + /** This class deals with the different framebuffer/sound/event implementations for the various ports of Stella, and always returns a @@ -108,10 +110,10 @@ class MediaFactory return make_unique(osystem); } - static unique_ptr createAudio(OSystem& osystem) + static unique_ptr createAudio(OSystem& osystem, AudioSettings& audioSettings) { #ifdef SOUND_SUPPORT - return make_unique(osystem); + return make_unique(osystem, audioSettings); #else return make_unique(osystem); #endif diff --git a/src/common/SoundNull.hxx b/src/common/SoundNull.hxx index 21560c589..41bfbaef4 100644 --- a/src/common/SoundNull.hxx +++ b/src/common/SoundNull.hxx @@ -86,7 +86,7 @@ class SoundNull : public Sound @param percent The new volume percentage level for the sound device */ - void setVolume(Int32 percent) override { } + void setVolume(uInt32 percent) override { } /** Adjusts the volume of the sound device based on the given direction. diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index a7f87caf9..fd27d432e 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -30,16 +30,18 @@ #include "SoundSDL2.hxx" #include "AudioQueue.hxx" #include "EmulationTiming.hxx" +#include "AudioSettings.hxx" #include "audio/SimpleResampler.hxx" #include "audio/LanczosResampler.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundSDL2::SoundSDL2(OSystem& osystem) +SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings) : Sound(osystem), myIsInitializedFlag(false), myVolume(100), myVolumeFactor(0xffff), - myCurrentFragment(nullptr) + myCurrentFragment(nullptr), + myAudioSettings(audioSettings) { myOSystem.logMessage("SoundSDL2::SoundSDL2 started ...", 2); @@ -48,10 +50,10 @@ SoundSDL2::SoundSDL2(OSystem& osystem) // This fixes a bug most prevalent with ATI video cards in Windows, // whereby sound stopped working after the first video change SDL_AudioSpec desired; - desired.freq = myOSystem.settings().getInt("freq"); + desired.freq = myAudioSettings.sampleRate(); desired.format = AUDIO_F32SYS; desired.channels = 2; - desired.samples = myOSystem.settings().getInt("fragsize"); + desired.samples = myAudioSettings.fragmentSize(); desired.callback = callback; desired.userdata = static_cast(this); @@ -95,7 +97,7 @@ SoundSDL2::~SoundSDL2() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::setEnabled(bool state) { - myOSystem.settings().setValue("sound", state); + myAudioSettings.setEnabled(state); myOSystem.logMessage(state ? "SoundSDL2::setEnabled(true)" : "SoundSDL2::setEnabled(false)", 2); @@ -110,7 +112,7 @@ void SoundSDL2::open(shared_ptr audioQueue, myOSystem.logMessage("SoundSDL2::open started ...", 2); mute(true); - if(!myOSystem.settings().getBool("sound")) + if(!myAudioSettings.enabled()) { myOSystem.logMessage("Sound disabled\n", 1); return; @@ -121,7 +123,7 @@ void SoundSDL2::open(shared_ptr audioQueue, myCurrentFragment = nullptr; // Adjust volume to that defined in settings - setVolume(myOSystem.settings().getInt("volume")); + setVolume(myAudioSettings.volume()); // Show some info ostringstream buf; @@ -171,11 +173,11 @@ void SoundSDL2::reset() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL2::setVolume(Int32 percent) +void SoundSDL2::setVolume(uInt32 percent) { - if(myIsInitializedFlag && (percent >= 0) && (percent <= 100)) + if(myIsInitializedFlag && (percent <= 100)) { - myOSystem.settings().setValue("volume", percent); + myAudioSettings.setVolume(percent); myVolume = percent; SDL_LockAudio(); @@ -213,7 +215,7 @@ void SoundSDL2::adjustVolume(Int8 direction) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 SoundSDL2::getFragmentSize() const { - return myHardwareSpec.size; + return myHardwareSpec.samples; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -253,24 +255,26 @@ void SoundSDL2::initResampler() Resampler::Format formatTo = Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1); - int quality = myOSystem.settings().getInt("resampling.quality"); - switch (quality) { - case 1: + switch (myAudioSettings.resamplingQuality()) { + case AudioSettings::ResamplingQuality::nearestNeightbour: myResampler = make_unique(formatFrom, formatTo, nextFragmentCallback); (cerr << "resampling quality 1: using nearest neighbor resampling\n").flush(); break; - default: - case 2: + case AudioSettings::ResamplingQuality::lanczos_2: (cerr << "resampling quality 2: using nearest Lanczos resampling, a = 2\n").flush(); myResampler = make_unique(formatFrom, formatTo, nextFragmentCallback, 2); break; - case 3: + case AudioSettings::ResamplingQuality::lanczos_3: (cerr << "resampling quality 3: using nearest Lanczos resampling, a = 3\n").flush(); myResampler = make_unique(formatFrom, formatTo, nextFragmentCallback, 3); break; + + default: + throw runtime_error("invalid resampling quality"); + break; } } diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 38156f79a..5ffdd63ef 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -23,6 +23,7 @@ class OSystem; class AudioQueue; class EmulationTiming; +class AudioSettings; #include "SDL_lib.hxx" @@ -42,7 +43,7 @@ class SoundSDL2 : public Sound Create a new sound object. The init method must be invoked before using the object. */ - SoundSDL2(OSystem& osystem); + SoundSDL2(OSystem& osystem, AudioSettings& audioSettings); /** Destructor @@ -88,7 +89,7 @@ class SoundSDL2 : public Sound @param percent The new volume percentage level for the sound device */ - void setVolume(Int32 percent) override; + void setVolume(uInt32 percent) override; /** Adjusts the volume of the sound device based on the given direction. @@ -137,6 +138,8 @@ class SoundSDL2 : public Sound unique_ptr myResampler; + AudioSettings& myAudioSettings; + 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/common/module.mk b/src/common/module.mk index e482cb9b9..4be025053 100644 --- a/src/common/module.mk +++ b/src/common/module.mk @@ -16,7 +16,8 @@ MODULE_OBJS := \ src/common/SoundSDL2.o \ src/common/StateManager.o \ src/common/ZipHandler.o \ - src/common/AudioQueue.o + src/common/AudioQueue.o \ + src/common/AudioSettings.o MODULE_DIRS += \ src/common diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 376548e02..0739588c7 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -56,6 +56,7 @@ #include "TIAConstants.hxx" #include "FrameLayout.hxx" #include "AudioQueue.hxx" +#include "AudioSettings.hxx" #include "frame-manager/FrameManager.hxx" #include "frame-manager/FrameLayoutDetector.hxx" #include "frame-manager/YStartDetector.hxx" @@ -76,7 +77,7 @@ namespace { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Console::Console(OSystem& osystem, unique_ptr& cart, - const Properties& props) + const Properties& props, AudioSettings& audioSettings) : myOSystem(osystem), myEvent(osystem.eventHandler().event()), myProperties(props), @@ -85,7 +86,8 @@ Console::Console(OSystem& osystem, unique_ptr& cart, myCurrentFormat(0), // Unknown format @ start, myAutodetectedYstart(0), myUserPaletteDefined(false), - myConsoleTiming(ConsoleTiming::ntsc) + myConsoleTiming(ConsoleTiming::ntsc), + myAudioSettings(audioSettings) { // Load user-defined palette for this ROM loadUserPalette(); @@ -553,8 +555,15 @@ void Console::initializeAudio() { myOSystem.sound().close(); - myEmulationTiming.updatePlaybackPeriod(myOSystem.sound().getSampleRate()); - myEmulationTiming.updatePlaybackPeriod(myOSystem.sound().getFragmentSize()); + myEmulationTiming + .updatePlaybackRate(myOSystem.sound().getSampleRate()) + .updatePlaybackPeriod(myOSystem.sound().getFragmentSize()) + .updateAudioQueueExtraFragments(myAudioSettings.bufferSize()) + .updateAudioQueueHeadroom(myAudioSettings.headroom()); + + (cout << "sample rate: " << myOSystem.sound().getSampleRate() << std::endl).flush(); + (cout << "fragment size: " << myOSystem.sound().getFragmentSize() << std::endl).flush(); + (cout << "prebuffer fragment count: " << myEmulationTiming.prebufferFragmentCount() << std::endl).flush(); createAudioQueue(); myTIA->setAudioQueue(myAudioQueue); diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index b10740467..0acb6b7e7 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -28,6 +28,7 @@ class Cartridge; class CompuMate; class Debugger; class AudioQueue; +class AudioSettings; #include "bspf.hxx" #include "Control.hxx" @@ -80,7 +81,7 @@ class Console : public Serializable @param props The properties for the cartridge */ Console(OSystem& osystem, unique_ptr& cart, - const Properties& props); + const Properties& props, AudioSettings& audioSettings); /** Destructor @@ -423,9 +424,12 @@ class Console : public Serializable ConsoleTiming myConsoleTiming; // Emulation timing provider. This ties together the timing of the core emulation loop - // and the audio synthesis parameters + // and the parameters that govern audio synthesis EmulationTiming myEmulationTiming; + // The audio settings + AudioSettings& myAudioSettings; + // Table of RGB values for NTSC, PAL and SECAM static uInt32 ourNTSCPalette[256]; static uInt32 ourPALPalette[256]; diff --git a/src/emucore/EmulationTiming.cxx b/src/emucore/EmulationTiming.cxx index 7f850d148..655be9cb7 100644 --- a/src/emucore/EmulationTiming.cxx +++ b/src/emucore/EmulationTiming.cxx @@ -19,8 +19,6 @@ namespace { constexpr uInt32 AUDIO_HALF_FRAMES_PER_FRAGMENT = 1; - constexpr uInt32 QUEUE_CAPACITY_EXTRA_FRAGMENTS = 1; - constexpr uInt32 PREBUFFER_EXTRA_FRAGMENT_COUNT = 2; uInt32 discreteDivCeil(uInt32 n, uInt32 d) { @@ -32,25 +30,44 @@ namespace { EmulationTiming::EmulationTiming(FrameLayout frameLayout) : myFrameLayout(frameLayout), myPlaybackRate(44100), - myPlaybackPeriod(512) + myPlaybackPeriod(512), + myAudioQueueExtraFragments(1), + myAudioQueueHeadroom(2) {} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EmulationTiming::updateFrameLayout(FrameLayout frameLayout) +EmulationTiming& EmulationTiming::updateFrameLayout(FrameLayout frameLayout) { myFrameLayout = frameLayout; + return *this; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EmulationTiming::updatePlaybackRate(uInt32 playbackRate) +EmulationTiming& EmulationTiming::updatePlaybackRate(uInt32 playbackRate) { myPlaybackRate = playbackRate; + return *this; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod) +EmulationTiming& EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod) { myPlaybackPeriod = playbackPeriod; + return *this; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +EmulationTiming& EmulationTiming::updateAudioQueueExtraFragments(uInt32 audioQueueExtraFragments) +{ + myAudioQueueExtraFragments = audioQueueExtraFragments; + return *this; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +EmulationTiming& EmulationTiming::updateAudioQueueHeadroom(uInt32 audioQueueHeadroom) +{ + myAudioQueueHeadroom = audioQueueHeadroom; + return *this; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -124,11 +141,11 @@ uInt32 EmulationTiming::audioQueueCapacity() const { uInt32 minCapacity = discreteDivCeil(maxCyclesPerTimeslice() * audioSampleRate(), audioFragmentSize() * cyclesPerSecond()); - return std::max(prebufferFragmentCount(), minCapacity) + QUEUE_CAPACITY_EXTRA_FRAGMENTS; + return std::max(prebufferFragmentCount(), minCapacity) + myAudioQueueExtraFragments; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::prebufferFragmentCount() const { - return discreteDivCeil(myPlaybackPeriod * audioSampleRate(), audioFragmentSize() * myPlaybackRate) + PREBUFFER_EXTRA_FRAGMENT_COUNT; + return discreteDivCeil(myPlaybackPeriod * audioSampleRate(), audioFragmentSize() * myPlaybackRate) + myAudioQueueHeadroom; } diff --git a/src/emucore/EmulationTiming.hxx b/src/emucore/EmulationTiming.hxx index 163df9088..8c6f68239 100644 --- a/src/emucore/EmulationTiming.hxx +++ b/src/emucore/EmulationTiming.hxx @@ -26,11 +26,15 @@ class EmulationTiming { EmulationTiming(FrameLayout frameLayout = FrameLayout::ntsc); - void updateFrameLayout(FrameLayout frameLayout); + EmulationTiming& updateFrameLayout(FrameLayout frameLayout); - void updatePlaybackRate(uInt32 playbackRate); + EmulationTiming& updatePlaybackRate(uInt32 playbackRate); - void updatePlaybackPeriod(uInt32 period); + EmulationTiming& updatePlaybackPeriod(uInt32 period); + + EmulationTiming& updateAudioQueueExtraFragments(uInt32 audioQueueExtraFragments); + + EmulationTiming& updateAudioQueueHeadroom(uInt32 audioQueueHeadroom); uInt32 maxCyclesPerTimeslice() const; @@ -60,6 +64,9 @@ class EmulationTiming { uInt32 myPlaybackPeriod; + uInt32 myAudioQueueExtraFragments; + uInt32 myAudioQueueHeadroom; + private: EmulationTiming(const EmulationTiming&) = delete; diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 87bb00905..5b05259a4 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -90,6 +90,7 @@ OSystem::OSystem() myBuildInfo = info.str(); mySettings = MediaFactory::createSettings(*this); + myAudioSettings = AudioSettings(mySettings.get()); myRandom = make_unique(*this); } @@ -321,9 +322,9 @@ FBInitStatus OSystem::createFrameBuffer() void OSystem::createSound() { if(!mySound) - mySound = MediaFactory::createAudio(*this); + mySound = MediaFactory::createAudio(*this, myAudioSettings); #ifndef SOUND_SUPPORT - mySettings->setValue("sound", false); + myAudioSettings.setEnabled(false); #endif } @@ -549,7 +550,7 @@ unique_ptr OSystem::openConsole(const FilesystemNode& romfile, string& // Finally, create the cart with the correct properties if(cart) - console = make_unique(*this, cart, props); + console = make_unique(*this, cart, props, myAudioSettings); } return console; diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 85d23b9d6..981f3c729 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -44,6 +44,7 @@ class EmulationWorker; #include "FrameBufferConstants.hxx" #include "EventHandlerConstants.hxx" #include "bspf.hxx" +#include "AudioSettings.hxx" /** This class provides an interface for accessing operating system specific @@ -481,6 +482,9 @@ class OSystem // Indicates whether to stop the main loop bool myQuitLoop; + // Audio settings + AudioSettings myAudioSettings; + private: string myBaseDir; string myStateDir; diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 81433eef2..8c48494b6 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -21,6 +21,7 @@ #include "OSystem.hxx" #include "Version.hxx" +#include "AudioSettings.hxx" #ifdef DEBUGGER_SUPPORT #include "DebuggerDialog.hxx" @@ -69,12 +70,14 @@ Settings::Settings(OSystem& osystem) setInternal("tv.bleed", "0.0"); // Sound options - setInternal("sound", "true"); - setInternal("aud.mode", "balanced"); - setInternal("fragsize", "512"); - setInternal("freq", "44100"); - setInternal("volume", "100"); - setInternal("resampling.quality", "2"); + setInternal(AudioSettings::SETTING_ENABLED, AudioSettings::DEFAULT_ENABLED); + setInternal(AudioSettings::SETTING_PRESET, static_cast(AudioSettings::DEFAULT_PRESET)); + setInternal(AudioSettings::SETTING_SAMPLE_RATE, AudioSettings::DEFAULT_SAMPLE_RATE); + setInternal(AudioSettings::SETTING_FRAGMENT_SIZE, AudioSettings::DEFAULT_FRAGMENT_SIZE); + setInternal(AudioSettings::SETTING_BUFFER_SIZE, AudioSettings::DEFAULT_BUFFER_SIZE); + setInternal(AudioSettings::SETTING_HEADROOM, AudioSettings::DEFAULT_HEADROOM); + setInternal(AudioSettings::SETTING_RESAMPLING_QUALITY, static_cast(AudioSettings::DEFAULT_RESAMPLING_QUALITY)); + setInternal(AudioSettings::SETTING_VOLUME, AudioSettings::DEFAULT_VOLUME); // Input event options setInternal("keymap", ""); @@ -361,13 +364,7 @@ void Settings::validate() if(i < 0 || i > 6) setInternal("plr.tm.horizon", 5);*/ #ifdef SOUND_SUPPORT - i = getInt("volume"); - if(i < 0 || i > 100) setInternal("volume", "100"); - i = getInt("freq"); - if(!(i == 44100 || i == 48000 || i == 96000)) - setInternal("freq", "44100"); - i = getInt("resampling.quality"); - if (i < 1 || i > 3) setInternal("resampling.quality", 2); + AudioSettings::normalize(*this); #endif i = getInt("joydeadzone"); @@ -447,11 +444,14 @@ void Settings::usage() const << " -uimessages <1|0> Show onscreen UI messages for different events\n" << endl #ifdef SOUND_SUPPORT - << " -sound <1|0> Enable sound generation\n" - << " -fragsize The size of sound fragments (must be a power of two)\n" - << " -freq Set sound sample output frequency (44100|48000|96000)\n" - << " -resampling.quality Resampling quality (1 -3), default: 2\n" - << " -volume Set the volume (0 - 100)\n" + << " -audio.enabled <1|0> Enable audio\n" + << " -audio.preset <1-5> Audio preset (or 1 for custom)\n" + << " -audio.sample_rate Output sample rate (44100|48000|96000)\n" + << " -audio.fragment_size Fragment size (128|256|512|1024|2048|4096)\n" + << " -audio.buffer_size Max. number of additional half-frames to buffer (0 -- 20)\n" + << " -audio.headroom Additional half-frames to prebuffer (0 -- 20)\n" + << " -audio.resampling_quality <1-3> Resampling quality\n" + << " -audio.volume Vokume (0 -- 100)\n" << endl #endif << " -tia.zoom Use the specified zoom level (windowed mode) for TIA image\n" diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index 763926460..44e7b5db0 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -89,7 +89,7 @@ class Sound @param percent The new volume percentage level for the sound device */ - virtual void setVolume(Int32 percent) = 0; + virtual void setVolume(uInt32 percent) = 0; /** Adjusts the volume of the sound device based on the given direction. diff --git a/src/windows/SettingsWINDOWS.cxx b/src/windows/SettingsWINDOWS.cxx index 7820b0937..432509b3f 100644 --- a/src/windows/SettingsWINDOWS.cxx +++ b/src/windows/SettingsWINDOWS.cxx @@ -20,6 +20,4 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SettingsWINDOWS::SettingsWINDOWS(OSystem& osystem) : Settings(osystem) -{ - setInternal("fragsize", "1024"); -} +{} From 176507cb4694c6f8a977d2eee69909234ddcd583 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sat, 23 Jun 2018 01:18:35 +0200 Subject: [PATCH 72/77] Fix memory corruption on console reset. --- src/emucore/Console.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 0739588c7..532f9f9e5 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -206,6 +206,10 @@ Console::~Console() // Some smart controllers need to be informed that the console is going away myLeftControl->close(); myRightControl->close(); + + // Close audio to prevent invalid access to myConsoleTiming from the audio + // callback + myOSystem.sound().close(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 47bbdb679eca04298b99c4df9847a27aa238620f Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 24 Jun 2018 22:48:28 +0200 Subject: [PATCH 73/77] Remove unnecessary code, don't spam if audio is disabled. --- src/common/AudioQueue.cxx | 17 ++++++++--------- src/common/AudioQueue.hxx | 18 +++++++++--------- src/common/SoundSDL2.cxx | 4 +++- src/emucore/Console.cxx | 3 +-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx index 845c63032..251796a9c 100644 --- a/src/common/AudioQueue.cxx +++ b/src/common/AudioQueue.cxx @@ -21,10 +21,9 @@ using std::mutex; using std::lock_guard; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -AudioQueue::AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate) +AudioQueue::AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo) : myFragmentSize(fragmentSize), myIsStereo(isStereo), - mySampleRate(sampleRate), myFragmentQueue(capacity), myAllFragments(capacity + 2), mySize(0), @@ -70,12 +69,6 @@ uInt32 AudioQueue::fragmentSize() const return myFragmentSize; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt16 AudioQueue::sampleRate() const -{ - return mySampleRate; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Int16* AudioQueue::enqueue(Int16* fragment) { @@ -101,7 +94,7 @@ Int16* AudioQueue::enqueue(Int16* fragment) if (mySize < capacity) mySize++; else { myNextFragment = (myNextFragment + 1) % capacity; - (cerr << "audio buffer overflow\n").flush(); + if (!myIgnoreOverflows) (cerr << "audio buffer overflow\n").flush(); } return newFragment; @@ -141,3 +134,9 @@ void AudioQueue::closeSink(Int16* fragment) if (!myFirstFragmentForDequeue) myFirstFragmentForDequeue = fragment; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioQueue::ignoreOverflows(bool shouldIgnoreOverflows) +{ + myIgnoreOverflows = shouldIgnoreOverflows; +} diff --git a/src/common/AudioQueue.hxx b/src/common/AudioQueue.hxx index e0d7c4868..af714a925 100644 --- a/src/common/AudioQueue.hxx +++ b/src/common/AudioQueue.hxx @@ -45,7 +45,7 @@ class AudioQueue @param isStereo Whether samples are stereo or mono. @param sampleRate The sample rate. This is not used, but can be queried. */ - AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate); + AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo); /** Capacity getter. @@ -67,11 +67,6 @@ class AudioQueue */ uInt32 fragmentSize() const; - /** - Sample rate getter. - */ - uInt16 sampleRate() const; - /** Enqueue a new fragment and get a new fragmen to fill. @@ -96,6 +91,11 @@ class AudioQueue */ void closeSink(Int16* fragment); + /** + Should we ignore overflows? + */ + void ignoreOverflows(bool shouldIgnoreOverflows); + private: // The size of an individual fragment (in stereo / mono samples) @@ -104,9 +104,6 @@ class AudioQueue // Are we using stereo samples? bool myIsStereo; - // The sample rate - uInt16 mySampleRate; - // The fragment queue vector myFragmentQueue; @@ -130,6 +127,9 @@ class AudioQueue // The first (empty) dequeue call replaces the returned fragment with this fragment. Int16* myFirstFragmentForDequeue; + // Log overflows? + bool myIgnoreOverflows; + private: AudioQueue() = delete; diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index fd27d432e..298d31a12 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -98,6 +98,7 @@ SoundSDL2::~SoundSDL2() void SoundSDL2::setEnabled(bool state) { myAudioSettings.setEnabled(state); + if (myAudioQueue) myAudioQueue->ignoreOverflows(!state); myOSystem.logMessage(state ? "SoundSDL2::setEnabled(true)" : "SoundSDL2::setEnabled(false)", 2); @@ -112,6 +113,7 @@ void SoundSDL2::open(shared_ptr audioQueue, myOSystem.logMessage("SoundSDL2::open started ...", 2); mute(true); + audioQueue->ignoreOverflows(!myAudioSettings.enabled()); if(!myAudioSettings.enabled()) { myOSystem.logMessage("Sound disabled\n", 1); @@ -251,7 +253,7 @@ void SoundSDL2::initResampler() }; Resampler::Format formatFrom = - Resampler::Format(myAudioQueue->sampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()); + Resampler::Format(myEmulationTiming->audioSampleRate(), myAudioQueue->fragmentSize(), myAudioQueue->isStereo()); Resampler::Format formatTo = Resampler::Format(myHardwareSpec.freq, myHardwareSpec.samples, myHardwareSpec.channels > 1); diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 532f9f9e5..3cbfeffa7 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -725,8 +725,7 @@ void Console::createAudioQueue() myAudioQueue = make_shared( myEmulationTiming.audioFragmentSize(), myEmulationTiming.audioQueueCapacity(), - myProperties.get(Cartridge_Sound) == "STEREO", - myEmulationTiming.audioSampleRate() + myProperties.get(Cartridge_Sound) == "STEREO" ); } From 5656051aae067cd942f5faeffb7b6c9dbc475ec7 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 25 Jun 2018 00:30:52 +0200 Subject: [PATCH 74/77] Tie in UI. --- src/common/AudioSettings.cxx | 37 ++++++++-- src/common/AudioSettings.hxx | 9 ++- src/emucore/OSystem.hxx | 5 ++ src/gui/AudioDialog.cxx | 129 +++++++++++++++++++++++------------ src/gui/AudioDialog.hxx | 5 ++ 5 files changed, 135 insertions(+), 50 deletions(-) diff --git a/src/common/AudioSettings.cxx b/src/common/AudioSettings.cxx index bdc2e2ead..94c740b26 100644 --- a/src/common/AudioSettings.cxx +++ b/src/common/AudioSettings.cxx @@ -41,9 +41,16 @@ namespace { } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioSettings::AudioSettings() + : mySettings(), + myIsPersistent(false) +{} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - AudioSettings::AudioSettings(Settings* settings) - : mySettings(settings) + : mySettings(settings), + myIsPersistent(true) { setPreset(normalizedPreset(mySettings->getInt(SETTING_PRESET))); } @@ -81,10 +88,10 @@ void AudioSettings::normalize(Settings& settings) } int settingBufferSize = settings.getInt(SETTING_BUFFER_SIZE); - if (settingBufferSize < 0 || settingBufferSize > 20) settings.setValue(SETTING_BUFFER_SIZE, DEFAULT_BUFFER_SIZE); + if (settingBufferSize < 0 || settingBufferSize > MAX_BUFFER_SIZE) settings.setValue(SETTING_BUFFER_SIZE, DEFAULT_BUFFER_SIZE); int settingHeadroom = settings.getInt(SETTING_HEADROOM); - if (settingHeadroom < 0 || settingHeadroom > 20) settings.setValue(SETTING_HEADROOM, DEFAULT_HEADROOM); + if (settingHeadroom < 0 || settingHeadroom > MAX_HEADROOM) settings.setValue(SETTING_HEADROOM, DEFAULT_HEADROOM); int settingResamplingQuality = settings.getInt(SETTING_RESAMPLING_QUALITY); ResamplingQuality resamplingQuality = normalizeResamplingQuality(settingResamplingQuality); @@ -198,12 +205,14 @@ void AudioSettings::setPreset(AudioSettings::Preset preset) throw runtime_error("invalid preset"); } - mySettings->setValue(SETTING_PRESET, static_cast(myPreset)); + if (myIsPersistent) mySettings->setValue(SETTING_PRESET, static_cast(myPreset)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setSampleRate(uInt32 sampleRate) { + if (!myIsPersistent) return; + mySettings->setValue(SETTING_SAMPLE_RATE, sampleRate); normalize(*mySettings); } @@ -211,6 +220,8 @@ void AudioSettings::setSampleRate(uInt32 sampleRate) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setFragmentSize(uInt32 fragmentSize) { + if (!myIsPersistent) return; + mySettings->setValue(SETTING_FRAGMENT_SIZE, fragmentSize); normalize(*mySettings); } @@ -218,6 +229,8 @@ void AudioSettings::setFragmentSize(uInt32 fragmentSize) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setBufferSize(uInt32 bufferSize) { + if (!myIsPersistent) return; + mySettings->setValue(SETTING_BUFFER_SIZE, bufferSize); normalize(*mySettings); } @@ -225,6 +238,8 @@ void AudioSettings::setBufferSize(uInt32 bufferSize) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setHeadroom(uInt32 headroom) { + if (!myIsPersistent) return; + mySettings->setValue(SETTING_HEADROOM, headroom); normalize(*mySettings); } @@ -232,6 +247,8 @@ void AudioSettings::setHeadroom(uInt32 headroom) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setResamplingQuality(AudioSettings::ResamplingQuality resamplingQuality) { + if (!myIsPersistent) return; + mySettings->setValue(SETTING_RESAMPLING_QUALITY, static_cast(resamplingQuality)); normalize(*mySettings); } @@ -239,6 +256,8 @@ void AudioSettings::setResamplingQuality(AudioSettings::ResamplingQuality resamp // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setVolume(uInt32 volume) { + if (!myIsPersistent) return; + mySettings->setValue(SETTING_VOLUME, volume); normalize(*mySettings); } @@ -246,9 +265,17 @@ void AudioSettings::setVolume(uInt32 volume) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::setEnabled(bool isEnabled) { + if (!myIsPersistent) return; + mySettings->setValue(SETTING_ENABLED, isEnabled); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioSettings::setPersistent(bool isPersistent) +{ + myIsPersistent = isPersistent; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool AudioSettings::customSettings() const { @@ -258,5 +285,7 @@ bool AudioSettings::customSettings() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioSettings::updatePresetFromSettings() { + if (!myIsPersistent) return; + setPreset(normalizedPreset(mySettings->getInt(SETTING_PRESET))); } diff --git a/src/common/AudioSettings.hxx b/src/common/AudioSettings.hxx index 3435a344d..61b66ffb2 100644 --- a/src/common/AudioSettings.hxx +++ b/src/common/AudioSettings.hxx @@ -58,9 +58,12 @@ class AudioSettings static constexpr uInt32 DEFAULT_VOLUME = 80; static constexpr bool DEFAULT_ENABLED = true; + static constexpr int MAX_BUFFER_SIZE = 10; + static constexpr int MAX_HEADROOM = 10; + public: - AudioSettings() = default; + AudioSettings(); AudioSettings(Settings* mySettings); @@ -100,6 +103,8 @@ class AudioSettings void setEnabled(bool isEnabled); + void setPersistent(bool isPersistent); + private: bool customSettings() const; @@ -117,6 +122,8 @@ class AudioSettings uInt32 myPresetBufferSize; uInt32 myPresetHeadroom; ResamplingQuality myPresetResamplingQuality; + + bool myIsPersistent; }; #endif // AUDIO_PARAMTERS_HXX diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 981f3c729..d52956fae 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -126,6 +126,11 @@ class OSystem Console& console() const { return *myConsole; } bool hasConsole() const; + /** + Get the audio settings ovject. + */ + AudioSettings& audioSettings() { return myAudioSettings; } + /** Get the serial port of the system. diff --git a/src/gui/AudioDialog.cxx b/src/gui/AudioDialog.cxx index cf5773bd3..0a3b22aba 100644 --- a/src/gui/AudioDialog.cxx +++ b/src/gui/AudioDialog.cxx @@ -29,6 +29,7 @@ #include "Settings.hxx" #include "Sound.hxx" #include "Widget.hxx" +#include "AudioSettings.hxx" #include "AudioDialog.hxx" @@ -50,7 +51,7 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, VariantList items; // Set real dimensions - _w = 35 * fontWidth + HBORDER * 2; + _w = 45 * fontWidth + HBORDER * 2; _h = 11 * (lineHeight + 4) + VBORDER + _th; xpos = HBORDER; ypos = VBORDER + _th; @@ -71,12 +72,13 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, ypos += lineHeight + 4; // - VarList::push_back(items, "Minimal Lag", "minlag"); - VarList::push_back(items, "Balanced", "balanced"); - VarList::push_back(items, "Max. Quality", "maxquality"); - VarList::push_back(items, "Custom", "Custom"); + VarList::push_back(items, "Low quality, medium lag", static_cast(AudioSettings::Preset::lowQualityMediumLag)); + VarList::push_back(items, "High quality, medium lag", static_cast(AudioSettings::Preset::highQualityMediumLag)); + VarList::push_back(items, "High quality, low lag", static_cast(AudioSettings::Preset::highQualityLowLag)); + VarList::push_back(items, "Ultra quality, minimal lag", static_cast(AudioSettings::Preset::veryHighQualityVeryLowLag)); + VarList::push_back(items, "Custom", static_cast(AudioSettings::Preset::custom)); myModePopup = new PopUpWidget(this, font, xpos, ypos, - font.getStringWidth("Max. Quality"), lineHeight, + font.getStringWidth("Ultry quality, minimal lag "), lineHeight, items, "Mode (*) ", 0, kModeChanged); wid.push_back(myModePopup); ypos += lineHeight + 4; @@ -84,35 +86,34 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, // Fragment size items.clear(); - VarList::push_back(items, "128 bytes", "128"); - VarList::push_back(items, "256 bytes", "256"); - VarList::push_back(items, "512 bytes", "512"); - VarList::push_back(items, "1 KB", "1024"); - VarList::push_back(items, "2 KB", "2048"); - VarList::push_back(items, "4 KB", "4096"); + VarList::push_back(items, "128 bytes", 128); + VarList::push_back(items, "256 bytes", 256); + VarList::push_back(items, "512 bytes", 512); + VarList::push_back(items, "1 KB", 1024); + VarList::push_back(items, "2 KB", 2048); + VarList::push_back(items, "4 KB", 4096); myFragsizePopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, - items, "Sample size (*) ", lwidth); + items, "Fragment size (*) ", lwidth); wid.push_back(myFragsizePopup); ypos += lineHeight + 4; // Output frequency items.clear(); - VarList::push_back(items, "44100 Hz", "44100"); - VarList::push_back(items, "48000 Hz", "48000"); - VarList::push_back(items, "96000 Hz", "96000"); + VarList::push_back(items, "44100 Hz", 44100); + VarList::push_back(items, "48000 Hz", 48000); + VarList::push_back(items, "96000 Hz", 96000); myFreqPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, - items, "Frequency (*) ", lwidth); + items, "Sample rate (*) ", lwidth); wid.push_back(myFreqPopup); ypos += lineHeight + 4; - // Resampling quality items.clear(); - VarList::push_back(items, "Low", "low"); - VarList::push_back(items, "Medium", "medium"); - VarList::push_back(items, "High", "high"); + VarList::push_back(items, "Low", static_cast(AudioSettings::ResamplingQuality::nearestNeightbour)); + VarList::push_back(items, "High", static_cast(AudioSettings::ResamplingQuality::lanczos_2)); + VarList::push_back(items, "Ultra", static_cast(AudioSettings::ResamplingQuality::lanczos_3)); myResamplingPopup = new PopUpWidget(this, font, xpos, ypos, pwidth, lineHeight, items, "Resampling quality ", lwidth); @@ -121,15 +122,15 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, // Param 1 myHeadroomSlider = new SliderWidget(this, font, xpos, ypos, - "Headroom "); - myHeadroomSlider->setMinValue(1); myHeadroomSlider->setMaxValue(10); + "Headroom ", 0, 0, 2 * fontWidth); + myHeadroomSlider->setMinValue(1); myHeadroomSlider->setMaxValue(AudioSettings::MAX_HEADROOM); wid.push_back(myHeadroomSlider); ypos += lineHeight + 4; // Param 2 myBufferSizeSlider = new SliderWidget(this, font, xpos, ypos, - "Buffer size "); - myBufferSizeSlider->setMinValue(1); myBufferSizeSlider->setMaxValue(10); + "Buffer size ", 0, 0, 2 * fontWidth); + myBufferSizeSlider->setMinValue(1); myBufferSizeSlider->setMaxValue(AudioSettings::MAX_BUFFER_SIZE); wid.push_back(myBufferSizeSlider); // Add message concerning usage @@ -148,41 +149,69 @@ AudioDialog::AudioDialog(OSystem& osystem, DialogContainer& parent, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioDialog::loadConfig() { + AudioSettings& audioSettings = instance().audioSettings(); + // Volume - myVolumeSlider->setValue(instance().settings().getInt("volume")); - - // Fragsize - myFragsizePopup->setSelected(instance().settings().getString("fragsize"), "512"); - - // Output frequency - myFreqPopup->setSelected(instance().settings().getString("freq"), "31400"); + myVolumeSlider->setValue(audioSettings.volume()); // Enable sound - bool b = instance().settings().getBool("sound"); - mySoundEnableCheckbox->setState(b); + mySoundEnableCheckbox->setState(audioSettings.enabled()); + + // Preset / mode + myModePopup->setSelected(static_cast(audioSettings.preset())); + + updatePresetSettings(instance().audioSettings()); // Make sure that mutually-exclusive items are not enabled at the same time - handleSoundEnableChange(b); + handleSoundEnableChange(audioSettings.enabled()); + handleModeChange(audioSettings.enabled()); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AudioDialog::updatePresetSettings(AudioSettings& audioSettings) +{ + // Fragsize + myFragsizePopup->setSelected(audioSettings.fragmentSize()); + + // Output frequency + myFreqPopup->setSelected(audioSettings.sampleRate()); + + // Headroom + myHeadroomSlider->setValue(audioSettings.headroom()); + + // Buffer size + myBufferSizeSlider->setValue(audioSettings.bufferSize()); + + // Resampling quality + myResamplingPopup->setSelected(static_cast(audioSettings.resamplingQuality())); +} + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioDialog::saveConfig() { - Settings& settings = instance().settings(); + AudioSettings audioSettings = instance().audioSettings(); // Volume - settings.setValue("volume", myVolumeSlider->getValue()); + audioSettings.setVolume(myVolumeSlider->getValue()); instance().sound().setVolume(myVolumeSlider->getValue()); - // Fragsize - settings.setValue("fragsize", myFragsizePopup->getSelectedTag().toString()); - - // Output frequency - settings.setValue("freq", myFreqPopup->getSelectedTag().toString()); - - // Enable/disable sound (requires a restart to take effect) + audioSettings.setEnabled(mySoundEnableCheckbox->getState()); instance().sound().setEnabled(mySoundEnableCheckbox->getState()); + AudioSettings::Preset preset = static_cast(myModePopup->getSelectedTag().toInt()); + audioSettings.setPreset(preset); + + if (preset == AudioSettings::Preset::custom) { + + // Fragsize + audioSettings.setFragmentSize(myFragsizePopup->getSelectedTag().toInt()); + audioSettings.setSampleRate(myFreqPopup->getSelectedTag().toInt()); + audioSettings.setHeadroom(myHeadroomSlider->getValue()); + audioSettings.setBufferSize(myBufferSizeSlider->getValue()); + audioSettings.setResamplingQuality(static_cast(myResamplingPopup->getSelectedTag().toInt())); + } + // Only force a re-initialization when necessary, since it can // be a time-consuming operation if(instance().hasConsole()) @@ -216,7 +245,17 @@ void AudioDialog::handleSoundEnableChange(bool active) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void AudioDialog::handleModeChange(bool active) { - bool userMode = active && "Custom" == myModePopup->getSelectedName(); + AudioSettings::Preset preset = static_cast(myModePopup->getSelectedTag().toInt()); + + AudioSettings audioSettings = instance().audioSettings(); + audioSettings.setPersistent(false); + audioSettings.setPreset(preset); + + (cout << "Preset: " << static_cast(preset) << std::endl).flush(); + + updatePresetSettings(audioSettings); + + bool userMode = active && preset == AudioSettings::Preset::custom; myFragsizePopup->setEnabled(userMode); myFreqPopup->setEnabled(userMode); diff --git a/src/gui/AudioDialog.hxx b/src/gui/AudioDialog.hxx index 91ff75df6..94e2666aa 100644 --- a/src/gui/AudioDialog.hxx +++ b/src/gui/AudioDialog.hxx @@ -26,6 +26,7 @@ class SliderWidget; class StaticTextWidget; class CheckboxWidget; class OSystem; +class AudioSettings; #include "bspf.hxx" @@ -59,6 +60,10 @@ class AudioDialog : public Dialog SliderWidget* myHeadroomSlider; SliderWidget* myBufferSizeSlider; + private: + + void updatePresetSettings(AudioSettings&); + private: // Following constructors and assignment operators not supported AudioDialog() = delete; From 674e5f01c076b91fc10fb5ff3252b3f8ce96d838 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Mon, 25 Jun 2018 00:42:40 +0200 Subject: [PATCH 75/77] Massage preset values. --- src/common/AudioSettings.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/AudioSettings.cxx b/src/common/AudioSettings.cxx index 94c740b26..f1b87a0cc 100644 --- a/src/common/AudioSettings.cxx +++ b/src/common/AudioSettings.cxx @@ -172,24 +172,24 @@ void AudioSettings::setPreset(AudioSettings::Preset preset) case Preset::lowQualityMediumLag: myPresetSampleRate = 44100; myPresetFragmentSize = 1024; - myPresetBufferSize = 4; - myPresetHeadroom = 3; + myPresetBufferSize = 6; + myPresetHeadroom = 5; myPresetResamplingQuality = ResamplingQuality::nearestNeightbour; break; case Preset::highQualityMediumLag: myPresetSampleRate = 44100; myPresetFragmentSize = 1024; - myPresetBufferSize = 4; - myPresetHeadroom = 3; + myPresetBufferSize = 6; + myPresetHeadroom = 5; myPresetResamplingQuality = ResamplingQuality::lanczos_2; break; case Preset::highQualityLowLag: myPresetSampleRate = 48000; myPresetFragmentSize = 512; - myPresetBufferSize = 2; - myPresetHeadroom = 1; + myPresetBufferSize = 3; + myPresetHeadroom = 2; myPresetResamplingQuality = ResamplingQuality::lanczos_2; break; From 2b23c8112613fd8277c50ed578fa0c9db5b89553 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Wed, 27 Jun 2018 23:12:50 +0200 Subject: [PATCH 76/77] Support variable emulation speed. --- .vscode/settings.json | 3 +- src/emucore/Console.cxx | 3 +- src/emucore/EmulationTiming.cxx | 115 +++++++++++++++++++++++--------- src/emucore/EmulationTiming.hxx | 21 +++++- src/emucore/Settings.cxx | 8 ++- 5 files changed, 112 insertions(+), 38 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d271851f7..7d0543e6c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -58,6 +58,7 @@ "cstring": "cpp", "iostream": "cpp", "cstdint": "cpp", - "ostream": "cpp" + "ostream": "cpp", + "__memory": "cpp" } } diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 3cbfeffa7..d961e3ef2 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -563,7 +563,8 @@ void Console::initializeAudio() .updatePlaybackRate(myOSystem.sound().getSampleRate()) .updatePlaybackPeriod(myOSystem.sound().getFragmentSize()) .updateAudioQueueExtraFragments(myAudioSettings.bufferSize()) - .updateAudioQueueHeadroom(myAudioSettings.headroom()); + .updateAudioQueueHeadroom(myAudioSettings.headroom()) + .updateSpeedFactor(myOSystem.settings().getFloat("speed")); (cout << "sample rate: " << myOSystem.sound().getSampleRate() << std::endl).flush(); (cout << "fragment size: " << myOSystem.sound().getFragmentSize() << std::endl).flush(); diff --git a/src/emucore/EmulationTiming.cxx b/src/emucore/EmulationTiming.cxx index 655be9cb7..80f29e084 100644 --- a/src/emucore/EmulationTiming.cxx +++ b/src/emucore/EmulationTiming.cxx @@ -15,6 +15,8 @@ // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ +#include + #include "EmulationTiming.hxx" namespace { @@ -32,13 +34,18 @@ EmulationTiming::EmulationTiming(FrameLayout frameLayout) : myPlaybackRate(44100), myPlaybackPeriod(512), myAudioQueueExtraFragments(1), - myAudioQueueHeadroom(2) -{} + myAudioQueueHeadroom(2), + mySpeedFactor(1) +{ + recalculate(); +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - EmulationTiming& EmulationTiming::updateFrameLayout(FrameLayout frameLayout) { myFrameLayout = frameLayout; + recalculate(); + return *this; } @@ -46,6 +53,8 @@ EmulationTiming& EmulationTiming::updateFrameLayout(FrameLayout frameLayout) EmulationTiming& EmulationTiming::updatePlaybackRate(uInt32 playbackRate) { myPlaybackRate = playbackRate; + recalculate(); + return *this; } @@ -53,6 +62,8 @@ EmulationTiming& EmulationTiming::updatePlaybackRate(uInt32 playbackRate) EmulationTiming& EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod) { myPlaybackPeriod = playbackPeriod; + recalculate(); + return *this; } @@ -60,6 +71,8 @@ EmulationTiming& EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod) EmulationTiming& EmulationTiming::updateAudioQueueExtraFragments(uInt32 audioQueueExtraFragments) { myAudioQueueExtraFragments = audioQueueExtraFragments; + recalculate(); + return *this; } @@ -67,85 +80,123 @@ EmulationTiming& EmulationTiming::updateAudioQueueExtraFragments(uInt32 audioQue EmulationTiming& EmulationTiming::updateAudioQueueHeadroom(uInt32 audioQueueHeadroom) { myAudioQueueHeadroom = audioQueueHeadroom; + recalculate(); + + return *this; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +EmulationTiming& EmulationTiming::updateSpeedFactor(float speedFactor) +{ + mySpeedFactor = speedFactor; + recalculate(); + return *this; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::maxCyclesPerTimeslice() const { - return 2 * cyclesPerFrame(); + return myMaxCyclesPerTimeslice; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::minCyclesPerTimeslice() const { - return cyclesPerFrame() / 2; + return myMinCyclesPerTimeslice; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::linesPerFrame() const { - switch (myFrameLayout) { - case FrameLayout::ntsc: - return 262; - - case FrameLayout::pal: - return 312; - - default: - throw runtime_error("invalid frame layout"); - } + return myLinesPerFrame; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::cyclesPerFrame() const { - return 76 * linesPerFrame(); + return myCyclesPerFrame; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::framesPerSecond() const { - switch (myFrameLayout) { - case FrameLayout::ntsc: - return 60; - - case FrameLayout::pal: - return 50; - - default: - throw runtime_error("invalid frame layout"); - } + return myFramesPerSecond; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::cyclesPerSecond() const { - return cyclesPerFrame() * framesPerSecond(); + return myCyclesPerSecond; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::audioFragmentSize() const { - return AUDIO_HALF_FRAMES_PER_FRAGMENT * linesPerFrame(); + return myAudioFragmentSize; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::audioSampleRate() const { - return 2 * linesPerFrame() * framesPerSecond(); + return myAudioSampleRate; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::audioQueueCapacity() const { - uInt32 minCapacity = discreteDivCeil(maxCyclesPerTimeslice() * audioSampleRate(), audioFragmentSize() * cyclesPerSecond()); - - return std::max(prebufferFragmentCount(), minCapacity) + myAudioQueueExtraFragments; + return myAudioQueueCapacity; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - uInt32 EmulationTiming::prebufferFragmentCount() const { - return discreteDivCeil(myPlaybackPeriod * audioSampleRate(), audioFragmentSize() * myPlaybackRate) + myAudioQueueHeadroom; + return myPrebufferFragmentCount; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void EmulationTiming::recalculate() +{ + switch (myFrameLayout) { + case FrameLayout::ntsc: + myLinesPerFrame = 262; + break; + + case FrameLayout::pal: + myLinesPerFrame = 312; + break; + + default: + throw runtime_error("invalid frame layout"); + } + + switch (myFrameLayout) { + case FrameLayout::ntsc: + myFramesPerSecond = round(mySpeedFactor * 60); + break; + + case FrameLayout::pal: + myFramesPerSecond = round(mySpeedFactor * 50); + break; + + default: + throw runtime_error("invalid frame layout"); + } + + myCyclesPerFrame = 76 * myLinesPerFrame; + myMaxCyclesPerTimeslice = round(mySpeedFactor * myCyclesPerFrame * 2); + myMinCyclesPerTimeslice = round(mySpeedFactor * myCyclesPerFrame / 2); + myCyclesPerSecond = myCyclesPerFrame * myFramesPerSecond; + myAudioFragmentSize = round(mySpeedFactor * AUDIO_HALF_FRAMES_PER_FRAGMENT * myLinesPerFrame); + myAudioSampleRate = 2 * myLinesPerFrame * myFramesPerSecond; + + myPrebufferFragmentCount = discreteDivCeil( + myPlaybackPeriod * myAudioSampleRate, + myAudioFragmentSize * myPlaybackRate + ) + myAudioQueueHeadroom; + + myAudioQueueCapacity = std::max( + myPrebufferFragmentCount, + discreteDivCeil(myMaxCyclesPerTimeslice * myAudioSampleRate, myAudioFragmentSize * myCyclesPerSecond) + ) + myAudioQueueExtraFragments; } diff --git a/src/emucore/EmulationTiming.hxx b/src/emucore/EmulationTiming.hxx index 8c6f68239..578844e4e 100644 --- a/src/emucore/EmulationTiming.hxx +++ b/src/emucore/EmulationTiming.hxx @@ -36,6 +36,8 @@ class EmulationTiming { EmulationTiming& updateAudioQueueHeadroom(uInt32 audioQueueHeadroom); + EmulationTiming& updateSpeedFactor(float speedFactor); + uInt32 maxCyclesPerTimeslice() const; uInt32 minCyclesPerTimeslice() const; @@ -56,17 +58,32 @@ class EmulationTiming { uInt32 prebufferFragmentCount() const; + private: + + void recalculate(); + private: FrameLayout myFrameLayout; uInt32 myPlaybackRate; - uInt32 myPlaybackPeriod; - uInt32 myAudioQueueExtraFragments; uInt32 myAudioQueueHeadroom; + uInt32 myMaxCyclesPerTimeslice; + uInt32 myMinCyclesPerTimeslice; + uInt32 myLinesPerFrame; + uInt32 myCyclesPerFrame; + uInt32 myFramesPerSecond; + uInt32 myCyclesPerSecond; + uInt32 myAudioFragmentSize; + uInt32 myAudioSampleRate; + uInt32 myAudioQueueCapacity; + uInt32 myPrebufferFragmentCount; + + float mySpeedFactor; + private: EmulationTiming(const EmulationTiming&) = delete; diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 8c48494b6..04676a9d2 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -35,7 +35,7 @@ Settings::Settings(OSystem& osystem) { // Video-related options setInternal("video", ""); - setInternal("framerate", "0"); + setInternal("speed", "1.0"); setInternal("vsync", "true"); setInternal("fullscreen", "false"); setInternal("center", "false"); @@ -303,6 +303,10 @@ void Settings::validate() { string s; int i; + float f; + + f = getFloat("speed"); + if (f <= 0) setInternal("speed", "1.0"); s = getString("timing"); if(s != "sleep" && s != "busy") setInternal("timing", "sleep"); @@ -439,7 +443,7 @@ void Settings::usage() const << " -palette \n" - << " -framerate Display the given number of frames per second (0 to auto-calculate)\n" + << " -speed Run emulation at the given speed\n" << " -timing Use the given type of wait between frames\n" << " -uimessages <1|0> Show onscreen UI messages for different events\n" << endl From 1c93fce6fc7da6e0faf1a241a1d8d042f3c40888 Mon Sep 17 00:00:00 2001 From: Christian Speckner Date: Sun, 1 Jul 2018 12:54:51 +0200 Subject: [PATCH 77/77] Speed UI. --- src/gui/VideoDialog.cxx | 84 +++++++++++++++++++++++++++++------------ src/gui/VideoDialog.hxx | 4 +- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/gui/VideoDialog.cxx b/src/gui/VideoDialog.cxx index d110cb028..9de92335a 100644 --- a/src/gui/VideoDialog.cxx +++ b/src/gui/VideoDialog.cxx @@ -15,6 +15,8 @@ // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ +#include + #include "bspf.hxx" #include "Control.hxx" #include "Dialog.hxx" @@ -32,6 +34,45 @@ #include "TIASurface.hxx" #include "VideoDialog.hxx" +namespace { + // Emulation speed is a positive float that multiplies the framerate. However, the UI controls + // adjust speed in terms of a speedup factor (1/10, 1/9 .. 1/2, 1, 2, 3, .., 10). The following + // mapping and formatting functions implement this conversion. The speedup factor is represented + // by an integer value between -900 and 900 (0 means no speedup). + + constexpr int MAX_SPEED = 900; + constexpr int MIN_SPEED = -900; + constexpr int SPEED_STEP = 10; + + int mapSpeed(float speed) + { + speed = abs(speed); + + return BSPF::clamp( + static_cast(round(100 * (speed >= 1 ? speed - 1 : -1 / speed + 1))), + MIN_SPEED, MAX_SPEED + ); + } + + float unmapSpeed(int speed) + { + float f_speed = static_cast(speed) / 100; + + return speed < 0 ? -1 / (f_speed - 1) : 1 + f_speed; + } + + string formatSpeed(int speed) { + stringstream ss; + + ss + << (speed >= 0 ? "x " : "/ ") + << std::setw(4) << std::fixed << std::setprecision(2) + << (1 + static_cast(speed < 0 ? -speed : speed) / 100); + + return ss.str(); + } +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent, const GUI::Font& font, int max_w, int max_h) @@ -121,13 +162,13 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent, wid.push_back(myPAspectRatio); ypos += lineHeight + VGAP; - // Framerate - myFrameRate = + // Speed + mySpeed = new SliderWidget(myTab, font, xpos, ypos-1, swidth, lineHeight, - "Frame rate ", lwidth, kFrameRateChanged, fontWidth * 6, "fps"); - myFrameRate->setMinValue(0); myFrameRate->setMaxValue(900); - myFrameRate->setStepValue(10); - wid.push_back(myFrameRate); + "Speed ", lwidth, kSpeedupChanged, fontWidth * 8, ""); + mySpeed->setMinValue(MIN_SPEED); mySpeed->setMaxValue(MAX_SPEED); + mySpeed->setStepValue(SPEED_STEP); + wid.push_back(mySpeed); ypos += lineHeight + VGAP; // Use sync to vblank @@ -141,7 +182,7 @@ VideoDialog::VideoDialog(OSystem& osystem, DialogContainer& parent, "(*) Requires application restart"); // Move over to the next column - xpos += myFrameRate->getWidth() + 16; + xpos += mySpeed->getWidth() + 16; ypos = VBORDER; // Fullscreen @@ -327,12 +368,10 @@ void VideoDialog::loadConfig() myNAspectRatio->setValue(instance().settings().getInt("tia.aspectn")); myPAspectRatio->setValue(instance().settings().getInt("tia.aspectp")); - // Framerate (0 or -1 means automatic framerate calculation) - int rate = instance().settings().getInt("framerate"); - myFrameRate->setValue(rate < 0 ? 0 : rate); - myFrameRate->setValueLabel(rate <= 0 ? "Auto" : - instance().settings().getString("framerate")); - myFrameRate->setValueUnit(rate <= 0 ? "" : "fps"); + // Emulation speed + int speed = mapSpeed(instance().settings().getFloat("speed")); + mySpeed->setValue(speed); + mySpeed->setValueLabel(formatSpeed(speed)); // Fullscreen myFullscreen->setState(instance().settings().getBool("fullscreen")); @@ -404,13 +443,10 @@ void VideoDialog::saveConfig() instance().settings().setValue("tia.aspectn", myNAspectRatio->getValueLabel()); instance().settings().setValue("tia.aspectp", myPAspectRatio->getValueLabel()); - // Framerate - int f = myFrameRate->getValue(); - instance().settings().setValue("framerate", f); - if(instance().hasConsole()) - { - // instance().console().setFramerate(float(f)); - } + // Speed + int speedup = mySpeed->getValue(); + instance().settings().setValue("speed", unmapSpeed(speedup)); + if (instance().hasConsole()) instance().console().initializeAudio(); // Fullscreen instance().settings().setValue("fullscreen", myFullscreen->getState()); @@ -484,7 +520,7 @@ void VideoDialog::setDefaults() myTIAInterpolate->setState(false); myNAspectRatio->setValue(91); myPAspectRatio->setValue(109); - myFrameRate->setValue(0); + mySpeed->setValue(0); myFullscreen->setState(false); //myFullScreenMode->setSelectedIndex(0); @@ -583,10 +619,8 @@ void VideoDialog::handleCommand(CommandSender* sender, int cmd, setDefaults(); break; - case kFrameRateChanged: - if(myFrameRate->getValue() == 0) - myFrameRate->setValueLabel("Auto"); - myFrameRate->setValueUnit(myFrameRate->getValue() == 0 ? "" : "fps"); + case kSpeedupChanged: + mySpeed->setValueLabel(formatSpeed(mySpeed->getValue())); break; case kTVModeChanged: diff --git a/src/gui/VideoDialog.hxx b/src/gui/VideoDialog.hxx index bec208387..8e50b3895 100644 --- a/src/gui/VideoDialog.hxx +++ b/src/gui/VideoDialog.hxx @@ -58,7 +58,7 @@ class VideoDialog : public Dialog CheckboxWidget* myTIAInterpolate; SliderWidget* myNAspectRatio; SliderWidget* myPAspectRatio; - SliderWidget* myFrameRate; + SliderWidget* mySpeed; CheckboxWidget* myFullscreen; //PopUpWidget* myFullScreenMode; @@ -99,7 +99,7 @@ class VideoDialog : public Dialog ButtonWidget* myCloneCustom; enum { - kFrameRateChanged = 'VDfr', + kSpeedupChanged = 'VDSp', kTVModeChanged = 'VDtv',