From 0a2d6847bab69d1bb3046b155fe043121dac6633 Mon Sep 17 00:00:00 2001 From: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com> Date: Wed, 12 Jan 2022 17:17:38 +0300 Subject: [PATCH] Added sound and music volume control. Issue #120. --- SpaceCadetPinball/Sound.cpp | 11 ++++- SpaceCadetPinball/Sound.h | 4 +- SpaceCadetPinball/midi.cpp | 13 +++++- SpaceCadetPinball/midi.h | 5 +- SpaceCadetPinball/options.cpp | 12 +++-- SpaceCadetPinball/options.h | 3 ++ SpaceCadetPinball/pch.h | 10 +++- SpaceCadetPinball/winmain.cpp | 87 ++++++++++++++++++++--------------- 8 files changed, 99 insertions(+), 46 deletions(-) diff --git a/SpaceCadetPinball/Sound.cpp b/SpaceCadetPinball/Sound.cpp index 082377e..3959df0 100644 --- a/SpaceCadetPinball/Sound.cpp +++ b/SpaceCadetPinball/Sound.cpp @@ -4,9 +4,11 @@ int Sound::num_channels; bool Sound::enabled_flag = false; int* Sound::TimeStamps = nullptr; +int Sound::Volume = MIX_MAX_VOLUME; -bool Sound::Init(int channels, bool enableFlag) +bool Sound::Init(int channels, bool enableFlag, int volume) { + Volume = volume; Mix_Init(MIX_INIT_MID_Proxy); auto result = Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024); SetChannels(channels); @@ -80,4 +82,11 @@ void Sound::SetChannels(int channels) delete[] TimeStamps; TimeStamps = new int[num_channels](); Mix_AllocateChannels(num_channels); + SetVolume(Volume); +} + +void Sound::SetVolume(int volume) +{ + Volume = volume; + Mix_Volume(-1, volume); } diff --git a/SpaceCadetPinball/Sound.h b/SpaceCadetPinball/Sound.h index 0665b78..2e2c3ca 100644 --- a/SpaceCadetPinball/Sound.h +++ b/SpaceCadetPinball/Sound.h @@ -4,7 +4,7 @@ class Sound { public: - static bool Init(int channels, bool enableFlag); + static bool Init(int channels, bool enableFlag, int volume); static void Enable(bool enableFlag); static void Activate(); static void Deactivate(); @@ -13,8 +13,10 @@ public: static Mix_Chunk* LoadWaveFile(const std::string& lpName); static void FreeSound(Mix_Chunk* wave); static void SetChannels(int channels); + static void SetVolume(int volume); private: static int num_channels; static bool enabled_flag; static int* TimeStamps; + static int Volume; }; diff --git a/SpaceCadetPinball/midi.cpp b/SpaceCadetPinball/midi.cpp index b045524..f17c6d2 100644 --- a/SpaceCadetPinball/midi.cpp +++ b/SpaceCadetPinball/midi.cpp @@ -9,6 +9,7 @@ std::vector midi::LoadedTracks{}; Mix_Music *midi::track1, *midi::track2, *midi::track3, *midi::active_track, *midi::NextTrack; bool midi::SetNextTrackFlag; +int midi::Volume = MIX_MAX_VOLUME; constexpr uint32_t FOURCC(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { @@ -47,8 +48,9 @@ int midi::music_stop() return true; } -int midi::music_init() +int midi::music_init(int volume) { + SetVolume(volume); active_track = nullptr; if (pb::FullTiltMode) @@ -87,6 +89,12 @@ void midi::music_shutdown() LoadedTracks.clear(); } +void midi::SetVolume(int volume) +{ + Volume = volume; + Mix_VolumeMusic(volume); +} + Mix_Music* midi::load_track(std::string fileName) { Mix_Music* audio = nullptr; @@ -157,6 +165,9 @@ bool midi::play_track(Mix_Music* midi) return false; } + // On Windows, MIDI volume can only be set during playback. + // And it changes application master volume for some reason. + SetVolume(Volume); active_track = midi; return true; } diff --git a/SpaceCadetPinball/midi.h b/SpaceCadetPinball/midi.h index 91441b8..864cbdc 100644 --- a/SpaceCadetPinball/midi.h +++ b/SpaceCadetPinball/midi.h @@ -88,12 +88,15 @@ class midi public: static int play_pb_theme(); static int music_stop(); - static int music_init(); + static int music_init(int volume); static void music_shutdown(); + static void SetVolume(int volume); private: static std::vector LoadedTracks; static Mix_Music *track1, *track2, *track3, *active_track, *NextTrack; static bool SetNextTrackFlag; + static int Volume; + static Mix_Music* load_track(std::string fileName); static bool play_track(Mix_Music* midi); static std::vector* MdsToMidi(std::string file); diff --git a/SpaceCadetPinball/options.cpp b/SpaceCadetPinball/options.cpp index e444f49..d05074e 100644 --- a/SpaceCadetPinball/options.cpp +++ b/SpaceCadetPinball/options.cpp @@ -9,6 +9,7 @@ constexpr int options::MaxUps, options::MaxFps, options::MinUps, options::MinFps, options::DefUps, options::DefFps; constexpr int options::MaxSoundChannels, options::MinSoundChannels, options::DefSoundChannels; +constexpr int options::MaxVolume, options::MinVolume, options::DefVolume; optionsStruct options::Options{}; std::map options::settings{}; @@ -92,16 +93,17 @@ void options::InitPrimary() ImGui::GetIO().FontGlobalScale = get_float("UI Scale", 1.0f); Options.Resolution = get_int("Screen Resolution", -1); Options.LinearFiltering = get_int("Linear Filtering", true); - Options.FramesPerSecond = std::min(MaxFps, std::max(MinUps, get_int("Frames Per Second", DefFps))); - Options.UpdatesPerSecond = std::min(MaxUps, std::max(MinUps, get_int("Updates Per Second", DefUps))); + Options.FramesPerSecond = Clamp(get_int("Frames Per Second", DefFps), MinFps, MaxFps); + Options.UpdatesPerSecond = Clamp(get_int("Updates Per Second", DefUps), MinUps, MaxUps); Options.UpdatesPerSecond = std::max(Options.UpdatesPerSecond, Options.FramesPerSecond); Options.ShowMenu = get_int("ShowMenu", true); Options.UncappedUpdatesPerSecond = get_int("Uncapped Updates Per Second", false); - Options.SoundChannels = get_int("Sound Channels", DefSoundChannels); - Options.SoundChannels = std::min(MaxSoundChannels, std::max(MinSoundChannels, Options.SoundChannels)); + Options.SoundChannels = Clamp(get_int("Sound Channels", DefSoundChannels), MinSoundChannels, MaxSoundChannels); Options.HybridSleep = get_int("HybridSleep", false); Options.Prefer3DPBGameData = get_int("Prefer 3DPB Game Data", false); Options.IntegerScaling = get_int("Integer Scaling", false); + Options.SoundVolume = Clamp(get_int("Sound Volume", DefVolume), MinVolume, MaxVolume); + Options.MusicVolume = Clamp(get_int("Music Volume", DefVolume), MinVolume, MaxVolume); } void options::InitSecondary() @@ -139,6 +141,8 @@ void options::uninit() set_int("HybridSleep", Options.HybridSleep); set_int("Prefer 3DPB Game Data", Options.Prefer3DPBGameData); set_int("Integer Scaling", Options.IntegerScaling); + set_int("Sound Volume", Options.SoundVolume); + set_int("Music Volume", Options.MusicVolume); } diff --git a/SpaceCadetPinball/options.h b/SpaceCadetPinball/options.h index 3bb1383..7ba4a20 100644 --- a/SpaceCadetPinball/options.h +++ b/SpaceCadetPinball/options.h @@ -78,6 +78,8 @@ struct optionsStruct bool HybridSleep; bool Prefer3DPBGameData; bool IntegerScaling; + int SoundVolume; + int MusicVolume; }; struct ControlRef @@ -95,6 +97,7 @@ public: DefUps = 120, DefFps = 60; // Original uses 8 sound channels static constexpr int MaxSoundChannels = 32, MinSoundChannels = 1, DefSoundChannels = 8; + static constexpr int MaxVolume = MIX_MAX_VOLUME, MinVolume = 0, DefVolume = MaxVolume; static optionsStruct Options; static void InitPrimary(); diff --git a/SpaceCadetPinball/pch.h b/SpaceCadetPinball/pch.h index 281dfd1..7d7cb2e 100644 --- a/SpaceCadetPinball/pch.h +++ b/SpaceCadetPinball/pch.h @@ -78,12 +78,18 @@ inline float RandFloat() return static_cast(std::rand() / static_cast(RAND_MAX)); } -template constexpr -int Sign(T val) +template +constexpr int Sign(T val) { return (T(0) < val) - (val < T(0)); } +template +const T& Clamp(const T& n, const T& lower, const T& upper) +{ + return std::max(lower, std::min(n, upper)); +} + // UTF-8 path adapter for fopen on Windows, implemented in SpaceCadetPinball.cpp #ifdef _WIN32 extern FILE* fopenu(const char* path, const char* opt); diff --git a/SpaceCadetPinball/winmain.cpp b/SpaceCadetPinball/winmain.cpp index e19891c..12e5d1a 100644 --- a/SpaceCadetPinball/winmain.cpp +++ b/SpaceCadetPinball/winmain.cpp @@ -133,10 +133,10 @@ int winmain::WinMain(LPCSTR lpCmdLine) // Second step: run updates depending on FullTiltMode options::InitSecondary(); - if (!Sound::Init(Options.SoundChannels, Options.Sounds)) + if (!Sound::Init(Options.SoundChannels, Options.Sounds, Options.SoundVolume)) Options.Sounds = false; - if (!pinball::quickFlag && !midi::music_init()) + if (!pinball::quickFlag && !midi::music_init(Options.MusicVolume)) Options.Music = false; if (pb::init()) @@ -291,8 +291,7 @@ int winmain::WinMain(LPCSTR lpCmdLine) } // Limit duration to 2 * target time - sleepRemainder = std::max(std::min(DurationMs(frameEnd - updateEnd) - targetTimeDelta, TargetFrameTime), - -TargetFrameTime); + sleepRemainder = Clamp(DurationMs(frameEnd - updateEnd) - targetTimeDelta, -TargetFrameTime, TargetFrameTime); frameDuration = std::min(DurationMs(frameEnd - frameStart), 2 * TargetFrameTime); frameStart = frameEnd; UpdateToFrameCounter++; @@ -431,50 +430,46 @@ void winmain::RenderUi() } ImGui::EndMenu(); } - ImGui::Separator(); - - if (ImGui::MenuItem("Sound", "F5", Options.Sounds)) - { - options::toggle(Menu1::Sounds); - } - if (ImGui::MenuItem("Music", "F6", Options.Music)) - { - options::toggle(Menu1::Music); - } - ImGui::TextUnformatted("Sound Channels"); - if (ImGui::SliderInt("##Sound Channels", &Options.SoundChannels, options::MinSoundChannels, - options::MaxSoundChannels, "%d", ImGuiSliderFlags_AlwaysClamp)) - { - Options.SoundChannels = std::min(options::MaxSoundChannels, - std::max(options::MinSoundChannels, Options.SoundChannels)); - Sound::SetChannels(Options.SoundChannels); - } - ImGui::Separator(); - if (ImGui::MenuItem("Player Controls...", "F8")) { pause(false); options::ShowControlDialog(); } - if (ImGui::BeginMenu("Table Resolution")) + ImGui::Separator(); + + if (ImGui::BeginMenu("Audio")) { - char buffer[20]{}; - auto maxResText = pinball::get_rc_string(fullscrn::GetMaxResolution() + 2030, 0); - if (ImGui::MenuItem(maxResText, nullptr, Options.Resolution == -1)) + if (ImGui::MenuItem("Sound", "F5", Options.Sounds)) { - options::toggle(Menu1::MaximumResolution); + options::toggle(Menu1::Sounds); } - for (auto i = 0; i <= fullscrn::GetMaxResolution(); i++) + ImGui::TextUnformatted("Sound Volume"); + if (ImGui::SliderInt("##Sound Volume", &Options.SoundVolume, options::MinVolume, options::MaxVolume, "%d", + ImGuiSliderFlags_AlwaysClamp)) { - auto& res = fullscrn::resolution_array[i]; - snprintf(buffer, sizeof buffer - 1, "%d x %d", res.ScreenWidth, res.ScreenHeight); - if (ImGui::MenuItem(buffer, nullptr, Options.Resolution == i)) - { - options::toggle(static_cast(static_cast(Menu1::R640x480) + i)); - } + Sound::SetVolume(Options.SoundVolume); + } + ImGui::TextUnformatted("Sound Channels"); + if (ImGui::SliderInt("##Sound Channels", &Options.SoundChannels, options::MinSoundChannels, + options::MaxSoundChannels, "%d", ImGuiSliderFlags_AlwaysClamp)) + { + Sound::SetChannels(Options.SoundChannels); + } + ImGui::Separator(); + + if (ImGui::MenuItem("Music", "F6", Options.Music)) + { + options::toggle(Menu1::Music); + } + ImGui::TextUnformatted("Music Volume"); + if (ImGui::SliderInt("##Music Volume", &Options.MusicVolume, options::MinVolume, options::MaxVolume, "%d", + ImGuiSliderFlags_AlwaysClamp)) + { + midi::SetVolume(Options.MusicVolume); } ImGui::EndMenu(); } + if (ImGui::BeginMenu("Graphics")) { if (ImGui::MenuItem("Uniform Scaling", nullptr, Options.UniformScaling)) @@ -533,6 +528,26 @@ void winmain::RenderUi() ImGui::EndMenu(); } + if (ImGui::BeginMenu("Table Resolution")) + { + char buffer[20]{}; + auto maxResText = pinball::get_rc_string(fullscrn::GetMaxResolution() + 2030, 0); + if (ImGui::MenuItem(maxResText, nullptr, Options.Resolution == -1)) + { + options::toggle(Menu1::MaximumResolution); + } + for (auto i = 0; i <= fullscrn::GetMaxResolution(); i++) + { + auto& res = fullscrn::resolution_array[i]; + snprintf(buffer, sizeof buffer - 1, "%d x %d", res.ScreenWidth, res.ScreenHeight); + if (ImGui::MenuItem(buffer, nullptr, Options.Resolution == i)) + { + options::toggle(static_cast(static_cast(Menu1::R640x480) + i)); + } + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Game Data")) { if (ImGui::MenuItem("Prefer 3DPB Data", nullptr, Options.Prefer3DPBGameData))