Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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