diff options
author | Torbjörn Andersson | 2006-05-18 21:46:07 +0000 |
---|---|---|
committer | Torbjörn Andersson | 2006-05-18 21:46:07 +0000 |
commit | 2531fd58737b1e02b32f496ac891e5a96bf95828 (patch) | |
tree | 4b05196eebcff9bd67618bc809d98dee18db1b0a | |
parent | 4ead8dff26c85f76ffcb46e35f8ed8684664713a (diff) | |
download | scummvm-rg350-2531fd58737b1e02b32f496ac891e5a96bf95828.tar.gz scummvm-rg350-2531fd58737b1e02b32f496ac891e5a96bf95828.tar.bz2 scummvm-rg350-2531fd58737b1e02b32f496ac891e5a96bf95828.zip |
Added player for the Kyra 3 VQA cutscenes, based on my earlier prototype. It
could use some cleanup, and there are a couple of TODOs sprinkled throughout
the code, but it seems to work reasonably well. Until the Kyra 3 main menu is
implemented, it won't actually be used though.
It uses the appendable audio stream class, which I have moved out of the SCUMM
engine.
svn-id: r22526
-rw-r--r-- | engines/kyra/kyra.h | 2 | ||||
-rw-r--r-- | engines/kyra/kyra3.cpp | 15 | ||||
-rw-r--r-- | engines/kyra/module.mk | 1 | ||||
-rw-r--r-- | engines/kyra/sound_digital.cpp | 2 | ||||
-rw-r--r-- | engines/kyra/vqa.cpp | 721 | ||||
-rw-r--r-- | engines/kyra/wsamovie.h | 82 | ||||
-rw-r--r-- | engines/scumm/imuse_digi/dimuse.cpp | 2 | ||||
-rw-r--r-- | engines/scumm/imuse_digi/dimuse.h | 2 | ||||
-rw-r--r-- | engines/scumm/imuse_digi/dimuse_track.cpp | 4 | ||||
-rw-r--r-- | engines/scumm/smush/smush_mixer.cpp | 2 | ||||
-rw-r--r-- | engines/scumm/smush/smush_mixer.h | 2 | ||||
-rw-r--r-- | engines/scumm/smush/smush_player.cpp | 2 | ||||
-rw-r--r-- | engines/scumm/smush/smush_player.h | 2 | ||||
-rw-r--r-- | engines/scumm/sound.cpp | 143 | ||||
-rw-r--r-- | engines/scumm/sound.h | 12 | ||||
-rw-r--r-- | sound/audiostream.cpp | 144 | ||||
-rw-r--r-- | sound/audiostream.h | 12 |
17 files changed, 986 insertions, 164 deletions
diff --git a/engines/kyra/kyra.h b/engines/kyra/kyra.h index 0c9800eefb..c003131189 100644 --- a/engines/kyra/kyra.h +++ b/engines/kyra/kyra.h @@ -1023,6 +1023,8 @@ public: int setupGameFlags() { _game = GI_KYRA3; return 0; } int go(); + + void playVQA(const char *filename); private: int init(); diff --git a/engines/kyra/kyra3.cpp b/engines/kyra/kyra3.cpp index f376145521..dbd1f9bde5 100644 --- a/engines/kyra/kyra3.cpp +++ b/engines/kyra/kyra3.cpp @@ -109,4 +109,19 @@ void KyraEngine_v3::playMenuAudioFile() { _musicSoundChannel = _soundDigital->playSound(handle, true, -1); } } + +void KyraEngine_v3::playVQA(const char *filename) { + VQAMovie vqa(this, _system); + + // TODO: Save the palette + + vqa.open(filename, 0, NULL); + if (vqa.opened()) { + vqa.play(); + vqa.close(); + } + + // TODO: Restore the palette } + +} // end of namespace Kyra diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk index 8a18f5298c..dbede17bf6 100644 --- a/engines/kyra/module.mk +++ b/engines/kyra/module.mk @@ -23,6 +23,7 @@ MODULE_OBJS := \ staticres.o \ text.o \ timer.o \ + vqa.o \ wsamovie.o MODULE_DIRS += \ diff --git a/engines/kyra/sound_digital.cpp b/engines/kyra/sound_digital.cpp index 84a56536a4..a9dae3c610 100644 --- a/engines/kyra/sound_digital.cpp +++ b/engines/kyra/sound_digital.cpp @@ -30,7 +30,7 @@ namespace Kyra { // this code is based on // TODO: cleanup of whole AUDStream -// FIXME: sound 'stutters' a bit, maybe a problem while converting int8 samples to int16? + class AUDStream : public Audio::AudioStream { public: AUDStream(Common::File *file, bool loop = false); diff --git a/engines/kyra/vqa.cpp b/engines/kyra/vqa.cpp new file mode 100644 index 0000000000..23fee5db6e --- /dev/null +++ b/engines/kyra/vqa.cpp @@ -0,0 +1,721 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2006 The ScummVM project + * + * 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$ + * + */ + +// Player for Kyrandia 3 VQA movies, based on the information found at +// http://multimedia.cx/VQA_INFO.TXT +// +// The benchl.vqa movie (or whatever it is) is not supported. It does not have +// a FINF chunk. +// +// The junk2.vqa movie does not work. The offset to the first frame is strange, +// so we don't find the palette. + +#include "common/stdafx.h" +#include "common/system.h" +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "kyra/sound.h" +#include "kyra/wsamovie.h" + +namespace Kyra { + +VQAMovie::VQAMovie(KyraEngine *vm, OSystem *system) : Movie(vm) { + _system = system; +} + +VQAMovie::~VQAMovie() { + if (_opened) + close(); +} + +void VQAMovie::initBuffers() { + for (int i = 0; i < ARRAYSIZE(_buffers); i++) { + _buffers[i].data = NULL; + _buffers[i].size = 0; + } +} + +void *VQAMovie::allocBuffer(int num, uint32 size) { + assert(num >= 0 && num < ARRAYSIZE(_buffers)); + assert(size > 0); + + if (size > _buffers[num].size) { + /* + * We could use realloc() here, but we don't actually need the + * old contents of the buffer. + */ + free(_buffers[num].data); + _buffers[num].data = malloc(size); + _buffers[num].size = size; + } + + assert(_buffers[num].data); + + return _buffers[num].data; +} + +void VQAMovie::freeBuffers() { + for (int i = 0; i < ARRAYSIZE(_buffers); i++) { + free(_buffers[i].data); + _buffers[i].data = NULL; + _buffers[i].size = 0; + } +} + +uint32 VQAMovie::readTag() { + // Some tags have to be on an even offset, so they are padded with a + // zero byte. Skip that. + + uint32 tag = _file.readUint32BE(); + + if (!(tag & 0xFF000000)) { + tag = (tag << 8) | _file.readByte(); + } + + return tag; +} + +// Chunk types ending in a 'Z' are decoded using this function. + +int VQAMovie::decodeFormat80(byte *inbuf, byte *outbuf) { + byte *src = inbuf; + byte *dst = outbuf; + + while (1) { + int relPos, pos; + int count; + byte color; + int i; + + byte command = *src++; + + switch (command) { + case 0x80: + return dst - outbuf; + + case 0xFF: + /* 11111111 <count> <pos> */ + count = src[0] | (src[1] << 8); + pos = src[2] | (src[3] << 8); + src += 4; + for (i = 0; i < count; i++) + dst[i] = outbuf[i + pos]; + break; + + case 0xFE: + /* 11111110 <count> <color> */ + count = src[0] | (src[1] << 8); + color = src[2]; + src += 3; + memset(dst, color, count); + break; + + default: + if (command >= 0xC0) { + /* 11 <count - 3> <pos> */ + count = (command & 0x3F) + 3; + pos = src[0] | (src[1] << 8); + src += 2; + for (i = 0; i < count; i++) + dst[i] = outbuf[pos + i]; + } else if (command >= 0x80) { + /* 10 <count> */ + count = command & 0x3F; + memcpy(dst, src, count); + src += count; + } else { + /* 0 <count - 3> <relative pos> */ + count = ((command & 0x70) >> 4) + 3; + relPos = ((command & 0x0F) << 8) | src[0]; + src++; + for (i = 0; i < count; i++) + dst[i] = dst[i - relPos]; + } + break; + } + + dst += count; + } +} + +inline int16 clip8BitSample(int16 sample) { + if (sample > 255) + return 255; + if (sample < 0) + return 0; + return sample; +} + +void VQAMovie::decodeSND1(byte *inbuf, uint32 insize, byte *outbuf, uint32 outsize) { + const int8 WSTable2Bit[] = { -2, -1, 0, 1 }; + const int8 WSTable4Bit[] = { + -9, -8, -6, -5, -4, -3, -2, -1, + 0, 1, 2, 3, 4, 5, 6, 8 + }; + + byte code; + int8 count; + uint16 input; + + int16 curSample = 0x80; + + while (outsize > 0) { + input = *inbuf++ << 2; + code = (input >> 8) & 0xff; + count = (input & 0xff) >> 2; + + switch (code) { + case 2: + if (count & 0x20) { + /* NOTE: count is signed! */ + count <<= 3; + curSample += (count >> 3); + *outbuf++ = curSample; + outsize--; + } else { + for (; count >= 0; count--) { + *outbuf++ = *inbuf++; + outsize--; + } + curSample = *(outbuf - 1); + } + break; + case 1: + for (; count >= 0; count--) { + code = *inbuf++; + + curSample += WSTable4Bit[code & 0x0f]; + curSample = clip8BitSample(curSample); + *outbuf++ = curSample; + + curSample += WSTable4Bit[code >> 4]; + curSample = clip8BitSample(curSample); + *outbuf++ = curSample; + + outsize -= 2; + } + break; + case 0: + for (; count >= 0; count--) { + code = *inbuf++; + + curSample += WSTable2Bit[code & 0x03]; + curSample = clip8BitSample(curSample); + *outbuf++ = curSample; + + curSample += WSTable2Bit[(code >> 2) & 0x03]; + curSample = clip8BitSample(curSample); + *outbuf++ = curSample; + + curSample += WSTable2Bit[(code >> 4) & 0x03]; + curSample = clip8BitSample(curSample); + *outbuf++ = curSample; + + curSample += WSTable2Bit[(code >> 6) & 0x03]; + curSample = clip8BitSample(curSample); + *outbuf++ = curSample; + + outsize -= 4; + } + break; + default: + for (; count >= 0; count--) { + *outbuf++ = curSample; + outsize--; + } + } + } +} + +void VQAMovie::open(const char *filename, int dummy1, uint8 *dummy2) { + debugC(9, kDebugLevelMovie, "VQAMovie::open('%s')", filename); + close(); + + if (!_file.open(filename)) + return; + + if (_file.readUint32BE() != MKID_BE('FORM')) { + warning("VQAMovie::open: Cannot find `FORM' tag"); + return; + } + + // For now, we ignore the size of the FORM chunk. + _file.readUint32BE(); + + if (_file.readUint32BE() != MKID_BE('WVQA')) { + warning("WQAMovie::open: Cannot find `WVQA' tag"); + return; + } + + bool foundHeader = false; + bool foundFrameInfo = false; + + // The information we need is stored in two chunks: VQHD and FINF. We + // need both of them before we can begin decoding the movie. + + while (!foundHeader || !foundFrameInfo) { + uint32 tag = readTag(); + uint32 size = _file.readUint32BE(); + + switch (tag) { + case MKID_BE('VQHD'): // VQA header + _header.version = _file.readUint16LE(); + _header.flags = _file.readUint16LE(); + _header.numFrames = _file.readUint16LE(); + _header.width = _file.readUint16LE(); + _header.height = _file.readUint16LE(); + _header.blockW = _file.readByte(); + _header.blockH = _file.readByte(); + _header.frameRate = _file.readByte(); + _header.cbParts = _file.readByte(); + _header.colors = _file.readUint16LE(); + _header.maxBlocks = _file.readUint16LE(); + _header.unk1 = _file.readUint32LE(); + _header.unk2 = _file.readUint16LE(); + _header.freq = _file.readUint16LE(); + _header.channels = _file.readByte(); + _header.bits = _file.readByte(); + _header.unk3 = _file.readUint32LE(); + _header.unk4 = _file.readUint16LE(); + _header.maxCBFZSize = _file.readUint32LE(); + _header.unk5 = _file.readUint32LE(); + + // Version 1 VQA files have some implicit defaults + + if (_header.version == 1) { + if (_header.flags & 1) { + if (_header.freq == 0) + _header.freq = 22050; + if (_header.channels == 0) + _header.channels = 1; + if (_header.bits == 0) + _header.bits = 8; + } + } + + setX((320 - _header.width) / 2); + setY((200 - _header.height) / 2); + + // HACK: I've only seen 8-bit mono audio in Kyra 3 + + assert(_header.bits == 8); + assert(_header.channels == 1); + + _frameInfo = new uint32[_header.numFrames]; + _frame = new byte[_header.width * _header.height]; + + size = 0xf00 * _header.blockW * _header.blockH; + _codeBook = new byte[size]; + _partialCodeBook = new byte[size]; + memset(_codeBook, 0, size); + memset(_partialCodeBook, 0, size); + + _numVectorPointers = (_header.width / _header.blockW) * (_header.height * _header.blockH); + _vectorPointers = new uint16[_numVectorPointers]; + memset(_vectorPointers, 0, _numVectorPointers * sizeof(uint16)); + + _partialCodeBookSize = 0; + _numPartialCodeBooks = 0; + + if (_header.flags & 1) { + // A 2-second buffer ought to be enough + _stream = Audio::makeAppendableAudioStream(_header.freq, Audio::Mixer::FLAG_UNSIGNED, 2 * _header.freq * _header.channels); + _sound = new Audio::SoundHandle; + } else { + _sound = NULL; + _stream = NULL; + } + + foundHeader = true; + break; + + case MKID_BE('FINF'): // Frame info + if (!foundHeader) { + warning("VQAMovie::open: Found `FINF' before `VQHD'"); + return; + } + + if (size != 4 * (uint32)_header.numFrames) { + warning("VQAMovie::open: Expected size %d for `FINF' chunk, but got %d", 4 * _header.numFrames, size); + return; + } + + foundFrameInfo = true; + + for (int i = 0; i < _header.numFrames; i++) { + _frameInfo[i] = 2 * _file.readUint32LE(); + } + break; + + default: + warning("VQAMovie::open: Unknown tag `%c%c%c%c'", (tag >> 24) & 0xFF, (tag >> 16) & 0xFF, (tag >> 8) & 0xFF, tag & 0xFF); + _file.seek(size, SEEK_CUR); + break; + } + } + + initBuffers(); + + _opened = true; +} + +void VQAMovie::close() { + debugC(9, kDebugLevelMovie, "VQAMovie::close()"); + if (_opened) { + delete [] _frameInfo; + delete [] _frame; + delete [] _codeBook; + delete [] _partialCodeBook; + delete [] _vectorPointers; + + if (_sound) { + _vm->_mixer->stopHandle(*_sound); + delete _sound; + _stream = NULL; + _sound = NULL; + } + + _frameInfo = NULL; + _frame = NULL; + _codeBook = NULL; + _partialCodeBook = NULL; + _vectorPointers = NULL; + _stream = NULL; + + if (_file.isOpen()) + _file.close(); + + freeBuffers(); + + _opened = false; + } +} + +void VQAMovie::displayFrame(int frameNum) { + debugC(9, kDebugLevelMovie, "VQAMovie::displayFrame(%d)", frameNum); + if (frameNum >= _header.numFrames || !_opened) + return; + + bool foundSound = _stream ? false : true; + bool foundFrame = false; + uint i; + + _file.seek(_frameInfo[frameNum] & 0x7FFFFFFF); + + while (!foundSound || !foundFrame) { + uint32 tag = readTag(); + uint32 size = _file.readUint32BE(); + + if (_file.eof()) { + // This happens at the last frame. Apparently it has + // no sound? + break; + } + + byte *inbuf, *outbuf; + uint32 insize, outsize; + byte *pal; + uint32 end; + + switch (tag) { + case MKID_BE('SND0'): // Uncompressed sound + foundSound = true; + inbuf = (byte *)allocBuffer(0, size); + _file.read(inbuf, size); + _stream->append(inbuf, size); + break; + + case MKID_BE('SND1'): // Compressed sound, almost like AUD + foundSound = true; + outsize = _file.readUint16LE(); + insize = _file.readUint16LE(); + + inbuf = (byte *)allocBuffer(0, insize); + _file.read(inbuf, insize); + + if (insize == outsize) { + _stream->append(inbuf, insize); + } else { + outbuf = (byte *)allocBuffer(1, outsize); + decodeSND1(inbuf, insize, outbuf, outsize); + _stream->append(outbuf, outsize); + } + break; + + case MKID_BE('SND2'): // Compressed sound + foundSound = true; + warning("VQAMovie::displayFrame: `SND2' is not implemented"); + _file.seek(size, SEEK_CUR); + break; + + case MKID_BE('VQFR'): + foundFrame = true; + end = _file.pos() + size - 8; + + while (_file.pos() < end) { + tag = readTag(); + size = _file.readUint32BE(); + + switch (tag) { + case MKID_BE('CBF0'): // Full codebook + _file.read(_codeBook, size); + break; + + case MKID_BE('CBFZ'): // Full codebook + inbuf = (byte *)allocBuffer(0, size); + _file.read(inbuf, size); + decodeFormat80(inbuf, _codeBook); + break; + + case MKID_BE('CBP0'): // Partial codebook + _compressedCodeBook = false; + _file.read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + + case MKID_BE('CBPZ'): // Partial codebook + _compressedCodeBook = true; + _file.read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + + case MKID_BE('CPL0'): // Palette + assert(size <= 3 * 256); + + inbuf = (byte *)allocBuffer(0, size); + pal = _palette; + _file.read(inbuf, size); + + for (i = 0; i < size / 3; i++) { + *pal++ = 4 * (0x3F & *inbuf++); + *pal++ = 4 * (0x3F & *inbuf++); + *pal++ = 4 * (0x3F & *inbuf++); + *pal++ = 0; + } + + break; + + case MKID_BE('CPLZ'): // Palette + inbuf = (byte *)allocBuffer(0, size); + outbuf = (byte *)allocBuffer(1, 3 * 256); + pal = _palette; + _file.read(inbuf, size); + size = decodeFormat80(inbuf, outbuf); + + for (i = 0; i < size / 3; i++) { + *pal++ = 4 * (0x3F & *outbuf++); + *pal++ = 4 * (0x3F & *outbuf++); + *pal++ = 4 * (0x3F & *outbuf++); + *pal++ = 0; + } + + break; + + case MKID_BE('VPT0'): // Frame data + assert(size / 2 <= _numVectorPointers); + + for (i = 0; i < size / 2; i++) + _vectorPointers[i] = _file.readUint16LE(); + break; + + case MKID_BE('VPTZ'): // Frame data + inbuf = (byte *)allocBuffer(0, size); + outbuf = (byte *)allocBuffer(1, 2 * _numVectorPointers); + + _file.read(inbuf, size); + size = decodeFormat80(inbuf, outbuf); + + assert(size / 2 <= _numVectorPointers); + + for (i = 0; i < size / 2; i++) + _vectorPointers[i] = READ_LE_UINT16(outbuf + 2 * i); + break; + + default: + warning("VQAMovie::displayFrame: Unknown `VQFR' sub-tag `%c%c%c%c'", (tag >> 24) & 0xFF, (tag >> 16) & 0xFF, (tag >> 8) & 0xFF, tag & 0xFF); + _file.seek(size, SEEK_CUR); + break; + } + + } + + break; + + default: + warning("VQAMovie::displayFrame: Unknown tag `%c%c%c%c'", (tag >> 24) & 0xFF, (tag >> 16) & 0xFF, (tag >> 8) & 0xFF, tag & 0xFF); + _file.seek(size, SEEK_CUR); + break; + } + } + + // The frame has been decoded + + if (_frameInfo[frameNum] & 0x80000000) { + _system->setPalette(_palette, 0, 256); + } + + int blockPitch = _header.width / _header.blockW; + + for (int by = 0; by < _header.height / _header.blockH; by++) { + for (int bx = 0; bx < blockPitch; bx++) { + byte *dst = _frame + by * _header.width * _header.blockH + bx * _header.blockW; + int val = _vectorPointers[by * blockPitch + bx]; + + if ((val & 0xFF00) == 0xFF00) { + // Solid color + for (i = 0; i < _header.blockH; i++) { + memset(dst, 255 - (val & 0xFF), _header.blockW); + dst += _header.width; + } + } else { + // Copy data from _vectorPointers. I'm not sure + // why we don't use the three least significant + // bits of 'val'. + byte *src = &_codeBook[(val >> 3) * _header.blockW * _header.blockH]; + + for (i = 0; i < _header.blockH; i++) { + memcpy(dst, src, _header.blockW); + src += _header.blockW; + dst += _header.width; + } + } + } + } + + if (_numPartialCodeBooks == _header.cbParts) { + if (_compressedCodeBook) { + decodeFormat80(_partialCodeBook, _codeBook); + } else { + memcpy(_codeBook, _partialCodeBook, _partialCodeBookSize); + } + _numPartialCodeBooks = 0; + _partialCodeBookSize = 0; + } + + _system->copyRectToScreen(_frame, _header.width, _x, _y, _header.width, _header.height); +} + +void VQAMovie::play() { + uint32 startTick; + + if (!_opened) + return; + + startTick = _system->getMillis(); + + // First, handle any sound chunk that apears before the first frame. + // At least in some versions, it will contain half a second of audio, + // presumably to lessen the risk of audio underflow. + // + // In most movies, we will find a CMDS tag. The purpose of this is + // currently unknown. + // + // In cow1_0.vqa, cow1_1.vqa, jung0.vqa, and jung1.vqa we will find a + // VQFR tag. A frame before the first frame? Weird. It doesn't seem to + // be needed, though. + + byte *inbuf, *outbuf; + uint32 insize, outsize; + + if (_stream) { + while (_file.pos() < (_frameInfo[0] & 0x7FFFFFFF)) { + uint32 tag = readTag(); + uint32 size = _file.readUint32BE(); + + if (_file.eof()) { + warning("VQAMovie::play: Unexpected EOF"); + break; + } + + switch (tag) { + case MKID_BE('SND0'): // Uncompressed sound + inbuf = (byte *)allocBuffer(0, size); + _file.read(inbuf, size); + _stream->append(inbuf, size); + break; + + case MKID_BE('SND1'): // Compressed sound + outsize = _file.readUint16LE(); + insize = _file.readUint16LE(); + + inbuf = (byte *)allocBuffer(0, insize); + _file.read(inbuf, insize); + + if (insize == outsize) { + _stream->append(inbuf, insize); + } else { + outbuf = (byte *)allocBuffer(1, outsize); + decodeSND1(inbuf, insize, outbuf, outsize); + _stream->append(outbuf, outsize); + } + break; + + case MKID_BE('SND2'): // Compressed sound + warning("VQAMovie::play: `SND2' is not implemented"); + _file.seek(size, SEEK_CUR); + break; + + default: + warning("VQAMovie::play: Unknown tag `%c%c%c%c'", (tag >> 24) & 0xFF, (tag >> 16) & 0xFF, (tag >> 8) & 0xFF, tag & 0xFF); + _file.seek(size, SEEK_CUR); + break; + } + } + } + + _vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, _sound, _stream); + + for (int i = 0; i < _header.numFrames; i++) { + displayFrame(i); + + // TODO: We ought to sync this to how much sound we've played. + // TODO: Implement frame skipping? + + while (_system->getMillis() < startTick + (i * 1000) / _header.frameRate) { + OSystem::Event event; + + while (_system->pollEvent(event)) { + switch (event.type) { + case OSystem::EVENT_KEYDOWN: + if (event.kbd.ascii == 27) + return; + break; + case OSystem::EVENT_QUIT: + _vm->quitGame(); + break; + default: + break; + } + } + + _system->delayMillis(10); + } + + _system->updateScreen(); + } + + // TODO: Wait for the sound to finish? +} + +} // end of namespace Kyra diff --git a/engines/kyra/wsamovie.h b/engines/kyra/wsamovie.h index a6319bba99..eb1058d504 100644 --- a/engines/kyra/wsamovie.h +++ b/engines/kyra/wsamovie.h @@ -25,6 +25,11 @@ #include "kyra/resource.h" +namespace Audio { +class AppendableAudioStream; +class SoundHandle; +} // end of namespace Audio + namespace Kyra { class KyraEngine; @@ -103,6 +108,83 @@ protected: int16 _yAdd; }; +// Kyra 3 VQA movies. Should perhaps be in another header file. + +class VQAMovie : public Movie { +public: + VQAMovie(KyraEngine *vm, OSystem *system); + ~VQAMovie(); + + int frames() { return _opened ? _header.numFrames : -1; } + + void displayFrame(int frameNum); + + // Only the first parameter is used. + virtual void open(const char *filename, int offscreen, uint8 *palette); + void close(); + void play(); + +protected: + OSystem *_system; + + struct VQAHeader { + uint16 version; + uint16 flags; + uint16 numFrames; + uint16 width; + uint16 height; + uint8 blockW; + uint8 blockH; + uint8 frameRate; + uint8 cbParts; + uint16 colors; + uint16 maxBlocks; + uint32 unk1; + uint16 unk2; + uint16 freq; + uint8 channels; + uint8 bits; + uint32 unk3; + uint16 unk4; + uint32 maxCBFZSize; + uint32 unk5; + }; + + struct Buffer { + void *data; + uint32 size; + }; + + Buffer _buffers[2]; + + void initBuffers(); + void *allocBuffer(int num, uint32 size); + void freeBuffers(); + + int decodeFormat80(byte *inbuf, byte *outbuf); + void decodeSND1(byte *inbuf, uint32 insize, byte *outbuf, uint32 outsize); + + Common::File _file; + + VQAHeader _header; + uint32 *_frameInfo; + byte *_codeBook; + byte *_partialCodeBook; + bool _compressedCodeBook; + int _partialCodeBookSize; + int _numPartialCodeBooks; + uint32 _numVectorPointers; + uint16 *_vectorPointers; + + byte _palette[4 * 256]; + byte *_frame; + + Audio::AppendableAudioStream *_stream; + Audio::SoundHandle *_sound; + + uint32 readTag(); +}; + } // end of namespace Kyra #endif diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp index b2c498a1c0..761f5dff55 100644 --- a/engines/scumm/imuse_digi/dimuse.cpp +++ b/engines/scumm/imuse_digi/dimuse.cpp @@ -187,7 +187,7 @@ void IMuseDigital::saveOrLoad(Serializer *ser) { int32 streamBufferSize = track->iteration; track->stream2 = NULL; - track->stream = makeAppendableAudioStream(freq, track->mixerFlags, streamBufferSize); + track->stream = Audio::makeAppendableAudioStream(freq, track->mixerFlags, streamBufferSize); const int pan = (track->pan != 64) ? 2 * track->pan - 127 : 0; const int vol = track->vol / 1000; diff --git a/engines/scumm/imuse_digi/dimuse.h b/engines/scumm/imuse_digi/dimuse.h index 2a254958b6..d94ad8d52d 100644 --- a/engines/scumm/imuse_digi/dimuse.h +++ b/engines/scumm/imuse_digi/dimuse.h @@ -79,7 +79,7 @@ private: ImuseDigiSndMgr::soundStruct *soundHandle; Audio::SoundHandle handle; - AppendableAudioStream *stream; + Audio::AppendableAudioStream *stream; Audio::AudioStream *stream2; Track(); diff --git a/engines/scumm/imuse_digi/dimuse_track.cpp b/engines/scumm/imuse_digi/dimuse_track.cpp index a203ee878a..d9baa00c6e 100644 --- a/engines/scumm/imuse_digi/dimuse_track.cpp +++ b/engines/scumm/imuse_digi/dimuse_track.cpp @@ -178,7 +178,7 @@ void IMuseDigital::startSound(int soundId, const char *soundName, int soundType, // setup 1 second stream wrapped buffer int32 streamBufferSize = track->iteration; track->stream2 = NULL; - track->stream = makeAppendableAudioStream(freq, track->mixerFlags, streamBufferSize); + track->stream = Audio::makeAppendableAudioStream(freq, track->mixerFlags, streamBufferSize); _vm->_mixer->playInputStream(type, &track->handle, track->stream, -1, vol, pan, false); track->started = true; } @@ -357,7 +357,7 @@ IMuseDigital::Track *IMuseDigital::cloneToFadeOutTrack(Track *track, int fadeDel // setup 1 second stream wrapped buffer int32 streamBufferSize = fadeTrack->iteration; - fadeTrack->stream = makeAppendableAudioStream(_sound->getFreq(fadeTrack->soundHandle), fadeTrack->mixerFlags, streamBufferSize); + fadeTrack->stream = Audio::makeAppendableAudioStream(_sound->getFreq(fadeTrack->soundHandle), fadeTrack->mixerFlags, streamBufferSize); _vm->_mixer->playInputStream(type, &fadeTrack->handle, fadeTrack->stream, -1, fadeTrack->vol / 1000, fadeTrack->pan, false); fadeTrack->started = true; fadeTrack->used = true; diff --git a/engines/scumm/smush/smush_mixer.cpp b/engines/scumm/smush/smush_mixer.cpp index a7b8fb9b27..b5002609df 100644 --- a/engines/scumm/smush/smush_mixer.cpp +++ b/engines/scumm/smush/smush_mixer.cpp @@ -129,7 +129,7 @@ bool SmushMixer::handleFrame() { if (_mixer->isReady()) { if (!_channels[i].stream) { - _channels[i].stream = makeAppendableAudioStream(rate, flags, 500000); + _channels[i].stream = Audio::makeAppendableAudioStream(rate, flags, 500000); _mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_channels[i].handle, _channels[i].stream); } _mixer->setChannelVolume(_channels[i].handle, vol); diff --git a/engines/scumm/smush/smush_mixer.h b/engines/scumm/smush/smush_mixer.h index 82b233873d..05e33b6a8f 100644 --- a/engines/scumm/smush/smush_mixer.h +++ b/engines/scumm/smush/smush_mixer.h @@ -41,7 +41,7 @@ private: int id; SmushChannel *chan; Audio::SoundHandle handle; - AppendableAudioStream *stream; + Audio::AppendableAudioStream *stream; } _channels[NUM_CHANNELS]; int _soundFrequency; diff --git a/engines/scumm/smush/smush_player.cpp b/engines/scumm/smush/smush_player.cpp index 9edf776989..76dc32284b 100644 --- a/engines/scumm/smush/smush_player.cpp +++ b/engines/scumm/smush/smush_player.cpp @@ -522,7 +522,7 @@ void SmushPlayer::handleIACT(Chunk &b) { } while (--count); if (!_IACTstream) { - _IACTstream = makeAppendableAudioStream(22050, Audio::Mixer::FLAG_STEREO | Audio::Mixer::FLAG_16BITS, 400000); + _IACTstream = Audio::makeAppendableAudioStream(22050, Audio::Mixer::FLAG_STEREO | Audio::Mixer::FLAG_16BITS, 400000); _vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_IACTchannel, _IACTstream); } _IACTstream->append(output_data, 0x1000); diff --git a/engines/scumm/smush/smush_player.h b/engines/scumm/smush/smush_player.h index 929f9d73d2..05dd5c362a 100644 --- a/engines/scumm/smush/smush_player.h +++ b/engines/scumm/smush/smush_player.h @@ -62,7 +62,7 @@ private: int32 _frame; Audio::SoundHandle _IACTchannel; - AppendableAudioStream *_IACTstream; + Audio::AppendableAudioStream *_IACTstream; Audio::SoundHandle _compressedFileSoundHandle; bool _compressedFileMode; diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index b32686a215..2b56b3c808 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -2109,147 +2109,4 @@ int ScummEngine::readSoundResourceSmallHeader(int type, int idx) { } -#pragma mark - -#pragma mark --- Appendable audio stream --- -#pragma mark - - - -/** - * Wrapped memory stream. - */ -template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> -class AppendableMemoryStream : public AppendableAudioStream { -protected: - Common::Mutex _mutex; - - byte *_bufferStart; - byte *_bufferEnd; - byte *_pos; - byte *_end; - bool _finalized; - const int _rate; - - inline bool eosIntern() const { return _end == _pos; }; -public: - AppendableMemoryStream(int rate, uint bufferSize); - ~AppendableMemoryStream(); - int readBuffer(int16 *buffer, const int numSamples); - - bool isStereo() const { return stereo; } - bool endOfStream() const { return _finalized && eosIntern(); } - bool endOfData() const { return eosIntern(); } - - int getRate() const { return _rate; } - - void append(const byte *data, uint32 len); - void finish() { _finalized = true; } -}; - -template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> -AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::AppendableMemoryStream(int rate, uint bufferSize) - : _finalized(false), _rate(rate) { - - // Verify the buffer size is sane - if (is16Bit && stereo) - assert((bufferSize & 3) == 0); - else if (is16Bit || stereo) - assert((bufferSize & 1) == 0); - - _bufferStart = (byte *)malloc(bufferSize); - _pos = _end = _bufferStart; - _bufferEnd = _bufferStart + bufferSize; -} - -template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> -AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::~AppendableMemoryStream() { - free(_bufferStart); -} - -template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> -int AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) { - Common::StackLock lock(_mutex); - - int samples = 0; - while (samples < numSamples && !eosIntern()) { - // Wrap around? - if (_pos >= _bufferEnd) - _pos = _pos - (_bufferEnd - _bufferStart); - - const byte *endMarker = (_pos > _end) ? _bufferEnd : _end; - const int len = MIN(numSamples, samples + (int)(endMarker - _pos) / (is16Bit ? 2 : 1)); - while (samples < len) { - *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _pos, isLE); - _pos += (is16Bit ? 2 : 1); - samples++; - } - } - - return samples; -} - -template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> -void AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::append(const byte *data, uint32 len) { - Common::StackLock lock(_mutex); - - // Verify the buffer size is sane - if (is16Bit && stereo) - assert((len & 3) == 0); - else if (is16Bit || stereo) - assert((len & 1) == 0); - - // Verify that the stream has not yet been finalized (by a call to finish()) - assert(!_finalized); - - if (_end + len > _bufferEnd) { - // Wrap-around case - uint32 size_to_end_of_buffer = _bufferEnd - _end; - len -= size_to_end_of_buffer; - if ((_end < _pos) || (_bufferStart + len >= _pos)) { - debug(2, "AppendableMemoryStream: buffer overflow (A)"); - return; - } - memcpy(_end, data, size_to_end_of_buffer); - memcpy(_bufferStart, data + size_to_end_of_buffer, len); - _end = _bufferStart + len; - } else { - if ((_end < _pos) && (_end + len >= _pos)) { - debug(2, "AppendableMemoryStream: buffer overflow (B)"); - return; - } - memcpy(_end, data, len); - _end += len; - } -} - - -#define MAKE_WRAPPED(STEREO, UNSIGNED) \ - if (is16Bit) { \ - if (isLE) \ - return new AppendableMemoryStream<STEREO, true, UNSIGNED, true>(rate, len); \ - else \ - return new AppendableMemoryStream<STEREO, true, UNSIGNED, false>(rate, len); \ - } else \ - return new AppendableMemoryStream<STEREO, false, UNSIGNED, false>(rate, len) - -AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags, uint32 len) { - const bool isStereo = (_flags & Audio::Mixer::FLAG_STEREO) != 0; - const bool is16Bit = (_flags & Audio::Mixer::FLAG_16BITS) != 0; - const bool isUnsigned = (_flags & Audio::Mixer::FLAG_UNSIGNED) != 0; - const bool isLE = (_flags & Audio::Mixer::FLAG_LITTLE_ENDIAN) != 0; - - if (isStereo) { - if (isUnsigned) { - MAKE_WRAPPED(true, true); - } else { - MAKE_WRAPPED(true, false); - } - } else { - if (isUnsigned) { - MAKE_WRAPPED(false, true); - } else { - MAKE_WRAPPED(false, false); - } - } -} - } // End of namespace Scumm diff --git a/engines/scumm/sound.h b/engines/scumm/sound.h index 8d09d4a7b4..6459d8772d 100644 --- a/engines/scumm/sound.h +++ b/engines/scumm/sound.h @@ -132,18 +132,6 @@ protected: virtual void processSoundQueues(); }; -/** - * An audio stream to which additional data can be appended on-the-fly. - * Used by SMUSH and iMuseDigital. - */ -class AppendableAudioStream : public Audio::AudioStream { -public: - virtual void append(const byte *data, uint32 len) = 0; - virtual void finish() = 0; -}; - -AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags, uint32 len); - } // End of namespace Scumm diff --git a/sound/audiostream.cpp b/sound/audiostream.cpp index 07115390e8..2f8a5e0d4c 100644 --- a/sound/audiostream.cpp +++ b/sound/audiostream.cpp @@ -214,4 +214,148 @@ AudioStream *makeLinearInputStream(int rate, byte flags, const byte *ptr, uint32 } +#pragma mark - +#pragma mark --- Appendable audio stream --- +#pragma mark - + + +/** + * Wrapped memory stream. + */ +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +class AppendableMemoryStream : public AppendableAudioStream { +protected: + Common::Mutex _mutex; + + byte *_bufferStart; + byte *_bufferEnd; + byte *_pos; + byte *_end; + bool _finalized; + const int _rate; + + inline bool eosIntern() const { return _end == _pos; }; +public: + AppendableMemoryStream(int rate, uint bufferSize); + ~AppendableMemoryStream(); + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return stereo; } + bool endOfStream() const { return _finalized && eosIntern(); } + bool endOfData() const { return eosIntern(); } + + int getRate() const { return _rate; } + + void append(const byte *data, uint32 len); + void finish() { _finalized = true; } +}; + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::AppendableMemoryStream(int rate, uint bufferSize) + : _finalized(false), _rate(rate) { + + // Verify the buffer size is sane + if (is16Bit && stereo) + assert((bufferSize & 3) == 0); + else if (is16Bit || stereo) + assert((bufferSize & 1) == 0); + + _bufferStart = (byte *)malloc(bufferSize); + _pos = _end = _bufferStart; + _bufferEnd = _bufferStart + bufferSize; +} + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::~AppendableMemoryStream() { + free(_bufferStart); +} + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +int AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + int samples = 0; + while (samples < numSamples && !eosIntern()) { + // Wrap around? + if (_pos >= _bufferEnd) + _pos = _pos - (_bufferEnd - _bufferStart); + + const byte *endMarker = (_pos > _end) ? _bufferEnd : _end; + const int len = MIN(numSamples, samples + (int)(endMarker - _pos) / (is16Bit ? 2 : 1)); + while (samples < len) { + *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _pos, isLE); + _pos += (is16Bit ? 2 : 1); + samples++; + } + } + + return samples; +} + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +void AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::append(const byte *data, uint32 len) { + Common::StackLock lock(_mutex); + + // Verify the buffer size is sane + if (is16Bit && stereo) + assert((len & 3) == 0); + else if (is16Bit || stereo) + assert((len & 1) == 0); + + // Verify that the stream has not yet been finalized (by a call to finish()) + assert(!_finalized); + + if (_end + len > _bufferEnd) { + // Wrap-around case + uint32 size_to_end_of_buffer = _bufferEnd - _end; + len -= size_to_end_of_buffer; + if ((_end < _pos) || (_bufferStart + len >= _pos)) { + debug(2, "AppendableMemoryStream: buffer overflow (A)"); + return; + } + memcpy(_end, data, size_to_end_of_buffer); + memcpy(_bufferStart, data + size_to_end_of_buffer, len); + _end = _bufferStart + len; + } else { + if ((_end < _pos) && (_end + len >= _pos)) { + debug(2, "AppendableMemoryStream: buffer overflow (B)"); + return; + } + memcpy(_end, data, len); + _end += len; + } +} + + +#define MAKE_WRAPPED(STEREO, UNSIGNED) \ + if (is16Bit) { \ + if (isLE) \ + return new AppendableMemoryStream<STEREO, true, UNSIGNED, true>(rate, len); \ + else \ + return new AppendableMemoryStream<STEREO, true, UNSIGNED, false>(rate, len); \ + } else \ + return new AppendableMemoryStream<STEREO, false, UNSIGNED, false>(rate, len) + +AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags, uint32 len) { + const bool isStereo = (_flags & Audio::Mixer::FLAG_STEREO) != 0; + const bool is16Bit = (_flags & Audio::Mixer::FLAG_16BITS) != 0; + const bool isUnsigned = (_flags & Audio::Mixer::FLAG_UNSIGNED) != 0; + const bool isLE = (_flags & Audio::Mixer::FLAG_LITTLE_ENDIAN) != 0; + + if (isStereo) { + if (isUnsigned) { + MAKE_WRAPPED(true, true); + } else { + MAKE_WRAPPED(true, false); + } + } else { + if (isUnsigned) { + MAKE_WRAPPED(false, true); + } else { + MAKE_WRAPPED(false, false); + } + } +} + + } // End of namespace Audio diff --git a/sound/audiostream.h b/sound/audiostream.h index 078c8506c5..5aa46f2e85 100644 --- a/sound/audiostream.h +++ b/sound/audiostream.h @@ -112,6 +112,18 @@ public: AudioStream *makeLinearInputStream(int rate, byte flags, const byte *ptr, uint32 len, uint loopOffset, uint loopLen); +/** + * An audio stream to which additional data can be appended on-the-fly. + * Used by SMUSH, iMuseDigital, and the Kyrandia 3 VQA player. + */ +class AppendableAudioStream : public Audio::AudioStream { +public: + virtual void append(const byte *data, uint32 len) = 0; + virtual void finish() = 0; +}; + +AppendableAudioStream *makeAppendableAudioStream(int rate, byte _flags, uint32 len); + // This used to be an inline template function, but // buggy template function handling in MSVC6 forced |