/* 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 . * */ #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 > _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 *_chanInstr[5]; const Common::SharedPtr *_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 *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 *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(nm.size(), sizeof(tag))); uint32 id = _useInstrTag ? FROM_BE_32(tag) : ids[i]; _instruments.push_back(Common::SharedPtr(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 *LoomMacSndLoader::fetchInstrument(uint16 id) const { Common::Array >::const_iterator instr = _instruments.end(); for (Common::Array >::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 *MonkeyMacSndLoader::fetchInstrument(const byte *data, uint32 dataSize, uint32 tagOrOffset) { Common::Array >::const_iterator instr = _instruments.end(); if (tagOrOffset & ~0x7fffff) { for (Common::Array >::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(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::_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::open(ScummEngine *vm, Audio::Mixer *mixer) { Common::SharedPtr scp = nullptr; if (_inst == nullptr) { scp = Common::SharedPtr(new LoomMonkeyMacSnd(vm, mixer)); _inst = new Common::WeakPtr(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(_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