Initial commit
This commit is contained in:
903
engines/scumm/players/player_mac_loom_monkey.cpp
Normal file
903
engines/scumm/players/player_mac_loom_monkey.cpp
Normal file
@@ -0,0 +1,903 @@
|
||||
/* 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 "scumm/players/player_mac_new.h"
|
||||
#include "scumm/players/player_mac_intern.h"
|
||||
#include "scumm/resource.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/punycode.h"
|
||||
#include "common/macresman.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
namespace Scumm {
|
||||
|
||||
#define ASC_DEVICE_RATE 0x56EE8BA3
|
||||
#define PCM_BUFFER_SIZE 1024
|
||||
|
||||
class MacSndLoader {
|
||||
protected:
|
||||
MacSndLoader(bool useInstrTag);
|
||||
public:
|
||||
virtual ~MacSndLoader() {}
|
||||
|
||||
virtual bool init() = 0;
|
||||
virtual bool checkResource(const byte *data, uint32 dataSize) const = 0;
|
||||
virtual bool blocked(const byte *data, uint32 dataSize) const = 0;
|
||||
virtual bool loadSound(const byte *data, uint32 dataSize) = 0;
|
||||
virtual void unblock() = 0;
|
||||
void setSynthType(byte type) { _synth = type; }
|
||||
|
||||
const MacLowLevelPCMDriver::PCMSound *getInstrData(uint16 chan);
|
||||
virtual bool isInstrUsable(uint16 chan) const = 0;
|
||||
virtual bool parseNextEvent(uint16 chan, uint16 &duration, uint8 ¬e, bool &skip, bool &updateInstr) = 0;
|
||||
virtual uint16 getChanSetup() const = 0;
|
||||
uint16 getTimbre() const { return _timbre; }
|
||||
virtual bool isMusic() const = 0;
|
||||
bool isLooping() const { return _loop; }
|
||||
virtual bool restartSoundAfterLoad() const = 0;
|
||||
|
||||
virtual bool ignoreMachineRating() const = 0;
|
||||
|
||||
protected:
|
||||
bool loadInstruments(const char *const *tryFileNames, uint16 numTryFileNames, uint16 numInstruments);
|
||||
|
||||
Common::Array<Common::SharedPtr<MacSndResource> > _instruments;
|
||||
|
||||
byte _sndRes6;
|
||||
byte _isMusic;
|
||||
byte _sndRes9;
|
||||
byte _sndRes10;
|
||||
uint16 _chanSetup;
|
||||
uint16 _timbre;
|
||||
bool _loop;
|
||||
byte _synth;
|
||||
const byte *_chanSndData[5];
|
||||
uint32 _chanNumEvents[5];
|
||||
uint32 _chanCurEvent[5];
|
||||
const Common::SharedPtr<MacSndResource> *_chanInstr[5];
|
||||
const Common::SharedPtr<MacSndResource> *_chanInstr2[5];
|
||||
|
||||
const bool _useInstrTag;
|
||||
};
|
||||
|
||||
class LoomMacSndLoader final : public MacSndLoader {
|
||||
public:
|
||||
LoomMacSndLoader() : MacSndLoader(false) {}
|
||||
~LoomMacSndLoader() override {}
|
||||
|
||||
bool init() override;
|
||||
bool checkResource(const byte *data, uint32 dataSize) const override;
|
||||
bool blocked(const byte *data, uint32 dataSize) const override { return false; }
|
||||
bool loadSound(const byte *data, uint32 dataSize) override;
|
||||
void unblock() override {}
|
||||
|
||||
bool isInstrUsable(uint16 chan) const override;
|
||||
bool parseNextEvent(uint16 chan, uint16 &duration, uint8 ¬e, bool &skip, bool &updateInstr) override;
|
||||
uint16 getChanSetup() const override { return _chanSetup; }
|
||||
bool isMusic() const override { return (_chanSetup == 0); }
|
||||
bool restartSoundAfterLoad() const override { return isMusic(); }
|
||||
bool ignoreMachineRating() const override { return false; }
|
||||
private:
|
||||
const Common::SharedPtr<MacSndResource> *fetchInstrument(uint16 id) const;
|
||||
};
|
||||
|
||||
class MonkeyMacSndLoader final : public MacSndLoader {
|
||||
public:
|
||||
MonkeyMacSndLoader() : MacSndLoader(true), _numInstrumentsMax(17), _blockSfx(false), _transpose(0) {}
|
||||
~MonkeyMacSndLoader() override {}
|
||||
|
||||
bool init() override;
|
||||
bool checkResource(const byte *data, uint32 dataSize) const override;
|
||||
bool blocked(const byte *data, uint32 dataSize) const override;
|
||||
bool loadSound(const byte *data, uint32 dataSize) override;
|
||||
void unblock() override { _blockSfx = false; }
|
||||
|
||||
bool isInstrUsable(uint16 chan) const override;
|
||||
bool parseNextEvent(uint16 chan, uint16 &duration, uint8 ¬e, bool &skip, bool &updateInstr) override;
|
||||
uint16 getChanSetup() const override { return _isMusic ? _chanSetup : 7; }
|
||||
bool isMusic() const override { return _isMusic; }
|
||||
bool restartSoundAfterLoad() const override { return _isMusic && _loop; }
|
||||
bool ignoreMachineRating() const override { return true; }
|
||||
private:
|
||||
const Common::SharedPtr<MacSndResource> *fetchInstrument(const byte *data, uint32 dataSize, uint32 tagOrOffset);
|
||||
bool _blockSfx;
|
||||
byte _transpose;
|
||||
const byte _numInstrumentsMax;
|
||||
};
|
||||
|
||||
MacSndLoader::MacSndLoader(bool useInstrTag) : _sndRes6(0), _isMusic(0), _sndRes9(0), _sndRes10(0), _chanSetup(0),
|
||||
_timbre(0), _useInstrTag(useInstrTag), _synth(0), _loop(false) {
|
||||
memset(_chanInstr, 0, sizeof(_chanInstr));
|
||||
memset(_chanInstr2, 0, sizeof(_chanInstr2));
|
||||
memset(_chanSndData, 0, sizeof(_chanSndData));
|
||||
memset(_chanNumEvents, 0, sizeof(_chanNumEvents));
|
||||
memset(_chanCurEvent, 0, sizeof(_chanCurEvent));
|
||||
}
|
||||
|
||||
const MacLowLevelPCMDriver::PCMSound *MacSndLoader::getInstrData(uint16 chan) {
|
||||
return (chan < ARRAYSIZE(_chanInstr) && _chanInstr[chan]) ? _chanInstr[chan]->get()->data() : nullptr;
|
||||
}
|
||||
|
||||
bool MacSndLoader::loadInstruments(const char *const *tryFileNames, uint16 numTryFileNames, uint16 numInstruments) {
|
||||
assert(tryFileNames && numTryFileNames && numInstruments);
|
||||
uint32 tag = 0;
|
||||
const Common::CodePage tryCodePages[] = {
|
||||
Common::kMacRoman,
|
||||
Common::kISO8859_1
|
||||
};
|
||||
|
||||
Common::MacResManager resMan;
|
||||
Common::Path resFile;
|
||||
for (int i = 0; resFile.empty() && i < numTryFileNames; ++i) {
|
||||
for (int ii = 0; resFile.empty() && ii < ARRAYSIZE(tryCodePages); ++ii) {
|
||||
Common::U32String fn(tryFileNames[i], tryCodePages[ii]);
|
||||
resFile = Common::Path(fn.encode(Common::kUtf8));
|
||||
if (!resMan.exists(resFile) || !resMan.open(resFile) || !resMan.hasResFork()) {
|
||||
resMan.close();
|
||||
resFile = Common::Path(Common::punycode_encodefilename(fn));
|
||||
if (!resMan.exists(resFile) || !resMan.open(resFile) || !resMan.hasResFork()) {
|
||||
resMan.close();
|
||||
resFile.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resFile.empty()) {
|
||||
warning("MacSndLoader::loadInstruments(): Resource fork not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::MacResIDArray ids = resMan.getResIDArray(MKTAG('s', 'n', 'd', ' '));
|
||||
for (uint i = 0; i < ids.size(); ++i) {
|
||||
Common::SeekableReadStream *str = resMan.getResource(MKTAG('s', 'n', 'd', ' '), ids[i]);
|
||||
if (!str || str->readUint16BE() != 1) {
|
||||
static const char *const errStr[2] = {"Failed to load", "Invalid sound resource format for"};
|
||||
warning("MacSndLoader::loadInstruments(): %s instrument with id 0x%04x", errStr[str ? 1 : 0], ids[i]);
|
||||
delete str;
|
||||
return false;
|
||||
}
|
||||
Common::String nm(resMan.getResName(MKTAG('s', 'n', 'd', ' '), ids[i]));
|
||||
memcpy(&tag, nm.c_str(), MIN<uint>(nm.size(), sizeof(tag)));
|
||||
uint32 id = _useInstrTag ? FROM_BE_32(tag) : ids[i];
|
||||
_instruments.push_back(Common::SharedPtr<MacSndResource>(new MacSndResource(id, str, Common::move(nm))));
|
||||
delete str;
|
||||
}
|
||||
|
||||
if (_instruments.size() != numInstruments)
|
||||
warning("MacSndLoader::loadInstruments(): Unexpected number of instruments found (expected: %d, found: %d)", numInstruments, _instruments.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoomMacSndLoader::init() {
|
||||
static const char *const execNames[] = {
|
||||
"Loom",
|
||||
"Loom\xaa",
|
||||
"Loom PPC",
|
||||
"Loom\xaa PPC"
|
||||
};
|
||||
|
||||
return loadInstruments(execNames, ARRAYSIZE(execNames), 10);
|
||||
}
|
||||
|
||||
bool LoomMacSndLoader::checkResource(const byte *data, uint32 dataSize) const {
|
||||
return (dataSize >= 14 && READ_BE_UINT16(data + 4) == MKTAG16('s','o') && !READ_BE_UINT32(data + 10));
|
||||
}
|
||||
|
||||
bool LoomMacSndLoader::loadSound(const byte *data, uint32 dataSize) {
|
||||
if (dataSize < 40)
|
||||
return false;
|
||||
|
||||
_sndRes6 = READ_BE_UINT16(data + 6) & 0xff;
|
||||
_isMusic = READ_BE_UINT16(data + 8) >> 8;
|
||||
_sndRes10 = READ_BE_UINT16(data + 10) >> 8;
|
||||
_chanSetup = READ_BE_UINT16(data + 16);
|
||||
_timbre = READ_BE_UINT16(data + 18);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
_chanInstr[i] = fetchInstrument(READ_BE_UINT16(data + 20 + 2 * i));
|
||||
_chanSndData[i] = data + READ_BE_UINT16(data + 30 + 2 * i) + 6;
|
||||
_chanNumEvents[i] = READ_BE_UINT16(_chanSndData[i] - 2);
|
||||
_chanCurEvent[i] = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoomMacSndLoader::isInstrUsable(uint16 chan) const {
|
||||
return (chan < ARRAYSIZE(_chanInstr) && _chanInstr[chan] && _chanInstr[chan]->get()->id() != 0x2D1C);
|
||||
}
|
||||
|
||||
bool LoomMacSndLoader::parseNextEvent(uint16 chan, uint16 &duration, uint8 ¬e, bool &skip, bool &updateInstr) {
|
||||
if (chan >= ARRAYSIZE(_chanSndData) || !_chanSndData[chan] || _chanCurEvent[chan] >= _chanNumEvents[chan])
|
||||
return false;
|
||||
|
||||
const byte *s = _chanSndData[chan] + (_chanCurEvent[chan]++) * 3;
|
||||
duration = READ_BE_UINT16(s);
|
||||
note = s[2] & 0x7f;
|
||||
skip = false;
|
||||
updateInstr = false;
|
||||
|
||||
if (_synth == 4 && chan != 0 && note == 0)
|
||||
note = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Common::SharedPtr<MacSndResource> *LoomMacSndLoader::fetchInstrument(uint16 id) const {
|
||||
Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator instr = _instruments.end();
|
||||
for (Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
|
||||
if ((*i)->id() == id)
|
||||
return i;
|
||||
else if ((*i)->id() == 0x2D1C)
|
||||
instr = i;
|
||||
}
|
||||
return (instr != _instruments.end()) ? instr : nullptr;
|
||||
}
|
||||
|
||||
bool MonkeyMacSndLoader::init() {
|
||||
static const char *const execNames[] = {
|
||||
"Monkey Island"
|
||||
};
|
||||
|
||||
return loadInstruments(execNames, ARRAYSIZE(execNames), _numInstrumentsMax - 1);
|
||||
}
|
||||
|
||||
bool MonkeyMacSndLoader::checkResource(const byte *data, uint32 dataSize) const {
|
||||
return (dataSize >= 14 && (READ_BE_UINT32(data) == MKTAG('M', 'a', 'c', '0') || READ_BE_UINT32(data) == MKTAG('M', 'a', 'c', '1')));
|
||||
}
|
||||
|
||||
bool MonkeyMacSndLoader::blocked(const byte *data, uint32 dataSize) const {
|
||||
return (dataSize < 14 || (_blockSfx && !data[13]));
|
||||
}
|
||||
|
||||
bool MonkeyMacSndLoader::loadSound(const byte *data, uint32 dataSize) {
|
||||
if (dataSize < 32)
|
||||
return false;
|
||||
|
||||
_sndRes9 = data[9];
|
||||
_isMusic = data[13];
|
||||
_sndRes10 = data[10];
|
||||
_chanSetup = data[11];
|
||||
_timbre = data[12];
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
uint32 offs = READ_BE_UINT32(data + 16 + 4 * i);
|
||||
_chanCurEvent[i] = 0;
|
||||
if (offs) {
|
||||
if (dataSize < offs + 12)
|
||||
return false;
|
||||
_chanInstr[i] = fetchInstrument(data, dataSize, READ_BE_UINT32(data + offs + 8));
|
||||
_chanInstr2[i] = nullptr;
|
||||
_chanSndData[i] = &data[offs + 12];
|
||||
_chanNumEvents[i] = 0;
|
||||
|
||||
for (const byte *s = _chanSndData[i]; s < &data[dataSize - 4]; s += 4) {
|
||||
uint32 in = READ_BE_UINT32(s);
|
||||
if (in == MKTAG('L', 'o', 'o', 'p') || in == MKTAG('D', 'o', 'n', 'e')) {
|
||||
if (i == 1)
|
||||
_loop = (in == MKTAG('L', 'o', 'o', 'p'));
|
||||
break;
|
||||
}
|
||||
_chanNumEvents[i]++;
|
||||
}
|
||||
} else {
|
||||
_chanInstr[i] = nullptr;
|
||||
_chanSndData[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
_blockSfx = _isMusic;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MonkeyMacSndLoader::isInstrUsable(uint16 chan) const {
|
||||
return (chan < ARRAYSIZE(_chanInstr) && _chanInstr[chan] && _chanInstr[chan]->get()->id() != MKTAG('s', 'i', 'l', 'e'));
|
||||
}
|
||||
|
||||
bool MonkeyMacSndLoader::parseNextEvent(uint16 chan, uint16 &duration, uint8 ¬e, bool &skip, bool &updateInstr) {
|
||||
if (chan >= ARRAYSIZE(_chanSndData) || !_chanSndData[chan] || _chanCurEvent[chan] >= _chanNumEvents[chan])
|
||||
return false;
|
||||
|
||||
const byte *s = _chanSndData[chan] + (_chanCurEvent[chan]++) * 4;
|
||||
duration = READ_BE_UINT16(s);
|
||||
note = s[2];
|
||||
skip = false;
|
||||
updateInstr = true;
|
||||
|
||||
if (duration == 0 && _chanCurEvent[chan] == _chanNumEvents[chan])
|
||||
skip = true;
|
||||
|
||||
if (_synth == 4) {
|
||||
if (!skip && note == 0) {
|
||||
note = 60;
|
||||
_chanInstr2[chan] = _chanInstr[chan];
|
||||
_chanInstr[chan] = fetchInstrument(nullptr, 0, MKTAG('s', 'i', 'l', 'e'));
|
||||
} else if (_chanInstr2[chan]) {
|
||||
_chanInstr[chan] = _chanInstr2[chan];
|
||||
_chanInstr2[chan] = nullptr;
|
||||
} else {
|
||||
updateInstr = false;
|
||||
}
|
||||
if (note == 1)
|
||||
skip = true;
|
||||
else if (s[6] == 1)
|
||||
duration += READ_BE_UINT16(s + 4);
|
||||
} else {
|
||||
updateInstr = false;
|
||||
if (note == 1)
|
||||
note = 0;
|
||||
else if (note != 0)
|
||||
note += _transpose;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const Common::SharedPtr<MacSndResource> *MonkeyMacSndLoader::fetchInstrument(const byte *data, uint32 dataSize, uint32 tagOrOffset) {
|
||||
Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator instr = _instruments.end();
|
||||
if (tagOrOffset & ~0x7fffff) {
|
||||
for (Common::Array<Common::SharedPtr<MacSndResource> >::const_iterator i = _instruments.begin(); i != _instruments.end(); ++i) {
|
||||
if ((*i)->id() == tagOrOffset)
|
||||
return i;
|
||||
else if ((*i)->id() == MKTAG('s', 'i', 'l', 'e'))
|
||||
instr = i;
|
||||
}
|
||||
} else if (dataSize >= tagOrOffset + 8) {
|
||||
Common::SeekableReadStream *str = new Common::MemoryReadStream(&data[tagOrOffset + 8], READ_BE_UINT32(data + tagOrOffset + 4), DisposeAfterUse::NO);
|
||||
if (_instruments.size() == _numInstrumentsMax)
|
||||
_instruments.pop_back();
|
||||
_instruments.push_back(Common::SharedPtr<MacSndResource>(new MacSndResource(READ_BE_UINT32(&data[tagOrOffset]), str, Common::String())));
|
||||
delete str;
|
||||
instr = _instruments.end() - 1;
|
||||
}
|
||||
return (instr != _instruments.end()) ? instr : nullptr;
|
||||
}
|
||||
|
||||
Common::WeakPtr<LoomMonkeyMacSnd> *LoomMonkeyMacSnd::_inst = nullptr;
|
||||
|
||||
LoomMonkeyMacSnd::LoomMonkeyMacSnd(ScummEngine *vm, Audio::Mixer *mixer) : VblTaskClientDriver(), _vm(vm), _mixer(mixer), _curSound(0), _loader(nullptr),
|
||||
_macstr(nullptr), _sdrv(nullptr), _vblTskProc(this, &VblTaskClientDriver::vblCallback), _songTimer(0), _songTimerInternal(0), _disableFlags(0),
|
||||
_machineRating(0), _selectedQuality(2), _effectiveChanConfig(0), _16bit(false), _idRangeMax(200), _sndChannel(0), _chanUse(0), _defaultChanConfig(0),
|
||||
_chanConfigTable(nullptr), _chanPlaying(0), _curChanConfig(0), _curSynthType(0), _curSndType(Audio::Mixer::kPlainSoundType), _mixerThread(false),
|
||||
_restartSound(0), _lastSndType(Audio::Mixer::kPlainSoundType), _chanCbProc(this, &MacLowLevelPCMDriver::CallbackClient::sndChannelCallback),
|
||||
_curSoundSaveVar(0), _saveVersionChange(vm->_game.id == GID_MONKEY ? 115 : 114), _legacySaveUnits(vm->_game.id == GID_MONKEY ? 3 : 5),
|
||||
_checkSound(vm->_game.id == GID_MONKEY ? _curSound : _curSoundSaveVar) {
|
||||
assert(_vm);
|
||||
assert(_mixer);
|
||||
|
||||
static const byte cfgtable[] = { 0, 0, 0, 1, 4, 5, 1, 5, 6, 6, 8, 9 };
|
||||
_chanConfigTable = new byte[sizeof(cfgtable)]();
|
||||
memcpy(_chanConfigTable, cfgtable, sizeof(cfgtable));
|
||||
|
||||
if (vm->_game.id == GID_MONKEY)
|
||||
_chanConfigTable[10] = 7;
|
||||
|
||||
memset(_musChannels, 0, sizeof(_musChannels));
|
||||
}
|
||||
|
||||
LoomMonkeyMacSnd::~LoomMonkeyMacSnd() {
|
||||
_mixer->stopHandle(_soundHandle);
|
||||
delete _macstr;
|
||||
delete[] _chanConfigTable;
|
||||
|
||||
disposeAllChannels();
|
||||
delete _sdrv;
|
||||
delete _loader;
|
||||
|
||||
delete _inst;
|
||||
_inst = nullptr;
|
||||
}
|
||||
|
||||
Common::SharedPtr<LoomMonkeyMacSnd> LoomMonkeyMacSnd::open(ScummEngine *vm, Audio::Mixer *mixer) {
|
||||
Common::SharedPtr<LoomMonkeyMacSnd> scp = nullptr;
|
||||
|
||||
if (_inst == nullptr) {
|
||||
scp = Common::SharedPtr<LoomMonkeyMacSnd>(new LoomMonkeyMacSnd(vm, mixer));
|
||||
_inst = new Common::WeakPtr<LoomMonkeyMacSnd>(scp);
|
||||
// We can start this with the ScummVM mixer output rate instead of the ASC rate. The Mac sample rate converter can handle it (at
|
||||
// least for up to 48 KHz, I haven't tried higher settings) and we don't have to do another rate conversion in the ScummVM mixer.
|
||||
if ((_inst == nullptr) || (mixer == nullptr) || !(scp->startDevice(mixer->getOutputRate(), mixer->getOutputRate() << 16/*ASC_DEVICE_RATE*/, PCM_BUFFER_SIZE, true, false, true)))
|
||||
error("LoomMonkeyMacSnd::open(): Failed to start player");
|
||||
}
|
||||
|
||||
return _inst->lock();
|
||||
}
|
||||
|
||||
bool LoomMonkeyMacSnd::startDevice(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit) {
|
||||
_macstr = new MacPlayerAudioStream(this, outputRate, stereo, enableInterpolation, internal16Bit);
|
||||
if (!_macstr || !_mixer)
|
||||
return false;
|
||||
|
||||
if (_vm->_game.id == GID_LOOM)
|
||||
_loader = new LoomMacSndLoader();
|
||||
else if(_vm->_game.id == GID_MONKEY)
|
||||
_loader = new MonkeyMacSndLoader();
|
||||
|
||||
if (!_loader || !_loader->init())
|
||||
return false;
|
||||
|
||||
_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, internal16Bit);
|
||||
if (!_sdrv)
|
||||
return false;
|
||||
|
||||
_effectiveChanConfig = 9;
|
||||
_16bit = internal16Bit;
|
||||
|
||||
_macstr->initBuffers(feedBufferSize);
|
||||
_macstr->addVolumeGroup(Audio::Mixer::kMusicSoundType);
|
||||
_macstr->addVolumeGroup(Audio::Mixer::kSFXSoundType);
|
||||
_macstr->setVblCallback(&_vblTskProc);
|
||||
|
||||
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _macstr, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::setMusicVolume(int vol) {
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
if (_macstr)
|
||||
_macstr->setMasterVolume(Audio::Mixer::kMusicSoundType, vol);
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::setSfxVolume(int vol) {
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
if (_macstr)
|
||||
_macstr->setMasterVolume(Audio::Mixer::kSFXSoundType, vol);
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::startSound(int id, int jumpToTick) {
|
||||
if (_sdrv == nullptr || id < 1 || id >= _idRangeMax) {
|
||||
warning("LoomMonkeyMacSnd::startSound(): sound id '%d' out of range (1 - %d)", id, _idRangeMax - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
|
||||
uint32 size = _vm->getResourceSize(rtSound, id);
|
||||
const byte *ptr = _vm->getResourceAddress(rtSound, id);
|
||||
assert(ptr);
|
||||
|
||||
if (!_loader->checkResource(ptr, size)) {
|
||||
warning("LoomMonkeyMacSnd::startSound(): Sound resource '%d' cannot be played", id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_loader->blocked(ptr, size))
|
||||
return;
|
||||
|
||||
if (_curSound)
|
||||
stopActiveSound();
|
||||
if (_chanUse <= 1)
|
||||
disposeAllChannels();
|
||||
|
||||
if (!_defaultChanConfig)
|
||||
detectQuality();
|
||||
|
||||
if (!_loader->loadSound(ptr, size)) {
|
||||
warning("LoomMonkeyMacSnd::startSound(): Sound resource '%d' cannot be played", id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_disableFlags) {
|
||||
if (_loader->restartSoundAfterLoad()) {
|
||||
_curSoundSaveVar = id;
|
||||
_loader->unblock();
|
||||
}
|
||||
if (_loader->isMusic() || (_disableFlags & 2))
|
||||
return;
|
||||
}
|
||||
|
||||
_effectiveChanConfig = _loader->getChanSetup() ? _loader->getChanSetup() : _defaultChanConfig;
|
||||
_curSndType = _loader->isMusic() ? Audio::Mixer::kMusicSoundType : Audio::Mixer::kSFXSoundType;
|
||||
|
||||
if (_lastSndType != _curSndType)
|
||||
_curChanConfig = 0;
|
||||
|
||||
_curSound = id;
|
||||
_curSoundSaveVar = _loader->restartSoundAfterLoad() ? _curSound : 0;
|
||||
|
||||
setupChannels();
|
||||
sendSoundCommands(jumpToTick);
|
||||
|
||||
if (!jumpToTick) {
|
||||
_songTimer = 0;
|
||||
_songTimerInternal = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::stopSound(int id) {
|
||||
if (id < 1 || id >= _idRangeMax) {
|
||||
warning("LoomMonkeyMacSnd::stopSound(): sound id '%d' out of range (1 - %d)", id, _idRangeMax - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
|
||||
if (id == _curSoundSaveVar)
|
||||
_curSoundSaveVar = 0;
|
||||
|
||||
if (id == _curSound)
|
||||
stopActiveSound();
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::stopAllSounds() {
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
stopActiveSound();
|
||||
}
|
||||
|
||||
int LoomMonkeyMacSnd::getMusicTimer() {
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
return _songTimer;
|
||||
}
|
||||
|
||||
int LoomMonkeyMacSnd::getSoundStatus(int id) const {
|
||||
if (id < 1 || id >= _idRangeMax) {
|
||||
warning("LoomMonkeyMacSnd::getSoundStatus(): sound id '%d' out of range (1 - %d)", id, _idRangeMax - 1);
|
||||
return 0;
|
||||
}
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
return (_checkSound == id) ? 1 : 0;
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::setQuality(int qual) {
|
||||
assert(qual >= MacSound::kQualityAuto && qual <= MacSound::kQualityHighest);
|
||||
Common::StackLock lock(_mixer->mutex());
|
||||
|
||||
if (qual > MacSound::kQualityAuto) {
|
||||
qual--;
|
||||
if (!_loader->ignoreMachineRating())
|
||||
_machineRating = (qual / 3) + 1;
|
||||
_selectedQuality = qual % 3;
|
||||
qual = _chanConfigTable[_machineRating * 3 + _selectedQuality];
|
||||
if (qual && qual == _defaultChanConfig)
|
||||
return;
|
||||
}
|
||||
|
||||
int csnd = _curSound;
|
||||
int32 timeStamp = csnd ? _songTimer * 1000 + ((_songTimerInternal * 1000) / 30) : 0;
|
||||
stopActiveSound();
|
||||
|
||||
detectQuality();
|
||||
if (csnd)
|
||||
startSound(csnd, timeStamp);
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::saveLoadWithSerializer(Common::Serializer &ser) {
|
||||
if (ser.isLoading() && ser.getVersion() < VER(94)) {
|
||||
_curSound = _curSoundSaveVar = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ser.isLoading()) {
|
||||
if (ser.getVersion() < VER(_saveVersionChange)) {
|
||||
// Skip over old driver savedata, since it is not needed here.
|
||||
ser.skip(4);
|
||||
// We need only this
|
||||
ser.syncAsSint16LE(_curSound);
|
||||
_curSoundSaveVar = CLIP<int>(_curSound, 0, _idRangeMax - 1);
|
||||
// Skip the rest
|
||||
ser.skip(_legacySaveUnits * (ser.getVersion() >= VER(94) && ser.getVersion() <= VER(103) ? 24 : 20));
|
||||
} else if (ser.getVersion() <= VER(114)) {
|
||||
_curSoundSaveVar = 0;
|
||||
byte tmp = 0;
|
||||
for (int i = 0; !_curSoundSaveVar && i < 200; ++i) {
|
||||
ser.syncAsByte(tmp);
|
||||
if (tmp)
|
||||
_curSoundSaveVar = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
ser.syncAsSint16LE(_curSoundSaveVar, VER(115));
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::restoreAfterLoad() {
|
||||
int sound = _curSoundSaveVar;
|
||||
stopActiveSound();
|
||||
if (sound)
|
||||
startSound(sound);
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::toggleMusic(bool enable) {
|
||||
if ((_disableFlags & 1) == (enable ? 0 : 1))
|
||||
return;
|
||||
_disableFlags ^= 1;
|
||||
updateDisabledState();
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::toggleSoundEffects(bool enable) {
|
||||
if ((_disableFlags & 2) == (enable ? 0 : 2))
|
||||
return;
|
||||
_disableFlags ^= 2;
|
||||
updateDisabledState();
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::vblCallback() {
|
||||
if (_songTimerInternal++ == 29) {
|
||||
_songTimerInternal = 0;
|
||||
++_songTimer;
|
||||
}
|
||||
|
||||
if (_restartSound) {
|
||||
startSound(_restartSound);
|
||||
_restartSound = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
|
||||
assert(dst);
|
||||
memset(dst, 0, len);
|
||||
_sdrv->feed(dst, len, type, expectStereo);
|
||||
}
|
||||
|
||||
const MacSoundDriver::Status &LoomMonkeyMacSnd::getDriverStatus(Audio::Mixer::SoundType sndType) const {
|
||||
return _sdrv->getStatus(sndType);
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::sndChannelCallback(uint16 arg1, const void*) {
|
||||
// The original Loom player stops the track immediately when the first channel invoked its end-of-track
|
||||
// callback. This cuts of the playback early, in an unpleasant way. The Monkey Island player stops the
|
||||
// playback not before all channels have finished. We do the same here for both games.
|
||||
_chanPlaying &= ~arg1;
|
||||
if (_chanPlaying)
|
||||
return;
|
||||
|
||||
if (_loader->isLooping())
|
||||
_restartSound = _curSound;
|
||||
|
||||
stopActiveSound();
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::sendSoundCommands(int timeStamp) {
|
||||
if (!_defaultChanConfig || !_curSound)
|
||||
return;
|
||||
|
||||
uint16 duration = 0;
|
||||
byte note = 0;
|
||||
bool skip = false;
|
||||
bool updateInstr = false;
|
||||
_chanPlaying = 0;
|
||||
|
||||
if (_chanUse == 1 && _sndChannel) {
|
||||
while (_loader->parseNextEvent(0, duration, note, skip, updateInstr)) {
|
||||
if (timeStamp > 0 && !skip) {
|
||||
timeStamp -= duration;
|
||||
if (timeStamp >= 0)
|
||||
skip = true;
|
||||
else if (timeStamp < 0)
|
||||
duration = -timeStamp;
|
||||
}
|
||||
|
||||
if (updateInstr)
|
||||
_sdrv->loadInstrument(_sndChannel, MacLowLevelPCMDriver::kEnqueue, _loader->getInstrData(0));
|
||||
|
||||
if (!skip) {
|
||||
_sdrv->playNote(_sndChannel, MacLowLevelPCMDriver::kEnqueue, note, duration);
|
||||
if (note == 0) // Workaround for tempo glitch in original driver
|
||||
_sdrv->wait(_sndChannel, MacLowLevelPCMDriver::kEnqueue, duration);
|
||||
}
|
||||
}
|
||||
_sdrv->quiet(_sndChannel, MacLowLevelPCMDriver::kEnqueue);
|
||||
_sdrv->callback(_sndChannel, MacLowLevelPCMDriver::kEnqueue, 1, nullptr);
|
||||
_chanPlaying |= 1;
|
||||
|
||||
} else if (_chanUse == 4) {
|
||||
int tmstmp[4];
|
||||
uint8 busy = 0;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
tmstmp[i] = timeStamp;
|
||||
if (_musChannels[i])
|
||||
busy |= (1 << i);
|
||||
}
|
||||
|
||||
while (busy) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!(busy & (1 << i)) || !_loader->parseNextEvent(i + 1, duration, note, skip, updateInstr)) {
|
||||
busy &= ~(1 << i);
|
||||
continue;
|
||||
}
|
||||
if (tmstmp[i] > 0 && !skip) {
|
||||
tmstmp[i] -= duration;
|
||||
if (tmstmp[i] >= 0)
|
||||
skip = true;
|
||||
else if (tmstmp[i] < 0)
|
||||
duration = -tmstmp[i];
|
||||
}
|
||||
|
||||
if (updateInstr)
|
||||
_sdrv->loadInstrument(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, _loader->getInstrData(i + 1));
|
||||
|
||||
if (!skip) {
|
||||
_sdrv->playNote(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, note, duration);
|
||||
// Workaround for tempo glitch in original driver. For the sampled synth in 4 channel mode, there is
|
||||
// some sort of fix in the original (see parseNextEvent()), but that really does not work well for the other cases.
|
||||
if (note == 0 && _curSynthType != 4)
|
||||
_sdrv->wait(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, duration);
|
||||
}
|
||||
_chanPlaying |= (1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (_chanPlaying & (1 << i)) {
|
||||
_sdrv->quiet(_musChannels[i], MacLowLevelPCMDriver::kEnqueue);
|
||||
_sdrv->callback(_musChannels[i], MacLowLevelPCMDriver::kEnqueue, 1 << i, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::stopActiveSound() {
|
||||
if (_sndChannel) {
|
||||
_sdrv->quiet(_sndChannel, MacLowLevelPCMDriver::kImmediate);
|
||||
_sdrv->flush(_sndChannel, MacLowLevelPCMDriver::kImmediate);
|
||||
}
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
|
||||
if (_musChannels[i]) {
|
||||
_sdrv->quiet(_musChannels[i], MacLowLevelPCMDriver::kImmediate);
|
||||
_sdrv->flush(_musChannels[i], MacLowLevelPCMDriver::kImmediate);
|
||||
}
|
||||
}
|
||||
|
||||
_chanPlaying = 0;
|
||||
_curSound = 0;
|
||||
_curSoundSaveVar = 0;
|
||||
_loader->unblock();
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::setupChannels() {
|
||||
static const byte synthType[] = { 0x00, 0x01, 0x02, 0x04, 0x04, 0x04, 0x02, 0x04, 0x04, 0x04 };
|
||||
static const byte numChan[] = { 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x04, 0x04 };
|
||||
static const byte attrib[] = { 0x00, 0x00, 0x04, 0xAC, 0xA4, 0xA0, 0x04, 0xAC, 0xA4, 0xA0 };
|
||||
|
||||
if (!_defaultChanConfig)
|
||||
return;
|
||||
|
||||
if (_curChanConfig != _effectiveChanConfig) {
|
||||
disposeAllChannels();
|
||||
_curChanConfig = _effectiveChanConfig;
|
||||
_curSynthType = synthType[_curChanConfig];
|
||||
_chanUse = numChan[_curChanConfig];
|
||||
_lastSndType = _curSndType;
|
||||
_loader->setSynthType(_curSynthType);
|
||||
|
||||
switch (_curSynthType) {
|
||||
case 1:
|
||||
if (_chanUse == 1 && !_sndChannel) {
|
||||
_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSquareWaveSynth, attrib[_curChanConfig], &_chanCbProc);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (_chanUse == 1 && !_sndChannel) {
|
||||
_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kWaveTableSynth, attrib[_curChanConfig], &_chanCbProc);
|
||||
} else if (_chanUse == 4) {
|
||||
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
|
||||
if (!_musChannels[i] && _loader->isInstrUsable(i + 1))
|
||||
_musChannels[i] = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kWaveTableSynth, attrib[_curChanConfig] + i, &_chanCbProc);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (_chanUse == 1 && !_sndChannel) {
|
||||
_sndChannel = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSampledSynth, attrib[_curChanConfig], &_chanCbProc);
|
||||
} else if (_chanUse == 4) {
|
||||
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
|
||||
if (!_musChannels[i] && _loader->isInstrUsable(i + 1))
|
||||
_musChannels[i] = _sdrv->createChannel(_curSndType, MacLowLevelPCMDriver::kSampledSynth, attrib[_curChanConfig], &_chanCbProc);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (_curSynthType) {
|
||||
case 1:
|
||||
if (_sndChannel)
|
||||
_sdrv->setTimbre(_sndChannel, MacLowLevelPCMDriver::kImmediate, _loader->getTimbre());
|
||||
break;
|
||||
case 2:
|
||||
if (_chanUse == 1) {
|
||||
if (_sndChannel)
|
||||
_sdrv->loadWaveTable(_sndChannel, MacLowLevelPCMDriver::kImmediate, _fourToneSynthWaveForm, _fourToneSynthWaveFormSize);
|
||||
} else {
|
||||
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
|
||||
if (_musChannels[i])
|
||||
_sdrv->loadWaveTable(_musChannels[i], MacLowLevelPCMDriver::kImmediate, _fourToneSynthWaveForm, _fourToneSynthWaveFormSize);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (_chanUse == 1) {
|
||||
if (_sndChannel && _loader->getInstrData(0))
|
||||
_sdrv->loadInstrument(_sndChannel, MacLowLevelPCMDriver::kImmediate, _loader->getInstrData(0));
|
||||
} else {
|
||||
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
|
||||
if (_musChannels[i] && _loader->getInstrData(i + 1))
|
||||
_sdrv->loadInstrument(_musChannels[i], MacLowLevelPCMDriver::kImmediate, _loader->getInstrData(i + 1));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::disposeAllChannels() {
|
||||
if (_sndChannel)
|
||||
_sdrv->disposeChannel(_sndChannel);
|
||||
_sndChannel = 0;
|
||||
|
||||
for (int i = 0; i < ARRAYSIZE(_musChannels); ++i) {
|
||||
if (_musChannels[i])
|
||||
_sdrv->disposeChannel(_musChannels[i]);
|
||||
_musChannels[i] = 0;
|
||||
}
|
||||
|
||||
_curChanConfig = 0;
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::updateDisabledState() {
|
||||
if (_disableFlags == 0) {
|
||||
if (_curSoundSaveVar)
|
||||
startSound(_curSoundSaveVar);
|
||||
} else {
|
||||
int sound = _curSoundSaveVar;
|
||||
stopActiveSound();
|
||||
_curSoundSaveVar = sound;
|
||||
}
|
||||
}
|
||||
|
||||
void LoomMonkeyMacSnd::detectQuality() {
|
||||
if (_machineRating == 0) {
|
||||
if (!_loader->ignoreMachineRating()) {
|
||||
if (isSoundCardType10())
|
||||
_machineRating = 2;
|
||||
/*else if (0)
|
||||
_machineRating = 1;*/
|
||||
}
|
||||
_machineRating ^= 3;
|
||||
}
|
||||
|
||||
_defaultChanConfig = _effectiveChanConfig = _chanConfigTable[_machineRating * 3 + _selectedQuality];
|
||||
_curChanConfig = 0;
|
||||
disposeAllChannels();
|
||||
setupChannels();
|
||||
_chanConfigTable[_machineRating * 3 + _selectedQuality] = _defaultChanConfig;
|
||||
}
|
||||
|
||||
bool LoomMonkeyMacSnd::isSoundCardType10() const {
|
||||
return _mixerThread ? (_machineRating == 1) : (_vm->VAR_SOUNDCARD != 0xff && _vm->VAR(_vm->VAR_SOUNDCARD) == 10);
|
||||
}
|
||||
|
||||
#undef ASC_DEVICE_RATE
|
||||
#undef PCM_BUFFER_SIZE
|
||||
|
||||
} // End of namespace Scumm
|
||||
Reference in New Issue
Block a user