scummvm/engines/tinsel/bmv.cpp
Torbjörn Andersson 0999534749 The error() and warning() functions add ! and newline automatically. (I didn't
look at debug() and debugC(), since I'm really bored with this now. :-)

svn-id: r41061
2009-05-31 10:02:16 +00:00

1273 lines
27 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 "common/file.h"
#include "sound/mixer.h"
#include "sound/audiostream.h"
#include "tinsel/tinsel.h"
#include "tinsel/background.h"
#include "tinsel/cliprect.h"
#include "tinsel/coroutine.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/heapmem.h"
#include "tinsel/multiobj.h"
#include "tinsel/object.h"
#include "tinsel/palette.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"
namespace Tinsel {
//----------------- GLOBAL GLOBAL DATA ------------------------
bool bOldAudio;
//----------------- LOCAL GLOBAL DATA ------------------------
//static READREQ rr;
//----------------- LOCAL DEFINES ----------------------------
#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
#ifndef _Windows
//#define ADVANCE_SOUND 12 // 1 second
#define ADVANCE_SOUND 18 // 1 1/2 second
//#define MAX_ADVANCE_SOUND 36 // 3 seconds
#else
#define ADVANCE_SOUND 18 // 1 1/2 seconds
#endif
#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
typedef struct {
short x;
short y;
short stringId;
unsigned char duration;
char r; // may be b!
char g;
char b; // may be r!
} TALK_CMD, *PTALK_CMD;
typedef struct {
int16 x;
int16 y;
int16 stringId;
unsigned char duration;
unsigned char fontId;
} PRINT_CMD, *PPRINT_CMD;
//----------------- LOCAL GLOBAL DATA ------------------------
// Set when a movie is on
static bool bMovieOn;
// Set to kill one off
static bool bAbort;
// For escaping out of movies
static int bmvEscape;
// Movie file pointer
static Common::File stream;
// Movie file name
static char szMovieFile[14];
// Pointers to buffers
static byte *bigBuffer; //, *screenBuffer;
// Next data to use to extract a frame
static int nextUseOffset;
// Next data to use to extract sound data
static int nextSoundOffset;
// When above offset gets to what this is set at, rewind
static int wrapUseOffset;
// The offset of the most future packet
static int mostFutureOffset;
// The current frame
static int currentFrame;
static int currentSoundFrame;
// Number of packets currently in RAM
static int numAdvancePackets;
// Next slot that will be read from disc
static int nextReadSlot;
// Set when the whole file has been read
static bool bFileEnd;
// Palette
static COLORREF moviePal[256];
static int blobsInBuffer;
static struct {
POBJECT pText;
int dieFrame;
} texts[2];
static COLORREF talkColour;
static int bigProblemCount;
static bool bIsText;
static int movieTick;
static int startTick;
static uint32 nextMovieTime = 0;
static int nowTick;
static uint16 Au_Prev1 = 0;
static uint16 Au_Prev2 = 0;
static byte *ScreenBeg;
static byte *screenBuffer;
static bool audioStarted;
static Audio::AppendableAudioStream *audioStream = 0;
static Audio::SoundHandle audioHandle;
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(const byte *sourceData, int length, short deltaFetchDisp) {
uint8 NibbleHi = 0;
const byte *saved_esi;
uint32 eax = 0;
uint32 edx = length;
int32 ebx = deltaFetchDisp;
uint32 ecx = 0;
const byte *esi;
byte *edi, *ebp;
bool forwardDirection = (deltaFetchDisp <= -SCREEN_WIDE) || (deltaFetchDisp >= 0);
if (forwardDirection) {
// Forward decompression
esi = sourceData;
edi = ScreenBeg;
ebp = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH;
} else {
esi = sourceData + length - 1;
edi = ScreenBeg + SCREEN_WIDE * SCREEN_HIGH - 1;
ebp = ScreenBeg - 1;
}
bool firstLoop, flag;
int loopCtr = 0;
for (;;) {
flag = false;
if ((loopCtr == 0) || (edx == 4)) {
// Get the next hi,lo nibble
eax = (eax & 0xffffff00) | *esi;
firstLoop = true;
} else {
// Get the high nibble
eax = (eax & 0xffffff00) | (NibbleHi >> 4);
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);
ecx += 2;
eax = (eax & 0xffffff00) | *esi;
if ((eax & 0xC) != 0)
break;
}
firstLoop = false;
//@_rD2nd_1:
ROR(eax, 2); // Save bi-bit into hi 2 bits
ecx += 2; // and increase bit-shifter
// Shift another 2 bits to get hi nibble
eax = (eax & 0xffffff00) | ((eax & 0xff) >> 2);
NEXT_BYTE(esi);
if ((eax & 0xC) != 0) {
flag = true;
ROL(eax, ecx);
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;
edx = 0; // offset rDNum_Hi ; Next nibble is a 'hi' (reserved)
eax &= 0xffffff0f;
NEXT_BYTE(esi);
ROL(eax, ecx);
}
//rDN_1:
//@_rD_or_R:
bool actionFlag = (eax & 1) != 0;
eax >>= 1;
ecx = eax - 1;
// Move to next loop index
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:
saved_esi = esi; // Save the source pointer
esi = edi + ebx; // Point it to existing data
while (ecx > 0) {
*edi = *esi;
NEXT_BYTE(esi);
NEXT_BYTE(edi);
--ecx;
}
esi = saved_esi;
break;
case 2:
// @_rRaw
// Copy data from source to dest
while (ecx > 0) {
*edi = *esi;
NEXT_BYTE(esi);
NEXT_BYTE(edi);
--ecx;
}
break;
case 3:
// @_rRun
// Repeating run of data
eax = forwardDirection ? *(edi - 1) : *(edi + 1);
while (ecx > 0) {
*edi = (uint8)eax;
NEXT_BYTE(edi);
--ecx;
}
break;
default:
break;
}
if (edi == ebp)
break; // Exit if complete
eax = 0;
}
}
static void 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 PrepAudio(const byte *sourceData, int blobCount, byte *destPtr) {
uint16 dx1 = Au_Prev1;
uint16 dx2 = Au_Prev2;
uint16 *destP = (uint16 *) destPtr;
int8 *srcP = (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 ----------------------------
static bool MaintainBuffer(void);
/**
* Called when a packet contains a palette field.
* Build a COLORREF array and queue it to the DAC.
*/
static void 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);
}
static void InitialiseMovieSound() {
audioStream =
Audio::makeAppendableAudioStream(22050,
Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_STEREO);
audioStarted = false;
}
static void StartMovieSound() {
}
static void FinishMovieSound() {
if (audioStream) {
_vm->_mixer->stopHandle(audioHandle);
delete audioStream;
audioStream = 0;
}
}
/**
* Called when a packet contains an audio field.
*/
static void MovieAudio(int audioOffset, int blobs) {
if (audioOffset == 0 && blobs == 0)
blobs = 57;
byte *data = new byte[blobs * 128];
if (audioOffset != 0)
PrepAudio(bigBuffer+audioOffset, blobs, data);
else
memset(data, 0, blobs * 128);
audioStream->queueBuffer(data, blobs * 128);
if (currentSoundFrame == ADVANCE_SOUND) {
if (!audioStarted) {
_vm->_mixer->playInputStream(Audio::Mixer::kSFXSoundType,
&audioHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume, 0, false);
audioStarted = true;
}
}
}
/*-----------------------------------------------------*\
|-------------------------------------------------------|
\*-----------------------------------------------------*/
void FettleMovieText(void) {
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 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 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(coroParam, 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.
*/
static int MovieCommand(char cmd, int commandOffset) {
if (cmd & CD_PRINT) {
PPRINT_CMD pCmd;
pCmd = (PPRINT_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 (bSubtitles) {
PTALK_CMD pCmd;
pCmd = (PTALK_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 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.
*/
static int 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.
*/
static void 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.
*/
static void InitialiseBMV(void) {
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;
// memset(&rr, 0, sizeof(rr));
// 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 FinishBMV(void) {
int i;
// Notify the sound channel
FinishMovieSound();
// Close the file stream
if (stream.isOpen())
stream.close();
// Release the data buffer
if (bigBuffer != NULL) {
free(bigBuffer);
bigBuffer = NULL;
}
// Release the screen buffer
if (screenBuffer != NULL) {
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()
*/
static bool MaintainBuffer(void) {
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
*/
static bool DoBMVFrame(void) {
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(bigBuffer + graphOffset, length, xscr);
currentFrame++;
numAdvancePackets--;
nextUseOffset = FollowingPacket(nextUseOffset, true);
if (nextUseOffset == wrapUseOffset) {
nextUseOffset %= SLOT_SIZE;
wrapUseOffset = -1;
}
return true;
}
}
/**
* DoSoundFrame
*/
static bool DoSoundFrame(void) {
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;
}
return true;
}
/**
* CopyMovieToScreen
*/
void CopyMovieToScreen(void) {
// 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));
BmvDrawText(false);
}
/**
* LookAtBuffers
*/
static void LookAtBuffers(void) {
static int junk;
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 FettleBMV(void) {
static int nextMaintain = 0;
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 MoviePlaying(void) {
return bMovieOn;
}
/**
* Returns the audio lag in ms
*/
int32 MovieAudioLag(void) {
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 NextMovieTime(void) {
return nextMovieTime;
}
void AbortMovie(void) {
bAbort = true;
}
void SlowMovieDown(void) {
bigProblemCount = 0;
if (currentFrame < (nowTick-startTick)/2 && bMovieOn) {
startTick = nowTick - 2*currentFrame;
} else
startTick += 2;
}
void SpeedMovieUp(void) {
if (!bigProblemCount) {
startTick -= 2;
}
}
} // end of namespace Tinsel