scummvm/engines/agos/res_snd.cpp
2021-12-26 18:48:43 +01:00

626 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 "common/config-manager.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "agos/intern.h"
#include "agos/agos.h"
#include "agos/midi.h"
#include "agos/sound.h"
#include "agos/vga.h"
#include "backends/audiocd/audiocd.h"
#include "audio/audiostream.h"
#include "audio/mods/protracker.h"
namespace AGOS {
void AGOSEngine_Simon1::playSpeech(uint16 speech_id, uint16 vgaSpriteId) {
if (speech_id == 9999) {
if (_subtitles)
return;
if (!getBitFlag(14) && !getBitFlag(28)) {
setBitFlag(14, true);
_variableArray[100] = 15;
animate(4, 1, 130, 0, 0, 0);
waitForSync(130);
}
_skipVgaWait = true;
} else {
if (_subtitles && _scriptVar2) {
animate(4, 2, 204, 0, 0, 0);
waitForSync(204);
stopAnimate(204);
}
if (vgaSpriteId < 100)
stopAnimate(201 + vgaSpriteId);
loadVoice(speech_id);
if (vgaSpriteId < 100)
animate(4, 2, 201 + vgaSpriteId, 0, 0, 0);
}
}
void AGOSEngine_Simon2::playSpeech(uint16 speech_id, uint16 vgaSpriteId) {
if (speech_id == 0xFFFF) {
if (_subtitles)
return;
if (!getBitFlag(14) && !getBitFlag(28)) {
setBitFlag(14, true);
_variableArray[100] = 5;
animate(4, 1, 30, 0, 0, 0);
waitForSync(130);
}
_skipVgaWait = true;
} else {
if (getGameType() == GType_SIMON2 && _subtitles && _language != Common::HE_ISR) {
loadVoice(speech_id);
return;
}
if (_subtitles && _scriptVar2) {
animate(4, 2, 5, 0, 0, 0);
waitForSync(205);
stopAnimateSimon2(2,5);
}
stopAnimateSimon2(2, vgaSpriteId + 2);
loadVoice(speech_id);
animate(4, 2, vgaSpriteId + 2, 0, 0, 0);
}
}
void AGOSEngine::skipSpeech() {
_sound->stopVoice();
if (!getBitFlag(28)) {
setBitFlag(14, true);
if (getGameType() == GType_FF) {
_variableArray[103] = 5;
animate(4, 2, 13, 0, 0, 0);
waitForSync(213);
stopAnimateSimon2(2, 1);
} else if (getGameType() == GType_SIMON2) {
_variableArray[100] = 5;
animate(4, 1, 30, 0, 0, 0);
waitForSync(130);
stopAnimateSimon2(2, 1);
} else {
_variableArray[100] = 15;
animate(4, 1, 130, 0, 0, 0);
waitForSync(130);
stopAnimate(1);
}
}
}
void AGOSEngine::loadMusic(uint16 music) {
char buf[4];
stopMusic();
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music - 1], SEEK_SET);
_gameFile->read(buf, 4);
if (!memcmp(buf, "FORM", 4)) {
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music - 1], SEEK_SET);
_midi->loadXMIDI(_gameFile);
} else {
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music - 1], SEEK_SET);
_midi->loadMultipleSMF(_gameFile);
}
_lastMusicPlayed = music;
_nextMusicToPlay = -1;
}
struct ModuleOffs {
uint8 tune;
uint8 fileNum;
uint32 offs;
};
static const ModuleOffs amigaWaxworksOffs[20] = {
// Pyramid
{2, 2, 0, },
{3, 2, 50980},
{4, 2, 56160},
{5, 2, 62364},
{6, 2, 73688},
// Zombie
{8, 8, 0},
{11, 8, 51156},
{12, 8, 56336},
{13, 8, 65612},
{14, 8, 68744},
// Mine
{9, 9, 0},
{15, 9, 47244},
{16, 9, 52424},
{17, 9, 59652},
{18, 9, 62784},
// Jack
{10, 10, 0},
{19, 10, 42054},
{20, 10, 47234},
{21, 10, 49342},
{22, 10, 51450},
};
void AGOSEngine::playModule(uint16 music) {
char filename[15];
Common::File f;
uint32 offs = 0;
if (getPlatform() == Common::kPlatformAmiga && getGameType() == GType_WW) {
// Multiple tunes are stored in music files for main locations
for (uint i = 0; i < 20; i++) {
if (amigaWaxworksOffs[i].tune == music) {
music = amigaWaxworksOffs[i].fileNum;
offs = amigaWaxworksOffs[i].offs;
}
}
}
if (getGameType() == GType_ELVIRA1 && getFeatures() & GF_DEMO)
sprintf(filename, "elvira2");
else if (getPlatform() == Common::kPlatformAcorn)
sprintf(filename, "%dtune.DAT", music);
else
sprintf(filename, "%dtune", music);
f.open(filename);
if (f.isOpen() == false) {
error("playModule: Can't load module from '%s'", filename);
}
Audio::AudioStream *audioStream;
if (!(getGameType() == GType_ELVIRA1 && getFeatures() & GF_DEMO) &&
getFeatures() & GF_CRUNCHED) {
uint32 srcSize = f.size();
byte *srcBuf = (byte *)malloc(srcSize);
if (f.read(srcBuf, srcSize) != srcSize)
error("playModule: Read failed");
uint32 dstSize = READ_BE_UINT32(srcBuf + srcSize - 4);
byte *dstBuf = (byte *)malloc(dstSize);
decrunchFile(srcBuf, dstBuf, srcSize);
free(srcBuf);
Common::MemoryReadStream stream(dstBuf, dstSize);
audioStream = Audio::makeProtrackerStream(&stream, offs);
free(dstBuf);
} else {
audioStream = Audio::makeProtrackerStream(&f);
}
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_modHandle, audioStream);
_mixer->pauseHandle(_modHandle, _musicPaused);
}
void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) {
stopMusic();
// Support for compressed music from the ScummVM Music Enhancement Project
_system->getAudioCDManager()->stop();
_system->getAudioCDManager()->play(music + 1, -1, 0, 0, true);
if (_system->getAudioCDManager()->isPlaying())
return;
if (getPlatform() == Common::kPlatformAmiga) {
playModule(music);
} else if (getFeatures() & GF_TALKIE) {
char buf[4];
// WORKAROUND: For a script bug in the CD versions
// We skip this music resource, as it was replaced by
// a sound effect, and the script was never updated.
if (music == 35)
return;
_midi->setLoop(true); // Must do this BEFORE loading music. (GMF may have its own override.)
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
_gameFile->read(buf, 4);
if (!memcmp(buf, "GMF\x1", 4)) {
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
_midi->loadSMF(_gameFile, music);
} else {
_gameFile->seek(_gameOffsetsPtr[_musicIndexBase + music], SEEK_SET);
_midi->loadMultipleSMF(_gameFile);
}
_midi->startTrack(0);
_midi->startTrack(track);
} else if (getPlatform() == Common::kPlatformAcorn) {
// TODO: Add support for Desktop Tracker format in Acorn disk version
} else {
char filename[15];
Common::File f;
sprintf(filename, "MOD%d.MUS", music);
f.open(filename);
if (f.isOpen() == false)
error("playMusic: Can't load music from '%s'", filename);
_midi->setLoop(true); // Must do this BEFORE loading music. (GMF may have its own override.)
if (getFeatures() & GF_DEMO)
_midi->loadS1D(&f);
else
_midi->loadSMF(&f, music);
_midi->startTrack(0);
_midi->startTrack(track);
}
}
void AGOSEngine::playMusic(uint16 music, uint16 track) {
stopMusic();
if (getPlatform() == Common::kPlatformAmiga) {
playModule(music);
} else if (getPlatform() == Common::kPlatformAtariST) {
// TODO: Add support for music formats used
} else {
_midi->setLoop(true); // Must do this BEFORE loading music.
Common::SeekableReadStream *str = nullptr;
if (getPlatform() == Common::kPlatformPC98) {
str = createPak98FileStream(Common::String::format("MOD%d.PAK", music).c_str());
if (!str)
error("playMusic: Can't load music from 'MOD%d.PAK'", music);
} else {
Common::File *file = new Common::File();
if (!file->open(Common::String::format("MOD%d.MUS", music)))
error("playMusic: Can't load music from 'MOD%d.MUS'", music);
str = file;
}
_midi->loadS1D(str);
_midi->startTrack(0);
_midi->startTrack(track);
delete str;
}
}
void AGOSEngine::stopMusic() {
if (_midiEnabled) {
_midi->stop();
}
_mixer->stopHandle(_modHandle);
}
void AGOSEngine::playSting(uint16 soundId) {
// The sound effects in floppy disk version of
// Simon the Sorcerer 1 are only meant for AdLib
if (!_midi->_adLibMusic || !_midi->_enable_sfx)
return;
char filename[16];
Common::File mus_file;
uint16 mus_offset;
sprintf(filename, "STINGS%i.MUS", _soundFileId);
mus_file.open(filename);
if (!mus_file.isOpen())
error("playSting: Can't load sound effect from '%s'", filename);
mus_file.seek(soundId * 2, SEEK_SET);
mus_offset = mus_file.readUint16LE();
if (mus_file.err())
error("playSting: Can't read sting %d offset", soundId);
mus_file.seek(mus_offset, SEEK_SET);
_midi->loadSMF(&mus_file, soundId, true);
_midi->startTrack(0);
}
static const byte elvira1_soundTable[100] = {
0, 2, 0, 1, 0, 0, 0, 0, 0, 3,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 6, 4, 0, 0, 9, 0,
0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 8, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 5, 0, 6, 6, 0, 0,
0, 5, 0, 0, 6, 0, 0, 0, 0, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
bool AGOSEngine::loadVGASoundFile(uint16 id, uint8 type) {
Common::File in;
char filename[15];
byte *dst;
uint32 srcSize, dstSize;
if (getPlatform() == Common::kPlatformAmiga || getPlatform() == Common::kPlatformAtariST) {
if (getGameType() == GType_ELVIRA1 && (getFeatures() & GF_DEMO) &&
getPlatform() == Common::kPlatformAmiga) {
sprintf(filename, "%c%d.out", 48 + id, type);
} else if (getGameType() == GType_ELVIRA1 || getGameType() == GType_ELVIRA2) {
sprintf(filename, "%.2d%d.out", id, type);
} else if (getGameType() == GType_PN) {
sprintf(filename, "%c%d.in", id + 48, type);
} else {
sprintf(filename, "%.3d%d.out", id, type);
}
} else {
if (getGameType() == GType_ELVIRA1) {
if (elvira1_soundTable[id] == 0)
return false;
sprintf(filename, "%.2d.SND", elvira1_soundTable[id]);
} else if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
sprintf(filename, "%.2d%d.VGA", id, type);
} else if (getGameType() == GType_PN) {
sprintf(filename, "%c%d.out", id + 48, type);
} else {
sprintf(filename, "%.3d%d.VGA", id, type);
}
}
in.open(filename);
if (in.isOpen() == false || in.size() == 0) {
return false;
}
dstSize = srcSize = in.size();
if (getGameType() == GType_PN && (getFeatures() & GF_CRUNCHED)) {
Common::Stack<uint32> data;
byte *dataOut = nullptr;
int dataOutSize = 0;
for (uint i = 0; i < srcSize / 4; ++i)
data.push(in.readUint32BE());
decompressPN(data, dataOut, dataOutSize);
dst = allocBlock (dataOutSize);
memcpy(dst, dataOut, dataOutSize);
delete[] dataOut;
} else if (getGameType() == GType_ELVIRA1 && getFeatures() & GF_DEMO) {
byte *srcBuffer = (byte *)malloc(srcSize);
if (in.read(srcBuffer, srcSize) != srcSize)
error("loadVGASoundFile: Read failed");
dstSize = READ_BE_UINT32(srcBuffer + srcSize - 4);
dst = allocBlock (dstSize);
decrunchFile(srcBuffer, dst, srcSize);
free(srcBuffer);
} else {
dst = allocBlock(dstSize);
if (in.read(dst, dstSize) != dstSize)
error("loadVGASoundFile: Read failed");
}
in.close();
return true;
}
static const char *const dimpSoundList[32] = {
"Beep",
"Birth",
"Boiling",
"Burp",
"Cough",
"Die1",
"Die2",
"Fart",
"Inject",
"Killchik",
"Puke",
"Lights",
"Shock",
"Snore",
"Snotty",
"Whip",
"Whistle",
"Work1",
"Work2",
"Yawn",
"And0w",
"And0x",
"And0y",
"And0z",
"And10",
"And11",
"And12",
"And13",
"And14",
"And15",
"And16",
"And17",
};
void AGOSEngine::loadSoundFile(const char* filename) {
Common::File in;
if (!in.open(filename))
error("loadSound: Can't load %s", filename);
uint32 dstSize = in.size();
byte *dst = (byte *)malloc(dstSize);
if (in.read(dst, dstSize) != dstSize)
error("loadSound: Read failed");
_sound->playSfxData(dst, 0, 0, 0);
}
void AGOSEngine::loadSound(uint16 sound, int16 pan, int16 vol, uint16 type) {
byte *dst;
if (getGameId() == GID_DIMP) {
Common::File in;
char filename[15];
assert(sound >= 1 && sound <= 32);
sprintf(filename, "%s.wav", dimpSoundList[sound - 1]);
if (!in.open(filename))
error("loadSound: Can't load %s", filename);
uint32 dstSize = in.size();
dst = (byte *)malloc(dstSize);
if (in.read(dst, dstSize) != dstSize)
error("loadSound: Read failed");
} else if (getFeatures() & GF_ZLIBCOMP) {
char filename[15];
uint32 file, offset, srcSize, dstSize;
if (getPlatform() == Common::kPlatformAmiga) {
loadOffsets((const char*)"sfxindex.dat", _zoneNumber * 22 + sound, file, offset, srcSize, dstSize);
} else {
loadOffsets((const char*)"effects.wav", _zoneNumber * 22 + sound, file, offset, srcSize, dstSize);
}
if (getPlatform() == Common::kPlatformAmiga)
sprintf(filename, "sfx%u.wav", file);
else
sprintf(filename, "effects.wav");
dst = (byte *)malloc(dstSize);
decompressData(filename, dst, offset, srcSize, dstSize);
} else {
if (_curSfxFile == nullptr)
return;
dst = _curSfxFile + READ_LE_UINT32(_curSfxFile + sound * 4);
}
if (type == Sound::TYPE_AMBIENT)
_sound->playAmbientData(dst, sound, pan, vol);
else if (type == Sound::TYPE_SFX)
_sound->playSfxData(dst, sound, pan, vol);
else if (type == Sound::TYPE_SFX5)
_sound->playSfx5Data(dst, sound, pan, vol);
}
void AGOSEngine::loadSound(uint16 sound, uint16 freq, uint16 flags) {
byte *dst;
uint32 offs, size = 0;
uint32 rate = 8000;
if (_curSfxFile == nullptr)
return;
dst = _curSfxFile;
if (getGameType() == GType_WW) {
uint16 tmp = sound;
while (tmp--) {
size += READ_LE_UINT16(dst) + 4;
dst += READ_LE_UINT16(dst) + 4;
if (size > _curSfxFileSize)
error("loadSound: Reading beyond EOF (%d, %d)", size, _curSfxFileSize);
}
size = READ_LE_UINT16(dst);
offs = 4;
} else if (getGameType() == GType_ELVIRA2) {
while (READ_BE_UINT32(dst + 4) != sound) {
size += 12;
dst += 12;
if (size > _curSfxFileSize)
error("loadSound: Reading beyond EOF (%d, %d)", size, _curSfxFileSize);
}
size = READ_BE_UINT32(dst);
offs = READ_BE_UINT32(dst + 8);
} else {
while (READ_BE_UINT16(dst + 6) != sound) {
size += 12;
dst += 12;
if (size > _curSfxFileSize)
error("loadSound: Reading beyond EOF (%d, %d)", size, _curSfxFileSize);
}
size = READ_BE_UINT16(dst + 2);
offs = READ_BE_UINT32(dst + 8);
}
if (getGameType() == GType_PN) {
if (freq == 0) {
rate = 4600;
} else if (freq == 1) {
rate = 7400;
} else {
rate = 9400;
}
}
// TODO: Handle other sound flags in Amiga/AtariST versions
if (flags == 2 && _sound->isSfxActive()) {
_sound->queueSound(dst + offs, sound, size, rate);
} else {
if (flags == 0)
_sound->stopSfx();
_sound->playRawData(dst + offs, sound, size, rate);
}
}
void AGOSEngine::loadVoice(uint speechId) {
if (getGameType() == GType_PP && speechId == 99) {
_sound->stopVoice();
return;
}
if (getFeatures() & GF_ZLIBCOMP) {
char filename[15];
uint32 file, offset, srcSize, dstSize;
if (getPlatform() == Common::kPlatformAmiga) {
loadOffsets((const char*)"spindex.dat", speechId, file, offset, srcSize, dstSize);
} else {
loadOffsets((const char*)"speech.wav", speechId, file, offset, srcSize, dstSize);
}
// Voice segment doesn't exist
if (offset == 0xFFFFFFFF && srcSize == 0xFFFFFFFF && dstSize == 0xFFFFFFFF) {
debug(0, "loadVoice: speechId %d removed", speechId);
return;
}
if (getPlatform() == Common::kPlatformAmiga)
sprintf(filename, "sp%u.wav", file);
else
sprintf(filename, "speech.wav");
byte *dst = (byte *)malloc(dstSize);
decompressData(filename, dst, offset, srcSize, dstSize);
_sound->playVoiceData(dst, speechId);
} else {
_sound->playVoice(speechId);
}
}
} // End of namespace AGOS