Added sound and music volume control.

Issue #120.
This commit is contained in:
Muzychenko Andrey 2022-01-12 17:17:38 +03:00
parent 43af97127b
commit 0a2d6847ba
8 changed files with 99 additions and 46 deletions

View File

@ -4,9 +4,11 @@
int Sound::num_channels; int Sound::num_channels;
bool Sound::enabled_flag = false; bool Sound::enabled_flag = false;
int* Sound::TimeStamps = nullptr; 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); Mix_Init(MIX_INIT_MID_Proxy);
auto result = Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024); auto result = Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024);
SetChannels(channels); SetChannels(channels);
@ -80,4 +82,11 @@ void Sound::SetChannels(int channels)
delete[] TimeStamps; delete[] TimeStamps;
TimeStamps = new int[num_channels](); TimeStamps = new int[num_channels]();
Mix_AllocateChannels(num_channels); Mix_AllocateChannels(num_channels);
SetVolume(Volume);
}
void Sound::SetVolume(int volume)
{
Volume = volume;
Mix_Volume(-1, volume);
} }

View File

@ -4,7 +4,7 @@
class Sound class Sound
{ {
public: public:
static bool Init(int channels, bool enableFlag); static bool Init(int channels, bool enableFlag, int volume);
static void Enable(bool enableFlag); static void Enable(bool enableFlag);
static void Activate(); static void Activate();
static void Deactivate(); static void Deactivate();
@ -13,8 +13,10 @@ public:
static Mix_Chunk* LoadWaveFile(const std::string& lpName); static Mix_Chunk* LoadWaveFile(const std::string& lpName);
static void FreeSound(Mix_Chunk* wave); static void FreeSound(Mix_Chunk* wave);
static void SetChannels(int channels); static void SetChannels(int channels);
static void SetVolume(int volume);
private: private:
static int num_channels; static int num_channels;
static bool enabled_flag; static bool enabled_flag;
static int* TimeStamps; static int* TimeStamps;
static int Volume;
}; };

View File

@ -9,6 +9,7 @@
std::vector<Mix_Music*> midi::LoadedTracks{}; std::vector<Mix_Music*> midi::LoadedTracks{};
Mix_Music *midi::track1, *midi::track2, *midi::track3, *midi::active_track, *midi::NextTrack; Mix_Music *midi::track1, *midi::track2, *midi::track3, *midi::active_track, *midi::NextTrack;
bool midi::SetNextTrackFlag; 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) 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; return true;
} }
int midi::music_init() int midi::music_init(int volume)
{ {
SetVolume(volume);
active_track = nullptr; active_track = nullptr;
if (pb::FullTiltMode) if (pb::FullTiltMode)
@ -87,6 +89,12 @@ void midi::music_shutdown()
LoadedTracks.clear(); LoadedTracks.clear();
} }
void midi::SetVolume(int volume)
{
Volume = volume;
Mix_VolumeMusic(volume);
}
Mix_Music* midi::load_track(std::string fileName) Mix_Music* midi::load_track(std::string fileName)
{ {
Mix_Music* audio = nullptr; Mix_Music* audio = nullptr;
@ -157,6 +165,9 @@ bool midi::play_track(Mix_Music* midi)
return false; 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; active_track = midi;
return true; return true;
} }

View File

@ -88,12 +88,15 @@ class midi
public: public:
static int play_pb_theme(); static int play_pb_theme();
static int music_stop(); static int music_stop();
static int music_init(); static int music_init(int volume);
static void music_shutdown(); static void music_shutdown();
static void SetVolume(int volume);
private: private:
static std::vector<Mix_Music*> LoadedTracks; static std::vector<Mix_Music*> LoadedTracks;
static Mix_Music *track1, *track2, *track3, *active_track, *NextTrack; static Mix_Music *track1, *track2, *track3, *active_track, *NextTrack;
static bool SetNextTrackFlag; static bool SetNextTrackFlag;
static int Volume;
static Mix_Music* load_track(std::string fileName); static Mix_Music* load_track(std::string fileName);
static bool play_track(Mix_Music* midi); static bool play_track(Mix_Music* midi);
static std::vector<uint8_t>* MdsToMidi(std::string file); static std::vector<uint8_t>* MdsToMidi(std::string file);

View File

@ -9,6 +9,7 @@
constexpr int options::MaxUps, options::MaxFps, options::MinUps, options::MinFps, options::DefUps, options::DefFps; 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::MaxSoundChannels, options::MinSoundChannels, options::DefSoundChannels;
constexpr int options::MaxVolume, options::MinVolume, options::DefVolume;
optionsStruct options::Options{}; optionsStruct options::Options{};
std::map<std::string, std::string> options::settings{}; std::map<std::string, std::string> options::settings{};
@ -92,16 +93,17 @@ void options::InitPrimary()
ImGui::GetIO().FontGlobalScale = get_float("UI Scale", 1.0f); ImGui::GetIO().FontGlobalScale = get_float("UI Scale", 1.0f);
Options.Resolution = get_int("Screen Resolution", -1); Options.Resolution = get_int("Screen Resolution", -1);
Options.LinearFiltering = get_int("Linear Filtering", true); Options.LinearFiltering = get_int("Linear Filtering", true);
Options.FramesPerSecond = std::min(MaxFps, std::max(MinUps, get_int("Frames Per Second", DefFps))); Options.FramesPerSecond = Clamp(get_int("Frames Per Second", DefFps), MinFps, MaxFps);
Options.UpdatesPerSecond = std::min(MaxUps, std::max(MinUps, get_int("Updates Per Second", DefUps))); Options.UpdatesPerSecond = Clamp(get_int("Updates Per Second", DefUps), MinUps, MaxUps);
Options.UpdatesPerSecond = std::max(Options.UpdatesPerSecond, Options.FramesPerSecond); Options.UpdatesPerSecond = std::max(Options.UpdatesPerSecond, Options.FramesPerSecond);
Options.ShowMenu = get_int("ShowMenu", true); Options.ShowMenu = get_int("ShowMenu", true);
Options.UncappedUpdatesPerSecond = get_int("Uncapped Updates Per Second", false); Options.UncappedUpdatesPerSecond = get_int("Uncapped Updates Per Second", false);
Options.SoundChannels = get_int("Sound Channels", DefSoundChannels); Options.SoundChannels = Clamp(get_int("Sound Channels", DefSoundChannels), MinSoundChannels, MaxSoundChannels);
Options.SoundChannels = std::min(MaxSoundChannels, std::max(MinSoundChannels, Options.SoundChannels));
Options.HybridSleep = get_int("HybridSleep", false); Options.HybridSleep = get_int("HybridSleep", false);
Options.Prefer3DPBGameData = get_int("Prefer 3DPB Game Data", false); Options.Prefer3DPBGameData = get_int("Prefer 3DPB Game Data", false);
Options.IntegerScaling = get_int("Integer Scaling", 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() void options::InitSecondary()
@ -139,6 +141,8 @@ void options::uninit()
set_int("HybridSleep", Options.HybridSleep); set_int("HybridSleep", Options.HybridSleep);
set_int("Prefer 3DPB Game Data", Options.Prefer3DPBGameData); set_int("Prefer 3DPB Game Data", Options.Prefer3DPBGameData);
set_int("Integer Scaling", Options.IntegerScaling); set_int("Integer Scaling", Options.IntegerScaling);
set_int("Sound Volume", Options.SoundVolume);
set_int("Music Volume", Options.MusicVolume);
} }

View File

@ -78,6 +78,8 @@ struct optionsStruct
bool HybridSleep; bool HybridSleep;
bool Prefer3DPBGameData; bool Prefer3DPBGameData;
bool IntegerScaling; bool IntegerScaling;
int SoundVolume;
int MusicVolume;
}; };
struct ControlRef struct ControlRef
@ -95,6 +97,7 @@ public:
DefUps = 120, DefFps = 60; DefUps = 120, DefFps = 60;
// Original uses 8 sound channels // Original uses 8 sound channels
static constexpr int MaxSoundChannels = 32, MinSoundChannels = 1, DefSoundChannels = 8; static constexpr int MaxSoundChannels = 32, MinSoundChannels = 1, DefSoundChannels = 8;
static constexpr int MaxVolume = MIX_MAX_VOLUME, MinVolume = 0, DefVolume = MaxVolume;
static optionsStruct Options; static optionsStruct Options;
static void InitPrimary(); static void InitPrimary();

View File

@ -78,12 +78,18 @@ inline float RandFloat()
return static_cast<float>(std::rand() / static_cast<double>(RAND_MAX)); return static_cast<float>(std::rand() / static_cast<double>(RAND_MAX));
} }
template <typename T> constexpr template <typename T>
int Sign(T val) constexpr int Sign(T val)
{ {
return (T(0) < val) - (val < T(0)); return (T(0) < val) - (val < T(0));
} }
template <typename T>
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 // UTF-8 path adapter for fopen on Windows, implemented in SpaceCadetPinball.cpp
#ifdef _WIN32 #ifdef _WIN32
extern FILE* fopenu(const char* path, const char* opt); extern FILE* fopenu(const char* path, const char* opt);

View File

@ -133,10 +133,10 @@ int winmain::WinMain(LPCSTR lpCmdLine)
// Second step: run updates depending on FullTiltMode // Second step: run updates depending on FullTiltMode
options::InitSecondary(); options::InitSecondary();
if (!Sound::Init(Options.SoundChannels, Options.Sounds)) if (!Sound::Init(Options.SoundChannels, Options.Sounds, Options.SoundVolume))
Options.Sounds = false; Options.Sounds = false;
if (!pinball::quickFlag && !midi::music_init()) if (!pinball::quickFlag && !midi::music_init(Options.MusicVolume))
Options.Music = false; Options.Music = false;
if (pb::init()) if (pb::init())
@ -291,8 +291,7 @@ int winmain::WinMain(LPCSTR lpCmdLine)
} }
// Limit duration to 2 * target time // Limit duration to 2 * target time
sleepRemainder = std::max(std::min(DurationMs(frameEnd - updateEnd) - targetTimeDelta, TargetFrameTime), sleepRemainder = Clamp(DurationMs(frameEnd - updateEnd) - targetTimeDelta, -TargetFrameTime, TargetFrameTime);
-TargetFrameTime);
frameDuration = std::min<DurationMs>(DurationMs(frameEnd - frameStart), 2 * TargetFrameTime); frameDuration = std::min<DurationMs>(DurationMs(frameEnd - frameStart), 2 * TargetFrameTime);
frameStart = frameEnd; frameStart = frameEnd;
UpdateToFrameCounter++; UpdateToFrameCounter++;
@ -431,50 +430,46 @@ void winmain::RenderUi()
} }
ImGui::EndMenu(); 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")) if (ImGui::MenuItem("Player Controls...", "F8"))
{ {
pause(false); pause(false);
options::ShowControlDialog(); options::ShowControlDialog();
} }
if (ImGui::BeginMenu("Table Resolution")) ImGui::Separator();
if (ImGui::BeginMenu("Audio"))
{ {
char buffer[20]{}; if (ImGui::MenuItem("Sound", "F5", Options.Sounds))
auto maxResText = pinball::get_rc_string(fullscrn::GetMaxResolution() + 2030, 0);
if (ImGui::MenuItem(maxResText, nullptr, Options.Resolution == -1))
{ {
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]; Sound::SetVolume(Options.SoundVolume);
snprintf(buffer, sizeof buffer - 1, "%d x %d", res.ScreenWidth, res.ScreenHeight); }
if (ImGui::MenuItem(buffer, nullptr, Options.Resolution == i)) ImGui::TextUnformatted("Sound Channels");
{ if (ImGui::SliderInt("##Sound Channels", &Options.SoundChannels, options::MinSoundChannels,
options::toggle(static_cast<Menu1>(static_cast<int>(Menu1::R640x480) + i)); 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(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("Graphics")) if (ImGui::BeginMenu("Graphics"))
{ {
if (ImGui::MenuItem("Uniform Scaling", nullptr, Options.UniformScaling)) if (ImGui::MenuItem("Uniform Scaling", nullptr, Options.UniformScaling))
@ -533,6 +528,26 @@ void winmain::RenderUi()
ImGui::EndMenu(); 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<Menu1>(static_cast<int>(Menu1::R640x480) + i));
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Game Data")) if (ImGui::BeginMenu("Game Data"))
{ {
if (ImGui::MenuItem("Prefer 3DPB Data", nullptr, Options.Prefer3DPBGameData)) if (ImGui::MenuItem("Prefer 3DPB Data", nullptr, Options.Prefer3DPBGameData))