aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/engine/kernel.h20
-rw-r--r--engines/sci/engine/kernel_tables.h68
-rw-r--r--engines/sci/engine/ksound.cpp169
-rw-r--r--engines/sci/module.mk2
-rw-r--r--engines/sci/resource.cpp19
-rw-r--r--engines/sci/resource.h13
-rw-r--r--engines/sci/resource_audio.cpp2
-rw-r--r--engines/sci/sci.cpp17
-rw-r--r--engines/sci/sci.h2
-rw-r--r--engines/sci/sound/audio.cpp8
-rw-r--r--engines/sci/sound/audio32.cpp961
-rw-r--r--engines/sci/sound/audio32.h566
-rw-r--r--engines/sci/sound/decoders/sol.cpp272
-rw-r--r--engines/sci/sound/decoders/sol.h89
-rw-r--r--engines/sci/sound/music.cpp74
-rw-r--r--engines/sci/sound/music.h4
-rw-r--r--engines/sci/sound/soundcmd.cpp104
-rw-r--r--engines/sci/sound/sync.cpp1
-rw-r--r--engines/sci/sound/sync.h6
19 files changed, 2341 insertions, 56 deletions
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 62566a74b2..d88dc75dab 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -420,6 +420,26 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv);
#ifdef ENABLE_SCI32
// SCI2 Kernel Functions
+reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioDistort(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv);
+reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv);
+
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
reg_t kArray(EngineState *s, int argc, reg_t *argv);
reg_t kListAt(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index 0ede307e6b..a61213edb7 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -168,7 +168,7 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
// signature for SCI21 should be "o"
{ SIG_SOUNDSCI21, 9, MAP_CALL(DoSoundStop), NULL, NULL },
{ SIG_SOUNDSCI21, 10, MAP_CALL(DoSoundPause), NULL, NULL },
- { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), NULL, kDoSoundFade_workarounds },
+ { SIG_SOUNDSCI21, 11, MAP_CALL(DoSoundFade), "oiiii", kDoSoundFade_workarounds },
{ SIG_SOUNDSCI21, 12, MAP_CALL(DoSoundSetHold), NULL, NULL },
{ SIG_SOUNDSCI21, 13, MAP_CALL(DoSoundDummy), NULL, NULL },
{ SIG_SOUNDSCI21, 14, MAP_CALL(DoSoundSetVolume), NULL, NULL },
@@ -182,6 +182,67 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
SCI_SUBOPENTRY_TERMINATOR
};
+#ifdef ENABLE_SCI32
+// NOTE: In SSCI, some 'unused' kDoAudio subops are actually
+// called indirectly by kDoSound:
+//
+// kDoSoundGetAudioCapability -> kDoAudioGetCapability
+// kDoSoundPlay -> kDoAudioPlay, kDoAudioStop
+// kDoSoundPause -> kDoAudioPause, kDoAudioResume
+// kDoSoundFade -> kDoAudioFade
+// kDoSoundSetVolume -> kDoAudioVolume
+// kDoSoundSetLoop -> kDoAudioSetLoop
+// kDoSoundUpdateCues -> kDoAudioPosition
+//
+// In ScummVM, logic inside these kernel functions has been
+// moved to methods of Audio32, and direct calls to Audio32
+// are made from kDoSound instead.
+//
+// Some kDoAudio methods are esoteric and appear to be used
+// only by one or two games:
+//
+// kDoAudioMixing: Phantasmagoria (other games call this
+// function, but only to disable the feature)
+// kDoAudioHasSignal: SQ6 TalkRandCycle
+// kDoAudioPan: Rama RegionSFX::pan method
+//
+// Finally, there is a split in SCI2.1mid audio code.
+// QFG4CD & SQ6 do not have opcodes 18 and 19, but they
+// exist in GK2, KQ7 2.00b, Phantasmagoria 1, PQ:SWAT, and
+// Torin. (It is unknown if they exist in MUMG Deluxe or
+// Shivers 1; they are not used in either of these games.)
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kDoAudio_subops[] = {
+ { SIG_SCI32, 0, MAP_CALL(DoAudioInit), "", NULL },
+ // SCI2 includes a Sync script that would call
+ // kDoAudioWaitForPlay, but SSCI has no opcode 1 until
+ // SCI2.1early
+ { SIG_SINCE_SCI21, 1, MAP_CALL(DoAudioWaitForPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(DoAudioPlay), "(i)(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(DoAudioStop), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 4, MAP_CALL(DoAudioPause), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 5, MAP_CALL(DoAudioResume), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 6, MAP_CALL(DoAudioPosition), "(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 7, MAP_CALL(DoAudioRate), "(i)", NULL },
+ { SIG_SCI32, 8, MAP_CALL(DoAudioVolume), "(i)(i)(i)(i)(i)(i)", NULL },
+ { SIG_SCI32, 9, MAP_CALL(DoAudioGetCapability), "", NULL },
+ { SIG_SCI32, 10, MAP_CALL(DoAudioBitDepth), "(i)", NULL },
+ { SIG_SCI32, 11, MAP_DUMMY(DoAudioDistort), "(i)", NULL },
+ { SIG_SCI32, 12, MAP_CALL(DoAudioMixing), "(i)", NULL },
+ { SIG_SCI32, 13, MAP_CALL(DoAudioChannels), "(i)", NULL },
+ { SIG_SCI32, 14, MAP_CALL(DoAudioPreload), "(i)", NULL },
+ { SIG_SINCE_SCI21MID, 15, MAP_CALL(DoAudioFade), "(iiii)(i)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 16, MAP_DUMMY(DoAudioFade36), "iiiii(iii)(i)", NULL },
+ { SIG_SINCE_SCI21MID, 17, MAP_CALL(DoAudioHasSignal), "", NULL },
+ { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "", NULL },
+ { SIG_SINCE_SCI21MID, 19, MAP_CALL(DoAudioSetLoop), "iii(o)", NULL },
+ { SIG_SCI3, 20, MAP_DUMMY(DoAudioPan), "", NULL },
+ { SIG_SCI3, 21, MAP_DUMMY(DoAudioPanOff), "", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+#endif
+
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kGraph_subops[] = {
{ SIG_SCI32, 1, MAP_CALL(StubNull), "", NULL }, // called by gk1 sci32 right at the start
@@ -484,7 +545,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(DisposeList), SIG_EVERYWHERE, "l", NULL, NULL },
{ MAP_CALL(DisposeScript), SIG_EVERYWHERE, "i(i*)", NULL, kDisposeScript_workarounds },
{ MAP_CALL(DisposeWindow), SIG_EVERYWHERE, "i(i)", NULL, NULL },
- { MAP_CALL(DoAudio), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop
+ { MAP_CALL(DoAudio), SCI_VERSION_NONE, SCI_VERSION_2, SIGFOR_ALL, "i(.*)", NULL, NULL }, // subop
+#ifdef ENABLE_SCI32
+ { "DoAudio", kDoAudio32, SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kDoAudio_subops, NULL },
+#endif
{ MAP_CALL(DoAvoider), SIG_EVERYWHERE, "o(i)", NULL, NULL },
{ MAP_CALL(DoBresen), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(DoSound), SIG_EVERYWHERE, "i(.*)", kDoSound_subops, NULL },
diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp
index 2b22d6867b..301e4c1b08 100644
--- a/engines/sci/engine/ksound.cpp
+++ b/engines/sci/engine/ksound.cpp
@@ -26,6 +26,9 @@
#include "sci/engine/kernel.h"
#include "sci/engine/vm.h" // for Object
#include "sci/sound/audio.h"
+#ifdef ENABLE_SCI32
+#include "sci/sound/audio32.h"
+#endif
#include "sci/sound/soundcmd.h"
#include "sci/sound/sync.h"
@@ -114,7 +117,8 @@ reg_t kDoCdAudio(EngineState *s, int argc, reg_t *argv) {
}
/**
- * Used for speech playback and digital soundtracks in CD games
+ * Used for speech playback and digital soundtracks in CD games.
+ * This is the SCI16 version; SCI32 is handled separately.
*/
reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
// JonesCD uses different functions based on the cdaudio.map file
@@ -185,14 +189,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
int16 volume = argv[1].toUint16();
volume = CLIP<int16>(volume, 0, AUDIO_VOLUME_MAX);
debugC(kDebugLevelSound, "kDoAudio: set volume to %d", volume);
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- int16 volumePrev = mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2;
- volumePrev = CLIP<int16>(volumePrev, 0, AUDIO_VOLUME_MAX);
- mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
- return make_reg(0, volumePrev);
- } else
-#endif
mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume * 2);
break;
}
@@ -233,12 +229,6 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) {
if (getSciVersion() <= SCI_VERSION_1_1) {
debugC(kDebugLevelSound, "kDoAudio: CD audio subop");
return kDoCdAudio(s, argc - 1, argv + 1);
-#ifdef ENABLE_SCI32
- } else {
- // TODO: This isn't CD Audio in SCI32 anymore
- warning("kDoAudio: Unhandled case 10, %d extra arguments passed", argc - 1);
- break;
-#endif
}
// 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C
@@ -320,6 +310,155 @@ reg_t kDoSync(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
+reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, 0);
+}
+
+reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_audio32->kernelPlay(false, argc, argv);
+}
+
+reg_t kDoAudioPlay(EngineState *s, int argc, reg_t *argv) {
+ return g_sci->_audio32->kernelPlay(true, argc, argv);
+}
+
+reg_t kDoAudioStop(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->stop(channelIndex));
+}
+
+reg_t kDoAudioPause(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->pause(channelIndex));
+}
+
+reg_t kDoAudioResume(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->resume(channelIndex));
+}
+
+reg_t kDoAudioPosition(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc > 1 ? argv[1] : NULL_REG);
+ return make_reg(0, g_sci->_audio32->getPosition(channelIndex));
+}
+
+reg_t kDoAudioRate(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP sampling rate; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const uint16 sampleRate = argv[0].toUint16();
+ if (sampleRate != 0) {
+ g_sci->_audio32->setSampleRate(sampleRate);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getSampleRate());
+}
+
+reg_t kDoAudioVolume(EngineState *s, int argc, reg_t *argv) {
+ const int16 volume = argc > 0 ? argv[0].toSint16() : -1;
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 1, argc > 2 ? argv[2] : NULL_REG);
+
+ if (volume != -1) {
+ g_sci->_audio32->setVolume(channelIndex, volume);
+ }
+
+ return make_reg(0, g_sci->_audio32->getVolume(channelIndex));
+}
+
+reg_t kDoAudioGetCapability(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, 1);
+}
+
+reg_t kDoAudioBitDepth(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP bit depth; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const uint16 bitDepth = argv[0].toUint16();
+ if (bitDepth != 0) {
+ g_sci->_audio32->setBitDepth(bitDepth);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getBitDepth());
+}
+
+reg_t kDoAudioMixing(EngineState *s, int argc, reg_t *argv) {
+ if (argc > 0) {
+ g_sci->_audio32->setAttenuatedMixing(argv[0].toUint16());
+ }
+
+ return make_reg(0, g_sci->_audio32->getAttenuatedMixing());
+}
+
+reg_t kDoAudioChannels(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would set the hardware
+ // DSP stereo output; ScummVM mixer does not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ const int16 numChannels = argv[0].toSint16();
+ if (numChannels != 0) {
+ g_sci->_audio32->setNumOutputChannels(numChannels);
+ }
+ }
+
+ return make_reg(0, g_sci->_audio32->getNumOutputChannels());
+}
+
+reg_t kDoAudioPreload(EngineState *s, int argc, reg_t *argv) {
+ // NOTE: In the original engine this would cause audio
+ // data for new channels to be preloaded to memory when
+ // the channel was initialized; we do not need this, so
+ // we only store the value to satisfy engine compatibility.
+
+ if (argc > 0) {
+ g_sci->_audio32->setPreload(argv[0].toUint16());
+ }
+
+ return make_reg(0, g_sci->_audio32->getPreload());
+}
+
+reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv) {
+ if (argc < 4) {
+ return make_reg(0, 0);
+ }
+
+ // NOTE: Sierra did a nightmarish hack here, temporarily replacing
+ // the argc of the kernel arguments with 2 and then restoring it
+ // after findChannelByArgs was called.
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(2, argv, 0, argc > 5 ? argv[5] : NULL_REG);
+
+ const int16 volume = argv[1].toSint16();
+ const int16 speed = argv[2].toSint16();
+ const int16 steps = argv[3].toSint16();
+ const bool stopAfterFade = argc > 4 ? (bool)argv[4].toUint16() : false;
+
+ return make_reg(0, g_sci->_audio32->fadeChannel(channelIndex, volume, speed, steps, stopAfterFade));
+}
+
+reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_audio32->hasSignal());
+}
+
+reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv) {
+ const int16 channelIndex = g_sci->_audio32->findChannelByArgs(argc, argv, 0, argc == 3 ? argv[2] : NULL_REG);
+
+ const bool loop = argv[0].toSint16() != 0 && argv[0].toSint16() != 1;
+
+ g_sci->_audio32->setLoop(channelIndex, loop);
+ return s->r_acc;
+}
reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv) {
// This is used by script 90 of MUMG Deluxe from the main menu to toggle
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index c35d0b51dd..69ddf00183 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -91,6 +91,8 @@ MODULE_OBJS += \
graphics/palette32.o \
graphics/screen_item32.o \
graphics/text32.o \
+ sound/audio32.o \
+ sound/decoders/sol.o \
video/robot_decoder.o
endif
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 9f977aaefd..2a83a57227 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -26,6 +26,9 @@
#include "common/fs.h"
#include "common/macresman.h"
#include "common/textconsole.h"
+#ifdef ENABLE_SCI32
+#include "common/memstream.h"
+#endif
#include "sci/resource.h"
#include "sci/resource_intern.h"
@@ -221,6 +224,12 @@ void Resource::writeToStream(Common::WriteStream *stream) const {
stream->write(data, size);
}
+#ifdef ENABLE_SCI32
+Common::SeekableReadStream *Resource::makeStream() const {
+ return new Common::MemoryReadStream(data, size, DisposeAfterUse::NO);
+}
+#endif
+
uint32 Resource::getAudioCompressionType() const {
return _source->getAudioCompressionType();
}
@@ -229,7 +238,6 @@ uint32 AudioVolumeResourceSource::getAudioCompressionType() const {
return _audioCompressionType;
}
-
ResourceSource::ResourceSource(ResSourceType type, const Common::String &name, int volNum, const Common::FSNode *resFile)
: _sourceType(type), _name(name), _volumeNumber(volNum), _resourceFile(resFile) {
_scanned = false;
@@ -1445,13 +1453,18 @@ void ResourceManager::readResourcePatchesBase36() {
files.clear();
// audio36 resources start with a @, A, or B
- // sync36 resources start with a #
+ // sync36 resources start with a #, S, or T
if (i == kResourceTypeAudio36) {
SearchMan.listMatchingMembers(files, "@???????.???");
SearchMan.listMatchingMembers(files, "A???????.???");
SearchMan.listMatchingMembers(files, "B???????.???");
- } else
+ } else {
SearchMan.listMatchingMembers(files, "#???????.???");
+#ifdef ENABLE_SCI32
+ SearchMan.listMatchingMembers(files, "S???????.???");
+ SearchMan.listMatchingMembers(files, "T???????.???");
+#endif
+ }
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
name = (*x)->getName();
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index ef474d97c2..f70bf48bd4 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -84,7 +84,10 @@ enum ResourceType {
kResourceTypePatch,
kResourceTypeBitmap,
kResourceTypePalette,
- kResourceTypeCdAudio,
+ kResourceTypeCdAudio = 12,
+#ifdef ENABLE_SCI32
+ kResourceTypeWave = 12,
+#endif
kResourceTypeAudio,
kResourceTypeSync,
kResourceTypeMessage,
@@ -212,6 +215,10 @@ public:
return (_type == other._type) && (_number == other._number) && (_tuple == other._tuple);
}
+ bool operator!=(const ResourceId &other) const {
+ return !operator==(other);
+ }
+
bool operator<(const ResourceId &other) const {
return (_type < other._type) || ((_type == other._type) && (_number < other._number))
|| ((_type == other._type) && (_number == other._number) && (_tuple < other._tuple));
@@ -259,6 +266,10 @@ public:
*/
void writeToStream(Common::WriteStream *stream) const;
+#ifdef ENABLE_SCI32
+ Common::SeekableReadStream *makeStream() const;
+#endif
+
const Common::String &getResourceLocation() const;
// FIXME: This audio specific method is a hack. After all, why should a
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 5717a09121..5aeff81b77 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -25,7 +25,7 @@
#include "common/archive.h"
#include "common/file.h"
#include "common/textconsole.h"
-
+#include "common/memstream.h"
#include "sci/resource.h"
#include "sci/resource_intern.h"
#include "sci/util.h"
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 8c23d322e7..6244c43764 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -68,6 +68,7 @@
#include "sci/graphics/palette32.h"
#include "sci/graphics/text32.h"
#include "sci/graphics/frameout.h"
+#include "sci/sound/audio32.h"
#include "sci/video/robot_decoder.h"
#endif
@@ -88,6 +89,9 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
_audio = 0;
_sync = nullptr;
+#ifdef ENABLE_SCI32
+ _audio32 = nullptr;
+#endif
_features = 0;
_resMan = 0;
_gamestate = 0;
@@ -167,6 +171,7 @@ SciEngine::~SciEngine() {
delete _robotDecoder;
delete _gfxFrameout;
delete _gfxRemap32;
+ delete _audio32;
#endif
delete _gfxMenu;
delete _gfxControls16;
@@ -269,7 +274,13 @@ Common::Error SciEngine::run() {
// Also, XMAS1990 apparently had a parser too. Refer to http://forums.scummvm.org/viewtopic.php?t=9135
if (getGameId() == GID_CHRISTMAS1990)
_vocabulary = new Vocabulary(_resMan, false);
- _audio = new AudioPlayer(_resMan);
+
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
+ _audio32 = new Audio32(_resMan);
+ } else
+#endif
+ _audio = new AudioPlayer(_resMan);
_sync = new Sync(_resMan, segMan);
_gamestate = new EngineState(segMan);
_eventMan = new EventManager(_resMan->detectFontExtended());
@@ -805,7 +816,9 @@ void SciEngine::runGame() {
void SciEngine::exitGame() {
if (_gamestate->abortScriptProcessing != kAbortLoadGame) {
_gamestate->_executionStack.clear();
- _audio->stopAllAudio();
+ if (_audio) {
+ _audio->stopAllAudio();
+ }
_sync->stop();
_soundCmd->clearPlayList();
}
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index ff414727fa..56ee2f4403 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -83,6 +83,7 @@ class GfxTransitions;
#ifdef ENABLE_SCI32
class RobotDecoder;
class GfxFrameout;
+class Audio32;
#endif
// our engine debug levels
@@ -368,6 +369,7 @@ public:
GfxMacIconBar *_gfxMacIconBar; // Mac Icon Bar manager
#ifdef ENABLE_SCI32
+ Audio32 *_audio32;
RobotDecoder *_robotDecoder;
GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx
#endif
diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp
index 71b081049e..4fb9a58003 100644
--- a/engines/sci/sound/audio.cpp
+++ b/engines/sci/sound/audio.cpp
@@ -253,13 +253,7 @@ static void deDPCM16(byte *soundBuf, Common::SeekableReadStream &audioStream, ui
static void deDPCM8Nibble(byte *soundBuf, int32 &s, byte b) {
if (b & 8) {
-#ifdef ENABLE_SCI32
- // SCI2.1 reverses the order of the table values here
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
- s -= tableDPCM8[b & 7];
- else
-#endif
- s -= tableDPCM8[7 - (b & 7)];
+ s -= tableDPCM8[7 - (b & 7)];
} else
s += tableDPCM8[b & 7];
s = CLIP<int32>(s, 0, 255);
diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
new file mode 100644
index 0000000000..56b10c55fe
--- /dev/null
+++ b/engines/sci/sound/audio32.cpp
@@ -0,0 +1,961 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "sci/sound/audio32.h"
+#include "audio/audiostream.h" // for SeekableAudioStream
+#include "audio/decoders/raw.h" // for makeRawStream, RawFlags::FLAG_16BITS
+#include "audio/decoders/wave.h" // for makeWAVStream
+#include "audio/rate.h" // for RateConverter, makeRateConverter
+#include "audio/timestamp.h" // for Timestamp
+#include "common/config-manager.h" // for ConfMan
+#include "common/endian.h" // for MKTAG
+#include "common/memstream.h" // for MemoryReadStream
+#include "common/str.h" // for String
+#include "common/stream.h" // for SeekableReadStream
+#include "common/system.h" // for OSystem, g_system
+#include "common/textconsole.h" // for warning
+#include "common/types.h" // for Flag::NO
+#include "engine.h" // for Engine, g_engine
+#include "sci/engine/vm_types.h" // for reg_t, make_reg, NULL_REG
+#include "sci/resource.h" // for ResourceId, ResourceType::kResour...
+#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
+#include "sci/sound/decoders/sol.h" // for makeSOLStream
+
+namespace Sci {
+
+bool detectSolAudio(Common::SeekableReadStream &stream) {
+ const size_t initialPosition = stream.pos();
+
+// TODO: Resource manager for audio resources reads past the
+// header so even though this is the detection algorithm
+// in SSCI, ScummVM can't use it
+#if 0
+ byte header[6];
+ if (stream.read(header, sizeof(header)) != sizeof(header)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+
+ if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) {
+ return false;
+ }
+
+ return true;
+#else
+ byte header[4];
+ if (stream.read(header, sizeof(header)) != sizeof(header)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+
+ if (READ_BE_UINT32(header) != MKTAG('S', 'O', 'L', 0)) {
+ return false;
+ }
+
+ return true;
+#endif
+}
+
+bool detectWaveAudio(Common::SeekableReadStream &stream) {
+ const size_t initialPosition = stream.pos();
+
+ byte blockHeader[8];
+ if (stream.read(blockHeader, sizeof(blockHeader)) != sizeof(blockHeader)) {
+ stream.seek(initialPosition);
+ return false;
+ }
+
+ stream.seek(initialPosition);
+ const uint32 headerType = READ_BE_UINT32(blockHeader);
+
+ if (headerType != MKTAG('R', 'I', 'F', 'F')) {
+ return false;
+ }
+
+ return true;
+}
+
+#pragma mark -
+
+Audio32::Audio32(ResourceManager *resMan) :
+ _resMan(resMan),
+ _mixer(g_system->getMixer()),
+ _handle(),
+ _mutex(),
+
+ _numActiveChannels(0),
+
+ _maxAllowedSampleRate(44100),
+ _maxAllowedBitDepth(16),
+ _maxAllowedOutputChannels(2),
+ _preload(0),
+
+ _robotAudioPaused(false),
+
+ _pausedAtTick(0),
+ _startedAtTick(0),
+
+ _attenuatedMixing(true),
+
+ _monitoredChannelIndex(-1),
+ _monitoredBuffer(nullptr),
+ _monitoredBufferSize(0),
+ _numMonitoredSamples(0) {
+
+ if (getSciVersion() < SCI_VERSION_3) {
+ _channels.resize(5);
+ } else {
+ _channels.resize(8);
+ }
+
+ _useModifiedAttenuation = false;
+ if (getSciVersion() == SCI_VERSION_2_1_MIDDLE) {
+ switch (g_sci->getGameId()) {
+ case GID_MOTHERGOOSEHIRES:
+ case GID_PQ4:
+ case GID_QFG4:
+ case GID_SQ6:
+ _useModifiedAttenuation = true;
+ default:
+ break;
+ }
+ } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+ switch (g_sci->getGameId()) {
+ // 1.51 uses the non-standard attenuation, but 2.00b
+ // does not, which is strange.
+ case GID_KQ7:
+ _useModifiedAttenuation = true;
+ default:
+ break;
+ }
+ }
+
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
+ _mixer->pauseHandle(_handle, true);
+}
+
+Audio32::~Audio32() {
+ stop(kAllChannels);
+ _mixer->stopHandle(_handle);
+ free(_monitoredBuffer);
+}
+
+#pragma mark -
+#pragma mark AudioStream implementation
+
+int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) {
+ int samplesToRead = numSamples;
+
+ // The parent rate converter will request N * 2
+ // samples from this `readBuffer` call, because
+ // we tell it that we send stereo output, but
+ // the source stream we're mixing in may be
+ // mono, in which case we need to request half
+ // as many samples from the mono stream and let
+ // the converter double them for stereo output
+ if (!sourceStream->isStereo()) {
+ samplesToRead >>= 1;
+ }
+
+ int samplesWritten = 0;
+
+ do {
+ if (loop && sourceStream->endOfStream()) {
+ sourceStream->rewind();
+ }
+
+ const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume);
+
+ if (loopSamplesWritten == 0) {
+ break;
+ }
+
+ samplesToRead -= loopSamplesWritten;
+ samplesWritten += loopSamplesWritten;
+ targetBuffer += loopSamplesWritten << 1;
+ } while (loop && samplesToRead > 0);
+
+ if (!sourceStream->isStereo()) {
+ samplesWritten <<= 1;
+ }
+
+ return samplesWritten;
+}
+
+// In earlier versions of SCI32 engine, audio mixing is
+// split into three different functions.
+//
+// The first function is called from the main game thread in
+// AsyncEventCheck; later versions of SSCI also call it when
+// getting the playback position. This function is
+// responsible for cleaning up finished channels and
+// filling active channel buffers with decompressed audio
+// matching the hardware output audio format so they can
+// just be copied into the main DAC buffer directly later.
+//
+// The second function is called by the audio hardware when
+// the DAC buffer needs to be filled, and by `play` when
+// there is only one active sample (so it can just blow away
+// whatever was already in the DAC buffer). It merges all
+// active channels into the DAC buffer and then updates the
+// offset into the DAC buffer.
+//
+// Finally, a third function is called by the second
+// function, and it actually puts data into the DAC buffer,
+// performing volume, distortion, and balance adjustments.
+//
+// Since we only have one callback from the audio thread,
+// and should be able to do all audio processing in
+// real time, and we have streams, and we do not need to
+// completely fill the audio buffer, the functionality of
+// all these original functions is combined here and
+// simplified.
+int Audio32::readBuffer(Audio::st_sample_t *const buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ // The system mixer should not try to get data when
+ // Audio32 is paused
+ assert(_pausedAtTick == 0 && _numActiveChannels > 0);
+
+ // The caller of `readBuffer` is a rate converter,
+ // which reuses (without clearing) an intermediate
+ // buffer, so we need to zero the intermediate buffer
+ // to prevent mixing into audio data from the last
+ // callback.
+ memset(buffer, 0, numSamples * sizeof(Audio::st_sample_t));
+
+ // This emulates the attenuated mixing mode of SSCI
+ // engine, which reduces the volume of the target
+ // buffer when each new channel is mixed in.
+ // Instead of manipulating the content of the target
+ // buffer when mixing (which would either require
+ // modification of RateConverter or an expensive second
+ // pass against the entire target buffer), we just
+ // scale the volume for each channel in advance, with
+ // the earliest (lowest) channel having the highest
+ // amount of attenuation (lowest volume).
+ uint8 attenuationAmount;
+ uint8 attenuationStepAmount;
+ if (_useModifiedAttenuation) {
+ // channel | divisor
+ // 0 | 0 (>> 0)
+ // 1 | 4 (>> 2)
+ // 2 | 8...
+ attenuationAmount = _numActiveChannels * 2;
+ attenuationStepAmount = 2;
+ } else {
+ // channel | divisor
+ // 0 | 2 (>> 1)
+ // 1 | 4 (>> 2)
+ // 2 | 6...
+ if (_monitoredChannelIndex == -1 && _numActiveChannels > 1) {
+ attenuationAmount = _numActiveChannels + 1;
+ attenuationStepAmount = 1;
+ } else {
+ attenuationAmount = 0;
+ attenuationStepAmount = 0;
+ }
+ }
+
+ int maxSamplesWritten = 0;
+
+ for (int16 channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) {
+ attenuationAmount -= attenuationStepAmount;
+
+ const AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick || (channel.robot && _robotAudioPaused)) {
+ continue;
+ }
+
+ // Channel finished fading and had the
+ // stopChannelOnFade flag set, so no longer exists
+ if (channel.fadeStepsRemaining && processFade(channelIndex)) {
+ --channelIndex;
+ continue;
+ }
+
+ if (channel.robot) {
+ // TODO: Robot audio into output buffer
+ continue;
+ }
+
+ if (channel.vmd) {
+ // TODO: VMD audio into output buffer
+ continue;
+ }
+
+ Audio::st_volume_t leftVolume, rightVolume;
+
+ if (channel.pan == -1 || !isStereo()) {
+ leftVolume = rightVolume = channel.volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ } else {
+ // TODO: This should match the SCI3 algorithm,
+ // which seems to halve the volume of each
+ // channel when centered; is this intended?
+ leftVolume = channel.volume * (100 - channel.pan) / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ rightVolume = channel.volume * channel.pan / 100 * Audio::Mixer::kMaxChannelVolume / kMaxVolume;
+ }
+
+ if (_monitoredChannelIndex == -1 && _attenuatedMixing) {
+ leftVolume >>= attenuationAmount;
+ rightVolume >>= attenuationAmount;
+ }
+
+ if (channelIndex == _monitoredChannelIndex) {
+ const size_t bufferSize = numSamples * sizeof(Audio::st_sample_t);
+ if (_monitoredBufferSize < bufferSize) {
+ _monitoredBuffer = (Audio::st_sample_t *)realloc(_monitoredBuffer, bufferSize);
+ _monitoredBufferSize = bufferSize;
+ }
+
+ memset(_monitoredBuffer, 0, _monitoredBufferSize);
+
+ _numMonitoredSamples = writeAudioInternal(channel.stream, channel.converter, _monitoredBuffer, numSamples, leftVolume, rightVolume, channel.loop);
+
+ Audio::st_sample_t *sourceBuffer = _monitoredBuffer;
+ Audio::st_sample_t *targetBuffer = buffer;
+ const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples;
+ while (sourceBuffer != end) {
+ Audio::clampedAdd(*targetBuffer++, *sourceBuffer++);
+ }
+
+ if (_numMonitoredSamples > maxSamplesWritten) {
+ maxSamplesWritten = _numMonitoredSamples;
+ }
+ } else if (!channel.stream->endOfStream() || channel.loop) {
+ const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, leftVolume, rightVolume, channel.loop);
+ if (channelSamplesWritten > maxSamplesWritten) {
+ maxSamplesWritten = channelSamplesWritten;
+ }
+ }
+ }
+
+ freeUnusedChannels();
+
+ return maxSamplesWritten;
+}
+
+#pragma mark -
+#pragma mark Channel management
+
+int16 Audio32::findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const {
+ // NOTE: argc/argv are already reduced by one in our engine because
+ // this call is always made from a subop, so no reduction for the
+ // subop is made in this function. SSCI takes extra steps to skip
+ // the subop argument.
+
+ argc -= startIndex;
+ if (argc <= 0) {
+ return kAllChannels;
+ }
+
+ Common::StackLock lock(_mutex);
+
+ if (_numActiveChannels == 0) {
+ return kNoExistingChannel;
+ }
+
+ ResourceId searchId;
+
+ if (argc < 5) {
+ searchId = ResourceId(kResourceTypeAudio, argv[startIndex].toUint16());
+ } else {
+ searchId = ResourceId(
+ kResourceTypeAudio36,
+ argv[startIndex].toUint16(),
+ argv[startIndex + 1].toUint16(),
+ argv[startIndex + 2].toUint16(),
+ argv[startIndex + 3].toUint16(),
+ argv[startIndex + 4].toUint16()
+ );
+ }
+
+ return findChannelById(searchId, soundNode);
+}
+
+int16 Audio32::findChannelById(const ResourceId resourceId, const reg_t soundNode) const {
+ Common::StackLock lock(_mutex);
+
+ if (_numActiveChannels == 0) {
+ return kNoExistingChannel;
+ }
+
+ if (resourceId.getType() == kResourceTypeAudio) {
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ const AudioChannel channel = _channels[i];
+ if (
+ channel.id == resourceId &&
+ (soundNode.isNull() || soundNode == channel.soundNode)
+ ) {
+ return i;
+ }
+ }
+ } else if (resourceId.getType() == kResourceTypeAudio36) {
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ const AudioChannel &candidate = getChannel(i);
+ if (!candidate.robot && candidate.id == resourceId) {
+ return i;
+ }
+ }
+ } else {
+ error("Audio32::findChannelById: Unknown resource type %d", resourceId.getType());
+ }
+
+ return kNoExistingChannel;
+}
+
+void Audio32::freeUnusedChannels() {
+ Common::StackLock lock(_mutex);
+ for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) {
+ const AudioChannel &channel = getChannel(channelIndex);
+ if (channel.stream->endOfStream()) {
+ if (channel.loop) {
+ channel.stream->rewind();
+ } else {
+ stop(channelIndex--);
+ }
+ }
+ }
+}
+
+void Audio32::freeChannel(const int16 channelIndex) {
+ // The original engine did this:
+ // 1. Unlock memory-cached resource, if one existed
+ // 2. Close patched audio file descriptor, if one existed
+ // 3. Free decompression memory buffer, if one existed
+ // 4. Clear monitored memory buffer, if one existed
+ Common::StackLock lock(_mutex);
+ AudioChannel &channel = getChannel(channelIndex);
+ _resMan->unlockResource(channel.resource);
+ channel.resource = nullptr;
+ delete channel.stream;
+ channel.stream = nullptr;
+ delete channel.resourceStream;
+ channel.resourceStream = nullptr;
+ delete channel.converter;
+ channel.converter = nullptr;
+
+ if (_monitoredChannelIndex == channelIndex) {
+ _monitoredChannelIndex = -1;
+ }
+}
+
+#pragma mark -
+#pragma mark Script compatibility
+
+void Audio32::setSampleRate(uint16 rate) {
+ if (rate > _maxAllowedSampleRate) {
+ rate = _maxAllowedSampleRate;
+ }
+
+ _globalSampleRate = rate;
+}
+
+void Audio32::setBitDepth(uint8 depth) {
+ if (depth > _maxAllowedBitDepth) {
+ depth = _maxAllowedBitDepth;
+ }
+
+ _globalBitDepth = depth;
+}
+
+void Audio32::setNumOutputChannels(int16 numChannels) {
+ if (numChannels > _maxAllowedOutputChannels) {
+ numChannels = _maxAllowedOutputChannels;
+ }
+
+ _globalNumOutputChannels = numChannels;
+}
+
+#pragma mark -
+#pragma mark Playback
+
+uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) {
+ Common::StackLock lock(_mutex);
+
+ if (channelIndex != kNoExistingChannel) {
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick) {
+ resume(channelIndex);
+ return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000);
+ }
+
+ warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str());
+ return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000);
+ }
+
+ if (_numActiveChannels == _channels.size()) {
+ warning("Audio mixer is full when trying to play %s", resourceId.toString().c_str());
+ return 0;
+ }
+
+ // NOTE: SCI engine itself normally searches in this order:
+ //
+ // For Audio36:
+ //
+ // 1. First, request a FD using Audio36 name and use it as the
+ // source FD for reading the audio resource data.
+ // 2a. If the returned FD is -1, or equals the audio map, or
+ // equals the audio bundle, try to get the offset of the
+ // data from the audio map, using the Audio36 name.
+ //
+ // If the returned offset is -1, this is not a valid resource;
+ // return 0. Otherwise, set the read offset for the FD to the
+ // returned offset.
+ // 2b. Otherwise, use the FD as-is (it is a patch file), with zero
+ // offset, and record it separately so it can be closed later.
+ //
+ // For plain audio:
+ //
+ // 1. First, request an Audio resource from the resource cache. If
+ // one does not exist, make the same request for a Wave resource.
+ // 2a. If an audio resource was discovered, record its memory ID
+ // and clear the streaming FD
+ // 2b. Otherwise, request an Audio FD. If one does not exist, make
+ // the same request for a Wave FD. If neither exist, this is not
+ // a valid resource; return 0. Otherwise, use the returned FD as
+ // the streaming ID and set the memory ID to null.
+ //
+ // Once these steps are complete, the audio engine either has a file
+ // descriptor + offset that it can use to read streamed audio, or it
+ // has a memory ID that it can use to read cached audio.
+ //
+ // Here in ScummVM we just ask the resource manager to give us the
+ // resource and we get a seekable stream.
+
+ // TODO: This should be fixed to use streaming, which means
+ // fixing the resource manager to allow streaming, which means
+ // probably rewriting a bunch of the resource manager.
+ Resource *resource = _resMan->findResource(resourceId, true);
+ if (resource == nullptr) {
+ return 0;
+ }
+
+ channelIndex = _numActiveChannels++;
+
+ AudioChannel &channel = getChannel(channelIndex);
+ channel.id = resourceId;
+ channel.resource = resource;
+ channel.loop = loop;
+ channel.robot = false;
+ channel.vmd = false;
+ channel.lastFadeTick = 0;
+ channel.fadeStepsRemaining = 0;
+ channel.soundNode = soundNode;
+ channel.volume = volume < 0 || volume > kMaxVolume ? (int)kMaxVolume : volume;
+ // TODO: SCI3 introduces stereo audio
+ channel.pan = -1;
+
+ if (monitor) {
+ _monitoredChannelIndex = channelIndex;
+ }
+
+ Common::MemoryReadStream headerStream(resource->_header, resource->_headerSize, DisposeAfterUse::NO);
+ Common::SeekableReadStream *dataStream = channel.resourceStream = resource->makeStream();
+
+ if (detectSolAudio(headerStream)) {
+ channel.stream = makeSOLStream(&headerStream, dataStream, DisposeAfterUse::NO);
+ } else if (detectWaveAudio(*dataStream)) {
+ channel.stream = Audio::makeWAVStream(dataStream, DisposeAfterUse::NO);
+ } else {
+ byte flags = Audio::FLAG_LITTLE_ENDIAN;
+ if (_globalBitDepth == 16) {
+ flags |= Audio::FLAG_16BITS;
+ } else {
+ flags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (_globalNumOutputChannels == 2) {
+ flags |= Audio::FLAG_STEREO;
+ }
+
+ channel.stream = Audio::makeRawStream(dataStream, _globalSampleRate, flags, DisposeAfterUse::NO);
+ }
+
+ channel.converter = Audio::makeRateConverter(channel.stream->getRate(), getRate(), channel.stream->isStereo(), false);
+
+ // NOTE: SCI engine sets up a decompression buffer here for the audio
+ // stream, plus writes information about the sample to the channel to
+ // convert to the correct hardware output format, and allocates the
+ // monitoring buffer to match the bitrate/samplerate/channels of the
+ // original stream. We do not need to do any of these things since we
+ // use audio streams, and allocate and fill the monitoring buffer
+ // when reading audio data from the stream.
+
+ channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000);
+
+ const uint32 now = g_sci->getTickCount();
+ if (!autoPlay) {
+ channel.pausedAtTick = now;
+ }
+ channel.startedAtTick = now;
+
+ if (_numActiveChannels == 1) {
+ _startedAtTick = now;
+ _mixer->pauseHandle(_handle, false);
+ }
+
+ return channel.duration;
+}
+
+bool Audio32::resume(const int16 channelIndex) {
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ Common::StackLock lock(_mutex);
+ const uint32 now = g_sci->getTickCount();
+
+ if (channelIndex == kAllChannels) {
+ // Global pause in SSCI is an extra layer over
+ // individual channel pauses, so only unpause channels
+ // if there was not a global pause in place
+ if (_pausedAtTick == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (!channel.pausedAtTick) {
+ channel.startedAtTick += now - _pausedAtTick;
+ }
+ }
+
+ _startedAtTick += now - _pausedAtTick;
+ _pausedAtTick = 0;
+ _mixer->pauseHandle(_handle, false);
+ return true;
+ } else if (channelIndex == kRobotChannel) {
+ for (int i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (channel.robot) {
+ channel.startedAtTick += now - channel.pausedAtTick;
+ channel.pausedAtTick = 0;
+ // TODO: Robot
+ // StartRobot();
+ return true;
+ }
+ }
+ } else {
+ AudioChannel &channel = getChannel(channelIndex);
+ if (channel.pausedAtTick) {
+ channel.startedAtTick += now - channel.pausedAtTick;
+ channel.pausedAtTick = 0;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool Audio32::pause(const int16 channelIndex) {
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ Common::StackLock lock(_mutex);
+ const uint32 now = g_sci->getTickCount();
+ bool didPause = false;
+
+ if (channelIndex == kAllChannels) {
+ if (_pausedAtTick == 0) {
+ _pausedAtTick = now;
+ _mixer->pauseHandle(_handle, true);
+ didPause = true;
+ }
+ } else if (channelIndex == kRobotChannel) {
+ _robotAudioPaused = true;
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ AudioChannel &channel = getChannel(i);
+ if (channel.robot) {
+ channel.pausedAtTick = now;
+ }
+ }
+
+ // NOTE: The actual engine returns false here regardless of whether
+ // or not channels were paused
+ } else {
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick == 0) {
+ channel.pausedAtTick = now;
+ didPause = true;
+ }
+ }
+
+ return didPause;
+}
+
+int16 Audio32::stop(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ const int16 oldNumChannels = _numActiveChannels;
+
+ if (channelIndex == kNoExistingChannel || oldNumChannels == 0) {
+ return 0;
+ }
+
+ if (channelIndex == kAllChannels) {
+ for (int i = 0; i < oldNumChannels; ++i) {
+ freeChannel(i);
+ }
+ _numActiveChannels = 0;
+ } else {
+ freeChannel(channelIndex);
+ --_numActiveChannels;
+ for (int i = channelIndex; i < oldNumChannels - 1; ++i) {
+ _channels[i] = _channels[i + 1];
+ if (i + 1 == _monitoredChannelIndex) {
+ _monitoredChannelIndex = i;
+ }
+ }
+ }
+
+ // NOTE: SSCI stops the DSP interrupt and frees the
+ // global decompression buffer here if there are no
+ // more active channels
+ if (_numActiveChannels == 0) {
+ _mixer->pauseHandle(_handle, true);
+ }
+
+ return oldNumChannels;
+}
+
+int16 Audio32::getPosition(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ if (channelIndex == kNoExistingChannel || _numActiveChannels == 0) {
+ return -1;
+ }
+
+ // NOTE: SSCI treats this as an unsigned short except for
+ // when the value is 65535, then it treats it as signed
+ int position = -1;
+ const uint32 now = g_sci->getTickCount();
+
+ // NOTE: The original engine also queried the audio driver to see whether
+ // it thought that there was audio playback occurring via driver opcode 9
+ if (channelIndex == kAllChannels) {
+ if (_pausedAtTick) {
+ position = _pausedAtTick - _startedAtTick;
+ } else {
+ position = now - _startedAtTick;
+ }
+ } else {
+ const AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.pausedAtTick) {
+ position = channel.pausedAtTick - channel.startedAtTick;
+ } else if (_pausedAtTick) {
+ position = _pausedAtTick - channel.startedAtTick;
+ } else {
+ position = now - channel.startedAtTick;
+ }
+ }
+
+ return MIN(position, 65534);
+}
+
+void Audio32::setLoop(const int16 channelIndex, const bool loop) {
+ Common::StackLock lock(_mutex);
+
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+ channel.loop = loop;
+}
+
+reg_t Audio32::kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv) {
+ if (argc == 0) {
+ return make_reg(0, _numActiveChannels);
+ }
+
+ const int16 channelIndex = findChannelByArgs(argc, argv, 0, NULL_REG);
+ ResourceId resourceId;
+ bool loop;
+ int16 volume;
+ bool monitor = false;
+ reg_t soundNode;
+
+ if (argc >= 5) {
+ resourceId = ResourceId(kResourceTypeAudio36, argv[0].toUint16(), argv[1].toUint16(), argv[2].toUint16(), argv[3].toUint16(), argv[4].toUint16());
+
+ if (argc < 6 || argv[5].toSint16() == 1) {
+ loop = false;
+ } else {
+ // NOTE: Uses -1 for infinite loop. Presumably the
+ // engine was supposed to allow counter loops at one
+ // point, but ended up only using loop as a boolean.
+ loop = (bool)argv[5].toSint16();
+ }
+
+ if (argc < 7 || argv[6].toSint16() < 0 || argv[6].toSint16() > Audio32::kMaxVolume) {
+ volume = Audio32::kMaxVolume;
+
+ if (argc >= 7) {
+ monitor = true;
+ }
+ } else {
+ volume = argv[6].toSint16();
+ }
+ } else {
+ resourceId = ResourceId(kResourceTypeAudio, argv[0].toUint16());
+
+ if (argc < 2 || argv[1].toSint16() == 1) {
+ loop = false;
+ } else {
+ loop = (bool)argv[1].toSint16();
+ }
+
+ // TODO: SCI3 uses the 0x80 bit as a flag to
+ // indicate "priority channel", but the volume is clamped
+ // in this call to 0x7F so that flag never makes it into
+ // the audio subsystem
+ if (argc < 3 || argv[2].toSint16() < 0 || argv[2].toSint16() > Audio32::kMaxVolume) {
+ volume = Audio32::kMaxVolume;
+
+ if (argc >= 3) {
+ monitor = true;
+ }
+ } else {
+ volume = argv[2].toSint16();
+ }
+
+ soundNode = argc == 4 ? argv[3] : NULL_REG;
+ }
+
+ return make_reg(0, play(channelIndex, resourceId, autoPlay, loop, volume, soundNode, monitor));
+}
+
+#pragma mark -
+#pragma mark Effects
+
+int16 Audio32::getVolume(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return _mixer->getChannelVolume(_handle) * kMaxVolume / Audio::Mixer::kMaxChannelVolume;
+ }
+
+ return getChannel(channelIndex).volume;
+}
+
+void Audio32::setVolume(const int16 channelIndex, int16 volume) {
+ Common::StackLock lock(_mutex);
+
+ volume = MIN((int16)kMaxVolume, volume);
+ if (channelIndex == kAllChannels) {
+ ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ _mixer->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume);
+ g_engine->syncSoundSettings();
+ } else if (channelIndex != kNoExistingChannel) {
+ getChannel(channelIndex).volume = volume;
+ }
+}
+
+bool Audio32::fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) {
+ Common::StackLock lock(_mutex);
+
+ if (channelIndex < 0 || channelIndex >= _numActiveChannels) {
+ return false;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (channel.id.getType() != kResourceTypeAudio || channel.volume == targetVolume) {
+ return false;
+ }
+
+ if (steps) {
+ channel.fadeVolume = targetVolume;
+ channel.fadeSpeed = speed;
+ channel.fadeStepsRemaining = steps;
+ channel.stopChannelOnFade = stopAfterFade;
+ channel.lastFadeTick = g_sci->getTickCount();
+ } else {
+ setVolume(channelIndex, targetVolume);
+ }
+
+ return true;
+}
+
+bool Audio32::processFade(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ AudioChannel &channel = getChannel(channelIndex);
+
+ uint32 now = g_sci->getTickCount();
+
+ if (channel.lastFadeTick + channel.fadeSpeed <= now) {
+ --channel.fadeStepsRemaining;
+
+ if (!channel.fadeStepsRemaining) {
+ if (channel.stopChannelOnFade) {
+ stop(channelIndex);
+ return true;
+ } else {
+ setVolume(channelIndex, channel.fadeVolume);
+ }
+ } else {
+ int volume = channel.volume - (channel.volume - channel.fadeVolume) / (channel.fadeStepsRemaining + 1);
+
+ if (volume == channel.fadeVolume) {
+ channel.fadeStepsRemaining = 1;
+ }
+
+ setVolume(channelIndex, volume);
+ channel.lastFadeTick = now;
+ }
+ }
+
+ return false;
+}
+
+#pragma mark -
+#pragma mark Signal monitoring
+
+bool Audio32::hasSignal() const {
+ Common::StackLock lock(_mutex);
+
+ if (_monitoredChannelIndex == -1) {
+ return false;
+ }
+
+ const Audio::st_sample_t *buffer = _monitoredBuffer;
+ const Audio::st_sample_t *const end = _monitoredBuffer + _numMonitoredSamples;
+
+ while (buffer != end) {
+ const Audio::st_sample_t sample = *buffer++;
+ if (sample > 1280 || sample < -1280) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h
new file mode 100644
index 0000000000..59aba66e2c
--- /dev/null
+++ b/engines/sci/sound/audio32.h
@@ -0,0 +1,566 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_AUDIO32_H
+#define SCI_AUDIO32_H
+#include "audio/audiostream.h" // for AudioStream, SeekableAudioStream (...
+#include "audio/mixer.h" // for Mixer, SoundHandle
+#include "audio/rate.h" // for Audio::st_volume_t, RateConverter
+#include "common/array.h" // for Array
+#include "common/mutex.h" // for StackLock, Mutex
+#include "common/scummsys.h" // for int16, uint8, uint32, uint16
+#include "engines/sci/resource.h" // for ResourceId
+#include "sci/engine/vm_types.h" // for reg_t, NULL_REG
+
+namespace Sci {
+
+/**
+ * An audio channel used by the software SCI mixer.
+ */
+struct AudioChannel {
+ /**
+ * The ID of the resource loaded into this channel.
+ */
+ ResourceId id;
+
+ /**
+ * The resource loaded into this channel.
+ */
+ Resource *resource;
+
+ /**
+ * Data stream containing the raw audio for the channel.
+ */
+ Common::SeekableReadStream *resourceStream;
+
+ /**
+ * The audio stream loaded into this channel.
+ * `SeekableAudioStream` is used here instead of
+ * `RewindableAudioStream` because
+ * `RewindableAudioStream` does not include the
+ * `getLength` function, which is needed to tell the
+ * game engine the duration of audio streams.
+ */
+ Audio::SeekableAudioStream *stream;
+
+ /**
+ * The converter used to transform and merge the input
+ * stream into the mixer's output buffer.
+ */
+ Audio::RateConverter *converter;
+
+ /**
+ * Duration of the channel, in ticks.
+ */
+ uint32 duration;
+
+ /**
+ * The tick when the channel was started.
+ */
+ uint32 startedAtTick;
+
+ /**
+ * The tick when the channel was paused.
+ */
+ uint32 pausedAtTick;
+
+ /**
+ * Whether or not the audio in this channel should loop
+ * infinitely.
+ */
+ bool loop;
+
+ /**
+ * The time the last fade iteration occurred.
+ */
+ uint32 lastFadeTick;
+
+ /**
+ * The target volume of the fade.
+ */
+ int fadeVolume;
+
+ /**
+ * The number of ticks that should elapse between
+ * each change of volume.
+ */
+ int fadeSpeed;
+
+ /**
+ * The number of iterations the fade should take to
+ * complete. If this value is 0, it indicates that the
+ * channel is not fading.
+ */
+ int fadeStepsRemaining;
+
+ /**
+ * Whether or not the channel should be stopped and
+ * freed when the fade is complete.
+ */
+ bool stopChannelOnFade;
+
+ /**
+ * Whether or not this channel contains a Robot
+ * audio block.
+ */
+ bool robot;
+
+ /**
+ * Whether or not this channel contains a VMD audio
+ * track.
+ */
+ bool vmd;
+
+ /**
+ * For digital sound effects, the related VM
+ * Sound::nodePtr object for the sound.
+ */
+ reg_t soundNode;
+
+ /**
+ * The playback volume, from 1 to 127 inclusive.
+ */
+ int volume;
+
+ /**
+ * The amount to pan to the right, from 0 to 100.
+ * 50 is centered, -1 is not panned.
+ */
+ int pan;
+};
+
+/**
+ * Special audio channel indexes used to select a channel
+ * for digital audio playback.
+ */
+enum AudioChannelIndex {
+ kRobotChannel = -3,
+ kNoExistingChannel = -2,
+ kAllChannels = -1
+};
+
+/**
+ * Audio32 acts as a permanent audio stream into the system
+ * mixer and provides digital audio services for the SCI32
+ * engine, since the system mixer does not support all the
+ * features of SCI.
+ */
+class Audio32 : public Audio::AudioStream {
+public:
+ Audio32(ResourceManager *resMan);
+ ~Audio32();
+
+private:
+ ResourceManager *_resMan;
+ Audio::Mixer *_mixer;
+ Audio::SoundHandle _handle;
+ Common::Mutex _mutex;
+
+ enum {
+ /**
+ * The maximum channel volume.
+ */
+ kMaxVolume = 127
+ };
+
+#pragma mark -
+#pragma mark AudioStream implementation
+public:
+ int readBuffer(Audio::st_sample_t *const buffer, const int numSamples);
+ bool isStereo() const { return true; }
+ int getRate() const { return _mixer->getOutputRate(); }
+ bool endOfData() const { return _numActiveChannels == 0; }
+ bool endOfStream() const { return false; }
+
+private:
+ /**
+ * Mixes audio from the given source stream into the
+ * target buffer using the given rate converter.
+ */
+ int writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop);
+
+#pragma mark -
+#pragma mark Channel management
+public:
+ /**
+ * Gets the number of currently active channels.
+ */
+ inline uint8 getNumActiveChannels() const {
+ Common::StackLock lock(_mutex);
+ return _numActiveChannels;
+ }
+
+ /**
+ * Finds a channel that is already configured for the
+ * given audio sample.
+ *
+ * @param startIndex The location of the audio resource
+ * information in the arguments list.
+ */
+ int16 findChannelByArgs(int argc, const reg_t *argv, const int startIndex, const reg_t soundNode) const;
+
+ /**
+ * Finds a channel that is already configured for the
+ * given audio sample.
+ */
+ int16 findChannelById(const ResourceId resourceId, const reg_t soundNode = NULL_REG) const;
+
+private:
+ /**
+ * The audio channels.
+ */
+ Common::Array<AudioChannel> _channels;
+
+ /**
+ * The number of active audio channels in the mixer.
+ * Being active is not the same as playing; active
+ * channels may be paused.
+ */
+ uint8 _numActiveChannels;
+
+ /**
+ * Gets the audio channel at the given index.
+ */
+ inline AudioChannel &getChannel(const int16 channelIndex) {
+ Common::StackLock lock(_mutex);
+ assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
+ return _channels[channelIndex];
+ }
+
+ /**
+ * Gets the audio channel at the given index.
+ */
+ inline const AudioChannel &getChannel(const int16 channelIndex) const {
+ Common::StackLock lock(_mutex);
+ assert(channelIndex >= 0 && channelIndex < _numActiveChannels);
+ return _channels[channelIndex];
+ }
+
+ /**
+ * Frees all non-looping channels that have reached the
+ * end of their stream.
+ */
+ void freeUnusedChannels();
+
+ /**
+ * Frees resources allocated to the given channel.
+ */
+ void freeChannel(const int16 channelIndex);
+
+#pragma mark -
+#pragma mark Script compatibility
+public:
+ /**
+ * Gets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ inline uint16 getSampleRate() const {
+ return _globalSampleRate;
+ }
+
+ /**
+ * Sets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ void setSampleRate(uint16 rate);
+
+ /**
+ * Gets the (fake) bit depth of the hardware DAC.
+ * For script compatibility only.
+ */
+ inline uint8 getBitDepth() const {
+ return _globalBitDepth;
+ }
+
+ /**
+ * Sets the (fake) sample rate of the hardware DAC.
+ * For script compatibility only.
+ */
+ void setBitDepth(uint8 depth);
+
+ /**
+ * Gets the (fake) number of output (speaker) channels
+ * of the hardware DAC. For script compatibility only.
+ */
+ inline uint8 getNumOutputChannels() const {
+ return _globalNumOutputChannels;
+ }
+
+ /**
+ * Sets the (fake) number of output (speaker) channels
+ * of the hardware DAC. For script compatibility only.
+ */
+ void setNumOutputChannels(int16 numChannels);
+
+ /**
+ * Gets the (fake) number of preloaded channels.
+ * For script compatibility only.
+ */
+ inline uint8 getPreload() const {
+ return _preload;
+ }
+
+ /**
+ * Sets the (fake) number of preloaded channels.
+ * For script compatibility only.
+ */
+ inline void setPreload(uint8 preload) {
+ _preload = preload;
+ }
+
+private:
+ /**
+ * The hardware DAC sample rate. Stored only for script
+ * compatibility.
+ */
+ uint16 _globalSampleRate;
+
+ /**
+ * The maximum allowed sample rate of the system mixer.
+ * Stored only for script compatibility.
+ */
+ uint16 _maxAllowedSampleRate;
+
+ /**
+ * The hardware DAC bit depth. Stored only for script
+ * compatibility.
+ */
+ uint8 _globalBitDepth;
+
+ /**
+ * The maximum allowed bit depth of the system mixer.
+ * Stored only for script compatibility.
+ */
+ uint8 _maxAllowedBitDepth;
+
+ /**
+ * The hardware DAC output (speaker) channel
+ * configuration. Stored only for script compatibility.
+ */
+ uint8 _globalNumOutputChannels;
+
+ /**
+ * The maximum allowed number of output (speaker)
+ * channels of the system mixer. Stored only for script
+ * compatibility.
+ */
+ uint8 _maxAllowedOutputChannels;
+
+ /**
+ * The number of audio channels that should have their
+ * data preloaded into memory instead of streaming from
+ * disk.
+ * 1 = all channels, 2 = 2nd active channel and above,
+ * etc.
+ * Stored only for script compatibility.
+ */
+ uint8 _preload;
+
+#pragma mark -
+#pragma mark Robot
+public:
+
+private:
+ /**
+ * When true, channels marked as robot audio will not be
+ * played.
+ */
+ bool _robotAudioPaused;
+
+#pragma mark -
+#pragma mark Playback
+public:
+ /**
+ * Starts or resumes playback of an audio channel.
+ */
+ uint16 play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor);
+
+ /**
+ * Resumes playback of a paused audio channel, or of
+ * the entire audio player.
+ */
+ bool resume(const int16 channelIndex);
+ bool resume(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return resume(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Pauses an audio channel, or the entire audio player.
+ */
+ bool pause(const int16 channelIndex);
+ bool pause(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return pause(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Stops and unloads an audio channel, or the entire
+ * audio player.
+ */
+ int16 stop(const int16 channelIndex);
+ int16 stop(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return stop(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Returns the playback position for the given channel
+ * number, in ticks.
+ */
+ int16 getPosition(const int16 channelIndex) const;
+ int16 getPosition(const ResourceId resourceId, const reg_t soundNode = NULL_REG) {
+ Common::StackLock lock(_mutex);
+ return getPosition(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Sets whether or not the given channel should loop.
+ */
+ void setLoop(const int16 channelIndex, const bool loop);
+ void setLoop(const ResourceId resourceId, const reg_t soundNode, const bool loop) {
+ Common::StackLock lock(_mutex);
+ setLoop(findChannelById(resourceId, soundNode), loop);
+ }
+
+ reg_t kernelPlay(const bool autoPlay, const int argc, const reg_t *const argv);
+
+private:
+ /**
+ * The tick when audio was globally paused.
+ */
+ uint32 _pausedAtTick;
+
+ /**
+ * The tick when audio was globally started.
+ */
+ uint32 _startedAtTick;
+
+#pragma mark -
+#pragma mark Effects
+public:
+ /**
+ * Gets the volume for a given channel. Passing
+ * `kAllChannels` will get the global volume.
+ */
+ int16 getVolume(const int16 channelIndex) const;
+ int16 getVolume(const ResourceId resourceId, const reg_t soundNode) const {
+ Common::StackLock lock(_mutex);
+ return getVolume(findChannelById(resourceId, soundNode));
+ }
+
+ /**
+ * Sets the volume of an audio channel. Passing
+ * `kAllChannels` will set the global volume.
+ */
+ void setVolume(const int16 channelIndex, int16 volume);
+ void setVolume(const ResourceId resourceId, const reg_t soundNode, const int16 volume) {
+ Common::StackLock lock(_mutex);
+ setVolume(findChannelById(resourceId, soundNode), volume);
+ }
+
+ /**
+ * Initiate an immediate fade of the given channel.
+ */
+ bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade);
+ bool fadeChannel(const ResourceId resourceId, const reg_t soundNode, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade) {
+ Common::StackLock lock(_mutex);
+ return fadeChannel(findChannelById(resourceId, soundNode), targetVolume, speed, steps, stopAfterFade);
+ }
+
+ /**
+ * Gets whether attenuated mixing mode is active.
+ */
+ inline bool getAttenuatedMixing() const {
+ return _attenuatedMixing;
+ }
+
+ /**
+ * Sets the attenuated mixing mode.
+ */
+ void setAttenuatedMixing(bool attenuated) {
+ Common::StackLock lock(_mutex);
+ _attenuatedMixing = attenuated;
+ }
+
+private:
+ /**
+ * If true, audio will be mixed by reducing the target
+ * buffer by half every time a new channel is mixed in.
+ * The final channel is not attenuated.
+ */
+ bool _attenuatedMixing;
+
+ /**
+ * When true, a modified attenuation algorithm is used
+ * (`A/4 + B`) instead of standard linear attenuation
+ * (`A/2 + B/2`).
+ */
+ bool _useModifiedAttenuation;
+
+ /**
+ * Processes an audio fade for the given channel.
+ *
+ * @returns true if the fade was completed and the
+ * channel was stopped.
+ */
+ bool processFade(const int16 channelIndex);
+
+#pragma mark -
+#pragma mark Signal monitoring
+public:
+ /**
+ * Returns whether the currently monitored audio channel
+ * contains any signal within the next audio frame.
+ */
+ bool hasSignal() const;
+
+private:
+ /**
+ * The index of the channel being monitored for signal,
+ * or -1 if no channel is monitored. When a channel is
+ * monitored, it also causes the engine to play only the
+ * monitored channel.
+ */
+ int16 _monitoredChannelIndex;
+
+ /**
+ * The data buffer holding decompressed audio data for
+ * the channel that will be monitored for an audio
+ * signal.
+ */
+ Audio::st_sample_t *_monitoredBuffer;
+
+ /**
+ * The size of the buffer, in bytes.
+ */
+ size_t _monitoredBufferSize;
+
+ /**
+ * The number of valid audio samples in the signal
+ * monitoring buffer.
+ */
+ int _numMonitoredSamples;
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp
new file mode 100644
index 0000000000..280b24fd3a
--- /dev/null
+++ b/engines/sci/sound/decoders/sol.cpp
@@ -0,0 +1,272 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "common/substream.h"
+#include "common/util.h"
+#include "engines/sci/sci.h"
+#include "engines/sci/sound/decoders/sol.h"
+
+namespace Sci {
+
+// Note that the 16-bit version is also used in coktelvideo.cpp
+static const uint16 tableDPCM16[128] = {
+ 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080,
+ 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120,
+ 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0,
+ 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230,
+ 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280,
+ 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0,
+ 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320,
+ 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370,
+ 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0,
+ 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480,
+ 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700,
+ 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00,
+ 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000
+};
+
+static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 };
+
+/**
+ * Decompresses 16-bit DPCM compressed audio. Each byte read
+ * outputs one sample into the decompression buffer.
+ */
+static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, int16 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ if (delta & 0x80) {
+ sample -= tableDPCM16[delta & 0x7f];
+ } else {
+ sample += tableDPCM16[delta];
+ }
+ sample = CLIP<int16>(sample, -32768, 32767);
+ *out++ = TO_LE_16(sample);
+ }
+}
+
+/**
+ * Decompresses one half of an 8-bit DPCM compressed audio
+ * byte.
+ */
+static void deDPCM8Nibble(int16 *out, uint8 &sample, uint8 delta) {
+ if (delta & 8) {
+ sample -= tableDPCM8[delta & 7];
+ } else {
+ sample += tableDPCM8[delta & 7];
+ }
+ sample = CLIP<byte>(sample, 0, 255);
+ *out = (sample << 8) ^ 0x8000;
+}
+
+/**
+ * Decompresses 8-bit DPCM compressed audio. Each byte read
+ * outputs two samples into the decompression buffer.
+ */
+static void deDPCM8(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, uint8 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = audioStream.readByte();
+ deDPCM8Nibble(out++, sample, delta >> 4);
+ deDPCM8Nibble(out++, sample, delta & 0xf);
+ }
+}
+
+# pragma mark -
+
+template<bool STEREO, bool S16BIT>
+SOLStream<STEREO, S16BIT>::SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize) :
+ _stream(stream, disposeAfterUse),
+ _dataOffset(dataOffset),
+ _sampleRate(sampleRate),
+ // SSCI aligns the size of SOL data to 32 bits
+ _rawDataSize(rawDataSize & ~3) {
+ // TODO: This is not valid for stereo SOL files, which
+ // have interleaved L/R compression so need to store the
+ // carried values for each channel separately. See
+ // 60900.aud from Lighthouse for an example stereo file
+ if (S16BIT) {
+ _dpcmCarry16 = 0;
+ } else {
+ _dpcmCarry8 = 0x80;
+ }
+
+ const uint8 compressionRatio = 2;
+ const uint8 numChannels = STEREO ? 2 : 1;
+ const uint8 bytesPerSample = S16BIT ? 2 : 1;
+ _length = Audio::Timestamp((_rawDataSize * compressionRatio * 1000) / (_sampleRate * numChannels * bytesPerSample), 60);
+ }
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::seek(const Audio::Timestamp &where) override {
+ if (where != 0) {
+ // In order to seek in compressed SOL files, all
+ // previous bytes must be known since it uses
+ // differential compression. Therefore, only seeking
+ // to the beginning is supported now (SSCI does not
+ // offer seeking anyway)
+ return false;
+ }
+
+ if (S16BIT) {
+ _dpcmCarry16 = 0;
+ } else {
+ _dpcmCarry8 = 0x80;
+ }
+
+ return _stream->seek(_dataOffset, SEEK_SET);
+}
+
+template <bool STEREO, bool S16BIT>
+Audio::Timestamp SOLStream<STEREO, S16BIT>::getLength() const override {
+ return _length;
+}
+
+template <bool STEREO, bool S16BIT>
+int SOLStream<STEREO, S16BIT>::readBuffer(int16 *buffer, const int numSamples) override {
+ // Reading an odd number of 8-bit samples will result in a loss of samples
+ // since one byte represents two samples and we do not store the second
+ // nibble in this case; it should never happen in reality
+ assert(S16BIT || (numSamples % 2) == 0);
+
+ const int samplesPerByte = S16BIT ? 1 : 2;
+
+ int32 bytesToRead = numSamples / samplesPerByte;
+ if (_stream->pos() + bytesToRead > _rawDataSize) {
+ bytesToRead = _rawDataSize - _stream->pos();
+ }
+
+ if (S16BIT) {
+ deDPCM16(buffer, *_stream, bytesToRead, _dpcmCarry16);
+ } else {
+ deDPCM8(buffer, *_stream, bytesToRead, _dpcmCarry8);
+ }
+
+ const int samplesRead = bytesToRead * samplesPerByte;
+ return samplesRead;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::isStereo() const override {
+ return STEREO;
+}
+
+template <bool STEREO, bool S16BIT>
+int SOLStream<STEREO, S16BIT>::getRate() const override {
+ return _sampleRate;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::endOfData() const override {
+ return _stream->eos() || _stream->pos() >= _dataOffset + _rawDataSize;
+}
+
+template <bool STEREO, bool S16BIT>
+bool SOLStream<STEREO, S16BIT>::rewind() override {
+ return seek(0);
+}
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+
+ // TODO: Might not be necessary? Makes seeking work, but
+ // not sure if audio is ever actually seeked in SSCI.
+ const int32 initialPosition = stream->pos();
+
+ byte header[6];
+ if (stream->read(header, sizeof(header)) != sizeof(header)) {
+ return nullptr;
+ }
+
+ if (header[0] != 0x8d || READ_BE_UINT32(header + 2) != MKTAG('S', 'O', 'L', 0)) {
+ return nullptr;
+ }
+
+ const uint8 headerSize = header[1];
+ const uint16 sampleRate = stream->readUint16LE();
+ const byte flags = stream->readByte();
+ const uint32 dataSize = stream->readUint32LE();
+
+ if (flags & kCompressed) {
+ if (flags & kStereo && flags & k16Bit) {
+ return new SOLStream<true, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else if (flags & kStereo) {
+ return new SOLStream<true, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else if (flags & k16Bit) {
+ return new SOLStream<false, true>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ } else {
+ return new SOLStream<false, false>(new Common::SeekableSubReadStream(stream, initialPosition, initialPosition + dataSize, disposeAfterUse), disposeAfterUse, headerSize, sampleRate, dataSize);
+ }
+ }
+
+ byte rawFlags = Audio::FLAG_LITTLE_ENDIAN;
+ if (flags & k16Bit) {
+ rawFlags |= Audio::FLAG_16BITS;
+ } else {
+ rawFlags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (flags & kStereo) {
+ rawFlags |= Audio::FLAG_STEREO;
+ }
+
+ return Audio::makeRawStream(new Common::SeekableSubReadStream(stream, initialPosition + headerSize, initialPosition + headerSize + dataSize, disposeAfterUse), sampleRate, rawFlags, disposeAfterUse);
+}
+
+// TODO: This needs to be removed when resource manager is fixed
+// to not split audio into two parts
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse) {
+
+ if (headerStream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) {
+ return nullptr;
+ }
+
+ const uint16 sampleRate = headerStream->readUint16LE();
+ const byte flags = headerStream->readByte();
+ const int32 dataSize = headerStream->readSint32LE();
+
+ if (flags & kCompressed) {
+ if (flags & kStereo && flags & k16Bit) {
+ return new SOLStream<true, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else if (flags & kStereo) {
+ return new SOLStream<true, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else if (flags & k16Bit) {
+ return new SOLStream<false, true>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ } else {
+ return new SOLStream<false, false>(dataStream, disposeAfterUse, 0, sampleRate, dataSize);
+ }
+ }
+
+ byte rawFlags = Audio::FLAG_LITTLE_ENDIAN;
+ if (flags & k16Bit) {
+ rawFlags |= Audio::FLAG_16BITS;
+ } else {
+ rawFlags |= Audio::FLAG_UNSIGNED;
+ }
+
+ if (flags & kStereo) {
+ rawFlags |= Audio::FLAG_STEREO;
+ }
+
+ return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse);
+}
+
+}
diff --git a/engines/sci/sound/decoders/sol.h b/engines/sci/sound/decoders/sol.h
new file mode 100644
index 0000000000..1046d0b213
--- /dev/null
+++ b/engines/sci/sound/decoders/sol.h
@@ -0,0 +1,89 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_SOUND_DECODERS_SOL_H
+#define SCI_SOUND_DECODERS_SOL_H
+#include "audio/audiostream.h"
+#include "common/stream.h"
+
+namespace Sci {
+
+enum SOLFlags {
+ kCompressed = 1,
+ k16Bit = 4,
+ kStereo = 16
+};
+
+template <bool STEREO, bool S16BIT>
+class SOLStream : public Audio::SeekableAudioStream {
+private:
+ /**
+ * Read stream containing possibly-compressed SOL audio.
+ */
+ Common::DisposablePtr<Common::SeekableReadStream> _stream;
+
+ /**
+ * Start offset of the audio data in the read stream.
+ */
+ int32 _dataOffset;
+
+ /**
+ * Sample rate of audio data.
+ */
+ uint16 _sampleRate;
+
+ /**
+ * The raw (possibly-compressed) size of audio data in
+ * the stream.
+ */
+ int32 _rawDataSize;
+
+ /**
+ * The last sample from the previous DPCM decode.
+ */
+ union {
+ int16 _dpcmCarry16;
+ uint8 _dpcmCarry8;
+ };
+
+ /**
+ * The calculated length of the stream.
+ */
+ Audio::Timestamp _length;
+
+ virtual bool seek(const Audio::Timestamp &where) override;
+ virtual Audio::Timestamp getLength() const override;
+ virtual int readBuffer(int16 *buffer, const int numSamples) override;
+ virtual bool isStereo() const override;
+ virtual int getRate() const override;
+ virtual bool endOfData() const override;
+ virtual bool rewind() override;
+
+public:
+ SOLStream(Common::SeekableReadStream *stream, const DisposeAfterUse::Flag disposeAfterUse, const int32 dataOffset, const uint16 sampleRate, const int32 rawDataSize);
+};
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
+
+Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStream, Common::SeekableReadStream *dataStream, DisposeAfterUse::Flag disposeAfterUse);
+}
+#endif
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 5a11ac386a..3f34ecc2f8 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -212,6 +212,13 @@ void SciMusic::clearPlayList() {
void SciMusic::pauseAll(bool pause) {
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
+#ifdef ENABLE_SCI32
+ // The entire DAC will have been paused by the caller;
+ // do not pause the individual samples too
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && (*i)->isSample) {
+ continue;
+ }
+#endif
soundToggle(*i, pause);
}
}
@@ -472,7 +479,16 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
}
}
- if (pSnd->pStreamAud) {
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+
+ g_sci->_audio32->play(kNoExistingChannel, ResourceId(kResourceTypeAudio, pSnd->resourceId), true, pSnd->loop != 0 && pSnd->loop != 1, pSnd->volume, pSnd->soundObj, false);
+
+ return;
+ } else
+#endif
if (!_pMixer->isSoundHandleActive(pSnd->hCurrentAud)) {
if ((_currentlyPlayingSample) && (_pMixer->isSoundHandleActive(_currentlyPlayingSample->hCurrentAud))) {
// Another sample is already playing, we have to stop that one
@@ -550,10 +566,18 @@ void SciMusic::soundStop(MusicEntry *pSnd) {
pSnd->status = kSoundStopped;
if (_soundVersion <= SCI_VERSION_0_LATE)
pSnd->isQueued = false;
- if (pSnd->pStreamAud) {
- if (_currentlyPlayingSample == pSnd)
- _currentlyPlayingSample = NULL;
- _pMixer->stopHandle(pSnd->hCurrentAud);
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+#endif
+ if (_currentlyPlayingSample == pSnd)
+ _currentlyPlayingSample = NULL;
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+#ifdef ENABLE_SCI32
+ }
+#endif
}
if (pSnd->pMidiParser) {
@@ -572,9 +596,12 @@ void SciMusic::soundStop(MusicEntry *pSnd) {
void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) {
assert(volume <= MUSIC_VOLUME_MAX);
- if (pSnd->pStreamAud) {
- // we simply ignore volume changes for samples, because sierra sci also
- // doesn't support volume for samples via kDoSound
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->setVolume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj, volume);
+ }
+#endif
} else if (pSnd->pMidiParser) {
Common::StackLock lock(_mutex);
pSnd->pMidiParser->mainThreadBegin();
@@ -614,12 +641,20 @@ void SciMusic::soundKill(MusicEntry *pSnd) {
_mutex.unlock();
- if (pSnd->pStreamAud) {
- if (_currentlyPlayingSample == pSnd) {
- // Forget about this sound, in case it was currently playing
- _currentlyPlayingSample = NULL;
+ if (pSnd->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+#endif
+ if (_currentlyPlayingSample == pSnd) {
+ // Forget about this sound, in case it was currently playing
+ _currentlyPlayingSample = NULL;
+ }
+ _pMixer->stopHandle(pSnd->hCurrentAud);
+#ifdef ENABLE_SCI32
}
- _pMixer->stopHandle(pSnd->hCurrentAud);
+#endif
delete pSnd->pStreamAud;
pSnd->pStreamAud = NULL;
delete pSnd->pLoopStream;
@@ -685,6 +720,18 @@ void SciMusic::soundResume(MusicEntry *pSnd) {
}
void SciMusic::soundToggle(MusicEntry *pSnd, bool pause) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && pSnd->isSample) {
+ if (pause) {
+ g_sci->_audio32->pause(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ } else {
+ g_sci->_audio32->resume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
+ }
+
+ return;
+ }
+#endif
+
if (pause)
soundPause(pSnd);
else
@@ -813,6 +860,7 @@ MusicEntry::MusicEntry() {
pStreamAud = 0;
pLoopStream = 0;
pMidiParser = 0;
+ isSample = false;
for (int i = 0; i < 16; ++i) {
_usedChannels[i] = 0xFF;
diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h
index 047f63b3b7..3a6de81c49 100644
--- a/engines/sci/sound/music.h
+++ b/engines/sci/sound/music.h
@@ -31,6 +31,9 @@
#include "sci/sci.h"
#include "sci/resource.h"
#include "sci/sound/drivers/mididriver.h"
+#ifdef ENABLE_SCI32
+#include "sci/sound/audio32.h"
+#endif
namespace Audio {
class LoopingAudioStream;
@@ -123,6 +126,7 @@ public:
Audio::RewindableAudioStream *pStreamAud;
Audio::LoopingAudioStream *pLoopStream;
Audio::SoundHandle hCurrentAud;
+ bool isSample;
public:
MusicEntry();
diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp
index efa4735bf4..7d2ab32c75 100644
--- a/engines/sci/sound/soundcmd.cpp
+++ b/engines/sci/sound/soundcmd.cpp
@@ -23,6 +23,7 @@
#include "common/config-manager.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
+#include "sci/resource.h"
#include "sci/sound/audio.h"
#include "sci/sound/music.h"
#include "sci/sound/soundcmd.h"
@@ -97,12 +98,21 @@ void SoundCommandParser::initSoundResource(MusicEntry *newSound) {
// user wants the digital version.
if (_useDigitalSFX || !newSound->soundRes) {
int sampleLen;
- newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen);
- newSound->soundType = Audio::Mixer::kSFXSoundType;
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ newSound->isSample = g_sci->getResMan()->testResource(ResourceId(kResourceTypeAudio, newSound->resourceId));
+ } else {
+#endif
+ newSound->pStreamAud = _audio->getAudioStream(newSound->resourceId, 65535, &sampleLen);
+ newSound->soundType = Audio::Mixer::kSFXSoundType;
+ newSound->isSample = newSound->pStreamAud != nullptr;
+#ifdef ENABLE_SCI32
+ }
+#endif
}
}
- if (!newSound->pStreamAud && newSound->soundRes)
+ if (!newSound->isSample && newSound->soundRes)
_music->soundInitSnd(newSound);
}
@@ -134,7 +144,7 @@ void SoundCommandParser::processInitSound(reg_t obj) {
_music->pushBackSlot(newSound);
- if (newSound->soundRes || newSound->pStreamAud) {
+ if (newSound->soundRes || newSound->isSample) {
// Notify the engine
if (_soundVersion <= SCI_VERSION_0_LATE)
writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundInitialized);
@@ -314,10 +324,22 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
}
reg_t obj = argv[0];
- uint16 value = argc > 1 ? argv[1].toUint16() : 0;
- if (!obj.getSegment()) { // pause the whole playlist
- _music->pauseAll(value);
- } else { // pause a playlist slot
+ const bool shouldPause = argc > 1 ? argv[1].toUint16() : false;
+ if (
+ (_soundVersion < SCI_VERSION_2_1_EARLY && !obj.getSegment()) ||
+ (_soundVersion >= SCI_VERSION_2_1_EARLY && obj.isNull())
+ ) {
+ _music->pauseAll(shouldPause);
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ if (shouldPause) {
+ g_sci->_audio32->pause(kAllChannels);
+ } else {
+ g_sci->_audio32->resume(kAllChannels);
+ }
+ }
+#endif
+ } else {
MusicEntry *musicSlot = _music->getSlot(obj);
if (!musicSlot) {
// This happens quite frequently
@@ -325,7 +347,23 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {
return acc;
}
- _music->soundToggle(musicSlot, value);
+#ifdef ENABLE_SCI32
+ // NOTE: The original engine also expected a global
+ // "kernel call" flag to be true in order to perform
+ // this action, but the architecture of the ScummVM
+ // implementation is so different that it doesn't
+ // matter here
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ const int16 channelIndex = g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj);
+
+ if (shouldPause) {
+ g_sci->_audio32->pause(channelIndex);
+ } else {
+ g_sci->_audio32->resume(channelIndex);
+ }
+ } else
+#endif
+ _music->soundToggle(musicSlot, shouldPause);
}
return acc;
}
@@ -355,7 +393,11 @@ reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc)
int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX);
vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX;
ConfMan.setInt("music_volume", vol);
- ConfMan.setInt("sfx_volume", vol);
+ // In SCI32, digital audio volume is controlled separately by
+ // kDoAudioVolume
+ if (_soundVersion < SCI_VERSION_2_1_EARLY) {
+ ConfMan.setInt("sfx_volume", vol);
+ }
g_engine->syncSoundSettings();
}
return acc;
@@ -378,6 +420,13 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {
int volume = musicSlot->volume;
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ g_sci->_audio32->fadeChannel(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, argv[2].toSint16(), argv[3].toSint16(), argv[4].toSint16(), (bool)argv[5].toSint16());
+ return acc;
+ }
+#endif
+
// If sound is not playing currently, set signal directly
if (musicSlot->status != kSoundPlaying) {
debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj));
@@ -466,7 +515,18 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {
return;
}
- if (musicSlot->pStreamAud) {
+ if (musicSlot->isSample) {
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ const int position = g_sci->_audio32->getPosition(g_sci->_audio32->findChannelById(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj));
+
+ if (position == -1) {
+ processStopSound(musicSlot->soundObj, true);
+ }
+
+ return;
+ }
+#endif
// Update digital sound effect slots
uint currentLoopCounter = 0;
@@ -669,6 +729,12 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {
value = CLIP<int>(value, 0, MUSIC_VOLUME_MAX);
+#ifdef ENABLE_SCI32
+ // SSCI unconditionally sets volume if it is digital audio
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ _music->soundSetVolume(musicSlot, value);
+ } else
+#endif
if (musicSlot->volume != value) {
musicSlot->volume = value;
_music->soundSetVolume(musicSlot, value);
@@ -727,6 +793,15 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
}
return acc;
}
+
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY) {
+ if (value != -1) {
+ value = 1;
+ }
+ }
+#endif
+
if (value == -1) {
musicSlot->loop = 0xFFFF;
} else {
@@ -734,6 +809,13 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {
}
writeSelectorValue(_segMan, obj, SELECTOR(loop), musicSlot->loop);
+
+#ifdef ENABLE_SCI32
+ if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {
+ g_sci->_audio32->setLoop(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, value == -1);
+ }
+#endif
+
return acc;
}
diff --git a/engines/sci/sound/sync.cpp b/engines/sci/sound/sync.cpp
index 08abad188b..4e75dab725 100644
--- a/engines/sci/sound/sync.cpp
+++ b/engines/sci/sound/sync.cpp
@@ -25,6 +25,7 @@
#include "sync.h"
namespace Sci {
+
Sync::Sync(ResourceManager *resMan, SegManager *segMan) :
_resMan(resMan),
_segMan(segMan),
diff --git a/engines/sci/sound/sync.h b/engines/sci/sound/sync.h
index c80982bff7..4b9e2d1b3c 100644
--- a/engines/sci/sound/sync.h
+++ b/engines/sci/sound/sync.h
@@ -38,6 +38,10 @@ class Resource;
class ResourceManager;
class SegManager;
+/**
+ * Sync class, kDoSync and relevant functions for SCI games.
+ * Provides AV synchronization for animations.
+ */
class Sync {
SegManager *_segMan;
ResourceManager *_resMan;
@@ -53,5 +57,5 @@ public:
void stop();
};
-}
+} // End of namespace Sci
#endif