diff options
Diffstat (limited to 'engines/sci/resource_audio.cpp')
-rw-r--r-- | engines/sci/resource_audio.cpp | 783 |
1 files changed, 783 insertions, 0 deletions
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp new file mode 100644 index 0000000000..a25505fe47 --- /dev/null +++ b/engines/sci/resource_audio.cpp @@ -0,0 +1,783 @@ +/* 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$ + * + */ + +// Resource library + +#include "common/archive.h" +#include "common/file.h" + +#include "sci/resource.h" +#include "sci/resource_intern.h" +#include "sci/util.h" + +namespace Sci { + +AudioVolumeResourceSource::AudioVolumeResourceSource(ResourceManager *resMan, const Common::String &name, ResourceSource *map, int volNum) + : VolumeResourceSource(name, map, volNum, kSourceAudioVolume) { + + _audioCompressionType = 0; + _audioCompressionOffsetMapping = NULL; + + /* + * Check if this audio volume got compressed by our tool. If that is the + * case, set _audioCompressionType and read in the offset translation + * table for later usage. + */ + + Common::SeekableReadStream *fileStream = getVolumeFile(resMan, 0); + if (!fileStream) + return; + + fileStream->seek(0, SEEK_SET); + uint32 compressionType = fileStream->readUint32BE(); + switch (compressionType) { + case MKID_BE('MP3 '): + case MKID_BE('OGG '): + case MKID_BE('FLAC'): + // Detected a compressed audio volume + _audioCompressionType = compressionType; + // Now read the whole offset mapping table for later usage + int32 recordCount = fileStream->readUint32LE(); + if (!recordCount) + error("compressed audio volume doesn't contain any entries!"); + int32 *offsetMapping = new int32[(recordCount + 1) * 2]; + _audioCompressionOffsetMapping = offsetMapping; + for (int recordNo = 0; recordNo < recordCount; recordNo++) { + *offsetMapping++ = fileStream->readUint32LE(); + *offsetMapping++ = fileStream->readUint32LE(); + } + // Put ending zero + *offsetMapping++ = 0; + *offsetMapping++ = fileStream->size(); + } + + if (_resourceFile) + delete fileStream; +} + +bool Resource::loadFromWaveFile(Common::SeekableReadStream *file) { + data = new byte[size]; + + uint32 really_read = file->read(data, size); + if (really_read != size) + error("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size); + + _status = kResStatusAllocated; + return true; +} + +bool Resource::loadFromAudioVolumeSCI11(Common::SeekableReadStream *file) { + // Check for WAVE files here + uint32 riffTag = file->readUint32BE(); + if (riffTag == MKID_BE('RIFF')) { + _headerSize = 0; + size = file->readUint32LE() + 8; + file->seek(-8, SEEK_CUR); + return loadFromWaveFile(file); + } + file->seek(-4, SEEK_CUR); + + ResourceType type = _resMan->convertResType(file->readByte()); + if (((getType() == kResourceTypeAudio || getType() == kResourceTypeAudio36) && (type != kResourceTypeAudio)) + || ((getType() == kResourceTypeSync || getType() == kResourceTypeSync36) && (type != kResourceTypeSync))) { + warning("Resource type mismatch loading %s", _id.toString().c_str()); + unalloc(); + return false; + } + + _headerSize = file->readByte(); + + if (type == kResourceTypeAudio) { + if (_headerSize != 7 && _headerSize != 11 && _headerSize != 12) { + warning("Unsupported audio header"); + unalloc(); + return false; + } + + if (_headerSize != 7) { // Size is defined already from the map + // Load sample size + file->seek(7, SEEK_CUR); + size = file->readUint32LE(); + // Adjust offset to point at the header data again + file->seek(-11, SEEK_CUR); + } + } + + return loadPatch(file); +} + +bool Resource::loadFromAudioVolumeSCI1(Common::SeekableReadStream *file) { + data = new byte[size]; + + if (data == NULL) { + error("Can't allocate %d bytes needed for loading %s", size, _id.toString().c_str()); + } + + unsigned int really_read = file->read(data, size); + if (really_read != size) + warning("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size); + + _status = kResStatusAllocated; + return true; +} + +void ResourceManager::addNewGMPatch(SciGameId gameId) { + Common::String gmPatchFile; + + switch (gameId) { + case GID_ECOQUEST: + gmPatchFile = "ECO1GM.PAT"; + break; + case GID_HOYLE3: + gmPatchFile = "HOY3GM.PAT"; + break; + case GID_LSL1: + gmPatchFile = "LL1_GM.PAT"; + break; + case GID_LSL5: + gmPatchFile = "LL5_GM.PAT"; + break; + case GID_LONGBOW: + gmPatchFile = "ROBNGM.PAT"; + break; + case GID_SQ1: + gmPatchFile = "SQ1_GM.PAT"; + break; + case GID_SQ4: + gmPatchFile = "SQ4_GM.PAT"; + break; + case GID_FAIRYTALES: + gmPatchFile = "TALEGM.PAT"; + break; + default: + break; + } + + if (!gmPatchFile.empty() && Common::File::exists(gmPatchFile)) { + ResourceSource *psrcPatch = new PatchResourceSource(gmPatchFile); + processPatch(psrcPatch, kResourceTypePatch, 4); + } +} + +void ResourceManager::processWavePatch(ResourceId resourceId, Common::String name) { + ResourceSource *resSrc = new WaveResourceSource(name); + Common::File file; + file.open(name); + + updateResource(resourceId, resSrc, file.size()); + + debugC(1, kDebugLevelResMan, "Patching %s - OK", name.c_str()); +} + +void ResourceManager::readWaveAudioPatches() { + // Here we do check for SCI1.1+ so we can patch wav files in as audio resources + Common::ArchiveMemberList files; + SearchMan.listMatchingMembers(files, "*.wav"); + + for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { + Common::String name = (*x)->getName(); + + if (isdigit(name[0])) + processWavePatch(ResourceId(kResourceTypeAudio, atoi(name.c_str())), name); + } +} + +void ResourceManager::removeAudioResource(ResourceId resId) { + // Remove resource, unless it was loaded from a patch + if (_resMap.contains(resId)) { + Resource *res = _resMap.getVal(resId); + + if (res->_source->getSourceType() == kSourceAudioVolume) { + if (res->_status == kResStatusLocked) { + warning("Failed to remove resource %s (still in use)", resId.toString().c_str()); + } else { + if (res->_status == kResStatusEnqueued) + removeFromLRU(res); + + _resMap.erase(resId); + delete res; + } + } + } +} + +// Early SCI1.1 65535.MAP structure (uses RESOURCE.AUD): +// ========= +// 6-byte entries: +// w nEntry +// dw offset + +// Late SCI1.1 65535.MAP structure (uses RESOURCE.SFX): +// ========= +// 5-byte entries: +// w nEntry +// tb offset (cumulative) + +// QFG3 Demo 0.MAP structure: +// ========= +// 10-byte entries: +// w nEntry +// dw offset +// dw size + +// LB2 Floppy/Mother Goose SCI1.1 0.MAP structure: +// ========= +// 8-byte entries: +// w nEntry +// w 0xffff +// dw offset + +// Early SCI1.1 MAP structure: +// =============== +// 10-byte entries: +// b noun +// b verb +// b cond +// b seq +// dw offset +// w syncSize + syncAscSize + +// Late SCI1.1 MAP structure: +// =============== +// Header: +// dw baseOffset +// Followed by 7 or 11-byte entries: +// b noun +// b verb +// b cond +// b seq +// tb cOffset (cumulative offset) +// w syncSize (iff seq has bit 7 set) +// w syncAscSize (iff seq has bit 6 set) + +int ResourceManager::readAudioMapSCI11(ResourceSource *map) { + uint32 offset = 0; + Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_volumeNumber), false); + + if (!mapRes) { + warning("Failed to open %i.MAP", map->_volumeNumber); + return SCI_ERROR_RESMAP_NOT_FOUND; + } + + ResourceSource *src = findVolume(map, 0); + + if (!src) + return SCI_ERROR_NO_RESOURCE_FILES_FOUND; + + byte *ptr = mapRes->data; + + // Heuristic to detect entry size + uint32 entrySize = 0; + for (int i = mapRes->size - 1; i >= 0; --i) { + if (ptr[i] == 0xff) + entrySize++; + else + break; + } + + if (map->_volumeNumber == 65535) { + while (ptr < mapRes->data + mapRes->size) { + uint16 n = READ_LE_UINT16(ptr); + ptr += 2; + + if (n == 0xffff) + break; + + if (entrySize == 6) { + offset = READ_LE_UINT32(ptr); + ptr += 4; + } else { + offset += READ_LE_UINT24(ptr); + ptr += 3; + } + + addResource(ResourceId(kResourceTypeAudio, n), src, offset); + } + } else if (map->_volumeNumber == 0 && entrySize == 10 && ptr[3] == 0) { + // QFG3 demo format + // ptr[3] would be 'seq' in the normal format and cannot possibly be 0 + while (ptr < mapRes->data + mapRes->size) { + uint16 n = READ_BE_UINT16(ptr); + ptr += 2; + + if (n == 0xffff) + break; + + offset = READ_LE_UINT32(ptr); + ptr += 4; + uint32 size = READ_LE_UINT32(ptr); + ptr += 4; + + addResource(ResourceId(kResourceTypeAudio, n), src, offset, size); + } + } else if (map->_volumeNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) { + // LB2 Floppy/Mother Goose SCI1.1 format + Common::SeekableReadStream *stream = getVolumeFile(src); + + while (ptr < mapRes->data + mapRes->size) { + uint16 n = READ_LE_UINT16(ptr); + ptr += 4; + + if (n == 0xffff) + break; + + offset = READ_LE_UINT32(ptr); + ptr += 4; + + // The size is not stored in the map and the entries have no order. + // We need to dig into the audio resource in the volume to get the size. + stream->seek(offset + 1); + byte headerSize = stream->readByte(); + assert(headerSize == 11 || headerSize == 12); + + stream->skip(5); + uint32 size = stream->readUint32LE() + headerSize + 2; + + addResource(ResourceId(kResourceTypeAudio, n), src, offset, size); + } + } else { + bool isEarly = (entrySize != 11); + + if (!isEarly) { + offset = READ_LE_UINT32(ptr); + ptr += 4; + } + + while (ptr < mapRes->data + mapRes->size) { + uint32 n = READ_BE_UINT32(ptr); + int syncSize = 0; + ptr += 4; + + if (n == 0xffffffff) + break; + + if (isEarly) { + offset = READ_LE_UINT32(ptr); + ptr += 4; + } else { + offset += READ_LE_UINT24(ptr); + ptr += 3; + } + + if (isEarly || (n & 0x80)) { + syncSize = READ_LE_UINT16(ptr); + ptr += 2; + + if (syncSize > 0) + addResource(ResourceId(kResourceTypeSync36, map->_volumeNumber, n & 0xffffff3f), src, offset, syncSize); + } + + if (n & 0x40) { + // This seems to define the size of raw lipsync data (at least + // in kq6), may also just be general appended data. + syncSize += READ_LE_UINT16(ptr); + ptr += 2; + } + + addResource(ResourceId(kResourceTypeAudio36, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize); + } + } + + return 0; +} + +// AUDIOnnn.MAP contains 10-byte entries: +// Early format: +// w 5 bits resource type and 11 bits resource number +// dw 7 bits volume number and 25 bits offset +// dw size +// Later format: +// w nEntry +// dw offset+volume (as in resource.map) +// dw size +// ending with 10 0xFFs +int ResourceManager::readAudioMapSCI1(ResourceSource *map, bool unload) { + Common::File file; + + if (!file.open(map->getLocationName())) + return SCI_ERROR_RESMAP_NOT_FOUND; + + bool oldFormat = (file.readUint16LE() >> 11) == kResourceTypeAudio; + file.seek(0); + + while (1) { + uint16 n = file.readUint16LE(); + uint32 offset = file.readUint32LE(); + uint32 size = file.readUint32LE(); + + if (file.eos() || file.err()) { + warning("Error while reading %s", map->getLocationName().c_str()); + return SCI_ERROR_RESMAP_NOT_FOUND; + } + + if (n == 0xffff) + break; + + byte volume_nr; + + if (oldFormat) { + n &= 0x07ff; // Mask out resource type + volume_nr = offset >> 25; // most significant 7 bits + offset &= 0x01ffffff; // least significant 25 bits + } else { + volume_nr = offset >> 28; // most significant 4 bits + offset &= 0x0fffffff; // least significant 28 bits + } + + ResourceSource *src = findVolume(map, volume_nr); + + if (src) { + if (unload) + removeAudioResource(ResourceId(kResourceTypeAudio, n)); + else + addResource(ResourceId(kResourceTypeAudio, n), src, offset, size); + } else { + warning("Failed to find audio volume %i", volume_nr); + } + } + + return 0; +} + +void ResourceManager::setAudioLanguage(int language) { + if (_audioMapSCI1) { + if (_audioMapSCI1->_volumeNumber == language) { + // This language is already loaded + return; + } + + // We already have a map loaded, so we unload it first + readAudioMapSCI1(_audioMapSCI1, true); + + // Remove all volumes that use this map from the source list + Common::List<ResourceSource *>::iterator it = _sources.begin(); + while (it != _sources.end()) { + ResourceSource *src = *it; + if (src->findVolume(_audioMapSCI1, src->_volumeNumber)) { + it = _sources.erase(it); + delete src; + } else { + ++it; + } + } + + // Remove the map itself from the source list + _sources.remove(_audioMapSCI1); + delete _audioMapSCI1; + + _audioMapSCI1 = NULL; + } + + char filename[9]; + snprintf(filename, 9, "AUDIO%03d", language); + + Common::String fullname = Common::String(filename) + ".MAP"; + if (!Common::File::exists(fullname)) { + warning("No audio map found for language %i", language); + return; + } + + _audioMapSCI1 = addSource(new ExtAudioMapResourceSource(fullname, language)); + + // Search for audio volumes for this language and add them to the source list + Common::ArchiveMemberList files; + SearchMan.listMatchingMembers(files, Common::String(filename) + ".0??"); + for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { + const Common::String name = (*x)->getName(); + const char *dot = strrchr(name.c_str(), '.'); + int number = atoi(dot + 1); + + addSource(new AudioVolumeResourceSource(this, name, _audioMapSCI1, number)); + } + + scanNewSources(); +} + +int ResourceManager::getAudioLanguage() const { + return (_audioMapSCI1 ? _audioMapSCI1->_volumeNumber : 0); +} + +SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVersion soundVersion) : _resMan(resMan), _soundVersion(soundVersion) { + Resource *resource = _resMan->findResource(ResourceId(kResourceTypeSound, resourceNr), true); + int trackNr, channelNr; + if (!resource) + return; + + _innerResource = resource; + + byte *data, *data2; + byte *dataEnd; + Channel *channel, *sampleChannel; + + switch (_soundVersion) { + case SCI_VERSION_0_EARLY: + case SCI_VERSION_0_LATE: + // SCI0 only has a header of 0x11/0x21 byte length and the actual midi track follows afterwards + _trackCount = 1; + _tracks = new Track[_trackCount]; + _tracks->digitalChannelNr = -1; + _tracks->type = 0; // Not used for SCI0 + _tracks->channelCount = 1; + // Digital sample data included? -> Add an additional channel + if (resource->data[0] == 2) + _tracks->channelCount++; + _tracks->channels = new Channel[_tracks->channelCount]; + memset(_tracks->channels, 0, sizeof(Channel) * _tracks->channelCount); + channel = &_tracks->channels[0]; + if (_soundVersion == SCI_VERSION_0_EARLY) { + channel->data = resource->data + 0x11; + channel->size = resource->size - 0x11; + } else { + channel->data = resource->data + 0x21; + channel->size = resource->size - 0x21; + } + if (_tracks->channelCount == 2) { + // Digital sample data included + _tracks->digitalChannelNr = 1; + sampleChannel = &_tracks->channels[1]; + // we need to find 0xFC (channel terminator) within the data + data = channel->data; + dataEnd = channel->data + channel->size; + while ((data < dataEnd) && (*data != 0xfc)) + data++; + // Skip any following 0xFCs as well + while ((data < dataEnd) && (*data == 0xfc)) + data++; + // Now adjust channels accordingly + sampleChannel->data = data; + sampleChannel->size = channel->size - (data - channel->data); + channel->size = data - channel->data; + // Read sample header information + //Offset 14 in the header contains the frequency as a short integer. Offset 32 contains the sample length, also as a short integer. + _tracks->digitalSampleRate = READ_LE_UINT16(sampleChannel->data + 14); + _tracks->digitalSampleSize = READ_LE_UINT16(sampleChannel->data + 32); + _tracks->digitalSampleStart = 0; + _tracks->digitalSampleEnd = 0; + sampleChannel->data += 44; // Skip over header + sampleChannel->size -= 44; + } + break; + + case SCI_VERSION_1_EARLY: + case SCI_VERSION_1_LATE: + case SCI_VERSION_2_1: + data = resource->data; + // Count # of tracks + _trackCount = 0; + while ((*data++) != 0xFF) { + _trackCount++; + while (*data != 0xFF) + data += 6; + data++; + } + _tracks = new Track[_trackCount]; + data = resource->data; + + byte channelCount; + + for (trackNr = 0; trackNr < _trackCount; trackNr++) { + // Track info starts with track type:BYTE + // Then the channel information gets appended Unknown:WORD, ChannelOffset:WORD, ChannelSize:WORD + // 0xFF:BYTE as terminator to end that track and begin with another track type + // Track type 0xFF is the marker signifying the end of the tracks + + _tracks[trackNr].type = *data++; + // Counting # of channels used + data2 = data; + channelCount = 0; + while (*data2 != 0xFF) { + data2 += 6; + channelCount++; + _tracks[trackNr].channelCount++; + } + _tracks[trackNr].channels = new Channel[channelCount]; + _tracks[trackNr].channelCount = 0; + _tracks[trackNr].digitalChannelNr = -1; // No digital sound associated + _tracks[trackNr].digitalSampleRate = 0; + _tracks[trackNr].digitalSampleSize = 0; + _tracks[trackNr].digitalSampleStart = 0; + _tracks[trackNr].digitalSampleEnd = 0; + if (_tracks[trackNr].type != 0xF0) { // Digital track marker - not supported currently + channelNr = 0; + while (channelCount--) { + channel = &_tracks[trackNr].channels[channelNr]; + channel->prio = READ_LE_UINT16(data); + uint dataOffset = READ_LE_UINT16(data + 2); + if (dataOffset < resource->size) { + channel->data = resource->data + dataOffset; + channel->size = READ_LE_UINT16(data + 4); + channel->curPos = 0; + channel->number = *channel->data; + channel->poly = *(channel->data + 1); + channel->time = channel->prev = 0; + channel->data += 2; // skip over header + channel->size -= 2; // remove header size + if (channel->number == 0xFE) { // Digital channel + _tracks[trackNr].digitalChannelNr = channelNr; + _tracks[trackNr].digitalSampleRate = READ_LE_UINT16(channel->data); + _tracks[trackNr].digitalSampleSize = READ_LE_UINT16(channel->data + 2); + _tracks[trackNr].digitalSampleStart = READ_LE_UINT16(channel->data + 4); + _tracks[trackNr].digitalSampleEnd = READ_LE_UINT16(channel->data + 6); + channel->data += 8; // Skip over header + channel->size -= 8; + } + _tracks[trackNr].channelCount++; + channelNr++; + } else { + warning("Invalid offset inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr); + } + data += 6; + } + } else { + // Skip over digital track + data += 6; + } + data++; // Skipping 0xFF that closes channels list + } + break; + + default: + error("SoundResource: SCI version %d is unsupported", _soundVersion); + } +} + +SoundResource::~SoundResource() { + for (int trackNr = 0; trackNr < _trackCount; trackNr++) + delete[] _tracks[trackNr].channels; + delete[] _tracks; + + _resMan->unlockResource(_innerResource); +} + +#if 0 +SoundResource::Track* SoundResource::getTrackByNumber(uint16 number) { + if (_soundVersion <= SCI_VERSION_0_LATE) + return &_tracks[0]; + + if (/*number >= 0 &&*/number < _trackCount) + return &_tracks[number]; + return NULL; +} +#endif + +SoundResource::Track *SoundResource::getTrackByType(byte type) { + if (_soundVersion <= SCI_VERSION_0_LATE) + return &_tracks[0]; + + for (int trackNr = 0; trackNr < _trackCount; trackNr++) { + if (_tracks[trackNr].type == type) + return &_tracks[trackNr]; + } + return NULL; +} + +SoundResource::Track *SoundResource::getDigitalTrack() { + for (int trackNr = 0; trackNr < _trackCount; trackNr++) { + if (_tracks[trackNr].digitalChannelNr != -1) + return &_tracks[trackNr]; + } + return NULL; +} + +// Gets the filter mask for SCI0 sound resources +int SoundResource::getChannelFilterMask(int hardwareMask, bool wantsRhythm) { + byte *data = _innerResource->data; + int channelMask = 0; + + if (_soundVersion > SCI_VERSION_0_LATE) + return 0; + + data++; // Skip over digital sample flag + + for (int channelNr = 0; channelNr < 16; channelNr++) { + channelMask = channelMask >> 1; + + byte flags; + + if (_soundVersion == SCI_VERSION_0_EARLY) { + // Each channel is specified by a single byte + // Upper 4 bits of the byte is a voices count + // Lower 4 bits -> bit 0 set: use for AdLib + // bit 1 set: use for PCjr + // bit 2 set: use for PC speaker + // bit 3 set and bit 0 clear: control channel (15) + // bit 3 set and bit 0 set: rhythm channel (9) + // Note: control channel is dynamically assigned inside the drivers, + // but seems to be fixed at 15 in the song data. + flags = *data++; + + // Get device bits + flags &= 0x7; + } else { + // Each channel is specified by 2 bytes + // 1st byte is voices count + // 2nd byte is play mask, which specifies if the channel is supposed to be played + // by the corresponding hardware + + // Skip voice count + data++; + + flags = *data++; + } + + bool play; + switch (channelNr) { + case 15: + // Always play control channel + play = true; + break; + case 9: + // Play rhythm channel when requested + play = wantsRhythm; + break; + default: + // Otherwise check for flag + play = flags & hardwareMask; + } + + if (play) { + // This Channel is supposed to be played by the hardware + channelMask |= 0x8000; + } + } + + return channelMask; +} + +byte SoundResource::getInitialVoiceCount(byte channel) { + byte *data = _innerResource->data; + + if (_soundVersion > SCI_VERSION_0_LATE) + return 0; // TODO + + data++; // Skip over digital sample flag + + if (_soundVersion == SCI_VERSION_0_EARLY) + return data[channel] >> 4; + else + return data[channel * 2]; +} + +} // End of namespace Sci |