[Scummvm-git-logs] scummvm master -> 6097f386e76fb9e62949f1387bcb584a3446c87d

AndywinXp noreply at scummvm.org
Fri May 31 21:52:53 UTC 2024


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
6097f386e7 SWORD1: Implement alternative Windows audio driver behavior


Commit: 6097f386e76fb9e62949f1387bcb584a3446c87d
    https://github.com/scummvm/scummvm/commit/6097f386e76fb9e62949f1387bcb584a3446c87d
Author: AndywinXp (andywinxp at gmail.com)
Date: 2024-05-31T23:52:48+02:00

Commit Message:
SWORD1: Implement alternative Windows audio driver behavior

Implements feature request #15092, bringing the DirectSound driver
as an option for the game.

For now this is toggleable by editing scummvm.ini and adding:
windows_audio_mode=true

Differences between the DOS and Windows audio drivers:
* DOS:
   - Apparently uses linear volume curves, as per the AIL/Miles drivers;
   - Music can fade in and out;
   - Fades sound effects in and out at each scene change;
   - Lowers the volume of the music each time a speech line is playing;

* Windows:
   - Uses DirectSound with volume tables for game volume -> decibel
     volume conversion; the resulting logarithmic curve and slightly
     different volume scaling for sound effects ensures audibly
     different intensities for some sounds;
   - Music can only fade out, not in;
   - Does NOT sound effects in and out at each scene change;
   - Does NOT lower the volume of the music each time a speech line is playing;

Changed paths:
    engines/sword1/sound.cpp
    engines/sword1/sword1.cpp
    engines/sword1/sword1.h


diff --git a/engines/sword1/sound.cpp b/engines/sword1/sound.cpp
index 0dc64713483..48fc9a9f838 100644
--- a/engines/sword1/sound.cpp
+++ b/engines/sword1/sound.cpp
@@ -71,6 +71,125 @@ Sound::~Sound() {
 	closeCowSystem();
 }
 
+/*  Original volume tables from the Windows drivers:
+ *  
+ *  static const int32 speechVolTable[17] = {
+ *      -10000,
+ *      -5000, -3000, -2500, -2250,
+ *      -2000, -1750, -1500, -1250,
+ *      -1000, -750,  -500,  -350,
+ *      -200,  -100,  -50,   0
+ *  };
+ *  
+ *  static const int32 musicVolTable[17] = {
+ *      -5000,
+ *      -4000, -3500, -3000, -2750,
+ *      -2500, -2250, -2000, -1800,
+ *      -1550, -1300, -1100, -900, 
+ *      -700,  -500,  -400,  -200  
+ *  };
+ *  
+ *  static const int32 fxVolTable[17] = {
+ *      -10000,
+ *      -5000, -3000, -2500, -2250,
+ *      -2000, -1750, -1500, -1250,
+ *      -1000, -750,  -500,  -350,
+ *      -200,  -100,  -50,   0
+ *  };
+ *  
+ *  These values are expressed in hundredths of a decibel, therefore we pre-processed them in the following way:
+ *  
+ *  * Convert each dB value to a linear scale (from 0.0 to 1.0):
+ *        pow(10, dB / 2000.0);
+ *  * Normalize the obtained value to the 0-255 range, keeping in mind that DirectSound allowable
+ *    values are between DSBVOLUME_MAX (no attenuation, 0) and DSBVOLUME_MIN (silence, -10000):
+ *        normalizedValue = int(round((linearValue - DSBVOLUME_MIN) / (DSBVOLUME_MAX - DSBVOLUME_MIN) * 255.0))
+ */
+
+static const int32 speechVolTable[17] = {
+	0,    // -100.00 dB
+	1,    // -50.00 dB
+	8,    // -30.00 dB
+	14,   // -25.00 dB
+	19,   // -22.50 dB
+	25,   // -20.00 dB
+	34,   // -17.50 dB
+	45,   // -15.00 dB
+	60,   // -12.50 dB
+	81,   // -10.00 dB
+	108,  // -7.50 dB
+	143,  // -5.00 dB
+	170,  // -3.50 dB
+	203,  // -2.00 dB
+	227,  // -1.00 dB
+	241,  // -0.50 dB
+	255   // 0 dB
+};
+
+static const int32 musicVolTable[17] = {
+	1,    // -50.00 dB
+	3,    // -40.00 dB
+	5,    // -35.00 dB
+	8,    // -30.00 dB
+	11,   // -27.50 dB
+	14,   // -25.00 dB
+	19,   // -22.50 dB
+	25,   // -20.00 dB
+	32,   // -18.00 dB
+	43,   // -15.50 dB
+	57,   // -13.00 dB
+	72,   // -11.00 dB
+	90,   // -9.00 dB
+	114,  // -7.00 dB
+	143,  // -5.00 dB
+	161,  // -4.00 dB
+	203   // -2.00 dB
+};
+
+static const int32 fxVolTable[17] = {
+	0,    // -100.00 dB
+	1,    // -50.00 dB
+	8,    // -30.00 dB
+	14,   // -25.00 dB
+	19,   // -22.50 dB
+	25,   // -20.00 dB
+	34,   // -17.50 dB
+	45,   // -15.00 dB
+	60,   // -12.50 dB
+	81,   // -10.00 dB
+	108,  // -7.50 dB
+	143,  // -5.00 dB
+	170,  // -3.50 dB
+	203,  // -2.00 dB
+	227,  // -1.00 dB
+	241,  // -0.50 dB
+	255   // 0 dB
+};
+
+static int32 getWindowsPanValue(int32 vol) {
+	// DirectSound handles pan values in decibel, just like volume and accepts
+	// values between -10000 (-100 dB, all to the left) and 10000 (100 dB, all to the right);
+	// more specifically, a negative value lowers the current volume of the right
+	// channel by that amount of decibels and viceversa for positive values.
+	//
+	// As we already know that any input value for this function is going to come
+	// exclusively from any of the volume tables (therefore the conversion from log
+	// to linear volumes is already performed), we can think of the range of possible
+	// target pan values in two parts:
+	// - The positive part (originally 0 to 10000 hundredths of dB) is the complementary part of the
+	//   input volume divided by 2 and multiplied by -1, so it fits the (0,127) part of the pan range;
+	// - The negative part (-10000 to 0 hundredths of dB) is the complementary part of the input volume 
+	//   multiplied by -1 and divided by 2, so it fits the (-127,0) part of the pan range.
+
+	if (vol > 0) {
+		return -((vol - 255) / 2);
+	} else if (vol < 0) {
+		return ((vol - 255) / 2);
+	}
+
+	return 0;
+}
+
 uint32 Sound::getSampleId(int32 fxNo) {
 	byte cluster = _fxList[fxNo].sampleId.cluster;
 	byte id;
@@ -730,13 +849,32 @@ void Sound::playSpeech() {
 		(byte *)_speechSample, _speechSize, 11025, SPEECH_FLAGS, DisposeAfterUse::NO);
 	_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_hSampleSpeech, stream);
 
-	byte speechVolume = clampVolume(2 * (4 * (_volSpeech[0] + _volSpeech[1])));
-	_mixer->setChannelVolume(_hSampleSpeech, speechVolume);
+	if (SwordEngine::_systemVars.useWindowsAudioMode) {
+		int32 vol = 0;
+		int32 pan = 0;
 
-	int pan = 64 + (4 * ((int32)_volSpeech[1] - (int32)_volSpeech[0]));
-	_mixer->setChannelBalance(_hSampleSpeech, scalePan(pan));
+		if (_volSpeech[0] < _volSpeech[1]) {		
+			vol = speechVolTable[_volSpeech[1]];
+			pan = getWindowsPanValue(-speechVolTable[(16 * _volSpeech[0]) / _volSpeech[1]]);
+		} else if (_volSpeech[0] > _volSpeech[1]) {
+			vol = speechVolTable[_volSpeech[0]];
+			pan = getWindowsPanValue(speechVolTable[(16 * _volSpeech[1]) / _volSpeech[0]]);
+		} else {
+			vol = speechVolTable[_volSpeech[1]];
+			pan = 0;
+		}
 
-	reduceMusicVolume();
+		_mixer->setChannelVolume(_hSampleSpeech, vol);
+		_mixer->setChannelBalance(_hSampleSpeech, pan);
+	} else {
+		byte speechVolume = clampVolume(2 * (4 * (_volSpeech[0] + _volSpeech[1])));
+		_mixer->setChannelVolume(_hSampleSpeech, speechVolume);
+
+		int pan = 64 + (4 * ((int32)_volSpeech[1] - (int32)_volSpeech[0]));
+		_mixer->setChannelBalance(_hSampleSpeech, scalePan(pan));
+
+		reduceMusicVolume();
+	}
 }
 
 void Sound::stopSpeech() {
@@ -745,7 +883,9 @@ void Sound::stopSpeech() {
 	if (_mixer->isSoundHandleActive(_hSampleSpeech)) {
 		_mixer->stopHandle(_hSampleSpeech);
 		_speechSampleBusy = false;
-		restoreMusicVolume();
+
+		if (!SwordEngine::_systemVars.useWindowsAudioMode)
+			restoreMusicVolume();
 	}
 }
 
@@ -786,11 +926,13 @@ static void soundCallback(void *refCon) {
 }
 
 void Sound::installFadeTimer() {
-	_vm->getTimerManager()->installTimerProc(&soundCallback, 1000000 / TIMER_RATE, this, "AILFadeTimer");
+	if (!SwordEngine::_systemVars.useWindowsAudioMode)
+		_vm->getTimerManager()->installTimerProc(&soundCallback, 1000000 / TIMER_RATE, this, "AILFadeTimer");
 }
 
 void Sound::uninstallFadeTimer() {
-	_vm->getTimerManager()->removeTimerProc(&soundCallback);
+	if (!SwordEngine::_systemVars.useWindowsAudioMode)
+		_vm->getTimerManager()->removeTimerProc(&soundCallback);
 }
 
 void Sound::setFXVolume(byte targetVolume, int handleIdx) {
@@ -958,18 +1100,31 @@ void Sound::streamMusicFile(int32 tuneId, int32 looped) {
 
 		_streamLoopingFlag[newHandleId] = looped;
 
-		_musicStreamFading[oldHandleId] = -12;
-		_musicStreamFading[newHandleId] = 1;
+		_musicStreamFading[oldHandleId] = SwordEngine::_systemVars.useWindowsAudioMode ? -16 : -12;
+		_musicStreamFading[newHandleId] = SwordEngine::_systemVars.useWindowsAudioMode ? 0 : 1;
 
 		if (_musicStreamPlaying[newHandleId]) {
 			// Whoops, they are BOTH busy! Let's kill the older one...
 			_mixer->stopHandle(_hSampleMusic[newHandleId]);
 			_musicFile[newHandleId].close();
 
-			bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId,
-												 2 * (2 * (_volMusic[0] + _volMusic[1])),
-												 scalePan(64 + (4 * (_volMusic[1] - _volMusic[0]))),
-												 assignedMode);
+			int32 vol = 2 * (2 * (_volMusic[0] + _volMusic[1]));
+			int32 pan = scalePan(64 + (4 * (_volMusic[1] - _volMusic[0])));
+
+			if (SwordEngine::_systemVars.useWindowsAudioMode) {
+				if (_volMusic[0] < _volMusic[1]) {
+					vol = musicVolTable[_volMusic[1]];
+					pan = getWindowsPanValue(-musicVolTable[16 * _volMusic[0] / _volMusic[1]]);
+				} else if (_volMusic[0] > _volMusic[1]) {
+					vol = musicVolTable[_volMusic[0]];
+					pan = getWindowsPanValue(musicVolTable[16 * _volMusic[1] / _volMusic[0]]);
+				} else {
+					vol = musicVolTable[_volMusic[1]];
+					pan = 0;
+				} 
+			}
+
+			bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId, vol, pan, assignedMode);
 
 			if (success)
 				debug(5, "Sound::streamMusicFile(): interrupting sound in handle %d to play %s", newHandleId, filename.c_str());
@@ -977,20 +1132,46 @@ void Sound::streamMusicFile(int32 tuneId, int32 looped) {
 			return;
 		} else {
 			// All good! We got the non-busy one :-)
-			bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId,
-												 0,
-												 scalePan(64 + (4 * (_volMusic[1] - _volMusic[0]))),
-												 assignedMode);
+			int32 vol = 0;
+			int32 pan = scalePan(64 + (4 * (_volMusic[1] - _volMusic[0])));
+
+			if (SwordEngine::_systemVars.useWindowsAudioMode) {
+				if (_volMusic[0] < _volMusic[1]) {
+					vol = musicVolTable[_volMusic[1]];
+				pan = getWindowsPanValue(-musicVolTable[16 * _volMusic[0] / _volMusic[1]]);
+				} else if (_volMusic[0] > _volMusic[1]) {
+					vol = musicVolTable[_volMusic[0]];
+					pan = getWindowsPanValue(musicVolTable[16 * _volMusic[1] / _volMusic[0]]);
+				} else {
+					vol = musicVolTable[_volMusic[1]];
+					pan = 0;
+				} 
+			}
+
+			bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId, vol, pan, assignedMode);
 
 			if (success)
 				debug(5, "Sound::streamMusicFile(): playing sound %s in handle %d with other handle busy", filename.c_str(), newHandleId);
 		}
 	} else {
 		// No streams are busy, let's go!
-		bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId,
-											 2 * (3 * (_volMusic[0] + _volMusic[1])),
-											 scalePan(64 + (4 * (_volMusic[1] - _volMusic[0]))),
-											 assignedMode);
+		int32 vol = 2 * (3 * (_volMusic[0] + _volMusic[1]));
+		int32 pan = scalePan(64 + (4 * (_volMusic[1] - _volMusic[0])));
+
+		if (SwordEngine::_systemVars.useWindowsAudioMode) {
+			if (_volMusic[0] < _volMusic[1]) {
+				vol = musicVolTable[_volMusic[1]];
+				pan = getWindowsPanValue(-musicVolTable[16 * _volMusic[0] / _volMusic[1]]);
+			} else if (_volMusic[0] > _volMusic[1]) {
+				vol = musicVolTable[_volMusic[0]];
+				pan = getWindowsPanValue(musicVolTable[16 * _volMusic[1] / _volMusic[0]]);
+			} else {
+				vol = musicVolTable[_volMusic[1]];
+				pan = 0;
+			} 
+		}
+
+		bool success = prepareMusicStreaming(Common::Path(filename), newHandleId, tuneId, vol, pan, assignedMode);
 
 		if (success)
 			debug(5, "Sound::streamMusicFile(): playing sound %s in handle %d", filename.c_str(), newHandleId);
@@ -1011,12 +1192,36 @@ void Sound::updateMusicStreaming() {
 					_crossFadeIncrement = false;
 
 					if (_musicStreamFading[i] < 0) {
-						debug("Sound::updateMusicStreaming(): Fading %s to %d", _musicFile[i].getName(),
-							2 * (((0 - _musicStreamFading[i]) * 3 * (_volMusic[0] + _volMusic[1])) / 16));
-						_mixer->setChannelVolume(_hSampleMusic[i],
-							clampVolume(2 * (((0 - _musicStreamFading[i]) * 3 * (_volMusic[0] + _volMusic[1])) / 16)));
-
-						_musicStreamFading[i] += 1;
+						if (!SwordEngine::_systemVars.useWindowsAudioMode) {
+							debug("Sound::updateMusicStreaming(): Fading %s to %d", _musicFile[i].getName(),
+								2 * (((0 - _musicStreamFading[i]) * 3 * (_volMusic[0] + _volMusic[1])) / 16));
+							_mixer->setChannelVolume(_hSampleMusic[i],
+								clampVolume(2 * (((0 - _musicStreamFading[i]) * 3 * (_volMusic[0] + _volMusic[1])) / 16)));
+
+							_musicStreamFading[i] += 1;
+						} else {
+							_musicStreamFading[i] += 1;
+
+							int32 vol, pan;
+							int32 volL = ((-_musicStreamFading[i]) * _volMusic[0]) >> 4;
+							int32 volR = ((-_musicStreamFading[i]) * _volMusic[1]) >> 4;
+
+							if (volL > volR) {
+								vol = musicVolTable[volL];
+								pan = musicVolTable[16 * volR / volL];
+							} else if (volR > volL) {
+								vol = musicVolTable[volR];
+								pan = -musicVolTable[16 * volL / volR];
+							} else {
+								vol = musicVolTable[volR];
+								pan = 0;
+							}
+
+							debug("Sound::updateMusicStreaming(): Fading %s to %d (pan %d)", _musicFile[i].getName(), vol, getWindowsPanValue(pan));
+							_mixer->setChannelVolume(_hSampleMusic[i], vol);
+							_mixer->setChannelBalance(_hSampleMusic[i], getWindowsPanValue(pan));
+						}
+						
 						if (_musicStreamFading[i] == 0) {
 							_mixer->setChannelVolume(_hSampleMusic[i], 0);
 							_musicOutputStream[i]->finish();
@@ -1027,7 +1232,7 @@ void Sound::updateMusicStreaming() {
 
 							_musicStreamPlaying[i] = false;
 						}
-					} else {
+					} else if (!SwordEngine::_systemVars.useWindowsAudioMode) { // The Windows driver doesn't fade-in
 						debug("Sound::updateMusicStreaming(): Fading %s to %d", _musicFile[i].getName(),
 							2 * ((_musicStreamFading[i] * 3 * (_volMusic[0] + _volMusic[1])) / 16));
 						_mixer->setChannelVolume(_hSampleMusic[i],
@@ -1147,12 +1352,34 @@ void Sound::playFX(int32 fxID, int32 type, uint8 *wavData, uint32 vol[2]) {
 			}
 
 			if (stream) {
-				v0 = _volFX[0] * vol[0];
-				v1 = _volFX[1] * vol[1];
-
 				_mixer->playStream(Audio::Mixer::kSFXSoundType, &_hSampleFX[i], stream, -1, 0);
-				_mixer->setChannelVolume(_hSampleFX[i], clampVolume(2 * ((v0 + v1) / 8)));
-				_mixer->setChannelBalance(_hSampleFX[i], scalePan(64 + ((v1 - v0) / 4)));
+
+				if (SwordEngine::_systemVars.useWindowsAudioMode) {
+					int32 leftVol  = (vol[0] * _volFX[0]) >> 4;
+					int32 rightVol = (vol[1] * _volFX[1]) >> 4;
+					int32 volume = 0;
+					int32 pan = 0;
+
+					if (leftVol > rightVol) {
+						volume = fxVolTable[leftVol];
+						pan = -fxVolTable[16 * rightVol / leftVol];
+					} else if (leftVol < rightVol) {
+						volume = fxVolTable[rightVol];
+						pan = fxVolTable[16 * leftVol / rightVol];
+					} else {
+						volume = fxVolTable[leftVol];
+						pan = 0;
+					}
+
+					_mixer->setChannelVolume(_hSampleFX[i], volume);
+					_mixer->setChannelBalance(_hSampleFX[i], getWindowsPanValue(pan));
+				} else {
+					v0 = _volFX[0] * vol[0];
+					v1 = _volFX[1] * vol[1];
+		
+					_mixer->setChannelVolume(_hSampleFX[i], clampVolume(2 * ((v0 + v1) / 8)));
+					_mixer->setChannelBalance(_hSampleFX[i], scalePan(64 + ((v1 - v0) / 4)));
+				}
 			}
 
 			return;
@@ -1188,7 +1415,7 @@ void Sound::clearAllFx() {
 
 void Sound::fadeMusicDown(int32 rate) {
 	Common::StackLock lock(_soundMutex);
-	_musicStreamFading[1 - _musicStreamPlaying[0]] = -12;
+	_musicStreamFading[1 - _musicStreamPlaying[0]] = SwordEngine::_systemVars.useWindowsAudioMode ? -16 : -12;
 }
 
 void Sound::fadeFxDown(int32 rate) {
diff --git a/engines/sword1/sword1.cpp b/engines/sword1/sword1.cpp
index a1099676492..eddfc521c47 100644
--- a/engines/sword1/sword1.cpp
+++ b/engines/sword1/sword1.cpp
@@ -165,6 +165,27 @@ Common::Error SwordEngine::init() {
 	_systemVars.framesPerSecondCounter = 0;
 	_systemVars.gameCycle = 0;
 
+	// Differences between the DOS and Windows audio drivers:
+	// * DOS:
+	//    - Apparently uses linear volume curves, as per the AIL/Miles drivers;
+	//    - Music can fade in and out;
+	//    - Fades sound effects in and out at each scene change;
+	//    - Lowers the volume of the music each time a speech line is playing;
+	// 
+	// * Windows:
+	//    - Uses DirectSound with volume tables for game volume -> decibel 
+	//      volume conversion; the resulting logarithmic curve and slightly
+	//      different volume scaling for sound effects ensures audibly
+	//      different intensities for some sounds;
+	//    - Music can only fade out, not in;
+	//    - Does NOT sound effects in and out at each scene change;
+	//    - Does NOT lower the volume of the music each time a speech line is playing;
+
+	ConfMan.registerDefault("windows_audio_mode", false);
+	if (ConfMan.hasKey("windows_audio_mode", _targetName)) {
+		_systemVars.useWindowsAudioMode = !(SwordEngine::isPsx() || SwordEngine::isMac()) && ConfMan.getBool("windows_audio_mode");
+	}
+
 	// Some Mac versions use big endian for the speech files but not all of them.
 	if (_systemVars.platform == Common::kPlatformMacintosh)
 		_sound->checkSpeechFileEndianness();
diff --git a/engines/sword1/sword1.h b/engines/sword1/sword1.h
index afff3474f50..55e9dda861a 100644
--- a/engines/sword1/sword1.h
+++ b/engines/sword1/sword1.h
@@ -89,6 +89,7 @@ struct SystemVars {
 	bool             displayDebugGrid;
 	uint32           framesPerSecondCounter;
 	uint32           gameCycle;
+	bool             useWindowsAudioMode; // DOS and Windows use different implementations of the audio driver, each with their own behavior
 };
 
 class SwordEngine : public Engine {




More information about the Scummvm-git-logs mailing list