aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorbjörn Andersson2006-05-18 21:46:07 +0000
committerTorbjörn Andersson2006-05-18 21:46:07 +0000
commit2531fd58737b1e02b32f496ac891e5a96bf95828 (patch)
tree4b05196eebcff9bd67618bc809d98dee18db1b0a
parent4ead8dff26c85f76ffcb46e35f8ed8684664713a (diff)
downloadscummvm-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.h2
-rw-r--r--engines/kyra/kyra3.cpp15
-rw-r--r--engines/kyra/module.mk1
-rw-r--r--engines/kyra/sound_digital.cpp2
-rw-r--r--engines/kyra/vqa.cpp721
-rw-r--r--engines/kyra/wsamovie.h82
-rw-r--r--engines/scumm/imuse_digi/dimuse.cpp2
-rw-r--r--engines/scumm/imuse_digi/dimuse.h2
-rw-r--r--engines/scumm/imuse_digi/dimuse_track.cpp4
-rw-r--r--engines/scumm/smush/smush_mixer.cpp2
-rw-r--r--engines/scumm/smush/smush_mixer.h2
-rw-r--r--engines/scumm/smush/smush_player.cpp2
-rw-r--r--engines/scumm/smush/smush_player.h2
-rw-r--r--engines/scumm/sound.cpp143
-rw-r--r--engines/scumm/sound.h12
-rw-r--r--sound/audiostream.cpp144
-rw-r--r--sound/audiostream.h12
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