2054 lines
58 KiB
C++
2054 lines
58 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/file.h"
|
|
#include "common/system.h"
|
|
#include "common/util.h"
|
|
#include "common/rect.h"
|
|
|
|
#include "audio/mixer.h"
|
|
|
|
#include "graphics/cursorman.h"
|
|
#include "graphics/palette.h"
|
|
|
|
#include "scumm/file.h"
|
|
#include "scumm/imuse_digi/dimuse_engine.h"
|
|
#include "scumm/scumm.h"
|
|
#include "scumm/scumm_v7.h"
|
|
#include "scumm/sound.h"
|
|
#include "scumm/smush/codec37.h"
|
|
#include "scumm/smush/codec47.h"
|
|
#include "scumm/smush/smush_font.h"
|
|
#include "scumm/smush/smush_player.h"
|
|
|
|
#include "scumm/insane/insane.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/mixer.h"
|
|
#include "audio/decoders/mp3.h"
|
|
#include "audio/decoders/raw.h"
|
|
#include "audio/decoders/vorbis.h"
|
|
|
|
#include "common/zlib.h"
|
|
|
|
namespace Scumm {
|
|
|
|
static const int MAX_STRINGS = 200;
|
|
static const int ETRS_HEADER_LENGTH = 16;
|
|
|
|
class StringResource {
|
|
private:
|
|
|
|
struct {
|
|
int id;
|
|
char *string;
|
|
} _strings[MAX_STRINGS];
|
|
|
|
int _nbStrings;
|
|
int _lastId;
|
|
const char *_lastString;
|
|
|
|
public:
|
|
|
|
StringResource() :
|
|
_nbStrings(0),
|
|
_lastId(-1),
|
|
_lastString(nullptr) {
|
|
for (int i = 0; i < MAX_STRINGS; i++) {
|
|
_strings[i].id = 0;
|
|
_strings[i].string = nullptr;
|
|
}
|
|
}
|
|
~StringResource() {
|
|
for (int32 i = 0; i < _nbStrings; i++) {
|
|
delete[] _strings[i].string;
|
|
}
|
|
}
|
|
|
|
bool init(char *buffer, int32 length) {
|
|
char *def_start = strchr(buffer, '#');
|
|
while (def_start != nullptr) {
|
|
char *def_end = strchr(def_start, '\n');
|
|
assert(def_end != nullptr);
|
|
|
|
char *id_end = def_end;
|
|
while (id_end >= def_start && !Common::isDigit(*(id_end-1))) {
|
|
id_end--;
|
|
}
|
|
|
|
assert(id_end > def_start);
|
|
char *id_start = id_end;
|
|
while (Common::isDigit(*(id_start - 1))) {
|
|
id_start--;
|
|
}
|
|
|
|
char idstring[32];
|
|
memcpy(idstring, id_start, id_end - id_start);
|
|
idstring[id_end - id_start] = 0;
|
|
int32 id = atoi(idstring);
|
|
char *data_start = def_end;
|
|
|
|
while (*data_start == '\n' || *data_start == '\r') {
|
|
data_start++;
|
|
}
|
|
char *data_end = data_start;
|
|
|
|
while (1) {
|
|
if (data_end[-2] == '\r' && data_end[-1] == '\n' && data_end[0] == '\r' && data_end[1] == '\n') {
|
|
break;
|
|
}
|
|
// In the Steam Mac version of The Dig, LF-LF is used
|
|
// instead of CR-LF
|
|
if (data_end[-2] == '\n' && data_end[-1] == '\n') {
|
|
break;
|
|
}
|
|
// In Russian Full Throttle strings are finished with
|
|
// just one pair of CR-LF
|
|
if (data_end[-2] == '\r' && data_end[-1] == '\n' && data_end[0] == '#') {
|
|
break;
|
|
}
|
|
data_end++;
|
|
if (data_end >= buffer + length) {
|
|
data_end = buffer + length;
|
|
break;
|
|
}
|
|
}
|
|
|
|
data_end -= 2;
|
|
assert(data_end > data_start);
|
|
char *value = new char[data_end - data_start + 1];
|
|
assert(value);
|
|
memcpy(value, data_start, data_end - data_start);
|
|
value[data_end - data_start] = 0;
|
|
char *line_start = value;
|
|
char *line_end;
|
|
|
|
while ((line_end = strchr(line_start, '\n'))) {
|
|
line_start = line_end+1;
|
|
if (line_start[0] == '/' && line_start[1] == '/') {
|
|
line_start += 2;
|
|
if (line_end[-1] == '\r')
|
|
line_end[-1] = ' ';
|
|
else
|
|
*line_end++ = ' ';
|
|
memmove(line_end, line_start, strlen(line_start)+1);
|
|
}
|
|
}
|
|
_strings[_nbStrings].id = id;
|
|
_strings[_nbStrings].string = value;
|
|
_nbStrings ++;
|
|
def_start = strchr(data_end + 2, '#');
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const char *get(int id) {
|
|
if (id == _lastId) {
|
|
return _lastString;
|
|
}
|
|
debugC(DEBUG_SMUSH, "StringResource::get(%d)", id);
|
|
for (int i = 0; i < _nbStrings; i++) {
|
|
if (_strings[i].id == id) {
|
|
_lastId = id;
|
|
_lastString = _strings[i].string;
|
|
return _strings[i].string;
|
|
}
|
|
}
|
|
warning("invalid string id : %d", id);
|
|
_lastId = -1;
|
|
_lastString = "unknown string";
|
|
return _lastString;
|
|
}
|
|
};
|
|
|
|
static StringResource *getStrings(ScummEngine *vm, const char *file, bool is_encoded) {
|
|
debugC(DEBUG_SMUSH, "trying to read text resources from %s", file);
|
|
ScummFile theFile;
|
|
|
|
vm->openFile(theFile, file);
|
|
if (!theFile.isOpen()) {
|
|
return 0;
|
|
}
|
|
int32 length = theFile.size();
|
|
char *filebuffer = new char [length + 1];
|
|
assert(filebuffer);
|
|
theFile.read(filebuffer, length);
|
|
filebuffer[length] = 0;
|
|
|
|
if (is_encoded && READ_BE_UINT32(filebuffer) == MKTAG('E','T','R','S')) {
|
|
assert(length > ETRS_HEADER_LENGTH);
|
|
length -= ETRS_HEADER_LENGTH;
|
|
for (int i = 0; i < length; ++i) {
|
|
filebuffer[i] = filebuffer[i + ETRS_HEADER_LENGTH] ^ 0xCC;
|
|
}
|
|
filebuffer[length] = '\0';
|
|
}
|
|
StringResource *sr = new StringResource;
|
|
assert(sr);
|
|
sr->init(filebuffer, length);
|
|
delete[] filebuffer;
|
|
return sr;
|
|
}
|
|
|
|
void SmushPlayer::timerCallback() {
|
|
parseNextFrame();
|
|
}
|
|
|
|
SmushPlayer::SmushPlayer(ScummEngine_v7 *scumm, IMuseDigital *imuseDigital, Insane *insane) {
|
|
_vm = scumm;
|
|
_imuseDigital = imuseDigital;
|
|
_insane = insane;
|
|
_nbframes = 0;
|
|
_codec37 = 0;
|
|
_codec47 = 0;
|
|
_strings = nullptr;
|
|
_sf[0] = nullptr;
|
|
_sf[1] = nullptr;
|
|
_sf[2] = nullptr;
|
|
_sf[3] = nullptr;
|
|
_sf[4] = nullptr;
|
|
_base = nullptr;
|
|
_frameBuffer = nullptr;
|
|
_specialBuffer = nullptr;
|
|
|
|
_seekPos = -1;
|
|
|
|
_skipNext = false;
|
|
_dst = nullptr;
|
|
_storeFrame = false;
|
|
_compressedFileMode = false;
|
|
_width = 0;
|
|
_height = 0;
|
|
_IACTpos = 0;
|
|
_speed = -1;
|
|
_insanity = false;
|
|
_middleAudio = false;
|
|
_skipPalette = false;
|
|
_IACTstream = nullptr;
|
|
_paused = false;
|
|
_pauseStartTime = 0;
|
|
_pauseTime = 0;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
_iactTable[i] = 0;
|
|
|
|
_IACTchannel = new Audio::SoundHandle();
|
|
_compressedFileSoundHandle = new Audio::SoundHandle();
|
|
|
|
_smushNumTracks = 0;
|
|
_gainReductionLowerBound = 64;
|
|
_gainReductionFactor = 256;
|
|
_gainReductionMultiplier = 256;
|
|
_smushTracksNeedInit = true;
|
|
_smushAudioInitialized = false;
|
|
_smushAudioCallbackEnabled = false;
|
|
|
|
initAudio(DIMUSE_SAMPLERATE, 200000);
|
|
}
|
|
|
|
SmushPlayer::~SmushPlayer() {
|
|
delete _IACTchannel;
|
|
delete _compressedFileSoundHandle;
|
|
terminateAudio();
|
|
}
|
|
|
|
void SmushPlayer::init(int32 speed) {
|
|
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
|
|
|
|
_frame = 0;
|
|
_speed = speed;
|
|
_endOfFile = false;
|
|
|
|
_vm->_smushVideoShouldFinish = false;
|
|
_vm->_smushActive = true;
|
|
|
|
_vm->setDirtyColors(0, 255);
|
|
_dst = vs->getPixels(0, 0);
|
|
|
|
// HACK HACK HACK: This is an *evil* trick, beware!
|
|
// We do this to fix bug #1792. A proper solution would change all the
|
|
// drawing code to use the pitch value specified by the virtual screen.
|
|
// However, since a lot of the SMUSH code currently assumes the screen
|
|
// width and pitch to be equal, this will require lots of changes. So
|
|
// we resort to this hackish solution for now.
|
|
_origPitch = vs->pitch;
|
|
_origNumStrips = _vm->_gdi->_numStrips;
|
|
vs->pitch = vs->w;
|
|
_vm->_gdi->_numStrips = vs->w / 8;
|
|
|
|
_vm->_mixer->stopHandle(*_compressedFileSoundHandle);
|
|
_vm->_mixer->stopHandle(*_IACTchannel);
|
|
_IACTpos = 0;
|
|
}
|
|
|
|
void SmushPlayer::release() {
|
|
_vm->_smushVideoShouldFinish = true;
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
delete _sf[i];
|
|
_sf[i] = nullptr;
|
|
}
|
|
|
|
delete _strings;
|
|
_strings = nullptr;
|
|
|
|
delete _base;
|
|
_base = nullptr;
|
|
|
|
free(_specialBuffer);
|
|
_specialBuffer = nullptr;
|
|
|
|
free(_frameBuffer);
|
|
_frameBuffer = nullptr;
|
|
|
|
_IACTstream = nullptr;
|
|
|
|
_vm->_smushActive = false;
|
|
_vm->_fullRedraw = true;
|
|
|
|
// HACK HACK HACK: This is an *evil* trick, beware! See above for
|
|
// some explanation.
|
|
_vm->_virtscr[kMainVirtScreen].pitch = _origPitch;
|
|
_vm->_gdi->_numStrips = _origNumStrips;
|
|
|
|
delete _codec37;
|
|
_codec37 = 0;
|
|
delete _codec47;
|
|
_codec47 = 0;
|
|
}
|
|
|
|
void SmushPlayer::handleStore(int32 subSize, Common::SeekableReadStream &b) {
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::handleStore()");
|
|
assert(subSize >= 4);
|
|
_storeFrame = true;
|
|
}
|
|
|
|
void SmushPlayer::handleFetch(int32 subSize, Common::SeekableReadStream &b) {
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::handleFetch()");
|
|
assert(subSize >= 6);
|
|
|
|
if (_frameBuffer != nullptr) {
|
|
memcpy(_dst, _frameBuffer, _width * _height);
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleIACT(int32 subSize, Common::SeekableReadStream &b) {
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::IACT()");
|
|
assert(subSize >= 8);
|
|
|
|
int code = b.readUint16LE();
|
|
int flags = b.readUint16LE();
|
|
int unknown = b.readSint16LE();
|
|
int userId = b.readUint16LE();
|
|
|
|
if ((code != 8) && (flags != 46)) {
|
|
_vm->_insane->procIACT(_dst, 0, 0, 0, b, 0, 0, code, flags, unknown, userId);
|
|
return;
|
|
}
|
|
|
|
if (_compressedFileMode) {
|
|
return;
|
|
}
|
|
|
|
assert(flags == 46 && unknown == 0);
|
|
/*int trkId =*/ b.readUint16LE();
|
|
int index = b.readUint16LE();
|
|
int nbframes = b.readUint16LE();
|
|
/*int32 size =*/ b.readUint32LE();
|
|
int32 bsize = subSize - 18;
|
|
|
|
if (_vm->_game.id == GID_CMI) {
|
|
// TODO: Move this code into another SmushChannel subclass?
|
|
byte *src = (byte *)malloc(bsize);
|
|
b.read(src, bsize);
|
|
byte *d_src = src;
|
|
byte value;
|
|
|
|
while (bsize > 0) {
|
|
if (_IACTpos >= 2) {
|
|
int32 len = READ_BE_UINT16(_IACToutput) + 2;
|
|
len -= _IACTpos;
|
|
if (len > bsize) {
|
|
memcpy(_IACToutput + _IACTpos, d_src, bsize);
|
|
_IACTpos += bsize;
|
|
bsize = 0;
|
|
} else {
|
|
byte *output_data = (byte *)malloc(4096);
|
|
|
|
memcpy(_IACToutput + _IACTpos, d_src, len);
|
|
byte *dst = output_data;
|
|
byte *d_src2 = _IACToutput;
|
|
d_src2 += 2;
|
|
int32 count = 1024;
|
|
byte variable1 = *d_src2++;
|
|
byte variable2 = variable1 / 16;
|
|
variable1 &= 0x0f;
|
|
do {
|
|
value = *(d_src2++);
|
|
if (value == 0x80) {
|
|
*dst++ = *d_src2++;
|
|
*dst++ = *d_src2++;
|
|
} else {
|
|
int16 val = (int8)value << variable2;
|
|
*dst++ = val >> 8;
|
|
*dst++ = (byte)(val);
|
|
}
|
|
value = *(d_src2++);
|
|
if (value == 0x80) {
|
|
*dst++ = *d_src2++;
|
|
*dst++ = *d_src2++;
|
|
} else {
|
|
int16 val = (int8)value << variable1;
|
|
*dst++ = val >> 8;
|
|
*dst++ = (byte)(val);
|
|
}
|
|
} while (--count);
|
|
|
|
if (!_IACTstream) {
|
|
_IACTstream = Audio::makeQueuingAudioStream(22050, true);
|
|
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, _IACTchannel, _IACTstream);
|
|
}
|
|
_IACTstream->queueBuffer(output_data, 0x1000, DisposeAfterUse::YES, Audio::FLAG_STEREO | Audio::FLAG_16BITS);
|
|
|
|
bsize -= len;
|
|
d_src += len;
|
|
_IACTpos = 0;
|
|
}
|
|
} else {
|
|
if (bsize > 1 && _IACTpos == 0) {
|
|
*(_IACToutput + 0) = *d_src++;
|
|
_IACTpos = 1;
|
|
bsize--;
|
|
}
|
|
*(_IACToutput + _IACTpos) = *d_src++;
|
|
_IACTpos++;
|
|
bsize--;
|
|
}
|
|
}
|
|
|
|
free(src);
|
|
} else if ((_vm->_game.id == GID_DIG) && !(_vm->_game.features & GF_DEMO)) {
|
|
int bufId, volume, paused, curSoundId;
|
|
|
|
byte *dataBuffer = (byte *)malloc(bsize);
|
|
b.read(dataBuffer, bsize);
|
|
|
|
switch (userId) {
|
|
case 1:
|
|
bufId = 1;
|
|
volume = 127;
|
|
break;
|
|
case 2:
|
|
bufId = 2;
|
|
volume = 127;
|
|
break;
|
|
case 3:
|
|
bufId = 3;
|
|
volume = 127;
|
|
break;
|
|
default:
|
|
if (userId >= 100 && userId <= 163) {
|
|
bufId = DIMUSE_BUFFER_SPEECH;
|
|
volume = 2 * userId - 200;
|
|
} else if (userId >= 200 && userId <= 263) {
|
|
bufId = DIMUSE_BUFFER_MUSIC;
|
|
volume = 2 * userId - 400;
|
|
} else if (userId >= 300 && userId <= 363) {
|
|
bufId = DIMUSE_BUFFER_SMUSH;
|
|
volume = 2 * userId - 600;
|
|
} else {
|
|
free(dataBuffer);
|
|
error("SmushPlayer::handleIACT(): ERROR: got invalid userID (%d)", userId);
|
|
}
|
|
break;
|
|
}
|
|
|
|
paused = nbframes - index == 1;
|
|
|
|
// Apparently this is expected to happen (e.g.: Brink's death video)
|
|
if (index && _iactTable[bufId] - index != -1) {
|
|
free(dataBuffer);
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::handleIACT(): WARNING: got out of order block");
|
|
return;
|
|
}
|
|
|
|
_iactTable[bufId] = index;
|
|
|
|
if (index) {
|
|
if (_imuseDigital->diMUSEGetParam(bufId + DIMUSE_SMUSH_SOUNDID, DIMUSE_P_SND_TRACK_NUM)) {
|
|
_imuseDigital->diMUSEFeedStream(bufId + DIMUSE_SMUSH_SOUNDID, dataBuffer, subSize - 18, paused);
|
|
free(dataBuffer);
|
|
return;
|
|
}
|
|
free(dataBuffer);
|
|
error("SmushPlayer::handleIACT(): ERROR: got unexpected non-zero IACT block, bufID %d", bufId);
|
|
} else {
|
|
if (READ_BE_UINT32(dataBuffer) != MKTAG('i', 'M', 'U', 'S')) {
|
|
free(dataBuffer);
|
|
error("SmushPlayer::handleIACT(): ERROR: got non-IMUS IACT block");
|
|
}
|
|
|
|
curSoundId = 0;
|
|
do {
|
|
curSoundId = _imuseDigital->diMUSEGetNextSound(curSoundId);
|
|
if (!curSoundId)
|
|
break;
|
|
} while (_imuseDigital->diMUSEGetParam(curSoundId, DIMUSE_P_SND_HAS_STREAM) != 1 || _imuseDigital->diMUSEGetParam(curSoundId, DIMUSE_P_STREAM_BUFID) != bufId);
|
|
|
|
if (!curSoundId) {
|
|
// There isn't any previous sound running: start a new stream
|
|
if (_imuseDigital->diMUSEStartStream(bufId + DIMUSE_SMUSH_SOUNDID, 126, bufId)) {
|
|
free(dataBuffer);
|
|
error("SmushPlayer::handleIACT(): ERROR: couldn't start stream");
|
|
}
|
|
} else {
|
|
// There's an old sound running: switch the stream from the old one to the new one
|
|
_imuseDigital->diMUSESwitchStream(curSoundId, bufId + DIMUSE_SMUSH_SOUNDID, bufId == 2 ? 1000 : 150, 0, 0);
|
|
}
|
|
|
|
_imuseDigital->diMUSESetParam(bufId + DIMUSE_SMUSH_SOUNDID, DIMUSE_P_VOLUME, volume);
|
|
|
|
if (bufId == DIMUSE_BUFFER_SPEECH) {
|
|
_imuseDigital->diMUSESetParam(bufId + DIMUSE_SMUSH_SOUNDID, DIMUSE_P_GROUP, DIMUSE_GROUP_SPEECH);
|
|
} else if (bufId == DIMUSE_BUFFER_MUSIC) {
|
|
_imuseDigital->diMUSESetParam(bufId + DIMUSE_SMUSH_SOUNDID, DIMUSE_P_GROUP, DIMUSE_GROUP_MUSIC);
|
|
} else {
|
|
_imuseDigital->diMUSESetParam(bufId + DIMUSE_SMUSH_SOUNDID, DIMUSE_P_GROUP, DIMUSE_GROUP_SFX);
|
|
}
|
|
|
|
_imuseDigital->diMUSEFeedStream(bufId + DIMUSE_SMUSH_SOUNDID, dataBuffer, subSize - 18, paused);
|
|
free(dataBuffer);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleTextResource(uint32 subType, int32 subSize, Common::SeekableReadStream &b) {
|
|
int pos_x = b.readSint16LE();
|
|
int pos_y = b.readSint16LE();
|
|
int flags = b.readSint16LE();
|
|
int left = b.readSint16LE();
|
|
int top = b.readSint16LE();
|
|
int width = b.readSint16LE();
|
|
int height = b.readSint16LE();
|
|
/*int32 unk2 =*/ b.readUint16LE();
|
|
|
|
const char *str;
|
|
char *string = nullptr, *string2 = nullptr;
|
|
if (subType == MKTAG('T','E','X','T')) {
|
|
string = (char *)malloc(subSize - 16);
|
|
str = string;
|
|
b.read(string, subSize - 16);
|
|
} else {
|
|
int string_id = b.readUint16LE();
|
|
if (!_strings)
|
|
return;
|
|
str = _strings->get(string_id);
|
|
}
|
|
|
|
// if subtitles disabled and bit 3 is set, then do not draw
|
|
//
|
|
// Query ConfMan here. However it may be slower, but
|
|
// player may want to switch the subtitles on or off during the
|
|
// playback. This fixes bug #2812
|
|
if ((!ConfMan.getBool("subtitles")) && ((flags & 8) == 8))
|
|
return;
|
|
|
|
bool isCJKComi = (_vm->_game.id == GID_CMI && _vm->_useCJKMode);
|
|
int color = 15;
|
|
int fontId = isCJKComi ? 1 : 0;
|
|
|
|
while (*str == '/') {
|
|
str++; // For Full Throttle text resources
|
|
}
|
|
|
|
byte transBuf[512];
|
|
if (_vm->_game.id == GID_CMI) {
|
|
_vm->translateText((const byte *)str - 1, transBuf, sizeof(transBuf));
|
|
while (*str++ != '/')
|
|
;
|
|
string2 = (char *)transBuf;
|
|
|
|
// If string2 contains formatting information there probably
|
|
// wasn't any translation for it in the language.tab file. In
|
|
// that case, pretend there is no string2.
|
|
if (string2[0] == '^')
|
|
string2[0] = 0;
|
|
}
|
|
|
|
while (str[0] == '^') {
|
|
switch (str[1]) {
|
|
case 'f':
|
|
fontId = str[3] - '0';
|
|
str += 4;
|
|
break;
|
|
case 'c':
|
|
color = str[4] - '0' + 10 *(str[3] - '0');
|
|
str += 5;
|
|
break;
|
|
default:
|
|
error("invalid escape code in text string");
|
|
}
|
|
}
|
|
|
|
if (_vm->_game.id == GID_CMI && string2[0] != 0)
|
|
str = string2;
|
|
|
|
// This is a hack from the original COMI CJK interpreter. Its purpose is to avoid
|
|
// ugly combinations of two byte characters (rendered with the respective special
|
|
// font) and standard one byte (NUT font) characters (see bug #11947).
|
|
if (isCJKComi && !(fontId == 0 && color == 1)) {
|
|
fontId = 1;
|
|
color = 255;
|
|
}
|
|
|
|
SmushFont *sf = getFont(fontId);
|
|
assert(sf != nullptr);
|
|
|
|
// The hack that used to be here to prevent bug #2220 is no longer necessary and
|
|
// has been removed. The font renderer can handle all ^codes it encounters (font
|
|
// changes on the fly will be ignored for Smush texts, since our code design does
|
|
// not permit it and the feature isn't used anyway).
|
|
|
|
if (_vm->_language == Common::HE_ISR && !(flags & kStyleAlignCenter)) {
|
|
flags |= kStyleAlignRight;
|
|
pos_x = _width - 1 - pos_x;
|
|
}
|
|
|
|
TextStyleFlags flg = (TextStyleFlags)(flags & 7);
|
|
// flags:
|
|
// bit 0 - center 0x01
|
|
// bit 1 - not used (align right) 0x02
|
|
// bit 2 - word wrap 0x04
|
|
// bit 3 - switchable 0x08
|
|
// bit 4 - fill background 0x10
|
|
// bit 5 - outline/shadow 0x20 (apparently only set by the text renderer itself, not from the smush data)
|
|
// bit 6 - vertical fix (COMI) 0x40 (COMI handles this in the printing method, but I haven't seen a case where it is used)
|
|
// bit 7 - skip ^ codes (COMI) 0x80 (should be irrelevant for Smush, we strip these commands anyway)
|
|
// bit 8 - no vertical fix (COMI) 0x100 (COMI handles this in the printing method, but I haven't seen a case where it is used)
|
|
|
|
if (flg & kStyleWordWrap) {
|
|
// COMI has to do it all a bit different, of course. SCUMM7 games immediately render the text from here and actually use the clipping data
|
|
// provided by the text resource. COMI does not render directly, but enqueues a blast string (which is then drawn through the usual main
|
|
// loop routines). During that process the rect data will get dumped and replaced with the following default values. It's hard to tell
|
|
// whether this is on purpose or not (the text looks not necessarily better or worse, just different), so we follow the original...
|
|
if (_vm->_game.id == GID_CMI) {
|
|
left = top = 10;
|
|
width = _width - 20;
|
|
height = _height - 20;
|
|
}
|
|
Common::Rect clipRect(MAX<int>(0, left), MAX<int>(0, top), MIN<int>(left + width, _width), MIN<int>(top + height, _height));
|
|
sf->drawStringWrap(str, _dst, clipRect, pos_x, pos_y, color, flg);
|
|
} else {
|
|
// Similiar to the wrapped text, COMI will pass on rect coords here, which will later be lost. Unlike with the wrapped text, it will
|
|
// finally use the full screen dimenstions. SCUMM7 renders directly from here (see comment above), but also with the full screen.
|
|
Common::Rect clipRect(0, 0, _width, _height);
|
|
sf->drawString(str, _dst, clipRect, pos_x, pos_y, color, flg);
|
|
}
|
|
|
|
free(string);
|
|
}
|
|
|
|
const char *SmushPlayer::getString(int id) {
|
|
return _strings->get(id);
|
|
}
|
|
|
|
bool SmushPlayer::readString(const char *file) {
|
|
const char *i = strrchr(file, '.');
|
|
if (i == nullptr) {
|
|
error("invalid filename : %s", file);
|
|
}
|
|
char fname[260];
|
|
memcpy(fname, file, MIN<int>(sizeof(fname), i - file));
|
|
Common::strlcpy(fname + (i - file), ".trs", sizeof(fname) - (i - file));
|
|
if ((_strings = getStrings(_vm, fname, false)) != 0) {
|
|
return true;
|
|
}
|
|
|
|
if (_vm->_game.id == GID_DIG && (_strings = getStrings(_vm, "digtxt.trs", true)) != 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SmushPlayer::readPalette(byte *out, Common::SeekableReadStream &in) {
|
|
in.read(out, 0x300);
|
|
}
|
|
|
|
static byte delta_color(byte org_color, int16 delta_color) {
|
|
int t = (org_color * 129 + delta_color) / 128;
|
|
return CLIP(t, 0, 255);
|
|
}
|
|
|
|
void SmushPlayer::handleDeltaPalette(int32 subSize, Common::SeekableReadStream &b) {
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::handleDeltaPalette()");
|
|
|
|
if (subSize == 0x300 * 3 + 4) {
|
|
|
|
b.readUint16LE();
|
|
b.readUint16LE();
|
|
|
|
for (int i = 0; i < 0x300; i++) {
|
|
_deltaPal[i] = b.readUint16LE();
|
|
}
|
|
readPalette(_pal, b);
|
|
setDirtyColors(0, 255);
|
|
} else if (subSize == 6) {
|
|
|
|
b.readUint16LE();
|
|
b.readUint16LE();
|
|
b.readUint16LE();
|
|
|
|
for (int i = 0; i < 0x300; i++) {
|
|
_pal[i] = delta_color(_pal[i], _deltaPal[i]);
|
|
}
|
|
setDirtyColors(0, 255);
|
|
} else {
|
|
error("SmushPlayer::handleDeltaPalette() Wrong size for DeltaPalette");
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleNewPalette(int32 subSize, Common::SeekableReadStream &b) {
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::handleNewPalette()");
|
|
assert(subSize >= 0x300);
|
|
|
|
if (_skipPalette)
|
|
return;
|
|
|
|
readPalette(_pal, b);
|
|
setDirtyColors(0, 255);
|
|
}
|
|
|
|
byte *SmushPlayer::getVideoPalette() {
|
|
return _pal;
|
|
}
|
|
|
|
void smush_decode_codec1(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
|
|
void smush_decode_codec20(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
|
|
|
|
void SmushPlayer::decodeFrameObject(int codec, const uint8 *src, int left, int top, int width, int height) {
|
|
if ((height == 242) && (width == 384)) {
|
|
if (_specialBuffer == 0)
|
|
_specialBuffer = (byte *)malloc(242 * 384);
|
|
_dst = _specialBuffer;
|
|
} else if ((height > _vm->_screenHeight) || (width > _vm->_screenWidth))
|
|
return;
|
|
// FT Insane uses smaller frames to draw overlays with moving objects
|
|
// Other .san files do have them as well but their purpose in unknown
|
|
// and often it causes memory overdraw. So just skip those frames
|
|
else if (!_insanity && ((height != _vm->_screenHeight) || (width != _vm->_screenWidth)))
|
|
return;
|
|
|
|
if ((height == 242) && (width == 384)) {
|
|
_width = width;
|
|
_height = height;
|
|
} else {
|
|
_width = _vm->_screenWidth;
|
|
_height = _vm->_screenHeight;
|
|
}
|
|
|
|
switch (codec) {
|
|
case 1:
|
|
case 3:
|
|
smush_decode_codec1(_dst, src, left, top, width, height, _vm->_screenWidth);
|
|
break;
|
|
case 37:
|
|
if (!_codec37)
|
|
_codec37 = new Codec37Decoder(width, height);
|
|
if (_codec37)
|
|
_codec37->decode(_dst, src);
|
|
break;
|
|
case 47:
|
|
if (!_codec47)
|
|
_codec47 = new Codec47Decoder(width, height);
|
|
if (_codec47)
|
|
_codec47->decode(_dst, src);
|
|
break;
|
|
case 20:
|
|
// Used by Full Throttle Classic (from Remastered)
|
|
smush_decode_codec20(_dst, src, left, top, width, height, _vm->_screenWidth);
|
|
break;
|
|
default:
|
|
error("Invalid codec for frame object : %d", codec);
|
|
}
|
|
|
|
if (_storeFrame) {
|
|
if (_frameBuffer == nullptr) {
|
|
_frameBuffer = (byte *)malloc(_width * _height);
|
|
}
|
|
memcpy(_frameBuffer, _dst, _width * _height);
|
|
_storeFrame = false;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ZLIB
|
|
void SmushPlayer::handleZlibFrameObject(int32 subSize, Common::SeekableReadStream &b) {
|
|
if (_skipNext) {
|
|
_skipNext = false;
|
|
return;
|
|
}
|
|
|
|
int32 chunkSize = subSize;
|
|
byte *chunkBuffer = (byte *)malloc(chunkSize);
|
|
assert(chunkBuffer);
|
|
b.read(chunkBuffer, chunkSize);
|
|
|
|
unsigned long decompressedSize = READ_BE_UINT32(chunkBuffer);
|
|
byte *fobjBuffer = (byte *)malloc(decompressedSize);
|
|
if (!Common::uncompress(fobjBuffer, &decompressedSize, chunkBuffer + 4, chunkSize - 4))
|
|
error("SmushPlayer::handleZlibFrameObject() Zlib uncompress error");
|
|
free(chunkBuffer);
|
|
|
|
byte *ptr = fobjBuffer;
|
|
int codec = READ_LE_UINT16(ptr); ptr += 2;
|
|
int left = READ_LE_UINT16(ptr); ptr += 2;
|
|
int top = READ_LE_UINT16(ptr); ptr += 2;
|
|
int width = READ_LE_UINT16(ptr); ptr += 2;
|
|
int height = READ_LE_UINT16(ptr); ptr += 2;
|
|
|
|
decodeFrameObject(codec, fobjBuffer + 14, left, top, width, height);
|
|
|
|
free(fobjBuffer);
|
|
}
|
|
#endif
|
|
|
|
void SmushPlayer::handleFrameObject(int32 subSize, Common::SeekableReadStream &b) {
|
|
assert(subSize >= 14);
|
|
if (_skipNext) {
|
|
_skipNext = false;
|
|
return;
|
|
}
|
|
|
|
int codec = b.readUint16LE();
|
|
int left = b.readUint16LE();
|
|
int top = b.readUint16LE();
|
|
int width = b.readUint16LE();
|
|
int height = b.readUint16LE();
|
|
|
|
b.readUint16LE();
|
|
b.readUint16LE();
|
|
|
|
int32 chunk_size = subSize - 14;
|
|
byte *chunk_buffer = (byte *)malloc(chunk_size);
|
|
assert(chunk_buffer);
|
|
b.read(chunk_buffer, chunk_size);
|
|
|
|
decodeFrameObject(codec, chunk_buffer, left, top, width, height);
|
|
|
|
free(chunk_buffer);
|
|
}
|
|
|
|
void SmushPlayer::handleFrame(int32 frameSize, Common::SeekableReadStream &b) {
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::handleFrame(%d)", _frame);
|
|
uint8 *audioChunk = nullptr;
|
|
_skipNext = false;
|
|
|
|
if (_insanity) {
|
|
_vm->_insane->procPreRendering();
|
|
}
|
|
|
|
while (frameSize > 0) {
|
|
const uint32 subType = b.readUint32BE();
|
|
const int32 subSize = b.readUint32BE();
|
|
const int32 subOffset = b.pos();
|
|
switch (subType) {
|
|
case MKTAG('N','P','A','L'):
|
|
handleNewPalette(subSize, b);
|
|
break;
|
|
case MKTAG('F','O','B','J'):
|
|
handleFrameObject(subSize, b);
|
|
break;
|
|
#ifdef USE_ZLIB
|
|
case MKTAG('Z','F','O','B'):
|
|
handleZlibFrameObject(subSize, b);
|
|
break;
|
|
#endif
|
|
case MKTAG('P','S','A','D'):
|
|
if (!_compressedFileMode) {
|
|
audioChunk = (uint8 *)malloc(subSize + 8);
|
|
b.seek(-8, SEEK_CUR);
|
|
b.read(audioChunk, subSize + 8);
|
|
feedAudio(audioChunk, 0, 127, 0, 0);
|
|
free(audioChunk);
|
|
audioChunk = nullptr;
|
|
}
|
|
|
|
break;
|
|
case MKTAG('T','R','E','S'):
|
|
handleTextResource(subType, subSize, b);
|
|
break;
|
|
case MKTAG('X','P','A','L'):
|
|
handleDeltaPalette(subSize, b);
|
|
break;
|
|
case MKTAG('I','A','C','T'):
|
|
handleIACT(subSize, b);
|
|
break;
|
|
case MKTAG('S','T','O','R'):
|
|
handleStore(subSize, b);
|
|
break;
|
|
case MKTAG('F','T','C','H'):
|
|
handleFetch(subSize, b);
|
|
break;
|
|
case MKTAG('S','K','I','P'):
|
|
_vm->_insane->procSKIP(subSize, b);
|
|
break;
|
|
case MKTAG('T','E','X','T'):
|
|
handleTextResource(subType, subSize, b);
|
|
break;
|
|
default:
|
|
error("Unknown frame subChunk found : %s, %d", tag2str(subType), subSize);
|
|
}
|
|
|
|
frameSize -= subSize + 8;
|
|
b.seek(subOffset + subSize, SEEK_SET);
|
|
if (subSize & 1) {
|
|
b.skip(1);
|
|
frameSize--;
|
|
}
|
|
}
|
|
|
|
if (_insanity) {
|
|
_vm->_insane->procPostRendering(_dst, 0, 0, 0, _frame, _nbframes-1);
|
|
}
|
|
|
|
if (_width != 0 && _height != 0) {
|
|
updateScreen();
|
|
}
|
|
|
|
_frame++;
|
|
}
|
|
|
|
void SmushPlayer::handleAnimHeader(int32 subSize, Common::SeekableReadStream &b) {
|
|
debugC(DEBUG_SMUSH, "SmushPlayer::handleAnimHeader()");
|
|
assert(subSize >= 0x300 + 6);
|
|
|
|
/* _version = */ b.readUint16LE();
|
|
_nbframes = b.readUint16LE();
|
|
b.readUint16LE();
|
|
|
|
if (_skipPalette)
|
|
return;
|
|
|
|
readPalette(_pal, b);
|
|
setDirtyColors(0, 255);
|
|
}
|
|
|
|
void SmushPlayer::setupAnim(const char *file) {
|
|
if (_insanity) {
|
|
if (!((_vm->_game.features & GF_DEMO) && (_vm->_game.platform == Common::kPlatformDOS)))
|
|
readString("mineroad.trs");
|
|
} else
|
|
readString(file);
|
|
}
|
|
|
|
SmushFont *SmushPlayer::getFont(int font) {
|
|
char file_font[11];
|
|
|
|
if (_sf[font])
|
|
return _sf[font];
|
|
|
|
if (_vm->_game.id == GID_FT) {
|
|
if (!((_vm->_game.features & GF_DEMO) && (_vm->_game.platform == Common::kPlatformDOS))) {
|
|
const char *ft_fonts[] = {
|
|
"scummfnt.nut",
|
|
"techfnt.nut",
|
|
"titlfnt.nut",
|
|
"specfnt.nut"
|
|
};
|
|
|
|
assert(font >= 0 && font < ARRAYSIZE(ft_fonts));
|
|
|
|
_sf[font] = new SmushFont(_vm, ft_fonts[font], true);
|
|
}
|
|
} else {
|
|
int numFonts = (_vm->_game.id == GID_CMI && !(_vm->_game.features & GF_DEMO)) ? 5 : 4;
|
|
assert(font >= 0 && font < numFonts);
|
|
Common::sprintf_s(file_font, "font%d.nut", font);
|
|
_sf[font] = new SmushFont(_vm, file_font, _vm->_game.id == GID_DIG && font != 0);
|
|
}
|
|
|
|
assert(_sf[font]);
|
|
return _sf[font];
|
|
}
|
|
|
|
void SmushPlayer::parseNextFrame() {
|
|
|
|
if (_seekPos >= 0) {
|
|
if (_seekFile.size() > 0) {
|
|
delete _base;
|
|
|
|
ScummFile *tmp = new ScummFile();
|
|
if (!g_scumm->openFile(*tmp, _seekFile))
|
|
error("SmushPlayer: Unable to open file %s", _seekFile.c_str());
|
|
_base = tmp;
|
|
_base->readUint32BE();
|
|
_baseSize = _base->readUint32BE();
|
|
|
|
if (_seekPos > 0) {
|
|
assert(_seekPos > 8);
|
|
// In this case we need to get palette and number of frames
|
|
const uint32 subType = _base->readUint32BE();
|
|
const int32 subSize = _base->readUint32BE();
|
|
const int32 subOffset = _base->pos();
|
|
assert(subType == MKTAG('A','H','D','R'));
|
|
handleAnimHeader(subSize, *_base);
|
|
_base->seek(subOffset + subSize, SEEK_SET);
|
|
|
|
_middleAudio = true;
|
|
_seekPos -= 8;
|
|
} else {
|
|
// We need this in Full Throttle when entering/leaving
|
|
// the old mine road.
|
|
tryCmpFile(_seekFile.c_str());
|
|
}
|
|
_skipPalette = false;
|
|
} else {
|
|
_skipPalette = true;
|
|
}
|
|
|
|
_base->seek(_seekPos + 8, SEEK_SET);
|
|
_frame = _seekFrame;
|
|
_startFrame = _frame;
|
|
_startTime = _vm->_system->getMillis();
|
|
|
|
_seekPos = -1;
|
|
}
|
|
|
|
assert(_base);
|
|
|
|
const uint32 subType = _base->readUint32BE();
|
|
const int32 subSize = _base->readUint32BE();
|
|
const int32 subOffset = _base->pos();
|
|
|
|
if (_base->pos() >= (int32)_baseSize) {
|
|
_vm->_smushVideoShouldFinish = true;
|
|
_endOfFile = true;
|
|
return;
|
|
}
|
|
|
|
debug(3, "Chunk: %s at %x", tag2str(subType), subOffset);
|
|
|
|
switch (subType) {
|
|
case MKTAG('A','H','D','R'): // FT INSANE may seek file to the beginning
|
|
handleAnimHeader(subSize, *_base);
|
|
break;
|
|
case MKTAG('F','R','M','E'):
|
|
handleFrame(subSize, *_base);
|
|
break;
|
|
default:
|
|
error("Unknown Chunk found at %x: %s, %d", subOffset, tag2str(subType), subSize);
|
|
}
|
|
|
|
_base->seek(subOffset + subSize, SEEK_SET);
|
|
|
|
if (_insanity)
|
|
_vm->_sound->processSound();
|
|
|
|
_vm->_imuseDigital->flushTracks();
|
|
}
|
|
|
|
void SmushPlayer::setPalette(const byte *palette) {
|
|
memcpy(_pal, palette, 0x300);
|
|
setDirtyColors(0, 255);
|
|
}
|
|
|
|
void SmushPlayer::setPaletteValue(int n, byte r, byte g, byte b) {
|
|
_pal[n * 3 + 0] = r;
|
|
_pal[n * 3 + 1] = g;
|
|
_pal[n * 3 + 2] = b;
|
|
setDirtyColors(n, n);
|
|
}
|
|
|
|
void SmushPlayer::setDirtyColors(int min, int max) {
|
|
if (_palDirtyMin > min)
|
|
_palDirtyMin = min;
|
|
if (_palDirtyMax < max)
|
|
_palDirtyMax = max;
|
|
}
|
|
|
|
void SmushPlayer::warpMouse(int x, int y, int buttons) {
|
|
_warpNeeded = true;
|
|
_warpX = x;
|
|
_warpY = y;
|
|
_warpButtons = buttons;
|
|
}
|
|
|
|
void SmushPlayer::updateScreen() {
|
|
uint32 end_time, start_time = _vm->_system->getMillis();
|
|
_updateNeeded = true;
|
|
end_time = _vm->_system->getMillis();
|
|
debugC(DEBUG_SMUSH, "Smush stats: updateScreen( %03d )", end_time - start_time);
|
|
}
|
|
|
|
void SmushPlayer::insanity(bool flag) {
|
|
_insanity = flag;
|
|
}
|
|
|
|
void SmushPlayer::seekSan(const char *file, int32 pos, int32 contFrame) {
|
|
_seekFile = file ? file : "";
|
|
_seekPos = pos;
|
|
_seekFrame = contFrame;
|
|
_pauseTime = 0;
|
|
}
|
|
|
|
void SmushPlayer::tryCmpFile(const char *filename) {
|
|
_vm->_mixer->stopHandle(*_compressedFileSoundHandle);
|
|
|
|
_compressedFileMode = false;
|
|
const char *i = strrchr(filename, '.');
|
|
if (i == NULL) {
|
|
error("invalid filename : %s", filename);
|
|
}
|
|
#if defined(USE_MAD) || defined(USE_VORBIS)
|
|
char fname[260];
|
|
#endif
|
|
Common::File *file = new Common::File();
|
|
|
|
// FIXME: How about using AudioStream::openStreamFile instead of the code below?
|
|
|
|
#ifdef USE_VORBIS
|
|
memcpy(fname, filename, MIN<int>(i - filename, sizeof(fname)));
|
|
Common::strlcpy(fname + (i - filename), ".ogg", sizeof(fname) - (i - filename));
|
|
if (file->open(fname)) {
|
|
_compressedFileMode = true;
|
|
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, _compressedFileSoundHandle, Audio::makeVorbisStream(file, DisposeAfterUse::YES));
|
|
return;
|
|
}
|
|
#endif
|
|
#ifdef USE_MAD
|
|
memcpy(fname, filename, MIN<int>(i - filename, sizeof(fname)));
|
|
Common::strlcpy(fname + (i - filename), ".mp3", sizeof(fname) - (i - filename));
|
|
if (file->open(fname)) {
|
|
_compressedFileMode = true;
|
|
_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, _compressedFileSoundHandle, Audio::makeMP3Stream(file, DisposeAfterUse::YES));
|
|
return;
|
|
}
|
|
#endif
|
|
delete file;
|
|
}
|
|
|
|
void SmushPlayer::pause() {
|
|
if (!_paused) {
|
|
_paused = true;
|
|
_pauseStartTime = _vm->_system->getMillis();
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::unpause() {
|
|
if (_paused) {
|
|
_paused = false;
|
|
_pauseTime += (_vm->_system->getMillis() - _pauseStartTime);
|
|
_pauseStartTime = 0;
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::play(const char *filename, int32 speed, int32 offset, int32 startFrame) {
|
|
// Verify the specified file exists
|
|
ScummFile f;
|
|
_vm->openFile(f, filename);
|
|
if (!f.isOpen()) {
|
|
warning("SmushPlayer::play() File not found %s", filename);
|
|
return;
|
|
}
|
|
f.close();
|
|
|
|
_updateNeeded = false;
|
|
_warpNeeded = false;
|
|
_palDirtyMin = 256;
|
|
_palDirtyMax = -1;
|
|
|
|
// Hide mouse
|
|
bool oldMouseState = CursorMan.showMouse(false);
|
|
|
|
// Load the video
|
|
_seekFile = filename;
|
|
_seekPos = offset;
|
|
_seekFrame = startFrame;
|
|
_base = 0;
|
|
|
|
setupAnim(filename);
|
|
init(speed);
|
|
|
|
_startTime = _vm->_system->getMillis();
|
|
_startFrame = startFrame;
|
|
_frame = startFrame;
|
|
|
|
_pauseTime = 0;
|
|
|
|
// This piece of code is used to ensure there are
|
|
// no audio hiccups while loading the SMUSH video;
|
|
// Each version of the engine does it in its own way.
|
|
if (_imuseDigital->isFTSoundEngine()) {
|
|
_imuseDigital->fillStreamsWhileMusicCritical(20);
|
|
} else {
|
|
_imuseDigital->floodMusicBuffer();
|
|
}
|
|
|
|
int skipped = 0;
|
|
|
|
for (;;) {
|
|
uint32 now, elapsed;
|
|
bool skipFrame = false;
|
|
|
|
if (_insanity) {
|
|
// Seeking makes a mess of trying to sync the audio to
|
|
// the sound. Synt to time instead.
|
|
now = _vm->_system->getMillis() - _pauseTime;
|
|
elapsed = now - _startTime;
|
|
} else if (_vm->_mixer->isSoundHandleActive(*_compressedFileSoundHandle)) {
|
|
// Compressed SMUSH files.
|
|
elapsed = _vm->_mixer->getSoundElapsedTime(*_compressedFileSoundHandle);
|
|
} else if (_vm->_mixer->isSoundHandleActive(*_IACTchannel)) {
|
|
// Curse of Monkey Island SMUSH files.
|
|
elapsed = _vm->_mixer->getSoundElapsedTime(*_IACTchannel);
|
|
} else {
|
|
// For other SMUSH files, we don't necessarily have any
|
|
// one channel to sync against, so we have to use
|
|
// elapsed real time.
|
|
now = _vm->_system->getMillis() - _pauseTime;
|
|
elapsed = now - _startTime;
|
|
}
|
|
|
|
if (elapsed >= ((_frame - _startFrame) * 1000) / _speed) {
|
|
if (elapsed >= ((_frame + 1) * 1000) / _speed)
|
|
skipFrame = true;
|
|
else
|
|
skipFrame = false;
|
|
timerCallback();
|
|
}
|
|
|
|
_vm->scummLoop_handleSound();
|
|
|
|
if (_warpNeeded) {
|
|
_vm->_system->warpMouse(_warpX, _warpY);
|
|
_warpNeeded = false;
|
|
}
|
|
_vm->parseEvents();
|
|
_vm->processInput();
|
|
if (_palDirtyMax >= _palDirtyMin) {
|
|
_vm->_system->getPaletteManager()->setPalette(_pal + _palDirtyMin * 3, _palDirtyMin, _palDirtyMax - _palDirtyMin + 1);
|
|
|
|
_palDirtyMax = -1;
|
|
_palDirtyMin = 256;
|
|
skipFrame = false;
|
|
}
|
|
if (skipFrame) {
|
|
if (++skipped > 10) {
|
|
skipFrame = false;
|
|
skipped = 0;
|
|
}
|
|
} else
|
|
skipped = 0;
|
|
if (_updateNeeded) {
|
|
if (!skipFrame) {
|
|
// WORKAROUND for bug #2415: "FT DEMO: assertion triggered
|
|
// when playing movie". Some frames there are 384 x 224
|
|
int w = MIN(_width, _vm->_screenWidth);
|
|
int h = MIN(_height, _vm->_screenHeight);
|
|
|
|
_vm->_system->copyRectToScreen(_dst, _width, 0, 0, w, h);
|
|
_vm->_system->updateScreen();
|
|
_updateNeeded = false;
|
|
}
|
|
}
|
|
if (_endOfFile)
|
|
break;
|
|
if (_vm->shouldQuit() || _vm->_saveLoadFlag || _vm->_smushVideoShouldFinish) {
|
|
_vm->_mixer->stopHandle(*_compressedFileSoundHandle);
|
|
_vm->_mixer->stopHandle(*_IACTchannel);
|
|
_IACTpos = 0;
|
|
|
|
resetAudioTracks(); // For DIG demo
|
|
_imuseDigital->stopSMUSHAudio(); // For DIG & COMI
|
|
break;
|
|
}
|
|
_vm->_system->delayMillis(10);
|
|
}
|
|
|
|
release();
|
|
|
|
// Reset mouse state
|
|
CursorMan.showMouse(oldMouseState);
|
|
}
|
|
|
|
void SmushPlayer::initAudio(int samplerate, int32 maxChunkSize) {
|
|
int32 maxSizes[SMUSH_MAX_TRACKS] = {100000, 100000, 100000, 400000};
|
|
|
|
_imuseDigital->setSmushPlayer(this);
|
|
|
|
// DIG demo uses this audio system but doesn't use INSANE
|
|
if (_insane)
|
|
_insane->setSmushPlayer(this);
|
|
|
|
setGainReductionParams(114, 2048);
|
|
|
|
memset(_smushAudioTable, 0, sizeof(_smushAudioTable));
|
|
|
|
for (int i = 0; i < SMUSH_MAX_TRACKS; i++) {
|
|
_smushTrackVols[i] = 127;
|
|
_smushTrackFlags[i] = 1;
|
|
addAudioTrack(maxSizes[i], maxChunkSize);
|
|
}
|
|
|
|
_smushAudioSampleRate = samplerate;
|
|
_smushAudioInitialized = true;
|
|
_smushAudioCallbackEnabled = true;
|
|
|
|
resetAudioTracks();
|
|
}
|
|
|
|
void SmushPlayer::terminateAudio() {
|
|
if (_smushAudioInitialized) {
|
|
_smushAudioInitialized = false;
|
|
_smushAudioCallbackEnabled = false;
|
|
}
|
|
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
free(_smushTracks[i].blockPtr);
|
|
free(_smushTracks[i].fadeBuf);
|
|
}
|
|
_smushNumTracks = 0;
|
|
}
|
|
|
|
int SmushPlayer::isChanActive(int flagId) {
|
|
return _smushTrackFlags[flagId];
|
|
}
|
|
|
|
int SmushPlayer::setChanFlag(int id, int flagVal) {
|
|
if (id >= 0 && id <= _smushNumTracks)
|
|
_smushTrackFlags[id] = flagVal;
|
|
|
|
return id;
|
|
}
|
|
|
|
int SmushPlayer::addAudioTrack(int32 trackBlockSize, int32 maxBlockSize) {
|
|
int id = _smushNumTracks;
|
|
_smushTracks[id].state = TRK_STATE_INACTIVE;
|
|
_smushTracks[id].audioRemaining = 0;
|
|
_smushTracks[id].flags = 0;
|
|
_smushTracks[id].groupId = GRP_MASTER;
|
|
_smushTracks[id].blockSize = trackBlockSize;
|
|
_smushTracks[id].parsedChunks = 0;
|
|
|
|
_smushTracks[id].fadeBuf = (uint8 *)malloc(SMUSH_FADE_SIZE);
|
|
if (!_smushTracks[id].fadeBuf)
|
|
return -1;
|
|
|
|
_smushTracks[id].blockPtr = (uint8 *)malloc(_smushTracks[id].blockSize);
|
|
if (!_smushTracks[id].blockPtr)
|
|
return -1;
|
|
|
|
memset(_smushTracks[id].blockPtr, 127, _smushTracks[id].blockSize);
|
|
// Track the effective number of tracks, so that if only one fails,
|
|
// the others can carry on as they should
|
|
_smushNumTracks++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SmushPlayer::resetAudioTracks() {
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
_smushTracks[i].state = TRK_STATE_INACTIVE;
|
|
_smushTracks[i].groupId = GRP_MASTER;
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::setGainReductionParams(int16 gainReductionLowerBound, int16 gainReductionMultiplier) {
|
|
if (gainReductionLowerBound)
|
|
_gainReductionLowerBound = gainReductionLowerBound;
|
|
if (gainReductionMultiplier)
|
|
_gainReductionFactor = gainReductionMultiplier;
|
|
}
|
|
|
|
void SmushPlayer::setGroupVolume(int groupId, int volValue) {
|
|
switch (groupId) {
|
|
case GRP_MASTER:
|
|
_smushTrackVols[0] = volValue; // Master
|
|
break;
|
|
case GRP_SFX:
|
|
_smushTrackVols[1] = volValue; // Sfx
|
|
break;
|
|
case GRP_BKGMUS:
|
|
_smushTrackVols[3] = volValue; // Music
|
|
break;
|
|
case GRP_SPEECH:
|
|
_smushTrackVols[2] = volValue; // Voice
|
|
break;
|
|
default: // Internal groups
|
|
for (int i = 0; i < SMUSH_MAX_TRACKS; i++) {
|
|
if (_smushTracks[i].groupId == groupId)
|
|
_smushTracks[i].volume = volValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::handleSAUDChunk(uint8 *srcBuf, uint32 size, int groupId, int vol, int pan, int16 flags, int trkId, int index, int maxFrames) {
|
|
int targetTrk, diff1, diff2;
|
|
uint32 dataSize, mod;
|
|
int32 maxBlockSize, saudSize;
|
|
uint16 flagsAccumulator[SMUSH_MAX_TRACKS];
|
|
|
|
if (!index) {
|
|
saudSize = READ_BE_UINT32(&srcBuf[4]);
|
|
|
|
// Find the maximum block sizes between the current ones
|
|
maxBlockSize = 0;
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
if (_smushTracks[i].blockSize > maxBlockSize) {
|
|
maxBlockSize = _smushTracks[i].blockSize;
|
|
}
|
|
}
|
|
|
|
diff2 = 10000000;
|
|
if ((flags & TRK_TYPE_MASK) != IS_BKG_MUSIC) {
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
if (_smushTracks[i].blockSize - saudSize >= 0) {
|
|
diff1 = _smushTracks[i].blockSize - saudSize;
|
|
} else {
|
|
diff1 = saudSize - _smushTracks[i].blockSize;
|
|
}
|
|
|
|
if (diff1 < diff2 && saudSize < 3 * _smushTracks[i].blockSize / 2) {
|
|
if (_smushTracks[i].blockSize - saudSize >= 0) {
|
|
diff2 = _smushTracks[i].blockSize - saudSize;
|
|
} else {
|
|
diff2 = saudSize - _smushTracks[i].blockSize;
|
|
}
|
|
|
|
maxBlockSize = _smushTracks[i].blockSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
flagsAccumulator[i] = 0;
|
|
if (_smushTracks[i].blockSize == maxBlockSize) {
|
|
if (_smushTracks[i].state == TRK_STATE_INACTIVE ||
|
|
_smushTracks[i].state == TRK_STATE_ENDING ||
|
|
_smushTracks[i].flags <= flags) {
|
|
if (_smushTracks[i].state == TRK_STATE_INACTIVE)
|
|
flagsAccumulator[i] = 0x1000;
|
|
|
|
if (_smushTracks[i].state == TRK_STATE_ENDING)
|
|
flagsAccumulator[i] += 0x200;
|
|
|
|
if (_smushTracks[i].flags == flags)
|
|
flagsAccumulator[i] += 0x400;
|
|
|
|
if (_smushTracks[i].flags < flags)
|
|
flagsAccumulator[i] += 0x800;
|
|
|
|
flagsAccumulator[i] += _smushTracks[i].parsedChunks + 1;
|
|
}
|
|
}
|
|
}
|
|
fillAudioTrackInfo(srcBuf, flagsAccumulator, size, groupId, vol, pan, flags, trkId, index, maxFrames);
|
|
return;
|
|
}
|
|
|
|
targetTrk = -1;
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
if (_smushTracks[i].state != TRK_STATE_INACTIVE &&
|
|
trkId == _smushTrackIds[i] &&
|
|
index == (_smushTrackIdxs[i] + 1) &&
|
|
maxFrames == _smushMaxFrames[i]) {
|
|
targetTrk = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (targetTrk != -1) {
|
|
_smushTrackIdxs[targetTrk]++;
|
|
dataSize = _smushTracks[targetTrk].dataSize;
|
|
mod = _smushTracks[targetTrk].availableSize % dataSize;
|
|
|
|
if (mod + size <= dataSize) {
|
|
memcpy(&_smushTracks[targetTrk].subChunkPtr[mod], srcBuf, size);
|
|
} else {
|
|
memcpy(&_smushTracks[targetTrk].subChunkPtr[mod], srcBuf, dataSize - mod);
|
|
memcpy(
|
|
_smushTracks[targetTrk].subChunkPtr,
|
|
&srcBuf[_smushTracks[targetTrk].dataSize - mod],
|
|
size + mod - _smushTracks[targetTrk].dataSize);
|
|
}
|
|
|
|
if (vol >= 0 && vol < 128)
|
|
_smushTracks[targetTrk].volume = vol;
|
|
|
|
if (pan > -128 && pan < 128)
|
|
_smushTracks[targetTrk].pan = pan;
|
|
|
|
_smushTracks[targetTrk].availableSize += size;
|
|
}
|
|
}
|
|
|
|
void SmushPlayer::fillAudioTrackInfo(uint8 *srcBuf, uint16 *flagsAccumulator, uint32 size, int groupId, int vol, int pan, int16 flags, int trkId, int index, int maxFrames) {
|
|
uint32 sdatSize, subChunkOffset, chunkSize;
|
|
|
|
int maxFlagAcc = -1;
|
|
int maxAccId = -1;
|
|
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
if (flagsAccumulator[i] && flagsAccumulator[i] > maxFlagAcc) {
|
|
maxFlagAcc = flagsAccumulator[i];
|
|
maxAccId = i;
|
|
}
|
|
}
|
|
|
|
if (maxAccId != -1) {
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
if (_smushTracks[i].parsedChunks < 255) {
|
|
_smushTracks[i].parsedChunks++;
|
|
}
|
|
}
|
|
|
|
_smushTracks[maxAccId].parsedChunks = 0;
|
|
_smushTracks[maxAccId].state = TRK_STATE_INACTIVE;
|
|
_smushTrackIds[maxAccId] = trkId;
|
|
_smushTrackIdxs[maxAccId] = 0;
|
|
_smushMaxFrames[maxAccId] = maxFrames;
|
|
subChunkOffset = READ_BE_UINT32(&srcBuf[12]);
|
|
sdatSize = READ_BE_UINT32(&srcBuf[subChunkOffset + 20]);
|
|
|
|
chunkSize = _smushTracks[maxAccId].blockSize;
|
|
|
|
if (size < chunkSize)
|
|
chunkSize = size;
|
|
|
|
memset(_smushTracks[maxAccId].blockPtr, 127, _smushTracks[maxAccId].blockSize);
|
|
memcpy(_smushTracks[maxAccId].blockPtr, srcBuf, chunkSize);
|
|
_smushTracks[maxAccId].dataBuf = _smushTracks[maxAccId].blockPtr + 16;
|
|
_smushTracks[maxAccId].dataSize = _smushTracks[maxAccId].blockSize - subChunkOffset - 24;
|
|
_smushTracks[maxAccId].subChunkPtr = &_smushTracks[maxAccId].dataBuf[subChunkOffset + 8];
|
|
_smushTracks[maxAccId].availableSize = size - subChunkOffset - 24;
|
|
_smushTracks[maxAccId].sdatSize = sdatSize;
|
|
_smushTracks[maxAccId].groupId = groupId;
|
|
_smushTracks[maxAccId].volume = 127;
|
|
_smushTracks[maxAccId].pan = 0;
|
|
|
|
if (vol >= 0 && vol < 128)
|
|
_smushTracks[maxAccId].volume = vol;
|
|
|
|
if (pan > -128 && pan < 128)
|
|
_smushTracks[maxAccId].pan = pan;
|
|
|
|
_smushTracks[maxAccId].flags = flags;
|
|
_smushTracks[maxAccId].audioRemaining = 0;
|
|
_smushTracks[maxAccId].state = TRK_STATE_FADING;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void SmushPlayer::processDispatches(int16 feedSize) {
|
|
int32 fadeStartOffset, cpySize, fadeRemaining, fadeFeedSize, mixFeedSize, mixInFrameCount;
|
|
int32 offset, tmpFeedSize, maxFadeChunkSize;
|
|
int16 fadeInFrameCount, flags, fadeMixStartingPoint;
|
|
|
|
int fadePan, fadeVolume, mixPan, mixVolume, baseVolume, mixStartingPoint;
|
|
|
|
bool isPlayableTrack;
|
|
bool speechIsPlaying = false;
|
|
|
|
if (!_paused) {
|
|
if (_smushTracksNeedInit) {
|
|
_smushTracksNeedInit = false;
|
|
for (int i = 0; i < SMUSH_MAX_TRACKS; i++) {
|
|
_smushDispatch[i].fadeRemaining = 0;
|
|
_smushDispatch[i].audioLength = 0;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _smushNumTracks; i++) {
|
|
isPlayableTrack = ((_smushTracks[i].flags & TRK_TYPE_MASK) == IS_SPEECH && isChanActive(CHN_SPEECH)) ||
|
|
((_smushTracks[i].flags & TRK_TYPE_MASK) == IS_BKG_MUSIC && isChanActive(CHN_BKGMUS)) ||
|
|
((_smushTracks[i].flags & TRK_TYPE_MASK) == IS_SFX && isChanActive(CHN_OTHER));
|
|
flags = _smushTracks[i].flags;
|
|
|
|
switch (flags & TRK_TYPE_MASK) {
|
|
case IS_SFX:
|
|
baseVolume = (_smushTrackVols[1] * _smushTracks[i].volume) >> 7;
|
|
break;
|
|
case IS_BKG_MUSIC:
|
|
baseVolume = (_smushTrackVols[3] * _smushTracks[i].volume) >> 7;
|
|
break;
|
|
case IS_SPEECH:
|
|
baseVolume = (_smushTrackVols[2] * _smushTracks[i].volume) >> 7;
|
|
break;
|
|
default:
|
|
error("SmushPlayer::processDispatches(): unrecognized flag %d", _smushTracks[i].flags & TRK_TYPE_MASK);
|
|
};
|
|
|
|
mixVolume = baseVolume * _smushTrackVols[0] / 127;
|
|
if ((flags & TRK_TYPE_MASK) == IS_BKG_MUSIC && isChanActive(CHN_SPEECH))
|
|
mixVolume = ((baseVolume * _smushTrackVols[0] / 127) * _gainReductionMultiplier) >> 8;
|
|
|
|
// Check if there's the need to allocate a crossfade
|
|
if (_smushTracks[i].state == TRK_STATE_FADING && _smushDispatch[i].state == TRK_STATE_PLAYING) {
|
|
fadeStartOffset = _smushDispatch[i].audioRemaining % _smushDispatch[i].dataSize;
|
|
_smushDispatch[i].fadeRemaining = SMUSH_FADE_SIZE;
|
|
_smushDispatch[i].fadeVolume = _smushTracks[i].volume;
|
|
_smushDispatch[i].fadeSampleRate = _smushDispatch[i].sampleRate;
|
|
|
|
memset(_smushTracks[i].fadeBuf, 127, _smushDispatch[i].fadeRemaining);
|
|
cpySize = _smushDispatch[i].dataSize - fadeStartOffset;
|
|
|
|
if (cpySize > _smushDispatch[i].fadeRemaining)
|
|
cpySize = _smushDispatch[i].fadeRemaining;
|
|
memcpy(_smushTracks[i].fadeBuf, &_smushDispatch[i].dataBuf[fadeStartOffset], cpySize);
|
|
|
|
_smushDispatch[i].volumeStep = 0;
|
|
} else if (_smushTracks[i].state == TRK_STATE_PLAYING) {
|
|
if (_smushDispatch[i].audioRemaining < _smushTracks[i].availableSize - _smushTracks[i].dataSize + 15000) {
|
|
if (_smushTracks[i].availableSize < _smushTracks[i].sdatSize) {
|
|
_smushDispatch[i].volumeStep = 0;
|
|
mixInFrameCount = _smushTracks[i].availableSize - _smushDispatch[i].audioRemaining - 15000;
|
|
|
|
if (mixInFrameCount > _smushDispatch[i].currentOffset)
|
|
mixInFrameCount = _smushDispatch[i].currentOffset;
|
|
|
|
if (_smushDispatch[i].audioRemaining + mixInFrameCount > _smushTracks[i].sdatSize - _smushDispatch[i].dataSize)
|
|
mixInFrameCount = _smushTracks[i].sdatSize - _smushDispatch[i].dataSize - _smushDispatch[i].audioRemaining;
|
|
|
|
if (mixInFrameCount > 0) {
|
|
_smushDispatch[i].fadeRemaining = SMUSH_FADE_SIZE;
|
|
_smushDispatch[i].fadeSampleRate = _smushDispatch[i].sampleRate;
|
|
|
|
memcpy(
|
|
_smushTracks[i].fadeBuf,
|
|
&_smushDispatch[i].dataBuf[_smushDispatch[i].audioRemaining % _smushDispatch[i].dataSize],
|
|
_smushDispatch[i].fadeRemaining);
|
|
|
|
_smushDispatch[i].audioRemaining += mixInFrameCount;
|
|
_smushDispatch[i].currentOffset -= mixInFrameCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the fade has been allocated, flush it in the mixer
|
|
if (_smushDispatch[i].fadeRemaining) {
|
|
maxFadeChunkSize = _smushDispatch[i].fadeSampleRate * feedSize / _smushAudioSampleRate;
|
|
|
|
if (_smushDispatch[i].fadeRemaining > maxFadeChunkSize) {
|
|
fadeRemaining = maxFadeChunkSize;
|
|
} else {
|
|
fadeRemaining = _smushDispatch[i].fadeRemaining;
|
|
}
|
|
|
|
fadeMixStartingPoint = 0;
|
|
while (fadeRemaining) {
|
|
fadeInFrameCount = (fadeRemaining < DIMUSE_FEEDSIZE / 2) ? fadeRemaining : DIMUSE_FEEDSIZE / 2;
|
|
|
|
if (fadeInFrameCount == maxFadeChunkSize) {
|
|
fadeFeedSize = feedSize;
|
|
} else {
|
|
fadeFeedSize = _smushAudioSampleRate * fadeInFrameCount / _smushDispatch[i].fadeSampleRate;
|
|
}
|
|
|
|
if (isPlayableTrack) {
|
|
fadeVolume = _smushDispatch[i].fadeRemaining * _smushDispatch[i].fadeVolume * _smushTrackVols[0] / (SMUSH_FADE_SIZE * 127);
|
|
fadePan = _smushTracks[i].pan;
|
|
|
|
sendAudioToDiMUSE(
|
|
&_smushTracks[i].fadeBuf[SMUSH_FADE_SIZE - _smushDispatch[i].fadeRemaining],
|
|
fadeMixStartingPoint,
|
|
fadeFeedSize,
|
|
fadeInFrameCount,
|
|
fadeVolume,
|
|
fadePan);
|
|
}
|
|
|
|
fadeMixStartingPoint += fadeFeedSize;
|
|
fadeRemaining -= fadeInFrameCount;
|
|
_smushDispatch[i].fadeRemaining -= fadeInFrameCount;
|
|
}
|
|
}
|
|
|
|
if (_smushTracks[i].state == TRK_STATE_FADING) {
|
|
if (_smushDispatch[i].state != TRK_STATE_PLAYING)
|
|
_smushDispatch[i].volumeStep = 16;
|
|
|
|
_smushDispatch[i].headerPtr = _smushTracks[i].dataBuf;
|
|
_smushDispatch[i].dataBuf = _smushTracks[i].subChunkPtr;
|
|
_smushDispatch[i].dataSize = _smushTracks[i].dataSize;
|
|
_smushDispatch[i].currentOffset = 0;
|
|
_smushDispatch[i].audioLength = 0;
|
|
_smushTracks[i].state = TRK_STATE_PLAYING;
|
|
}
|
|
|
|
if (_smushTracks[i].state != TRK_STATE_INACTIVE) {
|
|
tmpFeedSize = feedSize;
|
|
mixStartingPoint = 0;
|
|
if (feedSize > 0) {
|
|
while (1) {
|
|
mixInFrameCount = _smushDispatch[i].currentOffset;
|
|
if (mixInFrameCount > 0) {
|
|
offset = _smushDispatch[i].audioRemaining % _smushDispatch[i].dataSize;
|
|
|
|
if (mixInFrameCount > _smushDispatch[i].sampleRate * tmpFeedSize / _smushAudioSampleRate)
|
|
mixInFrameCount = _smushDispatch[i].sampleRate * tmpFeedSize / _smushAudioSampleRate;
|
|
|
|
if (offset + mixInFrameCount > _smushDispatch[i].dataSize)
|
|
mixInFrameCount = _smushDispatch[i].dataSize - offset;
|
|
|
|
if (mixInFrameCount + _smushDispatch[i].audioRemaining <= _smushTracks[i].availableSize) {
|
|
// Fade-in until full volume is reached
|
|
if (_smushDispatch[i].volumeStep < 16)
|
|
_smushDispatch[i].volumeStep++;
|
|
|
|
if (mixInFrameCount > DIMUSE_FEEDSIZE / 2)
|
|
mixInFrameCount = DIMUSE_FEEDSIZE / 2;
|
|
|
|
_smushTracks[i].state = TRK_STATE_PLAYING;
|
|
|
|
// This flag is toggled one time per chunk: if it's yields "true" for
|
|
// even one track, it stays like that for the whole chunk
|
|
speechIsPlaying = !speechIsPlaying ? (_smushTracks[i].flags & TRK_TYPE_MASK) == IS_SPEECH : true;
|
|
} else {
|
|
// Fade-out until silent
|
|
if (_smushDispatch[i].volumeStep)
|
|
_smushDispatch[i].volumeStep--;
|
|
|
|
_smushTracks[i].state = TRK_STATE_ENDING;
|
|
|
|
if (mixInFrameCount > DIMUSE_FEEDSIZE / 2)
|
|
mixInFrameCount = DIMUSE_FEEDSIZE / 2;
|
|
|
|
_smushDispatch[i].audioRemaining -= mixInFrameCount;
|
|
_smushDispatch[i].currentOffset += mixInFrameCount;
|
|
offset = _smushDispatch[i].audioRemaining % _smushDispatch[i].dataSize;
|
|
}
|
|
|
|
if (mixInFrameCount == _smushDispatch[i].sampleRate * tmpFeedSize / _smushAudioSampleRate) {
|
|
mixFeedSize = tmpFeedSize;
|
|
} else {
|
|
mixFeedSize = mixInFrameCount * _smushAudioSampleRate / _smushDispatch[i].sampleRate;
|
|
}
|
|
|
|
if (isPlayableTrack) {
|
|
mixPan = _smushTracks[i].pan;
|
|
sendAudioToDiMUSE(
|
|
&_smushDispatch[i].dataBuf[offset],
|
|
mixStartingPoint,
|
|
mixFeedSize,
|
|
mixInFrameCount,
|
|
(mixVolume * _smushDispatch[i].volumeStep) >> 4,
|
|
mixPan);
|
|
}
|
|
|
|
_smushDispatch[i].currentOffset -= mixInFrameCount;
|
|
_smushDispatch[i].audioRemaining += mixInFrameCount;
|
|
tmpFeedSize -= mixFeedSize;
|
|
mixStartingPoint += mixFeedSize;
|
|
}
|
|
|
|
if (_smushDispatch[i].currentOffset <= 0) {
|
|
if (processAudioCodes(i, tmpFeedSize, mixVolume) && tmpFeedSize <= 0) {
|
|
break;
|
|
}
|
|
} else if (tmpFeedSize <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_smushTracks[i].audioRemaining = _smushDispatch[i].audioRemaining;
|
|
_smushDispatch[i].state = _smushTracks[i].state;
|
|
};
|
|
|
|
if (speechIsPlaying) {
|
|
if (_gainReductionMultiplier > _gainReductionLowerBound) {
|
|
_gainReductionMultiplier -= (feedSize * 2 * _gainReductionFactor) >> 13;
|
|
if (_gainReductionMultiplier < _gainReductionLowerBound)
|
|
_gainReductionMultiplier = _gainReductionLowerBound;
|
|
}
|
|
} else {
|
|
if (_gainReductionMultiplier < 256) {
|
|
_gainReductionMultiplier += (feedSize * 2 * _gainReductionFactor) >> 15;
|
|
if (_gainReductionMultiplier > 256)
|
|
_gainReductionMultiplier = 256;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SmushPlayer::processAudioCodes(int idx, int32 &tmpFeedSize, int &mixVolume) {
|
|
uint8 *code, *buf, subcode, value;
|
|
int chunk;
|
|
|
|
while (tmpFeedSize) {
|
|
code = _smushDispatch[idx].headerPtr;
|
|
|
|
switch (code[0]) {
|
|
case 1: // Init
|
|
_smushDispatch[idx].audioLength = 0;
|
|
buf = _smushDispatch[idx].headerPtr;
|
|
_smushDispatch[idx].audioRemaining = READ_BE_UINT32(buf + 2);
|
|
_smushDispatch[idx].currentOffset = READ_BE_UINT32(buf + 6);
|
|
_smushDispatch[idx].sampleRate = _smushAudioSampleRate;
|
|
_smushDispatch[idx].headerPtr += _smushDispatch[idx].headerPtr[1] + 2;
|
|
if (_smushDispatch[idx].audioRemaining < _smushTracks[idx].availableSize + (_smushTracks[idx].availableSize >= _smushTracks[idx].sdatSize ? 0 : 15000) - _smushTracks[idx].dataSize) {
|
|
chunk = _smushTracks[idx].availableSize - _smushTracks[idx].dataSize - _smushDispatch[idx].audioRemaining + 15000;
|
|
if (chunk > _smushDispatch[idx].currentOffset) {
|
|
_smushTracks[idx].state = TRK_STATE_INACTIVE;
|
|
_smushTracks[idx].groupId = GRP_MASTER;
|
|
tmpFeedSize = 0;
|
|
break;
|
|
}
|
|
|
|
_smushDispatch[idx].audioRemaining += chunk;
|
|
_smushDispatch[idx].currentOffset -= chunk;
|
|
}
|
|
break;
|
|
case 2: //
|
|
case 8: //
|
|
case 9: // Compare params
|
|
case 0xA: //
|
|
case 0xB: //
|
|
subcode = code[4];
|
|
switch (subcode) {
|
|
case 0xFF:
|
|
value = _smushTrackVols[0];
|
|
break;
|
|
case 0xFE:
|
|
value = _smushTracks[idx].volume;
|
|
break;
|
|
case 0xFD:
|
|
value = _smushTracks[idx].pan;
|
|
break;
|
|
default:
|
|
value = _smushAudioTable[subcode];
|
|
break;
|
|
}
|
|
|
|
switch (code[0]) {
|
|
case 2:
|
|
if (value || (subcode == 0)) {
|
|
_smushDispatch[idx].headerPtr = &code[READ_BE_UINT16(&code[2])];
|
|
}
|
|
break;
|
|
case 8:
|
|
value = value > code[5];
|
|
break;
|
|
case 9:
|
|
value = value < code[5];
|
|
break;
|
|
case 0xA:
|
|
value = value == code[5];
|
|
break;
|
|
case 0xB:
|
|
value = value != code[5];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!value) {
|
|
_smushDispatch[idx].headerPtr = &code[code[1] + 2];
|
|
} else {
|
|
_smushDispatch[idx].headerPtr = &code[READ_BE_UINT16(&code[2])];
|
|
}
|
|
|
|
break;
|
|
case 3: // Set params
|
|
switch (code[2]) {
|
|
case 0xFF:
|
|
_smushTrackVols[0] = code[3];
|
|
break;
|
|
case 0xFE:
|
|
_smushTracks[idx].volume = code[3];
|
|
mixVolume = (_smushTrackVols[0] * _smushTracks[idx].volume) / 127;
|
|
|
|
// Set a lower mix volume of the background music if speech is active
|
|
if ((_smushTracks[idx].flags & TRK_TYPE_MASK) == IS_BKG_MUSIC && isChanActive(CHN_SPEECH))
|
|
mixVolume = (mixVolume * _gainReductionMultiplier) >> 8;
|
|
break;
|
|
case 0xFD:
|
|
_smushTracks[idx].pan = code[3];
|
|
break;
|
|
default:
|
|
_smushAudioTable[code[2]] = code[3];
|
|
break;
|
|
}
|
|
_smushDispatch[idx].headerPtr = &code[code[1] + 2];
|
|
break;
|
|
case 4: // Increment params
|
|
switch (code[2]) {
|
|
case 0xFF:
|
|
_smushTrackVols[0] += code[3];
|
|
break;
|
|
case 0xFE:
|
|
_smushTracks[idx].volume += code[3];
|
|
break;
|
|
case 0xFD:
|
|
_smushTracks[idx].pan += code[3];
|
|
break;
|
|
default:
|
|
_smushAudioTable[code[2]] += code[3];
|
|
break;
|
|
}
|
|
_smushDispatch[idx].headerPtr = &code[code[1] + 2];
|
|
break;
|
|
case 6: // Set offset
|
|
_smushDispatch[idx].audioLength = 0;
|
|
buf = _smushDispatch[idx].headerPtr;
|
|
_smushDispatch[idx].audioRemaining = READ_BE_UINT32(buf + 2);
|
|
_smushDispatch[idx].currentOffset = READ_BE_UINT32(buf + 6);
|
|
_smushDispatch[idx].sampleRate = _smushAudioSampleRate;
|
|
|
|
_smushDispatch[idx].headerPtr += _smushDispatch[idx].headerPtr[1] + 2;
|
|
if (_smushDispatch[idx].audioRemaining < _smushTracks[idx].availableSize + (_smushTracks[idx].availableSize >= _smushTracks[idx].sdatSize ? 0 : 15000) - _smushTracks[idx].dataSize) {
|
|
chunk = _smushTracks[idx].availableSize - _smushTracks[idx].dataSize - _smushDispatch[idx].audioRemaining + 15000;
|
|
if (chunk > _smushDispatch[idx].currentOffset) {
|
|
_smushTracks[idx].state = TRK_STATE_INACTIVE;
|
|
_smushTracks[idx].groupId = GRP_MASTER;
|
|
tmpFeedSize = 0;
|
|
break;
|
|
}
|
|
|
|
_smushDispatch[idx].audioRemaining += chunk;
|
|
_smushDispatch[idx].currentOffset -= chunk;
|
|
}
|
|
break;
|
|
case 7: // Set audio length
|
|
if (!_smushDispatch[idx].audioLength) {
|
|
_smushDispatch[idx].audioLength = READ_BE_UINT32(&code[6]);
|
|
_smushDispatch[idx].elapsedAudio = 0;
|
|
}
|
|
|
|
buf = _smushDispatch[idx].headerPtr;
|
|
_smushDispatch[idx].audioRemaining = _smushDispatch[idx].elapsedAudio + READ_BE_UINT32(buf + 2);
|
|
|
|
_smushDispatch[idx].currentOffset = READ_BE_UINT32(buf + 14);
|
|
if (_smushDispatch[idx].currentOffset > _smushDispatch[idx].audioLength)
|
|
_smushDispatch[idx].currentOffset = _smushDispatch[idx].audioLength;
|
|
|
|
_smushDispatch[idx].sampleRate = _smushAudioSampleRate;
|
|
|
|
_smushDispatch[idx].audioLength -= _smushDispatch[idx].currentOffset;
|
|
_smushDispatch[idx].elapsedAudio += _smushDispatch[idx].currentOffset;
|
|
|
|
if (_smushDispatch[idx].audioLength) {
|
|
_smushDispatch[idx].headerPtr = &code[code[1] + 2];
|
|
} else {
|
|
_smushDispatch[idx].headerPtr = &code[READ_BE_UINT16(&code[18])];
|
|
}
|
|
|
|
if (_smushDispatch[idx].audioRemaining >= _smushTracks[idx].availableSize + (_smushTracks[idx].availableSize >= _smushTracks[idx].sdatSize ? 0 : 15000) - _smushTracks[idx].dataSize) {
|
|
chunk = _smushTracks[idx].availableSize - _smushTracks[idx].dataSize - _smushDispatch[idx].audioRemaining + 15000;
|
|
if (chunk > _smushDispatch[idx].currentOffset) {
|
|
_smushTracks[idx].state = TRK_STATE_INACTIVE;
|
|
_smushTracks[idx].groupId = GRP_MASTER;
|
|
tmpFeedSize = 0;
|
|
} else {
|
|
_smushDispatch[idx].audioRemaining += chunk;
|
|
_smushDispatch[idx].currentOffset -= chunk;
|
|
}
|
|
}
|
|
|
|
break;
|
|
default:
|
|
_smushTracks[idx].state = TRK_STATE_INACTIVE;
|
|
_smushTracks[idx].groupId = GRP_MASTER;
|
|
tmpFeedSize = 0;
|
|
}
|
|
|
|
if (_smushDispatch[idx].currentOffset > 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SmushPlayer::sendAudioToDiMUSE(uint8 *mixBuf, int32 mixStartingPoint, int32 mixFeedSize, int32 mixInFrameCount, int volume, int pan) {
|
|
int clampedVol, clampedPan;
|
|
bool is11025Hz = false;
|
|
|
|
if (mixFeedSize == 2 * mixInFrameCount) {
|
|
is11025Hz = true;
|
|
} else if (mixFeedSize != mixInFrameCount) {
|
|
return;
|
|
}
|
|
|
|
clampedPan = CLIP<int>((pan / 2) + 64, 0, 127);
|
|
clampedVol = CLIP<int>(volume, 0, 127);
|
|
_imuseDigital->receiveAudioFromSMUSH(mixBuf, mixInFrameCount, mixFeedSize, mixStartingPoint, clampedVol, clampedPan, is11025Hz);
|
|
}
|
|
|
|
void SmushPlayer::feedAudio(uint8 *srcBuf, int groupId, int volume, int pan, int16 flags) {
|
|
int panDelta, effPan;
|
|
int32 maxFrames;
|
|
uint16 trkId, index;
|
|
|
|
if (_smushAudioInitialized) {
|
|
// Check file encoding
|
|
if (srcBuf[8] == 0 && srcBuf[9] == 0 && srcBuf[12] == 0 && srcBuf[13] == 0 && srcBuf[16] == 0 && srcBuf[17] == 0) {
|
|
trkId = READ_BE_INT16(&srcBuf[10]);
|
|
index = READ_BE_INT16(&srcBuf[14]);
|
|
maxFrames = READ_BE_INT16(&srcBuf[18]);
|
|
|
|
handleSAUDChunk(
|
|
srcBuf + 20,
|
|
READ_BE_UINT32(&srcBuf[4]) - 12,
|
|
groupId,
|
|
volume,
|
|
pan,
|
|
flags,
|
|
trkId,
|
|
index,
|
|
maxFrames);
|
|
} else {
|
|
trkId = READ_LE_INT16(&srcBuf[8]);
|
|
index = READ_LE_INT16(&srcBuf[10]);
|
|
maxFrames = READ_LE_INT16(&srcBuf[12]);
|
|
flags |= READ_LE_INT16(&srcBuf[14]);
|
|
volume = (volume * srcBuf[16]) >> 7;
|
|
panDelta = srcBuf[17];
|
|
|
|
if (panDelta == 128) {
|
|
effPan = 128;
|
|
} else {
|
|
effPan = pan + panDelta;
|
|
}
|
|
|
|
handleSAUDChunk(
|
|
srcBuf + 18,
|
|
READ_BE_UINT32(&srcBuf[4]) - 10,
|
|
groupId,
|
|
(volume * srcBuf[16]) >> 7,
|
|
effPan,
|
|
flags,
|
|
trkId,
|
|
index,
|
|
maxFrames);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SmushPlayer::isAudioCallbackEnabled() {
|
|
return _smushAudioCallbackEnabled;
|
|
}
|
|
|
|
} // End of namespace Scumm
|