/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ */ #include "common/scummsys.h" #include "common/util.h" #include "sound/flac.h" #include "sound/voc.h" #include "sound/vorbis.h" #include "sound/mp3.h" #include "scumm/scumm.h" #include "scumm/util.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/imuse_digi/dimuse_sndmgr.h" #include "scumm/imuse_digi/dimuse_bndmgr.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(); assert(_cacheBundleDir); BundleCodecs::initializeImcTables(); } ImuseDigiSndMgr::~ImuseDigiSndMgr() { for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) { closeSound(&_sounds[l]); } delete _cacheBundleDir; } void ImuseDigiSndMgr::countElements(byte *ptr, int &numRegions, int &numJumps, int &numSyncs) { uint32 tag; int32 size = 0; do { tag = READ_BE_UINT32(ptr); ptr += 4; switch (tag) { case MKID_BE('TEXT'): case MKID_BE('STOP'): case MKID_BE('FRMT'): case MKID_BE('DATA'): size = READ_BE_UINT32(ptr); ptr += size + 4; break; case MKID_BE('REGN'): numRegions++; size = READ_BE_UINT32(ptr); ptr += size + 4; break; case MKID_BE('JUMP'): numJumps++; size = READ_BE_UINT32(ptr); ptr += size + 4; break; case MKID_BE('SYNC'): numSyncs++; size = READ_BE_UINT32(ptr); ptr += size + 4; break; default: error("ImuseDigiSndMgr::countElements() Unknown sfx header '%s'", tag2str(tag)); } } while (tag != MKID_BE('DATA')); } void ImuseDigiSndMgr::prepareSoundFromRMAP(Common::File *file, SoundDesc *sound, int32 offset, int32 size) { int l; file->seek(offset, SEEK_SET); uint32 tag = file->readUint32BE(); assert(tag == MKID_BE('RMAP')); int32 version = file->readUint32BE(); if (version != 2) { error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 2, but it's: %d.", version); } sound->bits = file->readUint32BE(); sound->freq = file->readUint32BE(); sound->channels = file->readUint32BE(); sound->numRegions = file->readUint32BE(); sound->numJumps = file->readUint32BE(); sound->numSyncs = file->readUint32BE(); sound->region = new Region[sound->numRegions]; assert(sound->region); sound->jump = new Jump[sound->numJumps]; assert(sound->jump); sound->sync = new Sync[sound->numSyncs]; assert(sound->sync); for (l = 0; l < sound->numRegions; l++) { sound->region[l].offset = file->readUint32BE(); sound->region[l].length = file->readUint32BE(); } for (l = 0; l < sound->numJumps; l++) { sound->jump[l].offset = file->readUint32BE(); sound->jump[l].dest = file->readUint32BE(); sound->jump[l].hookId = file->readUint32BE(); sound->jump[l].fadeDelay = file->readUint32BE(); } for (l = 0; l < sound->numSyncs; l++) { sound->sync[l].size = file->readUint32BE(); sound->sync[l].ptr = (byte *)malloc(sound->sync[l].size); file->read(sound->sync[l].ptr, sound->sync[l].size); } } void ImuseDigiSndMgr::prepareSound(byte *ptr, SoundDesc *sound) { if (READ_BE_UINT32(ptr) == MKID_BE('Crea')) { bool quit = false; int len; int32 offset = READ_LE_UINT16(ptr + 20); int16 code = READ_LE_UINT16(ptr + 24); sound->numRegions = 0; sound->region = new Region[70]; assert(sound->region); sound->numJumps = 0; sound->jump = new Jump[1]; assert(sound->jump); sound->numSyncs = 0; sound->resPtr = ptr; sound->bits = 8; sound->channels = 1; while (!quit) { len = READ_LE_UINT32(ptr + offset); code = len & 0xFF; if ((code != 0) && (code != 1) && (code != 6) && (code != 7)) { // try again with 2 bytes forward (workaround for some FT sounds (ex.362, 363) offset += 2; len = READ_LE_UINT32(ptr + offset); code = len & 0xFF; if ((code != 0) && (code != 1) && (code != 6) && (code != 7)) { error("Invalid code in VOC file : %d", code); } } offset += 4; len >>= 8; switch (code) { case 0: quit = true; break; case 1: { int time_constant = ptr[offset]; offset += 2; len -= 2; sound->freq = Audio::getSampleRateFromVOCRate(time_constant); sound->region[sound->numRegions].offset = offset; sound->region[sound->numRegions].length = len; sound->numRegions++; } break; case 6: // begin of loop sound->jump[0].dest = offset + 8; sound->jump[0].hookId = 0; sound->jump[0].fadeDelay = 0; break; case 7: // end of loop sound->jump[0].offset = offset - 4; sound->numJumps++; sound->region[sound->numRegions].offset = offset - 4; sound->region[sound->numRegions].length = 0; sound->numRegions++; break; default: error("Invalid code in VOC file : %d", code); quit = true; break; } offset += len; } } else if (READ_BE_UINT32(ptr) == MKID_BE('iMUS')) { uint32 tag; int32 size = 0; byte *s_ptr = ptr; ptr += 16; int curIndexRegion = 0; int curIndexJump = 0; int curIndexSync = 0; sound->numRegions = 0; sound->numJumps = 0; sound->numSyncs = 0; countElements(ptr, sound->numRegions, sound->numJumps, sound->numSyncs); sound->region = new Region[sound->numRegions]; assert(sound->region); sound->jump = new Jump[sound->numJumps]; assert(sound->jump); sound->sync = new Sync[sound->numSyncs]; assert(sound->sync); do { tag = READ_BE_UINT32(ptr); ptr += 4; switch (tag) { case MKID_BE('FRMT'): ptr += 12; sound->bits = READ_BE_UINT32(ptr); ptr += 4; sound->freq = READ_BE_UINT32(ptr); ptr += 4; sound->channels = READ_BE_UINT32(ptr); ptr += 4; break; case MKID_BE('TEXT'): case MKID_BE('STOP'): size = READ_BE_UINT32(ptr); ptr += size + 4; break; case MKID_BE('REGN'): ptr += 4; sound->region[curIndexRegion].offset = READ_BE_UINT32(ptr); ptr += 4; sound->region[curIndexRegion].length = READ_BE_UINT32(ptr); ptr += 4; curIndexRegion++; break; case MKID_BE('JUMP'): ptr += 4; sound->jump[curIndexJump].offset = READ_BE_UINT32(ptr); ptr += 4; sound->jump[curIndexJump].dest = READ_BE_UINT32(ptr); ptr += 4; sound->jump[curIndexJump].hookId = READ_BE_UINT32(ptr); ptr += 4; sound->jump[curIndexJump].fadeDelay = READ_BE_UINT32(ptr); ptr += 4; curIndexJump++; break; case MKID_BE('SYNC'): size = READ_BE_UINT32(ptr); ptr += 4; sound->sync[curIndexSync].size = size; sound->sync[curIndexSync].ptr = (byte *)malloc(size); memcpy(sound->sync[curIndexSync].ptr, ptr, size); curIndexSync++; ptr += size; break; case MKID_BE('DATA'): ptr += 4; break; default: error("ImuseDigiSndMgr::prepareSound(%d/%s) Unknown sfx header '%s'", sound->soundId, sound->name, tag2str(tag)); } } while (tag != MKID_BE('DATA')); sound->offsetData = ptr - s_ptr; } else { error("ImuseDigiSndMgr::prepareSound(): Unknown sound format"); } } ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::allocSlot() { for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) { if (!_sounds[l].inUse) { _sounds[l].inUse = true; return &_sounds[l]; } } return NULL; } bool ImuseDigiSndMgr::openMusicBundle(SoundDesc *sound, int disk) { bool result = false; sound->bundle = new BundleMgr(_cacheBundleDir); assert(sound->bundle); if (_vm->_game.id == GID_CMI) { if (_vm->_game.features & GF_DEMO) { result = sound->bundle->open("music.bun", sound->compressed); } else { char musicfile[20]; if (disk == -1) disk = _vm->VAR(_vm->VAR_CURRENTDISK); sprintf(musicfile, "musdisk%d.bun", disk); // if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) { // _vm->_imuseDigital->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0); // _vm->_imuseDigital->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0); // _vm->_imuseDigital->stopAllSounds(); // sound->bundle->closeFile(); // } result = sound->bundle->open(musicfile, sound->compressed, true); // 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", sound->compressed, true); 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; sound->bundle = new BundleMgr(_cacheBundleDir); assert(sound->bundle); if (_vm->_game.id == GID_CMI) { if (_vm->_game.features & GF_DEMO) { result = sound->bundle->open("voice.bun", sound->compressed); } else { char voxfile[20]; if (disk == -1) disk = _vm->VAR(_vm->VAR_CURRENTDISK); sprintf(voxfile, "voxdisk%d.bun", disk); // if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) { // _vm->_imuseDigital->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0); // _vm->_imuseDigital->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0); // _vm->_imuseDigital->stopAllSounds(); // sound->bundle->closeFile(); // } result = sound->bundle->open(voxfile, sound->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", sound->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 = NULL; switch (soundType) { case IMUSE_RESOURCE: assert(soundName[0] == 0); // Paranoia check _vm->ensureResourceLoaded(rtSound, soundId); _vm->_res->lock(rtSound, soundId); ptr = _vm->getResourceAddress(rtSound, soundId); if (ptr == NULL) { closeSound(sound); return NULL; } sound->resPtr = ptr; 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 NULL; } if (sound->compressed) { char fileName[24]; int32 offset = 0, size = 0; sprintf(fileName, "%s.map", soundName); Common::File *rmapFile = sound->bundle->getFile(fileName, offset, size); if (!rmapFile) { closeSound(sound); return NULL; } prepareSoundFromRMAP(rmapFile, sound, offset, size); strcpy(sound->name, soundName); sound->soundId = soundId; sound->type = soundType; sound->volGroupId = volGroupId; sound->disk = _disk; return sound; } else if (soundName[0] == 0) { if (sound->bundle->decompressSampleByIndex(soundId, 0, 0x2000, &ptr, 0, header_outside) == 0 || ptr == NULL) { closeSound(sound); return NULL; } } else { if (sound->bundle->decompressSampleByName(soundName, 0, 0x2000, &ptr, header_outside) == 0 || ptr == NULL) { closeSound(sound); return NULL; } } sound->resPtr = 0; break; default: error("ImuseDigiSndMgr::openSound() Unknown soundType %d (trying to load sound %d)", soundType, soundId); } strcpy(sound->name, soundName); sound->soundId = soundId; sound->type = soundType; sound->volGroupId = volGroupId; sound->disk = _disk; prepareSound(ptr, sound); if ((soundType == IMUSE_BUNDLE) && !sound->compressed) { free(ptr); } return sound; } void ImuseDigiSndMgr::closeSound(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); 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->compressedStream; delete soundDesc->bundle; for (int r = 0; r < soundDesc->numSyncs; r++) free(soundDesc->sync[r].ptr); delete[] soundDesc->region; delete[] soundDesc->jump; delete[] soundDesc->sync; memset(soundDesc, 0, sizeof(SoundDesc)); } ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::cloneSound(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); return openSound(soundDesc->soundId, soundDesc->name, soundDesc->type, soundDesc->volGroupId, soundDesc->disk); } 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; } bool ImuseDigiSndMgr::isSndDataExtComp(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); return soundDesc->compressed; } int ImuseDigiSndMgr::getFreq(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); return soundDesc->freq; } int ImuseDigiSndMgr::getBits(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); return soundDesc->bits; } int ImuseDigiSndMgr::getChannels(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); return soundDesc->channels; } bool ImuseDigiSndMgr::isEndOfRegion(SoundDesc *soundDesc, int region) { assert(checkForProperHandle(soundDesc)); assert(region >= 0 && region < soundDesc->numRegions); return soundDesc->endFlag; } int ImuseDigiSndMgr::getNumRegions(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); return soundDesc->numRegions; } int ImuseDigiSndMgr::getNumJumps(SoundDesc *soundDesc) { assert(checkForProperHandle(soundDesc)); return soundDesc->numJumps; } int ImuseDigiSndMgr::getRegionOffset(SoundDesc *soundDesc, int region) { debug(5, "getRegionOffset() region:%d", region); assert(checkForProperHandle(soundDesc)); assert(region >= 0 && region < soundDesc->numRegions); return soundDesc->region[region].offset; } int ImuseDigiSndMgr::getJumpIdByRegionAndHookId(SoundDesc *soundDesc, int region, int hookId) { debug(5, "getJumpIdByRegionAndHookId() region:%d, hookId:%d", region, hookId); assert(checkForProperHandle(soundDesc)); assert(region >= 0 && region < soundDesc->numRegions); int32 offset = soundDesc->region[region].offset; for (int l = 0; l < soundDesc->numJumps; l++) { if (offset == soundDesc->jump[l].offset) { if (soundDesc->jump[l].hookId == hookId) return l; } } return -1; } void ImuseDigiSndMgr::getSyncSizeAndPtrById(SoundDesc *soundDesc, int number, int32 &sync_size, byte **sync_ptr) { assert(checkForProperHandle(soundDesc)); assert(number >= 0); if (number < soundDesc->numSyncs) { sync_size = soundDesc->sync[number].size; *sync_ptr = soundDesc->sync[number].ptr; } else { sync_size = 0; *sync_ptr = NULL; } } int ImuseDigiSndMgr::getRegionIdByJumpId(SoundDesc *soundDesc, int jumpId) { debug(5, "getRegionIdByJumpId() jumpId:%d", jumpId); assert(checkForProperHandle(soundDesc)); assert(jumpId >= 0 && jumpId < soundDesc->numJumps); int32 dest = soundDesc->jump[jumpId].dest; for (int l = 0; l < soundDesc->numRegions; l++) { if (dest == soundDesc->region[l].offset) { return l; } } return -1; } int ImuseDigiSndMgr::getJumpHookId(SoundDesc *soundDesc, int number) { debug(5, "getJumpHookId() number:%d", number); assert(checkForProperHandle(soundDesc)); assert(number >= 0 && number < soundDesc->numJumps); return soundDesc->jump[number].hookId; } int ImuseDigiSndMgr::getJumpFade(SoundDesc *soundDesc, int number) { debug(5, "getJumpFade() number:%d", number); assert(checkForProperHandle(soundDesc)); assert(number >= 0 && number < soundDesc->numJumps); return soundDesc->jump[number].fadeDelay; } int32 ImuseDigiSndMgr::getDataFromRegion(SoundDesc *soundDesc, int region, byte **buf, int32 offset, int32 size) { debug(5, "getDataFromRegion() region:%d, offset:%d, size:%d, numRegions:%d", region, offset, size, soundDesc->numRegions); assert(checkForProperHandle(soundDesc)); assert(buf && offset >= 0 && size >= 0); assert(region >= 0 && region < soundDesc->numRegions); int32 region_offset = soundDesc->region[region].offset; int32 region_length = soundDesc->region[region].length; int32 offset_data = soundDesc->offsetData; int32 start = region_offset - offset_data; if (offset + size + offset_data > region_length) { size = region_length - offset; soundDesc->endFlag = true; } else { soundDesc->endFlag = false; } int header_size = soundDesc->offsetData; bool header_outside = ((_vm->_game.id == GID_CMI) && !(_vm->_game.features & GF_DEMO)); if ((soundDesc->bundle) && (!soundDesc->compressed)) { size = soundDesc->bundle->decompressSampleByCurIndex(start + offset, size, buf, header_size, header_outside); } else if (soundDesc->resPtr) { *buf = new byte[size]; assert(*buf); memcpy(*buf, soundDesc->resPtr + start + offset + header_size, size); } else if ((soundDesc->bundle) && (soundDesc->compressed)) { *buf = new byte[size]; assert(*buf); char fileName[24]; sprintf(fileName, "%s_reg%03d", soundDesc->name, region); if (scumm_stricmp(fileName, soundDesc->lastFileName) != 0) { int32 offs = 0, len = 0; Common::File *cmpFile; uint8 soundMode = 0; sprintf(fileName, "%s_reg%03d.fla", soundDesc->name, region); cmpFile = soundDesc->bundle->getFile(fileName, offs, len); if (len) { #ifndef USE_FLAC error("FLAC library compiled support needed!"); #endif soundMode = 3; } if (!len) { sprintf(fileName, "%s_reg%03d.ogg", soundDesc->name, region); cmpFile = soundDesc->bundle->getFile(fileName, offs, len); if (len) { #ifndef USE_VORBIS error("Vorbis library compiled support needed!"); #endif soundMode = 2; } } if (!len) { sprintf(fileName, "%s_reg%03d.mp3", soundDesc->name, region); cmpFile = soundDesc->bundle->getFile(fileName, offs, len); if (len) { #ifndef USE_MAD error("Mad library compiled support needed!"); #endif soundMode = 1; } } assert(len); if (!soundDesc->compressedStream) { Common::MemoryReadStream *tmp = cmpFile->readStream(len); assert(tmp); #ifdef USE_FLAC if (soundMode == 3) soundDesc->compressedStream = Audio::makeFlacStream(tmp, true); #endif #ifdef USE_VORBIS if (soundMode == 2) soundDesc->compressedStream = Audio::makeVorbisStream(tmp, true); #endif #ifdef USE_MAD if (soundMode == 1) soundDesc->compressedStream = Audio::makeMP3Stream(tmp, true); #endif assert(soundDesc->compressedStream); } strcpy(soundDesc->lastFileName, fileName); } size = soundDesc->compressedStream->readBuffer((int16 *)*buf, size / 2) * 2; if (soundDesc->compressedStream->endOfData()) { delete soundDesc->compressedStream; soundDesc->compressedStream = NULL; soundDesc->lastFileName[0] = 0; } } return size; } } // End of namespace Scumm