Initial commit
This commit is contained in:
236
engines/mm/shared/xeen/cc_archive.cpp
Normal file
236
engines/mm/shared/xeen/cc_archive.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/* 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/file.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "mm/shared/xeen/cc_archive.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
uint16 BaseCCArchive::convertNameToId(const Common::Path &resourceName) {
|
||||
Common::String name = resourceName.baseName();
|
||||
if (resourceName.empty() || name.hasSuffix("."))
|
||||
return 0xffff;
|
||||
|
||||
name.toUppercase();
|
||||
|
||||
// Check if a resource number is being directly specified
|
||||
if (name.size() == 4) {
|
||||
char *endPtr;
|
||||
uint16 num = (uint16)strtol(name.c_str(), &endPtr, 16);
|
||||
if (!*endPtr)
|
||||
return num;
|
||||
}
|
||||
|
||||
const byte *msgP = (const byte *)name.c_str();
|
||||
int total = *msgP++;
|
||||
for (; *msgP; total += *msgP++) {
|
||||
// Rotate the bits in 'total' right 7 places
|
||||
total = (total & 0x007F) << 9 | (total & 0xFF80) >> 7;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
void BaseCCArchive::loadIndex(Common::SeekableReadStream &stream) {
|
||||
int count = stream.readUint16LE();
|
||||
size_t size = count * 8;
|
||||
|
||||
// Read in the data for the archive's index
|
||||
byte *rawIndex = new byte[size];
|
||||
|
||||
if (stream.read(rawIndex, size) != size) {
|
||||
delete[] rawIndex;
|
||||
error("Failed to read %zu bytes from CC file", size);
|
||||
}
|
||||
|
||||
// Decrypt the index
|
||||
int seed = 0xac;
|
||||
for (int i = 0; i < count * 8; ++i, seed += 0x67) {
|
||||
rawIndex[i] = (byte)((((rawIndex[i] << 2) | (rawIndex[i] >> 6)) + seed) & 0xff);
|
||||
}
|
||||
|
||||
// Extract the index data into entry structures
|
||||
_index.resize(count);
|
||||
const byte *entryP = &rawIndex[0];
|
||||
for (int idx = 0; idx < count; ++idx, entryP += 8) {
|
||||
CCEntry entry;
|
||||
entry._id = READ_LE_UINT16(entryP);
|
||||
entry._offset = READ_LE_UINT32(entryP + 2) & 0xffffff;
|
||||
entry._size = READ_LE_UINT16(entryP + 5);
|
||||
assert(!entryP[7]);
|
||||
|
||||
_index[idx] = entry;
|
||||
}
|
||||
|
||||
delete[] rawIndex;
|
||||
}
|
||||
|
||||
void BaseCCArchive::saveIndex(Common::WriteStream &stream) {
|
||||
// Fill up the data for the index entries into a raw data block
|
||||
byte *rawIndex = new byte[_index.size() * 8];
|
||||
byte b;
|
||||
|
||||
byte *entryP = rawIndex;
|
||||
for (uint i = 0; i < _index.size(); ++i, entryP += 8) {
|
||||
CCEntry &entry = _index[i];
|
||||
WRITE_LE_UINT16(&entryP[0], entry._id);
|
||||
WRITE_LE_UINT32(&entryP[2], entry._writeOffset);
|
||||
WRITE_LE_UINT16(&entryP[5], entry._size);
|
||||
entryP[7] = 0;
|
||||
}
|
||||
|
||||
// Encrypt the index
|
||||
int seed = 0xac;
|
||||
for (uint i = 0; i < _index.size() * 8; ++i, seed += 0x67) {
|
||||
b = (rawIndex[i] - seed) & 0xff;
|
||||
b = (byte)((b >> 2) | (b << 6));
|
||||
|
||||
assert(rawIndex[i] == (byte)((((b << 2) | (b >> 6)) + seed) & 0xff));
|
||||
rawIndex[i] = b;
|
||||
}
|
||||
|
||||
// Write out the number of entries and the encrypted index data
|
||||
stream.writeUint16LE(_index.size());
|
||||
stream.write(rawIndex, _index.size() * 8);
|
||||
|
||||
delete[] rawIndex;
|
||||
}
|
||||
|
||||
bool BaseCCArchive::hasFile(const Common::Path &path) const {
|
||||
CCEntry ccEntry;
|
||||
return getHeaderEntry(path, ccEntry);
|
||||
}
|
||||
|
||||
bool BaseCCArchive::getHeaderEntry(const Common::Path &resourceName, CCEntry &ccEntry) const {
|
||||
return getHeaderEntry(convertNameToId(resourceName), ccEntry);
|
||||
}
|
||||
|
||||
bool BaseCCArchive::getHeaderEntry(uint16 id, CCEntry &ccEntry) const {
|
||||
// Loop through the index
|
||||
for (uint i = 0; i < _index.size(); ++i) {
|
||||
if (_index[i]._id == id) {
|
||||
ccEntry = _index[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find an entry
|
||||
return false;
|
||||
}
|
||||
|
||||
const Common::ArchiveMemberPtr BaseCCArchive::getMember(const Common::Path &path) const {
|
||||
if (!hasFile(path))
|
||||
return Common::ArchiveMemberPtr();
|
||||
|
||||
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
|
||||
}
|
||||
|
||||
int BaseCCArchive::listMembers(Common::ArchiveMemberList &list) const {
|
||||
// CC files don't maintain the original filenames, so we can't list it
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
CCArchive::CCArchive(const Common::Path &filename, bool encoded) :
|
||||
BaseCCArchive(), _filename(filename), _encoded(encoded) {
|
||||
|
||||
Common::File f;
|
||||
if (!f.open(filename, SearchMan))
|
||||
error("Could not open file - %s", filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
loadIndex(f);
|
||||
}
|
||||
|
||||
CCArchive::CCArchive(const Common::Path &filename, const Common::String &prefix,
|
||||
bool encoded) : BaseCCArchive(), _filename(filename),
|
||||
_prefix(prefix), _encoded(encoded) {
|
||||
_prefix.toLowercase();
|
||||
|
||||
Common::File f;
|
||||
if (!f.open(filename, SearchMan))
|
||||
error("Could not open file - %s", filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
loadIndex(f);
|
||||
}
|
||||
|
||||
CCArchive::~CCArchive() {
|
||||
}
|
||||
|
||||
bool CCArchive::getHeaderEntry(const Common::Path &resourceName, Shared::Xeen::CCEntry &ccEntry) const {
|
||||
Common::String resName = resourceName.baseName();
|
||||
|
||||
if (!_prefix.empty() && resName.contains('|')) {
|
||||
resName.toLowercase();
|
||||
Common::String prefix = _prefix + "|";
|
||||
|
||||
if (!strncmp(resName.c_str(), prefix.c_str(), prefix.size()))
|
||||
// Matching CC prefix, so strip it off and allow processing to
|
||||
// continue onto the base getHeaderEntry method
|
||||
resName = Common::String(resName.c_str() + prefix.size());
|
||||
else
|
||||
// Not matching prefix, so don't allow a match
|
||||
return false;
|
||||
}
|
||||
|
||||
return BaseCCArchive::getHeaderEntry(resourceName.getParent().appendComponent(resName), ccEntry);
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *CCArchive::createReadStreamForMember(const Common::Path &path) const {
|
||||
Shared::Xeen::CCEntry ccEntry;
|
||||
|
||||
if (getHeaderEntry(path, ccEntry)) {
|
||||
// Open the correct CC file
|
||||
Common::File f;
|
||||
if (!f.open(_filename))
|
||||
error("Could not open CC file");
|
||||
|
||||
// Read in the data for the specific resource
|
||||
if (!f.seek(ccEntry._offset))
|
||||
error("Failed to seek to %d bytes in CC file", ccEntry._offset);
|
||||
|
||||
byte *data = (byte *)malloc(ccEntry._size);
|
||||
|
||||
if (f.read(data, ccEntry._size) != ccEntry._size) {
|
||||
free(data);
|
||||
error("Failed to read %hu bytes in CC file", ccEntry._size);
|
||||
}
|
||||
|
||||
if (_encoded) {
|
||||
// Decrypt the data
|
||||
for (int i = 0; i < ccEntry._size; ++i)
|
||||
data[i] ^= 0x35;
|
||||
}
|
||||
|
||||
// Return the data as a stream
|
||||
return new Common::MemoryReadStream(data, ccEntry._size, DisposeAfterUse::YES);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
114
engines/mm/shared/xeen/cc_archive.h
Normal file
114
engines/mm/shared/xeen/cc_archive.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_CC_ARCHIVE_H
|
||||
#define MM_SHARED_XEEN_CC_ARCHIVE_H
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
/**
|
||||
* Details of a single entry in a CC file index
|
||||
*/
|
||||
struct CCEntry {
|
||||
uint16 _id;
|
||||
int _offset;
|
||||
uint16 _size;
|
||||
int _writeOffset;
|
||||
|
||||
CCEntry() : _id(0), _offset(0), _size(0), _writeOffset(0) {
|
||||
}
|
||||
CCEntry(uint16 id, uint32 offset, uint32 size)
|
||||
: _id(id), _offset(offset), _size(size) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Base Xeen CC file implementation
|
||||
*/
|
||||
class BaseCCArchive : public Common::Archive {
|
||||
protected:
|
||||
Common::Array<CCEntry> _index;
|
||||
|
||||
/**
|
||||
* Load the index of a given CC file
|
||||
*/
|
||||
void loadIndex(Common::SeekableReadStream &stream);
|
||||
|
||||
/**
|
||||
* Saves out the contents of the index. Used when creating savegames
|
||||
*/
|
||||
void saveIndex(Common::WriteStream &stream);
|
||||
|
||||
/**
|
||||
* Given a resource name, returns whether an entry exists, and returns
|
||||
* the header index data for that entry
|
||||
*/
|
||||
virtual bool getHeaderEntry(const Common::Path &resourceName, CCEntry &ccEntry) const;
|
||||
|
||||
/**
|
||||
* Given a resource Id, returns whether an entry exists, and returns
|
||||
* the header index data for that entry
|
||||
*/
|
||||
virtual bool getHeaderEntry(uint16 id, CCEntry &ccEntry) const;
|
||||
public:
|
||||
/**
|
||||
* Hash a given filename to produce the Id that represents it
|
||||
*/
|
||||
static uint16 convertNameToId(const Common::Path &resourceName);
|
||||
public:
|
||||
BaseCCArchive() {
|
||||
}
|
||||
|
||||
// Archive implementation
|
||||
bool hasFile(const Common::Path &path) const override;
|
||||
int listMembers(Common::ArchiveMemberList &list) const override;
|
||||
const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Xeen CC file implementation
|
||||
*/
|
||||
class CCArchive : public BaseCCArchive {
|
||||
private:
|
||||
Common::Path _filename;
|
||||
Common::String _prefix;
|
||||
bool _encoded;
|
||||
protected:
|
||||
bool getHeaderEntry(const Common::Path &resourceName, CCEntry &ccEntry) const override;
|
||||
public:
|
||||
CCArchive(const Common::Path &filename, bool encoded);
|
||||
CCArchive(const Common::Path &filename, const Common::String &prefix, bool encoded);
|
||||
~CCArchive() override;
|
||||
|
||||
// Archive implementation
|
||||
Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
|
||||
};
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
|
||||
#endif
|
||||
203
engines/mm/shared/xeen/file.cpp
Normal file
203
engines/mm/shared/xeen/file.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
/* 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 "mm/shared/xeen/file.h"
|
||||
#ifdef ENABLE_XEEN
|
||||
#include "mm/xeen/files.h"
|
||||
#include "mm/xeen/xeen.h"
|
||||
#endif
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
File::File(const Common::Path &filename) {
|
||||
File::open(filename);
|
||||
}
|
||||
|
||||
File::File(const Common::Path &filename, Common::Archive &archive) {
|
||||
File::open(filename, archive);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_XEEN
|
||||
File::File(const Common::Path &filename, int ccMode) {
|
||||
File::open(filename, ccMode);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool File::open(const Common::Path &filename) {
|
||||
#ifdef ENABLE_XEEN
|
||||
MM::Xeen::XeenEngine *engine = dynamic_cast<MM::Xeen::XeenEngine *>(g_engine);
|
||||
|
||||
if (engine) {
|
||||
MM::Xeen::FileManager &fm = *engine->_files;
|
||||
|
||||
if (!fm._currentSave || !Common::File::open(filename, *fm._currentSave)) {
|
||||
if (!fm._currentArchive || !Common::File::open(filename, *fm._currentArchive)) {
|
||||
// Could not find in current archive, so try intro.cc or in folder
|
||||
if (!Common::File::open(filename))
|
||||
error("Could not open file - %s", filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!Common::File::open(filename))
|
||||
error("Could not open file - %s", filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
}
|
||||
#else
|
||||
if (!Common::File::open(filename))
|
||||
error("Could not open file - %s", filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::open(const Common::Path &filename, Common::Archive &archive) {
|
||||
if (!Common::File::open(filename, archive))
|
||||
error("Could not open file - %s", filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_XEEN
|
||||
bool File::open(const Common::Path &filename, int ccMode) {
|
||||
MM::Xeen::XeenEngine *engine = dynamic_cast<MM::Xeen::XeenEngine *>(g_engine);
|
||||
assert(engine);
|
||||
|
||||
MM::Xeen::FileManager &fm = *engine->_files;
|
||||
int oldNum = fm._ccNum;
|
||||
|
||||
fm.setGameCc(ccMode);
|
||||
if (File::exists(filename, *fm._currentArchive))
|
||||
File::open(filename, *fm._currentArchive);
|
||||
else
|
||||
File::open(filename);
|
||||
|
||||
fm.setGameCc(oldNum);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void File::setCurrentArchive(int ccMode) {
|
||||
MM::Xeen::XeenEngine *engine = dynamic_cast<MM::Xeen::XeenEngine *>(g_engine);
|
||||
assert(engine);
|
||||
MM::Xeen::FileManager &fm = *engine->_files;
|
||||
|
||||
switch (ccMode) {
|
||||
case 0:
|
||||
fm._currentArchive = fm._xeenCc;
|
||||
fm._currentSave = fm._xeenSave;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
fm._currentArchive = fm._darkCc;
|
||||
fm._currentSave = fm._darkSave;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
fm._currentArchive = fm._introCc;
|
||||
fm._currentSave = nullptr;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
assert(fm._currentArchive);
|
||||
}
|
||||
#endif
|
||||
|
||||
Common::String File::readString() {
|
||||
Common::String result;
|
||||
char c;
|
||||
|
||||
while (pos() < size() && (c = (char)readByte()) != '\0')
|
||||
result += c;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool File::exists(const Common::Path &filename) {
|
||||
#ifdef ENABLE_XEEN
|
||||
MM::Xeen::XeenEngine *engine = dynamic_cast<MM::Xeen::XeenEngine *>(g_engine);
|
||||
|
||||
if (engine) {
|
||||
MM::Xeen::FileManager &fm = *engine->_files;
|
||||
|
||||
if (!fm._currentSave || !fm._currentSave->hasFile(filename)) {
|
||||
if (!fm._currentArchive->hasFile(filename)) {
|
||||
// Could not find in current archive, so try intro.cc or in folder
|
||||
return Common::File::exists(filename);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return Common::File::exists(filename);
|
||||
}
|
||||
#else
|
||||
return Common::File::exists(filename);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_XEEN
|
||||
bool File::exists(const Common::Path &filename, int ccMode) {
|
||||
MM::Xeen::XeenEngine *engine = dynamic_cast<MM::Xeen::XeenEngine *>(g_engine);
|
||||
assert(engine);
|
||||
MM::Xeen::FileManager &fm = *engine->_files;
|
||||
int oldNum = fm._ccNum;
|
||||
|
||||
fm.setGameCc(ccMode);
|
||||
bool result = exists(filename);
|
||||
fm.setGameCc(oldNum);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool File::exists(const Common::Path &filename, Common::Archive &archive) {
|
||||
return archive.hasFile(filename);
|
||||
}
|
||||
|
||||
void File::syncBitFlags(Common::Serializer &s, bool *startP, bool *endP) {
|
||||
byte data = 0;
|
||||
|
||||
int bitCounter = 0;
|
||||
for (bool *p = startP; p < endP; ++p, bitCounter = (bitCounter + 1) % 8) {
|
||||
if (bitCounter == 0) {
|
||||
if (s.isLoading() || p != startP)
|
||||
s.syncAsByte(data);
|
||||
|
||||
if (s.isSaving())
|
||||
data = 0;
|
||||
}
|
||||
|
||||
if (s.isLoading())
|
||||
*p = ((data >> bitCounter) & 1) != 0;
|
||||
else if (*p)
|
||||
data |= 1 << bitCounter;
|
||||
}
|
||||
|
||||
if (s.isSaving())
|
||||
s.syncAsByte(data);
|
||||
}
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
128
engines/mm/shared/xeen/file.h
Normal file
128
engines/mm/shared/xeen/file.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_FILE_H
|
||||
#define MM_SHARED_XEEN_FILE_H
|
||||
|
||||
#include "common/file.h"
|
||||
#include "common/serializer.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
|
||||
/**
|
||||
* Derived file class
|
||||
*/
|
||||
class File : public Common::File {
|
||||
private:
|
||||
public:
|
||||
#ifdef ENABLE_XEEN
|
||||
/**
|
||||
* Sets which archive is used by default
|
||||
*/
|
||||
static void setCurrentArchive(int ccMode);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Synchronizes a boolean array as a bitfield set
|
||||
*/
|
||||
static void syncBitFlags(Common::Serializer &s, bool *startP, bool *endP);
|
||||
public:
|
||||
File() : Common::File() {
|
||||
}
|
||||
File(const Common::Path &filename);
|
||||
File(const Common::Path &filename, Common::Archive &archive);
|
||||
#ifdef ENABLE_XEEN
|
||||
File(const Common::Path &filename, int ccMode);
|
||||
#endif
|
||||
~File() override {}
|
||||
|
||||
/**
|
||||
* Opens the given file, throwing an error if it can't be opened
|
||||
*/
|
||||
bool open(const Common::Path &filename) override;
|
||||
|
||||
/**
|
||||
* Opens the given file, throwing an error if it can't be opened
|
||||
*/
|
||||
bool open(const Common::Path &filename, Common::Archive &archive) override;
|
||||
#ifdef ENABLE_XEEN
|
||||
/**
|
||||
* Opens the given file, throwing an error if it can't be opened
|
||||
*/
|
||||
virtual bool open(const Common::Path &filename, int ccMode);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Opens the given file
|
||||
*/
|
||||
bool open(const Common::FSNode &node) override {
|
||||
return Common::File::open(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given file
|
||||
*/
|
||||
bool open(SeekableReadStream *stream, const Common::String &name) override {
|
||||
return Common::File::open(stream, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads in a null terminated string
|
||||
*/
|
||||
Common::String readString();
|
||||
|
||||
/**
|
||||
* Checks if a given file exists
|
||||
*
|
||||
* @param filename the file to check for
|
||||
* @return true if the file exists, false otherwise
|
||||
*/
|
||||
static bool exists(const Common::Path &filename);
|
||||
|
||||
#ifdef ENABLE_XEEN
|
||||
/**
|
||||
* Checks if a given file exists
|
||||
*
|
||||
* @param filename the file to check for
|
||||
* @param ccMode Archive to use
|
||||
* @return true if the file exists, false otherwise
|
||||
*/
|
||||
static bool exists(const Common::Path &filename, int ccMode);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Checks if a given file exists
|
||||
*
|
||||
* @param filename the file to check for
|
||||
* @param archive Archive to use
|
||||
* @return true if the file exists, false otherwise
|
||||
*/
|
||||
static bool exists(const Common::Path &filename, Common::Archive &archive);
|
||||
};
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
|
||||
#endif
|
||||
360
engines/mm/shared/xeen/sound.cpp
Normal file
360
engines/mm/shared/xeen/sound.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
/* 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 "audio/decoders/raw.h"
|
||||
#include "audio/decoders/voc.h"
|
||||
#include "backends/audiocd/audiocd.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "mm/shared/xeen/sound.h"
|
||||
#include "mm/shared/xeen/sound_driver_adlib.h"
|
||||
#include "mm/shared/xeen/sound_driver_mt32.h"
|
||||
#include "mm/xeen/xeen.h"
|
||||
#include "mm/mm.h"
|
||||
#ifdef ENABLE_MM1
|
||||
#include "mm/mm1/mm1.h"
|
||||
#endif
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
Sound::Sound(Audio::Mixer *mixer) : _mixer(mixer), _fxOn(true), _musicOn(true), _subtitles(false),
|
||||
_songData(nullptr), _SoundDriver(nullptr), _effectsData(nullptr), _musicSide(0), _musicPercent(100),
|
||||
_musicVolume(0), _sfxVolume(0) {
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32);
|
||||
musicType = MidiDriver::getMusicType(dev);
|
||||
|
||||
switch (musicType) {
|
||||
case MT_MT32:
|
||||
_SoundDriver = new SoundDriverMT32();
|
||||
debugC(1, "Selected mt32 sound driver\n");
|
||||
break;
|
||||
case MT_ADLIB:
|
||||
default:
|
||||
_SoundDriver = new SoundDriverAdlib();
|
||||
debugC(1, "Selected adlib sound driver\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (g_engine->getGameID() != GType_MightAndMagic1
|
||||
#ifdef ENABLE_MM1
|
||||
|| static_cast<MM::MM1::MM1Engine *>(g_engine)->isEnhanced()
|
||||
#endif
|
||||
)
|
||||
// Force load effects early so custom instruments for mt32 are loaded before sound is played.
|
||||
loadEffectsData();
|
||||
|
||||
assert(_SoundDriver);
|
||||
|
||||
if (g_engine->getIsCD())
|
||||
g_system->getAudioCDManager()->open();
|
||||
}
|
||||
|
||||
Sound::~Sound() {
|
||||
stopAllAudio();
|
||||
if (g_engine->getIsCD())
|
||||
g_system->getAudioCDManager()->close();
|
||||
|
||||
delete _SoundDriver;
|
||||
delete[] _effectsData;
|
||||
delete[] _songData;
|
||||
}
|
||||
|
||||
void Sound::playSound(Common::SeekableReadStream &s, int unused) {
|
||||
stopSound();
|
||||
if (!_fxOn)
|
||||
return;
|
||||
|
||||
s.seek(0);
|
||||
Common::SeekableReadStream *srcStream = s.readStream(s.size());
|
||||
Audio::SeekableAudioStream *stream = Audio::makeVOCStream(srcStream,
|
||||
Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
|
||||
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, stream);
|
||||
}
|
||||
|
||||
void Sound::playSound(const Common::Path &name, int unused) {
|
||||
File f;
|
||||
if (!f.open(name))
|
||||
error("Could not open sound - %s", name.toString().c_str());
|
||||
|
||||
playSound(f);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_XEEN
|
||||
void Sound::playSound(const Common::Path &name, int ccNum, int unused) {
|
||||
File f;
|
||||
if (!f.open(name, ccNum))
|
||||
error("Could not open sound - %s", name.toString().c_str());
|
||||
|
||||
playSound(f);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_XEEN
|
||||
void Sound::playVoice(const Common::Path &name, int ccMode) {
|
||||
#else
|
||||
void Sound::playVoice(const Common::Path &name) {
|
||||
#endif
|
||||
stopSound();
|
||||
if (!_fxOn)
|
||||
return;
|
||||
File f;
|
||||
#ifdef ENABLE_XEEN
|
||||
bool result = (ccMode == -1) ? f.open(name) : f.open(name, ccMode);
|
||||
#else
|
||||
bool result = f.open(name);
|
||||
#endif
|
||||
|
||||
if (!result)
|
||||
error("Could not open sound - %s", name.toString().c_str());
|
||||
Common::SeekableReadStream *srcStream = f.readStream(f.size());
|
||||
Audio::SeekableAudioStream *stream = Audio::makeVOCStream(srcStream,
|
||||
Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
|
||||
_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandle, stream);
|
||||
}
|
||||
|
||||
void Sound::stopSound() {
|
||||
_mixer->stopHandle(_soundHandle);
|
||||
}
|
||||
|
||||
bool Sound::isSoundPlaying() const {
|
||||
return _mixer->isSoundHandleActive(_soundHandle);
|
||||
}
|
||||
|
||||
void Sound::stopAllAudio() {
|
||||
stopSong();
|
||||
stopFX(true);
|
||||
stopSound();
|
||||
setMusicPercent(100);
|
||||
}
|
||||
|
||||
void Sound::setFxOn(bool isOn) {
|
||||
ConfMan.setBool("sfx_mute", !isOn);
|
||||
if (isOn)
|
||||
ConfMan.setBool("mute", false);
|
||||
ConfMan.flushToDisk();
|
||||
|
||||
g_engine->syncSoundSettings();
|
||||
}
|
||||
|
||||
void Sound::loadEffectsData() {
|
||||
// Stop any prior FX
|
||||
stopFX();
|
||||
|
||||
// Skip if the sound driver hasn't been loaded, or effects have already been loaded
|
||||
if (!_SoundDriver || _effectsData)
|
||||
return;
|
||||
|
||||
if (musicType == MT_ADLIB) {
|
||||
// Load in an entire driver so we have quick access to the effects data that's hardcoded within it
|
||||
|
||||
const char *name = "blastmus";
|
||||
File file(name);
|
||||
size_t size = file.size();
|
||||
byte *effectsData = new byte[size];
|
||||
|
||||
if (file.read(effectsData, size) != size) {
|
||||
delete[] effectsData;
|
||||
error("Failed to read %zu bytes from '%s'", size, name);
|
||||
}
|
||||
|
||||
_effectsData = effectsData;
|
||||
|
||||
// Locate the playFX routine
|
||||
const byte *fx = effectsData + READ_LE_UINT16(effectsData + 10) + 12;
|
||||
assert(READ_BE_UINT16(fx + 28) == 0x81FB);
|
||||
uint numEffects = READ_LE_UINT16(fx + 30);
|
||||
|
||||
assert(READ_BE_UINT16(fx + 36) == 0x8B87);
|
||||
const byte *table = effectsData + READ_LE_UINT16(fx + 38);
|
||||
|
||||
// Extract the effects offsets
|
||||
_effectsOffsets.resize(numEffects);
|
||||
for (uint idx = 0; idx < numEffects; ++idx)
|
||||
_effectsOffsets[idx] = READ_LE_UINT16(&table[idx * 2]);
|
||||
} else if (musicType == MT_MT32) {
|
||||
// Load in an entire driver so we have quick access to the effects data that's hardcoded within it
|
||||
const char *name = "rolmus";
|
||||
File file(name);
|
||||
size_t size = file.size();
|
||||
byte *effectsData = new byte[size];
|
||||
|
||||
if (file.read(effectsData, size) != size) {
|
||||
delete[] effectsData;
|
||||
error("Failed to read %zu bytes from '%s'", size, name);
|
||||
}
|
||||
|
||||
_effectsData = effectsData;
|
||||
// Locate the playFX routine
|
||||
const byte *fx = effectsData + READ_LE_UINT16(effectsData + 10) + 12;
|
||||
assert(READ_BE_UINT16(fx + 36) == 0x81FB);
|
||||
// TODO: Investigate, additional 10 effects seem to exist in the table beyond the base 180. Not unique to rolmus as at least admus, blastmus and canmus have them too.
|
||||
uint numEffects = READ_LE_UINT16(fx + 38);
|
||||
|
||||
assert(READ_BE_UINT16(fx + 80) == 0x8B87);
|
||||
const byte *table = effectsData + READ_LE_UINT16(fx + 82);
|
||||
|
||||
// Extract the effects offsets
|
||||
_effectsOffsets.resize(numEffects);
|
||||
for (uint idx = 0; idx < numEffects; ++idx)
|
||||
_effectsOffsets[idx] = READ_LE_UINT16(&table[idx * 2]);
|
||||
|
||||
// rolmus in intro.cc
|
||||
if (effectsData[1] == 0xBD) {
|
||||
_patchesOffsetsMT32 = {0x0A86, 0x0ABC, 0x10A3, 0x0BC1, 0x10AF, 0x0CBB, 0x10BB, 0x0DB5, 0x10C7, 0x0EAF, 0x10D3, 0x0FA9, 0x10DF};
|
||||
debugC(3, "intro rolmus");
|
||||
// rolmus in xeen.cc
|
||||
} else if (effectsData[1] == 0xB9) {
|
||||
_patchesOffsetsMT32 = {0x09A7, 0x09DD, 0x0FC4, 0x0AE2, 0x0FD0, 0x0BDC, 0x0FDC, 0x0CD6, 0x0FE8, 0x0DD0, 0x0FF4, 0x0ECA, 0x1000};
|
||||
debugC(3, "xeen rolmus");
|
||||
}
|
||||
|
||||
assert(_patchesOffsetsMT32.size() == 13);
|
||||
|
||||
for (uint idx = 0; idx < 13; idx++) {
|
||||
const byte *ptr = &effectsData[_patchesOffsetsMT32[idx]];
|
||||
|
||||
_SoundDriver->sysExMessage(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::playFX(uint effectId) {
|
||||
stopFX();
|
||||
if (!_fxOn)
|
||||
return;
|
||||
loadEffectsData();
|
||||
|
||||
if (_SoundDriver && effectId < _effectsOffsets.size()) {
|
||||
const byte *dataP = &_effectsData[_effectsOffsets[effectId]];
|
||||
_SoundDriver->playFX(effectId, dataP);
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::stopFX(bool force) {
|
||||
if (_SoundDriver)
|
||||
_SoundDriver->stopFX(force);
|
||||
}
|
||||
|
||||
int Sound::songCommand(uint commandId, byte musicVolume, byte sfxVolume) {
|
||||
int result = 0;
|
||||
|
||||
if (_SoundDriver)
|
||||
result = _SoundDriver->songCommand(commandId, musicVolume, sfxVolume);
|
||||
|
||||
if (commandId == STOP_SONG) {
|
||||
delete[] _songData;
|
||||
_songData = nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Sound::playSong(Common::SeekableReadStream &stream) {
|
||||
stopSong();
|
||||
if (!_musicOn)
|
||||
return;
|
||||
|
||||
if (!stream.seek(0))
|
||||
error("Failed to seek to 0 for song data");
|
||||
|
||||
size_t size = stream.size();
|
||||
byte *songData = new byte[size];
|
||||
|
||||
if (stream.read(songData, size) != size) {
|
||||
delete[] songData;
|
||||
error("Failed to read %zu bytes of song data", size);
|
||||
}
|
||||
|
||||
assert(!_songData);
|
||||
_songData = songData;
|
||||
|
||||
if (_SoundDriver)
|
||||
_SoundDriver->playSong(_songData);
|
||||
}
|
||||
|
||||
void Sound::playSong(const Common::Path &name, int param) {
|
||||
if (isMusicPlaying() && name == _currentMusic)
|
||||
return;
|
||||
_currentMusic = name;
|
||||
|
||||
Common::File mf;
|
||||
if (mf.open(name)) {
|
||||
playSong(mf);
|
||||
#ifdef ENABLE_XEEN
|
||||
} else if (dynamic_cast<MM::Xeen::XeenEngine *>(g_engine)) {
|
||||
File f(name, _musicSide);
|
||||
playSong(f);
|
||||
#endif
|
||||
} else {
|
||||
File f(name);
|
||||
playSong(f);
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::setMusicOn(bool isOn) {
|
||||
ConfMan.setBool("music_mute", !isOn);
|
||||
if (isOn)
|
||||
ConfMan.setBool("mute", false);
|
||||
ConfMan.flushToDisk();
|
||||
|
||||
g_engine->syncSoundSettings();
|
||||
}
|
||||
|
||||
bool Sound::isMusicPlaying() const {
|
||||
return _SoundDriver && _SoundDriver->isPlaying();
|
||||
}
|
||||
|
||||
void Sound::setMusicPercent(byte percent) {
|
||||
assert(percent <= 100);
|
||||
_musicPercent = percent;
|
||||
|
||||
updateVolume();
|
||||
}
|
||||
|
||||
void Sound::updateSoundSettings() {
|
||||
_fxOn = !ConfMan.getBool("sfx_mute");
|
||||
if (!_fxOn)
|
||||
stopFX();
|
||||
|
||||
_musicOn = !ConfMan.getBool("music_mute");
|
||||
if (!_musicOn)
|
||||
stopSong();
|
||||
else if (!_currentMusic.empty())
|
||||
playSong(_currentMusic);
|
||||
|
||||
_subtitles = ConfMan.hasKey("subtitles") ? ConfMan.getBool("subtitles") : true;
|
||||
if (ConfMan.getBool("mute")) {
|
||||
_musicVolume = 0;
|
||||
_sfxVolume = 0;
|
||||
} else {
|
||||
_musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, 255);
|
||||
_sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, 255);
|
||||
}
|
||||
updateVolume();
|
||||
}
|
||||
|
||||
void Sound::updateVolume() {
|
||||
songCommand(SET_VOLUME, _musicPercent * _musicVolume / 100, _sfxVolume);
|
||||
}
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
175
engines/mm/shared/xeen/sound.h
Normal file
175
engines/mm/shared/xeen/sound.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_SOUND_H
|
||||
#define MM_SHARED_XEEN_SOUND_H
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/audiostream.h"
|
||||
#include "mm/shared/xeen/file.h"
|
||||
#include "mm/shared/xeen/sound_driver.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
class Sound {
|
||||
private:
|
||||
SoundDriver *_SoundDriver;
|
||||
const byte *_effectsData;
|
||||
Common::Array<uint16> _effectsOffsets;
|
||||
Common::Array<uint16> _patchesOffsetsMT32;
|
||||
const byte *_songData;
|
||||
Audio::Mixer *_mixer;
|
||||
Audio::SoundHandle _soundHandle;
|
||||
byte _musicPercent;
|
||||
int _musicVolume, _sfxVolume;
|
||||
private:
|
||||
/**
|
||||
* Loads effects data that was embedded in the music driver
|
||||
*/
|
||||
void loadEffectsData();
|
||||
|
||||
/**
|
||||
* Updates any playing music
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* Updates the music and sound effects playing volume
|
||||
*/
|
||||
void updateVolume();
|
||||
public:
|
||||
bool _fxOn;
|
||||
bool _musicOn;
|
||||
Common::Path _currentMusic;
|
||||
int _musicSide;
|
||||
bool _subtitles;
|
||||
MusicType musicType;
|
||||
public:
|
||||
Sound(Audio::Mixer *mixer);
|
||||
virtual ~Sound();
|
||||
|
||||
/**
|
||||
* Starts an effect playing
|
||||
*/
|
||||
void playFX(uint effectId);
|
||||
|
||||
/**
|
||||
* Stops any currently playing FX
|
||||
*/
|
||||
void stopFX(bool force = false);
|
||||
|
||||
/**
|
||||
* Executes special music command
|
||||
*/
|
||||
int songCommand(uint commandId, byte musicVolume = 0, byte sfxVolume = 0);
|
||||
|
||||
/**
|
||||
* Stops any currently playing music
|
||||
*/
|
||||
void stopSong() {
|
||||
songCommand(STOP_SONG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the in-game music volume percent. This is separate from the ScummVM volume
|
||||
*/
|
||||
void setMusicPercent(byte percent);
|
||||
|
||||
/**
|
||||
* Plays a song
|
||||
*/
|
||||
void playSong(Common::SeekableReadStream &stream);
|
||||
|
||||
/**
|
||||
* Plays a song
|
||||
*/
|
||||
void playSong(const Common::Path &name, int param = 0);
|
||||
|
||||
/**
|
||||
* Returns true if music is playing
|
||||
*/
|
||||
bool isMusicPlaying() const;
|
||||
|
||||
/**
|
||||
* Sets whether music is on
|
||||
*/
|
||||
void setMusicOn(bool isOn);
|
||||
|
||||
/**
|
||||
* Sets whether sound effects is on
|
||||
*/
|
||||
void setFxOn(bool isOn);
|
||||
|
||||
/**
|
||||
* Called to reload sound settings
|
||||
*/
|
||||
void updateSoundSettings();
|
||||
|
||||
/**
|
||||
* Stops all playing music, FX, and sound samples
|
||||
*/
|
||||
void stopAllAudio();
|
||||
|
||||
/**
|
||||
* Play a given sound
|
||||
*/
|
||||
void playSound(Common::SeekableReadStream &s, int unused = 0);
|
||||
|
||||
/**
|
||||
* Play a given sound
|
||||
*/
|
||||
void playSound(const Common::Path &name, int unused = 0);
|
||||
#ifdef ENABLE_XEEN
|
||||
/**
|
||||
* Play a given sound
|
||||
*/
|
||||
void playSound(const Common::Path &name, int ccNum, int unused);
|
||||
#endif
|
||||
/**
|
||||
* Stop playing a sound loaded from a .m file
|
||||
* @remarks In the original, passing 1 to playSound stopped the sound
|
||||
*/
|
||||
void stopSound();
|
||||
|
||||
/**
|
||||
* Returns true if a sound file is currently playing
|
||||
* @remarks In the original, passing 0 to playSound returned play status
|
||||
*/
|
||||
bool isSoundPlaying() const;
|
||||
|
||||
/**
|
||||
* Play a given voice file
|
||||
*/
|
||||
#ifdef ENABLE_XEEN
|
||||
void playVoice(const Common::Path &name, int ccMode = -1);
|
||||
#else
|
||||
void playVoice(const Common::Path &name);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
|
||||
#endif
|
||||
231
engines/mm/shared/xeen/sound_driver.cpp
Normal file
231
engines/mm/shared/xeen/sound_driver.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
/* 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/debug.h"
|
||||
#include "common/md5.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "mm/shared/xeen/sound_driver.h"
|
||||
#include "mm/shared/xeen/file.h"
|
||||
#include "mm/mm.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
SoundDriver::SoundDriver() : _frameCtr(0) {
|
||||
_channels.resize(CHANNEL_COUNT);
|
||||
_streams[stMUSIC] = Stream(MUSIC_COMMANDS);
|
||||
_streams[stFX] = Stream(FX_COMMANDS);
|
||||
}
|
||||
|
||||
SoundDriver::~SoundDriver() {
|
||||
}
|
||||
|
||||
SoundDriver::Stream *SoundDriver::tickStream() {
|
||||
for (size_t i = 0; i < stLAST; ++i) {
|
||||
Stream &stream = _streams[i];
|
||||
if (stream._playing && (stream._countdownTimer == 0 || --stream._countdownTimer == 0))
|
||||
return &stream;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SoundDriver::execute() {
|
||||
Stream *stream = tickStream();
|
||||
if (!stream) {
|
||||
pausePostProcess();
|
||||
return;
|
||||
}
|
||||
|
||||
++_frameCtr;
|
||||
debugC(3, kDebugSound, "\nSoundDriver frame - #%x", _frameCtr);
|
||||
|
||||
// Main loop
|
||||
bool breakFlag = false;
|
||||
while (!breakFlag) {
|
||||
if (!stream->_dataPtr || !stream->_startPtr)
|
||||
break;
|
||||
debugCN(3, kDebugSound, "MUSCODE %.4x - %.2x ", (uint)(stream->_dataPtr - stream->_startPtr), (uint)*stream->_dataPtr);
|
||||
byte nextByte = *stream->_dataPtr++;
|
||||
int cmd = (nextByte >> 4) & 15;
|
||||
int param = (nextByte & 15);
|
||||
|
||||
CommandFn fn = stream->_commands[cmd];
|
||||
breakFlag = (this->*fn)(stream->_dataPtr, param);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SoundDriver::musCallSubroutine(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "musCallSubroutine");
|
||||
if (_musSubroutines.size() < 16) {
|
||||
const byte *returnP = srcP + 2;
|
||||
srcP = _streams[stMUSIC]._startPtr + READ_LE_UINT16(srcP);
|
||||
|
||||
_musSubroutines.push(Subroutine(returnP, srcP));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriver::musSetCountdown(const byte *&srcP, byte param) {
|
||||
// Set the countdown timer
|
||||
if (!param)
|
||||
param = *srcP++;
|
||||
_streams[stMUSIC]._countdownTimer = param;
|
||||
_streams[stMUSIC]._dataPtr = srcP;
|
||||
debugC(3, kDebugSound, "musSetCountdown %d", param);
|
||||
|
||||
// Do paused handling and break out of processing loop
|
||||
pausePostProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SoundDriver::cmdNoOperation(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "cmdNoOperation");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriver::musSkipWord(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "musSkipWord");
|
||||
srcP += 2;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriver::musEndSubroutine(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "musEndSubroutine %d", param);
|
||||
|
||||
if (param != 15) {
|
||||
// Music has ended, so flag it stopped
|
||||
_streams[stMUSIC]._playing = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returning from subroutine, or looping back to start of music
|
||||
srcP = _musSubroutines.empty() ? _streams[stMUSIC]._startPtr : _musSubroutines.pop()._returnP;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriver::fxCallSubroutine(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxCallSubroutine");
|
||||
|
||||
if (_fxSubroutines.size() < 16) {
|
||||
const byte *startP = srcP + 2;
|
||||
srcP = _streams[stMUSIC]._startPtr + READ_LE_UINT16(srcP);
|
||||
|
||||
_fxSubroutines.push(Subroutine(startP, srcP));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriver::fxSetCountdown(const byte *&srcP, byte param) {
|
||||
// Set the countdown timer
|
||||
if (!param)
|
||||
param = *srcP++;
|
||||
_streams[stFX]._countdownTimer = param;
|
||||
_streams[stFX]._dataPtr = srcP;
|
||||
debugC(3, kDebugSound, "fxSetCountdown %d", param);
|
||||
|
||||
// Do paused handling and break out of processing loop
|
||||
pausePostProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SoundDriver::fxEndSubroutine(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxEndSubroutine %d", param);
|
||||
|
||||
if (param != 15) {
|
||||
// FX has ended, so flag it stopped
|
||||
_streams[stFX]._playing = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
srcP = _fxSubroutines.empty() ? _streams[stFX]._startPtr : _fxSubroutines.pop()._returnP;
|
||||
return false;
|
||||
}
|
||||
|
||||
void SoundDriver::playFX(uint effectId, const byte *data) {
|
||||
if (!_streams[stFX]._playing || effectId < 7 || effectId >= 11) {
|
||||
_streams[stFX]._dataPtr = _streams[stFX]._startPtr = data;
|
||||
_streams[stFX]._countdownTimer = 0;
|
||||
_channels[7]._changeFrequency = _channels[8]._changeFrequency = false;
|
||||
resetFX();
|
||||
_streams[stFX]._playing = true;
|
||||
}
|
||||
|
||||
debugC(1, kDebugSound, "Starting FX %d", effectId);
|
||||
}
|
||||
|
||||
void SoundDriver::stopFX(bool force) {
|
||||
if (force || !_streams[stFX]._playing) {
|
||||
resetFX();
|
||||
_streams[stFX]._playing = false;
|
||||
_streams[stFX]._startPtr = _streams[stFX]._dataPtr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundDriver::playSong(const byte *data) {
|
||||
_streams[stMUSIC]._dataPtr = _streams[stMUSIC]._startPtr = data;
|
||||
_musSubroutines.clear();
|
||||
_streams[stMUSIC]._countdownTimer = 0;
|
||||
_streams[stMUSIC]._playing = true;
|
||||
debugC(1, kDebugSound, "Starting song");
|
||||
}
|
||||
|
||||
int SoundDriver::songCommand(uint commandId, byte musicVolume, byte sfxVolume) {
|
||||
if (commandId == STOP_SONG) {
|
||||
_streams[stMUSIC]._playing = false;
|
||||
} else if (commandId == RESTART_SONG) {
|
||||
_streams[stMUSIC]._playing = true;
|
||||
_streams[stMUSIC]._dataPtr = nullptr;
|
||||
_musSubroutines.clear();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const CommandFn SoundDriver::MUSIC_COMMANDS[16] = {
|
||||
&SoundDriver::musCallSubroutine, &SoundDriver::musSetCountdown,
|
||||
&SoundDriver::musSetInstrument, &SoundDriver::cmdNoOperation,
|
||||
&SoundDriver::musSetPitchWheel, &SoundDriver::musSkipWord,
|
||||
&SoundDriver::musSetPanning, &SoundDriver::cmdNoOperation,
|
||||
&SoundDriver::musFade, &SoundDriver::musStartNote,
|
||||
&SoundDriver::musSetVolume, &SoundDriver::musInjectMidi,
|
||||
&SoundDriver::musPlayInstrument, &SoundDriver::cmdFreezeFrequency,
|
||||
&SoundDriver::cmdChangeFrequency, &SoundDriver::musEndSubroutine
|
||||
};
|
||||
|
||||
const CommandFn SoundDriver::FX_COMMANDS[16] = {
|
||||
&SoundDriver::fxCallSubroutine, &SoundDriver::fxSetCountdown,
|
||||
&SoundDriver::fxSetInstrument, &SoundDriver::fxSetVolume,
|
||||
&SoundDriver::fxMidiReset, &SoundDriver::fxMidiDword,
|
||||
&SoundDriver::fxSetPanning, &SoundDriver::fxChannelOff,
|
||||
&SoundDriver::fxFade, &SoundDriver::fxStartNote,
|
||||
&SoundDriver::cmdNoOperation, &SoundDriver::fxInjectMidi,
|
||||
&SoundDriver::fxPlayInstrument, &SoundDriver::cmdFreezeFrequency,
|
||||
&SoundDriver::cmdChangeFrequency, &SoundDriver::fxEndSubroutine
|
||||
};
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
214
engines/mm/shared/xeen/sound_driver.h
Normal file
214
engines/mm/shared/xeen/sound_driver.h
Normal file
@@ -0,0 +1,214 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_SOUND_DRIVER_H
|
||||
#define MM_SHARED_XEEN_SOUND_DRIVER_H
|
||||
|
||||
#include "audio/fmopl.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/array.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/queue.h"
|
||||
#include "common/stack.h"
|
||||
#include "mm/shared/xeen/file.h"
|
||||
|
||||
#define CHANNEL_COUNT 9
|
||||
// interrupt is every ~13.736ms, which is ~72.8 times a second
|
||||
#define CALLBACKS_PER_SECOND 72.8f
|
||||
|
||||
namespace OPL {
|
||||
class OPL;
|
||||
}
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
enum MusicCommand {
|
||||
STOP_SONG = 0, RESTART_SONG = 1, SET_VOLUME = 0x100, GET_STATUS = 0xFFE0
|
||||
};
|
||||
|
||||
class SoundDriver;
|
||||
|
||||
typedef bool (SoundDriver:: *CommandFn)(const byte *&srcP, byte param);
|
||||
|
||||
/**
|
||||
* Base class for sound drivers
|
||||
*/
|
||||
class SoundDriver {
|
||||
protected:
|
||||
struct Subroutine {
|
||||
const byte *_returnP;
|
||||
const byte *_jumpP;
|
||||
Subroutine() : _returnP(nullptr), _jumpP(nullptr) {
|
||||
}
|
||||
Subroutine(const byte *returnP, const byte *endP) :
|
||||
_returnP(returnP), _jumpP(endP) {
|
||||
}
|
||||
};
|
||||
struct Channel {
|
||||
bool _changeFrequency;
|
||||
int _freqCtrChange;
|
||||
int _freqChange;
|
||||
int _freqCtr;
|
||||
byte _volume;
|
||||
byte _totalLevel;
|
||||
bool _isFx;
|
||||
uint _frequency;
|
||||
Channel() : _changeFrequency(false), _freqCtr(0), _freqCtrChange(0),
|
||||
_freqChange(0), _volume(0), _totalLevel(0), _frequency(0), _isFx(false) {
|
||||
}
|
||||
};
|
||||
enum StreamType {
|
||||
stMUSIC,
|
||||
stFX,
|
||||
|
||||
stLAST
|
||||
};
|
||||
class Stream {
|
||||
public:
|
||||
Stream() {
|
||||
}
|
||||
Stream(const CommandFn *commands) : _playing(false), _countdownTimer(0), _dataPtr(nullptr), _startPtr(nullptr), _commands(commands) {
|
||||
}
|
||||
|
||||
bool _playing;
|
||||
int _countdownTimer;
|
||||
const byte *_dataPtr;
|
||||
const byte *_startPtr;
|
||||
const CommandFn *_commands;
|
||||
};
|
||||
|
||||
private:
|
||||
static const CommandFn FX_COMMANDS[16];
|
||||
static const CommandFn MUSIC_COMMANDS[16];
|
||||
private:
|
||||
Common::Stack<Subroutine> _musSubroutines, _fxSubroutines;
|
||||
uint _frameCtr;
|
||||
private:
|
||||
/**
|
||||
* Executes the next command
|
||||
* @param srcP Command data pointer
|
||||
* @returns If true, execution of commands for the current timer call stops
|
||||
*/
|
||||
bool command(const byte *&srcP);
|
||||
|
||||
Stream *tickStream();
|
||||
protected:
|
||||
Common::Array<Channel> _channels;
|
||||
Stream _streams[stLAST];
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Executes a series of commands until instructed to stop
|
||||
*/
|
||||
void execute();
|
||||
|
||||
// Music commands (with some also used by FX)
|
||||
virtual bool musCallSubroutine(const byte *&srcP, byte param);
|
||||
virtual bool musSetCountdown(const byte *&srcP, byte param);
|
||||
virtual bool musSetInstrument(const byte *&srcP, byte param) = 0;
|
||||
virtual bool cmdNoOperation(const byte *&srcP, byte param);
|
||||
virtual bool musSetPitchWheel(const byte *&srcP, byte param) = 0;
|
||||
virtual bool musSkipWord(const byte *&srcP, byte param);
|
||||
virtual bool musSetPanning(const byte *&srcP, byte param) = 0;
|
||||
virtual bool musFade(const byte *&srcP, byte param) = 0;
|
||||
virtual bool musStartNote(const byte *&srcP, byte param) = 0;
|
||||
virtual bool musSetVolume(const byte *&srcP, byte param) = 0;
|
||||
virtual bool musInjectMidi(const byte *&srcP, byte param) = 0;
|
||||
virtual bool musPlayInstrument(const byte *&srcP, byte param) = 0;
|
||||
virtual bool cmdFreezeFrequency(const byte *&srcP, byte param) = 0;
|
||||
virtual bool cmdChangeFrequency(const byte *&srcP, byte param) = 0;
|
||||
virtual bool musEndSubroutine(const byte *&srcP, byte param);
|
||||
|
||||
// FX commands
|
||||
virtual bool fxCallSubroutine(const byte *&srcP, byte param);
|
||||
virtual bool fxSetCountdown(const byte *&srcP, byte param);
|
||||
virtual bool fxSetInstrument(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxSetVolume(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxMidiReset(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxMidiDword(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxSetPanning(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxChannelOff(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxFade(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxStartNote(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxInjectMidi(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxPlayInstrument(const byte *&srcP, byte param) = 0;
|
||||
virtual bool fxEndSubroutine(const byte *&srcP, byte param);
|
||||
|
||||
/**
|
||||
* Post-processing done when a pause countdown starts or is in progress
|
||||
*/
|
||||
virtual void pausePostProcess() = 0;
|
||||
|
||||
/**
|
||||
* Does a reset of any sound effect
|
||||
*/
|
||||
virtual void resetFX() = 0;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SoundDriver();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
virtual ~SoundDriver();
|
||||
|
||||
/**
|
||||
* Starts a special effect playing
|
||||
*/
|
||||
virtual void playFX(uint effectId, const byte *data);
|
||||
|
||||
/**
|
||||
* Stop any playing FX
|
||||
*/
|
||||
void stopFX(bool force = false);
|
||||
|
||||
/**
|
||||
* Plays a song
|
||||
*/
|
||||
virtual void playSong(const byte *data);
|
||||
|
||||
/**
|
||||
* Executes special music command
|
||||
*/
|
||||
virtual int songCommand(uint commandId, byte musicVolume = 0, byte sfxVolume = 0);
|
||||
|
||||
/**
|
||||
* Returns whether music is currently playing
|
||||
*/
|
||||
bool isPlaying() const {
|
||||
return _streams[stMUSIC]._playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends SysEx message
|
||||
*/
|
||||
virtual void sysExMessage(const byte *&data) = 0;
|
||||
};
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
|
||||
#endif
|
||||
452
engines/mm/shared/xeen/sound_driver_adlib.cpp
Normal file
452
engines/mm/shared/xeen/sound_driver_adlib.cpp
Normal file
@@ -0,0 +1,452 @@
|
||||
/* 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/debug.h"
|
||||
#include "mm/shared/xeen/sound_driver_adlib.h"
|
||||
#include "mm/mm.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
const byte SoundDriverAdlib::OPERATOR1_INDEXES[CHANNEL_COUNT] = {
|
||||
0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12
|
||||
};
|
||||
|
||||
const byte SoundDriverAdlib::OPERATOR2_INDEXES[CHANNEL_COUNT] = {
|
||||
3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x15
|
||||
};
|
||||
|
||||
const uint SoundDriverAdlib::WAVEFORMS[24] = {
|
||||
0, 347, 388, 436, 462, 519, 582, 646,
|
||||
0, 362, 406, 455, 484, 542, 607, 680,
|
||||
0, 327, 367, 412, 436, 489, 549, 618
|
||||
};
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
SoundDriverAdlib::SoundDriverAdlib() : _field180(0), _field181(0), _field182(0),
|
||||
_musicVolume(0), _sfxVolume(0) {
|
||||
Common::fill(&_musInstrumentPtrs[0], &_musInstrumentPtrs[16], (const byte *)nullptr);
|
||||
Common::fill(&_fxInstrumentPtrs[0], &_fxInstrumentPtrs[16], (const byte *)nullptr);
|
||||
|
||||
_opl = OPL::Config::create();
|
||||
_opl->init();
|
||||
_opl->start(new Common::Functor0Mem<void, SoundDriverAdlib>(this, &SoundDriverAdlib::onTimer), CALLBACKS_PER_SECOND);
|
||||
initialize();
|
||||
}
|
||||
|
||||
SoundDriverAdlib::~SoundDriverAdlib() {
|
||||
_opl->stop();
|
||||
delete _opl;
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::onTimer() {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
execute();
|
||||
flush();
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::initialize() {
|
||||
write(1, 0x20);
|
||||
write(8, 0);
|
||||
write(0xBD, 0);
|
||||
|
||||
resetFrequencies();
|
||||
SoundDriverAdlib::resetFX();
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::playFX(uint effectId, const byte *data) {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
SoundDriver::playFX(effectId, data);
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::playSong(const byte *data) {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
SoundDriver::playSong(data);
|
||||
_field180 = 0;
|
||||
resetFrequencies();
|
||||
}
|
||||
|
||||
int SoundDriverAdlib::songCommand(uint commandId, byte musicVolume, byte sfxVolume) {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
SoundDriver::songCommand(commandId, musicVolume, sfxVolume);
|
||||
|
||||
if (commandId == STOP_SONG) {
|
||||
_field180 = 0;
|
||||
resetFrequencies();
|
||||
} else if (commandId == RESTART_SONG) {
|
||||
_field180 = 0;
|
||||
_streams[stMUSIC]._playing = true;
|
||||
} else if (commandId < 0x100) {
|
||||
if (_streams[stMUSIC]._playing) {
|
||||
_field180 = commandId;
|
||||
_field182 = 63;
|
||||
}
|
||||
} else if (commandId == SET_VOLUME) {
|
||||
_musicVolume = musicVolume;
|
||||
_sfxVolume = sfxVolume;
|
||||
} else if (commandId == GET_STATUS) {
|
||||
return _field180;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::write(int reg, int val) {
|
||||
_queue.push(RegisterValue(reg, val));
|
||||
debugC(9, kDebugSound, "%.2x %.2x", reg, val);
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::flush() {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
|
||||
while (!_queue.empty()) {
|
||||
RegisterValue v = _queue.pop();
|
||||
_opl->writeReg(v._regNum, v._value);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::pausePostProcess() {
|
||||
if (_field180 && ((_field181 += _field180) < 0)) {
|
||||
if (--_field182 < 0) {
|
||||
_streams[stMUSIC]._playing = false;
|
||||
_field180 = 0;
|
||||
resetFrequencies();
|
||||
} else {
|
||||
for (int channelNum = 6; channelNum >= 0; --channelNum) {
|
||||
if (_channels[channelNum]._volume < 63)
|
||||
setOutputLevel(channelNum, ++_channels[channelNum]._volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int channelNum = 8; channelNum > 6; --channelNum) {
|
||||
Channel &chan = _channels[channelNum];
|
||||
if (!chan._changeFrequency || (chan._freqCtr += chan._freqCtrChange) >= 0)
|
||||
continue;
|
||||
|
||||
uint freq = chan._frequency & 0x3FF;
|
||||
uint val = chan._frequency >> 8;
|
||||
byte val1 = val & 0x20;
|
||||
byte val2 = val & 0x1C;
|
||||
|
||||
freq += chan._freqChange;
|
||||
if (chan._freqChange < 0) {
|
||||
if (freq <= 388) {
|
||||
freq <<= 1;
|
||||
if (!(freq & 0x3FF))
|
||||
--freq;
|
||||
}
|
||||
|
||||
val2 = (val2 - 4) & 0x1C;
|
||||
} else {
|
||||
if (freq >= 734) {
|
||||
freq >>= 1;
|
||||
if (!(freq & 0x3FF))
|
||||
++freq;
|
||||
}
|
||||
|
||||
val2 = (val2 + 4) & 0x1C;
|
||||
}
|
||||
|
||||
freq &= 0x3FF;
|
||||
freq |= (val2 << 8);
|
||||
freq |= val1;
|
||||
chan._frequency = freq;
|
||||
setFrequency(channelNum, freq);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::resetFX() {
|
||||
_channels[7]._frequency = 0;
|
||||
setFrequency(7, 0);
|
||||
_channels[7]._volume = 63;
|
||||
setOutputLevel(7, 63);
|
||||
|
||||
_channels[8]._frequency = 0;
|
||||
setFrequency(8, 0);
|
||||
_channels[8]._volume = 63;
|
||||
setOutputLevel(8, 63);
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::resetFrequencies() {
|
||||
for (int opNum = 6; opNum >= 0; --opNum) {
|
||||
_channels[opNum]._frequency = 0;
|
||||
setFrequency(opNum, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::setFrequency(byte operatorNum, uint frequency) {
|
||||
write(0xA0 + operatorNum, frequency & 0xff);
|
||||
write(0xB0 + operatorNum, (frequency >> 8));
|
||||
}
|
||||
|
||||
uint SoundDriverAdlib::calcFrequency(byte note) {
|
||||
return WAVEFORMS[note & 0x1F] + ((note & 0xE0) << 5);
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::setOutputLevel(byte channelNum, uint level) {
|
||||
Channel &c = _channels[channelNum];
|
||||
write(0x40 + OPERATOR2_INDEXES[channelNum], calculateLevel(level, c._isFx) | (c._totalLevel & 0xC0));
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::playInstrument(byte channelNum, const byte *data, bool isFx) {
|
||||
byte op1 = OPERATOR1_INDEXES[channelNum];
|
||||
byte op2 = OPERATOR2_INDEXES[channelNum];
|
||||
int totalLevel;
|
||||
|
||||
debugC(2, kDebugSound, "---START-playInstrument - %d", channelNum);
|
||||
_channels[channelNum]._isFx = isFx;
|
||||
write(0x20 + op1, *data++);
|
||||
|
||||
totalLevel = *data++;
|
||||
write(0x40 + op1, calculateLevel(totalLevel, isFx));
|
||||
|
||||
write(0x60 + op1, *data++);
|
||||
write(0x80 + op1, *data++);
|
||||
write(0xE0 + op1, *data++);
|
||||
write(0x20 + op2, *data++);
|
||||
|
||||
totalLevel = *data++;
|
||||
_channels[channelNum]._totalLevel = totalLevel;
|
||||
|
||||
if (totalLevel > 63) {
|
||||
totalLevel = 63;
|
||||
if (_field180)
|
||||
totalLevel = (totalLevel & 0xC0) | _field182;
|
||||
}
|
||||
|
||||
write(0x40 + op2, calculateLevel(totalLevel, isFx));
|
||||
|
||||
write(0x60 + op2, *data++);
|
||||
write(0x80 + op2, *data++);
|
||||
write(0xE0 + op2, *data++);
|
||||
write(0xC0 + channelNum, *data++);
|
||||
|
||||
debugC(2, kDebugSound, "---END-playInstrument");
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musSetInstrument(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "musSetInstrument %d", param);
|
||||
_musInstrumentPtrs[param] = srcP;
|
||||
srcP += 26;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musSetPitchWheel(const byte *&srcP, byte param) {
|
||||
// Adlib does not support this
|
||||
debugC(3, kDebugSound, "musSetPitchWheel");
|
||||
srcP += 2;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musSetPanning(const byte *&srcP, byte param) {
|
||||
// Adlib does not support this
|
||||
debugC(3, kDebugSound, "musSetPanning");
|
||||
++srcP;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musFade(const byte *&srcP, byte param) {
|
||||
++srcP;
|
||||
if (param < 7)
|
||||
setFrequency(param, _channels[param]._frequency);
|
||||
debugC(3, kDebugSound, "musFade");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musStartNote(const byte *&srcP, byte param) {
|
||||
if (param < 7) {
|
||||
byte note = *srcP++;
|
||||
++srcP; // Second byte is fade, which is unused by Adlib
|
||||
uint freq = calcFrequency(note);
|
||||
debugC(3, kDebugSound, "musStartNote %x -> %x", note, freq);
|
||||
|
||||
setFrequency(param, freq);
|
||||
freq |= 0x2000;
|
||||
_channels[param]._frequency = freq;
|
||||
setFrequency(param, freq);
|
||||
} else {
|
||||
srcP += 2;
|
||||
debugC(3, kDebugSound, "musStartNote skipped");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musSetVolume(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "musSetVolume %d", (int)*srcP);
|
||||
|
||||
if (*srcP++ == 5 && !_field180) {
|
||||
_channels[param]._volume = *srcP;
|
||||
setOutputLevel(param, *srcP);
|
||||
}
|
||||
|
||||
++srcP;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musInjectMidi(const byte *&srcP, byte param) {
|
||||
// Adlib does not support MIDI. So simply keep skipping over bytes
|
||||
// until an 'F7' byte is found that flags the end of the MIDI data
|
||||
debugC(3, kDebugSound, "musInjectMidi");
|
||||
while (*srcP++ != 0xF7)
|
||||
;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::musPlayInstrument(const byte *&srcP, byte param) {
|
||||
byte instrument = *srcP++;
|
||||
debugC(3, kDebugSound, "musPlayInstrument %d, %d", param, instrument);
|
||||
|
||||
if (param < 7)
|
||||
playInstrument(param, _musInstrumentPtrs[instrument], false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxSetInstrument(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxSetInstrument %d", param);
|
||||
_fxInstrumentPtrs[param] = srcP;
|
||||
srcP += 11;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxSetVolume(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxSetVolume %d", (int)*srcP);
|
||||
|
||||
if (!_field180) {
|
||||
_channels[param]._volume = *srcP;
|
||||
setOutputLevel(param, *srcP);
|
||||
}
|
||||
|
||||
++srcP;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxMidiReset(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxMidiReset");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxMidiDword(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxMidiDword");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxSetPanning(const byte *&srcP, byte param) {
|
||||
byte note = *srcP++;
|
||||
debugC(3, kDebugSound, "fxSetPanning - %x", note);
|
||||
|
||||
uint freq = calcFrequency(note);
|
||||
setFrequency(param, freq);
|
||||
_channels[param]._frequency = freq;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxChannelOff(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxChannelOff %d", param);
|
||||
_channels[param]._frequency &= ~0x2000;
|
||||
write(0xB0 + param, _channels[param]._frequency);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxFade(const byte *&srcP, byte param) {
|
||||
uint freq = calcFrequency(*srcP++);
|
||||
debugC(3, kDebugSound, "fxFade %d %x", param, freq);
|
||||
|
||||
_channels[param]._frequency = freq;
|
||||
setFrequency(param, freq);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxStartNote(const byte *&srcP, byte param) {
|
||||
byte note = *srcP++;
|
||||
uint freq = calcFrequency(note);
|
||||
debugC(3, kDebugSound, "fxStartNote %x -> %x", note, freq);
|
||||
|
||||
setFrequency(param, freq);
|
||||
freq |= 0x2000;
|
||||
_channels[param]._frequency = freq;
|
||||
setFrequency(param, freq);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxInjectMidi(const byte *&srcP, byte param) {
|
||||
// Surpringly, unlike the musInjectMidi, this version doesn't have
|
||||
// any logic to skip over following MIDI data. Which must mean the opcode
|
||||
// and/or it's data aren't present in the admus driver file
|
||||
debugC(3, kDebugSound, "fxInjectMidi");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::fxPlayInstrument(const byte *&srcP, byte param) {
|
||||
byte instrument = *srcP++;
|
||||
debugC(3, kDebugSound, "fxPlayInstrument %d, %d", param, instrument);
|
||||
|
||||
playInstrument(param, _fxInstrumentPtrs[instrument], true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
byte SoundDriverAdlib::calculateLevel(byte level, bool isFx) {
|
||||
uint volume = isFx ? _sfxVolume : _musicVolume;
|
||||
uint scaling = level & 0xc0;
|
||||
uint totalLevel = 0x3f - (level & 0x3f);
|
||||
totalLevel = totalLevel * volume / 255;
|
||||
|
||||
return scaling | (0x3f - totalLevel);
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::cmdFreezeFrequency(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "cmdFreezeFrequency %d", param);
|
||||
_channels[param]._changeFrequency = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverAdlib::cmdChangeFrequency(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "cmdChangeFrequency %d", param);
|
||||
|
||||
_channels[param]._freqCtrChange = (int8)*srcP++;
|
||||
_channels[param]._freqCtr = 0xFF;
|
||||
_channels[param]._changeFrequency = true;
|
||||
_channels[param]._freqChange = (int16)READ_BE_UINT16(srcP);
|
||||
srcP += 2;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SoundDriverAdlib::sysExMessage(const byte *&data) {
|
||||
// not used in ad adlib
|
||||
}
|
||||
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
179
engines/mm/shared/xeen/sound_driver_adlib.h
Normal file
179
engines/mm/shared/xeen/sound_driver_adlib.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_SOUND_DRIVER_ADLIB_H
|
||||
#define MM_SHARED_XEEN_SOUND_DRIVER_ADLIB_H
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "mm/shared/xeen/sound_driver.h"
|
||||
|
||||
namespace OPL {
|
||||
class OPL;
|
||||
}
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
class SoundDriverAdlib : public SoundDriver {
|
||||
struct RegisterValue {
|
||||
uint8 _regNum;
|
||||
uint8 _value;
|
||||
|
||||
RegisterValue(int regNum, int value) {
|
||||
_regNum = regNum; _value = value;
|
||||
}
|
||||
};
|
||||
private:
|
||||
static const byte OPERATOR1_INDEXES[CHANNEL_COUNT];
|
||||
static const byte OPERATOR2_INDEXES[CHANNEL_COUNT];
|
||||
static const uint WAVEFORMS[24];
|
||||
private:
|
||||
OPL::OPL *_opl;
|
||||
Common::Queue<RegisterValue> _queue;
|
||||
Common::Mutex _driverMutex;
|
||||
const byte *_musInstrumentPtrs[16];
|
||||
const byte *_fxInstrumentPtrs[16];
|
||||
int _field180;
|
||||
int _field181;
|
||||
int _field182;
|
||||
int _musicVolume, _sfxVolume;
|
||||
private:
|
||||
/**
|
||||
* Initializes the state of the Adlib OPL driver
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* Adds a register write to the pending queue that will be flushed
|
||||
* out to the OPL on the next timer call
|
||||
*/
|
||||
void write(int reg, int val);
|
||||
|
||||
/**
|
||||
* Timer function for OPL
|
||||
*/
|
||||
void onTimer();
|
||||
|
||||
/**
|
||||
* Flushes any pending writes to the OPL
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* Resets all the output frequencies
|
||||
*/
|
||||
void resetFrequencies();
|
||||
|
||||
/**
|
||||
* Sets the frequency for an operator
|
||||
*/
|
||||
void setFrequency(byte operatorNum, uint frequency);
|
||||
|
||||
/**
|
||||
* Calculates the frequency for a note
|
||||
*/
|
||||
uint calcFrequency(byte note);
|
||||
|
||||
/**
|
||||
* Sets the output level for a channel
|
||||
*/
|
||||
void setOutputLevel(byte channelNum, uint level);
|
||||
|
||||
/**
|
||||
* Starts playing an instrument
|
||||
*/
|
||||
void playInstrument(byte channelNum, const byte *data, bool isFx);
|
||||
|
||||
/**
|
||||
* Calculates the scaling/volume level to output based on sfx or music master volume
|
||||
*/
|
||||
byte calculateLevel(byte level, bool isFx);
|
||||
|
||||
protected:
|
||||
bool musSetInstrument(const byte *&srcP, byte param) override;
|
||||
bool musSetPitchWheel(const byte *&srcP, byte param) override;
|
||||
bool musSetPanning(const byte *&srcP, byte param) override;
|
||||
bool musFade(const byte *&srcP, byte param) override;
|
||||
bool musStartNote(const byte *&srcP, byte param) override;
|
||||
bool musSetVolume(const byte *&srcP, byte param) override;
|
||||
bool musInjectMidi(const byte *&srcP, byte param) override;
|
||||
bool musPlayInstrument(const byte *&srcP, byte param) override;
|
||||
bool cmdFreezeFrequency(const byte *&srcP, byte param) override;
|
||||
bool cmdChangeFrequency(const byte *&srcP, byte param) override;
|
||||
|
||||
bool fxSetInstrument(const byte *&srcP, byte param) override;
|
||||
bool fxSetVolume(const byte *&srcP, byte param) override;
|
||||
bool fxMidiReset(const byte *&srcP, byte param) override;
|
||||
bool fxMidiDword(const byte *&srcP, byte param) override;
|
||||
bool fxSetPanning(const byte *&srcP, byte param) override;
|
||||
bool fxChannelOff(const byte *&srcP, byte param) override;
|
||||
bool fxFade(const byte *&srcP, byte param) override;
|
||||
bool fxStartNote(const byte *&srcP, byte param) override;
|
||||
bool fxInjectMidi(const byte *&srcP, byte param) override;
|
||||
bool fxPlayInstrument(const byte *&srcP, byte param) override;
|
||||
|
||||
/**
|
||||
* Post-processing done when a pause countdown starts or is in progress
|
||||
*/
|
||||
void pausePostProcess() override;
|
||||
|
||||
/**
|
||||
* Does a reset of any sound effect
|
||||
*/
|
||||
void resetFX() override;
|
||||
|
||||
/**
|
||||
* Sends SysEx message
|
||||
*/
|
||||
void sysExMessage(const byte *&data) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SoundDriverAdlib();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~SoundDriverAdlib() override;
|
||||
|
||||
/**
|
||||
* Starts a special effect playing
|
||||
*/
|
||||
void playFX(uint effectId, const byte *data) override;
|
||||
|
||||
/**
|
||||
* Plays a song
|
||||
*/
|
||||
void playSong(const byte *data) override;
|
||||
|
||||
/**
|
||||
* Executes special music command
|
||||
*/
|
||||
int songCommand(uint commandId, byte musicVolume = 0, byte sfxVolume = 0) override;
|
||||
};
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
|
||||
#endif
|
||||
489
engines/mm/shared/xeen/sound_driver_mt32.cpp
Normal file
489
engines/mm/shared/xeen/sound_driver_mt32.cpp
Normal file
@@ -0,0 +1,489 @@
|
||||
/* 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/debug.h"
|
||||
#include "mm/shared/xeen/sound_driver_mt32.h"
|
||||
#include "mm/mm.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
const uint8 SoundDriverMT32::MIDI_NOTE_MAP[24] = {
|
||||
0x00, 0x0C, 0x0E, 0x10, 0x11, 0x13, 0x15, 0x17,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18,
|
||||
0x00, 0x0B, 0x0D, 0x0F, 0x10, 0x12, 0x14, 0x16
|
||||
};
|
||||
|
||||
#define MT32_ADJUST_VOLUME
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
static void timerCallback(void *param) {
|
||||
SoundDriverMT32 *_driver = (SoundDriverMT32*)param;
|
||||
if (!_driver || !_driver->_midiDriver)
|
||||
return;
|
||||
|
||||
_driver->_timerCount += _driver->_midiDriver->getBaseTempo();
|
||||
if (_driver->_timerCount > ((float)1000000 / CALLBACKS_PER_SECOND)) {
|
||||
_driver->_timerCount -= (float)1000000 / CALLBACKS_PER_SECOND;
|
||||
_driver->onTimer();
|
||||
}
|
||||
}
|
||||
|
||||
SoundDriverMT32::SoundDriverMT32() : _field180(0), _field181(0), _field182(0),
|
||||
_musicVolume(0), _sfxVolume(0), _timerCount(0), _midiDriver(nullptr) {
|
||||
Common::fill(&_musInstrumentPtrs[0], &_musInstrumentPtrs[16], (const byte *)nullptr);
|
||||
Common::fill(&_fxInstrumentPtrs[0], &_fxInstrumentPtrs[16], (const byte *)nullptr);
|
||||
Common::fill(&_last_notes[0], &_last_notes[16], 0xFF);
|
||||
|
||||
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32);
|
||||
_midiDriver = MidiDriver::createMidi(dev);
|
||||
int ret = _midiDriver->open();
|
||||
if (ret) {
|
||||
error("Can't open midi device. Errorcode: %d", ret);
|
||||
} else {
|
||||
_midiDriver->setTimerCallback(this, timerCallback);
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
SoundDriverMT32::~SoundDriverMT32() {
|
||||
if (_midiDriver) {
|
||||
_midiDriver->close();
|
||||
delete _midiDriver;
|
||||
_midiDriver = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundDriverMT32::onTimer() {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
execute();
|
||||
flush();
|
||||
}
|
||||
|
||||
void SoundDriverMT32::initialize() {
|
||||
_midiDriver->sendMT32Reset();
|
||||
|
||||
// set volume
|
||||
for (int idx = 0; idx < CHANNEL_COUNT; idx++)
|
||||
write(0xB1 + idx, 0x07, idx == 8 ? 0x7F : 0x4F);
|
||||
|
||||
resetFrequencies();
|
||||
|
||||
SoundDriverMT32::resetFX();
|
||||
}
|
||||
|
||||
void SoundDriverMT32::playFX(uint effectId, const byte *data) {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
|
||||
SoundDriver::playFX(effectId, data);
|
||||
}
|
||||
|
||||
void SoundDriverMT32::playSong(const byte *data) {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
SoundDriver::playSong(data);
|
||||
_field180 = 0;
|
||||
resetFrequencies();
|
||||
}
|
||||
|
||||
int SoundDriverMT32::songCommand(uint commandId, byte musicVolume, byte sfxVolume) {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
SoundDriver::songCommand(commandId, musicVolume, sfxVolume);
|
||||
|
||||
if (commandId == STOP_SONG) {
|
||||
_field180 = 0;
|
||||
resetFrequencies();
|
||||
} else if (commandId == RESTART_SONG) {
|
||||
_field180 = 0;
|
||||
_streams[stMUSIC]._playing = true;
|
||||
} else if (commandId < 0x100) {
|
||||
if (_streams[stMUSIC]._playing) {
|
||||
_field180 = commandId;
|
||||
_field182 = 0x7F;
|
||||
}
|
||||
} else if (commandId == SET_VOLUME) {
|
||||
_musicVolume = musicVolume;
|
||||
_sfxVolume = sfxVolume;
|
||||
} else if (commandId == GET_STATUS) {
|
||||
return _field180;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SoundDriverMT32::write(uint8 command, uint8 op1, uint8 op2) {
|
||||
MidiValue v(command, op1, op2);
|
||||
_queue.push(v);
|
||||
debugC(9, kDebugSound, "push %08x", v._val);
|
||||
}
|
||||
|
||||
void SoundDriverMT32::flush() {
|
||||
Common::StackLock slock(_driverMutex);
|
||||
|
||||
while (!_queue.empty()) {
|
||||
MidiValue v = _queue.pop();
|
||||
debugC(9, kDebugSound, "pop %08x", v._val);
|
||||
_midiDriver->send(v._val);
|
||||
}
|
||||
}
|
||||
|
||||
byte SoundDriverMT32::noteMap(byte note) {
|
||||
assert((note & 0x1F) < sizeof(MIDI_NOTE_MAP));
|
||||
uint8 freq = (note & 0xE0) >> 2;
|
||||
uint8 result = MIDI_NOTE_MAP[note & 0x1F];
|
||||
result+= freq + (freq >> 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
void SoundDriverMT32::pausePostProcess() {
|
||||
if (_field180 && ((_field181 += _field180) < 0)) {
|
||||
if (--_field182 < 0) {
|
||||
_streams[stMUSIC]._playing = false;
|
||||
_field180 = 0;
|
||||
resetFrequencies();
|
||||
} else {
|
||||
for (int channelNum = 8; channelNum >= 0; --channelNum) {
|
||||
if (channelNum == 7)
|
||||
return;
|
||||
|
||||
if (_channels[channelNum]._volume >= 40) {
|
||||
_channels[channelNum]._volume--;
|
||||
// set volume expression
|
||||
write(0xB1 + channelNum, 0x0B, _channels[channelNum]._volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte channelNum = 7;
|
||||
if (_channels[channelNum]._freqChange) {
|
||||
_channels[channelNum]._frequency += _channels[channelNum]._freqChange;
|
||||
// pitch bend
|
||||
write(0xE1 + channelNum, _channels[channelNum]._frequency & 0x7F, (_channels[channelNum]._frequency >> 8) & 0x7F);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundDriverMT32::resetFX() {
|
||||
// notes off on 8ch
|
||||
write(0xB8, 0x7B, 0x00);
|
||||
// pitch bend on 8ch
|
||||
write(0xE8, 0x00, 0x40);
|
||||
}
|
||||
|
||||
void SoundDriverMT32::resetFrequencies() {
|
||||
// set pitch
|
||||
for (int idx = CHANNEL_COUNT - 1; idx >= 0; idx--)
|
||||
write(0xE1 + idx, 0x00, 0x40);
|
||||
// set pan
|
||||
for (int idx = CHANNEL_COUNT - 1; idx >= 0; idx--)
|
||||
write(0xB1 + idx, 0x0A, 0x3F);
|
||||
// notes off
|
||||
for (int idx = CHANNEL_COUNT - 1; idx >= 0; idx--)
|
||||
write(0xB1 + idx, 0x7B, 0x00);
|
||||
}
|
||||
|
||||
void SoundDriverMT32::playInstrument(byte channelNum, const byte *data, bool isFx) {
|
||||
debugC(2, kDebugSound, "---START-playInstrument - %d", channelNum);
|
||||
|
||||
write(0xC1 + channelNum, *data, 0x00);
|
||||
|
||||
debugC(2, kDebugSound, "---END-playInstrument");
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musSetInstrument(const byte *&srcP, byte param) {
|
||||
srcP += 24;
|
||||
debugC(3, kDebugSound, "musSetInstrument %d -> %x", param, *srcP);
|
||||
_musInstrumentPtrs[param] = srcP;
|
||||
srcP += 2;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musSetPitchWheel(const byte *&srcP, byte param) {
|
||||
byte pitch1 = *srcP++;
|
||||
byte pitch2 = *srcP++;
|
||||
debugC(3, kDebugSound, "musSetPitchWheel");
|
||||
write(0xE1 + param, pitch1, pitch2);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musSetPanning(const byte *&srcP, byte param) {
|
||||
byte pan = *srcP++;
|
||||
debugC(3, kDebugSound, "mmusSetPanning");
|
||||
write(0xB1 + param, 0x0A, pan);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musFade(const byte *&srcP, byte param) {
|
||||
byte note = *srcP++;
|
||||
debugC(3, kDebugSound, "musFade: %x", note);
|
||||
if (param != 8)
|
||||
note = noteMap(note);
|
||||
|
||||
write(0x81 + param, note & 0x7F, 0x40);
|
||||
_last_notes[param] = 0xFF;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musStartNote(const byte *&srcP, byte param) {
|
||||
byte note = *srcP++;
|
||||
byte fade = *srcP++;
|
||||
|
||||
debugC(3, kDebugSound, "musStartNote %x, %x", note, fade);
|
||||
if (param != 8) {
|
||||
note = noteMap(note);
|
||||
}
|
||||
|
||||
if (param != 8) {
|
||||
if (param != 7)
|
||||
write(0x81 + param, _last_notes[param] & 0x7F, 0x7F);
|
||||
else
|
||||
write(0x81 + param, note & 0x7F, 0x7F);
|
||||
}
|
||||
|
||||
#if defined(MT32_ADJUST_VOLUME)
|
||||
byte level = calculateLevel(fade, false);
|
||||
if (level > 0)
|
||||
write(0x91 + param, note & 0x7F, level);
|
||||
#else
|
||||
write(0x91 + param, note & 0x7F, fade);
|
||||
#endif
|
||||
_last_notes[param] = note & 0x7F;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musSetVolume(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "musSetVolume %d", (int)*srcP);
|
||||
|
||||
uint8 status = *srcP++;
|
||||
uint8 volume = *srcP++;
|
||||
|
||||
if (status == 0 && !_field180) {
|
||||
_channels[param]._volume = volume;
|
||||
#if defined(MT32_ADJUST_VOLUME)
|
||||
byte level = calculateLevel(volume, true);
|
||||
write(0xB1 + param, 0x0B, level);
|
||||
#else
|
||||
write(0xB1 + param, 0x0B, volume);
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musInjectMidi(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "musInjectMidi");
|
||||
|
||||
// TODO: When this happens? Wrap it in sysex as rolmus do
|
||||
sysExMessage(srcP);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::musPlayInstrument(const byte *&srcP, byte param) {
|
||||
byte instrument = *srcP++;
|
||||
debugC(3, kDebugSound, "musPlayInstrument %d -> %d", param, instrument);
|
||||
|
||||
// TODO: rolmus also have condition to use _fxInstrumentPtrs instead if isFx is set in timer, ignore for now
|
||||
playInstrument(param, _musInstrumentPtrs[instrument], false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxSetInstrument(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxSetInstrument %d -> %x", param, *srcP);
|
||||
_fxInstrumentPtrs[param] = srcP;
|
||||
++srcP;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxSetVolume(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxSetVolume %d", (int)*srcP);
|
||||
|
||||
uint8 volume = *srcP++;
|
||||
|
||||
if (!_field180) {
|
||||
_channels[param]._volume = volume;
|
||||
#if defined(MT32_ADJUST_VOLUME)
|
||||
byte level = calculateLevel(volume, true);
|
||||
write(0xB1 + param, 0x0B, level);
|
||||
#else
|
||||
write(0xB1 + param, 0x0B, volume);
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxMidiReset(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxMidiReset");
|
||||
|
||||
_channels[param]._freqChange = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxMidiDword(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxMidiDword");
|
||||
|
||||
_channels[param]._freqChange = READ_LE_UINT16(*&srcP);
|
||||
srcP += 2;
|
||||
_channels[param]._frequency = READ_LE_UINT16(*&srcP);
|
||||
srcP += 2;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxSetPanning(const byte *&srcP, byte param) {
|
||||
byte pan = *srcP++;
|
||||
debugC(3, kDebugSound, "fxSetPanning - %x", pan);
|
||||
|
||||
write(0xB1 + param, 0x0A, pan);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxChannelOff(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxChannelOff %d", param);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxFade(const byte *&srcP, byte param) {
|
||||
byte note = *srcP++;
|
||||
|
||||
debugC(3, kDebugSound, "fxFade %d %x", param, note);
|
||||
note = noteMap(note);
|
||||
|
||||
if (param == 7)
|
||||
write(0x81 + param, _last_notes[param] & 0x7F, 0x7F);
|
||||
else
|
||||
write(0x81 + param, note & 0x7F, 0x7F);
|
||||
|
||||
_last_notes[param] = 0xFF;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxStartNote(const byte *&srcP, byte param) {
|
||||
byte note = *srcP++;
|
||||
byte fade = *srcP++;
|
||||
|
||||
debugC(3, kDebugSound, "fxStartNote %x, %x", note, fade);
|
||||
|
||||
if (param != 8)
|
||||
note = noteMap(note);
|
||||
|
||||
#if defined(MT32_ADJUST_VOLUME)
|
||||
byte level = calculateLevel(fade, true);
|
||||
if (level > 0)
|
||||
write(0x91 + param, note & 0x7F, level);
|
||||
else
|
||||
write(0x81 + param, note & 0x7F, 0x7f);
|
||||
#else
|
||||
write(0x91 + param, note & 0x7F, fade);
|
||||
#endif
|
||||
_last_notes[param] = note & 0x7F;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxInjectMidi(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "fxInjectMidi");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::fxPlayInstrument(const byte *&srcP, byte param) {
|
||||
byte instrument = *srcP++;
|
||||
debugC(3, kDebugSound, "fxPlayInstrument %d, %d", param, instrument);
|
||||
|
||||
playInstrument(param, _fxInstrumentPtrs[instrument], true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
byte SoundDriverMT32::calculateLevel(byte level, bool isFx) {
|
||||
uint volume = isFx ? _sfxVolume : _musicVolume;
|
||||
float scaling = 127.0f / 255.0f;
|
||||
uint totalLevel = volume * scaling;
|
||||
|
||||
return totalLevel;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::cmdFreezeFrequency(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "cmdNoOperation");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SoundDriverMT32::cmdChangeFrequency(const byte *&srcP, byte param) {
|
||||
debugC(3, kDebugSound, "cmdChangeFrequency");
|
||||
|
||||
srcP += 3;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SoundDriverMT32::sysExMessage(const byte *&data) {
|
||||
byte sysExMessage[270];
|
||||
uint16 sysExPos = 0;
|
||||
byte sysExByte = 0;
|
||||
uint16 sysExChecksum = 0;
|
||||
|
||||
memset(&sysExMessage, 0, sizeof(sysExMessage));
|
||||
|
||||
sysExMessage[0] = 0x41; // Roland
|
||||
sysExMessage[1] = 0x10; // Device ID
|
||||
sysExMessage[2] = 0x16; // Model MT32
|
||||
sysExMessage[3] = 0x12; // Command DT1
|
||||
|
||||
sysExPos = 4;
|
||||
sysExChecksum = 0;
|
||||
while (1) {
|
||||
sysExByte = *data++;
|
||||
if (sysExByte == 0xF7)
|
||||
break; // Message done
|
||||
|
||||
// if we need bigger buffer then buffer in MidiDriver_ALSA::sysEx should also be increased!
|
||||
assert(sysExPos < sizeof(sysExMessage));
|
||||
sysExMessage[sysExPos++] = sysExByte;
|
||||
sysExChecksum -= sysExByte;
|
||||
}
|
||||
|
||||
// Calculate checksum
|
||||
assert(sysExPos < sizeof(sysExMessage));
|
||||
sysExMessage[sysExPos++] = sysExChecksum & 0x7f;
|
||||
|
||||
debugC(3, "sending sysex message, size %d", sysExPos);
|
||||
|
||||
// Send SysEx message
|
||||
_midiDriver->sysEx(sysExMessage, sysExPos);
|
||||
}
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
165
engines/mm/shared/xeen/sound_driver_mt32.h
Normal file
165
engines/mm/shared/xeen/sound_driver_mt32.h
Normal file
@@ -0,0 +1,165 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_SOUND_DRIVER_MT32_H
|
||||
#define MM_SHARED_XEEN_SOUND_DRIVER_MT32_H
|
||||
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "mm/shared/xeen/sound_driver.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
class SoundDriverMT32 : public SoundDriver {
|
||||
struct MidiValue {
|
||||
uint32 _val;
|
||||
|
||||
MidiValue(uint8 command, uint8 op1, uint8 op2) {
|
||||
_val = (command) | ((uint32)op2 << 16) | ((uint32)op1 << 8);
|
||||
}
|
||||
};
|
||||
public:
|
||||
MidiDriver *_midiDriver;
|
||||
uint32 _timerCount;
|
||||
private:
|
||||
static const uint8 MIDI_NOTE_MAP[24];
|
||||
private:
|
||||
Common::Queue<MidiValue> _queue;
|
||||
Common::Mutex _driverMutex;
|
||||
const byte *_musInstrumentPtrs[16];
|
||||
const byte *_fxInstrumentPtrs[16];
|
||||
byte _last_notes[16];
|
||||
int _field180;
|
||||
int _field181;
|
||||
int _field182;
|
||||
int _musicVolume, _sfxVolume;
|
||||
private:
|
||||
/**
|
||||
* Initializes the state of the MT32 driver
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* Adds a register write to the pending queue that will be flushed
|
||||
* out to the MT32 on the next timer call
|
||||
*/
|
||||
void write(uint8 command, uint8 op1, uint8 op2);
|
||||
|
||||
/**
|
||||
* Flushes any pending writes to the OPL
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* Resets all the output frequencies
|
||||
*/
|
||||
void resetFrequencies();
|
||||
|
||||
/**
|
||||
* Starts playing an instrument
|
||||
*/
|
||||
void playInstrument(byte channelNum, const byte *data, bool isFx);
|
||||
|
||||
/**
|
||||
* Calculates the scaling/volume level to output based on sfx or music master volume
|
||||
*/
|
||||
byte calculateLevel(byte level, bool isFx);
|
||||
|
||||
/**
|
||||
* Maps note using hardcoded notes table
|
||||
*/
|
||||
byte noteMap(byte note);
|
||||
protected:
|
||||
bool musSetInstrument(const byte *&srcP, byte param) override;
|
||||
bool musSetPitchWheel(const byte *&srcP, byte param) override;
|
||||
bool musSetPanning(const byte *&srcP, byte param) override;
|
||||
bool musFade(const byte *&srcP, byte param) override;
|
||||
bool musStartNote(const byte *&srcP, byte param) override;
|
||||
bool musSetVolume(const byte *&srcP, byte param) override;
|
||||
bool musInjectMidi(const byte *&srcP, byte param) override;
|
||||
bool musPlayInstrument(const byte *&srcP, byte param) override;
|
||||
bool cmdFreezeFrequency(const byte *&srcP, byte param) override;
|
||||
bool cmdChangeFrequency(const byte *&srcP, byte param) override;
|
||||
|
||||
bool fxSetInstrument(const byte *&srcP, byte param) override;
|
||||
bool fxSetVolume(const byte *&srcP, byte param) override;
|
||||
bool fxMidiReset(const byte *&srcP, byte param) override;
|
||||
bool fxMidiDword(const byte *&srcP, byte param) override;
|
||||
bool fxSetPanning(const byte *&srcP, byte param) override;
|
||||
bool fxChannelOff(const byte *&srcP, byte param) override;
|
||||
bool fxFade(const byte *&srcP, byte param) override;
|
||||
bool fxStartNote(const byte *&srcP, byte param) override;
|
||||
bool fxInjectMidi(const byte *&srcP, byte param) override;
|
||||
bool fxPlayInstrument(const byte *&srcP, byte param) override;
|
||||
|
||||
/**
|
||||
* Post-processing done when a pause countdown starts or is in progress
|
||||
*/
|
||||
void pausePostProcess() override;
|
||||
|
||||
/**
|
||||
* Does a reset of any sound effect
|
||||
*/
|
||||
void resetFX() override;
|
||||
|
||||
/**
|
||||
* Sends SysEx message
|
||||
*/
|
||||
void sysExMessage(const byte *&data) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SoundDriverMT32();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~SoundDriverMT32() override;
|
||||
|
||||
/**
|
||||
* Starts a special effect playing
|
||||
*/
|
||||
void playFX(uint effectId, const byte *data) override;
|
||||
|
||||
/**
|
||||
* Plays a song
|
||||
*/
|
||||
void playSong(const byte *data) override;
|
||||
|
||||
/**
|
||||
* Executes special music command
|
||||
*/
|
||||
int songCommand(uint commandId, byte musicVolume = 0, byte sfxVolume = 0) override;
|
||||
|
||||
/**
|
||||
* Timer function for MT32
|
||||
*/
|
||||
void onTimer();
|
||||
};
|
||||
|
||||
} // namespace Xeen
|
||||
} // namespace Shared
|
||||
} // namespace MM
|
||||
|
||||
#endif
|
||||
617
engines/mm/shared/xeen/sprites.cpp
Normal file
617
engines/mm/shared/xeen/sprites.cpp
Normal file
@@ -0,0 +1,617 @@
|
||||
/* 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/scummsys.h"
|
||||
#include "common/archive.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "graphics/paletteman.h"
|
||||
#include "image/bmp.h"
|
||||
#include "image/png.h"
|
||||
#include "mm/shared/xeen/sprites.h"
|
||||
#include "mm/mm.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
#define SCREEN_WIDTH 320
|
||||
#define SCREEN_HEIGHT 200
|
||||
#define PALETTE_COUNT 256
|
||||
#define PALETTE_SIZE (256 * 3)
|
||||
#define SCENE_CLIP_LEFT 8
|
||||
#define SCENE_CLIP_RIGHT 223
|
||||
|
||||
int SpriteResource::_clippedBottom;
|
||||
|
||||
SpriteResource::SpriteResource() {
|
||||
_filesize = 0;
|
||||
_data = nullptr;
|
||||
}
|
||||
|
||||
SpriteResource::SpriteResource(const Common::Path &filename) {
|
||||
_data = nullptr;
|
||||
load(filename);
|
||||
}
|
||||
|
||||
SpriteResource::SpriteResource(const SpriteResource &src) {
|
||||
copy(src);
|
||||
}
|
||||
|
||||
SpriteResource::~SpriteResource() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void SpriteResource::copy(const SpriteResource &src) {
|
||||
_filesize = src._filesize;
|
||||
_data = new byte[_filesize];
|
||||
Common::copy(src._data, src._data + _filesize, _data);
|
||||
|
||||
_index.resize(src._index.size());
|
||||
for (uint i = 0; i < src._index.size(); ++i)
|
||||
_index[i] = src._index[i];
|
||||
}
|
||||
|
||||
SpriteResource &SpriteResource::operator=(const SpriteResource &src) {
|
||||
delete[] _data;
|
||||
_index.clear();
|
||||
|
||||
copy(src);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SpriteResource::load(const Common::Path &filename) {
|
||||
_filename = filename;
|
||||
Common::File f;
|
||||
if (g_engine->getGameID() == GType_MightAndMagic1 && f.open(filename)) {
|
||||
load(f);
|
||||
} else {
|
||||
File f2(filename);
|
||||
load(f2);
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteResource::load(Common::SeekableReadStream &f) {
|
||||
// Read in a copy of the file
|
||||
_filesize = f.size();
|
||||
delete[] _data;
|
||||
_data = new byte[_filesize];
|
||||
f.read(_data, _filesize);
|
||||
|
||||
// Read in the index
|
||||
f.seek(0);
|
||||
int count = f.readUint16LE();
|
||||
_index.resize(count);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
_index[i]._offset1 = f.readUint16LE();
|
||||
_index[i]._offset2 = f.readUint16LE();
|
||||
|
||||
// Check for any images overriding the default Xeen sprites for M&M1 Enhanced mode
|
||||
const Common::String fnPNG = Common::String::format("gfx/%s/image%.2d.png", _filename.baseName().c_str(), i);
|
||||
const Common::String fnBitmap = Common::String::format("gfx/%s/image%.2d.bmp", _filename.baseName().c_str(), i);
|
||||
Image::PNGDecoder pngDecoder;
|
||||
Image::BitmapDecoder bmpDecoder;
|
||||
Common::File imgFile;
|
||||
|
||||
if (Common::File::exists(fnPNG.c_str()) && imgFile.open(fnPNG.c_str()) && pngDecoder.loadStream(imgFile)) {
|
||||
_index[i]._override.copyFrom(*pngDecoder.getSurface());
|
||||
} else if (Common::File::exists(fnBitmap.c_str()) && imgFile.open(fnBitmap.c_str()) && bmpDecoder.loadStream(imgFile)) {
|
||||
_index[i]._override.copyFrom(*bmpDecoder.getSurface());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteResource::clear() {
|
||||
delete[] _data;
|
||||
_data = nullptr;
|
||||
_filesize = 0;
|
||||
_index.clear();
|
||||
}
|
||||
|
||||
void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
|
||||
uint flags, int scale) const {
|
||||
draw(dest, frame, destPos, Common::Rect(0, 0, dest.w, dest.h), flags, scale);
|
||||
}
|
||||
|
||||
void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
|
||||
const Common::Rect &bounds, uint flags, int scale) const {
|
||||
Common::Rect r = bounds;
|
||||
if (flags & SPRFLAG_BOTTOM_CLIPPED)
|
||||
r.clip(SCREEN_WIDTH, _clippedBottom);
|
||||
|
||||
// Create drawer to handle the rendering
|
||||
SpriteDrawer *drawer;
|
||||
switch (flags & SPRFLAG_MODE_MASK) {
|
||||
case SPRFLAG_DRAWER1:
|
||||
drawer = new SpriteDrawer1(_data, _filesize, flags & 0x1F);
|
||||
break;
|
||||
case SPRFLAG_DRAWER2:
|
||||
drawer = new SpriteDrawer2(_data, _filesize, flags & 0x1F);
|
||||
break;
|
||||
case SPRFLAG_DRAWER3:
|
||||
drawer = new SpriteDrawer3(_data, _filesize, flags & 0x1F);
|
||||
break;
|
||||
case SPRFLAG_DRAWER5:
|
||||
drawer = new SpriteDrawer5(_data, _filesize, flags & 0x1F);
|
||||
break;
|
||||
case SPRFLAG_DRAWER6:
|
||||
drawer = new SpriteDrawer6(_data, _filesize, flags & 0x1F);
|
||||
break;
|
||||
default:
|
||||
drawer = new SpriteDrawer(_data, _filesize);
|
||||
break;
|
||||
}
|
||||
|
||||
// WORKAROUND: Crash clicking Vertigo well in Clouds
|
||||
if (frame < (int)_index.size()) {
|
||||
if (!_index[frame]._override.empty()) {
|
||||
const Graphics::ManagedSurface &src = _index[frame]._override;
|
||||
if (flags & Shared::Xeen::SPRFLAG_RESIZE)
|
||||
dest.create(src.w, src.h);
|
||||
dest.blitFrom(src, destPos);
|
||||
} else {
|
||||
// Sprites can consist of separate background & foreground
|
||||
drawer->draw(dest, _index[frame]._offset1, destPos, r, flags, scale);
|
||||
if (_index[frame]._offset2)
|
||||
drawer->draw(dest, _index[frame]._offset2, destPos, r, flags, scale);
|
||||
}
|
||||
}
|
||||
|
||||
delete drawer;
|
||||
}
|
||||
|
||||
void SpriteResource::draw(XSurface &dest, int frame) const {
|
||||
draw(dest, frame, Common::Point());
|
||||
}
|
||||
|
||||
void SpriteResource::draw(Graphics::ManagedSurface *dest, int frame, const Common::Point &destPos) const {
|
||||
XSurface tmp;
|
||||
tmp.w = dest->w;
|
||||
tmp.h = dest->h;
|
||||
tmp.pitch = dest->pitch;
|
||||
tmp.format = dest->format;
|
||||
tmp.setPixels(dest->getPixels());
|
||||
|
||||
draw(tmp, frame, destPos);
|
||||
}
|
||||
|
||||
|
||||
Common::Point SpriteResource::getFrameSize(int frame) const {
|
||||
Common::MemoryReadStream f(_data, _filesize);
|
||||
Common::Point frameSize;
|
||||
|
||||
for (int idx = 0; idx < (_index[frame]._offset2 ? 2 : 1); ++idx) {
|
||||
f.seek((idx == 0) ? _index[frame]._offset1 : _index[frame]._offset2);
|
||||
int xOffset = f.readUint16LE();
|
||||
int width = f.readUint16LE();
|
||||
int yOffset = f.readUint16LE();
|
||||
int height = f.readUint16LE();
|
||||
|
||||
frameSize.x = MAX((int)frameSize.x, xOffset + width);
|
||||
frameSize.y = MAX((int)frameSize.y, yOffset + height);
|
||||
}
|
||||
|
||||
return frameSize;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void SpriteDrawer::draw(XSurface &dest, uint16 offset, const Common::Point &pt,
|
||||
const Common::Rect &clipRect, uint flags, int scale) {
|
||||
static const uint SCALE_TABLE[] = {
|
||||
0xFFFF, 0xFFEF, 0xEFEF, 0xEFEE, 0xEEEE, 0xEEAE, 0xAEAE, 0xAEAA,
|
||||
0xAAAA, 0xAA8A, 0x8A8A, 0x8A88, 0x8888, 0x8880, 0x8080, 0x8000
|
||||
};
|
||||
static const int PATTERN_STEPS[] = { 0, 1, 1, 1, 2, 2, 3, 3, 0, -1, -1, -1, -2, -2, -3, -3 };
|
||||
|
||||
assert((scale & SCALE_MASK) < 16);
|
||||
uint16 scaleMask = SCALE_TABLE[scale & SCALE_MASK];
|
||||
uint16 scaleMaskX = scaleMask, scaleMaskY = scaleMask;
|
||||
bool flipped = (flags & SPRFLAG_HORIZ_FLIPPED) != 0;
|
||||
int xInc = flipped ? -1 : 1;
|
||||
bool enlarge = (scale & SCALE_ENLARGE) != 0;
|
||||
|
||||
_destTop = (byte *)dest.getBasePtr(clipRect.left, clipRect.top);
|
||||
_destBottom = (byte *)dest.getBasePtr(clipRect.right, clipRect.bottom - 1);
|
||||
_pitch = dest.pitch;
|
||||
|
||||
// Get cell header
|
||||
Common::MemoryReadStream f(_data, _filesize);
|
||||
f.seek(offset);
|
||||
int xOffset = f.readUint16LE();
|
||||
int width = f.readUint16LE();
|
||||
int yOffset = f.readUint16LE();
|
||||
int height = f.readUint16LE();
|
||||
|
||||
// Figure out drawing x, y
|
||||
Common::Point destPos;
|
||||
destPos.x = pt.x + getScaledVal(xOffset, scaleMaskX);
|
||||
destPos.x += (width - getScaledVal(width, scaleMaskX)) / 2;
|
||||
|
||||
destPos.y = pt.y + getScaledVal(yOffset, scaleMaskY);
|
||||
|
||||
// If the flags allow the dest surface to be resized, ensure dest surface is big enough
|
||||
Common::Rect bounds = clipRect;
|
||||
if (flags & SPRFLAG_RESIZE) {
|
||||
if (dest.w < (xOffset + width) || dest.h < (yOffset + height))
|
||||
dest.create(xOffset + width, yOffset + height);
|
||||
bounds = Common::Rect(0, 0, dest.w, dest.h);
|
||||
}
|
||||
if (flags & SPRFLAG_SCENE_CLIPPED) {
|
||||
bounds.clip(Common::Rect(8, 8, 223, 141));
|
||||
}
|
||||
|
||||
uint16 scaleMaskXCopy = scaleMaskX;
|
||||
Common::Rect drawBounds;
|
||||
drawBounds.left = SCREEN_WIDTH;
|
||||
drawBounds.top = SCREEN_HEIGHT;
|
||||
drawBounds.right = drawBounds.bottom = 0;
|
||||
|
||||
// Main loop
|
||||
for (int yCtr = height; yCtr > 0; --yCtr) {
|
||||
// The number of bytes in this scan line
|
||||
int lineLength = f.readByte();
|
||||
|
||||
if (lineLength == 0) {
|
||||
// Skip the specified number of scan lines
|
||||
int numLines = f.readByte();
|
||||
destPos.y += getScaledVal(numLines + 1, scaleMaskY);
|
||||
yCtr -= numLines;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Roll the scale mask
|
||||
uint bit = (scaleMaskY >> 15) & 1;
|
||||
scaleMaskY = ((scaleMaskY & 0x7fff) << 1) + bit;
|
||||
|
||||
if (!bit) {
|
||||
// Not a line to be drawn due to scaling down
|
||||
f.skip(lineLength);
|
||||
} else if (destPos.y < bounds.top || destPos.y >= bounds.bottom) {
|
||||
// Skip over the bytes of the line
|
||||
f.skip(lineLength);
|
||||
destPos.y++;
|
||||
} else {
|
||||
scaleMaskX = scaleMaskXCopy;
|
||||
xOffset = f.readByte();
|
||||
|
||||
// Initialize the array to hold the temporary data for the line. We do this to make it simpler
|
||||
// to handle both deciding which pixels to draw in a scaled image, as well as when images
|
||||
// have been horizontally flipped. Note that we allocate an extra line for before and after our
|
||||
// work line, just in case the sprite is screwed up and overruns the line
|
||||
int tempLine[SCREEN_WIDTH * 3];
|
||||
Common::fill(&tempLine[SCREEN_WIDTH], &tempLine[SCREEN_WIDTH * 3], -1);
|
||||
int *lineP = flipped ? &tempLine[SCREEN_WIDTH + width - 1 - xOffset] : &tempLine[SCREEN_WIDTH + xOffset];
|
||||
|
||||
// Build up the line
|
||||
int byteCount, opr1, opr2;
|
||||
int32 pos;
|
||||
for (byteCount = 1; byteCount < lineLength; ) {
|
||||
// The next byte is an opcode that determines what operators are to follow and how to interpret them.
|
||||
int opcode = f.readByte(); ++byteCount;
|
||||
|
||||
// Decode the opcode
|
||||
int len = opcode & 0x1F;
|
||||
int cmd = (opcode & 0xE0) >> 5;
|
||||
|
||||
switch (cmd) {
|
||||
case 0: // The following len + 1 bytes are stored as indexes into the color table.
|
||||
case 1: // The following len + 33 bytes are stored as indexes into the color table.
|
||||
for (int i = 0; i < opcode + 1; ++i, ++byteCount) {
|
||||
byte b = f.readByte();
|
||||
*lineP = b;
|
||||
lineP += xInc;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // The following byte is an index into the color table, draw it len + 3 times.
|
||||
opr1 = f.readByte(); ++byteCount;
|
||||
for (int i = 0; i < len + 3; ++i) {
|
||||
*lineP = opr1;
|
||||
lineP += xInc;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // Stream copy command.
|
||||
opr1 = f.readUint16LE(); byteCount += 2;
|
||||
pos = f.pos();
|
||||
f.seek(-opr1, SEEK_CUR);
|
||||
|
||||
for (int i = 0; i < len + 4; ++i) {
|
||||
*lineP = f.readByte();
|
||||
lineP += xInc;
|
||||
}
|
||||
|
||||
f.seek(pos, SEEK_SET);
|
||||
break;
|
||||
|
||||
case 4: // The following two bytes are indexes into the color table, draw the pair len + 2 times.
|
||||
opr1 = f.readByte(); ++byteCount;
|
||||
opr2 = f.readByte(); ++byteCount;
|
||||
for (int i = 0; i < len + 2; ++i) {
|
||||
*lineP = opr1;
|
||||
lineP += xInc;
|
||||
*lineP = opr2;
|
||||
lineP += xInc;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5: // Skip len + 1 pixels
|
||||
lineP += (len + 1) * xInc;
|
||||
break;
|
||||
|
||||
case 6: // Pattern command.
|
||||
case 7:
|
||||
// The pattern command has a different opcode format
|
||||
len = opcode & 0x07;
|
||||
cmd = (opcode >> 2) & 0x0E;
|
||||
|
||||
opr1 = f.readByte(); ++byteCount;
|
||||
for (int i = 0; i < len + 3; ++i) {
|
||||
*lineP = opr1;
|
||||
lineP += xInc;
|
||||
opr1 += PATTERN_STEPS[cmd + (i % 2)];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(byteCount == lineLength);
|
||||
|
||||
drawBounds.top = MIN(drawBounds.top, destPos.y);
|
||||
drawBounds.bottom = MAX((int)drawBounds.bottom, destPos.y + 1);
|
||||
|
||||
// Handle drawing out the line
|
||||
byte *destP = (byte *)dest.getBasePtr(destPos.x, destPos.y);
|
||||
_destLeft = (byte *)dest.getBasePtr(
|
||||
(flags & SPRFLAG_SCENE_CLIPPED) ? SCENE_CLIP_LEFT : clipRect.left, destPos.y);
|
||||
_destRight = (byte *)dest.getBasePtr(
|
||||
(flags & SPRFLAG_SCENE_CLIPPED) ? SCENE_CLIP_RIGHT : clipRect.right, destPos.y);
|
||||
int16 xp = destPos.x;
|
||||
lineP = &tempLine[SCREEN_WIDTH];
|
||||
|
||||
for (int xCtr = 0; xCtr < width; ++xCtr, ++lineP) {
|
||||
bit = (scaleMaskX >> 15) & 1;
|
||||
scaleMaskX = ((scaleMaskX & 0x7fff) << 1) + bit;
|
||||
|
||||
if (bit) {
|
||||
// Check whether there's a pixel to write, and we're within the allowable bounds. Note that for
|
||||
// the SPRFLAG_SCENE_CLIPPED or when enlarging, we also have an extra horizontal bounds check
|
||||
if (*lineP != -1 && xp >= bounds.left && xp < bounds.right) {
|
||||
drawBounds.left = MIN(drawBounds.left, xp);
|
||||
drawBounds.right = MAX((int)drawBounds.right, xp + 1);
|
||||
drawPixel(destP, (byte)*lineP);
|
||||
if (enlarge) {
|
||||
drawPixel(destP + SCREEN_WIDTH, (byte)*lineP);
|
||||
drawPixel(destP + 1, (byte)*lineP);
|
||||
drawPixel(destP + 1 + SCREEN_WIDTH, (byte)*lineP);
|
||||
}
|
||||
}
|
||||
|
||||
++xp;
|
||||
++destP;
|
||||
if (enlarge) {
|
||||
++destP;
|
||||
++xp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++destPos.y;
|
||||
if (enlarge)
|
||||
++destPos.y;
|
||||
}
|
||||
}
|
||||
|
||||
if (drawBounds.isValidRect()) {
|
||||
drawBounds.clip(Common::Rect(0, 0, dest.w, dest.h));
|
||||
if (!drawBounds.isEmpty())
|
||||
dest.addDirtyRect(drawBounds);
|
||||
}
|
||||
}
|
||||
|
||||
uint SpriteDrawer::getScaledVal(int xy, uint16 &scaleMask) {
|
||||
if (!xy)
|
||||
return 0;
|
||||
|
||||
uint result = 0;
|
||||
for (int idx = 0; idx < xy; ++idx) {
|
||||
uint bit = (scaleMask >> 15) & 1;
|
||||
scaleMask = ((scaleMask & 0x7fff) << 1) + bit;
|
||||
result += bit;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SpriteDrawer::drawPixel(byte *dest, byte pixel) {
|
||||
*dest = pixel;
|
||||
}
|
||||
|
||||
void SpriteDrawer::rcr(uint16 &val, bool &cf) {
|
||||
bool newCf = (val & 1);
|
||||
val = (val >> 1) | (cf ? 0x8000 : 0);
|
||||
cf = newCf;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
static const byte DRAWER1_OFFSET[24] = {
|
||||
0x30, 0xC0, 0xB0, 0x10, 0x41, 0x20, 0x40, 0x21, 0x48, 0x46, 0x43, 0x40,
|
||||
0xD0, 0xD3, 0xD6, 0xD8, 0x01, 0x04, 0x07, 0x0A, 0xEA, 0xEE, 0xF2, 0xF6
|
||||
};
|
||||
|
||||
static const byte DRAWER1_MASK[24] = {
|
||||
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x0F, 0x07, 0x07, 0x07, 0x07,
|
||||
0x07, 0x07, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x07, 0x07
|
||||
};
|
||||
|
||||
SpriteDrawer1::SpriteDrawer1(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
|
||||
_offset = DRAWER1_OFFSET[index];
|
||||
_mask = DRAWER1_MASK[index];
|
||||
}
|
||||
|
||||
void SpriteDrawer1::drawPixel(byte *dest, byte pixel) {
|
||||
*dest = (pixel & _mask) + _offset;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
static const byte DRAWER2_MASK1[32] = {
|
||||
3, 0, 3, 0, 3, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0,
|
||||
1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
static const byte DRAWER2_MASK2[16] = {
|
||||
0x7E, 0x7E, 0x7E, 0x7E, 0x3E, 0x3E, 0x3E, 0x3E,
|
||||
0x1E, 0x1E, 0x1E, 0x1E, 0x0E, 0x0E, 0x0E, 0x0E
|
||||
};
|
||||
|
||||
static const int8 DRAWER2_DELTA[64] = {
|
||||
-3, 3, 0, 0, 0, 0, 0, 0,
|
||||
-5, 5, 0, 0, 0, 0, 0, 0,
|
||||
-7, 7, 0, 0, 0, 0, 0, 0,
|
||||
-9, 9, 0, 0, 0, 0, 0, 0,
|
||||
-7, 7, 0, 0, 0, 0, 0, 0,
|
||||
-9, 9, 0, 0, 0, 0, 0, 0,
|
||||
-11, 11, 0, 0, 0, 0, 0, 0,
|
||||
-13, 13, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
SpriteDrawer2::SpriteDrawer2(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
|
||||
_mask1 = DRAWER2_MASK1[index];
|
||||
_mask2 = DRAWER2_MASK2[index];
|
||||
|
||||
MM::MMEngine *engine = static_cast<MM::MMEngine *>(g_engine);
|
||||
_random1 = engine->getRandomNumber(0xffff);
|
||||
_random2 = engine->getRandomNumber(0xffff);
|
||||
}
|
||||
|
||||
void SpriteDrawer2::drawPixel(byte *dest, byte pixel) {
|
||||
bool flag = (_random1 & 0x8000) != 0;
|
||||
_random1 = (int)((uint16)_random1 << 1) - _random2 - (flag ? 1 : 0);
|
||||
|
||||
rcr(_random2, flag);
|
||||
rcr(_random2, flag);
|
||||
_random2 ^= _random1;
|
||||
|
||||
dest += DRAWER2_DELTA[(_random2 & _mask1 & _mask2) / 2];
|
||||
if (dest >= _destLeft && dest < _destRight) {
|
||||
dest += _pitch * DRAWER2_DELTA[((_random2 >> 8) &_mask1 &_mask2) / 2];
|
||||
|
||||
if (dest >= _destTop && dest < _destBottom) {
|
||||
*dest = pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
static const uint16 DRAWER3_MASK[4] = { 1, 3, 7, 15 };
|
||||
static const uint16 DRAWER3_OFFSET[4] = { 1, 2, 4, 8 };
|
||||
|
||||
SpriteDrawer3::SpriteDrawer3(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
|
||||
_offset = DRAWER3_OFFSET[index];
|
||||
_mask = DRAWER3_MASK[index];
|
||||
|
||||
g_system->getPaletteManager()->grabPalette(_palette, 0, PALETTE_COUNT);
|
||||
_hasPalette = false;
|
||||
for (byte *pal = _palette; pal < _palette + PALETTE_SIZE && !_hasPalette; ++pal)
|
||||
_hasPalette = *pal != 0;
|
||||
}
|
||||
|
||||
void SpriteDrawer3::drawPixel(byte *dest, byte pixel) {
|
||||
// WORKAROUND: This is slightly different then the original:
|
||||
// 1) The original has bunches of black pixels appearing. This does index increments to avoid such pixels
|
||||
// 2) It also prevents any pixels being drawn in the single initial frame until the palette is set
|
||||
if (_hasPalette) {
|
||||
byte level = (pixel & _mask) - _offset + (*dest & 0xf);
|
||||
|
||||
if (level >= 0x80) {
|
||||
*dest &= 0xf0;
|
||||
} else if (level <= 0xf) {
|
||||
*dest = (*dest & 0xf0) | level;
|
||||
} else {
|
||||
*dest |= 0xf;
|
||||
}
|
||||
|
||||
//
|
||||
while (*dest < 0xff && !_palette[*dest * 3] && !_palette[*dest * 3 + 1] && !_palette[*dest * 3 + 2])
|
||||
++ *dest;
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
static const byte DRAWER4_THRESHOLD[4] = { 4, 7, 10, 13 };
|
||||
|
||||
SpriteDrawer4::SpriteDrawer4(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
|
||||
_threshold = DRAWER4_THRESHOLD[index];
|
||||
}
|
||||
|
||||
void SpriteDrawer4::drawPixel(byte *dest, byte pixel) {
|
||||
if ((pixel & 0xf) >= _threshold)
|
||||
*dest = pixel;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
static const uint16 DRAWER5_THRESHOLD[4] = { 0x3333, 0x6666, 0x999A, 0xCCCD };
|
||||
|
||||
SpriteDrawer5::SpriteDrawer5(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
|
||||
_threshold = DRAWER5_THRESHOLD[index];
|
||||
|
||||
MM::MMEngine *engine = static_cast<MM::MMEngine *>(g_engine);
|
||||
_random1 = engine->getRandomNumber(0xffff);
|
||||
_random2 = engine->getRandomNumber(0xffff);
|
||||
}
|
||||
|
||||
void SpriteDrawer5::drawPixel(byte *dest, byte pixel) {
|
||||
bool flag = (_random1 & 0x8000) != 0;
|
||||
_random1 = (int)((uint16)_random1 << 1) - _random2 - (flag ? 1 : 0);
|
||||
|
||||
rcr(_random2, flag);
|
||||
rcr(_random2, flag);
|
||||
_random2 ^= _random1;
|
||||
|
||||
if (_random2 > _threshold)
|
||||
*dest = pixel;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
static const byte DRAWER6_MASK[16] = { 1, 2, 4, 8, 1, 3, 7, 15, 8, 12, 14, 15, 1, 2, 1, 2 };
|
||||
|
||||
SpriteDrawer6::SpriteDrawer6(byte *data, size_t filesize, int index) : SpriteDrawer(data, filesize) {
|
||||
_mask = DRAWER6_MASK[index];
|
||||
}
|
||||
|
||||
void SpriteDrawer6::drawPixel(byte *dest, byte pixel) {
|
||||
*dest = pixel ^ _mask;
|
||||
}
|
||||
|
||||
} // End of namespace Xeen
|
||||
} // End of namespace Shared
|
||||
} // End of namespace MM
|
||||
307
engines/mm/shared/xeen/sprites.h
Normal file
307
engines/mm/shared/xeen/sprites.h
Normal file
@@ -0,0 +1,307 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_SPRITES_H
|
||||
#define MM_SHARED_XEEN_SPRITES_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/array.h"
|
||||
#include "common/file.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "mm/shared/xeen/file.h"
|
||||
#include "mm/shared/xeen/xsurface.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Xeen {
|
||||
class XeenEngine;
|
||||
class Window;
|
||||
} // namespace Xeen
|
||||
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
enum {
|
||||
SCALE_MASK = 0x7FFF, SCALE_ENLARGE = 0x8000
|
||||
};
|
||||
|
||||
enum SpriteFlags {
|
||||
SPRFLAG_MODE_MASK = 0xF00, SPRFLAG_DRAWER1 = 0x100, SPRFLAG_DRAWER2 = 0x200,
|
||||
SPRFLAG_DRAWER3 = 0x300, SPRFLAG_DRAWER4 = 0x400, SPRFLAG_DRAWER5 = 0x500, SPRFLAG_DRAWER6 = 0x600,
|
||||
SPRFLAG_DRAWER7 = 0x700, SPRFLAG_800 = 0x800, SPRFLAG_SCENE_CLIPPED = 0x2000,
|
||||
SPRFLAG_BOTTOM_CLIPPED = 0x4000, SPRFLAG_HORIZ_FLIPPED = 0x8000, SPRFLAG_RESIZE = 0x10000
|
||||
};
|
||||
|
||||
class SpriteResource {
|
||||
protected:
|
||||
struct IndexEntry {
|
||||
uint16 _offset1, _offset2;
|
||||
Graphics::ManagedSurface _override;
|
||||
};
|
||||
Common::Array<IndexEntry> _index;
|
||||
Common::Array<Graphics::ManagedSurface> _overrides;
|
||||
size_t _filesize;
|
||||
byte *_data;
|
||||
Common::Path _filename;
|
||||
static int _clippedBottom;
|
||||
|
||||
/**
|
||||
* Load a sprite resource from a stream
|
||||
*/
|
||||
void load(Common::SeekableReadStream &f);
|
||||
|
||||
/**
|
||||
* Draw the sprite onto the given surface
|
||||
*/
|
||||
void draw(XSurface &dest, int frame, const Common::Point &destPos,
|
||||
const Common::Rect &bounds, uint flags = 0, int scale = 0) const;
|
||||
|
||||
/**
|
||||
* Deep copy assuming that the current instance is clean
|
||||
*/
|
||||
void copy(const SpriteResource &src);
|
||||
public:
|
||||
SpriteResource();
|
||||
SpriteResource(const Common::Path &filename);
|
||||
SpriteResource(const SpriteResource &src);
|
||||
|
||||
virtual ~SpriteResource();
|
||||
|
||||
/**
|
||||
* Copy operator for duplicating a sprite resource
|
||||
*/
|
||||
SpriteResource &operator=(const SpriteResource &src);
|
||||
|
||||
/**
|
||||
* Load a sprite resource from a given file
|
||||
*/
|
||||
void load(const Common::Path &filename);
|
||||
|
||||
/**
|
||||
* Clears the sprite resource
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Draw a sprite onto a surface
|
||||
* @param dest Destination surface
|
||||
* @param frame Frame number
|
||||
* @param destPos Destination position
|
||||
* @param flags Flags
|
||||
* @param scale Scale: 0=No scale, SCALE_ENLARGE=Enlarge it
|
||||
* 1..15 -> reduces the sprite: the higher, the smaller it'll be
|
||||
*/
|
||||
void draw(XSurface &dest, int frame, const Common::Point &destPos,
|
||||
uint flags = 0, int scale = 0) const;
|
||||
|
||||
/**
|
||||
* Draw the sprite onto the given surface
|
||||
* @param dest Destination surface
|
||||
* @param frame Frame number
|
||||
*/
|
||||
void draw(XSurface &dest, int frame) const;
|
||||
|
||||
/**
|
||||
* Draw the sprite onto a given surface
|
||||
*/
|
||||
void draw(Graphics::ManagedSurface *dest, int frame, const Common::Point &destPos) const;
|
||||
|
||||
/**
|
||||
* Gets the size of a sprite
|
||||
*/
|
||||
Common::Point getFrameSize(int frame) const;
|
||||
|
||||
/**
|
||||
* Returns the number of frames the sprite resource has
|
||||
*/
|
||||
size_t size() const {
|
||||
return _index.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the sprite resource is empty (ie. nothing is loaded)
|
||||
*/
|
||||
bool empty() const {
|
||||
return _index.size() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bottom Y position where sprites are clipped if SPRFLAG_BOTTOM_CLIPPED
|
||||
* is applied
|
||||
*/
|
||||
static void setClippedBottom(int y) {
|
||||
_clippedBottom = y;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Basic sprite drawer
|
||||
*/
|
||||
class SpriteDrawer {
|
||||
private:
|
||||
byte *_data = nullptr;
|
||||
size_t _filesize = 0;
|
||||
protected:
|
||||
byte *_destTop = nullptr, *_destBottom = nullptr;
|
||||
byte *_destLeft = nullptr, *_destRight = nullptr;
|
||||
int _pitch = 0;
|
||||
private:
|
||||
/**
|
||||
* Scale a co-ordinate value based on the passed scaling mask
|
||||
*/
|
||||
static uint getScaledVal(int xy, uint16 &scaleMask);
|
||||
protected:
|
||||
/**
|
||||
* Roll carry right opcode emulation
|
||||
*/
|
||||
void rcr(uint16 &val, bool &cf);
|
||||
|
||||
/**
|
||||
* Output a pixel
|
||||
*/
|
||||
virtual void drawPixel(byte *dest, byte pixel);
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SpriteDrawer(byte *data, size_t filesize) : _data(data), _filesize(filesize) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
virtual ~SpriteDrawer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a sprite frame based on a passed offset into the data stream
|
||||
*/
|
||||
void draw(XSurface &dest, uint16 offset, const Common::Point &pt,
|
||||
const Common::Rect &clipRect, uint flags, int scale);
|
||||
};
|
||||
|
||||
class SpriteDrawer1 : public SpriteDrawer {
|
||||
private:
|
||||
byte _offset = 0, _mask = 0;
|
||||
protected:
|
||||
/**
|
||||
* Output a pixel
|
||||
*/
|
||||
void drawPixel(byte *dest, byte pixel) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SpriteDrawer1(byte *data, size_t filesize, int index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrambles up the sprite by drawing many of the pixels randomly
|
||||
* at a horizontal or vertical offset
|
||||
*/
|
||||
class SpriteDrawer2 : public SpriteDrawer {
|
||||
private:
|
||||
uint16 _mask1 = 0, _mask2 = 0;
|
||||
uint16 _random1 = 0, _random2 = 0;
|
||||
private:
|
||||
/**
|
||||
* Output a pixel
|
||||
*/
|
||||
void drawPixel(byte *dest, byte pixel) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SpriteDrawer2(byte *data, size_t filesize, int index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws the sprite as faint ghostly, see-through.
|
||||
*/
|
||||
class SpriteDrawer3 : public SpriteDrawer {
|
||||
private:
|
||||
uint16 _offset = 0, _mask = 0;
|
||||
byte _palette[256 * 3];
|
||||
bool _hasPalette = false;
|
||||
private:
|
||||
/**
|
||||
* Output a pixel
|
||||
*/
|
||||
void drawPixel(byte *dest, byte pixel) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SpriteDrawer3(byte *data, size_t filesize, int index);
|
||||
};
|
||||
|
||||
class SpriteDrawer4 : public SpriteDrawer {
|
||||
private:
|
||||
byte _threshold = 0;
|
||||
protected:
|
||||
/**
|
||||
* Output a pixel
|
||||
*/
|
||||
void drawPixel(byte *dest, byte pixel) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SpriteDrawer4(byte *data, size_t filesize, int index);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a sprite with a fuzziness effect where only some pixels of the sprite are randomly drawn
|
||||
*/
|
||||
class SpriteDrawer5 : public SpriteDrawer {
|
||||
private:
|
||||
uint16 _threshold = 0, _random1 = 0, _random2 = 0;
|
||||
protected:
|
||||
/**
|
||||
* Output a pixel
|
||||
*/
|
||||
void drawPixel(byte *dest, byte pixel) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SpriteDrawer5(byte *data, size_t filesize, int index);
|
||||
};
|
||||
|
||||
class SpriteDrawer6 : public SpriteDrawer {
|
||||
private:
|
||||
byte _mask = 0;
|
||||
protected:
|
||||
/**
|
||||
* Output a pixel
|
||||
*/
|
||||
void drawPixel(byte *dest, byte pixel) override;
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
SpriteDrawer6(byte *data, size_t filesize, int index);
|
||||
};
|
||||
|
||||
} // End of namespace Xeen
|
||||
} // End of namespace Shared
|
||||
} // End of namespace MM
|
||||
|
||||
#endif
|
||||
34
engines/mm/shared/xeen/xsurface.cpp
Normal file
34
engines/mm/shared/xeen/xsurface.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/* 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/algorithm.h"
|
||||
#include "common/util.h"
|
||||
#include "mm/shared/xeen/xsurface.h"
|
||||
#include "mm/xeen/resources.h"
|
||||
#include "mm/xeen/screen.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
} // End of namespace Xeen
|
||||
} // End of namespace Shared
|
||||
} // End of namespace MM
|
||||
56
engines/mm/shared/xeen/xsurface.h
Normal file
56
engines/mm/shared/xeen/xsurface.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MM_SHARED_XEEN_XSURFACE_H
|
||||
#define MM_SHARED_XEEN_XSURFACE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/system.h"
|
||||
#include "common/rect.h"
|
||||
#include "graphics/managed_surface.h"
|
||||
|
||||
namespace MM {
|
||||
namespace Shared {
|
||||
namespace Xeen {
|
||||
|
||||
class BaseSurface : public Graphics::ManagedSurface {
|
||||
public:
|
||||
void addDirtyRect(const Common::Rect &r) override {
|
||||
Graphics::ManagedSurface::addDirtyRect(r);
|
||||
}
|
||||
public:
|
||||
BaseSurface() : Graphics::ManagedSurface() {}
|
||||
BaseSurface(int width, int height) : Graphics::ManagedSurface(width, height) {}
|
||||
~BaseSurface() override {}
|
||||
};
|
||||
|
||||
class XSurface : public BaseSurface {
|
||||
public:
|
||||
XSurface() : BaseSurface() {}
|
||||
XSurface(int width, int height) : BaseSurface(width, height) {}
|
||||
~XSurface() override {}
|
||||
};
|
||||
|
||||
} // End of namespace Xeen
|
||||
} // End of namespace Shared
|
||||
} // End of namespace MM
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user