scummvm/engines/tinsel/bmv.cpp
Max Horn 1dbf8d73d5 TINSEL: Mark all (?) global vars with a FIXME comment
Use of global vars is what prevents RTL from working in Tinsel (and
probably in other engines). More specifically, the fact that many
global vars are not explicitly inited when the engine is (re)launched.

svn-id: r54262
2010-11-16 09:53:55 +00:00

1183 lines
26 KiB
C++

/* 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$
*
* The movie player.
*/
#include "tinsel/tinsel.h"
#include "tinsel/background.h"
#include "tinsel/bmv.h"
#include "tinsel/cliprect.h"
#include "tinsel/config.h"
#include "tinsel/dw.h"
#include "tinsel/events.h"
#include "tinsel/font.h"
#include "tinsel/graphics.h"
#include "tinsel/handle.h"
#include "tinsel/multiobj.h"
#include "tinsel/sched.h"
#include "tinsel/strres.h"
#include "tinsel/text.h"
#include "tinsel/timers.h"
#include "tinsel/tinlib.h"
#include "tinsel/tinsel.h"
#include "sound/decoders/raw.h"
namespace Tinsel {
//----------------- LOCAL DEFINES ----------------------------
#define BMOVIE_EXTENSION ".bmv"
#define SZ_C_BLOB 65
#define SZ_U_BLOB 128
#define BLANK_SOUND 0x0 // for 16 bit silence
#define PT_A 20 // Number of times PT_B may be reached
#define PT_B 6
#define SLOT_SIZE (25*1024)
//#define NUM_SLOTS 168
#define NUM_SLOTS 122 // -> ~ 3MB
#define PREFETCH (NUM_SLOTS/2) // For initial test
#define ADVANCE_SOUND 18 // 1 1/2 seconds
#define SUBSEQUENT_SOUND 6 // 1/2 second
// PACKET TYPE IDs & FLAGS
#define CD_SLOT_NOP 0x00 // Skip to next slot
#define CD_LE_FIN 0x01 // End of movie
#define CD_PDELTA 0x02 // Image compressed to previous one
#define CD_SDELTA 0x03 // Image self-compressed
#define BIT0 0x01
#define CD_XSCR 0x04 // Screen has a scroll offset
#define CD_CMAP 0x08 // Colour map is included
#define CD_CMND 0x10 // Command is included
#define CD_AUDIO 0x20 // Audio data is included
#define CD_EXTEND 0x40 // Extended modes "A"-"z"
#define CD_PRINT 0x80 // goes in conjunction with CD_CMD
// Data field sizes
#define sz_XSCR_pkt 2
#define sz_CMAP_pkt 0x300
#define sz_CMD_TALK_pkt 10
#define sz_CMD_PRINT_pkt 8
#define sz_AUDIO_pkt 3675
struct TALK_CMD {
short x;
short y;
short stringId;
unsigned char duration;
char r; // may be b!
char g;
char b; // may be r!
};
struct PRINT_CMD {
int16 x;
int16 y;
int16 stringId;
unsigned char duration;
unsigned char fontId;
};
//----------------- LOCAL GLOBAL DATA ------------------------
static const uint16 Au_DecTable[16] = {16512, 8256, 4128, 2064, 1032, 516, 258, 192,
129, 88, 64, 56, 48, 40, 36, 32};
//---------------- DECOMPRESSOR FUNCTIONS --------------------
#define SCREEN_WIDE 640
#define SCREEN_HIGH 429
#define SAM_P_BLOB (32 * 2)
#define ROR(x,v) x = ((x >> (v%32)) | (x << (32 - (v%32))))
#define ROL(x,v) x = ((x << (v%32)) | (x >> (32 - (v%32))))
#define NEXT_BYTE(v) v = (forwardDirection ? v + 1 : v - 1)
static void PrepBMV(byte *ScreenBeg, const byte *sourceData, int length, short deltaFetchDisp) {
uint8 NibbleHi = 0;
uint32 edx = length;
int32 ebx = deltaFetchDisp;
const byte *src;
byte *dst, *endDst;
const bool forwardDirection = (deltaFetchDisp <= -SCREEN_WIDE) || (deltaFetchDisp >= 0);
if (forwardDirection) {
// Forward decompression
src = sourceData;
dst = ScreenBeg;
endDst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH;
} else {
src = sourceData + length - 1;
dst = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH - 1;
endDst = ScreenBeg - 1;
}
bool firstLoop, flag;
int loopCtr = 0;
do {
uint32 eax = 0;
uint32 bitshift = 0;
flag = false;
if ((loopCtr == 0) || (edx == 4)) {
// Get the next hi,lo nibble
eax = (eax & 0xffffff00) | *src;
firstLoop = true;
} else {
// Get the high nibble
eax = (eax & 0xffffff00) | NibbleHi;
firstLoop = false;
}
// Is lo nibble '00xx'?
if ((eax & 0xC) == 0) {
for (;;) {
//@_rDN_Lp_1:
// Only execute this bit first the first time into the loop
if (!firstLoop) {
ROR(eax, 2);
bitshift += 2;
eax = (eax & 0xffffff00) | *src;
if ((eax & 0xC) != 0)
break;
}
firstLoop = false;
//@_rD2nd_1:
ROR(eax, 2); // Save bi-bit into hi 2 bits
bitshift += 2; // and increase bit-shifter
// Shift another 2 bits to get hi nibble
eax = (eax & 0xffffff00) | ((eax & 0xff) >> 2);
NEXT_BYTE(src);
if ((eax & 0xC) != 0) {
flag = true;
ROL(eax, bitshift);
break;
}
}
} else if (loopCtr != 0) {
flag = edx != 4;
}
if (flag) {
//@_rdNum__1:
edx = 4; // offset rDNum_Lo ; Next nibble is a 'lo'
} else {
// @_rDNum_1
NibbleHi = ((uint8)eax) >> 4;
edx = 0; // offset rDNum_Hi ; Next nibble is a 'hi' (reserved)
eax &= 0xffffff0f;
NEXT_BYTE(src);
ROL(eax, bitshift);
}
//rDN_1:
//@_rD_or_R:
bool actionFlag = (eax & 1) != 0;
eax >>= 1;
int byteLen = eax - 1;
// Move to next loop index
++loopCtr;
if (loopCtr == 4)
loopCtr = 1;
if (actionFlag) {
// Adjust loopCtr to fall into the correct processing case
loopCtr = loopCtr % 3 + 1;
}
switch (loopCtr) {
case 1: {
// @_rDelta:
const byte *saved_src = src; // Save the source pointer
src = dst + ebx; // Point it to existing data
while (byteLen > 0) {
*dst = *src;
NEXT_BYTE(src);
NEXT_BYTE(dst);
--byteLen;
}
src = saved_src;
break;
}
case 2:
// @_rRaw
// Copy data from source to dest
while (byteLen > 0) {
*dst = *src;
NEXT_BYTE(src);
NEXT_BYTE(dst);
--byteLen;
}
break;
case 3:
// @_rRun
// Repeating run of data
eax = forwardDirection ? *(dst - 1) : *(dst + 1);
while (byteLen > 0) {
*dst = (uint8)eax;
NEXT_BYTE(dst);
--byteLen;
}
break;
default:
break;
}
} while (dst != endDst);
}
void BMVPlayer::InitBMV(byte *memoryBuffer) {
// Clear the two extra 'off-screen' rows
memset(memoryBuffer, 0, SCREEN_WIDE);
memset(memoryBuffer + SCREEN_WIDE * (SCREEN_HIGH + 1), 0, SCREEN_WIDE);
if (_audioStream) {
_vm->_mixer->stopHandle(_audioHandle);
delete _audioStream;
_audioStream = 0;
}
// Set the screen beginning to the second line (ie. past the off-screen line)
ScreenBeg = memoryBuffer + SCREEN_WIDTH;
Au_Prev1 = Au_Prev2 = 0;
}
void BMVPlayer::PrepAudio(const byte *sourceData, int blobCount, byte *destPtr) {
uint16 dx1 = Au_Prev1;
uint16 dx2 = Au_Prev2;
uint16 *destP = (uint16 *)destPtr;
const int8 *srcP = (const int8 *)sourceData;
// Blob Loop
while (blobCount-- > 0) {
uint32 ebx = (uint8) *srcP++;
uint32 ebp = ebx & 0x1E;
int blobSize = SAM_P_BLOB / 2;
ebx = (((ebx & 0x0F) << 4) | ((ebx & 0xF0) >> 4)) & 0x1E;
ebp = Au_DecTable[ebp >> 1];
ebx = Au_DecTable[ebx >> 1];
// Inner loop
while (blobSize-- > 0) {
uint32 s1 = (((int32) *srcP++) * ((int32) ebp)) >> 5;
uint32 s2 = (((int32) *srcP++) * ((int32) ebx)) >> 5;
dx1 += s1 & 0xFFFF;
dx2 += s2 & 0xFFFF;
*destP++ = TO_BE_16(dx1);
*destP++ = TO_BE_16(dx2);
}
}
Au_Prev1 = dx1;
Au_Prev2 = dx2;
}
//----------------- BMV FUNCTIONS ----------------------------
BMVPlayer::BMVPlayer() {
bOldAudio = 0;
bMovieOn = 0;
bAbort = 0;
bmvEscape = 0;
memset(szMovieFile, 0, sizeof(szMovieFile));
bigBuffer = 0;
nextUseOffset = 0;
nextSoundOffset = 0;
wrapUseOffset = 0;
mostFutureOffset = 0;
currentFrame = 0;
currentSoundFrame = 0;
numAdvancePackets = 0;
nextReadSlot = 0;
bFileEnd = 0;
memset(moviePal, 0, sizeof(moviePal));
blobsInBuffer = 0;
memset(texts, 0, sizeof(texts));
talkColour = 0;
bigProblemCount = 0;
bIsText = 0;
movieTick = 0;
startTick = 0;
nextMovieTime = 0;
Au_Prev1 = 0;
Au_Prev2 = 0;
ScreenBeg = 0;
screenBuffer = 0;
audioStarted = 0;
_audioStream = 0;
nextMaintain = 0;
}
/**
* Called when a packet contains a palette field.
* Build a COLORREF array and queue it to the DAC.
*/
void BMVPlayer::MoviePalette(int paletteOffset) {
int i;
byte *r;
r = bigBuffer + paletteOffset;
for (i = 0; i < 256; i++, r += 3) {
moviePal[i] = TINSEL_RGB(*r, *(r + 1), *(r + 2));
}
UpdateDACqueue(1, 255, &moviePal[1]);
// Don't clobber talk
if (talkColour != 0)
SetTextPal(talkColour);
}
void BMVPlayer::InitialiseMovieSound() {
_audioStream = Audio::makeQueuingAudioStream(22050, true);
audioStarted = false;
}
void BMVPlayer::StartMovieSound() {
}
void BMVPlayer::FinishMovieSound() {
if (_audioStream) {
_vm->_mixer->stopHandle(_audioHandle);
delete _audioStream;
_audioStream = 0;
}
}
/**
* Called when a packet contains an audio field.
*/
void BMVPlayer::MovieAudio(int audioOffset, int blobs) {
if (audioOffset == 0 && blobs == 0)
blobs = 57;
byte *data = (byte *)malloc(blobs * 128);
if (audioOffset != 0)
PrepAudio(bigBuffer+audioOffset, blobs, data);
else
memset(data, 0, blobs * 128);
_audioStream->queueBuffer(data, blobs * 128, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_STEREO);
if (currentSoundFrame == ADVANCE_SOUND) {
if (!audioStarted) {
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType,
&_audioHandle, _audioStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO);
audioStarted = true;
}
}
}
/*-----------------------------------------------------*\
|-------------------------------------------------------|
\*-----------------------------------------------------*/
void BMVPlayer::FettleMovieText() {
int i;
bIsText = false;
for (i = 0; i < 2; i++) {
if (texts[i].pText) {
if (currentFrame > texts[i].dieFrame) {
MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText);
texts[i].pText = NULL;
} else {
MultiForceRedraw(texts[i].pText);
bIsText = true;
}
}
}
}
/*-----------------------------------------------------*\
|-------------------------------------------------------|
\*-----------------------------------------------------*/
void BMVPlayer::BmvDrawText(bool bDraw) {
int w, h, x, y;
for (int i = 0; i < 2; i++) {
if (texts[i].pText) {
x = MultiLeftmost(texts[i].pText);
y = MultiHighest(texts[i].pText);
w = MIN(MultiRightmost(texts[i].pText) + 1, (int)SCREEN_WIDTH) - x;
h = MIN(MultiLowest(texts[i].pText) + 1, SCREEN_HIGH) - y;
const byte *src = ScreenBeg + (y * SCREEN_WIDTH) + x;
byte *dest = (byte *)_vm->screen().getBasePtr(x, y);
for (int j = 0; j < h; j++, dest += SCREEN_WIDTH, src += SCREEN_WIDTH) {
memcpy(dest, src, w);
}
if (bDraw) {
Common::Point ptWin;
Common::Rect rcPlayClip;
ptWin.x = ptWin.y = 0;
rcPlayClip.left = x;
rcPlayClip.top = y;
rcPlayClip.right = x+w;
rcPlayClip.bottom = y+h;
UpdateClipRect(GetPlayfieldList(FIELD_STATUS), &ptWin, &rcPlayClip);
}
}
}
}
/*-----------------------------------------------------*\
|-------------------------------------------------------|
\*-----------------------------------------------------*/
void BMVPlayer::MovieText(CORO_PARAM, int stringId, int x, int y, int fontId, COLORREF *pTalkColour, int duration) {
SCNHANDLE hFont;
int index;
if (fontId == 1) {
// It's a 'print'
hFont = GetTagFontHandle();
index = 0;
} else {
// It's a 'talk'
if (pTalkColour != NULL)
SetTextPal(*pTalkColour);
hFont = GetTalkFontHandle();
index = 1;
}
if (texts[index].pText)
MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[index].pText);
LoadSubString(stringId, 0, TextBufferAddr(), TBUFSZ);
texts[index].dieFrame = currentFrame + duration;
texts[index].pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
TextBufferAddr(),
0,
x, y,
hFont,
TXT_CENTRE, 0);
KeepOnScreen(texts[index].pText, &x, &y);
}
/**
* Called when a packet contains a command field.
*/
int BMVPlayer::MovieCommand(char cmd, int commandOffset) {
if (cmd & CD_PRINT) {
PRINT_CMD *pCmd = (PRINT_CMD *)(bigBuffer + commandOffset);
MovieText(nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
(int16)READ_LE_UINT16(&pCmd->x),
(int16)READ_LE_UINT16(&pCmd->y),
pCmd->fontId,
NULL,
pCmd->duration);
return sz_CMD_PRINT_pkt;
} else {
if (_vm->_config->_useSubtitles) {
TALK_CMD *pCmd = (TALK_CMD *)(bigBuffer + commandOffset);
talkColour = TINSEL_RGB(pCmd->r, pCmd->g, pCmd->b);
MovieText(nullContext, (int16)READ_LE_UINT16(&pCmd->stringId),
(int16)READ_LE_UINT16(&pCmd->x),
(int16)READ_LE_UINT16(&pCmd->y),
0,
&talkColour,
pCmd->duration);
}
return sz_CMD_TALK_pkt;
}
}
/**
* Called from PlayMovie() in tinlib.cpp
* Kicks off the playback of a movie, and waits around
* until it's finished.
*/
void BMVPlayer::PlayBMV(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) {
CORO_BEGIN_CONTEXT;
CORO_END_CONTEXT(_ctx);
CORO_BEGIN_CODE(_ctx);
assert(!bMovieOn);
strcpy(szMovieFile, (char *)LockMem(hFileStem));
strcat(szMovieFile, BMOVIE_EXTENSION);
assert(strlen(szMovieFile) <= 12);
bMovieOn = true;
bAbort = false;
bmvEscape = myEscape;
do {
CORO_SLEEP(1);
} while (bMovieOn);
CORO_END_CODE;
}
/**
* Given a packet offset, calculates the offset of the
* next packet. The packet may not yet exist, and the
*return value may be off the end of bigBuffer.
*/
int BMVPlayer::FollowingPacket(int thisPacket, bool bReallyImportant) {
unsigned char *data;
int nextSlot, length;
// Set pointer to thisPacket's data
data = bigBuffer + thisPacket;
switch (*data) {
case CD_SLOT_NOP:
nextSlot = thisPacket/SLOT_SIZE;
if (thisPacket%SLOT_SIZE)
nextSlot++;
return nextSlot * SLOT_SIZE;
case CD_LE_FIN:
return -1;
default:
// Following 3 bytes are the length
if (bReallyImportant) {
// wrapped round or at least 3 bytes
assert(((nextReadSlot * SLOT_SIZE) < thisPacket) ||
((thisPacket + 3) < (nextReadSlot * SLOT_SIZE)));
if ((nextReadSlot * SLOT_SIZE >= thisPacket) &&
((thisPacket + 3) >= nextReadSlot*SLOT_SIZE)) {
// MaintainBuffer calls this back, but with false
MaintainBuffer();
}
} else {
// not wrapped and not 3 bytes
if (nextReadSlot*SLOT_SIZE >= thisPacket && thisPacket+3 >= nextReadSlot*SLOT_SIZE)
return thisPacket + 3;
}
length = (int32)READ_LE_UINT32(bigBuffer + thisPacket + 1);
length &= 0x00ffffff;
return thisPacket + length + 4;
}
}
/**
* Called from the foreground when starting playback of a movie.
*/
void BMVPlayer::LoadSlots(int number) {
int nextOffset;
assert(number + nextReadSlot < NUM_SLOTS);
if (stream.read(bigBuffer + nextReadSlot*SLOT_SIZE, number * SLOT_SIZE) !=
(uint32)(number * SLOT_SIZE)) {
int possibleSlots;
// May be a short file
possibleSlots = stream.size() / SLOT_SIZE;
if ((number + nextReadSlot) > possibleSlots) {
bFileEnd = true;
nextReadSlot = possibleSlots;
} else
error(FILE_IS_CORRUPT, szMovieFile);
}
nextReadSlot += number;
nextOffset = FollowingPacket(nextUseOffset, true);
while (nextOffset < nextReadSlot*SLOT_SIZE
&& nextOffset != -1) {
numAdvancePackets++;
mostFutureOffset = nextOffset;
nextOffset = FollowingPacket(mostFutureOffset, false);
}
}
/**
* Called from the foreground when starting playback of a movie.
*/
void BMVPlayer::InitialiseBMV() {
if (!stream.open(szMovieFile))
error(CANNOT_FIND_FILE, szMovieFile);
// Grab the data buffer
bigBuffer = (byte *)malloc(NUM_SLOTS * SLOT_SIZE);
if (bigBuffer == NULL)
error(NO_MEM, "FMV data buffer");
// Screen buffer (2 lines more than screen
screenBuffer = (byte *)malloc(SCREEN_WIDTH * (SCREEN_HIGH + 2));
if (screenBuffer == NULL)
error(NO_MEM, "FMV screen buffer");
// Pass the sceen buffer to the decompresser
InitBMV(screenBuffer);
// Initialise some stuff
nextUseOffset = 0;
nextSoundOffset = 0;
wrapUseOffset = -1;
mostFutureOffset = 0;
currentFrame = 0;
currentSoundFrame = 0;
numAdvancePackets = 0;
nextReadSlot = 0;
bFileEnd = false;
blobsInBuffer = 0;
memset(texts, 0, sizeof(texts));
talkColour = 0;
bigProblemCount = 0;
movieTick = 0;
bIsText = false;
// Prefetch data
LoadSlots(PREFETCH);
while (numAdvancePackets < ADVANCE_SOUND)
LoadSlots(1);
// Initialise the sound channel
InitialiseMovieSound();
}
/**
* Called from the foreground when ending playback of a movie.
*/
void BMVPlayer::FinishBMV() {
int i;
// Notify the sound channel
FinishMovieSound();
// Close the file stream
if (stream.isOpen())
stream.close();
// Release the data buffer
free(bigBuffer);
bigBuffer = NULL;
// Release the screen buffer
free(screenBuffer);
screenBuffer = NULL;
// Ditch any text objects
for (i = 0; i < 2; i++) {
if (texts[i].pText) {
MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), texts[i].pText);
texts[i].pText = NULL;
}
}
bMovieOn = false;
nextMovieTime = 0;
// Test for 'twixt-movie glitch
ClearScreen();
}
/**
* MaintainBuffer()
*/
bool BMVPlayer::MaintainBuffer() {
int nextOffset;
// No action if the file is all read
if (bFileEnd == true)
return false;
// See if next complete packet exists
// and if so, if it will fit in the top of the buffer
nextOffset = FollowingPacket(mostFutureOffset, false);
if (nextOffset == -1) {
// No following packets
return false;
} else if (nextOffset > NUM_SLOTS * SLOT_SIZE) {
// The current unfinished packet will not fit
// Copy this slot to slot 0
// Not if we're still using it!!!
// Or, indeed, if the player is still lagging
if (nextUseOffset < SLOT_SIZE || nextUseOffset > mostFutureOffset) {
// Slot 0 is still in use, buffer is full!
return false;
}
// Tell data player where to make the jump
wrapUseOffset = mostFutureOffset;
// mostFuture Offset is now in slot 0
mostFutureOffset %= SLOT_SIZE;
// Copy the data we already have for unfinished packet
memcpy(bigBuffer + mostFutureOffset,
bigBuffer + wrapUseOffset,
SLOT_SIZE - mostFutureOffset);
// Next read is into slot 1
nextReadSlot = 1;
}
if (nextReadSlot == NUM_SLOTS) {
// Want to go to slot zero, wait if still in use
if (nextUseOffset < SLOT_SIZE) {
// Slot 0 is still in use, buffer is full!
return false;
}
// nextOffset must be the buffer size
assert(nextOffset == NUM_SLOTS*SLOT_SIZE);
// wrapUseOffset must not be set
assert(wrapUseOffset == -1);
wrapUseOffset = nextOffset;
nextReadSlot = 0;
mostFutureOffset = 0;
}
// Don't overwrite unused data
if (nextUseOffset / SLOT_SIZE == nextReadSlot) {
// Buffer is full!
return false;
}
if (stream.read(bigBuffer + nextReadSlot * SLOT_SIZE, SLOT_SIZE) != SLOT_SIZE) {
bFileEnd = true;
}
// Read next slot next time
nextReadSlot++;
// Find new mostFutureOffset
nextOffset = FollowingPacket(mostFutureOffset, false);
while (nextOffset < nextReadSlot*SLOT_SIZE
&& nextOffset != -1) {
numAdvancePackets++;
mostFutureOffset = nextOffset;
nextOffset = FollowingPacket(mostFutureOffset, false);
}
// New test feature for e.g. short files
if (bFileEnd && *(bigBuffer+mostFutureOffset) != CD_LE_FIN)
bAbort = true;
return true;
}
/**
* DoBMVFrame
*/
bool BMVPlayer::DoBMVFrame() {
unsigned char *data;
int graphOffset, length;
signed short xscr;
if (nextUseOffset == wrapUseOffset) {
nextUseOffset %= SLOT_SIZE;
}
while (nextUseOffset == mostFutureOffset) {
data = bigBuffer + nextUseOffset;
if (*data != CD_LE_FIN) {
// Don't get stuck in an infinite loop
if (!MaintainBuffer()) {
FinishBMV();
return false;
}
if (nextUseOffset == wrapUseOffset) {
nextUseOffset %= SLOT_SIZE;
}
} else
break;
}
// Set pointer to data
data = bigBuffer + nextUseOffset;
// If still at most Future, it must be last
if (nextUseOffset == mostFutureOffset) {
assert(*data == CD_LE_FIN);
}
switch (*data) {
case CD_SLOT_NOP:
nextUseOffset = FollowingPacket(nextUseOffset, true);
if (nextUseOffset == wrapUseOffset) {
nextUseOffset %= SLOT_SIZE;
wrapUseOffset = -1;
}
numAdvancePackets--;
return false;
case CD_LE_FIN:
FinishBMV();
numAdvancePackets--;
return true;
default:
length = (int32)READ_LE_UINT32(data + 1);
length &= 0x00ffffff;
graphOffset = nextUseOffset + 4; // Skip command byte and length
if (*data & CD_AUDIO) {
if (bOldAudio) {
graphOffset += sz_AUDIO_pkt; // Skip audio data
length -= sz_AUDIO_pkt;
} else {
int blobs;
blobs = *(bigBuffer + graphOffset);
blobs *= SZ_C_BLOB;
graphOffset += (blobs + 1);
length -= (blobs + 1);
}
}
if (*data & CD_CMND) {
int cmdLen;
// Process command and skip data
cmdLen = MovieCommand(*data, graphOffset);
graphOffset += cmdLen;
length -= cmdLen;
}
if (*data & CD_CMAP) {
MoviePalette(graphOffset);
graphOffset += sz_CMAP_pkt; // Skip palette data
length -= sz_CMAP_pkt;
}
if (*data & CD_XSCR) {
xscr = (int16)READ_LE_UINT16(bigBuffer + graphOffset);
graphOffset += sz_XSCR_pkt; // Skip scroll offset
length -= sz_XSCR_pkt;
} else if (*data & BIT0)
xscr = -640;
else
xscr = 0;
PrepBMV(ScreenBeg, bigBuffer + graphOffset, length, xscr);
currentFrame++;
numAdvancePackets--;
nextUseOffset = FollowingPacket(nextUseOffset, true);
if (nextUseOffset == wrapUseOffset) {
nextUseOffset %= SLOT_SIZE;
wrapUseOffset = -1;
}
return true;
}
}
/**
* DoSoundFrame
*/
bool BMVPlayer::DoSoundFrame() {
unsigned char *data;
int graphOffset;
if (nextSoundOffset == wrapUseOffset) {
nextSoundOffset %= SLOT_SIZE;
}
// Make sure the full slot is here
while (nextSoundOffset == mostFutureOffset) {
data = bigBuffer + nextSoundOffset;
if (*data != CD_LE_FIN) {
// Don't get stuck in an infinite loop
if (!MaintainBuffer()) {
if (!bOldAudio)
MovieAudio(0, 0);
currentSoundFrame++;
return false;
}
if (nextSoundOffset == wrapUseOffset) {
nextSoundOffset %= SLOT_SIZE;
}
} else
break;
}
// Set pointer to data
data = bigBuffer + nextSoundOffset;
// If still at most Future, it must be last
if (nextSoundOffset == mostFutureOffset) {
assert(*data == CD_LE_FIN);
}
switch (*data) {
case CD_SLOT_NOP:
nextSoundOffset = FollowingPacket(nextSoundOffset, true);
if (nextSoundOffset == wrapUseOffset) {
nextSoundOffset %= SLOT_SIZE;
}
return false;
case CD_LE_FIN:
if (!bOldAudio)
MovieAudio(0, 0);
currentSoundFrame++;
return true;
default:
if (*data & CD_AUDIO) {
graphOffset = nextSoundOffset + 4; // Skip command byte and length
if (!bOldAudio) {
int blobs = *(bigBuffer + graphOffset);
MovieAudio(graphOffset+1, blobs);
}
} else {
if (!bOldAudio)
MovieAudio(0, 0);
}
nextSoundOffset = FollowingPacket(nextSoundOffset, false);
if (nextSoundOffset == wrapUseOffset) {
nextSoundOffset %= SLOT_SIZE;
}
currentSoundFrame++;
return true;
}
}
/**
* CopyMovieToScreen
*/
void BMVPlayer::CopyMovieToScreen() {
// Not if not up and running yet!
if (!screenBuffer || (currentFrame == 0)) {
ForceEntireRedraw();
DrawBackgnd();
return;
}
// The movie surface is slightly less high than the output screen (429 rows versus 432).
// Because of this, there's some extra line clearing above and below the displayed area
int yStart = (SCREEN_HEIGHT - SCREEN_HIGH) / 2;
memset(_vm->screen().getBasePtr(0, 0), 0, yStart * SCREEN_WIDTH);
memcpy(_vm->screen().getBasePtr(0, yStart), ScreenBeg, SCREEN_WIDTH * SCREEN_HIGH);
memset(_vm->screen().getBasePtr(0, yStart + SCREEN_HIGH), 0,
(SCREEN_HEIGHT - SCREEN_HIGH - yStart) * SCREEN_WIDTH);
BmvDrawText(true);
PalettesToVideoDAC(); // Keep palette up-to-date
UpdateScreenRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
g_system->updateScreen();
BmvDrawText(false);
}
/**
* LookAtBuffers
*/
void BMVPlayer::LookAtBuffers() {
// FIXME: What's the point of this function???
// Maybe to ensure the relevant data is loaded into cache by the CPU?
static int junk; // FIXME: Avoid non-const global vars
int i;
if (bigBuffer) {
for (i = 0; i < NUM_SLOTS; i++)
junk += bigBuffer[i*SLOT_SIZE];
}
}
/**
* Handles playback of any active movie. Called from the foreground 24 times a second.
*/
void BMVPlayer::FettleBMV() {
int refFrame;
// Tick counter needs to be incrementing at a 24Hz rate
int tick = movieTick++;
if (!bMovieOn)
return;
// Escape the rest if appropriate
if (bAbort || (bmvEscape && bmvEscape != GetEscEvents())) {
FinishBMV();
return;
}
LookAtBuffers();
if (!stream.isOpen()) {
int i;
// First time in with this movie
InitialiseBMV();
for (i = 0; i < ADVANCE_SOUND;) {
if (DoSoundFrame())
i++;
}
startTick = -ONE_SECOND / 4; // 1/4 second
return;
}
if (startTick < 0) {
startTick++;
return;
}
if (startTick == 0) {
startTick = tick;
nextMaintain = startTick + 1;
StartMovieSound();
}
nextMovieTime = g_system->getMillis() + 41;
FettleMovieText();
if (bigProblemCount < PT_A) {
refFrame = currentSoundFrame;
while (currentSoundFrame < ((tick+1-startTick)/2 + ADVANCE_SOUND) && bMovieOn) {
if (currentSoundFrame == refFrame+PT_B)
break;
DoSoundFrame();
}
}
// Time to process a frame (or maybe more)
if (bigProblemCount < PT_A) {
refFrame = currentFrame;
while ((currentFrame < (tick-startTick)/2) && bMovieOn) {
DoBMVFrame();
if (currentFrame == refFrame+PT_B) {
bigProblemCount++;
if (bigProblemCount == PT_A) {
startTick = tick-(2*currentFrame);
bigProblemCount = 0;
}
break;
}
}
if (currentFrame == refFrame || currentFrame <= refFrame+3) {
bigProblemCount = 0;
}
} else {
while (currentFrame < (tick-startTick)/2 && bMovieOn) {
DoBMVFrame();
}
}
if (tick >= nextMaintain || numAdvancePackets < SUBSEQUENT_SOUND) {
MaintainBuffer();
nextMaintain = tick + 2;
}
}
/**
* Returns true if a movie is playing.
*/
bool BMVPlayer::MoviePlaying() {
return bMovieOn;
}
/**
* Returns the audio lag in ms
*/
int32 BMVPlayer::MovieAudioLag() {
if (!bMovieOn || !_audioStream)
return 0;
// Calculate lag
int32 playLength = (movieTick - startTick - 1) * ((((uint32) 1000) << 10) / 24);
return (playLength - (((int32) _vm->_mixer->getSoundElapsedTime(_audioHandle)) << 10)) >> 10;
}
uint32 BMVPlayer::NextMovieTime() {
return nextMovieTime;
}
void BMVPlayer::AbortMovie() {
bAbort = true;
}
} // End of namespace Tinsel