Files
2026-02-02 04:50:13 +01:00

588 lines
16 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/voc.h"
#include "common/config-manager.h"
#include "common/util.h"
#include "darkseed/sound.h"
#include "darkseed/darkseed.h"
namespace Darkseed {
static constexpr char musicDosFloppyFilenameTbl[][14] = {
"lab1",
"victory1",
"cemetry1",
"leech1",
"ext1",
"quiet",
"package",
"dth",
"library",
"radio",
"outdoor",
"town"
};
static constexpr char musicDosCDFilenameTbl[][14] = {
"lab.mid",
"victory.mid",
"cemetery.mid",
"leech.mid",
"exterior.mid",
"passtime.mid",
"mirrorst.mid",
"dth.mid",
"lib_moz.mid",
"carradio.mid",
"greenwal.mid",
"walktown.mid"
};
static constexpr char startMusicDosFloppyFilenameTbl[][14] = {
"credits",
"alien",
"implant",
"launch",
"night2",
"night3",
"book",
"doll"
};
static constexpr char startMusicDosCDFilenameTbl[][14] = {
"openingt.mid",
"alienmou.mid",
"mindfuck.mid",
"spaceshi.mid",
"mindfuck.mid",
"zombie.mid",
"booktran.mid",
"babydoll.mid"
};
static constexpr char sfxCDFilenameTbl[][14] = {
"",
"opendoor.sfx",
"showers2.sfx",
"razzsysb.sfx",
"medicine.sfx",
"pop.sfx",
"pickupit.sfx",
"rockener.sfx",
"pullleve.sfx",
"starship.sfx",
"podwrith.sfx",
"starterc.sfx",
"sigils.sfx",
"tombdoor.sfx",
"digging.sfx",
"opendoor.sfx",
"carstart.sfx",
"makehamm.sfx",
"picklock.sfx",
"impaled.sfx",
"evilbeas.sfx",
"laser.sfx",
"knock.sfx",
"bubblesi.sfx",
"phone.sfx",
"softphon.sfx",
"pulsar.sfx",
"doorbell.sfx",
"mirrorsm.sfx",
"softdoor.sfx",
"electroc.sfx",
"medicine.sfx",
"pourings.sfx",
"tuneinra.sfx",
"opendoor.sfx",
"showers1.sfx",
"yo.sfx",
"showers2.sfx",
"popii.sfx",
"carhorn.sfx",
"yo.sfx",
"secretdo.sfx",
"opendoor.sfx",
"tick.sfx",
"tock.sfx",
"chime.sfx",
"softchim.sfx",
"shakeurn.sfx",
"beaming.sfx"
};
// Maps CD SFX IDs to floppy SFX IDs
static constexpr int sfxCdFloppyMapping[60] = {
// 0
-1, // Unused
10, // House front door
11, // Shower
-1, // Delbert's dog (not present in floppy version)
13, // Stairs
14, // Press button
15, // Pick up item
16, // Unused (energizing hammer head?)
17, // Lever
18, // Spaceship
// 10
19, // Leech
20, // Car engine start
21, // Mausoleum sigils (only used by floppy version)
22, // Mausoleum door (only used by CD version)
23, // Shovel
24, // Car door
25, // Car engine start failure
26, // Assembling hammer
27, // Lock picking
28, // Guardian
// 20
29, // Alien dog
30, // Alien cop gun
12, // Cup
32, // Cocoon
90, // Phone (loud)
91, // Phone (quiet)
118, // Fixing mirror
93, // Doorbell (loud)
94, // Destroying mirror
95, // Doorbell (quiet)
// 30
96, // Electricity
97, // Car trunk / trunk (attic)
98, // Drinking / pouring
99, // Unused (tuning radio?)
100, // Bathroom cabinet
101, // Bathroom faucet
-1, // Unused
-1, // Unused (running water?)
-1, // Unused (press button alternate?)
111, // Car horn
// 40
-1, // Unused
116, // Secret door
115, // Kitchen doors
107, // Clock tick (only used by floppy version)
108, // Clock tock (only used by floppy version)
104, // Clock chime (loud)
106, // Clock chime (quiet)
113, // Urn
114, // Teleporter
-1, // Unused
// 50 Floppy-only sound effects
35, // Recruitment center
51, // Car engine running (original code uses 50 here, which sounds wrong)
92, // Footstep
105, // Mosquito (upstairs hallway)
109, // Book stamp
110, // Kitchen faucet / fountain
112, // Phone dial tone
-1,
-1,
-1
};
// Maps DOS CD speech IDs to DOS floppy SFX IDs
static Common::Pair<int, int> speechCdFloppyMapping[] = {
// 59: ralph1 (unused & invalid)
{ 904, 60 }, // m1-1 Librarian (phone): "Hello?" "Hello Mike. This is Sue at the library. ..."
{ 927, 61 }, // cl1 Store clerk: "Serve yourself, Mr. Dawson."
{ 905, 62 }, // cl2 Store clerk: "That's the last bottle of Scotch. ..."
{ 907, 63 }, // d4a-1 Delbert: "Hi you must be Mike. ..."
{ 908, 64 }, // d6c-2 Delbert: "You're a writer, huh? ..."
{ 909, 65 }, // d5a-1 Delbert: "Good to see you, Dawson. ..."
{ 910, 66 }, // d6a-2 Delbert: "Boy, that's smooth. ..."
{ 906, 67 }, // cl3 Store clerk: "I'm sorry, Mr. Dawson, ..."
// CD ID 925 includes both this line and the next. These are 2 separate IDs in the floppy version. CD ID 926 is unused.
{ 925, 68 }, // gl0a Librarian: "I'm not really sure why I'm here, ..."
{ 926, 69 }, // gl1b Librarian: "I know it sounds strange, ..."
{ 924, 70 }, // gl2a Librarian: "This card really should be kept with the book. ..."
// 71: s7a-1 (invalid)
{ 912, 72 }, // s8a-2 Cop: "You're under arrest, Dawson. ..."
{ 913, 73 }, // k9a-3 Keeper: "Greetings Michael. ..."
{ 914, 74 }, // k9c-3 Keeper: "If born, this creature will destroy you..."
{ 915, 75 }, // k9e-3 Keeper: "Also, the Police in your world..."
{ 928, 76 }, // gl3a Librarian: "Hi Mike. Here's the book that was put on hold for you."
// 77: k9e-3 (duplicate of 75)
// 78: k9f-3 (invalid)
{ 916, 79 }, // g10a-1 Sargo: "Greetings, human. ..."
{ 917, 80 }, // g10b-1 Sargo: "I am prepared to give you the gift..."
{ 918, 81 }, // m11a-1 Mike: "I'm just beginning to understand."
{ 919, 82 }, // o12a-1 Keeper (radio): "Steal from those who protect you..."
{ 920, 83 }, // o13a-1 Keeper (radio): "What you do in the light..."
// 84: o13b-1 (invalid)
{ 921, 85 }, // o14a-1 Keeper (radio): "Turn yourself in and leave behind the key..."
{ 922, 86 }, // k15a-1 Keeper (phone): "Remember, anything seen in the mirror..."
{ 923, 87 }, // s16a-1 Alien cop: "So that's where my gun went! ..."
// 88: l17a-1 (invalid)
// 89: l18a-1 (invalid)
{ -1, -1 }
};
Sound::Sound(Audio::Mixer *mixer) : _mixer(mixer), _lastPlayedDigitalSfx(0) {
memset(_sfxFloppyDigFilenameTbl, 0, 120 * 14);
bool floppyMusicSetting = ConfMan.hasKey("use_floppy_music") ? ConfMan.getBool("use_floppy_music") : false;
_useFloppyMusic = g_engine->isDosFloppy() || floppyMusicSetting;
// SFX mode 0: CD SFX only
// SFX mode 1: CD SFX + additional floppy SFX
// SFX mode 2: floppy SFX only
// CD SFX are only available when using the CD version
// Floppy SFX are only available when using floppy music
int sfxMode = ConfMan.hasKey("sfx_mode") ? ConfMan.getInt("sfx_mode") : -1;
_useCdSfx = g_engine->isCdVersion() && (!_useFloppyMusic || sfxMode != 2);
_useFloppySfx = _useFloppyMusic && (g_engine->isDosFloppy() || sfxMode == 1 || sfxMode == 2);
_musicPlayer = new MusicPlayer(g_engine, _useFloppyMusic, _useFloppySfx);
_didSpeech.resize(978);
resetSpeech();
}
Sound::~Sound() {
delete _musicPlayer;
}
int Sound::init() {
int returnCode = _musicPlayer->open();
if (returnCode != 0)
return returnCode;
if (_useFloppyMusic || _useFloppySfx) {
Common::File file;
Common::Path path = g_engine->isCdVersion() ? Common::Path("sound").join("tos1.sit") : Common::Path("tos1.sit");
if (file.open(path)) {
_musicPlayer->loadTosInstrumentBankData(&file, (int32)file.size());
} else {
warning("Failed to load TOS floppy instrument bank data %s", path.toString().c_str());
}
file.close();
}
if (_useFloppySfx) {
Common::File file;
Common::Path path = g_engine->isCdVersion() ? Common::Path("sound").join("tos1.sbr") : Common::Path("tos1.sbr");
if (file.open(path)) {
_musicPlayer->load(&file, (int32)file.size(), true);
} else {
warning("Failed to load TOS floppy sound effects data %s", path.toString().c_str());
}
file.close();
}
if (g_engine->isDosFloppy()) {
Common::File file;
Common::Path path = Common::Path("tos1.dig");
if (file.open(path)) {
loadTosDigData(&file, (int32)file.size());
} else {
warning("Failed to load TOS floppy speech data %s", path.toString().c_str());
}
file.close();
}
return 0;
}
void Sound::loadTosDigData(Common::SeekableReadStream* in, int32 size) {
int32 bytesRead = 0;
int entriesRead = 0;
while (bytesRead < size && entriesRead < 120) {
byte type = in->readByte();
if (type == 3) {
// VOC filename entry
uint32 entryBytesRead = in->read(_sfxFloppyDigFilenameTbl[entriesRead], 14);
if (entryBytesRead != 14) {
warning("Failed to read all bytes from DIG filename entry %i", entriesRead);
return;
}
bytesRead += 15;
}
else if (type == 0) {
// Unknown what this entry type contains. It is ignored by the original code.
uint16 entrySize = in->readUint16LE();
if (!in->skip(entrySize - 3)) {
warning("Failed to read all bytes from DIG type 0 entry %i", entriesRead);
return;
}
bytesRead += entrySize;
}
else {
// Unknown entry type.
warning("Unknown DIG entry type %X in entry %i", type, entriesRead);
return;
}
entriesRead++;
}
if (entriesRead < 100) {
// DIG files typically contain at least 100 entries
warning("DIG file only contained %i entries", entriesRead);
}
}
void Sound::playTosSpeech(int tosIdx) {
if (g_engine->isDosFloppy()) {
int floppySfxId = convertCdSpeechToFloppySfxId(tosIdx);
if (floppySfxId == -1)
return;
playDosFloppySfx(floppySfxId, 5);
return;
}
if (!g_engine->isCdVersion() || _didSpeech[tosIdx] == 1) {
return;
}
Common::String filename = Common::String::format("%d.voc", tosIdx + 1);
Common::Path path = Common::Path("speech").join(filename);
Common::File f;
if (!f.open(path)) {
return;
}
Common::SeekableReadStream *srcStream = f.readStream((uint32)f.size());
Audio::SeekableAudioStream *stream = Audio::makeVOCStream(srcStream,
Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_speechHandle, stream);
_didSpeech[tosIdx] = 1;
}
void Sound::stopSpeech() {
_mixer->stopHandle(_speechHandle);
}
bool Sound::isPlayingSpeech() const {
return _mixer->isSoundHandleActive(_speechHandle);
}
bool Sound::isPlayingSfx() const {
return _mixer->isSoundHandleActive(_sfxHandle) || _musicPlayer->isPlayingSfx();
}
bool Sound::isPlayingSfx(uint8 sfxId) const {
if (_useFloppySfx) {
int floppySfxId = sfxCdFloppyMapping[sfxId];
if (floppySfxId == -1)
return false;
return _musicPlayer->isPlayingSfx(floppySfxId);
}
return _lastPlayedDigitalSfx == sfxId && _mixer->isSoundHandleActive(_sfxHandle);
}
bool Sound::isPlayingMusic() {
return _musicPlayer->isPlayingMusic();
}
void Sound::resetSpeech() {
for (int i = 0; i < (int)_didSpeech.size(); i++) {
_didSpeech[i] = 0;
}
}
void Sound::playMusic(MusicId musicId, bool loop) {
if (musicId == MusicId::kNone) {
return;
}
int filenameIdx = static_cast<uint8>(musicId) - 1;
playMusic(_useFloppyMusic ?
Common::String(musicDosFloppyFilenameTbl[filenameIdx]) + ".sbr" : musicDosCDFilenameTbl[filenameIdx],
nullptr, 6, loop);
}
void Sound::playMusic(StartMusicId musicId) {
int filenameIdx = static_cast<uint8>(musicId);
if (_useFloppyMusic) {
Common::String const &filenameBase = startMusicDosFloppyFilenameTbl[filenameIdx];
Common::String const &filenameSbr = filenameBase + ".sbr";
Common::String const &filenameSit = filenameBase + ".sit";
playMusic(filenameSbr, &filenameSit, 5);
}
else {
playMusic(startMusicDosCDFilenameTbl[filenameIdx]);
}
}
void Sound::playMusic(Common::String const &musicFilename, Common::String const *instrBankFilename, uint8 priority, bool loop) {
Common::File file;
Common::Path path;
if (_useFloppyMusic) {
if (instrBankFilename != nullptr) {
path = g_engine->isCdVersion() ? Common::Path("sound").join(instrBankFilename->c_str()) : Common::Path(instrBankFilename->c_str());
debug("Loading instrument bank: %s", path.toString().c_str());
if (!file.open(path)) {
debug("Failed to load %s", path.toString().c_str());
return;
}
_musicPlayer->loadInstrumentBank(&file, (int32)file.size());
file.close();
}
else {
debug("Loading TOS instrument bank");
_musicPlayer->loadTosInstrumentBank();
}
}
path = g_engine->isCdVersion() ? Common::Path("sound").join(musicFilename) : Common::Path(musicFilename);
debug("Loading music: %s", path.toString().c_str());
if (!file.open(path)) {
debug("Failed to load %s", path.toString().c_str());
return;
}
_musicPlayer->load(&file, (int32)file.size());
file.close();
_musicPlayer->playMusic(priority, loop);
}
void Sound::stopMusic() {
_musicPlayer->stopMusic();
}
void Sound::pauseMusic(bool pause) {
_musicPlayer->pauseMusic(pause);
}
void Sound::killAllSound() {
stopMusic();
stopSfx();
stopSpeech();
}
void Sound::syncSoundSettings() {
_musicPlayer->syncSoundSettings();
}
Common::Error Sound::sync(Common::Serializer &s) {
s.syncArray(_didSpeech.data(), _didSpeech.size(), Common::Serializer::Byte);
return Common::kNoError;
}
bool Sound::isMuted() const {
bool soundIsMuted = false;
if (ConfMan.hasKey("mute")) {
soundIsMuted = ConfMan.getBool("mute");
}
return soundIsMuted;
}
void Sound::playSfx(uint8 sfxId, uint8 priority, int unk2) {
// Do not play floppy-only SFX using the CD SFX playing code
if (_useCdSfx && sfxId <= 48 && sfxId != 43 && sfxId != 44) {
playDosCDSfx(sfxId);
}
else if (_useFloppySfx && sfxId < 60) {
int floppySfxId = sfxCdFloppyMapping[sfxId];
if (floppySfxId == -1)
return;
playDosFloppySfx(floppySfxId, priority);
}
}
void Sound::stopSfx() {
_mixer->stopHandle(_sfxHandle);
_musicPlayer->stopAllSfx();
}
void Sound::playDosCDSfx(int sfxId) {
if (sfxId == 0 || sfxId > 48) {
return;
}
if (isPlayingSfx()) {
return;
}
Common::Path path = Common::Path("sound").join(sfxCDFilenameTbl[sfxId]);
Common::File f;
if (!f.open(path)) {
debug("Failed to load sfx. %s", path.toString().c_str());
return;
}
Common::SeekableReadStream *srcStream = f.readStream((uint32)f.size());
Audio::SeekableAudioStream *stream = Audio::makeVOCStream(srcStream,
Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, stream);
_lastPlayedDigitalSfx = sfxId;
}
void Sound::playDosFloppySfx(byte floppySfxId, uint8 priority) {
if (_musicPlayer->isSampleSfx(floppySfxId)) {
playFloppySpeech(floppySfxId);
} else if (floppySfxId >= 10 && floppySfxId < 120) {
_musicPlayer->playSfx(floppySfxId, priority);
}
}
void Sound::playFloppySpeech(int floppySfxId) {
Common::String filename = _sfxFloppyDigFilenameTbl[floppySfxId];
if (filename.size() == 0)
return;
Common::Path path = Common::Path(filename + ".voc");
Common::File f;
if (!f.open(path)) {
warning("Failed to load speech file %s", path.toString().c_str());
return;
}
Common::SeekableReadStream *srcStream = f.readStream((uint32)f.size());
Audio::SeekableAudioStream *stream = Audio::makeVOCStream(srcStream,
Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_speechHandle, stream);
}
void Sound::startFadeOut() {
_musicPlayer->startFadeOutMusic();
}
bool Sound::isFading() {
return _musicPlayer->isFadingMusic();
}
int Sound::convertCdSpeechToFloppySfxId(int cdSfxId) {
int i = 0;
while (true) {
int entryCdSfxId = speechCdFloppyMapping[i].first;
if (entryCdSfxId == -1)
return -1;
if (entryCdSfxId == cdSfxId)
return speechCdFloppyMapping[i].second;
i++;
}
}
bool Sound::isUsingCdSfx() const {
return _useCdSfx;
}
bool Sound::isUsingFloppySfx() const {
return _useFloppySfx;
}
void Sound::resetIndividualSpeech(int tosIdx) {
_didSpeech[tosIdx] = 0;
}
} // End of namespace Darkseed