stefanhendriks / Dune-II---The-Maker

A remake of the classic Dune 2 - The Building of a Dynasty (by Westwood Studios) with several enhancements. Like: higher screenresolutions, zooming, multiselect, skirmish play, etc.
https://www.dune2themaker.com
301 stars 26 forks source link

[PATCH ENHANCEMENT] Control midi volume in game by keyboard keys #591

Open cm8 opened 8 months ago

cm8 commented 8 months ago

Since it would be nice to alter the music volume in game, the following patch adds functionality to d2tm to do so. It adds

Keyboard key M, as proposed by below patch, does not result in muting the music, but rather stops playing midi completely, freeing the resources used by allegro to generate the music. As a result, hitting M again, the last played midi restarts; just to point out that this is not a pause/resume function. The set_volume allegro range is 0..255 and allegro clamps the values given to this range itself.

Keyboard key P might not be the best choice. For future improvements it may be wise to reserve it as a hotkey for some 'pause' mnemonic, so please alter those defines to your likings. For the same reason KEY_PLUS_PAD or KEY_PLUS was not chosen, although it is currently unused.

diff --git a/cGame_logic.cpp b/cGame_logic.cpp
index b64a91d4..98c3458b 100644
--- a/cGame_logic.cpp
+++ b/cGame_logic.cpp
@@ -1919,11 +1919,34 @@ void cGame::onKeyDownGamePlaying(const cKeyboardEvent &event) {

 void cGame::onKeyPressedGamePlaying(const cKeyboardEvent &event) {
     cPlayer &humanPlayer = players[HUMAN];
+    int digivol;
+    int midivol;

     if (event.hasKey(KEY_F)) {
         m_drawFps = false;
     }

+    if (event.hasKey(KEY_M)) {
+        game.m_playMusic = !game.m_playMusic;
+        if (!game.m_playMusic) {
+            m_soundPlayer->stopMusic();
+        } else {
+            m_soundPlayer->playMusic(m_newMusicSample);
+        }
+    }
+
+    if (event.hasKey(KEY_O)) {
+        get_volume(&digivol, &midivol);
+        midivol -= 10;
+        set_volume(digivol, midivol);
+    }
+
+    if (event.hasKey(KEY_P)) {
+        get_volume(&digivol, &midivol);
+        midivol += 10;
+        set_volume(digivol, midivol);
+    }
+
     if (event.hasKey(KEY_H)) {
         mapCamera->centerAndJumpViewPortToCell(humanPlayer.getFocusCell());
     }

This bypasses m_musicVolume, but this has been / is an unused variable anyway. Even if the game is to initialize the midi volume on start, the variable is propaly not needed to steer midi volume levels as shown above. EDIT: The game does set midi volume on start in class cSoundPlayer, but does not reference m_musicVolume from there.

If midi does not play at all, it may be caused by reserve_voices() in cSoundPlayer. It hard-codes value 0 for midi voices to be reserved. While it may be irrelevant for hardware midi (?), this renders the DIGMID softsynth provided by allegro unusable. The following patch preserves the previous behavior, unless DIGMID is configured (as to not break drivers unknown to have the same issue).

diff --git a/utils/cSoundPlayer.cpp b/utils/cSoundPlayer.cpp
index 3ecb24d4..35d87361 100644
--- a/utils/cSoundPlayer.cpp
+++ b/utils/cSoundPlayer.cpp
@@ -3,6 +3,7 @@
 #include "definitions.h"
 #include "utils/cLog.h"

+#include <allegro/config.h>
 #include <allegro/datafile.h>
 #include <allegro/digi.h>
 #include <allegro/midi.h>
@@ -72,13 +73,25 @@ cSoundPlayer::cSoundPlayer(const cPlatformLayerInit&, int maxNrVoices)
     }

     int nr_voices = maxNrVoices;
+    int midi_voices = 0;
+
+    int card_id = get_config_id("sound", "midi_card", MIDI_AUTODETECT);
+
     while (true) {
         if (nr_voices < kMinNrVoices) {
             logger->log(LOG_WARN, COMP_SOUND, "Initialization", "Failed installing sound.", OUTC_FAILED);
             return;
         }

-        reserve_voices(nr_voices, 0); // Reserve nothing for MIDI, assume it will "steal" from the digi voices
+        nr_voices = maxNrVoices;
+        if (card_id == MIDI_DIGMID) {
+            midi_voices = nr_voices / 8;
+            nr_voices = CLAMP(kMinNrVoices, nr_voices - midi_voices, maxNrVoices);
+            midi_voices = maxNrVoices - nr_voices;
+        } else {
+            // Reserve nothing for MIDI, assume it will "steal" from the digi voices
+        }
+        reserve_voices(nr_voices, midi_voices);

         if (install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, nullptr) == 0)
         {
@@ -93,7 +106,7 @@ cSoundPlayer::cSoundPlayer(const cPlatformLayerInit&, int maxNrVoices)
         auto msg = fmt::format("Failed reserving {} voices. Will try {}.", nr_voices, (nr_voices / 2));
         logger->log(LOG_INFO, COMP_SOUND, "Initialization", msg, OUTC_FAILED);

-        nr_voices /= 2;
+        maxNrVoices = (nr_voices + midi_voices) / 2;
     }

     // Sound effects are loud, the music is queiter (its background music, so it should not be disturbing).

If alsa rawmidi does not work out of the box, the more basic DIGMID driver should work with these d2tm.cfg ini options:

[sound]
midi_card = DIGI
patches = /path/to/patches.dat

In this case allegro does wavetable synth on its own, but relies on a patches.dat. See allegro docs how to generate this, e. g. pat2dat patches.dat FluidR3_GM.sf2 -8 . However, as long as the game calls reserve_voices(nr_voices, 0) the allegro midi player probably stays mute, which may or may not be true for other midi drivers such as alsa rawmidi midi_card = AMID as well (untested).


Above inline patches as zip: control_ingame_midi_by_keyboard.patch.zip

stefanhendriks commented 3 months ago

thanks for the patch! I'll process this in a PR 👍