[Scummvm-git-logs] scummvm branch-2-9 -> 0338ceb15e3011ac7bfa16597c09cf41967da89f
sluicebox
noreply at scummvm.org
Fri Dec 6 09:48:34 UTC 2024
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
7debbba6ae SCI32: Implement GK1 CD DPCM8 speech audio repair
7e04e05ebb SCI32: Update GK1 CD speech repair config check
0338ceb15e NEWS: Update SCI news
Commit: 7debbba6aec74d6924593088e6c95eba9a7db005
https://github.com/scummvm/scummvm/commit/7debbba6aec74d6924593088e6c95eba9a7db005
Author: AllTinker (49541155+TheAllTinker at users.noreply.github.com)
Date: 2024-12-06T01:45:42-08:00
Commit Message:
SCI32: Implement GK1 CD DPCM8 speech audio repair
Changed paths:
engines/sci/detection.h
engines/sci/detection_options.h
engines/sci/detection_tables.h
engines/sci/sound/decoders/sol.cpp
engines/sci/sound/decoders/sol.h
diff --git a/engines/sci/detection.h b/engines/sci/detection.h
index ae5df995f24..6652ee28da7 100644
--- a/engines/sci/detection.h
+++ b/engines/sci/detection.h
@@ -44,6 +44,7 @@ namespace Sci {
#define GAMEOPTION_SQ1_BEARDED_MUSICIANS GUIO_GAMEOPTIONS16
#define GAMEOPTION_TTS GUIO_GAMEOPTIONS17
#define GAMEOPTION_ENABLE_GMM_SAVE GUIO_GAMEOPTIONS18
+#define GAMEOPTION_GK1_ENABLE_AUDIO_POPFIX GUIO_GAMEOPTIONS19
enum SciGameId {
GID_ALL,
diff --git a/engines/sci/detection_options.h b/engines/sci/detection_options.h
index 9bcadf2c4f0..adccff49587 100644
--- a/engines/sci/detection_options.h
+++ b/engines/sci/detection_options.h
@@ -242,6 +242,18 @@ const ADExtraGuiOptionsMap optionsList[] = {
}
},
+ {
+ GAMEOPTION_GK1_ENABLE_AUDIO_POPFIX,
+ {
+ _s("Repair speech audio"),
+ _s("Detect and attempt to repair overflows in DPCM8 audio, which cause noticeable pops and crackles."),
+ "audio_popfix_enabled",
+ true,
+ 0,
+ 0
+ }
+ },
+
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index 78f68c50861..c199a96890d 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -873,15 +873,17 @@ static const struct ADGameDescription SciGameDescriptions[] = {
GAMEOPTION_ORIGINAL_SAVELOAD, \
GAMEOPTION_TTS, \
GAMEOPTION_ENABLE_GMM_SAVE)
-#define GUIO_GK1_CD_DOS GUIO5(GUIO_LINKSPEECHTOSFX, \
+#define GUIO_GK1_CD_DOS GUIO6(GUIO_LINKSPEECHTOSFX, \
GAMEOPTION_ORIGINAL_SAVELOAD, \
GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \
GAMEOPTION_HQ_VIDEO, \
- GAMEOPTION_ENABLE_GMM_SAVE)
-#define GUIO_GK1_CD_WIN GUIO4(GUIO_LINKSPEECHTOSFX, \
+ GAMEOPTION_ENABLE_GMM_SAVE, \
+ GAMEOPTION_GK1_ENABLE_AUDIO_POPFIX)
+#define GUIO_GK1_CD_WIN GUIO5(GUIO_LINKSPEECHTOSFX, \
GAMEOPTION_ORIGINAL_SAVELOAD, \
GAMEOPTION_HQ_VIDEO, \
- GAMEOPTION_ENABLE_GMM_SAVE)
+ GAMEOPTION_ENABLE_GMM_SAVE, \
+ GAMEOPTION_GK1_ENABLE_AUDIO_POPFIX)
#define GUIO_GK1_MAC GUIO3(GUIO_NOSPEECH, \
GAMEOPTION_TTS, \
GAMEOPTION_ENABLE_GMM_SAVE)
diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp
index 8dcf670e9ae..1dfd5b4c2f6 100644
--- a/engines/sci/sound/decoders/sol.cpp
+++ b/engines/sci/sound/decoders/sol.cpp
@@ -24,6 +24,7 @@
#include "audio/decoders/raw.h"
#include "common/substream.h"
#include "common/util.h"
+#include "common/config-manager.h"
#include "sci/sci.h"
#include "sci/sound/decoders/sol.h"
#include "sci/resource/resource.h"
@@ -47,7 +48,14 @@ static const uint16 tableDPCM16[128] = {
0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
};
-static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 };
+// Each 4-bit nibble indexes into this table to refer to one of the 16 delta values.
+// deDPCM8Nibble() currently uses the first 8 values, with logic to order the negative
+// deltas differently depending on the exact encoding used.
+// deDPCM8NibbleWithRepair() uses the whole table, since it matches the order of "old"
+// encoding as-is. This saves a tiny bit of computation in the more complex function.
+static const int8 tableDPCM8[16] = {
+ 0, 1, 2, 3, 6, 10, 15, 21, -21, -15, -10, -6, -3, -2, -1, -0
+};
/**
* Decompresses one channel of 16-bit DPCM compressed audio.
@@ -112,16 +120,118 @@ static void deDPCM8Nibble(int16 *out, uint8 &sample, uint8 delta) {
*out = ((lastSample + sample) << 7) ^ 0x8000;
}
+/**
+ * Decompresses one half of an 8-bit DPCM compressed audio
+ * byte. Attempts to repair overflows on the fly.
+ */
+static void deDPCM8NibbleWithRepair(int16 *const out, uint8 &sample, const uint8 delta,
+ uint8 &repairState, uint8 &preRepairSample) {
+ const uint8 lastSample = sample;
+
+ // In Gabriel Knight: Sins of the Fathers CD, the DPCM8-encoded speech contains overflows.
+ // Overflows wrap from positive to negative, or negative to positive. This continues
+ // until the wave settles at a new (wrapped) zero DC offset.
+
+ // We can't look ahead to see where the wave "reconnects" to valid data, so we
+ // decay the wave on an artificial slope until it does. This seems to take on average
+ // around 9-10 samples. The slope value below was chosen through analysing spectrographs
+ // of the decoded/repaired wave data to find the slope which removed the pops most
+ // cleanly across a test selection of game speech.
+
+#define REPAIR_SLOPE 12
+
+ switch (repairState) {
+ case 0: {
+ const int16 newSampleOverflow = (int16)sample + tableDPCM8[delta & 15];
+
+ if (newSampleOverflow > 255) {
+ // Positive overflow has occurred; begin artificial negative slope.
+ repairState = 1;
+ sample = lastSample - REPAIR_SLOPE;
+ // We also begin tracking the un-repaired waveform, so we can tell when to stop.
+ preRepairSample = (uint8)newSampleOverflow;
+
+ debugC(1, kDebugLevelSound, "DPCM8 OVERFLOW (+)");
+
+ } else if (newSampleOverflow < 0) {
+ // Negative overflow has occurred; begin artificial positive slope.
+ repairState = 2;
+ sample = lastSample + REPAIR_SLOPE;
+ // We also begin tracking the un-repaired waveform, so we can tell when to stop.
+ preRepairSample = (uint8)newSampleOverflow;
+
+ debugC(1, kDebugLevelSound, "DPCM8 OVERFLOW (-)");
+
+ } else {
+ sample = (uint8)newSampleOverflow;
+ }
+ } break;
+ case 1: {
+ // Check for a slope wrap. This circumstance should never happen in reality;
+ // the unrepaired wave would somehow need to be stuck near minimum
+ // value over the entire course of the slope.
+ if (lastSample < REPAIR_SLOPE)
+ warning("Negative slope wrap!");
+
+ const uint8 slopeSample = lastSample - REPAIR_SLOPE;
+ preRepairSample += tableDPCM8[delta & 15];
+
+ // Stop the repair if the artificial slope has intersected with real data.
+ if (preRepairSample >= slopeSample) {
+ // Return to real data.
+ repairState = 0;
+ sample = preRepairSample;
+ } else {
+ sample = slopeSample;
+ }
+ } break;
+ case 2: {
+ // Check for a slope wrap. This circumstance should never happen in reality;
+ // the unrepaired wave would somehow need to be stuck near maximum
+ // value over the entire course of the slope.
+ if (lastSample > (255 - REPAIR_SLOPE))
+ warning("Positive slope wrap!");
+
+ const uint8 slopeSample = lastSample + REPAIR_SLOPE;
+ preRepairSample += tableDPCM8[delta & 15];
+
+ // Stop the repair if the artificial slope has intersected with real data.
+ if (preRepairSample <= slopeSample) {
+ // Return to real data.
+ repairState = 0;
+ sample = preRepairSample;
+ } else {
+ sample = slopeSample;
+ }
+ } break;
+ default:
+ warning("Invalid repair state!");
+ repairState = 0;
+ break;
+ }
+
+ *out = ((lastSample + sample) << 7) ^ 0x8000;
+}
+
/**
* Decompresses 8-bit DPCM compressed audio. Each byte read
* outputs two samples into the decompression buffer.
*/
template <bool OLD>
-static void deDPCM8Mono(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, uint8 &sample) {
- for (uint32 i = 0; i < numBytes; ++i) {
- const uint8 delta = audioStream.readByte();
- deDPCM8Nibble<OLD>(out++, sample, delta >> 4);
- deDPCM8Nibble<OLD>(out++, sample, delta & 0xf);
+static void deDPCM8Mono(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, uint8 &sample,
+ const bool popfixEnabled, uint8 &repairState, uint8 &preRepairSample) {
+ if (popfixEnabled) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ deDPCM8NibbleWithRepair(out++, sample, delta >> 4, repairState, preRepairSample);
+ deDPCM8NibbleWithRepair(out++, sample, delta & 0xf, repairState, preRepairSample);
+ }
+ } else {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ deDPCM8Nibble<OLD>(out++, sample, delta >> 4);
+ deDPCM8Nibble<OLD>(out++, sample, delta & 0xf);
+ }
}
}
@@ -140,7 +250,9 @@ SOLStream<STEREO, S16BIT, OLDDPCM8>::SOLStream(Common::SeekableReadStream *strea
_stream(stream, disposeAfterUse),
_sampleRate(sampleRate),
// SSCI aligns the size of SOL data to 32 bits
- _rawDataSize(rawDataSize & ~3) {
+ _rawDataSize(rawDataSize & ~3),
+ // The pop fix is only verified with (relevant to?) "old" DPCM8, so we enforce that here.
+ _popfixDPCM8(ConfMan.getBool("audio_popfix_enabled") && OLDDPCM8) {
if (S16BIT) {
_dpcmCarry16.l = _dpcmCarry16.r = 0;
} else {
@@ -201,7 +313,8 @@ int SOLStream<STEREO, S16BIT, OLDDPCM8>::readBuffer(int16 *buffer, const int num
if (STEREO) {
deDPCM8Stereo(buffer, *_stream, bytesToRead, _dpcmCarry8.l, _dpcmCarry8.r);
} else {
- deDPCM8Mono<OLDDPCM8>(buffer, *_stream, bytesToRead, _dpcmCarry8.l);
+ deDPCM8Mono<OLDDPCM8>(buffer, *_stream, bytesToRead, _dpcmCarry8.l,
+ _popfixDPCM8.enabled, _popfixDPCM8.state, _popfixDPCM8.preRepairSample);
}
}
diff --git a/engines/sci/sound/decoders/sol.h b/engines/sci/sound/decoders/sol.h
index 1542ed1dba5..9997b64e666 100644
--- a/engines/sci/sound/decoders/sol.h
+++ b/engines/sci/sound/decoders/sol.h
@@ -51,6 +51,21 @@ private:
*/
int32 _rawDataSize;
+ /**
+ * DPCM8 pop (overflow) fix working data:
+ * - Whether or not the fix is enabled, set once on construction.
+ * - The current state of the repair (0 = inactive, 1 = positive, 2 = negative).
+ * - The sample state without repair, to detect where repairing should stop.
+ */
+ struct PopFixData {
+ const bool enabled;
+ uint8 state;
+ uint8 preRepairSample;
+
+ PopFixData(const bool e):
+ enabled(e), state(0), preRepairSample(0) {}
+ } _popfixDPCM8;
+
/**
* The last sample from the previous DPCM decode.
*/
Commit: 7e04e05ebbf89e27d39c320649178100334096dc
https://github.com/scummvm/scummvm/commit/7e04e05ebbf89e27d39c320649178100334096dc
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-12-06T01:45:50-08:00
Commit Message:
SCI32: Update GK1 CD speech repair config check
Ensures this option is only applied to the intended detection entries
Changed paths:
engines/sci/engine/features.cpp
engines/sci/engine/features.h
engines/sci/sound/decoders/sol.cpp
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp
index 90b7e8a04fb..68f2dd9ec02 100644
--- a/engines/sci/engine/features.cpp
+++ b/engines/sci/engine/features.cpp
@@ -27,6 +27,7 @@
#include "common/config-manager.h"
#include "common/file.h"
+#include "common/gui_options.h"
namespace Sci {
@@ -46,6 +47,8 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan)
_forceDOSTracks = false;
_useWindowsCursors = ConfMan.getBool("windows_cursors");
_pseudoMouseAbility = kPseudoMouseAbilityUninitialized;
+ _useAudioPopfix = Common::checkGameGUIOption(GAMEOPTION_GK1_ENABLE_AUDIO_POPFIX, ConfMan.get("guioptions")) &&
+ ConfMan.getBool("audio_popfix_enabled");
}
reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc, int methodNum) {
diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h
index 730117817ce..3bc976d118f 100644
--- a/engines/sci/engine/features.h
+++ b/engines/sci/engine/features.h
@@ -272,6 +272,8 @@ public:
*/
PseudoMouseAbilityType detectPseudoMouseAbility();
+ bool useAudioPopfix() const { return _useAudioPopfix; }
+
bool useEarlyGetLongestTextCalculations() const;
/**
@@ -327,6 +329,8 @@ private:
PseudoMouseAbilityType _pseudoMouseAbility;
+ bool _useAudioPopfix;
+
SegManager *_segMan;
Kernel *_kernel;
};
diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp
index 1dfd5b4c2f6..a02f199fc06 100644
--- a/engines/sci/sound/decoders/sol.cpp
+++ b/engines/sci/sound/decoders/sol.cpp
@@ -24,8 +24,8 @@
#include "audio/decoders/raw.h"
#include "common/substream.h"
#include "common/util.h"
-#include "common/config-manager.h"
#include "sci/sci.h"
+#include "sci/engine/features.h"
#include "sci/sound/decoders/sol.h"
#include "sci/resource/resource.h"
@@ -252,7 +252,7 @@ SOLStream<STEREO, S16BIT, OLDDPCM8>::SOLStream(Common::SeekableReadStream *strea
// SSCI aligns the size of SOL data to 32 bits
_rawDataSize(rawDataSize & ~3),
// The pop fix is only verified with (relevant to?) "old" DPCM8, so we enforce that here.
- _popfixDPCM8(ConfMan.getBool("audio_popfix_enabled") && OLDDPCM8) {
+ _popfixDPCM8(g_sci->_features->useAudioPopfix() && OLDDPCM8) {
if (S16BIT) {
_dpcmCarry16.l = _dpcmCarry16.r = 0;
} else {
Commit: 0338ceb15e3011ac7bfa16597c09cf41967da89f
https://github.com/scummvm/scummvm/commit/0338ceb15e3011ac7bfa16597c09cf41967da89f
Author: sluicebox (22204938+sluicebox at users.noreply.github.com)
Date: 2024-12-06T01:45:58-08:00
Commit Message:
NEWS: Update SCI news
Changed paths:
NEWS.md
diff --git a/NEWS.md b/NEWS.md
index 80610b8d173..2312bc97ad6 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -186,6 +186,8 @@ For a more comprehensive changelog of the latest experimental code, see:
SCI 0 DOS games. Also added an EGA dithering mode and a VGA gray scale
mode for many SCI 1 DOS games, a 16 colors mode for KQ6 Windows and
8 colors modes for all PC-98 games.
+ - Added Gabriel Knight 1 CD speech repair by AllTinker.
+ Fixes the majority of pops and clicks in the DPCM8 speech audio.
- Improved PCjr audio.
- Improved KQ6 CD settings. The DOS platform now defaults to DOS behavior.
- Better support for Mac KQ6.
More information about the Scummvm-git-logs
mailing list