Files
scummvm-cursorfix/engines/scumm/players/player_mac_loom_monkey.cpp
2026-02-02 04:50:13 +01:00

904 lines
29 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "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 &note, 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 &note, 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 &note, 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 &note, 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 &note, 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