Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,421 @@
/* 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/scummsys.h"
#include "scumm/scumm.h"
#include "scumm/util.h"
#include "scumm/file.h"
#include "scumm/imuse_digi/dimuse_bndmgr.h"
#include "scumm/imuse_digi/dimuse_codecs.h"
namespace Scumm {
BundleDirCache::BundleDirCache(const ScummEngine *vm) : _vm(vm) {
for (int fileId = 0; fileId < ARRAYSIZE(_bundleDirCache); fileId++) {
_bundleDirCache[fileId].bundleTable = nullptr;
_bundleDirCache[fileId].fileName[0] = 0;
_bundleDirCache[fileId].numFiles = 0;
_bundleDirCache[fileId].isCompressed = false;
_bundleDirCache[fileId].indexTable = nullptr;
}
}
BundleDirCache::~BundleDirCache() {
for (int fileId = 0; fileId < ARRAYSIZE(_bundleDirCache); fileId++) {
free(_bundleDirCache[fileId].bundleTable);
free(_bundleDirCache[fileId].indexTable);
}
}
BundleDirCache::AudioTable *BundleDirCache::getTable(int slot) {
return _bundleDirCache[slot].bundleTable;
}
int32 BundleDirCache::getNumFiles(int slot) {
return _bundleDirCache[slot].numFiles;
}
BundleDirCache::IndexNode *BundleDirCache::getIndexTable(int slot) {
return _bundleDirCache[slot].indexTable;
}
bool BundleDirCache::isSndDataExtComp(int slot) {
return _bundleDirCache[slot].isCompressed;
}
int BundleDirCache::matchFile(const char *filename) {
int32 tag, offset;
bool found = false;
int freeSlot = -1;
int fileId;
for (fileId = 0; fileId < ARRAYSIZE(_bundleDirCache); fileId++) {
if ((_bundleDirCache[fileId].bundleTable == nullptr) && (freeSlot == -1)) {
freeSlot = fileId;
}
if (scumm_stricmp(filename, _bundleDirCache[fileId].fileName) == 0) {
found = true;
break;
}
}
if (!found) {
ScummFile file(_vm);
if (g_scumm->openFile(file, filename) == false) {
error("BundleDirCache::matchFile() Can't open bundle file: %s", filename);
return false;
}
if (freeSlot == -1)
error("BundleDirCache::matchFileFile() Can't find free slot for file bundle dir cache");
tag = file.readUint32BE();
if (tag == MKTAG('L','B','2','3'))
_bundleDirCache[freeSlot].isCompressed = true;
offset = file.readUint32BE();
Common::strlcpy(_bundleDirCache[freeSlot].fileName, filename, sizeof(_bundleDirCache[freeSlot].fileName));
_bundleDirCache[freeSlot].numFiles = file.readUint32BE();
_bundleDirCache[freeSlot].bundleTable = (AudioTable *)malloc(_bundleDirCache[freeSlot].numFiles * sizeof(AudioTable));
assert(_bundleDirCache[freeSlot].bundleTable);
file.seek(offset, SEEK_SET);
_bundleDirCache[freeSlot].indexTable =
(IndexNode *)calloc(_bundleDirCache[freeSlot].numFiles, sizeof(IndexNode));
assert(_bundleDirCache[freeSlot].indexTable);
for (int32 i = 0; i < _bundleDirCache[freeSlot].numFiles; i++) {
char name[24], c;
int32 z = 0;
int32 z2;
if (tag == MKTAG('L','B','2','3')) {
file.read(_bundleDirCache[freeSlot].bundleTable[i].filename, 24);
} else {
for (z2 = 0; z2 < 8; z2++)
if ((c = file.readByte()) != 0)
name[z++] = c;
name[z++] = '.';
for (z2 = 0; z2 < 4; z2++)
if ((c = file.readByte()) != 0)
name[z++] = c;
name[z] = '\0';
Common::strlcpy(_bundleDirCache[freeSlot].bundleTable[i].filename, name, sizeof(_bundleDirCache[freeSlot].bundleTable[i].filename));
}
_bundleDirCache[freeSlot].bundleTable[i].offset = file.readUint32BE();
_bundleDirCache[freeSlot].bundleTable[i].size = file.readUint32BE();
Common::strlcpy(_bundleDirCache[freeSlot].indexTable[i].filename, _bundleDirCache[freeSlot].bundleTable[i].filename, sizeof(_bundleDirCache[freeSlot].indexTable[i].filename));
_bundleDirCache[freeSlot].indexTable[i].index = i;
}
qsort(_bundleDirCache[freeSlot].indexTable, _bundleDirCache[freeSlot].numFiles,
sizeof(IndexNode), (int (*)(const void *, const void *))scumm_stricmp);
return freeSlot;
} else {
return fileId;
}
}
BundleMgr::BundleMgr(const ScummEngine *vm, BundleDirCache *cache) {
_cache = cache;
_bundleTable = nullptr;
_compTable = nullptr;
_numFiles = 0;
_numCompItems = 0;
_lastBlockDecompressedSize = 0;
_curSampleId = -1;
_fileBundleId = -1;
_file = new ScummFile(vm);
_compInputBuff = nullptr;
}
BundleMgr::~BundleMgr() {
close();
delete _file;
}
Common::SeekableReadStream *BundleMgr::getFile(const char *filename, int32 &offset, int32 &size) {
BundleDirCache::IndexNode target;
Common::strlcpy(target.filename, filename, sizeof(target.filename));
BundleDirCache::IndexNode *found = (BundleDirCache::IndexNode *)bsearch(&target, _indexTable, _numFiles,
sizeof(BundleDirCache::IndexNode), (int (*)(const void *, const void *))scumm_stricmp);
if (found) {
_file->seek(_bundleTable[found->index].offset, SEEK_SET);
offset = _bundleTable[found->index].offset;
size = _bundleTable[found->index].size;
return _file;
}
return nullptr;
}
bool BundleMgr::open(const char *filename, bool &isCompressed, bool errorFlag) {
if (_file->isOpen())
return true;
if (g_scumm->openFile(*_file, filename) == false) {
if (errorFlag) {
error("BundleMgr::open() Can't open bundle file: %s", filename);
} else {
warning("BundleMgr::open() Can't open bundle file: %s", filename);
}
return false;
}
int slot = _cache->matchFile(filename);
assert(slot != -1);
isCompressed = _cache->isSndDataExtComp(slot);
_numFiles = _cache->getNumFiles(slot);
assert(_numFiles);
_bundleTable = _cache->getTable(slot);
_indexTable = _cache->getIndexTable(slot);
assert(_bundleTable);
_compTableLoaded = false;
_isUncompressed = false;
_outputSize = 0;
_lastBlockDecompressedSize = 0;
_curDecompressedFilePos = 0;
_lastBlock = -1;
return true;
}
void BundleMgr::close() {
if (_file->isOpen()) {
_file->close();
_bundleTable = nullptr;
_numFiles = 0;
_numCompItems = 0;
_lastBlockDecompressedSize = 0;
_curDecompressedFilePos = 0;
_compTableLoaded = false;
_isUncompressed = false;
_lastBlock = -1;
_outputSize = 0;
_curSampleId = -1;
free(_compTable);
_compTable = nullptr;
free(_compInputBuff);
_compInputBuff = nullptr;
}
}
bool BundleMgr::loadCompTable(int32 index) {
_file->seek(_bundleTable[index].offset, SEEK_SET);
uint32 tag = _file->readUint32BE();
if (tag == MKTAG('i','M','U','S')) {
_isUncompressed = true;
return true;
}
_numCompItems = _file->readUint32BE();
assert(_numCompItems > 0);
_file->seek(4, SEEK_CUR);
_lastBlockDecompressedSize = _file->readUint32BE();
if (tag != MKTAG('C','O','M','P')) {
debug("BundleMgr::loadCompTable() Compressed sound %d (%s:%d) invalid (%s)", index, _file->getDebugName().c_str(), _bundleTable[index].offset, tag2str(tag));
return false;
}
_compTable = (CompTable *)malloc(sizeof(CompTable) * _numCompItems);
assert(_compTable);
int32 maxSize = 0;
for (int i = 0; i < _numCompItems; i++) {
_compTable[i].offset = _file->readUint32BE();
_compTable[i].size = _file->readUint32BE();
_compTable[i].codec = _file->readUint32BE();
_file->seek(4, SEEK_CUR);
if (_compTable[i].size > maxSize)
maxSize = _compTable[i].size;
}
// CMI hack: one more byte at the end of input buffer
_compInputBuff = (byte *)malloc(maxSize + 1);
assert(_compInputBuff);
return true;
}
int32 BundleMgr::seekFile(int32 offset, int mode) {
// We don't actually seek the file, but instead try to find that the specified offset exists
// within the decompressed blocks, and save that offset in _curDecompressedFilePos
int result = 0;
switch (mode) {
case SEEK_END:
if (_isUncompressed) {
result = offset + _bundleTable[_curSampleId].size;
} else {
result = offset + ((_numCompItems - 1) * DIMUSE_BUN_CHUNK_SIZE) + _lastBlockDecompressedSize;
}
_curDecompressedFilePos = result;
break;
case SEEK_SET:
default:
if (_isUncompressed) {
result = offset;
_curDecompressedFilePos = result;
} else {
int destBlock = offset / DIMUSE_BUN_CHUNK_SIZE + (offset % DIMUSE_BUN_CHUNK_SIZE != 0);
if (destBlock <= _numCompItems) {
result = offset;
_curDecompressedFilePos = result;
}
}
break;
}
return result;
}
int32 BundleMgr::readFile(const char *name, int32 size, byte **comp_final, bool header_outside) {
int32 final_size = 0;
if (!_file->isOpen()) {
error("BundleMgr::readFile() File is not open");
return 0;
}
// Find the sound in the bundle
BundleDirCache::IndexNode target;
strncpy(target.filename, name, sizeof(target.filename));
target.filename[sizeof(target.filename) - 1] = '\0';
BundleDirCache::IndexNode *found = (BundleDirCache::IndexNode *)bsearch(&target, _indexTable, _numFiles,
sizeof(BundleDirCache::IndexNode), (int(*)(const void *, const void *))scumm_stricmp);
if (found) {
int32 i, finalSize, outputSize;
int skip, firstBlock, lastBlock;
int headerSize = 0;
assert(0 <= found->index && found->index < _numFiles);
if (_file->isOpen() == false) {
error("BundleMgr::readFile() File is not open");
return 0;
}
if (_curSampleId == -1)
_curSampleId = found->index;
assert(_curSampleId == found->index);
if (!_compTableLoaded) {
_compTableLoaded = loadCompTable(found->index);
if (!_compTableLoaded)
return 0;
}
if (_isUncompressed) {
_file->seek(_bundleTable[found->index].offset + _curDecompressedFilePos + headerSize, SEEK_SET);
*comp_final = (byte *)malloc(size);
assert(*comp_final);
_file->read(*comp_final, size);
_curDecompressedFilePos += size;
return size;
}
firstBlock = (_curDecompressedFilePos + headerSize) / DIMUSE_BUN_CHUNK_SIZE;
lastBlock = (_curDecompressedFilePos + headerSize + size - 1) / DIMUSE_BUN_CHUNK_SIZE;
// Clip last_block by the total number of blocks (= "comp items")
if ((lastBlock >= _numCompItems) && (_numCompItems > 0))
lastBlock = _numCompItems - 1;
int32 blocksFinalSize = DIMUSE_BUN_CHUNK_SIZE * (1 + lastBlock - firstBlock);
*comp_final = (byte *)malloc(blocksFinalSize);
assert(*comp_final);
finalSize = 0;
skip = (_curDecompressedFilePos + headerSize) % DIMUSE_BUN_CHUNK_SIZE; // Excess length after the last block
for (i = firstBlock; i <= lastBlock; i++) {
if (_lastBlock != i) {
// CMI hack: one more zero byte at the end of input buffer
_compInputBuff[_compTable[i].size] = 0;
_file->seek(_bundleTable[found->index].offset + _compTable[i].offset, SEEK_SET);
_file->read(_compInputBuff, _compTable[i].size);
_outputSize = BundleCodecs::decompressCodec(_compTable[i].codec, _compInputBuff, _compOutputBuff, _compTable[i].size);
if (_outputSize > DIMUSE_BUN_CHUNK_SIZE) {
error("_outputSize: %d", _outputSize);
}
_lastBlock = i;
}
outputSize = _outputSize;
if (header_outside) {
outputSize -= skip;
} else {
if ((headerSize != 0) && (skip >= headerSize))
outputSize -= skip;
}
if ((outputSize + skip) > DIMUSE_BUN_CHUNK_SIZE) // workaround
outputSize -= (outputSize + skip) - DIMUSE_BUN_CHUNK_SIZE;
if (outputSize > size)
outputSize = size;
assert(finalSize + outputSize <= blocksFinalSize);
memcpy(*comp_final + finalSize, _compOutputBuff + skip, outputSize);
finalSize += outputSize;
size -= outputSize;
assert(size >= 0);
if (size == 0)
break;
skip = 0;
}
_curDecompressedFilePos += finalSize;
return finalSize;
}
debug(2, "BundleMgr::readFile() Failed finding sound %s", name);
return final_size;
}
bool BundleMgr::isExtCompBun(byte gameId) {
bool isExtComp = false;
if (gameId == GID_CMI) {
bool isExtComp1 = false, isExtComp2 = false, isExtComp3 = false, isExtComp4 = false;
this->open("voxdisk1.bun", isExtComp1); this->close();
this->open("voxdisk2.bun", isExtComp2); this->close();
this->open("musdisk1.bun", isExtComp3); this->close();
this->open("musdisk2.bun", isExtComp4); this->close();
isExtComp = isExtComp1 | isExtComp2 | isExtComp3 | isExtComp4;
} else {
bool isExtComp1 = false, isExtComp2 = false;
this->open("digvoice.bun", isExtComp1); this->close();
this->open("digmusic.bun", isExtComp2); this->close();
isExtComp = isExtComp1 | isExtComp2;
}
return isExtComp;
}
} // End of namespace Scumm

View File

@@ -0,0 +1,112 @@
/* 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/>.
*
*/
#ifndef SCUMM_IMUSE_DIGI_BUNDLE_MGR_H
#define SCUMM_IMUSE_DIGI_BUNDLE_MGR_H
#include "common/scummsys.h"
#include "common/file.h"
#include "scumm/imuse_digi/dimuse_defs.h"
namespace Scumm {
class BaseScummFile;
class BundleDirCache {
public:
struct AudioTable {
char filename[24];
int32 offset;
int32 size;
};
struct IndexNode {
char filename[24];
int32 index;
};
private:
struct FileDirCache {
char fileName[20];
AudioTable *bundleTable;
int32 numFiles;
bool isCompressed;
IndexNode *indexTable;
} _bundleDirCache[4];
const ScummEngine *_vm;
public:
BundleDirCache(const ScummEngine *vm);
~BundleDirCache();
int matchFile(const char *filename);
AudioTable *getTable(int slot);
IndexNode *getIndexTable(int slot);
int32 getNumFiles(int slot);
bool isSndDataExtComp(int slot);
};
class BundleMgr {
private:
struct CompTable {
int32 offset;
int32 size;
int32 codec;
};
BundleDirCache *_cache;
BundleDirCache::AudioTable *_bundleTable;
BundleDirCache::IndexNode *_indexTable = nullptr;
CompTable *_compTable;
int _numFiles = 0;
int _numCompItems = 0;
int _lastBlockDecompressedSize = 0;
int _curSampleId = 0;
int _curDecompressedFilePos = 0;
BaseScummFile *_file;
bool _compTableLoaded = 0;
bool _isUncompressed = 0;
int _fileBundleId = 0;
byte _compOutputBuff[0x2000] = {};
byte *_compInputBuff = nullptr;
int _outputSize = 0;
int _lastBlock = 0;
bool loadCompTable(int32 index);
public:
BundleMgr(const ScummEngine *vm, BundleDirCache *_cache);
~BundleMgr();
bool open(const char *filename, bool &isCompressed, bool errorFlag = false);
void close();
Common::SeekableReadStream *getFile(const char *filename, int32 &offset, int32 &size);
int32 seekFile(int32 offset, int size);
int32 readFile(const char *name, int32 size, byte **compFinal, bool headerOutside);
bool isExtCompBun(byte gameId);
};
} // End of namespace Scumm
#endif

View File

@@ -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 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 "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
// We have some uintptr arguments as commands 28, 29 and 30 actually require pointer arguments
// Unfortunately this makes function calls for other command a little less pretty...
int IMuseDigital::cmdsHandleCmd(int cmd, uint8 *ptr, int a, int b, int c, int d, int e,
int f, int g, int h, int i, int j, int k, int l, int m, int n) {
// Convert the character constant (single quotes '') to string
char marker[5];
if (!_isEarlyDiMUSE && (cmd == 17 || cmd == 18 || cmd == 19)) {
for (int index = 0; index < 4; index++) {
#if defined SCUMM_BIG_ENDIAN
marker[index] = (b >> (8 * index)) & 0xff;
#elif defined SCUMM_LITTLE_ENDIAN
marker[3 - index] = (b >> (8 * index)) & 0xff;
#endif
}
marker[4] = '\0';
}
switch (cmd) {
case DIMUSE_C_INIT:
return cmdsInit();
case DIMUSE_C_PAUSE:
return cmdsPause();
case DIMUSE_C_RESUME:
return cmdsResume();
case DIMUSE_C_SET_GRP_VOL:
return _groupsHandler->setGroupVol(a, b);
case DIMUSE_C_START_SND:
cmdsStartSound(a, b);
break;
case DIMUSE_C_STOP_SND:
cmdsStopSound(a);
break;
case DIMUSE_C_STOP_ALL_SNDS:
cmdsStopAllSounds();
break;
case DIMUSE_C_GET_NEXT_SND:
return cmdsGetNextSound(a);
case DIMUSE_C_SET_PARAM:
cmdsSetParam(a, b, c);
break;
case DIMUSE_C_GET_PARAM:
return cmdsGetParam(a, b);
case DIMUSE_C_FADE_PARAM:
return _fadesHandler->fadeParam(a, b, c, d);
case DIMUSE_C_SET_HOOK:
return cmdsSetHook(a, b);
case DIMUSE_C_GET_HOOK:
return cmdsGetHook(a);
case DIMUSE_C_SET_TRIGGER:
return _triggersHandler->setTrigger(a, marker, c, d, e, f, g, h, i, j, k, l, m, n);
case DIMUSE_C_CHECK_TRIGGER:
return _triggersHandler->checkTrigger(a, marker, c);
case DIMUSE_C_CLEAR_TRIGGER:
return _triggersHandler->clearTrigger(a, marker, c);
case DIMUSE_C_DEFER_CMD:
return _triggersHandler->deferCommand(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
case DIMUSE_C_GET_MARKER_SYNCS:
_vm->_sound->extractSyncsFromDiMUSEMarker((char *)ptr);
break;
case DIMUSE_C_START_STREAM:
return waveStartStream(a, b, c);
case DIMUSE_C_SWITCH_STREAM:
if (_isEarlyDiMUSE)
return waveSwitchStream(a, b, ptr, d, e);
else
return waveSwitchStream(a, b, c, d, e);
case DIMUSE_C_PROCESS_STREAMS:
return waveProcessStreams();
case DIMUSE_C_FEED_STREAM:
return waveFeedStream(a, ptr, c, d);
default:
debug(5, "IMuseDigital::cmdsHandleCmd(): bogus/unused opcode ignored (%d).", cmd);
return -1;
}
return 0;
}
int IMuseDigital::cmdsInit() {
_cmdsRunning60HzCount = 0;
_cmdsRunning10HzCount = 0;
if (_groupsHandler->init() || _fadesHandler->init() ||
_triggersHandler->init() || waveInit()) {
return -1;
}
_cmdsPauseCount = 0;
return 48;
}
int IMuseDigital::cmdsDeinit() {
waveTerminate();
waveOutDeinit();
_triggersHandler->deinit();
_fadesHandler->deinit();
_cmdsPauseCount = 0;
_cmdsRunning60HzCount = 0;
_cmdsRunning10HzCount = 0;
return 0;
}
int IMuseDigital::cmdsTerminate() {
return 0;
}
int IMuseDigital::cmdsPause() {
int result = 0;
if (_cmdsPauseCount == 0) {
result = wavePause();
}
if (!result) {
result = _cmdsPauseCount + 1;
}
_cmdsPauseCount++;
return result;
}
int IMuseDigital::cmdsResume() {
int result = 0;
if (_cmdsPauseCount == 1) {
result = waveResume();
}
if (_cmdsPauseCount != 0) {
_cmdsPauseCount--;
}
if (!result) {
result = _cmdsPauseCount;
}
return result;
}
void IMuseDigital::cmdsSaveLoad(Common::Serializer &ser) {
// Serialize in this order:
// - Open files
// - Fades
// - Triggers
// - Pass the control to waveSaveLoad and then tracksSaveLoad, which will serialize:
// - Dispatches
// - Tracks (with SYNCs, if the game is COMI)
// - State and sequence info
// - Attributes
// - Full Throttle's music cue ID
_filesHandler->saveLoad(ser);
_fadesHandler->saveLoad(ser);
_triggersHandler->saveLoad(ser);
waveSaveLoad(ser);
ser.syncAsSint32LE(_curMusicState, VER(103));
ser.syncAsSint32LE(_curMusicSeq, VER(103));
ser.syncAsSint32LE(_nextSeqToPlay, VER(103));
ser.syncAsByte(_radioChatterSFX, VER(103));
ser.syncArray(_attributes, 188, Common::Serializer::Sint32LE, VER(103));
ser.syncAsSint32LE(_curMusicCue, VER(103));
}
int IMuseDigital::cmdsStartSound(int soundId, int priority) {
uint8 *src = _filesHandler->getSoundAddrData(soundId);
if (src == nullptr) {
debug(5, "IMuseDigital::cmdsStartSound(): ERROR: resource address for sound %d is NULL", soundId);
return -1;
}
// Check for the "Creative Voice File" header
if (_isEarlyDiMUSE && READ_BE_UINT32(src) == MKTAG('C', 'r', 'e', 'a'))
return waveStartSound(soundId, priority);
// Check for the "iMUS" header
if (READ_BE_UINT32(src) == MKTAG('i', 'M', 'U', 'S'))
return waveStartSound(soundId, priority);
return -1;
}
int IMuseDigital::cmdsStopSound(int soundId) {
int result = _filesHandler->getNextSound(soundId);
if (result != 2)
return -1;
return waveStopSound(soundId);
}
int IMuseDigital::cmdsStopAllSounds() {
return _triggersHandler->clearAllTriggers() | waveStopAllSounds();
}
int IMuseDigital::cmdsGetNextSound(int soundId) {
return waveGetNextSound(soundId);
}
int IMuseDigital::cmdsSetParam(int soundId, int subCmd, int value) {
int result = _filesHandler->getNextSound(soundId);
if (result != 2)
return -1;
return waveSetParam(soundId, subCmd, value);
}
int IMuseDigital::cmdsGetParam(int soundId, int subCmd) {
int result = _filesHandler->getNextSound(soundId);
if (subCmd != 0) {
if (subCmd == DIMUSE_P_TRIGS_SNDS) {
return _triggersHandler->countPendingSounds(soundId);
}
if (result == 2) {
return waveGetParam(soundId, subCmd);
}
result = (subCmd == DIMUSE_P_SND_TRACK_NUM) - 1;
}
return result;
}
int IMuseDigital::cmdsSetHook(int soundId, int hookId) {
int result = _filesHandler->getNextSound(soundId);
if (result != 2)
return -1;
return waveSetHook(soundId, hookId);
}
int IMuseDigital::cmdsGetHook(int soundId) {
int result = _filesHandler->getNextSound(soundId);
if (result != 2)
return -1;
return waveGetHook(soundId);
}
} // End of namespace Scumm

View File

@@ -0,0 +1,643 @@
/* 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/scummsys.h"
#include "common/endian.h"
#include "common/util.h"
#include "scumm/imuse_digi/dimuse_codecs.h"
#include "audio/decoders/adpcm_intern.h"
namespace Scumm {
namespace BundleCodecs {
/*
* The "IMC" codec below (see cases 13 & 15 in decompressCodec) is actually a
* variant of the IMA codec, see also
* <https://multimedia.cx/simpleaudio.html>
*
* It is somewhat different, though: the standard ADPCM codecs use a fixed
* size for their data packets (4 bits), while the codec implemented here
* varies the size of each "packet" between 2 and 7 bits.
*/
static byte *_destImcTable = nullptr;
static uint32 *_destImcTable2 = nullptr;
// This table is the "big brother" of Audio::ADPCMStream::_stepAdjustTable.
static const byte imxOtherTable[6][64] = {
{
0xFF,
4
},
{
0xFF, 0xFF,
2, 8
},
{
0xFF, 0xFF, 0xFF, 0xFF,
1, 2, 4, 6
},
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1, 2, 4, 6, 8, 12, 16, 32
},
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1, 2, 4, 6, 8, 10, 12, 14,
16, 18, 20, 22, 24, 26, 28, 32
},
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32
}
};
void releaseImcTables() {
free(_destImcTable);
_destImcTable = nullptr;
free(_destImcTable2);
_destImcTable2 = nullptr;
}
void initializeImcTables() {
int pos;
if (!_destImcTable) _destImcTable = (byte *)calloc(89, sizeof(byte));
if (!_destImcTable2) _destImcTable2 = (uint32 *)calloc(89 * 64, sizeof(uint32));
for (pos = 0; pos <= 88; ++pos) {
byte put = 1;
int32 tableValue = ((Audio::Ima_ADPCMStream::_imaTable[pos] * 4) / 7) / 2;
while (tableValue != 0) {
tableValue /= 2;
put++;
}
if (put < 3) {
put = 3;
}
if (put > 8) {
put = 8;
}
_destImcTable[pos] = put - 1;
}
for (int n = 0; n < 64; n++) {
for (pos = 0; pos <= 88; ++pos) {
int32 count = 32;
int32 put = 0;
int32 tableValue = Audio::Ima_ADPCMStream::_imaTable[pos];
do {
if ((count & n) != 0) {
put += tableValue;
}
count /= 2;
tableValue /= 2;
} while (count != 0);
_destImcTable2[n + pos * 64] = put;
}
}
}
#define NextBit \
do { \
bit = mask & 1; \
mask >>= 1; \
if (!--bitsleft) { \
mask = READ_LE_UINT16(srcptr); \
srcptr += 2; \
bitsleft = 16; \
} \
} while (0)
static int32 compDecode(byte *src, byte *dst) {
byte *result, *srcptr = src, *dstptr = dst;
int data, size, bit, bitsleft = 16, mask = READ_LE_UINT16(srcptr);
srcptr += 2;
for (;;) {
NextBit;
if (bit) {
*dstptr++ = *srcptr++;
} else {
NextBit;
if (!bit) {
NextBit;
size = bit << 1;
NextBit;
size = (size | bit) + 3;
data = *srcptr++ | 0xffffff00;
} else {
data = *srcptr++;
size = *srcptr++;
data |= 0xfffff000 + ((size & 0xf0) << 4);
size = (size & 0x0f) + 3;
if (size == 3)
if (((*srcptr++) + 1) == 1)
return dstptr - dst;
}
result = dstptr + data;
while (size--)
*dstptr++ = *result++;
}
}
}
#undef NextBit
int32 decompressADPCM(byte *compInput, byte *compOutput, int channels) {
byte *src;
// Decoder for the IMA ADPCM variants used in COMI.
// Contrary to regular IMA ADPCM, this codec uses a variable
// bitsize for the encoded data.
const int MAX_CHANNELS = 2;
int32 outputSamplesLeft;
int32 destPos;
int16 firstWord;
byte initialTablePos[MAX_CHANNELS] = {0, 0};
//int32 initialimcTableEntry[MAX_CHANNELS] = {7, 7};
int32 initialOutputWord[MAX_CHANNELS] = {0, 0};
int32 totalBitOffset, curTablePos, outputWord;
byte *dst;
int i;
// We only support mono and stereo
assert(channels == 1 || channels == 2);
src = compInput;
dst = compOutput;
outputSamplesLeft = 0x1000;
// Every data packet contains 0x2000 bytes of audio data
// when extracted. In order to encode bigger data sets,
// one has to split the data into multiple blocks.
//
// Every block starts with a 2 byte word. If that word is
// non-zero, it indicates the size of a block of raw audio
// data (not encoded) following it. That data we simply copy
// to the output buffer and then proceed by decoding the
// remaining data.
//
// If on the other hand the word is zero, then what follows
// are 7*channels bytes containing seed data for the decoder.
firstWord = READ_BE_UINT16(src);
src += 2;
if (firstWord != 0) {
// Copy raw data
memcpy(dst, src, firstWord);
dst += firstWord;
src += firstWord;
assert((firstWord & 1) == 0);
outputSamplesLeft -= firstWord / 2;
} else {
// Read the seed values for the decoder.
for (i = 0; i < channels; i++) {
initialTablePos[i] = *src;
src += 1;
//initialimcTableEntry[i] = READ_BE_UINT32(src);
src += 4;
initialOutputWord[i] = READ_BE_UINT32(src);
src += 4;
}
}
totalBitOffset = 0;
// The channels are encoded separately.
for (int chan = 0; chan < channels; chan++) {
// Read initial state (this makes it possible for the data stream
// to be split & spread across multiple data chunks.
curTablePos = initialTablePos[chan];
//imcTableEntry = initialimcTableEntry[chan];
outputWord = initialOutputWord[chan];
// We need to interleave the channels in the output; we achieve
// that by using a variables dest offset:
destPos = chan * 2;
const int bound = (channels == 1)
? outputSamplesLeft
: ((chan == 0)
? (outputSamplesLeft+1) / 2
: outputSamplesLeft / 2);
for (i = 0; i < bound; ++i) {
// Determine the size (in bits) of the next data packet
const int32 curTableEntryBitCount = _destImcTable[curTablePos];
assert(2 <= curTableEntryBitCount && curTableEntryBitCount <= 7);
// Read the next data packet
const byte *readPos = src + (totalBitOffset >> 3);
const uint16 readWord = (uint16)(READ_BE_UINT16(readPos) << (totalBitOffset & 7));
const byte packet = (byte)(readWord >> (16 - curTableEntryBitCount));
// Advance read position to the next data packet
totalBitOffset += curTableEntryBitCount;
// Decode the data packet into a delta value for the output signal.
const byte signBitMask = (1 << (curTableEntryBitCount - 1));
const byte dataBitMask = (signBitMask - 1);
const byte data = (packet & dataBitMask);
const int32 tmpA = (data << (7 - curTableEntryBitCount));
const int32 imcTableEntry = Audio::Ima_ADPCMStream::_imaTable[curTablePos] >> (curTableEntryBitCount - 1);
int32 delta = imcTableEntry + _destImcTable2[tmpA + (curTablePos * 64)];
// The topmost bit in the data packet tells is a sign bit
if ((packet & signBitMask) != 0) {
delta = -delta;
}
// Accumulate the delta onto the output data
outputWord += delta;
// Clip outputWord to 16 bit signed, and write it into the destination stream
outputWord = CLIP<int32>(outputWord, -0x8000, 0x7fff);
// This is being written as-is (LE), without concerns regarding endianness:
// this is because the internal DiMUSE mixer handles the data in LE format,
// and we'll convert data to the appropriate format using the QueuingAudioStream flags
// when flushing the final audio data to the output stream (see IMuseDigital::waveOutWrite())
WRITE_UINT16(dst + destPos, outputWord);
destPos += channels << 1;
// Adjust the curTablePos
curTablePos += (int8)imxOtherTable[curTableEntryBitCount - 2][data];
curTablePos = CLIP<int32>(curTablePos, 0, ARRAYSIZE(Audio::Ima_ADPCMStream::_imaTable) - 1);
}
}
return 0x2000;
}
int32 decompressCodec(int32 codec, byte *compInput, byte *compOutput, int32 inputSize) {
int32 outputSize;
int32 offset1, offset2, offset3, length, k, c, s, j, r, t, z;
byte *src, *t_table, *p, *ptr;
byte t_tmp1, t_tmp2;
switch (codec) {
case 0:
memcpy(compOutput, compInput, inputSize);
outputSize = inputSize;
break;
case 1:
outputSize = compDecode(compInput, compOutput);
break;
case 2:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
break;
case 3:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 2; z < outputSize; z++)
p[z] += p[z - 1];
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
break;
case 4:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 2; z < outputSize; z++)
p[z] += p[z - 1];
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
t_table = (byte *)calloc(outputSize, 1);
assert(t_table);
src = compOutput;
length = (outputSize << 3) / 12;
k = 0;
if (length > 0) {
c = -12;
s = 0;
j = 0;
do {
ptr = src + length + (k >> 1);
t_tmp2 = src[j];
if (k & 1) {
r = c >> 3;
t_table[r + 2] = ((t_tmp2 & 0x0f) << 4) | (ptr[1] >> 4);
t_table[r + 1] = (t_tmp2 & 0xf0) | (t_table[r + 1]);
} else {
r = s >> 3;
t_table[r + 0] = ((t_tmp2 & 0x0f) << 4) | (ptr[0] & 0x0f);
t_table[r + 1] = t_tmp2 >> 4;
}
s += 12;
c += 12;
k++;
j++;
} while (k < length);
}
offset1 = ((length - 1) * 3) >> 1;
t_table[offset1 + 1] = (t_table[offset1 + 1]) | (src[length - 1] & 0xf0);
memcpy(src, t_table, outputSize);
free(t_table);
break;
case 5:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 2; z < outputSize; z++)
p[z] += p[z - 1];
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
t_table = (byte *)malloc(outputSize);
assert(t_table);
src = compOutput;
length = (outputSize << 3) / 12;
k = 1;
c = 0;
s = 12;
t_table[0] = src[length] >> 4;
t = length + k;
j = 1;
if (t > k) {
do {
t_tmp1 = *(src + length + (k >> 1));
t_tmp2 = src[j - 1];
if (k & 1) {
r = c >> 3;
t_table[r + 0] = (t_tmp2 & 0xf0) | t_table[r];
t_table[r + 1] = ((t_tmp2 & 0x0f) << 4) | (t_tmp1 & 0x0f);
} else {
r = s >> 3;
t_table[r + 0] = t_tmp2 >> 4;
t_table[r - 1] = ((t_tmp2 & 0x0f) << 4) | (t_tmp1 >> 4);
}
s += 12;
c += 12;
k++;
j++;
} while (k < t);
}
memcpy(src, t_table, outputSize);
free(t_table);
break;
case 6:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 2; z < outputSize; z++)
p[z] += p[z - 1];
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
t_table = (byte *)malloc(outputSize);
assert(t_table);
src = compOutput;
length = (outputSize << 3) / 12;
k = 0;
c = 0;
j = 0;
s = -12;
t_table[0] = src[outputSize - 1];
t_table[outputSize - 1] = src[length - 1];
t = length - 1;
if (t > 0) {
do {
t_tmp1 = *(src + length + (k >> 1));
t_tmp2 = src[j];
if (k & 1) {
r = s >> 3;
t_table[r + 2] = (t_tmp2 & 0xf0) | t_table[r + 2];
t_table[r + 3] = ((t_tmp2 & 0x0f) << 4) | (t_tmp1 >> 4);
} else {
r = c >> 3;
t_table[r + 2] = t_tmp2 >> 4;
t_table[r + 1] = ((t_tmp2 & 0x0f) << 4) | (t_tmp1 & 0x0f);
}
s += 12;
c += 12;
k++;
j++;
} while (k < t);
}
memcpy(src, t_table, outputSize);
free(t_table);
break;
case 10:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 2; z < outputSize; z++)
p[z] += p[z - 1];
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
t_table = (byte *)malloc(outputSize);
assert(t_table);
memcpy(t_table, p, outputSize);
offset1 = outputSize / 3;
offset2 = offset1 << 1;
offset3 = offset2;
src = compOutput;
while (offset1--) {
offset2 -= 2;
offset3--;
t_table[offset2 + 0] = src[offset1];
t_table[offset2 + 1] = src[offset3];
}
src = compOutput;
length = (outputSize << 3) / 12;
k = 0;
if (length > 0) {
c = -12;
s = 0;
do {
j = length + (k >> 1);
t_tmp1 = t_table[k];
if (k & 1) {
r = c >> 3;
t_tmp2 = t_table[j + 1];
src[r + 2] = ((t_tmp1 & 0x0f) << 4) | (t_tmp2 >> 4);
src[r + 1] = (src[r + 1]) | (t_tmp1 & 0xf0);
} else {
r = s >> 3;
t_tmp2 = t_table[j];
src[r + 0] = ((t_tmp1 & 0x0f) << 4) | (t_tmp2 & 0x0f);
src[r + 1] = t_tmp1 >> 4;
}
s += 12;
c += 12;
k++;
} while (k < length);
}
offset1 = ((length - 1) * 3) >> 1;
src[offset1 + 1] = (t_table[length] & 0xf0) | src[offset1 + 1];
free(t_table);
break;
case 11:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 2; z < outputSize; z++)
p[z] += p[z - 1];
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
t_table = (byte *)malloc(outputSize);
assert(t_table);
memcpy(t_table, p, outputSize);
offset1 = outputSize / 3;
offset2 = offset1 << 1;
offset3 = offset2;
src = compOutput;
while (offset1--) {
offset2 -= 2;
offset3--;
t_table[offset2 + 0] = src[offset1];
t_table[offset2 + 1] = src[offset3];
}
src = compOutput;
length = (outputSize << 3) / 12;
k = 1;
c = 0;
s = 12;
t_tmp1 = t_table[length] >> 4;
src[0] = t_tmp1;
t = length + k;
if (t > k) {
do {
j = length + (k >> 1);
t_tmp1 = t_table[k - 1];
t_tmp2 = t_table[j];
if (k & 1) {
r = c >> 3;
src[r + 0] = (src[r]) | (t_tmp1 & 0xf0);
src[r + 1] = ((t_tmp1 & 0x0f) << 4) | (t_tmp2 & 0x0f);
} else {
r = s >> 3;
src[r + 0] = t_tmp1 >> 4;
src[r - 1] = ((t_tmp1 & 0x0f) << 4) | (t_tmp2 >> 4);
}
s += 12;
c += 12;
k++;
} while (k < t);
}
free(t_table);
break;
case 12:
outputSize = compDecode(compInput, compOutput);
p = compOutput;
for (z = 2; z < outputSize; z++)
p[z] += p[z - 1];
for (z = 1; z < outputSize; z++)
p[z] += p[z - 1];
t_table = (byte *)malloc(outputSize);
assert(t_table);
memcpy(t_table, p, outputSize);
offset1 = outputSize / 3;
offset2 = offset1 << 1;
offset3 = offset2;
src = compOutput;
while (offset1--) {
offset2 -= 2;
offset3--;
t_table[offset2 + 0] = src[offset1];
t_table[offset2 + 1] = src[offset3];
}
src = compOutput;
length = (outputSize << 3) / 12;
k = 0;
c = 0;
s = -12;
src[0] = t_table[outputSize - 1];
src[outputSize - 1] = t_table[length - 1];
t = length - 1;
if (t > 0) {
do {
j = length + (k >> 1);
t_tmp1 = t_table[k];
t_tmp2 = t_table[j];
if (k & 1) {
r = s >> 3;
src[r + 2] = (src[r + 2]) | (t_tmp1 & 0xf0);
src[r + 3] = ((t_tmp1 & 0x0f) << 4) | (t_tmp2 >> 4);
} else {
r = c >> 3;
src[r + 2] = t_tmp1 >> 4;
src[r + 1] = ((t_tmp1 & 0x0f) << 4) | (t_tmp2 & 0x0f);
}
s += 12;
c += 12;
k++;
} while (k < t);
}
free(t_table);
break;
case 13:
case 15:
outputSize = decompressADPCM(compInput, compOutput, (codec == 13) ? 1 : 2);
break;
default:
error("BundleCodecs::decompressCodec() Unknown codec %d", (int)codec);
outputSize = 0;
break;
}
return outputSize;
}
} // End of namespace BundleCodecs
} // End of namespace Scumm

View File

@@ -0,0 +1,40 @@
/* 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/>.
*
*/
#ifndef SCUMM_IMUSE_DIGI_CODECS_H
#define SCUMM_IMUSE_DIGI_CODECS_H
#include "common/scummsys.h"
namespace Scumm {
namespace BundleCodecs {
int32 decompressCodec(int32 codec, byte *compInput, byte *compOutput, int32 inputSize);
void initializeImcTables();
void releaseImcTables();
} // End of namespace BundleCodecs
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,280 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_DEFS_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_DEFS_H
namespace Scumm {
#define DIMUSE_MAX_GROUPS 16
#define DIMUSE_MAX_FADES 16
#define DIMUSE_MAX_TRIGGERS 8
#define DIMUSE_MAX_DEFERS 8
#define DIMUSE_MAX_TRACKS 8
#define DIMUSE_MAX_MAP_SIZE 2048
#define DIMUSE_MAX_DISPATCHES 8
#define DIMUSE_MAX_STREAMZONES 50
#define DIMUSE_MAX_FADE_VOLUME 8323072
#define DIMUSE_MAX_STREAMS 3
#define DIMUSE_LARGE_FADES 1
#define DIMUSE_SMALL_FADES 4
#define DIMUSE_LARGE_FADE_DIM 350000
#define DIMUSE_SMALL_FADE_DIM 44100
#define DIMUSE_BASE_SAMPLERATE 22050
#define DIMUSE_BASE_FEEDSIZE 512
#define DIMUSE_NUM_WAVE_BUFS 8
#define DIMUSE_SMUSH_SOUNDID 12345678
#define DIMUSE_BUN_CHUNK_SIZE 0x2000
#define DIMUSE_GROUP_SFX 1
#define DIMUSE_GROUP_SPEECH 2
#define DIMUSE_GROUP_MUSIC 3
#define DIMUSE_GROUP_MUSICEFF 4
#define DIMUSE_BUFFER_SPEECH 1
#define DIMUSE_BUFFER_MUSIC 2
#define DIMUSE_BUFFER_SFX 3
#define DIMUSE_TIMER_BASE_RATE_HZ 50
#define DIMUSE_TIMER_BASE_RATE_USEC 20000 // 1000000 / 50Hz
#define DIMUSE_TIMER_GAIN_RED_RATE_USEC 100000 // 1000000 / 10Hz
#define DIMUSE_TIMER_FADES_RATE_USEC 16667 // 1000000 / 60Hz
// Parameters IDs
#define DIMUSE_P_BOGUS_ID 0x0
#define DIMUSE_P_SND_TRACK_NUM 0x100
#define DIMUSE_P_TRIGS_SNDS 0x200
#define DIMUSE_P_MARKER 0x300
#define DIMUSE_P_GROUP 0x400
#define DIMUSE_P_PRIORITY 0x500
#define DIMUSE_P_VOLUME 0x600
#define DIMUSE_P_PAN 0x700
#define DIMUSE_P_DETUNE 0x800
#define DIMUSE_P_TRANSPOSE 0x900
#define DIMUSE_P_MAILBOX 0xA00
#define DIMUSE_P_UNKNOWN 0xF00
#define DIMUSE_P_SND_HAS_STREAM 0x1800
#define DIMUSE_P_STREAM_BUFID 0x1900
#define DIMUSE_P_SND_POS_IN_MS 0x1A00
// Soundkludge command IDs
#define DIMUSE_C_KLUDGE_SET_STATE 0x1000
#define DIMUSE_C_KLUDGE_SET_SEQUENCE 0x1001
#define DIMUSE_C_KLUDGE_SET_CUE_POINT 0x1002
#define DIMUSE_C_KLUDGE_SET_ATTRIBUTE 0x1003
#define DIMUSE_C_KLUDGE_SET_SFX_VOLUME 0x2000
#define DIMUSE_C_KLUDGE_SET_VOICE_VOLUME 0x2001
#define DIMUSE_C_KLUDGE_SET_MUSIC_VOLUME 0x2002
#define DIMUSE_C_KLUDGE_STOP_ALL_SNDS 10
#define DIMUSE_C_KLUDGE_SET_PARAM 12
#define DIMUSE_C_KLUDGE_FADE_PARAM 14
#define DIMUSE_C_KLUDGE_START_STREAM 25
#define DIMUSE_C_KLUDGE_SWITCH_STREAM 26
// Script command IDs
#define DIMUSE_C_SCRIPT_INIT 0
#define DIMUSE_C_SCRIPT_TERMINATE 1
#define DIMUSE_C_SCRIPT_SAVE 2
#define DIMUSE_C_SCRIPT_RESTORE 3
#define DIMUSE_C_SCRIPT_REFRESH 4
#define DIMUSE_C_SCRIPT_SET_STATE 5
#define DIMUSE_C_SCRIPT_SET_SEQUENCE 6
#define DIMUSE_C_SCRIPT_CUE_POINT 7
#define DIMUSE_C_SCRIPT_SET_ATTRIBUTE 8
// Internal command IDs
#define DIMUSE_C_INIT 0
#define DIMUSE_C_PAUSE 3
#define DIMUSE_C_RESUME 4
#define DIMUSE_C_SET_GRP_VOL 7
#define DIMUSE_C_START_SND 8
#define DIMUSE_C_STOP_SND 9
#define DIMUSE_C_STOP_ALL_SNDS 10
#define DIMUSE_C_GET_NEXT_SND 11
#define DIMUSE_C_SET_PARAM 12
#define DIMUSE_C_GET_PARAM 13
#define DIMUSE_C_FADE_PARAM 14
#define DIMUSE_C_SET_HOOK 15
#define DIMUSE_C_GET_HOOK 16
#define DIMUSE_C_SET_TRIGGER 17
#define DIMUSE_C_CHECK_TRIGGER 18
#define DIMUSE_C_CLEAR_TRIGGER 19
#define DIMUSE_C_DEFER_CMD 20
#define DIMUSE_C_GET_MARKER_SYNCS 21
#define DIMUSE_C_START_STREAM 25
#define DIMUSE_C_SWITCH_STREAM 26
#define DIMUSE_C_PROCESS_STREAMS 27
#define DIMUSE_C_FEED_STREAM 29
// Trigger callback command ID
#define DIMUSE_C_SCRIPT_CALLBACK 0
// Block IDs for the Creative Voice File format
// used within Full Throttle and The Dig (demo)
#define VOC_DIGI_DATA_BLOCK 1
#define VOC_MARKER_BLOCK 4
#define VOC_LOOP_START_BLOCK 6
#define VOC_LOOP_END_BLOCK 7
struct IMuseDigiDispatch;
struct IMuseDigiTrack;
struct IMuseDigiStreamZone;
typedef struct {
int sound;
char text[256];
int opcode;
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
int j;
int clearLater;
} IMuseDigiTrigger;
typedef struct {
int counter;
int opcode;
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
int j;
} IMuseDigiDefer;
typedef struct {
int status;
int sound;
int param;
int currentVal;
int counter;
int length;
int slope;
int slopeMod;
int modOvfloCounter;
int nudge;
} IMuseDigiFade;
struct IMuseDigiTrack {
int index;
IMuseDigiTrack *prev;
IMuseDigiTrack *next;
IMuseDigiDispatch *dispatchPtr;
int soundId;
int marker;
int group;
int priority;
int vol;
int effVol;
int pan;
int detune;
int transpose;
int pitchShift;
int mailbox;
int jumpHook;
int32 syncSize_0;
byte *syncPtr_0;
int32 syncSize_1;
byte *syncPtr_1;
int32 syncSize_2;
byte *syncPtr_2;
int32 syncSize_3;
byte *syncPtr_3;
};
struct IMuseDigiStreamZone {
IMuseDigiStreamZone *prev;
IMuseDigiStreamZone *next;
int useFlag;
int32 offset;
int32 size;
int fadeFlag;
};
typedef struct {
int soundId;
int32 curOffset;
int32 endOffset;
int bufId;
uint8 *buf;
int32 bufFreeSize;
int32 loadSize;
int32 criticalSize;
int32 maxRead;
int32 loadIndex;
int32 readIndex;
int paused;
int vocLoopFlag;
int32 vocLoopTriggerOffset;
} IMuseDigiStream;
typedef struct {
uint8 *buffer;
int32 bufSize;
int32 loadSize;
int32 criticalSize;
} IMuseDigiSndBuffer;
struct IMuseDigiDispatch {
IMuseDigiTrack *trackPtr;
int wordSize;
int sampleRate;
int channelCount;
int32 currentOffset;
int32 audioRemaining;
int32 map[DIMUSE_MAX_MAP_SIZE];
IMuseDigiStream *streamPtr;
int streamBufID;
IMuseDigiStreamZone *streamZoneList;
int streamErrFlag;
uint8 *fadeBuf;
int32 fadeOffset;
int32 fadeRemaining;
int fadeWordSize;
int fadeSampleRate;
int fadeChannelCount;
int fadeSyncFlag;
int32 fadeSyncDelta;
int fadeVol;
int fadeSlope;
int32 vocLoopStartingPoint;
};
typedef struct {
int bytesPerSample;
int numChannels;
uint8 *mixBuf;
int mixBufSize;
int sizeSampleKB;
} waveOutParamsStruct;
} // End of namespace Scumm
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_H
#include "common/scummsys.h"
#include "common/mutex.h"
#include "common/serializer.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "scumm/scumm_v7.h"
#include "scumm/music.h"
#include "scumm/sound.h"
#include "scumm/file.h"
#include "scumm/debugger.h"
#include "scumm/imuse_digi/dimuse_defs.h"
#include "scumm/imuse_digi/dimuse_internalmixer.h"
#include "scumm/imuse_digi/dimuse_groups.h"
#include "scumm/imuse_digi/dimuse_fades.h"
#include "scumm/imuse_digi/dimuse_files.h"
#include "scumm/imuse_digi/dimuse_triggers.h"
#include "scumm/imuse_digi/dimuse_bndmgr.h"
#include "scumm/imuse_digi/dimuse_sndmgr.h"
#include "scumm/imuse_digi/dimuse_tables.h"
#include "scumm/smush/smush_player.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
namespace Audio {
class AudioStream;
class Mixer;
class QueuingAudioStream;
}
namespace Scumm {
class ScummEngine_v7;
class SmushPlayer;
struct imuseDigTable;
struct imuseComiTable;
struct IMuseDigiDispatch;
struct IMuseDigiTrack;
struct IMuseDigiStreamZone;
class IMuseDigital : public MusicEngine {
private:
Common::Mutex *_mutex;
ScummEngine_v7 *_vm;
Audio::Mixer *_mixer;
SmushPlayer *_splayer;
IMuseDigiInternalMixer *_internalMixer;
IMuseDigiGroupsHandler *_groupsHandler;
IMuseDigiFadesHandler *_fadesHandler;
IMuseDigiTriggersHandler *_triggersHandler;
IMuseDigiFilesHandler *_filesHandler;
int _callbackFps;
static void timer_handler(void *refConf);
void callback();
bool _isEarlyDiMUSE;
bool _isEngineDisabled;
bool _checkForUnderrun;
int _underrunCooldown;
bool _lowLatencyMode;
int _internalFeedSize;
int _internalSampleRate;
// These three are manipulated in the waveOut functions
uint8 *_outputAudioBuffer;
int _outputFeedSize;
int _outputSampleRate;
// Used in low latency mode only
uint8 *_outputLowLatencyAudioBuffers[DIMUSE_MAX_TRACKS];
int _maxQueuedStreams; // maximum number of streams which can be queued before they are played
int _nominalBufferCount;
int _currentSpeechVolume, _currentSpeechFrequency, _currentSpeechPan;
int _curMixerMusicVolume, _curMixerSpeechVolume, _curMixerSFXVolume;
bool _radioChatterSFX;
int32 _attributes[188]; // internal attributes for each music file to store and check later
int32 _nextSeqToPlay;
int32 _curMusicState;
int32 _curMusicSeq;
int32 _curMusicCue;
char *_audioNames; // filenames of sound SFX used in FT
int32 _numAudioNames; // number of above filenames
uint8 _currentVOCHeader[52]; // Header for the current sound for early DiMUSE
int _stopSequenceFlag;
int _scriptInitializedFlag;
char _emptyMarker[1];
bool _spooledMusicEnabled;
int _usecPerInt; // Microseconds between each callback (will be set to 50 Hz)
int _callbackInterruptFlag;
void diMUSEHeartbeat();
void setFtMusicState(int stateId);
void setFtMusicSequence(int seqId);
void playFtMusic(const char *songName, int opcode, int volume);
void setDigMusicState(int stateId);
void setDigMusicSequence(int seqId);
void playDigMusic(const char *songName, const imuseDigTable *table, int attribPos, bool sequence);
void setComiMusicState(int stateId);
void setComiMusicSequence(int seqId);
void playComiMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence);
void playComiDemoMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence);
int getSoundIdByName(const char *soundName);
bool isMusicStreamIdle();
bool isMusicCritical();
// Script
int scriptParse(int cmd, int a, int b);
int scriptInit();
int scriptTerminate();
void scriptRefresh();
void scriptSetState(int soundId);
void scriptSetSequence(int soundId);
void scriptSetCuePoint(int cueId);
int scriptSetAttribute(int attrIndex, int attrVal);
// CMDs
int _cmdsPauseCount;
int _cmdsRunning60HzCount;
int _cmdsRunning10HzCount;
int cmdsInit();
int cmdsDeinit();
int cmdsTerminate();
int cmdsPause();
int cmdsResume();
void cmdsSaveLoad(Common::Serializer &ser);
int cmdsStartSound(int soundId, int priority);
int cmdsStopSound(int soundId);
int cmdsStopAllSounds();
int cmdsGetNextSound(int soundId);
int cmdsSetParam(int soundId, int opcode, int value);
int cmdsGetParam(int soundId, int opcode);
int cmdsSetHook(int soundId, int hookId);
int cmdsGetHook(int soundId);
// Streamer
IMuseDigiStream _streams[DIMUSE_MAX_STREAMS];
IMuseDigiStream *_lastStreamLoaded;
int _streamerBailFlag;
int streamerInit();
IMuseDigiStream *streamerAllocateSound(int soundId, int bufId, int32 maxRead);
int streamerClearSoundInStream(IMuseDigiStream *streamPtr);
int streamerProcessStreams();
uint8 *streamerGetStreamBuffer(IMuseDigiStream *streamPtr, int size);
uint8 *streamerGetStreamBufferAtOffset(IMuseDigiStream *streamPtr, int32 offset, int size);
int streamerSetReadIndex(IMuseDigiStream *streamPtr, int offset);
int streamerSetLoadIndex(IMuseDigiStream *streamPtr, int offset);
int streamerGetFreeBufferAmount(IMuseDigiStream *streamPtr);
int streamerSetSoundToStreamFromOffset(IMuseDigiStream *streamPtr, int soundId, int32 offset);
void streamerQueryStream(IMuseDigiStream *streamPtr, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused);
int streamerFeedStream(IMuseDigiStream *streamPtr, uint8 *srcBuf, int32 sizeToFeed, int paused);
int streamerFetchData(IMuseDigiStream *streamPtr);
void streamerSetLoopFlag(IMuseDigiStream *streamPtr, int offset);
void streamerRemoveLoopFlag(IMuseDigiStream *streamPtr);
// Tracks
IMuseDigiTrack _tracks[DIMUSE_MAX_TRACKS];
IMuseDigiTrack *_trackList;
int _trackCount;
int _tracksPauseTimer;
int _tracksMicroSecsToFeed;
int tracksInit();
void tracksPause();
void tracksResume();
void tracksSaveLoad(Common::Serializer &ser);
void tracksSetGroupVol();
void tracksCallback();
void tracksLowLatencyCallback();
int tracksStartSound(int soundId, int tryPriority, int group);
int tracksStopSound(int soundId);
int tracksStopAllSounds();
int tracksGetNextSound(int soundId);
int tracksQueryStream(int soundId, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused);
int tracksFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused);
void tracksClear(IMuseDigiTrack *trackPtr);
int tracksSetParam(int soundId, int opcode, int value);
int tracksGetParam(int soundId, int opcode);
int tracksLipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height);
int tracksSetHook(int soundId, int hookId);
int tracksGetHook(int soundId);
IMuseDigiTrack *tracksReserveTrack(int priority);
void tracksDeinit();
// Dispatch
IMuseDigiDispatch _dispatches[DIMUSE_MAX_DISPATCHES];
IMuseDigiStreamZone _streamZones[DIMUSE_MAX_STREAMZONES];
uint8 *_dispatchBuffer;
uint8 _ftCrossfadeBuffer[30000]; // Used by FT & DIG demo
int32 _dispatchSize;
uint8 *_dispatchSmallFadeBufs;
uint8 *_dispatchLargeFadeBufs;
int32 _dispatchFadeSize;
int _dispatchLargeFadeFlags[DIMUSE_LARGE_FADES];
int _dispatchSmallFadeFlags[DIMUSE_SMALL_FADES];
int _dispatchFadeStartedFlag;
int _dispatchBufferedHookId;
int32 _dispatchJumpFadeSize;
int32 _dispatchCurStreamBufSize;
int32 _dispatchCurStreamCriticalSize;
int32 _dispatchCurStreamFreeSpace;
int _dispatchCurStreamPaused;
int dispatchInit();
IMuseDigiDispatch *dispatchGetDispatchByTrackId(int trackId);
void dispatchSaveLoad(Common::Serializer &ser);
int dispatchRestoreStreamZones();
int dispatchAllocateSound(IMuseDigiTrack *trackPtr, int groupId);
int dispatchRelease(IMuseDigiTrack *trackPtr);
int dispatchSwitchStream(int oldSoundId, int newSoundId, int fadeLength, int exitTriggerSyncFlag, int offsetFadeSyncFlag);
int dispatchSwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag);
void dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize, int sampleRate);
void dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize);
void dispatchPredictFirstStream();
int dispatchNavigateMap(IMuseDigiDispatch *dispatchPtr);
int dispatchGetMap(IMuseDigiDispatch *dispatchPtr);
int dispatchConvertMap(uint8 *rawMap, int32 *destMap);
uint8 *dispatchGetNextMapEvent(int32 *mapPtr, int32 soundOffset, uint8 *mapEvent);
void dispatchPredictStream(IMuseDigiDispatch *dispatchPtr);
uint8 *dispatchCheckForJump(int32 *mapPtr, IMuseDigiStreamZone *strZnPtr, int &candidateHookId);
void dispatchPrepareToJump(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr, uint8 *jumpParamsFromMap, int calledFromGetNextMapEvent);
void dispatchStreamNextZone(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr);
IMuseDigiStreamZone *dispatchAllocateStreamZone();
uint8 *dispatchAllocateFade(int32 &fadeSize, const char *functionName);
void dispatchDeallocateFade(IMuseDigiDispatch *dispatchPtr, const char *functionName);
int dispatchGetFadeSize(IMuseDigiDispatch *dispatchPtr, int fadeLength);
void dispatchValidateFadeSize(IMuseDigiDispatch *dispatchPtr, int32 &dispatchSize, const char *functionName);
int dispatchUpdateFadeMixVolume(IMuseDigiDispatch *dispatchPtr, int32 remainingFade);
int dispatchUpdateFadeSlope(IMuseDigiDispatch *dispatchPtr);
void dispatchVOCLoopCallback(int soundId);
int dispatchSeekToNextChunk(IMuseDigiDispatch *dispatchPtr);
// Wave (mainly a wrapper for Tracks functions)
int waveInit();
int waveTerminate();
int wavePause();
int waveResume();
void waveSaveLoad(Common::Serializer &ser);
void waveUpdateGroupVolumes();
int waveStartSound(int soundId, int priority);
int waveStopSound(int soundId);
int waveStopAllSounds();
int waveGetNextSound(int soundId);
int waveSetParam(int soundId, int opcode, int value);
int waveGetParam(int soundId, int opcode);
int waveSetHook(int soundId, int hookId);
int waveGetHook(int soundId);
int waveStartStream(int soundId, int priority, int groupId);
int waveSwitchStream(int oldSoundId, int newSoundId, int fadeLengthMs, int fadeSyncFlag2, int fadeSyncFlag1);
int waveSwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag);
int waveProcessStreams();
int waveQueryStream(int soundId, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused);
int waveFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused);
int waveLipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height);
// Waveapi
waveOutParamsStruct waveOutSettings;
int _waveOutSampleRate;
int _waveOutBytesPerSample;
int _waveOutNumChannels;
int _waveOutZeroLevel;
int _waveOutPreferredFeedSize;
uint8 *_waveOutMixBuffer;
uint8 *_waveOutOutputBuffer;
int _waveOutXorTrigger;
int _waveOutWriteIndex;
int _waveOutDisableWrite;
int waveOutInit(waveOutParamsStruct *waveOutSettings);
void waveOutWrite(uint8 **audioBuffer, int &feedSize, int &sampleRate);
int waveOutDeinit();
void waveOutCallback();
byte waveOutGetStreamFlags();
// Low latency mode
void waveOutLowLatencyWrite(uint8 **audioBuffer, int &feedSize, int &sampleRate, int idx);
void waveOutEmptyBuffer(int idx);
uint8 *_waveOutLowLatencyOutputBuffer;
public:
IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *mixer, Common::Mutex *mutex, bool lowLatencyMode = false);
~IMuseDigital() override;
// Wrapper functions used by the main engine
void startSound(int sound) override { error("IMuseDigital::startSound(int) should be never called"); }
void setMusicVolume(int vol) override {}
void stopSound(int sound) override;
void stopAllSounds() override;
int getSoundStatus(int sound) const override { return 0; }
int isSoundRunning(int soundId); // Needed because getSoundStatus is a const function, and I needed a workaround
int startVoice(int soundId, const char *soundName, byte speakingActorId);
int startVoice(const char *fileName, ScummFile *file, uint32 offset, uint32 size);
void saveLoadEarly(Common::Serializer &ser);
void setRadioChatterSFX(bool state);
void setAudioNames(int32 num, char *names);
int startSfx(int soundId, int priority) ;
void setPriority(int soundId, int priority);
void setVolume(int soundId, int volume);
void setPan(int soundId, int pan);
void setFrequency(int soundId, int frequency);
int getCurSpeechVolume() const;
int getCurSpeechPan() const;
int getCurSpeechFrequency() const;
void pause(bool pause);
void parseScriptCmds(int cmd, int soundId, int sub_cmd, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p);
void refreshScripts();
void flushTracks();
void disableEngine();
bool isEngineDisabled();
void stopSMUSHAudio();
void receiveAudioFromSMUSH(uint8 *srcBuf, int32 inFrameCount, int32 feedSize, int32 mixBufStartIndex, int volume, int pan, bool is11025Hz);
void setSmushPlayer(SmushPlayer *splayer);
void floodMusicBuffer();
void fillStreamsWhileMusicCritical(int fillTimesAfter);
bool queryNextSoundFile(int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused);
int getSampleRate();
int getFeedSize();
bool isFTSoundEngine(); // Used in the handlers to check if we're using the FT version of the engine
int32 getCurMusicPosInMs();
int32 getCurVoiceLipSyncWidth();
int32 getCurVoiceLipSyncHeight();
int32 getCurMusicLipSyncWidth(int syncId);
int32 getCurMusicLipSyncHeight(int syncId);
void getSpeechLipSyncInfo(int32 &width, int32 &height);
void getMusicLipSyncInfo(int syncId, int32 &width, int32 &height);
int32 getSoundElapsedTimeInMs(int soundId);
// General engine functions
int diMUSETerminate();
int diMUSEInitialize();
int diMUSEPause();
int diMUSEResume();
void diMUSESaveLoad(Common::Serializer &ser);
int diMUSESetGroupVol(int groupId, int volume);
int diMUSEStartSound(int soundId, int priority);
int diMUSEStopSound(int soundId);
int diMUSEStopAllSounds();
int diMUSEGetNextSound(int soundId);
int diMUSESetParam(int soundId, int paramId, int value);
int diMUSEGetParam(int soundId, int paramId);
int diMUSEFadeParam(int soundId, int opcode, int destValue, int fadeLength);
int diMUSESetHook(int soundId, int hookId);
int diMUSESetTrigger(int soundId, int marker, int opcode,
int d = -1, int e = -1, int f = -1, int g = -1,
int h = -1, int i = -1, int j = -1, int k = -1,
int l = -1, int m = -1, int n = -1);
int diMUSEStartStream(int soundId, int priority, int groupId);
int diMUSESwitchStream(int oldSoundId, int newSoundId, int fadeDelay, int fadeSyncFlag2, int fadeSyncFlag1);
int diMUSESwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag);
int diMUSEProcessStreams();
int diMUSEQueryStream(int soundId, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused);
int diMUSEFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused);
int diMUSELipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height);
int diMUSEGetMusicGroupVol();
int diMUSEGetSFXGroupVol();
int diMUSEGetVoiceGroupVol();
int diMUSESetMusicGroupVol(int volume);
int diMUSESetSFXGroupVol(int volume);
int diMUSESetVoiceGroupVol(int volume);
void diMUSEUpdateGroupVolumes();
int diMUSEInitializeScript();
void diMUSERefreshScript();
int diMUSESetState(int soundId);
int diMUSESetSequence(int soundId);
int diMUSESetCuePoint(int cueId);
int diMUSESetAttribute(int attrIndex, int attrVal);
void diMUSEEnableSpooledMusic();
void diMUSEDisableSpooledMusic();
// Utils
int addTrackToList(IMuseDigiTrack **listPtr, IMuseDigiTrack *listPtr_Item);
int removeTrackFromList(IMuseDigiTrack **listPtr, IMuseDigiTrack *itemPtr);
int addStreamZoneToList(IMuseDigiStreamZone **listPtr, IMuseDigiStreamZone *listPtr_Item);
int removeStreamZoneFromList(IMuseDigiStreamZone **listPtr, IMuseDigiStreamZone *itemPtr);
int clampNumber(int value, int minValue, int maxValue);
int clampTuning(int value, int minValue, int maxValue);
int checkHookId(int &trackHookId, int sampleHookId);
int roundRobinSetBufferCount();
void adaptBufferCount();
// CMDs
int cmdsHandleCmd(int cmd, uint8 *ptr = nullptr,
int a = -1, int b = -1, int c = -1, int d = -1, int e = -1,
int f = -1, int g = -1, int h = -1, int i = -1, int j = -1,
int k = -1, int l = -1, int m = -1, int n = -1);
// Script
int scriptTriggerCallback(char *marker);
// Debugger utility functions
void listStates();
void listSeqs();
void listCues();
void listTracks();
void listGroups();
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,178 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
#include "scumm/imuse_digi/dimuse_fades.h"
namespace Scumm {
IMuseDigiFadesHandler::IMuseDigiFadesHandler(IMuseDigital *engine, Common::Mutex *mutex) {
_engine = engine;
_mutex = mutex;
}
IMuseDigiFadesHandler::~IMuseDigiFadesHandler() {}
int IMuseDigiFadesHandler::init() {
clearAllFades();
return 0;
}
int IMuseDigiFadesHandler::fadeParam(int soundId, int opcode, int destinationValue, int fadeLength) {
Common::StackLock lock(*_mutex);
if (!soundId || fadeLength < 0)
return -5;
if (opcode != DIMUSE_P_PRIORITY && opcode != DIMUSE_P_VOLUME && opcode != DIMUSE_P_PAN && opcode != DIMUSE_P_DETUNE && opcode != DIMUSE_P_UNKNOWN && opcode != 17)
return -5;
clearFadeStatus(soundId, opcode);
if (!fadeLength) {
debug(5, "IMuseDigiFadesHandler::fadeParam(): WARNING: allocated fade with zero length for sound %d", soundId);
if (opcode != DIMUSE_P_VOLUME || destinationValue) {
_engine->diMUSESetParam(soundId, opcode, destinationValue);
} else {
_engine->diMUSEStopSound(soundId);
}
return 0;
}
for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
if (!_fades[l].status) {
_fades[l].sound = soundId;
_fades[l].param = opcode;
_fades[l].currentVal = _engine->diMUSEGetParam(soundId, opcode);
_fades[l].length = fadeLength;
_fades[l].counter = fadeLength;
_fades[l].slope = (destinationValue - _fades[l].currentVal) / fadeLength;
_fades[l].modOvfloCounter = 0;
_fades[l].status = 1;
_fadesOn = 1;
if ((destinationValue - _fades[l].currentVal) < 0) {
_fades[l].nudge = -1;
_fades[l].slopeMod = (-(destinationValue - _fades[l].currentVal) % fadeLength);
} else {
_fades[l].nudge = 1;
_fades[l].slopeMod = (destinationValue - _fades[l].currentVal) % fadeLength;
}
return 0;
}
}
debug(5, "IMuseDigiFadesHandler::fadeParam(): unable to allocate fade for sound %d", soundId);
return -6;
}
void IMuseDigiFadesHandler::clearFadeStatus(int soundId, int opcode) {
for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
if (_fades[l].status
&& _fades[l].sound == soundId
&& (_fades[l].param == opcode || opcode == -1)) {
_fades[l].status = 0;
}
}
}
void IMuseDigiFadesHandler::loop() {
if (!_fadesOn)
return;
_fadesOn = 0;
for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
if (_fades[l].status) {
_fadesOn = 1;
if (--_fades[l].counter == 0) {
_fades[l].status = 0;
}
int currentVolume = _fades[l].currentVal + _fades[l].slope;
int currentSlopeMod = _fades[l].modOvfloCounter + _fades[l].slopeMod;
_fades[l].modOvfloCounter += _fades[l].slopeMod;
if (_fades[l].length <= currentSlopeMod) {
_fades[l].modOvfloCounter = currentSlopeMod - _fades[l].length;
currentVolume += _fades[l].nudge;
}
if (_fades[l].currentVal != currentVolume) {
_fades[l].currentVal = currentVolume;
if (!(_fades[l].counter % 6)) {
debug(5, "IMuseDigiFadesHandler::loop(): running fade for sound %d with id %d, currently at volume %d", _fades[l].sound, l, currentVolume);
if ((_fades[l].param != DIMUSE_P_VOLUME) || currentVolume) {
_engine->diMUSESetParam(_fades[l].sound, _fades[l].param, currentVolume);
} else {
_engine->diMUSEStopSound(_fades[l].sound);
}
}
}
}
}
}
void IMuseDigiFadesHandler::deinit() {
clearAllFades();
}
void IMuseDigiFadesHandler::saveLoad(Common::Serializer &ser) {
for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
ser.syncAsSint32LE(_fades[l].status, VER(103));
ser.syncAsSint32LE(_fades[l].sound, VER(103));
ser.syncAsSint32LE(_fades[l].param, VER(103));
ser.syncAsSint32LE(_fades[l].currentVal, VER(103));
ser.syncAsSint32LE(_fades[l].counter, VER(103));
ser.syncAsSint32LE(_fades[l].length, VER(103));
ser.syncAsSint32LE(_fades[l].slope, VER(103));
ser.syncAsSint32LE(_fades[l].slopeMod, VER(103));
ser.syncAsSint32LE(_fades[l].modOvfloCounter, VER(103));
ser.syncAsSint32LE(_fades[l].nudge, VER(103));
}
if (ser.isLoading())
_fadesOn = 1;
}
void IMuseDigiFadesHandler::clearAllFades() {
Common::StackLock lock(*_mutex);
for (int l = 0; l < DIMUSE_MAX_FADES; l++) {
_fades[l].status = 0;
_fades[l].sound = 0;
_fades[l].param = 0;
_fades[l].currentVal = 0;
_fades[l].counter = 0;
_fades[l].length = 0;
_fades[l].slope = 0;
_fades[l].slopeMod = 0;
_fades[l].modOvfloCounter = 0;
_fades[l].nudge = 0;
}
_fadesOn = 0;
}
} // End of namespace Scumm

View File

@@ -0,0 +1,54 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_FADES_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_FADES_H
#include "common/scummsys.h"
#include "common/textconsole.h"
#include "common/util.h"
namespace Scumm {
class IMuseDigiFadesHandler {
private:
IMuseDigital *_engine;
Common::Mutex *_mutex;
IMuseDigiFade _fades[DIMUSE_MAX_FADES] = {};
int _fadesOn = 0;
void clearAllFades();
public:
IMuseDigiFadesHandler(IMuseDigital *engine, Common::Mutex *mutex);
~IMuseDigiFadesHandler();
int init();
void deinit();
void saveLoad(Common::Serializer &ser);
int fadeParam(int soundId, int opcode, int destinationValue, int fadeLength);
void clearFadeStatus(int soundId, int opcode);
void loop();
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,440 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
#include "scumm/imuse_digi/dimuse_files.h"
#include "scumm/file.h"
namespace Scumm {
IMuseDigiFilesHandler::IMuseDigiFilesHandler(IMuseDigital *engine, ScummEngine_v7 *vm) {
_engine = engine;
_sound = new ImuseDigiSndMgr(vm);
assert(_sound);
_vm = vm;
_ftSpeechFilename[0] = '\0';
_ftSpeechSubFileOffset = 0;
_ftSpeechFileSize = 0;
_ftSpeechFileCurPos = 0;
_ftSpeechFile = nullptr;
for (int i = 0; i < 4; i++) {
IMuseDigiSndBuffer *selectedSoundBuf = &_soundBuffers[i];
selectedSoundBuf->buffer = nullptr;
selectedSoundBuf->bufSize = 0;
selectedSoundBuf->loadSize = 0;
selectedSoundBuf->criticalSize = 0;
}
}
IMuseDigiFilesHandler::~IMuseDigiFilesHandler() {
delete _ftSpeechFile;
delete _sound;
}
void IMuseDigiFilesHandler::saveLoad(Common::Serializer &ser) {
int curSound = 0;
ImuseDigiSndMgr::SoundDesc *sounds = _sound->getSounds();
ser.syncArray(_currentSpeechFilename, 60, Common::Serializer::SByte, VER(103));
if (ser.isSaving()) {
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
ser.syncAsSint32LE(sounds[l].soundId, VER(103));
}
if (_engine->isFTSoundEngine()) {
ser.syncAsSint32LE(_ftSpeechFileCurPos, VER(103));
ser.syncAsSint32LE(_ftSpeechFileSize, VER(103));
ser.syncAsSint32LE(_ftSpeechSubFileOffset, VER(103));
ser.syncArray(_ftSpeechFilename, sizeof(_ftSpeechFilename), Common::Serializer::SByte, VER(103));
}
}
if (ser.isLoading()) {
// Close prior sounds if we're reloading (needed for edge cases like the recipe book in COMI)
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
_sound->closeSound(&sounds[l]);
}
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
ser.syncAsSint32LE(curSound, VER(103));
if (curSound) {
openSound(curSound);
if (curSound != kTalkSoundID)
closeSound(curSound);
}
}
if (_engine->isFTSoundEngine()) {
ser.syncAsSint32LE(_ftSpeechFileCurPos, VER(103));
ser.syncAsSint32LE(_ftSpeechFileSize, VER(103));
ser.syncAsSint32LE(_ftSpeechSubFileOffset, VER(103));
ser.syncArray(_ftSpeechFilename, sizeof(_ftSpeechFilename), Common::Serializer::SByte, VER(103));
if (strlen(_ftSpeechFilename))
_ftSpeechFile = _vm->_sound->restoreDiMUSESpeechFile(_ftSpeechFilename);
}
}
}
uint8 *IMuseDigiFilesHandler::getSoundAddrData(int soundId) {
if (_engine->isEngineDisabled())
return nullptr;
// This function is always used for SFX (tracks which do not
// have a stream pointer), hence the use of the resource address
if (soundId != 0) {
_vm->_res->lock(rtSound, soundId);
byte *ptr = _vm->getResourceAddress(rtSound, soundId);
if (!ptr) {
_vm->_res->unlock(rtSound, soundId);
return nullptr;
}
return ptr;
}
debug(5, "IMuseDigiFilesHandler::getSoundAddrData(): soundId is 0 or out of range");
return nullptr;
}
int IMuseDigiFilesHandler::getSoundAddrDataSize(int soundId, bool hasStream) {
if (_engine->isEngineDisabled())
return 0;
if (hasStream) {
ImuseDigiSndMgr::SoundDesc *s = _sound->findSoundById(soundId);
if (s) {
if (soundId != kTalkSoundID) {
return s->resSize;
}
} else if (soundId == kTalkSoundID) {
return _ftSpeechFileSize;
}
} else {
return _vm->getResourceSize(rtSound, soundId);
}
return 0;
}
int IMuseDigiFilesHandler::getNextSound(int soundId) {
int foundSoundId = 0;
do {
foundSoundId = _engine->diMUSEGetNextSound(foundSoundId);
if (!foundSoundId)
return -1;
} while (foundSoundId != soundId);
return 2;
}
int IMuseDigiFilesHandler::seek(int soundId, int32 offset, int mode, int bufId) {
// This function and files_read() are used for sounds for which a stream is needed (speech
// and music), therefore they will always refer to sounds in a bundle file for DIG and COMI
// The seek'd position is in reference to the decompressed sound
if (_engine->isEngineDisabled())
return 0;
char fileName[60] = "";
getFilenameFromSoundId(soundId, fileName, sizeof(fileName));
ImuseDigiSndMgr::SoundDesc *s = _sound->findSoundById(soundId);
if (s || (_engine->isFTSoundEngine() && soundId == kTalkSoundID)) {
if (soundId != 0) {
if (_engine->isFTSoundEngine()) {
switch (mode) {
case SEEK_END:
if (soundId != kTalkSoundID) {
return s->resSize;
} else {
return _ftSpeechFileSize;
}
case SEEK_SET:
default:
if (soundId != kTalkSoundID) {
if (offset <= s->resSize) {
s->resCurOffset = offset;
return offset;
}
} else {
if (offset <= _ftSpeechFileSize) {
_ftSpeechFileCurPos = offset;
return _ftSpeechFileCurPos;
}
}
}
} else {
// A soundId > 10000 is a SAN cutscene
if ((_vm->_game.id == GID_DIG && !(_vm->_game.features & GF_DEMO)) && (soundId > kTalkSoundID))
return 0;
return s->bundle->seekFile(offset, mode);
}
} else {
debug(5, "IMuseDigiFilesHandler::seek(): soundId is 0 or out of range");
}
} else {
debug(5, "IMuseDigiFilesHandler::seek(): can't find sound %d (%s); did you forget to open it?", soundId, fileName);
}
return 0;
}
int IMuseDigiFilesHandler::read(int soundId, uint8 *buf, int32 size, int bufId) {
// This function and files_seek() are used for sounds for which a stream is needed (speech
// and music), therefore they will always refer to sounds in a bundle file for DIG and COMI
if (_engine->isEngineDisabled())
return 0;
if (soundId != 0) {
uint8 *tmpBuf = nullptr;
int32 resultingSize;
// We don't have SoundDesc objects for FT & DIG demo speech files
if (_engine->isFTSoundEngine() && soundId == kTalkSoundID) {
_ftSpeechFile->seek(_ftSpeechSubFileOffset + _ftSpeechFileCurPos, SEEK_SET);
resultingSize = size > _ftSpeechFileSize ? (_ftSpeechFileSize - _ftSpeechFileCurPos) : size;
return _ftSpeechFile->read(buf, resultingSize);
}
char fileName[60] = "";
getFilenameFromSoundId(soundId, fileName, sizeof(fileName));
ImuseDigiSndMgr::SoundDesc *s = _sound->getSounds();
ImuseDigiSndMgr::SoundDesc *curSnd = nullptr;
for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
curSnd = &s[i];
if (curSnd->inUse) {
if (curSnd->soundId == soundId) {
if (_engine->isFTSoundEngine()) { // FT & DIG demo
resultingSize = size > (curSnd->resSize - curSnd->resCurOffset) ? (curSnd->resSize - curSnd->resCurOffset) : size;
tmpBuf = &curSnd->resPtr[curSnd->resCurOffset];
if (resultingSize != size)
debug(5, "IMuseDigiFilesHandler::read(): WARNING: tried to read %d bytes, got %d instead (soundId %d (%s))", size, resultingSize, soundId, fileName);
memcpy(buf, tmpBuf, resultingSize); // We don't free tmpBuf: it's the resource pointer
return resultingSize;
} else { // DIG & COMI
resultingSize = curSnd->bundle->readFile(fileName, size, &tmpBuf, ((_vm->_game.id == GID_CMI) && !(_vm->_game.features & GF_DEMO)));
if (resultingSize != size)
debug(5, "IMuseDigiFilesHandler::read(): WARNING: tried to read %d bytes, got %d instead (soundId %d (%s))", size, resultingSize, soundId, fileName);
memcpy(buf, tmpBuf, resultingSize);
free(tmpBuf);
return resultingSize;
}
}
}
}
debug(5, "IMuseDigiFilesHandler::read(): can't find sound %d (%s); did you forget to open it?", soundId, fileName);
} else {
debug(5, "IMuseDigiFilesHandler::read(): soundId is 0 or out of range");
}
return 0;
}
IMuseDigiSndBuffer *IMuseDigiFilesHandler::getBufInfo(int bufId) {
if (bufId > 0 && bufId <= 4) {
return &_soundBuffers[bufId];
}
debug(5, "IMuseDigiFilesHandler::getBufInfo(): ERROR: invalid buffer id");
return nullptr;
}
int IMuseDigiFilesHandler::openSound(int soundId) {
if (_engine->isEngineDisabled())
return 1;
ImuseDigiSndMgr::SoundDesc *s = nullptr;
if (!_engine->isFTSoundEngine()) {
char fileName[60] = "";
getFilenameFromSoundId(soundId, fileName, sizeof(fileName));
int groupId = soundId == kTalkSoundID ? IMUSE_VOLGRP_VOICE : IMUSE_VOLGRP_MUSIC;
s = _sound->findSoundById(soundId);
if (!s)
s = _sound->openSound(soundId, fileName, IMUSE_BUNDLE, groupId, -1);
if (!s)
s = _sound->openSound(soundId, fileName, IMUSE_BUNDLE, groupId, 1);
if (!s)
s = _sound->openSound(soundId, fileName, IMUSE_BUNDLE, groupId, 2);
if (!s) {
debug(5, "IMuseDigiFilesHandler::openSound(): can't open sound %d (%s)", soundId, fileName);
return 1;
}
} else {
s = _sound->findSoundById(soundId);
if (!s)
s = _sound->openSound(soundId, "", IMUSE_RESOURCE, -1, -1);
if (!s)
s = _sound->openSound(soundId, "", IMUSE_RESOURCE, -1, 1);
if (!s)
s = _sound->openSound(soundId, "", IMUSE_RESOURCE, -1, 2);
if (!s) {
debug(5, "IMuseDigiFilesHandler::openSound(): can't open sound %d", soundId);
return 1;
}
}
return 0;
}
void IMuseDigiFilesHandler::closeSound(int soundId) {
if (_engine->isEngineDisabled())
return;
_sound->scheduleSoundForDeallocation(soundId);
}
void IMuseDigiFilesHandler::closeAllSounds() {
ImuseDigiSndMgr::SoundDesc *s = _sound->getSounds();
for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
if (s[i].inUse) {
closeSound((&s[i])->soundId);
}
}
_engine->flushTracks();
}
void IMuseDigiFilesHandler::getFilenameFromSoundId(int soundId, char *fileName, size_t size) {
if (_engine->isFTSoundEngine())
return;
int i = 0;
if (soundId == kTalkSoundID) {
Common::strlcpy(fileName, _currentSpeechFilename, size);
return;
}
if (_vm->_game.id == GID_CMI) {
if (_vm->_game.features & GF_DEMO) {
while (_comiDemoStateMusicTable[i].soundId != -1) {
if (_comiDemoStateMusicTable[i].soundId == soundId) {
Common::strlcpy(fileName, _comiDemoStateMusicTable[i].filename, size);
return;
}
i++;
}
} else {
if (soundId < 2000) {
while (_comiStateMusicTable[i].soundId != -1) {
if (_comiStateMusicTable[i].soundId == soundId) {
Common::strlcpy(fileName, _comiStateMusicTable[i].filename, size);
return;
}
i++;
}
} else {
while (_comiSeqMusicTable[i].soundId != -1) {
if (_comiSeqMusicTable[i].soundId == soundId) {
Common::strlcpy(fileName, _comiSeqMusicTable[i].filename, size);
return;
}
i++;
}
}
}
} else if (_vm->_game.id == GID_DIG) {
if (soundId < 2000) {
while (_digStateMusicTable[i].soundId != -1) {
if (_digStateMusicTable[i].soundId == soundId) {
Common::strlcpy(fileName, _digStateMusicTable[i].filename, size);
return;
}
i++;
}
} else {
while (_digSeqMusicTable[i].soundId != -1) {
if (_digSeqMusicTable[i].soundId == soundId) {
Common::strlcpy(fileName, _digSeqMusicTable[i].filename, size);
return;
}
i++;
}
}
}
}
void IMuseDigiFilesHandler::allocSoundBuffer(int bufId, int32 size, int32 loadSize, int32 criticalSize) {
IMuseDigiSndBuffer *selectedSoundBuf;
selectedSoundBuf = &_soundBuffers[bufId];
selectedSoundBuf->buffer = (uint8 *)malloc(size);
selectedSoundBuf->bufSize = size;
selectedSoundBuf->loadSize = loadSize;
selectedSoundBuf->criticalSize = criticalSize;
}
void IMuseDigiFilesHandler::deallocSoundBuffer(int bufId) {
IMuseDigiSndBuffer *selectedSoundBuf;
selectedSoundBuf = &_soundBuffers[bufId];
free(selectedSoundBuf->buffer);
selectedSoundBuf->buffer = nullptr;
}
void IMuseDigiFilesHandler::flushSounds() {
if (_engine->isEngineDisabled())
return;
ImuseDigiSndMgr::SoundDesc *s = _sound->getSounds();
for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
ImuseDigiSndMgr::SoundDesc *curSnd = &s[i];
if (curSnd && curSnd->inUse) {
if (curSnd->scheduledForDealloc)
if (!_engine->diMUSEGetParam(curSnd->soundId, DIMUSE_P_SND_TRACK_NUM) && !_engine->diMUSEGetParam(curSnd->soundId, DIMUSE_P_TRIGS_SNDS))
_sound->closeSound(curSnd);
}
}
}
int IMuseDigiFilesHandler::setCurrentSpeechFilename(const char *fileName) {
Common::strlcpy(_currentSpeechFilename, fileName, sizeof(_currentSpeechFilename));
if (openSound(kTalkSoundID))
return 1;
return 0;
}
void IMuseDigiFilesHandler::setCurrentFtSpeechFile(const char *fileName, ScummFile *file, uint32 offset, uint32 size) {
Common::strlcpy(_ftSpeechFilename, fileName, sizeof(_ftSpeechFilename));
delete _ftSpeechFile;
_ftSpeechFile = file;
_ftSpeechSubFileOffset = offset;
_ftSpeechFileSize = size;
}
void IMuseDigiFilesHandler::closeSoundImmediatelyById(int soundId) {
if (_engine->isEngineDisabled())
return;
_sound->closeSoundById(soundId);
}
} // End of namespace Scumm

View File

@@ -0,0 +1,74 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_FILES_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_FILES_H
#include "common/scummsys.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "scumm/resource.h"
#include "scumm/file.h"
#include "scumm/imuse_digi/dimuse_bndmgr.h"
#include "scumm/imuse_digi/dimuse_sndmgr.h"
namespace Scumm {
class IMuseDigiFilesHandler {
private:
IMuseDigital *_engine;
ImuseDigiSndMgr *_sound;
ScummEngine_v7 *_vm;
Common::Mutex _mutex;
IMuseDigiSndBuffer _soundBuffers[4];
char _currentSpeechFilename[60] = {};
ScummFile *_ftSpeechFile;
char _ftSpeechFilename[160] = {};
int _ftSpeechSubFileOffset;
int _ftSpeechFileSize;
int _ftSpeechFileCurPos;
void getFilenameFromSoundId(int soundId, char *fileName, size_t size);
public:
IMuseDigiFilesHandler(IMuseDigital *engine, ScummEngine_v7 *vm);
~IMuseDigiFilesHandler();
uint8 *getSoundAddrData(int soundId);
int getSoundAddrDataSize(int soundId, bool hasStream);
int getNextSound(int soundId);
int seek(int soundId, int32 offset, int mode, int bufId);
int read(int soundId, uint8 *buf, int32 size, int bufId);
IMuseDigiSndBuffer *getBufInfo(int bufId);
int openSound(int soundId);
void closeSound(int soundId);
void closeAllSounds();
void allocSoundBuffer(int bufId, int32 size, int32 loadSize, int32 criticalSize);
void deallocSoundBuffer(int bufId);
void flushSounds();
int setCurrentSpeechFilename(const char *fileName);
void setCurrentFtSpeechFile(const char *fileName, ScummFile *file, uint32 offset, uint32 size);
void closeSoundImmediatelyById(int soundId);
void saveLoad(Common::Serializer &ser);
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,84 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
#include "scumm/imuse_digi/dimuse_groups.h"
namespace Scumm {
IMuseDigiGroupsHandler::IMuseDigiGroupsHandler(IMuseDigital *engine, Common::Mutex *mutex) {
_engine = engine;
_mutex = mutex;
}
IMuseDigiGroupsHandler::~IMuseDigiGroupsHandler() {}
int IMuseDigiGroupsHandler::init() {
for (int i = 0; i < DIMUSE_MAX_GROUPS; i++) {
_effVols[i] = 127;
_vols[i] = 127;
}
return 0;
}
int IMuseDigiGroupsHandler::setGroupVol(int id, int volume) {
Common::StackLock lock(*_mutex);
int l;
if (id >= DIMUSE_MAX_GROUPS) {
return -5;
}
if (volume == -1) {
return _vols[id];
}
if (volume > 127)
return -5;
if (id) {
_vols[id] = volume;
_effVols[id] = (_vols[0] * (volume + 1)) / 128;
} else {
_effVols[0] = volume;
_vols[0] = volume;
for (l = 1; l < DIMUSE_MAX_GROUPS; l++) {
_effVols[l] = (volume * (_vols[l] + 1)) / 128;
}
}
_engine->diMUSEUpdateGroupVolumes();
return _vols[id];
}
int IMuseDigiGroupsHandler::getGroupVol(int id) {
Common::StackLock lock(*_mutex);
if (id >= DIMUSE_MAX_GROUPS) {
return -5;
}
return _effVols[id];
}
} // End of namespace Scumm

View File

@@ -0,0 +1,50 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_GROUPS_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_GROUPS_H
#include "common/scummsys.h"
#include "common/mutex.h"
#include "common/serializer.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
class IMuseDigiGroupsHandler {
private:
IMuseDigital *_engine;
Common::Mutex *_mutex;
int _effVols[DIMUSE_MAX_GROUPS] = {};
int _vols[DIMUSE_MAX_GROUPS] = {};
public:
IMuseDigiGroupsHandler(IMuseDigital *engine, Common::Mutex *mutex);
~IMuseDigiGroupsHandler();
int init();
int setGroupVol(int id, int volume);
int getGroupVol(int id);
};
} // End of namespace Scumm
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_MIXER_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_MIXER_H
#include "common/scummsys.h"
#include "common/mutex.h"
#include "common/serializer.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "scumm/imuse_digi/dimuse_engine.h"
#include "scumm/music.h"
#include "scumm/sound.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
namespace Audio {
class AudioStream;
class Mixer;
class QueuingAudioStream;
}
namespace Scumm {
class IMuseDigiInternalMixer {
private:
int32 *_amp8Table = nullptr;
int32 *_amp12Table = nullptr;
int32 *_softLMID = nullptr;
int32 *_softLTable = nullptr;
uint8 *_mixBuf = nullptr;
Audio::Mixer *_mixer;
Audio::SoundHandle _channelHandle;
int _mixBufSize = 0;
int _radioChatter = 0;
int _outWordSize = 0;
int _outChannelCount = 0;
int _sampleRate = 0;
int _stereoReverseFlag = 0;
bool _isEarlyDiMUSE = false;
bool _lowLatencyMode = false;
void mixBits8Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable, bool ftIs11025Hz);
void mixBits12Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
void mixBits16Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
void mixBits8ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
void mixBits12ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
void mixBits16ConvertToMono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
void mixBits8ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable, bool ftIs11025Hz);
void mixBits12ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable);
void mixBits16ConvertToStereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *leftAmpTable, int32 *rightAmpTable);
void mixBits8Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
void mixBits12Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
void mixBits16Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
public:
IMuseDigiInternalMixer(Audio::Mixer *mixer, int sampleRate, bool isEarlyDiMUSE, bool lowLatencyMode = false);
~IMuseDigiInternalMixer();
int init(int bytesPerSample, int numChannels, uint8 *mixBuf, int mixBufSize, int sizeSampleKB, int mixChannelsNum);
void setRadioChatter();
void clearRadioChatter();
int clearMixerBuffer();
void mix(uint8 *srcBuf, int32 inFrameCount, int wordSize, int channelCount, int feedSize, int32 mixBufStartIndex, int volume, int pan, bool ftIs11025Hz);
int loop(uint8 **destBuffer, int len);
Audio::QueuingAudioStream *_stream;
// For low latency audio
void setCurrentMixerBuffer(uint8 *newBuf);
void endStream(int idx);
Audio::QueuingAudioStream *getStream(int idx);
Audio::QueuingAudioStream *_separateStreams[DIMUSE_MAX_TRACKS];
Audio::SoundHandle _separateChannelHandles[DIMUSE_MAX_TRACKS];
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,931 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
#define DIG_STATE_OFFSET 11
#define DIG_SEQ_OFFSET (DIG_STATE_OFFSET + 65)
#define COMI_STATE_OFFSET 3
int IMuseDigital::scriptParse(int cmd, int a, int b) {
if (_scriptInitializedFlag || !cmd) {
switch (cmd) {
case DIMUSE_C_SCRIPT_INIT:
if (_scriptInitializedFlag) {
debug(5, "IMuseDigital::scriptParse(): script module already initialized");
return -1;
} else {
_scriptInitializedFlag = 1;
return scriptInit();
}
case DIMUSE_C_SCRIPT_TERMINATE:
_scriptInitializedFlag = 0;
return scriptTerminate();
case DIMUSE_C_SCRIPT_SAVE:
case DIMUSE_C_SCRIPT_RESTORE:
break;
case DIMUSE_C_SCRIPT_REFRESH:
scriptRefresh();
return 0;
case DIMUSE_C_SCRIPT_SET_STATE:
scriptSetState(a);
return 0;
case DIMUSE_C_SCRIPT_SET_SEQUENCE:
scriptSetSequence(a);
return 0;
case DIMUSE_C_SCRIPT_CUE_POINT:
scriptSetCuePoint(a);
return 0;
case DIMUSE_C_SCRIPT_SET_ATTRIBUTE:
return scriptSetAttribute(a, b);
default:
debug(5, "IMuseDigital::scriptParse(): unrecognized opcode (%d)", cmd);
return -1;
}
} else {
debug(5, "IMuseDigital::scriptParse(): script module not initialized");
return -1;
}
return -1;
}
int IMuseDigital::scriptInit() {
_curMusicState = 0;
_curMusicSeq = 0;
_nextSeqToPlay = 0;
_curMusicCue = 0;
memset(_attributes, 0, sizeof(_attributes));
return 0;
}
int IMuseDigital::scriptTerminate() {
diMUSETerminate();
_curMusicState = 0;
_curMusicSeq = 0;
_nextSeqToPlay = 0;
_curMusicCue = 0;
memset(_attributes, 0, sizeof(_attributes));
return 0;
}
void IMuseDigital::scriptRefresh() {
int soundId;
int nextSound;
if (_stopSequenceFlag) {
scriptSetSequence(0);
_stopSequenceFlag = 0;
}
soundId = 0;
while (1) {
nextSound = diMUSEGetNextSound(soundId);
soundId = nextSound;
if (!nextSound)
break;
if (diMUSEGetParam(nextSound, DIMUSE_P_SND_HAS_STREAM) && diMUSEGetParam(soundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC) {
if (soundId)
return;
break;
}
}
if (_curMusicSeq)
scriptSetSequence(0);
flushTracks();
}
void IMuseDigital::scriptSetState(int soundId) {
if (_vm->_game.id == GID_DIG && !_isEarlyDiMUSE) {
setDigMusicState(soundId);
} else if (_vm->_game.id == GID_CMI) {
setComiMusicState(soundId);
} else {
setFtMusicState(soundId);
}
}
void IMuseDigital::scriptSetSequence(int soundId) {
if (_vm->_game.id == GID_DIG && !_isEarlyDiMUSE) {
setDigMusicSequence(soundId);
} else if (_vm->_game.id == GID_CMI) {
setComiMusicSequence(soundId);
} else {
setFtMusicSequence(soundId);
}
}
void IMuseDigital::scriptSetCuePoint(int cueId) {
if (!_isEarlyDiMUSE)
return;
if (cueId > 3)
return;
debug(5, "IMuseDigital::scriptSetCuePoint(): Cue point sequence: %d", cueId);
if (_curMusicSeq && _curMusicCue != cueId) {
if (cueId == 0)
playFtMusic(nullptr, 0, 0);
else {
int seq = ((_curMusicSeq - 1) * 4) + cueId;
playFtMusic(_ftSeqMusicTable[seq].audioName, _ftSeqMusicTable[seq].transitionType, _ftSeqMusicTable[seq].volume);
}
}
_curMusicCue = cueId;
}
int IMuseDigital::scriptSetAttribute(int attrIndex, int attrVal) {
// FT appears to set a single attribute to 1 at start-up and
// never use it again, so we currently ignore that behavior
if (_vm->_game.id == GID_DIG) {
_attributes[attrIndex] = attrVal;
}
return 0;
}
int IMuseDigital::scriptTriggerCallback(char *marker) {
if (marker[0] != '_') {
debug(5, "IMuseDigital::scriptTriggerCallback(): got marker != '_end', callback ignored");
return -1;
}
_stopSequenceFlag = 1;
return 0;
}
void Scumm::IMuseDigital::setFtMusicState(int stateId) {
if (stateId > 48)
return;
debug(5, "IMuseDigital::setFtMusicState(): State music: %s, %s", _ftStateMusicTable[stateId].name, _ftStateMusicTable[stateId].audioName);
if (_curMusicState == stateId)
return;
if (_curMusicSeq == 0) {
if (stateId == 0) {
playFtMusic(nullptr, 0, 0);
} else {
playFtMusic(_ftStateMusicTable[stateId].audioName, _ftStateMusicTable[stateId].transitionType, _ftStateMusicTable[stateId].volume);
}
}
_curMusicState = stateId;
}
void IMuseDigital::setFtMusicSequence(int seqId) {
if (seqId > 52)
return;
debug(5, "IMuseDigital::setFtMusicSequence(): Sequence music: %s", _ftSeqNames[seqId].name);
if (_curMusicSeq != seqId) {
if (seqId == 0) {
if (_curMusicState == 0) {
playFtMusic(nullptr, 0, 0);
} else {
playFtMusic(_ftStateMusicTable[_curMusicState].audioName, _ftStateMusicTable[_curMusicState].transitionType, _ftStateMusicTable[_curMusicState].volume);
}
} else {
int seq = (seqId - 1) * 4;
playFtMusic(_ftSeqMusicTable[seq].audioName, _ftSeqMusicTable[seq].transitionType, _ftSeqMusicTable[seq].volume);
}
}
_curMusicSeq = seqId;
_curMusicCue = 0;
}
void IMuseDigital::setDigMusicState(int stateId) {
int l, num = -1;
for (l = 0; _digStateMusicTable[l].soundId != -1; l++) {
if ((_digStateMusicTable[l].soundId == stateId)) {
debug(5, "IMuseDigital::setDigMusicState(): Set music state: %s, %s", _digStateMusicTable[l].name, _digStateMusicTable[l].filename);
num = l;
break;
}
}
if (num == -1) {
for (l = 0; _digStateMusicMap[l].roomId != -1; l++) {
if ((_digStateMusicMap[l].roomId == stateId)) {
break;
}
}
num = l;
int offset = _attributes[_digStateMusicMap[num].offset];
if (offset == 0) {
if (_attributes[_digStateMusicMap[num].attribPos] != 0) {
num = _digStateMusicMap[num].stateIndex3;
} else {
num = _digStateMusicMap[num].stateIndex1;
}
} else {
int stateIndex2 = _digStateMusicMap[num].stateIndex2;
if (stateIndex2 == 0) {
num = _digStateMusicMap[num].stateIndex1 + offset;
} else {
num = stateIndex2;
}
}
}
debug(5, "IMuseDigital::setDigMusicState(): Set music state: %s, %s", _digStateMusicTable[num].name, _digStateMusicTable[num].filename);
if (_curMusicState == num)
return;
if (_curMusicSeq == 0) {
if (num == 0)
playDigMusic(nullptr, &_digStateMusicTable[0], num, false);
else
playDigMusic(_digStateMusicTable[num].name, &_digStateMusicTable[num], num, false);
}
_curMusicState = num;
}
void IMuseDigital::setDigMusicSequence(int seqId) {
int l, num = -1;
if (seqId == 0)
seqId = 2000;
for (l = 0; _digSeqMusicTable[l].soundId != -1; l++) {
if ((_digSeqMusicTable[l].soundId == seqId)) {
debug(5, "IMuseDigital::setDigMusicSequence(): Set music sequence: %s, %s", _digSeqMusicTable[l].name, _digSeqMusicTable[l].filename);
num = l;
break;
}
}
if (num == -1)
return;
if (_curMusicSeq == num)
return;
if (num != 0) {
if (_curMusicSeq && ((_digSeqMusicTable[_curMusicSeq].transitionType == 4)
|| (_digSeqMusicTable[_curMusicSeq].transitionType == 6))) {
_nextSeqToPlay = num;
return;
} else {
playDigMusic(_digSeqMusicTable[num].name, &_digSeqMusicTable[num], 0, true);
_nextSeqToPlay = 0;
_attributes[DIG_SEQ_OFFSET + num] = 1;
}
} else {
if (_nextSeqToPlay != 0) {
playDigMusic(_digSeqMusicTable[_nextSeqToPlay].name, &_digSeqMusicTable[_nextSeqToPlay], 0, true);
_attributes[DIG_SEQ_OFFSET + _nextSeqToPlay] = 1;
num = _nextSeqToPlay;
_nextSeqToPlay = 0;
} else {
if (_curMusicState != 0) {
playDigMusic(_digStateMusicTable[_curMusicState].name, &_digStateMusicTable[_curMusicState], _curMusicState, true);
} else {
playDigMusic(nullptr, &_digStateMusicTable[0], _curMusicState, true);
}
num = 0;
}
}
_curMusicSeq = num;
}
void IMuseDigital::setComiMusicState(int stateId) {
int l, num = -1;
if (stateId == 0)
stateId = 1000;
if ((_vm->_game.features & GF_DEMO) && stateId == 1000)
stateId = 0;
if (!(_vm->_game.features & GF_DEMO)) {
for (l = 0; _comiStateMusicTable[l].soundId != -1; l++) {
if ((_comiStateMusicTable[l].soundId == stateId)) {
debug(5, "IMuseDigital::setComiMusicState(): Set music state: %s, %s", _comiStateMusicTable[l].name, _comiStateMusicTable[l].filename);
num = l;
break;
}
}
}
if (num == -1 && !(_vm->_game.features & GF_DEMO))
return;
if (!(_vm->_game.features & GF_DEMO) && _curMusicState == num) {
return;
} else if ((_vm->_game.features & GF_DEMO) && _curMusicState == stateId) {
return;
}
if (_curMusicSeq == 0) {
if (_vm->_game.features & GF_DEMO) {
if (_curMusicSeq == 0) {
if (stateId == 0)
playComiDemoMusic(nullptr, &_comiDemoStateMusicTable[0], stateId, false);
else
playComiDemoMusic(_comiDemoStateMusicTable[stateId].name, &_comiDemoStateMusicTable[stateId], stateId, false);
}
} else {
if (num == 0)
playComiMusic(nullptr, &_comiStateMusicTable[0], num, false);
else
playComiMusic(_comiStateMusicTable[num].name, &_comiStateMusicTable[num], num, false);
}
}
if (!(_vm->_game.features & GF_DEMO)) {
_curMusicState = num;
} else {
_curMusicState = stateId;
}
}
void IMuseDigital::setComiMusicSequence(int seqId) {
int l, num = -1;
if (seqId == 0)
seqId = 2000;
for (l = 0; _comiSeqMusicTable[l].soundId != -1; l++) {
if ((_comiSeqMusicTable[l].soundId == seqId)) {
debug(5, "IMuseDigital::setComiMusicSequence(): Set music sequence: %s, %s", _comiSeqMusicTable[l].name, _comiSeqMusicTable[l].filename);
num = l;
break;
}
}
if (num == -1)
return;
if (_curMusicSeq == num)
return;
if (num != 0) {
if (_curMusicSeq && ((_comiSeqMusicTable[_curMusicSeq].transitionType == 4)
|| (_comiSeqMusicTable[_curMusicSeq].transitionType == 6))) {
_nextSeqToPlay = num;
return;
} else {
playComiMusic(_comiSeqMusicTable[num].name, &_comiSeqMusicTable[num], 0, true);
_nextSeqToPlay = 0;
}
} else {
if (_nextSeqToPlay != 0) {
playComiMusic(_comiSeqMusicTable[_nextSeqToPlay].name, &_comiSeqMusicTable[_nextSeqToPlay], 0, true);
num = _nextSeqToPlay;
_nextSeqToPlay = 0;
} else {
if (_curMusicState != 0) {
playComiMusic(_comiStateMusicTable[_curMusicState].name, &_comiStateMusicTable[_curMusicState], _curMusicState, true);
} else {
playComiMusic(nullptr, &_comiStateMusicTable[0], _curMusicState, true);
}
num = 0;
}
}
_curMusicSeq = num;
}
void IMuseDigital::playFtMusic(const char *songName, int transitionType, int volume) {
int oldSoundId = 0;
int soundId;
if (!_spooledMusicEnabled)
return;
// Check for any music piece which is played as a SFX (without an associated stream)
// and fade it out
for (int i = diMUSEGetNextSound(0); i; i = diMUSEGetNextSound(i)) {
if (diMUSEGetParam(i, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(i, DIMUSE_P_SND_HAS_STREAM))
diMUSEFadeParam(i, DIMUSE_P_VOLUME, 0, 200);
}
// Now grab the current standard music soundId: it will either be crossfaded by SwitchStream,
// or faded out
for (int j = diMUSEGetNextSound(0); j; j = diMUSEGetNextSound(j)) {
if (diMUSEGetParam(j, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && diMUSEGetParam(j, DIMUSE_P_SND_HAS_STREAM))
oldSoundId = j;
}
if (songName) {
switch (transitionType) {
case 0:
debug(5, "IMuseDigital::playFtMusic(): NULL transition, ignored");
return;
case 1:
soundId = getSoundIdByName(songName);
if (_filesHandler->openSound(soundId))
return;
if (!soundId) {
debug(5, "IMuseDigital::playFtMusic(): failed to retrieve soundId for sound \"%s\"", songName);
break;
}
if (diMUSEStartSound(soundId, 126))
debug(5, "IMuseDigital::playFtMusic(): transition 1, failed to start sound \"%s\"(%d)", songName, soundId);
_filesHandler->closeSound(soundId);
diMUSESetParam(soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetParam(soundId, DIMUSE_P_VOLUME, volume);
break;
case 2:
case 3:
soundId = getSoundIdByName(songName);
if (soundId)
if (_filesHandler->openSound(soundId))
return;
if (soundId) {
if (oldSoundId) {
if (oldSoundId != soundId || transitionType == 2) {
diMUSESwitchStream(oldSoundId, soundId, _ftCrossfadeBuffer, sizeof(_ftCrossfadeBuffer), 0);
}
// WORKAROUND for bug in the original: at the beginning of the game, going in
// and out of the bar a couple of times breaks and temporarily stops the music
// Here, we override the fade out, just like the remastered does
if (oldSoundId == soundId && soundId == 622 && _vm->enhancementEnabled(kEnhAudioChanges)) {
diMUSEFadeParam(soundId, DIMUSE_P_VOLUME, volume, 200);
}
} else if (diMUSEStartStream(soundId, 126, DIMUSE_BUFFER_MUSIC)) {
debug(5, "IMuseDigital::playFtMusic(): failed to start the stream for \"%s\" (%d)", songName, soundId);
}
_filesHandler->closeSound(soundId);
diMUSESetParam(soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetParam(soundId, DIMUSE_P_VOLUME, volume);
} else {
debug(5, "IMuseDigital::playFtMusic(): failed to retrieve soundId for sound \"%s\" (%d)", songName, soundId);
}
break;
case 4:
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 200);
return;
default:
debug(5, "IMuseDigital::playFtMusic(): bogus transition type, ignored");
return;
}
} else {
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 200);
}
}
void IMuseDigital::playDigMusic(const char *songName, const imuseDigTable *table, int attribPos, bool sequence) {
int hookId = 0;
if (songName != nullptr) {
if ((_attributes[DIG_SEQ_OFFSET + 38]) && (!_attributes[DIG_SEQ_OFFSET + 41])) {
if ((attribPos == 43) || (attribPos == 44))
hookId = 3;
}
if ((_attributes[DIG_SEQ_OFFSET + 46] != 0) && (_attributes[DIG_SEQ_OFFSET + 48] == 0)) {
if ((attribPos == 38) || (attribPos == 39))
hookId = 3;
}
if ((_attributes[DIG_SEQ_OFFSET + 53] != 0)) {
if ((attribPos == 50) || (attribPos == 51))
hookId = 3;
}
if ((attribPos != 0) && (hookId == 0)) {
if (table->attribPos != 0)
attribPos = table->attribPos;
hookId = _attributes[DIG_STATE_OFFSET + attribPos];
if (table->hookId != 0) {
if ((hookId != 0) && (table->hookId > 1)) {
_attributes[DIG_STATE_OFFSET + attribPos] = 2;
} else {
_attributes[DIG_STATE_OFFSET + attribPos] = hookId + 1;
if (table->hookId < hookId + 1)
_attributes[DIG_STATE_OFFSET + attribPos] = 1;
}
}
}
}
int nextSoundId = 0;
while (1) {
nextSoundId = diMUSEGetNextSound(nextSoundId);
if (!nextSoundId)
break;
// If a sound is found (and its stream is active), fade it out if it's a music track
if (diMUSEGetParam(nextSoundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM))
diMUSEFadeParam(nextSoundId, DIMUSE_P_VOLUME, 0, 120);
}
int oldSoundId = 0;
nextSoundId = 0;
while (1) {
nextSoundId = diMUSEGetNextSound(nextSoundId);
if (!nextSoundId)
break;
if (diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM) && (diMUSEGetParam(nextSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC)) {
oldSoundId = nextSoundId;
break;
}
}
if (!songName) {
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 120);
return;
}
switch (table->transitionType) {
case 0:
debug(5, "IMuseDigital::playDigMusic(): NULL transition, ignored");
break;
case 1:
if (_filesHandler->openSound(table->soundId))
return;
if (table->soundId) {
if (diMUSEStartSound(table->soundId, 126))
debug(5, "IMuseDigital::playDigMusic(): transition 1, failed to start the sound (%d)", table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 1);
diMUSEFadeParam(table->soundId, DIMUSE_P_VOLUME, 127, 120);
_filesHandler->closeSound(table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
} else {
debug(5, "IMuseDigital::playDigMusic(): transition 1, empty soundId, ignored");
}
break;
case 2:
case 3:
case 4:
if (_filesHandler->openSound(table->soundId))
return;
if (table->filename[0] == 0 || table->soundId == 0) {
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
return;
}
if (table->transitionType == 4) {
_stopSequenceFlag = 0;
diMUSESetTrigger(table->soundId, MKTAG('_', 'e', 'n', 'd'), DIMUSE_C_SCRIPT_CALLBACK);
}
if (oldSoundId) {
if (table->transitionType == 2) {
diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 0);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, hookId);
diMUSEProcessStreams();
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
return;
}
if (oldSoundId != table->soundId) {
if ((!sequence) && (table->attribPos != 0) &&
(table->attribPos == _digStateMusicTable[_curMusicState].attribPos)) {
diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 1);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSEProcessStreams();
} else {
diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 0);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, hookId);
diMUSEProcessStreams();
_filesHandler->closeSound(table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
}
}
} else {
if (diMUSEStartStream(table->soundId, 126, DIMUSE_BUFFER_MUSIC))
debug(5, "IMuseDigital::playDigMusic(): failed to start the stream for sound %d", table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, hookId);
}
_filesHandler->closeSound(table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
break;
case 5:
debug(5, "IMuseDigital::playDigMusic(): no-op transition type (5), ignored");
break;
case 6:
_stopSequenceFlag = 0;
diMUSESetTrigger(DIMUSE_SMUSH_SOUNDID + DIMUSE_BUFFER_MUSIC, MKTAG('_', 'e', 'n', 'd'), DIMUSE_C_SCRIPT_CALLBACK);
break;
case 7:
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
break;
default:
debug(5, "IMuseDigital::playDigMusic(): bogus transition type, ignored");
break;
}
}
void IMuseDigital::playComiDemoMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence) {
// This is a stripped down version of playDigMusic
int hookId = 0;
if (songName != nullptr) {
if (attribPos != 0) {
if (table->attribPos != 0)
attribPos = table->attribPos;
}
}
int nextSoundId = 0;
while (1) {
nextSoundId = diMUSEGetNextSound(nextSoundId);
if (!nextSoundId)
break;
// If a sound is found (and its stream is active), fade it out if it's a music track
if (diMUSEGetParam(nextSoundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM))
diMUSEFadeParam(nextSoundId, DIMUSE_P_VOLUME, 0, 120);
}
int oldSoundId = 0;
nextSoundId = 0;
while (1) {
nextSoundId = diMUSEGetNextSound(nextSoundId);
if (!nextSoundId)
break;
if (diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM) && (diMUSEGetParam(nextSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC)) {
oldSoundId = nextSoundId;
break;
}
}
if (!songName) {
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 120);
return;
}
switch (table->transitionType) {
case 3:
if (_filesHandler->openSound(table->soundId))
return;
if (table->filename[0] == 0 || table->soundId == 0) {
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
return;
}
if (oldSoundId) {
if (oldSoundId != table->soundId) {
if ((!sequence) && (table->attribPos != 0) &&
(table->attribPos == _comiDemoStateMusicTable[_curMusicState].attribPos)) {
diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 1);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSEProcessStreams();
} else {
diMUSESwitchStream(oldSoundId, table->soundId, 1800, 0, 0);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, hookId);
diMUSEProcessStreams();
_filesHandler->closeSound(table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
}
}
} else {
if (diMUSEStartStream(table->soundId, 126, DIMUSE_BUFFER_MUSIC))
debug(5, "IMuseDigital::playComiDemoMusic(): failed to start the stream for sound %d", table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, hookId);
}
_filesHandler->closeSound(table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF); // Repeated intentionally
break;
default:
debug(5, "IMuseDigital::playComiDemoMusic(): bogus or unused transition type, ignored");
break;
}
}
void IMuseDigital::playComiMusic(const char *songName, const imuseComiTable *table, int attribPos, bool sequence) {
int hookId = 0;
int fadeDelay = 0;
// This flag is not set in the original,
// but since we're not using DirectSound and this is pretty much timing related
// please see IMuseDigital::waveOutWrite() for more context...
Common::StackLock lock(*_mutex);
_waveOutXorTrigger = 1;
if ((songName != nullptr) && (attribPos != 0)) {
if (table->attribPos != 0)
attribPos = table->attribPos;
hookId = _attributes[COMI_STATE_OFFSET + attribPos];
if (table->hookId != 0) {
if ((hookId != 0) && (table->hookId > 1)) {
_attributes[COMI_STATE_OFFSET + attribPos] = 2;
} else {
_attributes[COMI_STATE_OFFSET + attribPos] = hookId + 1;
if (table->hookId < hookId + 1)
_attributes[COMI_STATE_OFFSET + attribPos] = 1;
}
}
}
int nextSoundId = 0;
while (1) {
nextSoundId = diMUSEGetNextSound(nextSoundId);
if (!nextSoundId)
break;
// If a sound is found (and its stream is active), fade it out if it's a music track
if (diMUSEGetParam(nextSoundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF && !diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM))
diMUSEFadeParam(nextSoundId, DIMUSE_P_VOLUME, 0, 120);
}
int oldSoundId = 0;
nextSoundId = 0;
while (1) {
nextSoundId = diMUSEGetNextSound(nextSoundId);
if (!nextSoundId)
break;
if (diMUSEGetParam(nextSoundId, DIMUSE_P_SND_HAS_STREAM) && (diMUSEGetParam(nextSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC)) {
oldSoundId = nextSoundId;
break;
}
}
if (!songName) {
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 120);
return;
}
switch (table->transitionType) {
case 0:
debug(5, "IMuseDigital::playComiMusic(): NULL transition, ignored");
break;
case 1:
if (_filesHandler->openSound(table->soundId))
return;
if (table->soundId) {
if (diMUSEStartSound(table->soundId, 126))
debug(5, "IMuseDigital::playComiMusic(): transition 1, failed to start the sound (%d)", table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 1);
diMUSEFadeParam(table->soundId, DIMUSE_P_VOLUME, 127, 120);
_filesHandler->closeSound(table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
} else {
debug(5, "IMuseDigital::playComiMusic(): transition 1, empty soundId, ignored");
}
break;
case 2:
case 3:
case 4:
case 12:
if (_filesHandler->openSound(table->soundId))
return;
if (table->filename[0] == 0 || table->soundId == 0) {
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
break;
}
if (table->transitionType == 4) {
_stopSequenceFlag = 0;
diMUSESetTrigger(table->soundId, MKTAG('_', 'e', 'n', 'd'), DIMUSE_C_SCRIPT_CALLBACK);
}
if (oldSoundId) {
fadeDelay = table->fadeOutDelay;
if (!fadeDelay)
fadeDelay = 1000;
else
fadeDelay = (fadeDelay * 100) / 6; // Set dimuse_table fade out time to millisecond scale
if (table->transitionType == 2) {
diMUSESwitchStream(oldSoundId, table->soundId, fadeDelay, 0, 0);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, table->hookId);
diMUSEProcessStreams();
} else if (oldSoundId != table->soundId) {
if ((!sequence) && (table->attribPos != 0) &&
(table->attribPos == _comiStateMusicTable[_curMusicState].attribPos)) {
debug(5, "IMuseDigital::playComiMusic(): Starting new sound (%s) with same attribute as old sound (%s)",
table->name, _comiStateMusicTable[_curMusicState].name);
diMUSESwitchStream(oldSoundId, table->soundId, fadeDelay, 0, 1);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSEProcessStreams();
} else {
switch (table->transitionType) {
case 12:
diMUSESetHook(oldSoundId, table->hookId);
diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), DIMUSE_C_SWITCH_STREAM, oldSoundId, table->soundId, fadeDelay, 1, 0);
diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), DIMUSE_C_SET_PARAM, table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), DIMUSE_C_SET_PARAM, table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetTrigger(oldSoundId, MKTAG('e', 'x', 'i', 't'), DIMUSE_C_SET_HOOK, table->soundId, hookId);
diMUSEProcessStreams();
break;
default:
diMUSESwitchStream(oldSoundId, table->soundId, fadeDelay, 0, 0);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, hookId);
diMUSEProcessStreams();
break;
}
}
}
} else {
if (diMUSEStartStream(table->soundId, 126, DIMUSE_BUFFER_MUSIC))
debug(5, "IMuseDigital::playComiMusic(): failed to start the stream for sound %d", table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_VOLUME, 127);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
diMUSESetHook(table->soundId, hookId);
}
_filesHandler->closeSound(table->soundId);
diMUSESetParam(table->soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSICEFF);
break;
case 5:
debug(5, "IMuseDigital::playComiMusic(): no-op transition type (5), ignored");
break;
case 6:
_stopSequenceFlag = 0;
diMUSESetTrigger(DIMUSE_SMUSH_SOUNDID + DIMUSE_BUFFER_MUSIC, MKTAG('_', 'e', 'n', 'd'), DIMUSE_C_SCRIPT_CALLBACK);
break;
case 7:
if (oldSoundId)
diMUSEFadeParam(oldSoundId, DIMUSE_P_VOLUME, 0, 60);
break;
case 8:
if (oldSoundId)
diMUSESetHook(oldSoundId, table->hookId);
break;
case 9:
if (oldSoundId)
diMUSESetHook(oldSoundId, table->hookId);
_stopSequenceFlag = 0;
diMUSESetTrigger(oldSoundId, MKTAG('_', 'e', 'n', 'd'), DIMUSE_C_SCRIPT_CALLBACK);
break;
default:
debug(5, "IMuseDigital::playComiMusic(): bogus transition type, ignored");
break;
}
}
} // End of namespace Scumm

View File

@@ -0,0 +1,285 @@
/* 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/scummsys.h"
#include "audio/audiostream.h"
#include "audio/decoders/flac.h"
#include "audio/decoders/voc.h"
#include "audio/decoders/vorbis.h"
#include "audio/decoders/mp3.h"
#include "scumm/resource.h"
#include "scumm/scumm.h"
#include "scumm/imuse_digi/dimuse_bndmgr.h"
#include "scumm/imuse_digi/dimuse_codecs.h"
#include "scumm/imuse_digi/dimuse_sndmgr.h"
namespace Scumm {
ImuseDigiSndMgr::ImuseDigiSndMgr(ScummEngine *scumm) {
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
memset(&_sounds[l], 0, sizeof(SoundDesc));
}
_vm = scumm;
_disk = 0;
_cacheBundleDir = new BundleDirCache(scumm);
assert(_cacheBundleDir);
BundleCodecs::initializeImcTables();
}
ImuseDigiSndMgr::~ImuseDigiSndMgr() {
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
closeSound(&_sounds[l]);
}
delete _cacheBundleDir;
BundleCodecs::releaseImcTables();
}
ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::allocSlot() {
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
if (!_sounds[l].inUse) {
_sounds[l].inUse = true;
_sounds[l].scheduledForDealloc = false;
return &_sounds[l];
}
}
return nullptr;
}
bool ImuseDigiSndMgr::openMusicBundle(SoundDesc *sound, int &disk) {
bool result = false;
bool compressed = false;
sound->bundle = new BundleMgr(_vm, _cacheBundleDir);
assert(sound->bundle);
if (_vm->_game.id == GID_CMI) {
if (_vm->_game.features & GF_DEMO) {
result = sound->bundle->open("music.bun", compressed);
} else {
char musicfile[20];
if (disk == -1)
disk = _vm->VAR(_vm->VAR_CURRENTDISK);
Common::sprintf_s(musicfile, "musdisk%d.bun", disk);
// if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) {
// _vm->_DiMUSE_v1->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
// _vm->_DiMUSE_v1->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
// _vm->_DiMUSE_v1->stopAllSounds();
// sound->bundle->closeFile();
// }
result = sound->bundle->open(musicfile, compressed);
// FIXME: Shouldn't we only set _disk if result == true?
_disk = (byte)_vm->VAR(_vm->VAR_CURRENTDISK);
}
} else if (_vm->_game.id == GID_DIG)
result = sound->bundle->open("digmusic.bun", compressed);
else
error("ImuseDigiSndMgr::openMusicBundle() Don't know which bundle file to load");
_vm->VAR(_vm->VAR_MUSIC_BUNDLE_LOADED) = result ? 1 : 0;
return result;
}
bool ImuseDigiSndMgr::openVoiceBundle(SoundDesc *sound, int &disk) {
bool result = false;
bool compressed = false;
sound->bundle = new BundleMgr(_vm, _cacheBundleDir);
assert(sound->bundle);
if (_vm->_game.id == GID_CMI) {
if (_vm->_game.features & GF_DEMO) {
result = sound->bundle->open("voice.bun", compressed);
} else {
char voxfile[20];
if (disk == -1)
disk = _vm->VAR(_vm->VAR_CURRENTDISK);
Common::sprintf_s(voxfile, "voxdisk%d.bun", disk);
// if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) {
// _vm->_DiMUSE_v1->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
// _vm->_DiMUSE_v1->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
// _vm->_DiMUSE_v1->stopAllSounds();
// sound->bundle->closeFile();
// }
result = sound->bundle->open(voxfile, compressed);
// FIXME: Shouldn't we only set _disk if result == true?
_disk = (byte)_vm->VAR(_vm->VAR_CURRENTDISK);
}
} else if (_vm->_game.id == GID_DIG)
result = sound->bundle->open("digvoice.bun", compressed);
else
error("ImuseDigiSndMgr::openVoiceBundle() Don't know which bundle file to load");
_vm->VAR(_vm->VAR_VOICE_BUNDLE_LOADED) = result ? 1 : 0;
return result;
}
ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::openSound(int32 soundId, const char *soundName, int soundType, int volGroupId, int disk) {
assert(soundId >= 0);
assert(soundType);
SoundDesc *sound = allocSlot();
if (!sound) {
error("ImuseDigiSndMgr::openSound() can't alloc free sound slot");
}
const bool header_outside = ((_vm->_game.id == GID_CMI) && !(_vm->_game.features & GF_DEMO));
bool result = false;
byte *ptr = nullptr;
switch (soundType) {
case IMUSE_RESOURCE:
assert(soundName[0] == 0); // Paranoia check
_vm->_res->lock(rtSound, soundId);
ptr = _vm->getResourceAddress(rtSound, soundId);
if (ptr == nullptr) {
closeSound(sound);
return nullptr;
}
sound->resPtr = ptr;
sound->resSize = _vm->getResourceSize(rtSound, soundId) - 8;
break;
case IMUSE_BUNDLE:
if (volGroupId == IMUSE_VOLGRP_VOICE)
result = openVoiceBundle(sound, disk);
else if (volGroupId == IMUSE_VOLGRP_MUSIC)
result = openMusicBundle(sound, disk);
else
error("ImuseDigiSndMgr::openSound() Don't know how load sound: %d", soundId);
if (!result) {
closeSound(sound);
return nullptr;
}
if (soundName[0] != 0) {
if (sound->bundle->readFile(soundName, 0x2000, &ptr, header_outside) == 0 || ptr == nullptr) {
closeSound(sound);
free(ptr);
return nullptr;
}
}
sound->resPtr = nullptr;
break;
default:
error("ImuseDigiSndMgr::openSound() Unknown soundType %d (trying to load sound %d)", soundType, soundId);
}
Common::strlcpy(sound->name, soundName, sizeof(sound->name));
sound->soundId = soundId;
if (soundType == IMUSE_BUNDLE) {
free(ptr);
}
return sound;
}
void ImuseDigiSndMgr::closeSound(SoundDesc *soundDesc) {
// Check if there's an actual sound to close...
if (!checkForProperHandle(soundDesc))
return;
if (soundDesc->resPtr) {
bool found = false;
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
if ((_sounds[l].soundId == soundDesc->soundId) && (&_sounds[l] != soundDesc))
found = true;
}
if (!found)
_vm->_res->unlock(rtSound, soundDesc->soundId);
}
delete soundDesc->bundle;
memset(soundDesc, 0, sizeof(SoundDesc));
}
ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::findSoundById(int soundId) {
SoundDesc *soundDesc = nullptr;
for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
if (_sounds[i].soundId == soundId) {
soundDesc = &_sounds[i];
break;
}
}
return soundDesc;
}
ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::getSounds() {
return _sounds;
}
void ImuseDigiSndMgr::scheduleSoundForDeallocation(int soundId) {
SoundDesc *soundDesc = nullptr;
for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
if (_sounds[i].soundId == soundId) {
soundDesc = &_sounds[i];
}
}
// Check if there's an actual sound to deallocate...
if (!checkForProperHandle(soundDesc))
return;
soundDesc->scheduledForDealloc = true;
}
void ImuseDigiSndMgr::closeSoundById(int soundId) {
SoundDesc *soundDesc = nullptr;
for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
if (_sounds[i].soundId == soundId) {
soundDesc = &_sounds[i];
}
}
if (soundDesc) {
assert(checkForProperHandle(soundDesc));
if (soundDesc->resPtr) {
_vm->_res->unlock(rtSound, soundDesc->soundId);
}
delete soundDesc->bundle;
memset(soundDesc, 0, sizeof(SoundDesc));
}
}
bool ImuseDigiSndMgr::checkForProperHandle(SoundDesc *soundDesc) {
if (!soundDesc)
return false;
for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
if (soundDesc == &_sounds[l])
return true;
}
return false;
}
} // End of namespace Scumm

View File

@@ -0,0 +1,97 @@
/* 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/>.
*
*/
#ifndef SCUMM_IMUSE_DIGI_SNDMGR_H
#define SCUMM_IMUSE_DIGI_SNDMGR_H
#include "common/scummsys.h"
namespace Audio {
class SeekableAudioStream;
}
namespace Common {
class SeekableReadStream;
}
namespace Scumm {
class ScummEngine;
class BundleMgr;
class BundleDirCache;
class ImuseDigiSndMgr {
public:
#define MAX_IMUSE_SOUNDS 16
#define IMUSE_RESOURCE 1
#define IMUSE_BUNDLE 2
#define IMUSE_VOLGRP_VOICE 1
#define IMUSE_VOLGRP_SFX 2
#define IMUSE_VOLGRP_MUSIC 3
public:
struct SoundDesc {
bool inUse;
bool scheduledForDealloc;
byte *resPtr;
int resSize;
int resCurOffset;
char name[15];
int16 soundId;
BundleMgr *bundle;
};
private:
SoundDesc _sounds[MAX_IMUSE_SOUNDS];
bool checkForProperHandle(SoundDesc *soundDesc);
SoundDesc *allocSlot();
ScummEngine *_vm;
byte _disk;
BundleDirCache *_cacheBundleDir;
bool openMusicBundle(SoundDesc *sound, int &disk);
bool openVoiceBundle(SoundDesc *sound, int &disk);
public:
ImuseDigiSndMgr(ScummEngine *scumm);
~ImuseDigiSndMgr();
SoundDesc *openSound(int32 soundId, const char *soundName, int soundType, int volGroupId, int disk);
void closeSound(SoundDesc *soundDesc);
void closeSoundById(int soundId);
SoundDesc *findSoundById(int soundId);
SoundDesc *getSounds();
void scheduleSoundForDeallocation(int soundId);
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,386 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
int IMuseDigital::streamerInit() {
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
_streams[l].soundId = 0;
}
_lastStreamLoaded = nullptr;
return 0;
}
IMuseDigiStream *IMuseDigital::streamerAllocateSound(int soundId, int bufId, int32 maxRead) {
IMuseDigiSndBuffer *bufInfoPtr = _filesHandler->getBufInfo(bufId);
if (!bufInfoPtr) {
debug(5, "IMuseDigital::streamerAlloc(): ERROR: couldn't get buffer info");
return nullptr;
}
if ((bufInfoPtr->bufSize / 4) <= maxRead) {
debug(5, "IMuseDigital::streamerAlloc(): ERROR: maxRead too big for buffer");
return nullptr;
}
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
if (_streams[l].soundId && _streams[l].bufId == bufId) {
debug(5, "IMuseDigital::streamerAlloc(): ERROR: stream bufId %d already in use", bufId);
return nullptr;
}
}
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
if (!_streams[l].soundId) {
_streams[l].endOffset = _filesHandler->seek(soundId, 0, SEEK_END, bufId);
_streams[l].curOffset = 0;
_streams[l].soundId = soundId;
_streams[l].bufId = bufId;
_streams[l].buf = bufInfoPtr->buffer;
_streams[l].bufFreeSize = bufInfoPtr->bufSize - maxRead - (_isEarlyDiMUSE ? 0 : 4);
_streams[l].loadSize = bufInfoPtr->loadSize;
_streams[l].criticalSize = bufInfoPtr->criticalSize;
_streams[l].maxRead = maxRead;
_streams[l].loadIndex = 0;
_streams[l].readIndex = 0;
_streams[l].paused = 0;
_streams[l].vocLoopFlag = 0;
_streams[l].vocLoopTriggerOffset = 0;
return &_streams[l];
}
}
debug(5, "IMuseDigital::streamerAlloc(): ERROR: no spare streams");
return nullptr;
}
int IMuseDigital::streamerClearSoundInStream(IMuseDigiStream *streamPtr) {
streamPtr->soundId = 0;
if (_lastStreamLoaded == streamPtr) {
_lastStreamLoaded = 0;
}
return 0;
}
int IMuseDigital::streamerProcessStreams() {
if (!_isEarlyDiMUSE)
dispatchPredictFirstStream();
IMuseDigiStream *stream1 = nullptr;
IMuseDigiStream *stream2 = nullptr;
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
if ((_streams[l].soundId) && (!_streams[l].paused)) {
if (stream2) {
if (stream1) {
debug(5, "IMuseDigital::streamerProcessStreams(): WARNING: three streams in use");
} else {
stream1 = &_streams[l];
}
} else {
stream2 = &_streams[l];
}
}
}
if (!stream1) {
if (stream2)
streamerFetchData(stream2);
return 0;
}
if (!stream2) {
if (stream1)
streamerFetchData(stream1);
return 0;
}
// Check if we've reached or surpassed criticalSize
bool critical1 = (streamerGetFreeBufferAmount(stream1) >= stream1->criticalSize);
bool critical2 = (streamerGetFreeBufferAmount(stream2) >= stream2->criticalSize);
if (!critical1) {
if (!critical2) {
if (stream1 == _lastStreamLoaded) {
streamerFetchData(stream1);
streamerFetchData(stream2);
return 0;
} else {
streamerFetchData(stream2);
streamerFetchData(stream1);
return 0;
}
} else {
streamerFetchData(stream1);
return 0;
}
}
if (!critical2) {
streamerFetchData(stream2);
return 0;
} else {
if (stream1 == _lastStreamLoaded) {
streamerFetchData(stream1);
} else {
streamerFetchData(stream2);
}
return 0;
}
}
uint8 *IMuseDigital::streamerGetStreamBuffer(IMuseDigiStream *streamPtr, int size) {
if (size > streamerGetFreeBufferAmount(streamPtr) || streamPtr->maxRead < size)
return 0;
if (size > streamPtr->bufFreeSize - streamPtr->readIndex) {
memcpy(&streamPtr->buf[streamPtr->bufFreeSize],
streamPtr->buf,
size - streamPtr->bufFreeSize + streamPtr->readIndex + (_isEarlyDiMUSE ? 0 : 4));
}
int32 readIndex_tmp = streamPtr->readIndex;
uint8 *ptr = &streamPtr->buf[readIndex_tmp];
streamPtr->readIndex = readIndex_tmp + size;
if (streamPtr->bufFreeSize <= readIndex_tmp + size)
streamPtr->readIndex = readIndex_tmp + size - streamPtr->bufFreeSize;
return ptr;
}
uint8 *IMuseDigital::streamerGetStreamBufferAtOffset(IMuseDigiStream *streamPtr, int32 offset, int size) {
if (offset + size > streamerGetFreeBufferAmount(streamPtr) || streamPtr->maxRead < size)
return 0;
int32 offsetReadIndex = offset + streamPtr->readIndex;
if (offsetReadIndex >= streamPtr->bufFreeSize) {
offsetReadIndex -= streamPtr->bufFreeSize;
}
if (size > streamPtr->bufFreeSize - offsetReadIndex) {
memcpy(&streamPtr->buf[streamPtr->bufFreeSize], streamPtr->buf, size + offsetReadIndex - streamPtr->bufFreeSize);
}
return &streamPtr->buf[offsetReadIndex];
}
int IMuseDigital::streamerSetReadIndex(IMuseDigiStream *streamPtr, int offset) {
_streamerBailFlag = 1;
if (offset > streamerGetFreeBufferAmount(streamPtr))
return -1;
streamPtr->readIndex += offset;
if (streamPtr->readIndex >= streamPtr->bufFreeSize) {
streamPtr->readIndex -= streamPtr->bufFreeSize;
}
return 0;
}
int IMuseDigital::streamerSetLoadIndex(IMuseDigiStream *streamPtr, int offset) {
_streamerBailFlag = 1;
if (offset > streamerGetFreeBufferAmount(streamPtr))
return -1;
streamPtr->loadIndex = streamPtr->readIndex + offset;
if (streamPtr->loadIndex >= streamPtr->bufFreeSize) {
streamPtr->loadIndex -= streamPtr->bufFreeSize;
}
return 0;
}
int IMuseDigital::streamerGetFreeBufferAmount(IMuseDigiStream *streamPtr) {
int32 freeBufferSize = streamPtr->loadIndex - streamPtr->readIndex;
if (freeBufferSize < 0)
freeBufferSize += streamPtr->bufFreeSize;
return freeBufferSize;
}
int IMuseDigital::streamerSetSoundToStreamFromOffset(IMuseDigiStream *streamPtr, int soundId, int32 offset) {
_streamerBailFlag = 1;
streamPtr->soundId = soundId;
streamPtr->curOffset = offset;
streamPtr->endOffset = _isEarlyDiMUSE ? _filesHandler->seek(streamPtr->soundId, 0, SEEK_END, streamPtr->bufId) : 0;
streamPtr->paused = 0;
if (_lastStreamLoaded == streamPtr) {
_lastStreamLoaded = nullptr;
}
return 0;
}
void IMuseDigital::streamerQueryStream(IMuseDigiStream *streamPtr, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused) {
if (!isFTSoundEngine())
dispatchPredictFirstStream();
bufSize = streamPtr->bufFreeSize;
criticalSize = (isFTSoundEngine() && streamPtr->paused) ? 0 : streamPtr->criticalSize;
freeSpace = streamerGetFreeBufferAmount(streamPtr);
paused = streamPtr->paused;
}
int IMuseDigital::streamerFeedStream(IMuseDigiStream *streamPtr, uint8 *srcBuf, int32 sizeToFeed, int paused) {
int32 size = streamPtr->readIndex - streamPtr->loadIndex;
if (size <= 0)
size += streamPtr->bufFreeSize;
if (sizeToFeed > (size - 4)) {
// Don't worry, judging from the intro of The Dig, this is totally expected
// to happen, and when it does, the output matches the EXE behavior
debug(5, "IMuseDigital::streamerFeedStream(): WARNING: buffer overflow");
_streamerBailFlag = 1;
int32 newTentativeSize = sizeToFeed - (size - 4) - (sizeToFeed - (size - 4)) % 12 + 12;
size = streamPtr->loadIndex - streamPtr->readIndex;
if (size < 0)
size += streamPtr->bufFreeSize;
if (size >= newTentativeSize) {
streamPtr->readIndex = newTentativeSize + streamPtr->readIndex;
if (streamPtr->bufFreeSize <= streamPtr->readIndex)
streamPtr->readIndex -= streamPtr->bufFreeSize;
}
}
if (sizeToFeed > 0) {
do {
size = streamPtr->bufFreeSize - streamPtr->loadIndex;
if (size >= sizeToFeed) {
size = sizeToFeed;
}
sizeToFeed -= size;
memcpy(&streamPtr->buf[streamPtr->loadIndex], srcBuf, size);
srcBuf += size;
int val = size + streamPtr->loadIndex;
streamPtr->curOffset += size;
streamPtr->loadIndex += size;
if (val >= streamPtr->bufFreeSize) {
streamPtr->loadIndex = val - streamPtr->bufFreeSize;
}
} while (sizeToFeed > 0);
}
streamPtr->paused = paused;
return 0;
}
int IMuseDigital::streamerFetchData(IMuseDigiStream *streamPtr) {
if (!_isEarlyDiMUSE && streamPtr->endOffset == 0) {
streamPtr->endOffset = _filesHandler->seek(streamPtr->soundId, 0, SEEK_END, streamPtr->bufId);
}
int32 size = streamPtr->readIndex - streamPtr->loadIndex;
if (size <= 0)
size += streamPtr->bufFreeSize;
int32 loadSize = streamPtr->loadSize;
int32 remainingSize = streamPtr->endOffset - streamPtr->curOffset;
if (loadSize >= size - (_isEarlyDiMUSE ? 1 : 4)) {
loadSize = size - (_isEarlyDiMUSE ? 1 : 4);
}
if (loadSize >= remainingSize) {
loadSize = remainingSize;
}
if (remainingSize <= 0) {
streamPtr->paused = 1;
if (!_isEarlyDiMUSE) {
// Pad the buffer
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
}
}
int32 actualAmount;
int32 requestedAmount;
while (1) {
if (!_isEarlyDiMUSE && loadSize <= 0)
return 0;
requestedAmount = streamPtr->bufFreeSize - streamPtr->loadIndex;
if (requestedAmount >= loadSize) {
requestedAmount = loadSize;
}
if (_filesHandler->seek(streamPtr->soundId, streamPtr->curOffset, SEEK_SET, streamPtr->bufId) != streamPtr->curOffset) {
debug(5, "IMuseDigital::streamerFetchData(): ERROR: invalid seek in streamer (%d), pausing stream...", streamPtr->curOffset);
streamPtr->paused = 1;
return 0;
}
_streamerBailFlag = 0;
_mutex->lock();
actualAmount = _filesHandler->read(streamPtr->soundId, &streamPtr->buf[streamPtr->loadIndex], requestedAmount, streamPtr->bufId);
_mutex->unlock();
// FT has no bailFlag
if (!_isEarlyDiMUSE && _streamerBailFlag)
return 0;
loadSize -= actualAmount;
streamPtr->curOffset += actualAmount;
_lastStreamLoaded = streamPtr;
int32 newLoadIndex = actualAmount + streamPtr->loadIndex;
streamPtr->loadIndex = newLoadIndex;
if (newLoadIndex >= streamPtr->bufFreeSize) {
streamPtr->loadIndex = newLoadIndex - streamPtr->bufFreeSize;
}
if (_isEarlyDiMUSE && streamPtr->vocLoopFlag) {
if (streamPtr->vocLoopTriggerOffset <= streamPtr->curOffset) {
dispatchVOCLoopCallback(streamPtr->soundId);
streamPtr->vocLoopFlag = 0;
}
}
if (actualAmount != requestedAmount)
break;
// FT checks for negative or zero loadSize here
if (_isEarlyDiMUSE && loadSize <= 0)
return 0;
}
debug(5, "IMuseDigital::streamerFetchData(): ERROR: unable to load the correct amount of data (req=%d, act=%d)", requestedAmount, actualAmount);
_lastStreamLoaded = nullptr;
return 0;
}
void IMuseDigital::streamerSetLoopFlag(IMuseDigiStream *streamPtr, int offset) {
streamPtr->vocLoopFlag = 1;
streamPtr->vocLoopTriggerOffset = offset;
}
void IMuseDigital::streamerRemoveLoopFlag(IMuseDigiStream *streamPtr) {
streamPtr->vocLoopFlag = 0;
}
} // End of namespace Scumm

View File

@@ -0,0 +1,866 @@
/* 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 "scumm/imuse_digi/dimuse_tables.h"
namespace Scumm {
const imuseRoomMap _digStateMusicMap[] = {
{0, 0, 0, 0, 0, 0 },
{1, 0, 0, 0, 0, 0 },
{2, 2, 0, 0, 0, 0 },
{4, 3, 0, 0, 0, 0 },
{5, 3, 0, 0, 0, 0 },
{6, 3, 0, 0, 0, 0 },
{7, 3, 0, 0, 0, 0 },
{8, 4, 0, 0, 0, 0 },
{9, 5, 0, 0, 0, 0 },
{10, 4, 0, 0, 0, 0 },
{12, 5, 0, 0, 0, 0 },
{14, 5, 0, 0, 0, 0 },
{15, 6, 29, 7, 0, 0 },
{16, 8, 0, 0, 0, 0 },
{17, 1, 0, 0, 0, 0 },
{18, 9, 0, 0, 0, 0 },
{19, 9, 0, 0, 0, 0 },
{20, 6, 0, 0, 0, 0 },
{21, 6, 0, 0, 0, 0 },
{22, 44, 0, 0, 0, 0 },
{23, 10, 7, 0, 0, 0 },
{24, 26, 0, 0, 0, 0 },
{25, 17, 0, 0, 0, 0 },
{26, 17, 0, 0, 0, 0 },
{27, 18, 0, 0, 0, 0 },
{28, 1, 0, 0, 0, 0 },
{29, 20, 0, 0, 0, 0 },
{30, 22, 0, 0, 0, 0 },
{31, 23, 0, 0, 0, 0 },
{32, 22, 0, 0, 0, 0 },
{33, 26, 0, 0, 0, 0 },
{34, 24, 0, 0, 0, 0 },
{35, 1, 0, 0, 0, 0 },
{36, 1, 0, 0, 0, 0 },
{37, 42, 0, 0, 0, 0 },
{38, 43, 0, 0, 0, 0 },
{39, 44, 0, 0, 0, 0 },
{40, 1, 0, 0, 0, 0 },
{41, 43, 0, 0, 0, 0 },
{42, 44, 0, 0, 0, 0 },
{43, 43, 0, 0, 0, 0 },
{44, 45, 117,45, 114,46},
{47, 1, 0, 0, 0, 0 },
{48, 43, 0, 0, 0, 0 },
{49, 44, 0, 0, 0, 0 },
{51, 1, 0, 0, 0, 0 },
{53, 28, 0, 0, 0, 0 },
{54, 28, 0, 0, 0, 0 },
{55, 29, 0, 0, 0, 0 },
{56, 29, 0, 0, 0, 0 },
{57, 29, 0, 0, 0, 0 },
{58, 31, 0, 0, 0, 0 },
{59, 1, 0, 0, 0, 0 },
{60, 37, 0, 0, 0, 0 },
{61, 39, 0, 0, 0, 0 },
{62, 38, 0, 0, 0, 0 },
{63, 39, 0, 0, 0, 0 },
{64, 39, 0, 0, 0, 0 },
{65, 40, 0, 0, 0, 0 },
{67, 40, 0, 0, 0, 0 },
{68, 39, 0, 0, 0, 0 },
{69, 1, 0, 0, 0, 0 },
{70, 49, 0, 0, 0, 0 },
{73, 50, 0, 0, 0, 0 },
{75, 51, 0, 0, 0, 0 },
{76, 1, 0, 0, 0, 0 },
{77, 52, 7, 0, 0, 0 },
{78, 63, 0, 0, 0, 0 },
{79, 1, 0, 0, 0, 0 },
{82, 21, 0, 0, 0, 0 },
{85, 1, 0, 0, 0, 0 },
{86, 0, 0, 0, 0, 0 },
{89, 33, 6, 35, 5, 34},
{90, 16, 0, 0, 0, 0 },
{91, 57, 0, 0, 0, 0 },
{88, 32, 0, 0, 0, 0 },
{92, 25, 0, 0, 0, 0 },
{93, 0, 0, 0, 0, 0 },
{95, 19, 0, 0, 0, 0 },
{80, 41, 0, 0, 0, 0 },
{81, 48, 0, 0, 0, 0 },
{83, 27, 0, 0, 0, 0 },
{94, 36, 0, 0, 0, 0 },
{40, 1, 0, 0, 0, 0 },
{96, 13, 0, 0, 0, 0 },
{97, 14, 0, 0, 0, 0 },
{98, 11, 0, 0, 0, 0 },
{99, 15, 0, 0, 0, 0 },
{100, 17, 0, 0, 0, 0 },
{101, 38, 0, 0, 0, 0 },
{103, 0, 0, 0, 0, 0 },
{104, 0, 0, 0, 0, 0 },
{11, 44, 0, 0, 0, 0 },
{3, 47, 0, 0, 0, 0 },
{105, 30, 128,29, 0, 0 },
{106, 0, 0, 0, 0, 0 },
{107, 1, 0, 0, 0, 0 },
{108, 1, 0, 0, 0, 0 },
{47, 1, 0, 0, 0, 0 },
{50, 1, 0, 0, 0, 0 },
{52, 0, 0, 0, 0, 0 },
{71, 1, 0, 0, 0, 0 },
{13, 1, 0, 0, 0, 0 },
{72, 1, 0, 0, 0, 0 },
{46, 33, 6, 35, 5, 34},
{74, 1, 0, 0, 0, 0 },
{84, 1, 0, 0, 0, 0 },
{66, 1, 0, 0, 0, 0 },
{102, 1, 0, 0, 0, 0 },
{109, 1, 0, 0, 0, 0 },
{110, 2, 0, 0, 0, 0 },
{45, 1, 0, 0, 0, 0 },
{87, 1, 0, 0, 0, 0 },
{111, 1, 0, 0, 0, 0 },
{-1, 1, 0, 0, 0, 0 }
};
const imuseDigTable _digStateMusicTable[] = {
{0, 1000, "STATE_NULL", 0, 0, ""}, /* 00 */
{0, 1001, "stateNoChange", 0, 0, ""}, /* 01 */
{3, 1100, "stateAstShip", 2, 0, "ASTERO~1.IMU"}, /* 02 */
{3, 1120, "stateAstClose", 2, 0, "ASTERO~2.IMU"}, /* 03 */
{3, 1140, "stateAstInside", 0, 0, "ASTERO~3.IMU"}, /* 04 */
{3, 1150, "stateAstCore", 0, 2, "ASTERO~4.IMU"}, /* 05 */
{3, 1200, "stateCanyonClose", 0, 1, "CANYON~1.IMU"}, /* 06 */
{3, 1205, "stateCanyonClose_m", 0, 0, "CANYON~2.IMU"}, /* 07 */
{3, 1210, "stateCanyonOver", 0, 1, "CANYON~3.IMU"}, /* 08 */
{3, 1220, "stateCanyonWreck", 0, 1, "CANYON~4.IMU"}, /* 09 */
{3, 1300, "stateNexusCanyon", 10, 0, "NEXUS(~1.IMU"}, /* 10 */
{3, 1310, "stateNexusPlan", 10, 0, "NEXUS(~1.IMU"}, /* 11 */
{3, 1320, "stateNexusRamp", 10, 0, "NEXUS(~2.IMU"}, /* 12 */
{3, 1330, "stateNexusMuseum", 10, 0, "NEXUS(~3.IMU"}, /* 13 */
{3, 1340, "stateNexusMap", 10, 0, "NEXUS(~4.IMU"}, /* 14 */
{3, 1350, "stateNexusTomb", 10, 0, "NE3706~5.IMU"}, /* 15 */
{3, 1360, "stateNexusCath", 10, 0, "NE3305~5.IMU"}, /* 16 */
{3, 1370, "stateNexusAirlock", 0, 0, "NE2D3A~5.IMU"}, /* 17 */
{3, 1380, "stateNexusPowerOff", 0, 1, "NE8522~5.IMU"}, /* 18 */
{3, 1400, "stateMuseumTramNear", 0, 1, "TRAM(M~1.IMU"}, /* 19 */
{3, 1410, "stateMuseumTramFar", 0, 0, "TRAM(M~2.IMU"}, /* 20 */
{3, 1420, "stateMuseumLockup", 0, 0, "MUSEUM~1.IMU"}, /* 21 */
{3, 1433, "stateMuseumPool", 22, 1, "MUSEUM~2.IMU"}, /* 22 */
{3, 1436, "stateMuseumSpire", 22, 2, "MUSEUM~3.IMU"}, /* 23 */
{3, 1440, "stateMuseumMuseum", 22, 2, "MUSEUM~4.IMU"}, /* 24 */
{3, 1450, "stateMuseumLibrary", 0, 0, "MUB575~5.IMU"}, /* 25 */
{3, 1460, "stateMuseumCavern", 0, 0, "MUF9BE~5.IMU"}, /* 26 */
{3, 1500, "stateTombTramNear", 0, 1, "TRAM(T~1.IMU"}, /* 27 */
{3, 1510, "stateTombBase", 28, 2, "TOMB(A~1.IMU"}, /* 28 */
{3, 1520, "stateTombSpire", 28, 2, "TOMB(A~2.IMU"}, /* 29 */
{3, 1530, "stateTombCave", 28, 2, "TOMB(A~3.IMU"}, /* 30 */
{3, 1540, "stateTombCrypt", 31, 1, "TOMB(C~1.IMU"}, /* 31 */
{3, 1550, "stateTombGuards", 31, 1, "TOMB(C~2.IMU"}, /* 32 */
{3, 1560, "stateTombInner", 0, 1, "TOMB(I~1.IMU"}, /* 33 */
{3, 1570, "stateTombCreator1", 0, 0, "TOMB(C~3.IMU"}, /* 34 */
{3, 1580, "stateTombCreator2", 0, 0, "TOMB(C~4.IMU"}, /* 35 */
{3, 1600, "statePlanTramNear", 0, 1, "TRAM(P~1.IMU"}, /* 36 */
{3, 1610, "statePlanTramFar", 0, 0, "TRAM(P~2.IMU"}, /* 37 */
{3, 1620, "statePlanBase", 38, 2, "PLAN(A~1.IMU"}, /* 38 */
{3, 1630, "statePlanSpire", 38, 2, "PLAN(A~2.IMU"}, /* 39 */
{3, 1650, "statePlanDome", 0, 0, "PLAN(D~1.IMU"}, /* 40 */
{3, 1700, "stateMapTramNear", 0, 1, "TRAM(M~3.IMU"}, /* 41 */
{3, 1710, "stateMapTramFar", 0, 0, "TRAM(M~4.IMU"}, /* 42 */
{3, 1720, "stateMapCanyon", 43, 2, "MAP(AM~1.IMU"}, /* 43 */
{3, 1730, "stateMapExposed", 43, 2, "MAP(AM~2.IMU"}, /* 44 */
{3, 1750, "stateMapNestEmpty", 43, 2, "MAP(AM~4.IMU"}, /* 45 */
{3, 1760, "stateMapNestMonster", 0, 0, "MAP(MO~1.IMU"}, /* 46 */
{3, 1770, "stateMapKlein", 0, 0, "MAP(KL~1.IMU"}, /* 47 */
{3, 1800, "stateCathTramNear", 0, 1, "TRAM(C~1.IMU"}, /* 48 */
{3, 1810, "stateCathTramFar", 0, 0, "TRAM(C~2.IMU"}, /* 49 */
{3, 1820, "stateCathLab", 50, 1, "CATH(A~1.IMU"}, /* 50 */
{3, 1830, "stateCathOutside", 50, 1, "CATH(A~2.IMU"}, /* 51 */
{3, 1900, "stateWorldMuseum", 52, 0, "WORLD(~1.IMU"}, /* 52 */
{3, 1901, "stateWorldPlan", 52, 0, "WORLD(~2.IMU"}, /* 53 */
{3, 1902, "stateWorldTomb", 52, 0, "WORLD(~3.IMU"}, /* 54 */
{3, 1903, "stateWorldMap", 52, 0, "WORLD(~4.IMU"}, /* 55 */
{3, 1904, "stateWorldCath", 52, 0, "WO3227~5.IMU"}, /* 56 */
{3, 1910, "stateEye1", 0, 0, "EYE1~1.IMU"}, /* 57 */
{3, 1911, "stateEye2", 0, 0, "EYE2~1.IMU"}, /* 58 */
{3, 1912, "stateEye3", 0, 0, "EYE3~1.IMU"}, /* 59 */
{3, 1913, "stateEye4", 0, 0, "EYE4~1.IMU"}, /* 60 */
{3, 1914, "stateEye5", 0, 0, "EYE5~1.IMU"}, /* 61 */
{3, 1915, "stateEye6", 0, 0, "EYE6~1.IMU"}, /* 62 */
{3, 1916, "stateEye7", 0, 0, "EYE7~1.IMU"}, /* 63 */
{0, -1, "", 0, 0, ""}
};
const imuseDigTable _digSeqMusicTable[] = {
{0, 2000, "SEQ_NULL", 0, 0, ""},
{0, 2005, "seqLogo", 0, 0, ""},
{0, 2010, "seqIntro", 0, 0, ""},
{6, 2020, "seqExplosion1b", 0, 0, ""},
{3, 2030, "seqAstTunnel1a", 0, 0, "SEQ(AS~1.IMU"},
{6, 2031, "seqAstTunnel2b", 0, 0, ""},
{4, 2032, "seqAstTunnel3a", 0, 0, "SEQ(AS~2.IMU"},
{5, 2040, "seqToPlanet1b", 0, 0, ""},
{4, 2045, "seqArgBegin", 0, 0, "SEQ(AR~1.IMU"},
{4, 2046, "seqArgEnd", 0, 0, "SEQ(AR~2.IMU"},
{4, 2050, "seqWreckGhost", 0, 0, "SEQ(GH~1.IMU"},
{4, 2060, "seqCanyonGhost", 0, 0, "SEQ(GH~2.IMU"},
{0, 2070, "seqBrinkFall", 0, 0, ""},
{4, 2080, "seqPanUpCanyon", 0, 0, "SEQ(PA~1.IMU"},
{6, 2091, "seqAirlockTunnel1b", 0, 0, ""},
{6, 2100, "seqTramToMu", 0, 0, ""},
{6, 2101, "seqTramFromMu", 0, 0, ""},
{6, 2102, "seqTramToTomb", 0, 0, ""},
{6, 2103, "seqTramFromTomb", 0, 0, ""},
{6, 2104, "seqTramToPlan", 0, 0, ""},
{6, 2105, "seqTramFromPlan", 0, 0, ""},
{6, 2106, "seqTramToMap", 0, 0, ""},
{6, 2107, "seqTramFromMap", 0, 0, ""},
{6, 2108, "seqTramToCath", 0, 0, ""},
{6, 2109, "seqTramFromCath", 0, 0, ""},
{0, 2110, "seqMuseumGhost", 0, 0, ""},
{0, 2120, "seqSerpentAppears", 0, 0, ""},
{0, 2130, "seqSerpentEats", 0, 0, ""},
{6, 2140, "seqBrinkRes1b", 0, 0, ""},
{4, 2141, "seqBrinkRes2a", 0, 0, "SEQ(BR~1.IMU"},
{3, 2150, "seqLockupEntry", 0, 0, "SEQ(BR~1.IMU"},
{0, 2160, "seqSerpentExplodes", 0, 0, ""},
{4, 2170, "seqSwimUnderwater", 0, 0, "SEQ(DE~1.IMU"},
{4, 2175, "seqWavesPlunge", 0, 0, "SEQ(PL~1.IMU"},
{0, 2180, "seqCryptOpens", 0, 0, ""},
{0, 2190, "seqGuardsFight", 0, 0, ""},
{3, 2200, "seqCreatorRes1.1a", 0, 0, "SEQ(CR~1.IMU"},
{6, 2201, "seqCreatorRes1.2b", 0, 0, ""},
{6, 2210, "seqMaggieCapture1b", 0, 0, ""},
{3, 2220, "seqStealCrystals", 0, 0, "SEQ(BR~1.IMU"},
{0, 2230, "seqGetByMonster", 0, 0, ""},
{6, 2240, "seqKillMonster1b", 0, 0, ""},
{3, 2250, "seqCreatorRes2.1a", 0, 0, "SEQ(CR~2.IMU"},
{6, 2251, "seqCreatorRes2.2b", 0, 0, ""},
{4, 2252, "seqCreatorRes2.3a", 0, 0, "SEQ(CR~3.IMU"},
{0, 2260, "seqMaggieInsists", 0, 0, ""},
{0, 2270, "seqBrinkHelpCall", 0, 0, ""},
{3, 2280, "seqBrinkCrevice1a", 0, 0, "SEQ(BR~2.IMU"},
{3, 2281, "seqBrinkCrevice2a", 0, 0, "SEQ(BR~3.IMU"},
{6, 2290, "seqCathAccess1b", 0, 0, ""},
{4, 2291, "seqCathAccess2a", 0, 0, "SEQ(CA~1.IMU"},
{3, 2300, "seqBrinkAtGenerator", 0, 0, "SEQ(BR~1.IMU"},
{6, 2320, "seqFightBrink1b", 0, 0, ""},
{6, 2340, "seqMaggieDies1b", 0, 0, ""},
{6, 2346, "seqMaggieRes1b", 0, 0, ""},
{4, 2347, "seqMaggieRes2a", 0, 0, "SEQ(MA~1.IMU"},
{0, 2350, "seqCreatureFalls", 0, 0, ""},
{5, 2360, "seqFinale1b", 0, 0, ""},
{3, 2370, "seqFinale2a", 0, 0, "SEQ(FI~1.IMU"},
{6, 2380, "seqFinale3b1", 0, 0, ""},
{6, 2390, "seqFinale3b2", 0, 0, ""},
{3, 2400, "seqFinale4a", 0, 0, "SEQ(FI~2.IMU"},
{3, 2410, "seqFinale5a", 0, 0, "SEQ(FI~3.IMU"},
{3, 2420, "seqFinale6a", 0, 0, "SEQ(FI~4.IMU"},
{3, 2430, "seqFinale7a", 0, 0, "SE3D2B~5.IMU"},
{6, 2440, "seqFinale8b", 0, 0, ""},
{4, 2450, "seqFinale9a", 0, 0, "SE313B~5.IMU"},
{0, -1, "", 0, 0, ""}
};
const imuseComiTable _comiStateMusicTable[] = {
{0, 1000, "STATE_NULL", 0, 0, 0, "" }, /* 00 */
{0, 1001, "stateNoChange", 0, 0, 0, "" }, /* 01 */
{3, 1098, "stateCredits1", 0, 0, 60, "1098-C~1.IMX"}, /* 02 */
{3, 1099, "stateMenu", 0, 0, 60, "1099-M~1.IMX"}, /* 03 */
{3, 1100, "stateHold1", 4, 0, 60, "1100-H~1.IMX"}, /* 04 */
{3, 1101, "stateWaterline1", 4, 0, 60, "1101-W~1.IMX"}, /* 05 */
{3, 1102, "stateHold2", 6, 1, 60, "1102-H~1.IMX"}, /* 06 */
{3, 1103, "stateWaterline2", 6, 0, 60, "1103-W~1.IMX"}, /* 07 */
{3, 1104, "stateCannon", 0, 0, 60, "1104-C~1.IMX"}, /* 08 */
{3, 1105, "stateTreasure", 0, 0, 60, "1105-T~1.IMX"}, /* 09 */
{3, 1200, "stateFortBase", 10, 1, 60, "1200-F~1.IMX"}, /* 10 */
{3, 1201, "statePreFort", 10, 1, 60, "1201-P~1.IMX"}, /* 11 */
{3, 1202, "statePreVooOut", 12, 0, 60, "1202-P~1.IMX"}, /* 12 */
{3, 1203, "statePreVooIn", 12, 0, 60, "1203-P~1.IMX"}, /* 13 */
{3, 1204, "statePreVooLady", 12, 0, 60, "1204-P~1.IMX"}, /* 14 */
{3, 1205, "stateVoodooOut", 0, 0, 60, "1205-V~1.IMX"}, /* 15 */
{3, 1210, "stateVoodooIn", 0, 0, 60, "1210-V~1.IMX"}, /* 16 */
{12,1212, "stateVoodooInAlt", 0, 1, 42, "1210-V~1.IMX"}, /* 17 */
{3, 1215, "stateVoodooLady", 0, 0, 60, "1215-V~1.IMX"}, /* 18 */
{3, 1219, "statePrePlundermap", 0, 0, 60, "1219-P~1.IMX"}, /* 19 */
{3, 1220, "statePlundermap", 0, 0, 60, "1220-P~1.IMX"}, /* 20 */
{3, 1222, "statePreCabana", 0, 0, 60, "1222-P~1.IMX"}, /* 21 */
{3, 1223, "stateCabana", 0, 0, 60, "1223-C~1.IMX"}, /* 22 */
{3, 1224, "statePostCabana", 23, 0, 60, "1224-P~1.IMX"}, /* 23 */
{3, 1225, "stateBeachClub", 23, 0, 60, "1225-B~1.IMX"}, /* 24 */
{3, 1230, "stateCliff", 0, 0, 60, "1230-C~1.IMX"}, /* 25 */
{3, 1232, "stateBelly", 0, 0, 48, "1232-B~1.IMX"}, /* 26 */
{3, 1235, "stateQuicksand", 0, 0, 60, "1235-Q~1.IMX"}, /* 27 */
{3, 1240, "stateDangerBeach", 0, 0, 48, "1240-D~1.IMX"}, /* 28 */
{12,1241, "stateDangerBeachAlt",0, 2, 48, "1240-D~1.IMX"}, /* 29 */
{3, 1245, "stateRowBoat", 0, 0, 60, "1245-R~1.IMX"}, /* 30 */
{3, 1247, "stateAlongside", 0, 0, 48, "1247-A~1.IMX"}, /* 31 */
{12,1248, "stateAlongsideAlt", 0, 1, 48, "1247-A~1.IMX"}, /* 32 */
{3, 1250, "stateChimpBoat", 0, 0, 30, "1250-C~1.IMX"}, /* 33 */
{3, 1255, "stateMrFossey", 0, 0, 48, "1255-M~1.IMX"}, /* 34 */
{3, 1259, "statePreTown", 0, 0, 60, "1259-P~1.IMX"}, /* 35 */
{3, 1260, "stateTown", 0, 0, 60, "1260-T~1.IMX"}, /* 36 */
{3, 1264, "statePreMeadow", 0, 0, 60, "1264-P~1.IMX"}, /* 37 */
{3, 1265, "stateMeadow", 0, 0, 60, "1265-M~1.IMX"}, /* 38 */
{3, 1266, "stateMeadowAmb", 0, 0, 60, "1266-M~1.IMX"}, /* 39 */
{3, 1270, "stateWardrobePre", 40, 0, 60, "1270-W~1.IMX"}, /* 40 */
{3, 1272, "statePreShow", 40, 0, 60, "1272-P~1.IMX"}, /* 41 */
{3, 1274, "stateWardrobeShow", 42, 0, 60, "1274-W~1.IMX"}, /* 42 */
{3, 1276, "stateShow", 42, 0, 60, "1276-S~1.IMX"}, /* 43 */
{3, 1277, "stateWardrobeJug", 44, 0, 60, "1277-W~1.IMX"}, /* 44 */
{3, 1278, "stateJuggling", 44, 0, 60, "1278-J~1.IMX"}, /* 45 */
{3, 1279, "statePostShow", 0, 0, 60, "1279-P~1.IMX"}, /* 46 */
{3, 1280, "stateChickenShop", 0, 0, 60, "1280-C~1.IMX"}, /* 47 */
{3, 1285, "stateBarberShop", 48, 0, 60, "1285-B~1.IMX"}, /* 48 */
{3, 1286, "stateVanHelgen", 48, 0, 60, "1286-V~1.IMX"}, /* 49 */
{3, 1287, "stateBill", 48, 0, 60, "1287-B~1.IMX"}, /* 50 */
{3, 1288, "stateHaggis", 48, 0, 60, "1288-H~1.IMX"}, /* 51 */
{3, 1289, "stateRottingham", 48, 0, 60, "1289-R~1.IMX"}, /* 52 */
{3, 1305, "stateDeck", 0, 0, 60, "1305-D~1.IMX"}, /* 53 */
{3, 1310, "stateCombatMap", 0, 0, 60, "1310-C~1.IMX"}, /* 54 */
{3, 1320, "stateShipCombat", 0, 0, 60, "1320-S~1.IMX"}, /* 55 */
{3, 1325, "stateSwordfight", 0, 0, 60, "1325-S~1.IMX"}, /* 56 */
{3, 1327, "stateSwordRott", 0, 0, 60, "1327-S~1.IMX"}, /* 57 */
{3, 1330, "stateTownEdge", 0, 0, 60, "1330-T~1.IMX"}, /* 58 */
{3, 1335, "stateSwordLose", 0, 0, 60, "1335-S~1.IMX"}, /* 59 */
{3, 1340, "stateSwordWin", 0, 0, 60, "1340-S~1.IMX"}, /* 60 */
{3, 1345, "stateGetMap", 0, 0, 60, "1345-G~1.IMX"}, /* 61 */
{3, 1400, "stateWreckBeach", 0, 0, 60, "1400-W~1.IMX"}, /* 62 */
{3, 1405, "stateBloodMap", 63, 0, 60, "1405-B~1.IMX"}, /* 63 */
{3, 1410, "stateClearing", 0, 0, 60, "1410-C~1.IMX"}, /* 64 */
{3, 1415, "stateLighthouse", 63, 0, 60, "1415-L~1.IMX"}, /* 65 */
{3, 1420, "stateVillage", 66, 0, 60, "1420-V~1.IMX"}, /* 66 */
{3, 1423, "stateVolcano", 66, 0, 60, "1423-V~1.IMX"}, /* 67 */
{3, 1425, "stateAltar", 66, 0, 60, "1425-A~1.IMX"}, /* 68 */
{3, 1430, "stateHotelOut", 0, 0, 60, "1430-H~1.IMX"}, /* 69 */
{3, 1435, "stateHotelBar", 70, 0, 60, "1435-H~1.IMX"}, /* 70 */
{3, 1440, "stateHotelIn", 70, 0, 60, "1440-H~1.IMX"}, /* 71 */
{3, 1445, "stateTarotLady", 70, 0, 60, "1445-T~1.IMX"}, /* 72 */
{3, 1447, "stateGoodsoup", 70, 0, 60, "1447-G~1.IMX"}, /* 73 */
{3, 1448, "stateGuestRoom", 0, 0, 60, "1448-G~1.IMX"}, /* 74 */
{3, 1450, "stateWindmill", 63, 0, 60, "1450-W~1.IMX"}, /* 75 */
{3, 1455, "stateCemetary", 0, 0, 60, "1455-C~1.IMX"}, /* 76 */
{3, 1460, "stateCrypt", 77, 0, 60, "1460-C~1.IMX"}, /* 77 */
{3, 1463, "stateGraveDigger", 77, 0, 60, "1463-G~1.IMX"}, /* 78 */
{3, 1465, "stateMonkey1", 0, 0, 60, "1465-M~1.IMX"}, /* 79 */
{3, 1475, "stateStanDark", 0, 0, 60, "1475-S~1.IMX"}, /* 80 */
{3, 1477, "stateStanLight", 0, 0, 60, "1477-S~1.IMX"}, /* 81 */
{3, 1480, "stateEggBeach", 63, 0, 60, "1480-E~1.IMX"}, /* 82 */
{3, 1485, "stateSkullIsland", 0, 0, 60, "1485-S~1.IMX"}, /* 83 */
{3, 1490, "stateSmugglersCave", 0, 0, 60, "1490-S~1.IMX"}, /* 84 */
{3, 1500, "stateLeChuckTalk", 0, 0, 60, "1500-L~1.IMX"}, /* 85 */
{3, 1505, "stateCarnival", 0, 0, 60, "1505-C~1.IMX"}, /* 86 */
{3, 1511, "stateHang", 87, 0, 60, "1511-H~1.IMX"}, /* 87 */
{3, 1512, "stateRum", 87, 0, 60, "1512-RUM.IMX"}, /* 88 */
{3, 1513, "stateTorture", 87, 0, 60, "1513-T~1.IMX"}, /* 89 */
{3, 1514, "stateSnow", 87, 0, 60, "1514-S~1.IMX"}, /* 90 */
{3, 1515, "stateCredits", 0, 0, 60, "1515-C~1.IMX"}, /* 91 */
{3, 1520, "stateCarnAmb", 0, 0, 60, "1520-C~1.IMX"}, /* 92 */
{0, -1, "", 0, 0, 0, "", }
};
const imuseComiTable _comiSeqMusicTable[] = {
{0, 2000, "SEQ_NULL", 0, 0, 0, "" },
{0, 2100, "seqINTRO", 0, 0, 0, "" },
{3, 2105, "seqInterlude1", 0, 0, 60, "2105-I~1.IMX"},
{8, 2110, "seqLastBoat", 0, 1, 0, "" },
{0, 2115, "seqSINK_SHIP", 0, 0, 0, "" },
{0, 2120, "seqCURSED_RING", 0, 0, 60, "" },
{3, 2200, "seqInterlude2", 0, 0, 60, "2200-I~1.IMX"},
{3, 2210, "seqKidnapped", 0, 0, 60, "2210-K~1.IMX"},
{8, 2220, "seqSnakeVomits", 0, 1, 0, "" },
{8, 2222, "seqPopBalloon", 0, 1, 0, "" },
{3, 2225, "seqDropBalls", 0, 0, 60, "2225-D~1.IMX"},
{4, 2232, "seqArriveBarber", 0, 0, 60, "2232-A~1.IMX"},
{3, 2233, "seqAtonal", 0, 0, 60, "2233-A~1.IMX"},
{3, 2235, "seqShaveHead1", 0, 0, 60, "2235-S~1.IMX"},
{2, 2236, "seqShaveHead2", 0, 2, 60, "2235-S~1.IMX"},
{3, 2245, "seqCaberLose", 0, 0, 60, "2245-C~1.IMX"},
{3, 2250, "seqCaberWin", 0, 0, 60, "2250-C~1.IMX"},
{3, 2255, "seqDuel1", 0, 0, 60, "2255-D~1.IMX"},
{2, 2256, "seqDuel2", 0, 2, 60, "2255-D~1.IMX"},
{2, 2257, "seqDuel3", 0, 3, 60, "2255-D~1.IMX"},
{3, 2260, "seqBlowUpTree1", 0, 0, 60, "2260-B~1.IMX"},
{2, 2261, "seqBlowUpTree2", 0, 2, 60, "2260-B~1.IMX"},
{3, 2275, "seqMonkeys", 0, 0, 60, "2275-M~1.IMX"},
{9, 2277, "seqAttack", 0, 1, 0, "" },
{3, 2285, "seqSharks", 0, 0, 60, "2285-S~1.IMX"},
{3, 2287, "seqTowelWalk", 0, 0, 60, "2287-T~1.IMX"},
{0, 2293, "seqNICE_BOOTS", 0, 0, 0, "" },
{0, 2295, "seqBIG_BONED", 0, 0, 0, "" },
{3, 2300, "seqToBlood", 0, 0, 60, "2300-T~1.IMX"},
{3, 2301, "seqInterlude3", 0, 0, 60, "2301-I~1.IMX"},
{3, 2302, "seqRott1", 0, 0, 60, "2302-R~1.IMX"},
{2, 2304, "seqRott2", 0, 2, 60, "2302-R~1.IMX"},
{2, 2305, "seqRott2b", 0,21, 60, "2302-R~1.IMX"},
{2, 2306, "seqRott3", 0, 3, 60, "2302-R~1.IMX"},
{2, 2308, "seqRott4", 0, 4, 60, "2302-R~1.IMX"},
{2, 2309, "seqRott5", 0, 5, 60, "2302-R~1.IMX"},
{3, 2311, "seqVerse1", 0, 0, 60, "2311-S~1.IMX"},
{2, 2312, "seqVerse2", 0, 2, 60, "2311-S~1.IMX"},
{2, 2313, "seqVerse3", 0, 3, 60, "2311-S~1.IMX"},
{2, 2314, "seqVerse4", 0, 4, 60, "2311-S~1.IMX"},
{2, 2315, "seqVerse5", 0, 5, 60, "2311-S~1.IMX"},
{2, 2316, "seqVerse6", 0, 6, 60, "2311-S~1.IMX"},
{2, 2317, "seqVerse7", 0, 7, 60, "2311-S~1.IMX"},
{2, 2318, "seqVerse8", 0, 8, 60, "2311-S~1.IMX"},
{2, 2319, "seqSongEnd", 0, 9, 60, "2311-S~1.IMX"},
{2, 2336, "seqRiposteLose", 0, 0, 60, "2336-R~1.IMX"},
{2, 2337, "seqRiposteWin", 0, 0, 60, "2337-R~1.IMX"},
{2, 2338, "seqInsultLose", 0, 0, 60, "2338-I~1.IMX"},
{2, 2339, "seqInsultWin", 0, 0, 60, "2339-I~1.IMX"},
{3, 2340, "seqSwordLose", 0, 0, 60, "1335-S~1.IMX"},
{3, 2345, "seqSwordWin", 0, 0, 60, "1340-S~1.IMX"},
{3, 2347, "seqGetMap", 0, 0, 60, "1345-G~1.IMX"},
{3, 2400, "seqInterlude4", 0, 0, 60, "2400-I~1.IMX"},
{0, 2405, "seqSHIPWRECK", 0, 0, 0, "" },
{3, 2408, "seqFakeCredits", 0, 0, 60, "2408-F~1.IMX"},
{3, 2410, "seqPassOut", 0, 0, 60, "2410-P~1.IMX"},
{3, 2414, "seqGhostTalk", 0, 0, 60, "2414-G~1.IMX"},
{2, 2415, "seqGhostWedding", 0, 1, 60, "2414-G~1.IMX"},
{3, 2420, "seqEruption", 0, 0, 60, "2420-E~1.IMX"},
{3, 2425, "seqSacrifice", 0, 0, 60, "2425-S~1.IMX"},
{2, 2426, "seqSacrificeEnd", 0, 1, 60, "2425-S~1.IMX"},
{3, 2430, "seqScareDigger", 0, 0, 60, "2430-S~1.IMX"},
{3, 2445, "seqSkullArrive", 0, 0, 60, "2445-S~1.IMX"},
{3, 2450, "seqFloat", 0, 0, 60, "2450-C~1.IMX"},
{2, 2451, "seqFall", 0, 1, 60, "2450-C~1.IMX"},
{2, 2452, "seqUmbrella", 0, 2, 60, "2450-C~1.IMX"},
{3, 2460, "seqFight", 0, 0, 60, "2460-F~1.IMX"},
{0, 2465, "seqLAVE_RIDE", 0, 0, 0, "" },
{0, 2470, "seqMORE_SLAW", 0, 0, 0, "" },
{0, 2475, "seqLIFT_CURSE", 0, 0, 0, "" },
{3, 2500, "seqInterlude5", 0, 0, 60, "2500-I~1.IMX"},
{3, 2502, "seqExitSkycar", 0, 0, 60, "2502-E~1.IMX"},
{3, 2504, "seqGrow1", 0, 0, 60, "2504-G~1.IMX"},
{2, 2505, "seqGrow2", 0, 1, 60, "2504-G~1.IMX"},
{3, 2508, "seqInterlude6", 0, 0, 60, "2508-I~1.IMX"},
{0, 2515, "seqFINALE", 0, 0, 0, "" },
{3, 2520, "seqOut", 0, 0, 60, "2520-OUT.IMX"},
{3, 2530, "seqZap1a", 0, 0, 60, "2530-Z~1.IMX"},
{2, 2531, "seqZap1b", 0, 1, 60, "2530-Z~1.IMX"},
{2, 2532, "seqZap1c", 0, 2, 60, "2530-Z~1.IMX"},
{2, 2540, "seqZap2a", 0, 0, 60, "2540-Z~1.IMX"},
{2, 2541, "seqZap2b", 0, 1, 60, "2540-Z~1.IMX"},
{2, 2542, "seqZap2c", 0, 2, 60, "2540-Z~1.IMX"},
{3, 2550, "seqZap3a", 0, 0, 60, "2550-Z~1.IMX"},
{2, 2551, "seqZap3b", 0, 1, 60, "2550-Z~1.IMX"},
{2, 2552, "seqZap3c", 0, 2, 60, "2550-Z~1.IMX"},
{3, 2560, "seqZap4a", 0, 0, 60, "2560-Z~1.IMX"},
{2, 2561, "seqZap4b", 0, 1, 60, "2560-Z~1.IMX"},
{2, 2562, "seqZap4c", 0, 2, 60, "2560-Z~1.IMX"},
{0, -1, "", 0, 0, 0, "" }
};
const imuseComiTable _comiDemoStateMusicTable[] = {
{0, 1000, "STATE_NULL", 0, 0, 0, "", }, /* 00 */
{0, 1001, "stateNoChange", 0, 0, 0, "", }, /* 01 */
{3, 1100, "stateHold1", 2, 0, 60, "in1.imx" }, /* 02 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 03 */
{3, 1120, "stateHold2", 4, 0, 60, "in2.imx" }, /* 04 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 05 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 06 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 07 */
{3, 1140, "stateWaterline1", 2, 0, 60, "out1.imx"}, /* 08 */
{3, 1150, "stateWaterline2", 4, 0, 60, "out2.imx"}, /* 09 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 10 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 11 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 12 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 13 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 14 */
{0, 1001, "empty", 0, 0, 0, "", }, /* 15 */
{3, 1210, "stateCannon", 4, 0, 60, "gun.imx" }, /* 16 */
{0, -1, "", 0, 0, 0, "", }
};
const imuseFtStateTable _ftStateMusicTable[] = {
{"", 0, 0, "STATE_NULL" },
{"", 4, 127, "stateKstandOutside" },
{"kinside", 2, 127, "stateKstandInside" },
{"moshop", 3, 64, "stateMoesInside" },
{"melcut", 2, 127, "stateMoesOutside" },
{"mellover", 2, 127, "stateMellonAbove" },
{"radloop", 3, 28, "stateTrailerOutside" },
{"radloop", 3, 58, "stateTrailerInside" },
{"radloop", 3, 127, "stateTodShop" },
{"junkgate", 2, 127, "stateJunkGate" },
{"junkover", 3, 127, "stateJunkAbove" },
{"gastower", 2, 127, "stateGasTower" },
{"", 4, 0, "stateTowerAlarm" },
{"melcut", 2, 127, "stateCopsOnGround" },
{"melcut", 2, 127, "stateCopsAround" },
{"melcut", 2, 127, "stateMoesRuins" },
{"melcut", 2, 127, "stateKstandNight" },
{"trukblu2", 2, 127, "stateTruckerTalk" },
{"stretch", 2, 127, "stateMumblyPeg" },
{"kstand", 2, 100, "stateRanchOutside" },
{"kinside", 2, 127, "stateRanchInside" },
{"desert", 2, 127, "stateWreckedTruck" },
{"opening", 2, 100, "stateGorgeVista" },
{"caveopen", 2, 127, "stateCaveOpen" },
{"cavecut1", 2, 127, "stateCaveOuter" },
{"cavecut1", 1, 127, "stateCaveMiddle" },
{"cave", 2, 127, "stateCaveInner" },
{"corville", 2, 127, "stateCorvilleFront" },
{"mines", 2, 127, "stateMineField" },
{"bunyman3", 2, 127, "stateBunnyStore" },
{"stretch", 2, 127, "stateStretchBen" },
{"saveme", 2, 127, "stateBenPleas" },
{"", 4, 0, "stateBenConvinces" },
{"derby", 3, 127, "stateDemoDerby" },
{"fire", 3, 127, "stateLightMyFire" },
{"derby", 3, 127, "stateDerbyChase" },
{"carparts", 2, 127, "stateVultureCarParts"},
{"cavecut1", 2, 127, "stateVulturesInside" },
{"mines", 2, 127, "stateFactoryRear" },
{"croffice", 2, 127, "stateCorleyOffice" },
{"melcut", 2, 127, "stateCorleyHall" },
{"", 4, 0, "stateProjRoom" },
{"", 4, 0, "stateMMRoom" },
{"bumper", 2, 127, "stateBenOnBumper" },
{"benump", 2, 127, "stateBenOnBack" },
{"plane", 2, 127, "stateInCargoPlane" },
{"saveme", 2, 127, "statePlaneControls" },
{"", 4, 0, "stateCliffHanger1" },
{"", 4, 0, "stateCliffHanger2" },
{"", 0, 0, "" },
};
const imuseFtNames _ftSeqNames[] = {
{"SEQ_NULL" },
{"seqLogo" },
{"seqOpenFlick" },
{"seqBartender" },
{"seqBenWakes" },
{"seqPhotoScram" },
{"seqClimbChain" },
{"seqDogChase" },
{"seqDogSquish" },
{"seqDogHoist" },
{"seqCopsArrive" },
{"seqCopsLand" },
{"seqCopsLeave" },
{"seqCopterFlyby" },
{"seqCopterCrash" },
{"seqMoGetsParts" },
{"seqMoFixesBike" },
{"seqFirstGoodbye" },
{"seqCopRoadblock" },
{"seqDivertCops" },
{"seqMurder" },
{"seqCorleyDies" },
{"seqTooLateAtMoes" },
{"seqPicture" },
{"seqNewsReel" },
{"seqCopsInspect" },
{"seqHijack" },
{"seqNestolusAtRanch" },
{"seqRipLimo" },
{"seqGorgeTurn" },
{"seqStealRamp" },
{"seqCavefishTalk" },
{"seqArriveCorville" },
{"seqSingleBunny" },
{"seqBunnyArmy" },
{"seqArriveAtMines" },
{"seqArriveAtVultures"},
{"seqMakePlan" },
{"seqShowPlan" },
{"seqDerbyStart" },
{"seqLightBales" },
{"seqNestolusBBQ" },
{"seqCallSecurity" },
{"seqFilmFail" },
{"seqFilmBurn" },
{"seqRipSpeech" },
{"seqExposeRip" },
{"seqRipEscape" },
{"seqRareMoment" },
{"seqFanBunnies" },
{"seqRipDead" },
{"seqFuneral" },
{"seqCredits" },
{"" }
};
const imuseFtSeqTable _ftSeqMusicTable[] = {
{"", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"opening", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"barbeat", 2, 127},
{"barwarn", 2, 127},
{"", 0, 0 },
{"", 0, 0, },
{"benwakes", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"barwarn", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"swatben", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"dogattak", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 4, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 4, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"cops2", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"cops2", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"cops2", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"bunymrch", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 4, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"melcut", 2, 127},
{"tada", 2, 127},
{"", 0, 0 },
{"", 4, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"trucker", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"cops2", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"barwarn", 2, 127},
{"murder", 2, 127},
{"murder2", 2, 127},
{"", 0, 0 },
{"corldie", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"barwarn", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"picture", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"ripintro", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"trucker", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"hosed", 2, 127},
{"ripdead", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"nesranch", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"scolding", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"desert", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"cavecut1", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"caveamb", 2, 80 },
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"castle", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"bunymrch", 2, 105},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"valkyrs", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"melcut", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"vultures", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"sorry", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"makeplan", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"castle", 2, 127},
{"derby", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"fire", 3, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"saveme", 3, 127},
{"", 0, 0 },
{"", 0, 0 },
{"scolding", 2, 127},
{"cops2", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"sorry", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"sorry", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"caveamb", 2, 85 },
{"tada", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"expose", 2, 127},
{"", 4, 0 },
{"", 0, 0 },
{"mocoup", 2, 127},
{"ripscram", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"valkyrs", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"ripdead", 2, 127},
{"", 0, 0 },
{"", 0, 0 },
{"", 0, 0 },
{"funeral", 2, 127},
{"", 0, 0 },
{"moshop", 3, 64 },
{"", 0, 0 },
{"bornbad", 2, 127},
{"hammvox", 2, 127},
{"legavox", 2, 127},
{"chances", 2, 90 },
};
} // End of namespace Scumm

View File

@@ -0,0 +1,87 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_TABLES_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_TABLES_H
#include "common/scummsys.h"
namespace Scumm {
struct imuseRoomMap {
int8 roomId;
byte stateIndex1;
byte offset;
byte stateIndex2;
byte attribPos;
byte stateIndex3;
};
struct imuseDigTable {
byte transitionType;
int16 soundId;
char name[20];
byte attribPos;
byte hookId;
char filename[13];
};
struct imuseComiTable {
byte transitionType;
int16 soundId;
char name[20];
byte attribPos;
byte hookId;
int16 fadeOutDelay;
char filename[13];
};
struct imuseFtNames {
char name[20];
};
struct imuseFtStateTable {
char audioName[9];
byte transitionType;
byte volume;
char name[21];
};
struct imuseFtSeqTable {
char audioName[9];
byte transitionType;
byte volume;
};
extern const imuseRoomMap _digStateMusicMap[];
extern const imuseDigTable _digStateMusicTable[];
extern const imuseDigTable _digSeqMusicTable[];
extern const imuseComiTable _comiStateMusicTable[];
extern const imuseComiTable _comiSeqMusicTable[];
extern const imuseComiTable _comiDemoStateMusicTable[];
extern const imuseFtStateTable _ftStateMusicTable[];
extern const imuseFtSeqTable _ftSeqMusicTable[];
extern const imuseFtNames _ftSeqNames[];
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,821 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
int IMuseDigital::tracksInit() {
_trackCount = _lowLatencyMode ? DIMUSE_MAX_TRACKS : 6;
_tracksPauseTimer = 0;
_trackList = nullptr;
if (waveOutInit(&waveOutSettings))
return -1;
if (_internalMixer->init(waveOutSettings.bytesPerSample,
waveOutSettings.numChannels,
waveOutSettings.mixBuf,
waveOutSettings.mixBufSize,
waveOutSettings.sizeSampleKB,
_trackCount) ||
dispatchInit() ||
streamerInit()) {
return -1;
}
for (int l = 0; l < _trackCount; l++) {
_tracks[l].index = l;
_tracks[l].prev = nullptr;
_tracks[l].next = nullptr;
_tracks[l].dispatchPtr = dispatchGetDispatchByTrackId(l);
_tracks[l].dispatchPtr->trackPtr = &_tracks[l];
_tracks[l].soundId = 0;
_tracks[l].group = 0;
_tracks[l].marker = 0;
_tracks[l].priority = 0;
_tracks[l].vol = 0;
_tracks[l].effVol = 0;
_tracks[l].pan = 0;
_tracks[l].detune = 0;
_tracks[l].transpose = 0;
_tracks[l].pitchShift = 0;
_tracks[l].mailbox = 0;
_tracks[l].jumpHook = 0;
_tracks[l].syncSize_0 = 0;
_tracks[l].syncSize_1 = 0;
_tracks[l].syncSize_2 = 0;
_tracks[l].syncSize_3 = 0;
_tracks[l].syncPtr_0 = nullptr;
_tracks[l].syncPtr_1 = nullptr;
_tracks[l].syncPtr_2 = nullptr;
_tracks[l].syncPtr_3 = nullptr;
}
return 0;
}
void IMuseDigital::tracksPause() {
_tracksPauseTimer = 1;
}
void IMuseDigital::tracksResume() {
_tracksPauseTimer = 0;
}
void IMuseDigital::tracksSaveLoad(Common::Serializer &ser) {
Common::StackLock lock(*_mutex);
dispatchSaveLoad(ser);
for (int l = 0; l < _trackCount; l++) {
ser.syncAsSint32LE(_tracks[l].soundId, VER(103));
ser.syncAsSint32LE(_tracks[l].marker, VER(103));
ser.syncAsSint32LE(_tracks[l].group, VER(103));
ser.syncAsSint32LE(_tracks[l].priority, VER(103));
ser.syncAsSint32LE(_tracks[l].vol, VER(103));
ser.syncAsSint32LE(_tracks[l].effVol, VER(103));
ser.syncAsSint32LE(_tracks[l].pan, VER(103));
ser.syncAsSint32LE(_tracks[l].detune, VER(103));
ser.syncAsSint32LE(_tracks[l].transpose, VER(103));
ser.syncAsSint32LE(_tracks[l].pitchShift, VER(103));
ser.syncAsSint32LE(_tracks[l].mailbox, VER(103));
ser.syncAsSint32LE(_tracks[l].jumpHook, VER(103));
if (_vm->_game.id == GID_CMI) {
ser.syncAsSint32LE(_tracks[l].syncSize_0, VER(103));
ser.syncAsSint32LE(_tracks[l].syncSize_1, VER(103));
ser.syncAsSint32LE(_tracks[l].syncSize_2, VER(103));
ser.syncAsSint32LE(_tracks[l].syncSize_3, VER(103));
if (_tracks[l].syncSize_0) {
if (ser.isLoading())
_tracks[l].syncPtr_0 = (byte *)malloc(_tracks[l].syncSize_0);
ser.syncArray(_tracks[l].syncPtr_0, _tracks[l].syncSize_0, Common::Serializer::Byte, VER(103));
}
if (_tracks[l].syncSize_1) {
if (ser.isLoading())
_tracks[l].syncPtr_1 = (byte *)malloc(_tracks[l].syncSize_1);
ser.syncArray(_tracks[l].syncPtr_1, _tracks[l].syncSize_1, Common::Serializer::Byte, VER(103));
}
if (_tracks[l].syncSize_2) {
if (ser.isLoading())
_tracks[l].syncPtr_2 = (byte *)malloc(_tracks[l].syncSize_2);
ser.syncArray(_tracks[l].syncPtr_2, _tracks[l].syncSize_2, Common::Serializer::Byte, VER(103));
}
if (_tracks[l].syncSize_3) {
if (ser.isLoading())
_tracks[l].syncPtr_3 = (byte *)malloc(_tracks[l].syncSize_3);
ser.syncArray(_tracks[l].syncPtr_3, _tracks[l].syncSize_3, Common::Serializer::Byte, VER(103));
}
}
}
if (ser.isLoading()) {
for (int l = 0; l < _trackCount; l++) {
_tracks[l].prev = nullptr;
_tracks[l].next = nullptr;
_tracks[l].dispatchPtr = dispatchGetDispatchByTrackId(l);
_tracks[l].dispatchPtr->trackPtr = &_tracks[l];
if (_tracks[l].soundId) {
addTrackToList(&_trackList, &_tracks[l]);
}
}
dispatchRestoreStreamZones();
}
}
void IMuseDigital::tracksSetGroupVol() {
IMuseDigiTrack* curTrack = _trackList;
while (curTrack) {
curTrack->effVol = ((curTrack->vol + 1) * _groupsHandler->getGroupVol(curTrack->group)) / 128;
curTrack = curTrack->next;
}
}
void IMuseDigital::tracksCallback() {
if (_tracksPauseTimer) {
if (++_tracksPauseTimer < 3)
return;
_tracksPauseTimer = 3;
}
// This piece of code is responsible for adaptive buffer overrun correction:
// it checks whether a buffer underrun has occurred within our output stream
// and then it increments the buffer count.
//
// This is not part of the original implementation, but it's used to yield
// smooth audio hopefully on every device.
if (_internalMixer->_stream->endOfData() && _checkForUnderrun) {
debug(5, "IMuseDigital::tracksCallback(): WARNING: audio buffer underrun, adapting the buffer queue count...");
adaptBufferCount();
// Allow the routine to cooldown: i.e. wait until the engine manages to
// refill the stream with the most recent maximum number of queueable buffers.
_underrunCooldown = _maxQueuedStreams;
_checkForUnderrun = false;
}
// If we leave the number of queued streams unbounded, we fill the queue with streams faster than
// we can play them: this leads to a very noticeable audio latency and desync with the graphics.
if ((int)_internalMixer->_stream->numQueuedStreams() < _maxQueuedStreams) {
if (!_isEarlyDiMUSE)
dispatchPredictFirstStream();
waveOutWrite(&_outputAudioBuffer, _outputFeedSize, _outputSampleRate);
if (_outputFeedSize != 0) {
// Let's see if we should check for buffer underruns...
if (!_checkForUnderrun) {
if (_underrunCooldown == 0) {
_checkForUnderrun = true;
} else {
_underrunCooldown--;
}
}
_internalMixer->clearMixerBuffer();
if (_isEarlyDiMUSE && _splayer && _splayer->isAudioCallbackEnabled()) {
_splayer->processDispatches(_outputFeedSize);
}
if (!_tracksPauseTimer) {
IMuseDigiTrack *track = _trackList;
while (track) {
IMuseDigiTrack *next = track->next;
if (_isEarlyDiMUSE) {
dispatchProcessDispatches(track, _outputFeedSize);
} else {
dispatchProcessDispatches(track, _outputFeedSize, _outputSampleRate);
}
track = next;
};
}
_internalMixer->loop(&_outputAudioBuffer, _outputFeedSize);
// The Dig tries to write a second time
if (!_isEarlyDiMUSE && _vm->_game.id == GID_DIG) {
waveOutWrite(&_outputAudioBuffer, _outputFeedSize, _outputSampleRate);
}
}
}
}
void IMuseDigital::tracksLowLatencyCallback() {
// Why do we need a low latency mode?
//
// For every audio callback, this engine works by collecting all the sound
// data for every track and by mixing it up in a single output stream.
// This is exactly how the original executables worked, so our implementation
// provides a very faithful recreation of that experience. And it comes with
// a compromise that e.g. The Dig and Full Throttle didn't have to front:
//
// in order to provide glitchless audio, an appropriate stream queue size
// has to be enforced: a longer queue yields a lower probability of audio glitches
// but a higher latency, and viceversa. In our case, this depends on the audio backend
// configuration. As such: some configurations might encounter audible latency (#13462).
//
// We solve this issue by offering this alternate low latency mode which, instead
// of keeping a single stream for everything, creates (and disposes) streams on the fly
// for every different sound. This means that whenever the new sound data is ready,
// a new stream is initialized and played immediately, without having to wait for all
// the other sounds to be processed and mixed in the same sample pool.
if (_tracksPauseTimer) {
if (++_tracksPauseTimer < 3)
return;
_tracksPauseTimer = 3;
}
// The callback path is heavily inspired from the original one (see tracksCallback()),
// but it handles each track separatedly, with the exception of SMUSH audio for Full
// Throttle: this is why we operate on two parallel paths...
if (!_isEarlyDiMUSE)
dispatchPredictFirstStream();
IMuseDigiTrack *track = _trackList;
// This flag ensures that, even when no track is available,
// FT SMUSH audio can still be played. At least, and AT MOST once :-)
bool runSMUSHAudio = _isEarlyDiMUSE;
while (track || runSMUSHAudio) {
IMuseDigiTrack *next = track ? track->next : nullptr;
int idx = track ? track->index : -1;
// We use a separate queue cardinality handling, since SMUSH audio and iMUSE audio can overlap...
bool canQueueBufs = (int)_internalMixer->getStream(idx)->numQueuedStreams() < (_maxQueuedStreams + 1);
bool canQueueFtSmush = _internalMixer->getStream(-1) != nullptr;
if (canQueueFtSmush) {
canQueueFtSmush &= (int)_internalMixer->getStream(-1)->numQueuedStreams() < (_maxQueuedStreams + 1);
}
if (canQueueBufs) {
if (track)
waveOutLowLatencyWrite(&_outputLowLatencyAudioBuffers[idx], _outputFeedSize, _outputSampleRate, idx);
// Notice how SMUSH audio for Full Throttle uses the original single-stream mode:
// this is necessary both for code cleanliness and for correct audio sync.
if (runSMUSHAudio && canQueueFtSmush)
waveOutWrite(&_outputAudioBuffer, _outputFeedSize, _outputSampleRate);
if (_outputFeedSize != 0) {
// FT SMUSH dispatch processing...
if (runSMUSHAudio && canQueueFtSmush && _isEarlyDiMUSE && _splayer && _splayer->isAudioCallbackEnabled()) {
_internalMixer->setCurrentMixerBuffer(_outputAudioBuffer);
_internalMixer->clearMixerBuffer();
_splayer->processDispatches(_outputFeedSize);
_internalMixer->loop(&_outputAudioBuffer, _outputFeedSize);
}
// Ordinary audio tracks handling...
if (track) {
_internalMixer->setCurrentMixerBuffer(_outputLowLatencyAudioBuffers[idx]);
_internalMixer->clearMixerBuffer();
if (!_tracksPauseTimer) {
if (_isEarlyDiMUSE) {
dispatchProcessDispatches(track, _outputFeedSize);
} else {
dispatchProcessDispatches(track, _outputFeedSize, _outputSampleRate);
}
}
_internalMixer->loop(&_outputLowLatencyAudioBuffers[idx], _outputFeedSize);
// The Dig tries to write a second time
if (!_isEarlyDiMUSE && _vm->_game.id == GID_DIG) {
waveOutLowLatencyWrite(&_outputLowLatencyAudioBuffers[idx], _outputFeedSize, _outputSampleRate, idx);
}
// If, after processing the track dispatch, the sound is set to zero
// it means that it has reached the end: let's notify its stream...
if (track->soundId == 0) {
_internalMixer->endStream(idx);
}
}
}
}
if (track)
track = next;
runSMUSHAudio = false;
}
}
int IMuseDigital::tracksStartSound(int soundId, int tryPriority, int group) {
int priority = clampNumber(tryPriority, 0, 127);
debug(5, "IMuseDigital::tracksStartSound(): sound %d with priority %d and group %d", soundId, priority, group);
IMuseDigiTrack *allocatedTrack = tracksReserveTrack(priority);
if (!allocatedTrack) {
debug(5, "IMuseDigital::tracksStartSound(): ERROR: couldn't find a spare track to allocate sound %d", soundId);
return -6;
}
allocatedTrack->soundId = soundId;
allocatedTrack->marker = 0;
allocatedTrack->group = 0;
allocatedTrack->priority = priority;
allocatedTrack->vol = 127;
allocatedTrack->effVol = _groupsHandler->getGroupVol(0);
allocatedTrack->pan = 64;
allocatedTrack->detune = 0;
allocatedTrack->transpose = 0;
allocatedTrack->pitchShift = 256;
allocatedTrack->mailbox = 0;
allocatedTrack->jumpHook = 0;
allocatedTrack->syncSize_0 = 0;
allocatedTrack->syncPtr_0 = nullptr;
allocatedTrack->syncSize_1 = 0;
allocatedTrack->syncPtr_1 = nullptr;
allocatedTrack->syncSize_2 = 0;
allocatedTrack->syncPtr_2 = nullptr;
allocatedTrack->syncSize_3 = 0;
allocatedTrack->syncPtr_3 = nullptr;
if (dispatchAllocateSound(allocatedTrack, group)) {
debug(5, "IMuseDigital::tracksStartSound(): ERROR: dispatch couldn't start sound %d", soundId);
allocatedTrack->soundId = 0;
return -1;
}
_mutex->lock();
addTrackToList(&_trackList, allocatedTrack);
_mutex->unlock();
return 0;
}
int IMuseDigital::tracksStopSound(int soundId) {
if (!_trackList)
return -1;
IMuseDigiTrack *nextTrack = _trackList;
IMuseDigiTrack *curTrack;
while (nextTrack) {
curTrack = nextTrack;
nextTrack = curTrack->next;
if (curTrack->soundId == soundId) {
tracksClear(curTrack);
}
}
return 0;
}
int IMuseDigital::tracksStopAllSounds() {
Common::StackLock lock(*_mutex);
IMuseDigiTrack *nextTrack = _trackList;
IMuseDigiTrack *curTrack;
while (nextTrack) {
curTrack = nextTrack;
nextTrack = curTrack->next;
tracksClear(curTrack);
}
_filesHandler->closeAllSounds();
return 0;
}
int IMuseDigital::tracksGetNextSound(int soundId) {
int foundSoundId = 0;
IMuseDigiTrack *track = _trackList;
while (track) {
if (track->soundId > soundId) {
if (!foundSoundId || track->soundId < foundSoundId) {
foundSoundId = track->soundId;
}
}
track = track->next;
};
return foundSoundId;
}
int IMuseDigital::tracksQueryStream(int soundId, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused) {
if (!_trackList) {
debug(5, "IMuseDigital::tracksQueryStream(): WARNING: empty trackList, ignoring call...");
return isFTSoundEngine() ? 0 : -1;
}
IMuseDigiTrack *track = _trackList;
if (isFTSoundEngine()) {
IMuseDigiTrack *chosenTrack = nullptr;
do {
if (track->soundId > soundId && (!chosenTrack || track->soundId < chosenTrack->soundId)) {
if (track->dispatchPtr->streamPtr)
chosenTrack = track;
}
track = track->next;
} while (track);
if (!chosenTrack)
return 0;
streamerQueryStream(chosenTrack->dispatchPtr->streamPtr, bufSize, criticalSize, freeSpace, paused);
return chosenTrack->soundId;
} else {
do {
if (track->soundId) {
if (soundId == track->soundId && track->dispatchPtr->streamPtr) {
streamerQueryStream(track->dispatchPtr->streamPtr, bufSize, criticalSize, freeSpace, paused);
return 0;
}
}
track = track->next;
} while (track);
debug(5, "IMuseDigital::tracksQueryStream(): WARNING: couldn't find sound %d in trackList, ignoring call...", soundId);
return -1;
}
}
int IMuseDigital::tracksFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused) {
if (!_trackList)
return -1;
IMuseDigiTrack *track = _trackList;
do {
if (track->soundId != 0) {
if (track->soundId == soundId && track->dispatchPtr->streamPtr) {
streamerFeedStream(track->dispatchPtr->streamPtr, srcBuf, sizeToFeed, paused);
return 0;
}
}
track = track->next;
} while (track);
return -1;
}
void IMuseDigital::tracksClear(IMuseDigiTrack *trackPtr) {
Common::StackLock lock(*_mutex);
if (_vm->_game.id == GID_CMI) {
if (trackPtr->syncPtr_0) {
trackPtr->syncSize_0 = 0;
free(trackPtr->syncPtr_0);
trackPtr->syncPtr_0 = nullptr;
}
if (trackPtr->syncPtr_1) {
trackPtr->syncSize_1 = 0;
free(trackPtr->syncPtr_1);
trackPtr->syncPtr_1 = nullptr;
}
if (trackPtr->syncPtr_2) {
trackPtr->syncSize_2 = 0;
free(trackPtr->syncPtr_2);
trackPtr->syncPtr_2 = nullptr;
}
if (trackPtr->syncPtr_3) {
trackPtr->syncSize_3 = 0;
free(trackPtr->syncPtr_3);
trackPtr->syncPtr_3 = nullptr;
}
}
removeTrackFromList(&_trackList, trackPtr);
dispatchRelease(trackPtr);
_fadesHandler->clearFadeStatus(trackPtr->soundId, -1);
_triggersHandler->clearTrigger(trackPtr->soundId, _emptyMarker, -1);
// Unlock the sound, if it's loaded as a resource
if (trackPtr->soundId < 1000 && trackPtr->soundId) {
_vm->_res->unlock(rtSound, trackPtr->soundId);
}
if (_lowLatencyMode)
waveOutEmptyBuffer(trackPtr->index);
trackPtr->soundId = 0;
}
int IMuseDigital::tracksSetParam(int soundId, int opcode, int value) {
if (!_trackList)
return -4;
IMuseDigiTrack *track = _trackList;
while (track) {
if (track->soundId == soundId) {
switch (opcode) {
case DIMUSE_P_GROUP:
if (value >= 16)
return -5;
track->group = value;
track->effVol = ((track->vol + 1) * _groupsHandler->getGroupVol(value)) / 128;
return 0;
case DIMUSE_P_PRIORITY:
if (value > 127)
return -5;
track->priority = value;
return 0;
case DIMUSE_P_VOLUME:
if (value > 127)
return -5;
track->vol = value;
track->effVol = ((value + 1) * _groupsHandler->getGroupVol(track->group)) / 128;
return 0;
case DIMUSE_P_PAN:
if (value > 127)
return -5;
track->pan = value;
return 0;
case DIMUSE_P_DETUNE:
if (value < -9216 || value > 9216)
return -5;
track->detune = value;
track->pitchShift = value + track->transpose * 256;
return 0;
case DIMUSE_P_TRANSPOSE:
if (_vm->_game.id == GID_DIG || _vm->_game.id == GID_FT) {
if (value < -12 || value > 12)
return -5;
if (value == 0) {
track->transpose = 0;
} else {
track->transpose = clampTuning(track->detune + value, -12, 12);
}
track->pitchShift = track->detune + (track->transpose * 256);
} else if (_vm->_game.id == GID_CMI) {
if (value < 0 || value > 4095)
return -5;
track->pitchShift = value;
}
return 0;
case DIMUSE_P_MAILBOX:
track->mailbox = value;
return 0;
default:
debug(5, "IMuseDigital::tracksSetParam(): unknown opcode %d", opcode);
return -5;
}
}
track = track->next;
}
return -4;
}
int IMuseDigital::tracksGetParam(int soundId, int opcode) {
if (!_trackList) {
if (opcode != DIMUSE_P_SND_TRACK_NUM)
return -4;
else
return 0;
}
IMuseDigiTrack *track = _trackList;
int l = 0;
do {
if (track)
l++;
if (track->soundId == soundId) {
switch (opcode) {
case DIMUSE_P_BOGUS_ID:
return -1;
case DIMUSE_P_SND_TRACK_NUM:
return l;
case DIMUSE_P_TRIGS_SNDS:
return -1;
case DIMUSE_P_MARKER:
return track->marker;
case DIMUSE_P_GROUP:
return track->group;
case DIMUSE_P_PRIORITY:
return track->priority;
case DIMUSE_P_VOLUME:
return track->vol;
case DIMUSE_P_PAN:
return track->pan;
case DIMUSE_P_DETUNE:
return track->detune;
case DIMUSE_P_TRANSPOSE:
return track->transpose;
case DIMUSE_P_MAILBOX:
return track->mailbox;
case DIMUSE_P_SND_HAS_STREAM:
return (track->dispatchPtr->streamPtr != 0);
case DIMUSE_P_STREAM_BUFID:
return track->dispatchPtr->streamBufID;
case DIMUSE_P_SND_POS_IN_MS: // getCurSoundPositionInMs
if (track->dispatchPtr->wordSize == 0)
return 0;
if (track->dispatchPtr->sampleRate == 0)
return 0;
if (track->dispatchPtr->channelCount == 0)
return 0;
return (track->dispatchPtr->currentOffset * 5) / (((track->dispatchPtr->wordSize / 8) * track->dispatchPtr->sampleRate * track->dispatchPtr->channelCount) / 200);
default:
return -5;
}
}
track = track->next;
} while (track);
return 0;
}
int IMuseDigital::tracksLipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height) {
int32 w, h;
byte *syncPtr = nullptr;
int32 syncSize = 0;
IMuseDigiTrack *curTrack;
int16 val;
w = 0;
h = 0;
curTrack = _trackList;
if (msPos >= 0) {
// Check for an invalid timestamp:
// this has to be a suitable 2-bytes word...
if (((msPos >> 4) & 0xFFFF0000) != 0) {
return -5;
} else {
if (_trackList) {
do {
if (curTrack->soundId == soundId)
break;
curTrack = curTrack->next;
} while (curTrack);
}
if (curTrack) {
if (syncId >= 0 && syncId < 4) {
if (syncId == 0) {
syncPtr = curTrack->syncPtr_0;
syncSize = curTrack->syncSize_0;
} else if (syncId == 1) {
syncPtr = curTrack->syncPtr_1;
syncSize = curTrack->syncSize_1;
} else if (syncId == 2) {
syncPtr = curTrack->syncPtr_2;
syncSize = curTrack->syncSize_2;
} else if (syncId == 3) {
syncPtr = curTrack->syncPtr_3;
syncSize = curTrack->syncSize_3;
}
if (syncSize && syncPtr) {
// SYNC data is packed in a number of 4-bytes entries, in the following order:
// - Width and height values, packed as one byte each, next to each other;
// - The time position of said values, packed as an unsigned word (2-bytes).
// Given an input timestamp (in ms), we're going to get its representation as 60Hz
// increments by dividing it by 16, then we're going to search the SYNC data from
// the beginning to find the first entry with a timestamp being equal or greater
// our 60Hz timestamp.
uint16 inputTs = msPos >> 4;
int32 numOfEntries = (syncSize >> 2);
uint16 *syncDataWordPtr = (uint16 *)syncPtr;
uint16 curEntryTs = 0;
int idx;
for (idx = 0; idx < numOfEntries; idx++) {
curEntryTs = READ_LE_UINT16(&syncDataWordPtr[idx * 2 + 1]);
if (curEntryTs >= inputTs) {
break;
}
}
// If no relevant entry is found, or if the found entry timestamp is strictly greater
// than ours, then we get the previous entry. If no entry was found, this will get the
// last entry in our data block.
if (idx == numOfEntries || curEntryTs > inputTs) {
idx--;
}
// Finally, extract width and height values and remove
// their signs by performing AND operations with 0x7F...
val = READ_LE_INT16(&syncDataWordPtr[idx * 2]);
w = (val >> 8) & 0x7F;
h = val & 0x7F;
}
}
} else {
return -4;
}
}
}
width = w;
height = h;
return 0;
}
int IMuseDigital::tracksSetHook(int soundId, int hookId) {
if (_isEarlyDiMUSE)
return -2;
if (hookId > 128)
return -5;
if (!_trackList)
return -4;
IMuseDigiTrack *track = _trackList;
while (track->soundId != soundId) {
track = track->next;
if (!track)
return -4;
}
track->jumpHook = hookId;
return 0;
}
int IMuseDigital::tracksGetHook(int soundId) {
if (_isEarlyDiMUSE)
return -2;
if (!_trackList)
return -4;
IMuseDigiTrack *track = _trackList;
while (track->soundId != soundId) {
track = track->next;
if (!track)
return -4;
}
return track->jumpHook;
}
IMuseDigiTrack *IMuseDigital::tracksReserveTrack(int priority) {
IMuseDigiTrack *curTrack;
IMuseDigiTrack *reservedTrack = nullptr;
int minPriorityFound;
// Pick the track from the pool of free tracks
for (int i = 0; i < _trackCount; i++) {
reservedTrack = &_tracks[i];
if (!reservedTrack->soundId) {
return reservedTrack;
}
}
// If no free track is found, steal the lower priority one
curTrack = _trackList;
for (minPriorityFound = 127; curTrack; curTrack = curTrack->next) {
if (curTrack->priority <= minPriorityFound) {
minPriorityFound = curTrack->priority;
reservedTrack = curTrack;
}
}
if (reservedTrack && priority >= minPriorityFound) {
tracksClear(reservedTrack);
}
return reservedTrack;
}
void IMuseDigital::tracksDeinit() {
tracksStopAllSounds();
}
} // End of namespace Scumm

View File

@@ -0,0 +1,348 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
#include "scumm/imuse_digi/dimuse_triggers.h"
namespace Scumm {
IMuseDigiTriggersHandler::IMuseDigiTriggersHandler(IMuseDigital *engine, Common::Mutex *mutex) {
_engine = engine;
_mutex = mutex;
_emptyMarker[0] = '\0';
}
IMuseDigiTriggersHandler::~IMuseDigiTriggersHandler() {}
int IMuseDigiTriggersHandler::init() {
return clearAllTriggers();
}
int IMuseDigiTriggersHandler::deinit() {
return clearAllTriggers();
}
int IMuseDigiTriggersHandler::clearAllTriggers() {
Common::StackLock lock(*_mutex);
for (int l = 0; l < DIMUSE_MAX_TRIGGERS; l++) {
_trigs[l].sound = 0;
memset(_trigs[l].text, 0, sizeof(_trigs[l].text));
_trigs[l].opcode = 0;
_trigs[l].a = 0;
_trigs[l].b = 0;
_trigs[l].c = 0;
_trigs[l].d = 0;
_trigs[l].e = 0;
_trigs[l].f = 0;
_trigs[l].g = 0;
_trigs[l].h = 0;
_trigs[l].i = 0;
_trigs[l].j = 0;
_trigs[l].clearLater = 0;
}
for (int l = 0; l < DIMUSE_MAX_DEFERS; l++) {
_defers[l].counter = 0;
_defers[l].opcode = 0;
_defers[l].a = 0;
_defers[l].b = 0;
_defers[l].c = 0;
_defers[l].d = 0;
_defers[l].e = 0;
_defers[l].f = 0;
_defers[l].g = 0;
_defers[l].h = 0;
_defers[l].i = 0;
_defers[l].j = 0;
}
_defersOn = 0;
_midProcessing = 0;
return 0;
}
void IMuseDigiTriggersHandler::saveLoad(Common::Serializer &ser) {
for (int l = 0; l < DIMUSE_MAX_TRIGGERS; l++) {
ser.syncAsSint32LE(_trigs[l].sound, VER(103));
ser.syncArray(_trigs[l].text, 256, Common::Serializer::SByte, VER(103));
ser.syncAsSint32LE(_trigs[l].opcode, VER(103));
ser.syncAsSint32LE(_trigs[l].a, VER(103));
ser.syncAsSint32LE(_trigs[l].b, VER(103));
ser.syncAsSint32LE(_trigs[l].c, VER(103));
ser.syncAsSint32LE(_trigs[l].d, VER(103));
ser.syncAsSint32LE(_trigs[l].e, VER(103));
ser.syncAsSint32LE(_trigs[l].f, VER(103));
ser.syncAsSint32LE(_trigs[l].g, VER(103));
ser.syncAsSint32LE(_trigs[l].h, VER(103));
ser.syncAsSint32LE(_trigs[l].i, VER(103));
ser.syncAsSint32LE(_trigs[l].j, VER(103));
ser.syncAsSint32LE(_trigs[l].clearLater, VER(103));
}
for (int l = 0; l < DIMUSE_MAX_DEFERS; l++) {
ser.syncAsSint32LE(_defers[l].counter, VER(103));
ser.syncAsSint32LE(_defers[l].opcode, VER(103));
ser.syncAsSint32LE(_defers[l].a, VER(103));
ser.syncAsSint32LE(_defers[l].b, VER(103));
ser.syncAsSint32LE(_defers[l].c, VER(103));
ser.syncAsSint32LE(_defers[l].d, VER(103));
ser.syncAsSint32LE(_defers[l].e, VER(103));
ser.syncAsSint32LE(_defers[l].f, VER(103));
ser.syncAsSint32LE(_defers[l].g, VER(103));
ser.syncAsSint32LE(_defers[l].h, VER(103));
ser.syncAsSint32LE(_defers[l].i, VER(103));
ser.syncAsSint32LE(_defers[l].j, VER(103));
}
if (ser.isLoading())
_defersOn = 1;
}
int IMuseDigiTriggersHandler::setTrigger(int soundId, char *marker, int opcode, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n) {
Common::StackLock lock(*_mutex);
if (soundId == 0) {
return -5;
}
if (marker == nullptr) {
marker = _emptyMarker;
}
if (strlen(marker) >= 256) {
debug(5, "IMuseDigiTriggersHandler::setTrigger(): ERROR: attempting to set trigger with oversized marker string");
return -5;
}
for (int index = 0; index < DIMUSE_MAX_TRIGGERS; index++) {
if (_trigs[index].sound == 0) {
_trigs[index].sound = soundId;
_trigs[index].clearLater = 0;
_trigs[index].opcode = opcode;
Common::strlcpy(_trigs[index].text, marker, sizeof(_trigs[index].text));
_trigs[index].a = d;
_trigs[index].b = e;
_trigs[index].c = f;
_trigs[index].d = g;
_trigs[index].e = h;
_trigs[index].f = i;
_trigs[index].g = j;
_trigs[index].h = k;
_trigs[index].i = l;
_trigs[index].j = m;
debug(5, "IMuseDigiTriggersHandler::setTrigger(): Successfully set trigger for soundId %d and marker '%s'", soundId, marker);
return 0;
}
}
debug(5, "IMuseDigiTriggersHandler::setTrigger(): ERROR: unable to allocate trigger \"%s\" for sound %d, every slot is full", marker, soundId);
return -6;
}
int IMuseDigiTriggersHandler::checkTrigger(int soundId, char *marker, int opcode) {
Common::StackLock lock(*_mutex);
int r = 0;
for (int l = 0; l < DIMUSE_MAX_TRIGGERS; l++) {
if (_trigs[l].sound != 0) {
if (soundId == -1 || _trigs[l].sound == soundId) {
if (!strcmp(marker, _emptyMarker) || !strcmp(marker, _trigs[l].text)) {
if (opcode == -1 || _trigs[l].opcode == opcode)
r++;
}
}
}
}
return r;
}
int IMuseDigiTriggersHandler::clearTrigger(int soundId, char *marker, int opcode) {
Common::StackLock lock(*_mutex);
for (int l = 0; l < DIMUSE_MAX_TRIGGERS; l++) {
if ((_trigs[l].sound != 0) && (soundId == -1 || _trigs[l].sound == soundId) &&
(!strcmp(marker, _emptyMarker) || !strcmp(marker, _trigs[l].text)) &&
(opcode == -1 || _trigs[l].opcode == opcode)) {
if (_midProcessing) {
_trigs[l].clearLater = 1;
} else {
_trigs[l].sound = 0;
}
}
}
return 0;
}
void IMuseDigiTriggersHandler::processTriggers(int soundId, char *marker) {
char textBuffer[256];
int r;
if (strlen(marker) >= 256) {
debug(5, "IMuseDigiTriggersHandler::processTriggers(): ERROR: the input marker string is oversized");
return;
}
Common::strlcpy(_textBuffer, marker, sizeof(textBuffer));
_midProcessing++;
for (int l = 0; l < DIMUSE_MAX_TRIGGERS; l++) {
if (!_trigs[l].sound || _trigs[l].sound != soundId || (_trigs[l].text[0] && strcmp(_textBuffer, _trigs[l].text))) {
continue;
}
// Save the string into our local buffer for later
r = 0;
if (_textBuffer[0] != '\0') {
do {
textBuffer[r] = _textBuffer[r];
r++;
} while (_textBuffer[r] != '\0');
}
textBuffer[r] = '\0';
_trigs[l].sound = 0;
debug(5, "IMuseDigiTriggersHandler::processTriggers(): executing trigger for soundId %d and marker '%s'", soundId, marker);
if (_trigs[l].opcode == DIMUSE_C_SCRIPT_CALLBACK) {
// Call the script callback (a function which sets _stoppingSequence to 1)
_engine->scriptTriggerCallback(_textBuffer);
} else {
if (_trigs[l].opcode < 30) {
// Execute a command
_engine->cmdsHandleCmd(_trigs[l].opcode, (uint8 *)textBuffer,
_trigs[l].a, _trigs[l].b,
_trigs[l].c, _trigs[l].d,
_trigs[l].e, _trigs[l].f,
_trigs[l].g, _trigs[l].h,
_trigs[l].i, _trigs[l].j);
}
}
// Restore the global textBuffer
r = 0;
if (textBuffer[0] != '\0') {
do {
_textBuffer[r] = textBuffer[r];
r++;
} while (textBuffer[r] != '\0');
}
_textBuffer[r] = '\0';
}
if (--_midProcessing == 0) {
for (int l = 0; l < DIMUSE_MAX_TRIGGERS; l++) {
if (_trigs[l].clearLater) {
_trigs[l].sound = 0;
}
}
}
}
int IMuseDigiTriggersHandler::deferCommand(int count, int opcode, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n) {
Common::StackLock lock(*_mutex);
if (!count) {
return -5;
}
for (int index = 0; index < DIMUSE_MAX_DEFERS; index++) {
if (!_defers[index].counter) {
_defers[index].counter = count;
_defers[index].opcode = opcode;
_defers[index].a = c;
_defers[index].b = d;
_defers[index].c = e;
_defers[index].d = f;
_defers[index].e = g;
_defers[index].f = h;
_defers[index].g = i;
_defers[index].h = j;
_defers[index].i = k;
_defers[index].j = l;
_defersOn = 1;
return 0;
}
}
debug(5, "IMuseDigiTriggersHandler::deferCommand(): ERROR: couldn't allocate deferred command");
return -6;
}
void IMuseDigiTriggersHandler::loop() {
if (!_defersOn)
return;
_defersOn = 0;
for (int l = 0; l < DIMUSE_MAX_DEFERS; l++) {
if (_defers[l].counter == 0)
continue;
_defersOn = 1;
_defers[l].counter--;
if (_defers[l].counter == 1) {
if (_defers[l].opcode == DIMUSE_C_SCRIPT_CALLBACK) {
_engine->scriptTriggerCallback(_trigs[l].text);
} else {
if (_defers[l].opcode < 30) {
_engine->cmdsHandleCmd(_trigs[l].opcode, nullptr,
_trigs[l].a, _trigs[l].b,
_trigs[l].c, _trigs[l].d,
_trigs[l].e, _trigs[l].f,
_trigs[l].g, _trigs[l].h,
_trigs[l].i, _trigs[l].j);
}
}
}
}
}
int IMuseDigiTriggersHandler::countPendingSounds(int soundId) {
Common::StackLock lock(*_mutex);
int r = 0;
for (int l = 0; l < DIMUSE_MAX_TRIGGERS; l++) {
if (!_trigs[l].sound)
continue;
int opcode = _trigs[l].opcode;
if ((opcode == DIMUSE_C_START_SND && _trigs[l].a == soundId) ||
(opcode == DIMUSE_C_SWITCH_STREAM && _trigs[l].b == soundId)) {
r++;
}
}
for (int l = 0; l < DIMUSE_MAX_DEFERS; l++) {
if (!_defers[l].counter)
continue;
int opcode = _defers[l].opcode;
if ((opcode == DIMUSE_C_START_SND && _defers[l].a == soundId) ||
(opcode == DIMUSE_C_SWITCH_STREAM && _defers[l].b == soundId)) {
r++;
}
}
return r;
}
} // End of namespace Scumm

View File

@@ -0,0 +1,64 @@
/* 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/>.
*
*/
#if !defined(SCUMM_IMUSE_DIGI_TRIGGERS_H) && defined(ENABLE_SCUMM_7_8)
#define SCUMM_IMUSE_DIGI_TRIGGERS_H
#include "common/scummsys.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "scumm/imuse_digi/dimuse_defs.h"
namespace Scumm {
class IMuseDigiTriggersHandler {
private:
IMuseDigital *_engine;
Common::Mutex *_mutex;
IMuseDigiTrigger _trigs[DIMUSE_MAX_TRIGGERS] = {};
IMuseDigiDefer _defers[DIMUSE_MAX_DEFERS] = {};
int _defersOn = 0;
int _midProcessing = 0;
char _textBuffer[256] = {};
char _emptyMarker[1] = {};
public:
IMuseDigiTriggersHandler(IMuseDigital *engine, Common::Mutex *mutex);
~IMuseDigiTriggersHandler();
int init();
int clearAllTriggers();
void saveLoad(Common::Serializer &ser);
int setTrigger(int soundId, char *marker, int opcode, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n);
int checkTrigger(int soundId, char *marker, int opcode);
int clearTrigger(int soundId, char *marker, int opcode);
void processTriggers(int soundId, char *marker);
int deferCommand(int count, int opcode, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n);
void loop();
int countPendingSounds(int soundId);
int deinit();
};
} // End of namespace Scumm
#endif

View File

@@ -0,0 +1,187 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
int IMuseDigital::addTrackToList(IMuseDigiTrack **listPtr, IMuseDigiTrack *listPtr_Item) {
Common::StackLock lock(*_mutex);
// [0] is ->prev, [1] is ->next
if (!listPtr_Item || listPtr_Item->prev || listPtr_Item->next) {
debug(5, "IMuseDigital::addTrackToList(): ERROR: arguments might be null");
return -5;
} else {
// Set item's next element to the list
listPtr_Item->next = *listPtr;
if (*listPtr) {
// If the list is empty, use this item as the list
(*listPtr)->prev = listPtr_Item;
}
// Set the previous element of the item as nullptr,
// effectively making the item the first element of the list
listPtr_Item->prev = nullptr;
// Update the list with the new data
*listPtr = listPtr_Item;
}
return 0;
}
int IMuseDigital::removeTrackFromList(IMuseDigiTrack **listPtr, IMuseDigiTrack *listPtr_Item) {
Common::StackLock lock(*_mutex);
IMuseDigiTrack *currentTrack = *listPtr;
IMuseDigiTrack *nextTrack;
if (listPtr_Item && currentTrack) {
do {
if (currentTrack == listPtr_Item)
break;
currentTrack = currentTrack->next;
} while (currentTrack);
if (currentTrack) {
nextTrack = listPtr_Item->next;
if (nextTrack)
nextTrack->prev = listPtr_Item->prev;
if (listPtr_Item->prev) {
listPtr_Item->prev->next = listPtr_Item->next;
} else {
*listPtr = listPtr_Item->next;
}
listPtr_Item->prev = nullptr;
listPtr_Item->next = nullptr;
return 0;
} else {
debug(5, "IMuseDigital::removeTrackFromList(): ERROR: item not on list");
return -3;
}
} else {
debug(5, "IMuseDigital::removeTrackFromList(): ERROR: arguments might be null");
return -5;
}
}
int IMuseDigital::addStreamZoneToList(IMuseDigiStreamZone **listPtr, IMuseDigiStreamZone *listPtr_Item) {
if (!listPtr_Item || listPtr_Item->prev || listPtr_Item->next) {
debug(5, "IMuseDigital::addStreamZoneToList(): ERROR: arguments might be null");
return -5;
} else {
// Set item's next element to the list
listPtr_Item->next = *listPtr;
if (*listPtr) {
// If the list is empty, use this item as the list
(*listPtr)->prev = listPtr_Item;
}
// Set the previous element of the item as nullptr,
// effectively making the item the first element of the list
listPtr_Item->prev = nullptr;
// Update the list with the new data
*listPtr = listPtr_Item;
}
return 0;
}
int IMuseDigital::removeStreamZoneFromList(IMuseDigiStreamZone **listPtr, IMuseDigiStreamZone *listPtr_Item) {
IMuseDigiStreamZone *currentStrZone = *listPtr;
IMuseDigiStreamZone *nextStrZone;
if (listPtr_Item && currentStrZone) {
do {
if (currentStrZone == listPtr_Item)
break;
currentStrZone = currentStrZone->next;
} while (currentStrZone);
if (currentStrZone) {
nextStrZone = listPtr_Item->next;
if (nextStrZone)
nextStrZone->prev = listPtr_Item->prev;
if (listPtr_Item->prev) {
listPtr_Item->prev->next = listPtr_Item->next;
} else {
*listPtr = listPtr_Item->next;
}
listPtr_Item->prev = nullptr;
listPtr_Item->next = nullptr;
return 0;
} else {
debug(5, "IMuseDigital::removeStreamZoneFromList(): ERROR: item not on list");
return -3;
}
} else {
debug(5, "IMuseDigital::removeStreamZoneFromList(): ERROR: arguments might be null");
return -5;
}
}
int IMuseDigital::clampNumber(int value, int minValue, int maxValue) {
if (value < minValue)
return minValue;
if (value > maxValue)
return maxValue;
return value;
}
int IMuseDigital::clampTuning(int value, int minValue, int maxValue) {
if (minValue > value) {
value += (12 * ((minValue - value) + 11) / 12);
}
if (maxValue < value) {
value -= (12 * ((value - maxValue) + 11) / 12);
}
return value;
}
int IMuseDigital::checkHookId(int &trackHookId, int sampleHookId) {
if (sampleHookId) {
if (trackHookId == sampleHookId) {
trackHookId = 0;
return 0;
} else {
return -1;
}
} else if (trackHookId == 128) {
trackHookId = 0;
return -1;
} else {
return 0;
}
}
} // End of namespace Scumm

View File

@@ -0,0 +1,132 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
int IMuseDigital::waveInit() {
if (tracksInit())
return -1;
return 0;
}
int IMuseDigital::waveTerminate() {
return 0;
}
int IMuseDigital::wavePause() {
Common::StackLock lock(*_mutex);
tracksPause();
return 0;
}
int IMuseDigital::waveResume() {
Common::StackLock lock(*_mutex);
tracksResume();
return 0;
}
void IMuseDigital::waveSaveLoad(Common::Serializer &ser) {
Common::StackLock lock(*_mutex);
tracksSaveLoad(ser);
}
void IMuseDigital::waveUpdateGroupVolumes() {
Common::StackLock lock(*_mutex);
tracksSetGroupVol();
}
int IMuseDigital::waveStartSound(int soundId, int priority) {
Common::StackLock lock(*_mutex);
return tracksStartSound(soundId, priority, 0);
}
int IMuseDigital::waveStopSound(int soundId) {
Common::StackLock lock(*_mutex);
return tracksStopSound(soundId);
}
int IMuseDigital::waveStopAllSounds() {
Common::StackLock lock(*_mutex);
return tracksStopAllSounds();
}
int IMuseDigital::waveGetNextSound(int soundId) {
return tracksGetNextSound(soundId);
}
int IMuseDigital::waveSetParam(int soundId, int opcode, int value) {
Common::StackLock lock(*_mutex);
return tracksSetParam(soundId, opcode, value);
}
int IMuseDigital::waveGetParam(int soundId, int opcode) {
Common::StackLock lock(*_mutex);
return tracksGetParam(soundId, opcode);
}
int IMuseDigital::waveSetHook(int soundId, int hookId) {
return tracksSetHook(soundId, hookId);
}
int IMuseDigital::waveGetHook(int soundId) {
return tracksGetHook(soundId);
}
int IMuseDigital::waveStartStream(int soundId, int priority, int bufferId) {
if (soundId == 0)
return -1;
Common::StackLock lock(*_mutex);
return tracksStartSound(soundId, priority, bufferId);
}
int IMuseDigital::waveSwitchStream(int oldSoundId, int newSoundId, int fadeLengthMs, int fadeSyncFlag2, int fadeSyncFlag1) {
Common::StackLock lock(*_mutex);
return dispatchSwitchStream(oldSoundId, newSoundId, fadeLengthMs, fadeSyncFlag2, fadeSyncFlag1);
}
int IMuseDigital::waveSwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag) {
Common::StackLock lock(*_mutex);
return dispatchSwitchStream(oldSoundId, newSoundId, crossfadeBuffer, crossfadeBufferSize, vocLoopFlag);
}
int IMuseDigital::waveProcessStreams() {
Common::StackLock lock(*_mutex);
return streamerProcessStreams();
}
int IMuseDigital::waveQueryStream(int soundId, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused) {
Common::StackLock lock(*_mutex);
return tracksQueryStream(soundId, bufSize, criticalSize, freeSpace, paused);
}
int IMuseDigital::waveFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused) {
Common::StackLock lock(*_mutex);
return tracksFeedStream(soundId, srcBuf, sizeToFeed, paused);
}
int IMuseDigital::waveLipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height) {
return tracksLipSync(soundId, syncId, msPos, width, height);
}
} // End of namespace Scumm

View File

@@ -0,0 +1,195 @@
/* 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 "scumm/imuse_digi/dimuse_engine.h"
namespace Scumm {
int IMuseDigital::waveOutInit(waveOutParamsStruct *waveOutSettingsStruct) {
_waveOutBytesPerSample = 2;
_waveOutNumChannels = _mixer->getOutputStereo() ? 2 : 1;
_waveOutZeroLevel = 0;
_waveOutSampleRate = _internalSampleRate;
_waveOutPreferredFeedSize = _internalFeedSize;
_waveOutOutputBuffer = nullptr;
_waveOutMixBuffer = nullptr;
_waveOutLowLatencyOutputBuffer = nullptr;
if (!_lowLatencyMode || _isEarlyDiMUSE) {
// Nine buffers (waveOutPreferredFeedSize * 4 bytes each), two will be used for the mixer
_waveOutOutputBuffer = (uint8 *)malloc(_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
_waveOutMixBuffer = _waveOutOutputBuffer + (_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 7); // 8-th buffer
}
// Replicate another set of buffers for the low latency mode, we will use the previous ones for cutscenes if the mode is active
if (_lowLatencyMode) {
_waveOutLowLatencyOutputBuffer = (uint8 *)malloc(_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
}
// This information will be fed to the internal mixer during its initialization
waveOutSettingsStruct->bytesPerSample = _waveOutBytesPerSample * 8;
waveOutSettingsStruct->numChannels = _waveOutNumChannels;
waveOutSettingsStruct->mixBufSize = (_waveOutBytesPerSample * _waveOutNumChannels) * _waveOutPreferredFeedSize;
waveOutSettingsStruct->sizeSampleKB = 0;
waveOutSettingsStruct->mixBuf = _waveOutMixBuffer; // Note: in low latency mode this initialization is a dummy
// Init the buffers filling them with zero volume samples
if (!_lowLatencyMode || _isEarlyDiMUSE) {
memset(_waveOutOutputBuffer, _waveOutZeroLevel, _waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
}
if (_lowLatencyMode) {
memset(_waveOutLowLatencyOutputBuffer, _waveOutZeroLevel, _waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
}
_waveOutDisableWrite = 0;
return 0;
}
void IMuseDigital::waveOutWrite(uint8 **audioData, int &feedSize, int &sampleRate) {
uint8 *curBufferBlock;
if (_waveOutDisableWrite)
return;
feedSize = 0;
// A bit of context for what follows:
//
// In COMI, when entering certain rooms (e.g. barber shop in Plunder Island), the game
// sends a State music event followed immediately by a Sequence music event.
// If the iMUSE callback happens to run between these two commands, it can cause a brief
// audio glitch where a few milliseconds of the intermediate state music is heard
// before being replaced by the sequence music, with the fade in between canceled.
//
// To prevent this, we skip the very first audio callback after any music change
// by setting _waveOutXorTrigger to 1 in playComiMusic(). This gives the scripts
// enough time to send any follow-up sequence events before iMUSE processes
// the audio transition, ensuring the absence of glitches and most importantly
// no permanent audio delay on other audio feeds.
//
// This is very strongly inspired by what the disassembly does:
//
// The original DirectSound implementation uses the XOR flag combined with play
// cursor position checks that naturally skip audio processing cycles when the
// play cursor gets too close to the write cursor. In addition to this, the main
// portion of tracksCallback() is executed in loop until feedSide is finally 0.
// This creates the exact timing conditions where rapid music events have sufficient
// time to accumulate before iMUSE processes any audio transitions.
//
// We're not using DirectSound, so recreate this I could either simulate the
// play cursor handling shenanigans, or I could just do exactly what I have done :-)
if (_vm->_game.id == GID_CMI && _waveOutXorTrigger != 0) {
_waveOutXorTrigger = 0;
return;
}
if (!_isEarlyDiMUSE && _vm->_game.id == GID_DIG) {
_waveOutXorTrigger ^= 1;
if (!_waveOutXorTrigger)
return;
}
if (_mixer->isReady()) {
curBufferBlock = &_waveOutOutputBuffer[_waveOutPreferredFeedSize * _waveOutWriteIndex * _waveOutBytesPerSample * _waveOutNumChannels];
*audioData = curBufferBlock;
sampleRate = _waveOutSampleRate;
feedSize = _waveOutPreferredFeedSize;
_waveOutWriteIndex = (_waveOutWriteIndex + 1) % 7;
byte *ptr = (byte *)malloc(_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
memcpy(ptr, curBufferBlock, _outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
_internalMixer->getStream(-1)->queueBuffer(ptr,
_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels,
DisposeAfterUse::YES,
waveOutGetStreamFlags());
}
}
int IMuseDigital::waveOutDeinit() {
_waveOutDisableWrite = 1;
return 0;
}
void IMuseDigital::waveOutCallback() {
Common::StackLock lock(*_mutex);
if (_lowLatencyMode) {
tracksLowLatencyCallback();
} else {
tracksCallback();
}
}
byte IMuseDigital::waveOutGetStreamFlags() {
byte flags = Audio::FLAG_16BITS;
if (_mixer->getOutputStereo()) {
flags |= Audio::FLAG_STEREO;
}
#ifdef SCUMM_LITTLE_ENDIAN
flags |= Audio::FLAG_LITTLE_ENDIAN;
#endif
return flags;
}
void IMuseDigital::waveOutLowLatencyWrite(uint8 **audioData, int &feedSize, int &sampleRate, int idx) {
uint8 *curBufferBlock;
if (_waveOutDisableWrite)
return;
if (!_isEarlyDiMUSE && _vm->_game.id == GID_DIG) {
_waveOutXorTrigger ^= 1;
if (!_waveOutXorTrigger)
return;
}
feedSize = 0;
if (_mixer->isReady()) {
curBufferBlock = &_waveOutLowLatencyOutputBuffer[_waveOutPreferredFeedSize * idx * _waveOutBytesPerSample * _waveOutNumChannels];
*audioData = curBufferBlock;
sampleRate = _waveOutSampleRate;
feedSize = _waveOutPreferredFeedSize;
byte *ptr = (byte *)malloc(_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
memcpy(ptr, curBufferBlock, _outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
_internalMixer->getStream(idx)->queueBuffer(ptr,
_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels,
DisposeAfterUse::YES,
waveOutGetStreamFlags());
}
}
void IMuseDigital::waveOutEmptyBuffer(int idx) {
// This is necessary in low latency mode to clean-up the buffers of stale/finished sounds
int bufferSize = _waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize;
memset(&_waveOutLowLatencyOutputBuffer[bufferSize * idx], _waveOutZeroLevel, bufferSize);
}
} // End of namespace Scumm