From 129ec5886a9b958a6d4e1d599e2eb5d8a946fdcb Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Sat, 24 Sep 2022 21:33:39 -0230 Subject: [PATCH] First pass at separating Sound mute/pause functionality. This will need lots of testing, since it touches many parts of the code. --- src/common/SoundNull.hxx | 36 +++++++---- src/common/SoundSDL2.cxx | 102 +++++++++++++----------------- src/common/SoundSDL2.hxx | 47 ++++++++------ src/emucore/Console.cxx | 4 +- src/emucore/EventHandler.cxx | 8 +-- src/emucore/FrameBuffer.cxx | 12 ++-- src/emucore/Sound.hxx | 35 ++++++---- src/gui/VideoAudioDialog.cxx | 7 +- src/os/libretro/SoundLIBRETRO.hxx | 5 +- 9 files changed, 135 insertions(+), 121 deletions(-) diff --git a/src/common/SoundNull.hxx b/src/common/SoundNull.hxx index 191f35d90..5007bf49c 100644 --- a/src/common/SoundNull.hxx +++ b/src/common/SoundNull.hxx @@ -66,29 +66,39 @@ class SoundNull : public Sound void close() override { } /** - Set the mute state of the sound object. While muted no sound is played. + Sets the sound mute state; sound processing continues. When turned + off, sound volume is 0; when turned on, sound volume returns to + previously set level. @param state Mutes sound if true, unmute if false - - @return The previous (old) mute state */ - bool mute(bool state) override { return true; } + void mute(bool state) override { } /** - Toggles the sound mute state. While muted no sound is played. - - @return The previous (old) mute state + Toggles the sound mute state; sound processing continues. + Switches between mute(true) and mute(false). */ - bool toggleMute() override { return true; } + void toggleMute() override { } + + /** + Set the pause state of the sound object. While paused, sound is + neither played nor processed (ie, the sound subsystem is temporarily + disabled). + + @param state Pause sound if true, unpause if false + + @return The previous (old) pause state + */ + bool pause(bool state) override { return false; } /** Sets the volume of the sound device to the specified level. The - volume is given as a percentage from 0 to 100. Values outside - this range indicate that the volume shouldn't be changed at all. + volume is given as a range from 0 to 100 (0 indicates mute). Values + outside this range indicate that the volume shouldn't be changed at all. - @param percent The new volume percentage level for the sound device + @param volume The new volume level for the sound device */ - void setVolume(uInt32 percent) override { } + void setVolume(uInt32 volume) override { } /** Adjusts the volume of the sound device based on the given direction. @@ -102,7 +112,7 @@ class SoundNull : public Sound @param device The number of the device to select (0 = default). */ - void setDevice(uInt32 device) override { }; + void setDevice(uInt32 device) override { } /** This method is called to provide information about the sound device. diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index e9cede687..e66799cc4 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -63,10 +63,6 @@ SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings) if(!openDevice()) return; - SoundSDL2::mute(true); - myMuteState = !audioSettings.enabled(); - myWavVolumeFactor = myMuteState ? 0 : myVolumeFactor; - Logger::debug("SoundSDL2::SoundSDL2 initialized"); } @@ -127,7 +123,8 @@ bool SoundSDL2::openDevice() if(myIsInitializedFlag) SDL_CloseAudioDevice(myDevice); - myDeviceId = BSPF::clamp(myAudioSettings.device(), 0U, static_cast(myDevices.size() - 1)); + myDeviceId = BSPF::clamp(myAudioSettings.device(), 0U, + static_cast(myDevices.size() - 1)); const char* device = myDeviceId ? myDevices.at(myDeviceId).first.c_str() : nullptr; myDevice = SDL_OpenAudioDevice(device, 0, &desired, &myHardwareSpec, @@ -149,16 +146,9 @@ bool SoundSDL2::openDevice() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::setEnabled(bool enable) { - myAudioSettings.setEnabled(enable); - if(myAudioQueue) - myAudioQueue->ignoreOverflows(!enable); - - // Set new mute state and resulting WAV data volume - myMuteState = !enable; - myWavVolumeFactor = myMuteState ? 0 : myVolumeFactor; - - Logger::debug(enable ? "SoundSDL2::setEnabled(true)" : - "SoundSDL2::setEnabled(false)"); +cerr << "setEnabled: " << enable << endl; + mute(!enable); + pause(!enable); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -178,8 +168,8 @@ void SoundSDL2::open(shared_ptr audioQueue, myWavSpeed = 262 * 60 * 2. / myEmulationTiming->audioSampleRate(); Logger::debug("SoundSDL2::open started ..."); - mute(true); + myAudioSettings.setEnabled(true); audioQueue->ignoreOverflows(!myAudioSettings.enabled()); if(!myAudioSettings.enabled()) { @@ -202,7 +192,7 @@ void SoundSDL2::open(shared_ptr audioQueue, Logger::info(myAboutString); // And start the SDL sound subsystem ... - mute(myMuteState); + pause(false); Logger::debug("SoundSDL2::open finished"); } @@ -213,9 +203,6 @@ void SoundSDL2::close() if(!myIsInitializedFlag) return; - // Mute and remember current mute state for 'open()' - myMuteState = mute(true); - if(myAudioQueue) myAudioQueue->closeSink(myCurrentFragment); myAudioQueue.reset(); @@ -223,8 +210,36 @@ void SoundSDL2::close() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool SoundSDL2::mute(bool state) +void SoundSDL2::mute(bool state) { + myAudioSettings.setEnabled(!state); + if(state) + { + SDL_LockAudioDevice(myDevice); + myVolumeFactor = 0; + SDL_UnlockAudioDevice(myDevice); + } + else + setVolume(myAudioSettings.volume()); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL2::toggleMute() +{ + const bool state = !myAudioSettings.enabled(); + mute(!state); + + string message = "Sound "; + message += state ? "unmuted" : "muted"; + + myOSystem.frameBuffer().showTextMessage(message); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SoundSDL2::pause(bool state) +{ + ASSERT_MAIN_THREAD; + const bool oldstate = SDL_GetAudioDeviceStatus(myDevice) == SDL_AUDIO_PAUSED; if(myIsInitializedFlag) SDL_PauseAudioDevice(myDevice, state ? 1 : 0); @@ -235,39 +250,14 @@ bool SoundSDL2::mute(bool state) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool SoundSDL2::toggleMute() +void SoundSDL2::setVolume(uInt32 volume) { - const bool enabled = !myAudioSettings.enabled(); - - setEnabled(enabled); - myOSystem.console().initializeAudio(); - - // Adjust TIA sound to new mute state - myMuteState = !enabled; - mute(myMuteState); - // Make sure the current WAV file continues playing if it got stopped by 'mute()' - if(myWavDevice) - SDL_PauseAudioDevice(myWavDevice, 0); - - string message = "Sound "; - message += enabled ? "unmuted" : "muted"; - - myOSystem.frameBuffer().showTextMessage(message); - - return enabled; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL2::setVolume(uInt32 percent) -{ - if(myIsInitializedFlag && (percent <= 100)) + if(myIsInitializedFlag && (volume <= 100)) { - myAudioSettings.setVolume(percent); - myVolume = percent; + myAudioSettings.setVolume(volume); SDL_LockAudioDevice(myDevice); - myVolumeFactor = static_cast(percent) / 100.F; - myWavVolumeFactor = myAudioSettings.enabled() ? myVolumeFactor : 0; + myVolumeFactor = myAudioSettings.enabled() ? static_cast(volume) / 100.F : 0; SDL_UnlockAudioDevice(myDevice); } } @@ -275,7 +265,7 @@ void SoundSDL2::setVolume(uInt32 percent) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::adjustVolume(int direction) { - Int32 percent = myVolume; + Int32 percent = myAudioSettings.volume(); percent = BSPF::clamp(percent + direction * 2, 0, 100); // Enable audio if it is currently disabled @@ -285,8 +275,6 @@ void SoundSDL2::adjustVolume(int direction) { setEnabled(true); myOSystem.console().initializeAudio(); - myMuteState = false; - mute(false); } setVolume(percent); @@ -301,7 +289,7 @@ string SoundSDL2::about() const { ostringstream buf; buf << "Sound enabled:" << endl - << " Volume: " << myVolume << "%" << endl + << " Volume: " << myAudioSettings.volume() << "%" << endl << " Device: " << myDevices.at(myDeviceId).first << endl << " Channels: " << static_cast(myHardwareSpec.channels) << (myAudioQueue->isStereo() ? " (Stereo)" : " (Mono)") << endl @@ -520,7 +508,7 @@ void SoundSDL2::wavCallback(void* udata, uInt8* stream, int len) SDL_ConvertAudio(&cvt); // Mix volume adjusted WAV data into silent buffer SDL_MixAudioFormat(stream, cvt.buf, myWavSpec.format, cvt.len_cvt, - SDL_MIX_MAXVOLUME * myWavVolumeFactor); + SDL_MIX_MAXVOLUME * myVolumeFactor); } else { @@ -529,7 +517,7 @@ void SoundSDL2::wavCallback(void* udata, uInt8* stream, int len) // Mix volume adjusted WAV data into silent buffer SDL_MixAudioFormat(stream, myWavPos, myWavSpec.format, len, - SDL_MIX_MAXVOLUME * myWavVolumeFactor); + SDL_MIX_MAXVOLUME * myVolumeFactor); } myWavPos += len; myWavLen -= len; @@ -537,7 +525,7 @@ void SoundSDL2::wavCallback(void* udata, uInt8* stream, int len) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -float SoundSDL2::myWavVolumeFactor = 0xffff; +float SoundSDL2::myVolumeFactor = 0.F; SDL_AudioSpec SoundSDL2::myWavSpec; // audio output format uInt8* SoundSDL2::myWavPos = nullptr; // pointer to the audio buffer to be played uInt32 SoundSDL2::myWavLen = 0; // remaining length of the sample we have to play diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index ce398d4b5..2753afb78 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -62,7 +62,8 @@ 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, EmulationTiming* emulationTiming) override; + void open(shared_ptr audioQueue, + EmulationTiming* emulationTiming) override; /** Should be called to close the sound device. Once called the sound @@ -71,29 +72,39 @@ class SoundSDL2 : public Sound void close() override; /** - Set the mute state of the sound object. While muted no sound is played. + Sets the sound mute state; sound processing continues. When turned + off, sound volume is 0; when turned on, sound volume returns to + previously set level. @param state Mutes sound if true, unmute if false - - @return The previous (old) mute state */ - bool mute(bool state) override; + void mute(bool state) override; /** - Toggles the sound mute state. While muted no sound is played. - - @return The previous (old) mute state + Toggles the sound mute state; sound processing continues. + Switches between mute(true) and mute(false). */ - bool toggleMute() override; + void toggleMute() override; + + /** + Set the pause state of the sound object. While paused, sound is + neither played nor processed (ie, the sound subsystem is temporarily + disabled). + + @param state Pause sound if true, unpause if false + + @return The previous (old) pause state + */ + bool pause(bool state) override; /** Sets the volume of the sound device to the specified level. The - volume is given as a percentage from 0 to 100. Values outside - this range indicate that the volume shouldn't be changed at all. + volume is given as a range from 0 to 100 (0 indicates mute). Values + outside this range indicate that the volume shouldn't be changed at all. - @param percent The new volume percentage level for the sound device + @param volume The new volume level for the sound device */ - void setVolume(uInt32 percent) override; + void setVolume(uInt32 volume) override; /** Adjusts the volume of the sound device based on the given direction. @@ -163,12 +174,6 @@ class SoundSDL2 : public Sound // Indicates if the sound device was successfully initialized bool myIsInitializedFlag{false}; - // Current volume as a percentage (0 - 100) - uInt32 myVolume{100}; - float myVolumeFactor{0xffff}; - // Current mute state, used to control WAV file sound - bool myMuteState{false}; - // Audio specification structure SDL_AudioSpec myHardwareSpec; @@ -188,15 +193,15 @@ class SoundSDL2 : public Sound AudioSettings& myAudioSettings; // WAV file sound variables - string myWavFilename{EmptyString}; + string myWavFilename; uInt32 myWavLength{0}; SDL_AudioDeviceID myWavDevice{0}; uInt8* myWavBuffer{nullptr}; + static float myVolumeFactor; // Current volume level (0 - 100) static double myWavSpeed; static unique_ptr myWavCvtBuffer; static uInt32 myWavCvtBufferSize; - static float myWavVolumeFactor; static SDL_AudioSpec myWavSpec; // audio output format static uInt8* myWavPos; // pointer to the audio buffer to be played static uInt32 myWavLen; // remaining length of the sample we have to play diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 8b0615fc6..95064c502 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -171,8 +171,8 @@ Console::Console(OSystem& osystem, unique_ptr& cart, const string& md5 = myProperties.get(PropType::Cart_MD5); setControllers(md5); - // Mute audio and clear framebuffer while autodetection runs - myOSystem.sound().mute(true); + // Pause audio and clear framebuffer while autodetection runs + myOSystem.sound().pause(true); myOSystem.frameBuffer().clear(); if(myDisplayFormat == "AUTO" || myOSystem.settings().getBool("rominfo")) diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx index 4930b186f..ef122ca6e 100644 --- a/src/emucore/EventHandler.cxx +++ b/src/emucore/EventHandler.cxx @@ -2567,7 +2567,7 @@ void EventHandler::enterMenuMode(EventHandlerState state) #ifdef GUI_SUPPORT setState(state); myOverlay->reStack(); - myOSystem.sound().mute(true); + myOSystem.sound().pause(true); #endif } @@ -2577,7 +2577,7 @@ void EventHandler::leaveMenuMode() #ifdef GUI_SUPPORT myOverlay->removeDialog(); // remove the base dialog from dialog stack setState(EventHandlerState::EMULATION); - myOSystem.sound().mute(false); + myOSystem.sound().pause(false); #endif } @@ -2673,12 +2673,12 @@ void EventHandler::setState(EventHandlerState state) { case EventHandlerState::EMULATION: case EventHandlerState::PLAYBACK: - myOSystem.sound().mute(false); + myOSystem.sound().pause(false); enableTextEvents(false); break; case EventHandlerState::PAUSE: - myOSystem.sound().mute(true); + myOSystem.sound().pause(true); enableTextEvents(false); break; diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 64e974419..7efc0ec5f 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -521,8 +521,8 @@ void FrameBuffer::update(UpdateMode mode) //frames = intervalFrames + std::sqrt(std::max(stateFrames - intervalFrames, 0)); frames = std::round(std::sqrt(stateFrames)); - // Mute sound if saved states were removed or states are too far apart - myOSystem.sound().mute(stateFrames > intervalFrames || + // Pause sound if saved states were removed or states are too far apart + myOSystem.sound().pause(stateFrames > intervalFrames || frames > static_cast(myOSystem.audioSettings().bufferSize() / 2 + 1)); } redraw |= success; @@ -534,7 +534,7 @@ void FrameBuffer::update(UpdateMode mode) if(!success) { frames = 0; - myOSystem.sound().mute(true); + myOSystem.sound().pause(true); myOSystem.eventHandler().enterMenuMode(EventHandlerState::TIMEMACHINE); } break; // EventHandlerState::PLAYBACK @@ -1253,8 +1253,8 @@ FBInitStatus FrameBuffer::applyVideoMode() // Changing the video mode can take some time, during which the last // sound played may get 'stuck' - // So we mute the sound until the operation completes - const bool oldMuteState = myOSystem.sound().mute(true); + // So we pause the sound until the operation completes + const bool oldPauseState = myOSystem.sound().pause(true); FBInitStatus status = FBInitStatus::FailNotSupported; if(myBackend->setVideoMode(mode, @@ -1287,7 +1287,7 @@ FBInitStatus FrameBuffer::applyVideoMode() Logger::error("ERROR: Couldn't initialize video subsystem"); // Restore sound settings - myOSystem.sound().mute(oldMuteState); + myOSystem.sound().pause(oldPauseState); return status; } diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index f06aa710b..6e5543205 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -62,29 +62,39 @@ class Sound virtual void close() = 0; /** - Set the mute state of the sound object. While muted no sound is played. + Sets the sound mute state; sound processing continues. When turned + off, sound volume is 0; when turned on, sound volume returns to + previously set level. @param state Mutes sound if true, unmute if false - - @return The previous (old) mute state */ - virtual bool mute(bool state) = 0; + virtual void mute(bool state) = 0; /** - Toggles the sound mute state. While muted no sound is played. - - @return The previous (old) mute state + Toggles the sound mute state; sound processing continues. + Switches between mute(true) and mute(false). */ - virtual bool toggleMute() = 0; + virtual void toggleMute() = 0; + + /** + Set the pause state of the sound object. While paused, sound is + neither played nor processed (ie, the sound subsystem is temporarily + disabled). + + @param state Pause sound if true, unpause if false + + @return The previous (old) pause state + */ + virtual bool pause(bool state) = 0; /** Sets the volume of the sound device to the specified level. The - volume is given as a percentage from 0 to 100. Values outside - this range indicate that the volume shouldn't be changed at all. + volume is given as a range from 0 to 100 (0 indicates mute). Values + outside this range indicate that the volume shouldn't be changed at all. - @param percent The new volume percentage level for the sound device + @param volume The new volume level for the sound device */ - virtual void setVolume(uInt32 percent) = 0; + virtual void setVolume(uInt32 volume) = 0; /** Adjusts the volume of the sound device based on the given direction. @@ -137,7 +147,6 @@ class Sound */ virtual void queryHardware(VariantList& devices) = 0; - protected: // The OSystem for this sound object OSystem& myOSystem; diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx index 715c1d12a..de014dded 100644 --- a/src/gui/VideoAudioDialog.cxx +++ b/src/gui/VideoAudioDialog.cxx @@ -487,7 +487,7 @@ void VideoAudioDialog::addAudioTab() VarList::push_back(items, "Ultra quality, minimal lag", static_cast(AudioSettings::Preset::ultraQualityMinimalLag)); VarList::push_back(items, "Custom", static_cast(AudioSettings::Preset::custom)); myModePopup = new PopUpWidget(myTab, _font, xpos, ypos, - _font.getStringWidth("Ultry quality, minimal lag"), lineHeight, + _font.getStringWidth("Ultra quality, minimal lag"), lineHeight, items, "Mode", lwidth, kModeChanged); wid.push_back(myModePopup); ypos += lineHeight + VGAP; @@ -1274,7 +1274,7 @@ void VideoAudioDialog::colorPalette() if(instance().hasConsole()) { - const int order[2][NUM_CHROMA] = + static constexpr int order[2][NUM_CHROMA] = { {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, {0, 1, 2, 4, 6, 8, 10, 12, 13, 11, 9, 7, 5, 3, 14, 15} @@ -1312,7 +1312,8 @@ void VideoAudioDialog::updateEnabledState() myStereoSoundCheckbox->setEnabled(active); myModePopup->setEnabled(active); // enable only for Pitfall II cart - myDpcPitch->setEnabled(active && instance().hasConsole() && instance().console().cartridge().name() == "CartridgeDPC"); + myDpcPitch->setEnabled(active && instance().hasConsole() && + instance().console().cartridge().name() == "CartridgeDPC"); myFragsizePopup->setEnabled(active && userMode); myFreqPopup->setEnabled(active && userMode); diff --git a/src/os/libretro/SoundLIBRETRO.hxx b/src/os/libretro/SoundLIBRETRO.hxx index d626190f1..aa20237f2 100644 --- a/src/os/libretro/SoundLIBRETRO.hxx +++ b/src/os/libretro/SoundLIBRETRO.hxx @@ -147,8 +147,9 @@ class SoundLIBRETRO : public Sound void queryHardware(VariantList& devices) override { } void setVolume(uInt32 percent) override { } void adjustVolume(int direction = +1) override { } - bool mute(bool state) override { return !myIsInitializedFlag; } - bool toggleMute() override { return !myIsInitializedFlag; } + void mute(bool state) override { } + void toggleMute() override { } + bool pause(bool state) override { return !myIsInitializedFlag; } string about() const override { return ""; } private: