/* 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 "common/macresman.h" #include "common/punycode.h" #include "scumm/detection.h" #include "scumm/file.h" #include "scumm/scumm.h" #include "scumm/players/mac_sound_lowlevel.h" #include "scumm/players/player_mac_new.h" #include "scumm/imuse/drivers/macintosh.h" #include "scumm/imuse/imuse.h" namespace IMSMacintosh { using namespace Scumm; enum : byte { kRhythmPart = 9 }; struct ChanControlNode; struct DeviceChannel { DeviceChannel(const uint32 *pitchtable) : pitchTable(pitchtable), frequency(0), phase(0), end(nullptr), pos(nullptr), smpBuffStart(nullptr), smpBuffEnd(nullptr), loopStart(nullptr), loopEnd(nullptr), pitch(0), mute(true), release(false), instr(nullptr), rhtm(false), prog(0), baseFreq(0), note(0), volumeL(0), volumeR(0), rate(0), totalLevelL(0), totalLevelR(0), node(nullptr), prev(nullptr), next(nullptr) {} ~DeviceChannel() {} void recalcFrequency(); uint32 frequency; uint32 phase; const byte *end; const byte *pos; const byte *smpBuffStart; const byte *smpBuffEnd; const byte *loopStart; const byte *loopEnd; uint16 pitch; byte volumeL; byte volumeR; byte totalLevelL; byte totalLevelR; byte baseFreq; uint32 rate; uint16 prog; byte note; bool rhtm; bool mute; bool release; const ChanControlNode *node; Common::SharedPtr instr; const uint32 *pitchTable; DeviceChannel *prev; DeviceChannel *next; }; class IMSMacSoundSystem : public VblTaskClientDriver, public MacLowLevelPCMDriver::CallbackClient { public: IMSMacSoundSystem(Audio::Mixer *mixer, byte version); virtual ~IMSMacSoundSystem(); bool init(const char *const *instrFileNames, int numInstrFileNames, bool stereo, bool internal16Bit); void deinit(); virtual bool start() = 0; void stop(); virtual void setQuality(int qual) = 0; void noteOn(const ChanControlNode *node); void noteOff(const ChanControlNode *node); void voiceOff(const ChanControlNode *node); void setVolume(const ChanControlNode *node); void setPitchBend(const ChanControlNode *node); void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); void vblCallback() override; void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override; const MacSoundDriver::Status &getDriverStatus(Audio::Mixer::SoundType type) const override; void dblBuffCallback(MacLowLevelPCMDriver::DoubleBuffer *dblBuffer) override; void setMasterVolume(Audio::Mixer::SoundType type, uint16 volume); protected: bool setupResourceFork(Common::MacResManager &rm, const char *const *fileNames, int numFileNames); Common::SharedPtr getSndResource(uint16 id); void fillPitchTable(); Common::Array > _smpResources; MacLowLevelPCMDriver::ChanHandle _musicChan; MacLowLevelPCMDriver::ChanHandle _sfxChan; MacLowLevelPCMDriver *_sdrv; bool _internal16Bit; bool _stereo; uint16 _feedBufferSize; uint16 _defaultInstrID; byte _quality; const uint16 _numChannels; Audio::Mixer *_mixer; DeviceChannel **_channels; private: virtual bool loadInstruments(const char *const *fileNames, int numFileNames) = 0; virtual void setInstrument(DeviceChannel *chan) = 0; virtual void recalcFrequency(DeviceChannel *chan) = 0; virtual void recalcVolume(DeviceChannel *chan) = 0; virtual void noteOffIntern(DeviceChannel *chan) = 0; virtual DeviceChannel *allocateChannel(const ChanControlNode *node) = 0; uint32 *_pitchTable; byte *_ampTable; int16 *_mixTable; int16 *_mixBuffer16Bit; MacPlayerAudioStream *_macstr; Audio::SoundHandle _soundHandle; MacPlayerAudioStream::CallbackProc _vblTskProc; void *_timerParam; Common::TimerManager::TimerProc _timerProc; const byte _version; }; class DJMSoundSystem final : public IMSMacSoundSystem { public: DJMSoundSystem(Audio::Mixer *mixer); ~DJMSoundSystem() override {} bool start() override; void setQuality(int qual) override; private: bool loadInstruments(const char *const *fileNames, int numFileNames) override; void setInstrument(DeviceChannel *chan) override; void recalcFrequency(DeviceChannel *chan) override; void recalcVolume(DeviceChannel *chan) override; void noteOffIntern(DeviceChannel *chan) override; DeviceChannel *allocateChannel(const ChanControlNode *node) override; MacLowLevelPCMDriver::DBCallback _dbCbProc; }; class NewMacSoundSystem final : public IMSMacSoundSystem { public: NewMacSoundSystem(ScummEngine *vm, Audio::Mixer *mixer); ~NewMacSoundSystem() override; bool start() override; void setQuality(int qual) override {} private: bool loadInstruments(const char *const *fileNames, int numFileNames) override; Common::SharedPtr getNoteRangeSndResource(uint16 id, byte note); void setInstrument(DeviceChannel *chan) override; void recalcFrequency(DeviceChannel *chan) override; void recalcVolume(DeviceChannel *chan) override; void noteOffIntern(DeviceChannel *chan) override; DeviceChannel *allocateChannel(const ChanControlNode *node) override; MacLowLevelPCMDriver::DBCallback _dbCbProc; ScummFile *_fileMan; Common::Path &_container; Common::Path _dummy; struct Instrument { Instrument(uint16 resId) : id(resId), noteSmplsMapping(nullptr) { noteSmplsMapping = new byte[128](); memset(noteSmplsMapping, 0xFF, 128); } ~Instrument() { delete[] noteSmplsMapping; } uint16 id; byte *noteSmplsMapping; Common::Array > sndRes; }; Common::Array > _instruments; }; class IMuseChannel_Macintosh final : public MidiChannel { public: IMuseChannel_Macintosh(IMuseDriver_Macintosh *drv, int number); ~IMuseChannel_Macintosh() override {} MidiDriver *device() override { return _drv; } byte getNumber() override { return _number; } bool allocate(); void release() override { _allocated = false; } void send(uint32 b) override { if (_drv) _drv->send((b & ~0x0F) | _number); } // Regular messages void noteOff(byte note) override; void noteOn(byte note, byte velocity) override; void controlChange(byte control, byte value) override; void programChange(byte program) override; void pitchBend(int16 bend) override; // Control Change and SCUMM specific functions void pitchBendFactor(byte value) override { pitchBend(0); _pitchBendSensitivity = value; } void transpose(int8 value) override { _transpose = value; pitchBend(_pitchBendSet); } void detune(int16 value) override { _detune = value; pitchBend(_pitchBendSet); } void priority(byte value) override { _prio = value; } void sustain(bool value) override; void bankSelect(byte bank) override { _bank = bank; } void allNotesOff() override; void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override {} private: void dataEntry(byte value); void updateVolume(); bool _allocated; byte _prio; byte _bank; byte _volume; byte _panPos; int16 _detune; int8 _transpose; byte _polyphony; byte _usage; bool _overuse; int16 _pitchBendSet; byte _pitchBendSensitivity; int16 _pitchBendEff; uint16 _pitchBendRange; byte _rpn; bool _sustain; uint16 _prog; const byte _number; ChanControlNode *allocateNode(int prio); ChanControlNode **_channels; ChanControlNode *_rtmChannel; ChanControlNode *_out; static byte _allocCur; IMuseDriver_Macintosh *_drv; IMSMacSoundSystem *_device; const byte _numChannels; const int8 _version; }; struct ChanControlNode { ChanControlNode(byte num) : in(nullptr), number(num), note(0), sustain(false), rhythmPart(false), prio(0x7F), volume(0x7F), panPos(0x40), velocity(0), pitchBend(0), prog(0), prev(nullptr), next(nullptr) {} IMuseChannel_Macintosh *in; const byte number; bool sustain; bool rhythmPart; byte prio; byte note; byte volume; byte panPos; byte velocity; int16 pitchBend; uint16 prog; ChanControlNode *prev; ChanControlNode *next; }; void connect(ChanControlNode *&chain, ChanControlNode *node) { if (!node || node->prev || node->next) return; if ((node->next = chain)) chain->prev = node; chain = node; } void disconnect(ChanControlNode *&chain, ChanControlNode *node) { if (!node || !chain) return; const ChanControlNode *ch = chain; while (ch && ch != node) ch = ch->next; if (!ch) return; if (node->next) node->next->prev = node->prev; if (node->prev) node->prev->next = node->next; else chain = node->next; node->in = nullptr; node->next = node->prev = nullptr; } void DeviceChannel::recalcFrequency() { int cpos = (pitch >> 7) + 60 - baseFreq; if (cpos < 0) frequency = (uint32)-1; else if ((pitch & 0x7F) && cpos < 0x7F) frequency = pitchTable[cpos] + (((pitchTable[cpos + 1] - pitchTable[cpos]) * (pitch & 0x7F)) >> 7); else frequency = pitchTable[cpos]; } IMSMacSoundSystem::IMSMacSoundSystem(Audio::Mixer *mixer, byte version) : VblTaskClientDriver(), _mixer(mixer), _macstr(nullptr), _sdrv(nullptr), _vblTskProc(this, &VblTaskClientDriver::vblCallback), _musicChan(0), _sfxChan(0), _quality(22), _feedBufferSize(1024), _channels(nullptr), _timerParam(nullptr), _timerProc(nullptr), _pitchTable(nullptr), _ampTable(nullptr), _mixTable(nullptr), _numChannels(version ? 12 : 8), _defaultInstrID(0), _version(version), _stereo(false) { _pitchTable = new uint32[128](); assert(_pitchTable); _channels = new DeviceChannel*[_numChannels]; assert(_channels); for (int i = 0; i < _numChannels; ++i) _channels[i] = new DeviceChannel(_pitchTable); _mixBuffer16Bit = new int16[_feedBufferSize](); assert(_mixBuffer16Bit); fillPitchTable(); _ampTable = new byte[8192](); assert(_ampTable); _mixTable = new int16[_numChannels << 8](); assert(_mixTable); } IMSMacSoundSystem::~IMSMacSoundSystem() { deinit(); if (_channels) { for (int i = 0; i < _numChannels; ++i) delete _channels[i]; delete[] _channels; } delete[] _pitchTable; delete[] _mixTable; delete[] _mixBuffer16Bit; } bool IMSMacSoundSystem::init(const char *const *instrFileNames, int numInstrFileNames, bool stereo, bool internal16Bit) { if (!loadInstruments(instrFileNames, numInstrFileNames)) return false; _internal16Bit = internal16Bit; _stereo = (_version) > 0 && stereo; uint16 feedBufferSize = _stereo ? 2048 : 1024; if (_feedBufferSize != feedBufferSize) { delete[] _mixBuffer16Bit; _feedBufferSize = feedBufferSize; _mixBuffer16Bit = new int16[_feedBufferSize](); assert(_mixBuffer16Bit); } static const byte ampTableV0[32] = { 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x06, 0x08, 0x0b, 0x0d, 0x10, 0x13, 0x16, 0x1a, 0x1e, 0x22, 0x26, 0x2b, 0x30, 0x35, 0x3a, 0x40, 0x46, 0x4c, 0x53, 0x59, 0x60, 0x68, 0x6f, 0x77, 0x7f }; byte *dst = _ampTable; for (int i = 0; i < 32; ++i) { byte mul = (_version == 0) ? ampTableV0[i] : i << 2; for (int ii = 0; ii < 256; ++ii) *dst++ = (((ii - 128) * mul) / 127) + 128; } uint32 m = _numChannels << 7; int16 *d1 = &_mixTable[m]; int16 *d2 = &_mixTable[m - 1]; if (_version == 0) { byte sh = _internal16Bit ? 5 : 0; byte div = _internal16Bit ? 1 : _numChannels; byte base = _internal16Bit? 0 : 128; for (uint32 i = 0; i < m; ++i) { uint16 val = (i << sh) / div; *d1++ = base + val; *d2-- = base - val - 1; } } else if (_internal16Bit) { for (uint32 i = 0; i < m; ++i) { uint16 val = (((i * _numChannels * 127) << 6) / (((_numChannels >> 1) - 1) * i + _numChannels * 127)) << 1; *d1++ = val; *d2-- = -val - 1; } } else { for (uint32 i = 0; i < m; ++i) { uint16 val = ((((i * _numChannels * 127) << 7) >> 1) / (((_numChannels >> 1) - 1) * i + _numChannels * 127) + 128) >> 7; *d1++ = (byte)(128 + val); *d2-- = (byte)(128 - val - 1); } } _macstr = new MacPlayerAudioStream(this, _mixer->getOutputRate(), _stereo, false, internal16Bit); if (!_macstr) return false; // 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. _sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), _mixer->getOutputRate() << 16/*ASC_DEVICE_RATE*/, internal16Bit); if (!_sdrv) return false; _macstr->initBuffers(1024); _macstr->addVolumeGroup(Audio::Mixer::kMusicSoundType); // Only MI2 and FOA have MIDI sound effects. Also, the later versions use a different update method. if (_version == 0) { _macstr->addVolumeGroup(Audio::Mixer::kSFXSoundType); _macstr->setVblCallback(&_vblTskProc); } // The stream will by default expect 8-bit data and scale that to 16-bit. Or at least only low amplitude 16-bit data that only needs minor adjustment. // For full range 16-bit input, we need some post-process downscaling, otherwise the application of the ScummVM global volume setting will cause overflows. if (internal16Bit) _macstr->scaleVolume(0, 5); _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _macstr, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); return true; } void IMSMacSoundSystem::deinit() { Common::StackLock lock(_mixer->mutex()); stop(); _mixer->stopHandle(_soundHandle); _timerProc = nullptr; delete _macstr; _macstr = nullptr; delete _sdrv; _sdrv = nullptr; delete[] _ampTable; _ampTable = nullptr; } void IMSMacSoundSystem::stop() { Common::StackLock lock(_mixer->mutex()); if (_sdrv) { _sdrv->disposeChannel(_musicChan); if (_sfxChan) _sdrv->disposeChannel(_sfxChan); } } void IMSMacSoundSystem::noteOn(const ChanControlNode *node) { assert(node); Common::StackLock lock(_mixer->mutex()); DeviceChannel *c = allocateChannel(node); if (!c) return; setInstrument(c); if (c->instr == nullptr) return; debug(6, "NOTE ON: ims part %d, chan node %d, note %d, instr id %d (%s)", node->in ? node->in->getNumber() : node->number, node->number, node->note, node->prog, c->instr.get()->name()); // SAMNMAX: This is a workaround to fix a hanging note in the final credits track. The issue is present in the original game,too. bool fixHangingNote = (c->instr.get()->id() == 321 && c->note == 40); if (fixHangingNote) debug(7, "%s:() Triggered hanging note workaround.", __FUNCTION__); recalcVolume(c); const MacLowLevelPCMDriver::PCMSound *s = c->instr.get()->data(); c->baseFreq = !node->rhythmPart ? s->baseFreq : 60; c->rate = s->rate; c->smpBuffStart = s->data.get(); c->smpBuffEnd = c->smpBuffStart + s->len; if ((_version == 0 && (int32)s->loopst >= (int32)s->loopend - 12) || (_version > 0 && (fixHangingNote || !s->loopst || !s->loopend || node->rhythmPart || (int32)s->loopst > (int32)s->len - 10))) { c->loopStart = nullptr; c->loopEnd = c->smpBuffEnd; } else { c->loopStart = c->smpBuffStart + s->loopst; c->loopEnd = c->smpBuffStart + s->loopend; } c->pitch = (node->note << 7) + node->pitchBend; c->mute = c->release = false; c->end = c->loopEnd; c->pos = c->smpBuffStart; c->phase = 0; recalcFrequency(c); } void IMSMacSoundSystem::noteOff(const ChanControlNode *node) { Common::StackLock lock(_mixer->mutex()); for (int i = 0; i < _numChannels; ++i) { DeviceChannel *c = _channels[i]; if (c->node == node) { noteOffIntern(c); c->node = nullptr; } } } void IMSMacSoundSystem::voiceOff(const ChanControlNode *node) { Common::StackLock lock(_mixer->mutex()); for (int i = 0; i < _numChannels; ++i) { DeviceChannel *c = _channels[i]; if (c->node == node) { c->mute = true; c->release = false; c->node = nullptr; } } } void IMSMacSoundSystem::setVolume(const ChanControlNode *node) { Common::StackLock lock(_mixer->mutex()); for (int i = 0; i < _numChannels; ++i) { DeviceChannel *c = _channels[i]; if (c->node == node) recalcVolume(c); } } void IMSMacSoundSystem::setPitchBend(const ChanControlNode *node) { Common::StackLock lock(_mixer->mutex()); for (int i = 0; i < _numChannels; ++i) { DeviceChannel *c = _channels[i]; if (c->node == node) { c->pitch = (node->note << 7) + node->pitchBend; recalcFrequency(c); } } } void IMSMacSoundSystem::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { _timerParam = timerParam; _timerProc = timerProc; } void IMSMacSoundSystem::vblCallback() { if (_timerProc) _timerProc(_timerParam); } void IMSMacSoundSystem::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 &IMSMacSoundSystem::getDriverStatus(Audio::Mixer::SoundType type) const { return _sdrv->getStatus(type); } void IMSMacSoundSystem::dblBuffCallback(MacLowLevelPCMDriver::DoubleBuffer *dblBuffer) { if (_version > 0 && _timerProc) _timerProc(_timerParam); uint16 sil = 0; memset(_mixBuffer16Bit, 0, _feedBufferSize * sizeof(uint16)); uint16 frameSize = _stereo ? 2 : 1; for (int i = 0; i < _numChannels; ++i) { DeviceChannel &c = *_channels[i]; if (c.release) { c.totalLevelL = (c.totalLevelL > 12) ? c.totalLevelL - 12 : 0; c.totalLevelR = (c.totalLevelR > 12) ? c.totalLevelR - 12 : 0; if (c.totalLevelL == 0 && c.totalLevelR == 0) { c.release = false; c.mute = true; } } if (c.mute || c.frequency == (uint32)-1 || // This is our "trick" for telling apart music and sfx in MI2/INDY4: We look at the sound bank that // is currently in use, the musical one (1000 - 1127) or the sfx one (2000 - 2255). Unfortunately, // most sound effects seem to use the musical bank, though. So, in the end we can't distinguish all that much... (_version == 0 && ((dblBuffer->chanHandle == _musicChan && c.instr.get()->id() >= 2000) || (dblBuffer->chanHandle == _sfxChan && c.instr.get()->id() < 2000)))) { ++sil; continue; } const byte *a1 = &_ampTable[(c.totalLevelL & ~3) << 6]; const byte *a2 = &_ampTable[(c.totalLevelR & ~3) << 6]; int16 *t = _mixBuffer16Bit; for (int ii = 0; ii < _feedBufferSize; ii += frameSize) { if (!c.pos) { *t++ += 0x80; if (_stereo) *t++ += 0x80; continue; } c.phase += c.frequency; if (c.phase >> 16) { c.pos += (c.phase >> 16); c.phase &= 0xFFFF; if (c.loopEnd <= c.pos) { if (c.loopStart) { c.pos -= (c.loopEnd - c.loopStart); } else { c.pos = nullptr; c.mute = true; c.release = false; ii -= frameSize; continue; } } } *t++ += a1[*c.pos]; if (_stereo) *t++ += a2[*c.pos]; } } const int16 *s = _mixBuffer16Bit; sil <<= 7; if (_internal16Bit) { int16 *d = reinterpret_cast(dblBuffer->data); for (int i = 0; i < _feedBufferSize; ++i) *d++ = _mixTable[sil + *s++]; } else { byte *d = dblBuffer->data; for (int i = 0; i < _feedBufferSize; ++i) *d++ = _mixTable[sil + *s++]; } dblBuffer->numFrames = _feedBufferSize / frameSize; dblBuffer->flags |= MacLowLevelPCMDriver::DoubleBuffer::kBufferReady; } void IMSMacSoundSystem::setMasterVolume(Audio::Mixer::SoundType type, uint16 volume) { if (_macstr) _macstr->setMasterVolume(type, volume); } bool IMSMacSoundSystem::setupResourceFork(Common::MacResManager &rm, const char *const *fileNames, int numFileNames) { const Common::CodePage tryCodePages[] = { Common::kMacRoman, Common::kISO8859_1 }; Common::Path resFile; for (int i = 0; resFile.empty() && i < numFileNames && fileNames[i] != nullptr; ++i) { for (int ii = 0; resFile.empty() && ii < ARRAYSIZE(tryCodePages); ++ii) { Common::U32String fn(fileNames[i], tryCodePages[ii]); resFile = Common::Path(fn.encode(Common::kUtf8)); if (!rm.exists(resFile) || !rm.open(resFile) || !rm.hasResFork()) { rm.close(); resFile = Common::Path(Common::punycode_encodefilename(fn)); if (!rm.exists(resFile) || !rm.open(resFile) || !rm.hasResFork()) { rm.close(); resFile.clear(); } } } } if (resFile.empty()) { warning("%s(): Resource fork not found", __FUNCTION__); return false; } return true; } Common::SharedPtr IMSMacSoundSystem::getSndResource(uint16 id) { Common::SharedPtr res; Common::SharedPtr *def = nullptr; for (Common::Array >::iterator it = _smpResources.begin(); res == nullptr && it != _smpResources.end(); ++it) { uint16 cid = (*it)->id(); if (cid == id) res = *it; else if (cid == _defaultInstrID) def = it; } if (res == nullptr) { if (def != nullptr) res = *def; else error("%s(): Failure (instrument id %d)", __FUNCTION__, id); } return res; } void IMSMacSoundSystem::fillPitchTable() { static const double ptbl[12] = { 1664510.645469, 1763487.599042, 1868350.028545, 1979447.902589, 2097152.000000, 2221855.147262, 2353973.529536, 2493948.079642, 2642245.949629, 2799362.069852, 2965820.800758, 3142177.682886 }; for (int i = 0; i < 12; ++i) _pitchTable[116 + i] = (uint32)trunc(ptbl[i]); for (int i = 115; i >= 0; --i) _pitchTable[i] = _pitchTable[i + 12] >> 1; if (_quality != 11) return; for (int i = 0; i < 128; ++i) _pitchTable[i] <<= 1; } DJMSoundSystem::DJMSoundSystem(Audio::Mixer *mixer) : IMSMacSoundSystem(mixer, 0), _dbCbProc(this, &MacLowLevelPCMDriver::CallbackClient::dblBuffCallback) { _defaultInstrID = 999; } bool DJMSoundSystem::start() { _musicChan = _sdrv->createChannel(Audio::Mixer::kMusicSoundType, MacLowLevelPCMDriver::kSampledSynth, 0x8C, nullptr); _sfxChan = _sdrv->createChannel(Audio::Mixer::kSFXSoundType, MacLowLevelPCMDriver::kSampledSynth, 0x8C, nullptr); if (!_musicChan || !_sfxChan) return false; uint32 rate = 0; switch (_quality) { case 5: rate = 0x15BBA2E8; break; case 7: rate = 0x1CFA2E8B; break; case 11: rate = 0x2B7745D1; break; case 22: rate = 0x56EE8BA3; break; default: warning("%s(): Invalid quality setting %d", __FUNCTION__, _quality); return false; } return _sdrv->playDoubleBuffer(_musicChan, 1, _internal16Bit ? 16 : 8, rate, &_dbCbProc, _internal16Bit ? _numChannels : 1) && _sdrv->playDoubleBuffer(_sfxChan, 1, _internal16Bit ? 16 : 8, rate, &_dbCbProc, _internal16Bit ? _numChannels : 1); } void DJMSoundSystem::setQuality(int qual) { Common::StackLock lock(_mixer->mutex()); qual = qual > 1 ? 22 : 11; if (qual == _quality) return; _quality = qual; _feedBufferSize = (qual == 22) ? 1024 : 512; fillPitchTable(); for (int i = 0; i < _numChannels; ++i) _channels[i]->recalcFrequency(); stop(); if (!start()) error("%s(): Unknown error", __FUNCTION__); } bool DJMSoundSystem::loadInstruments(const char *const *fileNames, int numFileNames) { Common::MacResManager resMan; if (!setupResourceFork(resMan, fileNames, numFileNames)) return false; _smpResources.clear(); Common::MacResIDArray ids = resMan.getResIDArray(MKTAG('s', 'n', 'd', ' ')); for (Common::MacResIDArray::const_iterator i = ids.begin(); i != ids.end(); ++i) { Common::SeekableReadStream *str = resMan.getResource(MKTAG('s', 'n', 'd', ' '), *i); uint16 type = str ? str->readUint16BE() : 0; if (type == 1 || type == 2) _smpResources.push_back(Common::SharedPtr(new MacSndResource(*i, str, resMan.getResName(MKTAG('s', 'n', 'd', ' '), *i)))); delete str; } return !_smpResources.empty(); } void DJMSoundSystem::setInstrument(DeviceChannel *chan) { assert(chan && chan->node); if (chan->instr == nullptr || chan->prog != chan->node->prog) { chan->instr = getSndResource(chan->node->prog); chan->prog = chan->node->prog; } } void DJMSoundSystem::recalcFrequency(DeviceChannel *chan) { assert(chan); chan->recalcFrequency(); if (chan->instr != nullptr) chan->mute = false; } void DJMSoundSystem::recalcVolume(DeviceChannel *c) { assert(c && c->node); c->totalLevelL = c->node->volume; if (c->instr != nullptr) c->mute = false; } void DJMSoundSystem::noteOffIntern(DeviceChannel *chan) { if (chan->loopStart) chan->mute = true; chan->note = 0; } DeviceChannel *DJMSoundSystem::allocateChannel(const ChanControlNode *node) { assert(node && node->number < _numChannels); DeviceChannel *c = _channels[node->number]; c->node = node; return c; } NewMacSoundSystem::NewMacSoundSystem(ScummEngine *vm, Audio::Mixer *mixer) : IMSMacSoundSystem(mixer, 1), // It is sufficient to distinguish version 0 and 1 here. _fileMan(nullptr), _container(vm ? vm->_containerFile : _dummy), _dbCbProc(this, &MacLowLevelPCMDriver::CallbackClient::dblBuffCallback) { assert(vm); _defaultInstrID = 0xFFFF; _fileMan = new ScummFile(vm); } NewMacSoundSystem::~NewMacSoundSystem() { delete _fileMan; } bool NewMacSoundSystem::start() { _musicChan = _sdrv->createChannel(Audio::Mixer::kMusicSoundType, MacLowLevelPCMDriver::kSampledSynth, _stereo ? 0xCC : 0x8C, nullptr); return _musicChan ? _sdrv->playDoubleBuffer(_musicChan, _stereo ? 2 : 1, _internal16Bit ? 16 : 8, 0x56220000, &_dbCbProc, _internal16Bit ? _numChannels : 1) : false; } bool NewMacSoundSystem::loadInstruments(const char *const *fileNames, int numFileNames) { if ((_container.empty() && !_fileMan->open(fileNames[0])) || (!_container.empty() && (!_fileMan->open(_container) || !_fileMan->openSubFile(fileNames[0])))) return false; uint32 num = _fileMan->readUint32BE(); uint32 size = _fileMan->readUint32BE(); byte *buff = new byte[size](); _fileMan->read(buff, size); _fileMan->close(); _smpResources.clear(); const byte *s = buff; for (uint i = 0; i < num; ++i) { uint32 offset = READ_BE_UINT32(s + 4); uint32 resSize = (i < num - 1 ? READ_BE_UINT32(s + 12) : size) - offset; _smpResources.push_back(Common::SharedPtr(new MacSndResource(READ_BE_UINT16(s + 2), buff + offset, resSize))); s += 8; } delete[] buff; if (_smpResources.empty()) return false; Common::MacResManager resMan; if (!setupResourceFork(resMan, &fileNames[1], numFileNames - 1)) return false; Common::MacResIDArray instIDs = resMan.getResIDArray(MKTAG('I', 'N', 'S', 'T')); for (Common::MacResIDArray::const_iterator i = instIDs.begin(); i != instIDs.end(); ++i) { Common::SeekableReadStream *str = resMan.getResource(MKTAG('I', 'N', 'S', 'T'), *i); if (!str) error("%s(): Failed to load instrument resource (type 'INST', id '%d')", __FUNCTION__, *i); uint sz = str->size(); byte *b = new byte[sz](); str->read(b, sz); delete str; Instrument *ins = new Instrument(*i); ins->sndRes.push_back(getSndResource(READ_BE_UINT16(b))); if (ins->sndRes[0] != nullptr) memset(ins->noteSmplsMapping, 0, 128); byte numRanges = CLIP(b[13], 0, 7); assert(sz >= 16u + numRanges * 8u); for (int ii = 0; ii < numRanges; ++ii) { ins->sndRes.push_back(getSndResource(READ_BE_INT16(b + 16 + ii * 8))); if (ins->sndRes.back() != nullptr) { byte rl = b[14 + ii * 8]; byte rh = b[15 + ii * 8]; assert(rl < 128 && rh < 128); for (int iii = rl; iii <= rh; ++iii) ins->noteSmplsMapping[iii] = ii + 1; } } delete[] b; _instruments.push_back(Common::SharedPtr(ins)); } return !_instruments.empty(); } Common::SharedPtr NewMacSoundSystem::getNoteRangeSndResource(uint16 id, byte note) { assert(note < 128); Common::SharedPtr res; for (Common::Array >::const_iterator it = _instruments.begin(); res == nullptr && it != _instruments.end(); ++it) { uint16 cid = (*it)->id; if (cid == id) res = (*it)->sndRes[(*it)->noteSmplsMapping[note]]; } return res; } void NewMacSoundSystem::setInstrument(DeviceChannel *chan) { assert(chan && chan->node); if (chan->instr == nullptr || chan->node->rhythmPart != chan->rhtm || (!chan->node->rhythmPart && chan->prog != chan->node->prog) || chan->note != chan->node->note) { chan->note = chan->node->note; chan->prog = chan->node->rhythmPart ? 0 : chan->node->prog; chan->rhtm = chan->node->rhythmPart; chan->instr = chan->node->rhythmPart ? getSndResource(6000 + chan->note) : getNoteRangeSndResource(chan->prog, chan->note); } } void NewMacSoundSystem::recalcFrequency(DeviceChannel *chan) { assert(chan && chan->node); if (chan->node->rhythmPart) chan->frequency = 0x8000; else chan->recalcFrequency(); chan->frequency = MacLowLevelPCMDriver::calcRate(0x56220000, chan->rate , chan->frequency); //if (chan->frequency == (uint32)-1) // error("%s(): Frequency calculation failed", __FUNCTION__); } void NewMacSoundSystem::recalcVolume(DeviceChannel *chan) { assert(chan && chan->node); static const byte volumeTable[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17, 0x18, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2d, 0x2e, 0x2f, 0x31, 0x32, 0x33, 0x35, 0x36, 0x38, 0x39, 0x3b, 0x3c, 0x3e, 0x40, 0x42, 0x43, 0x45, 0x47, 0x49, 0x4b, 0x4d, 0x4f, 0x51, 0x53, 0x56, 0x58, 0x5a, 0x5d, 0x5f, 0x62, 0x64, 0x67, 0x6a, 0x6c, 0x6f, 0x72, 0x75, 0x78, 0x7c, 0x7f }; if (_stereo) { chan->volumeL = (volumeTable[127 - (chan->node->panPos >> 2)] * chan->node->volume) >> 7; chan->volumeR = (volumeTable[96 + (chan->node->panPos >> 2)] * chan->node->volume) >> 7; } else { chan->volumeL = chan->node->volume; } static const byte veloTable[] = { 0x41, 0x43, 0x45, 0x46, 0x47, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x53, 0x54, 0x55, 0x55, 0x56, 0x57, 0x57, 0x58, 0x58, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x63, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68, 0x68, 0x69, 0x69, 0x6a, 0x6b, 0x6b, 0x6c, 0x6d, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7f }; if (chan->release) return; chan->totalLevelL = volumeTable[(veloTable[chan->node->velocity] * chan->volumeL) >> 7]; chan->totalLevelR = volumeTable[(veloTable[chan->node->velocity] * chan->volumeR) >> 7]; } void NewMacSoundSystem::noteOffIntern(DeviceChannel *chan) { assert(chan && chan->node); if (!chan->node->rhythmPart) chan->release = true; chan->loopStart = nullptr; chan->loopEnd = chan->smpBuffEnd; } DeviceChannel *NewMacSoundSystem::allocateChannel(const ChanControlNode *node) { assert(node); DeviceChannel *res = nullptr; DeviceChannel *res2 = nullptr; DeviceChannel *res3 = nullptr; byte lovol = 0x7F; byte lopri = node->prio; for (byte i = 0; i < _numChannels; ++i) { DeviceChannel *c = _channels[i]; if (c->mute) { res = c; break; } if (c->release) { if (c->node && c->node->volume < lovol) { res2 = c; lovol = c->node->volume; } } if (c->node && c->node->prio < lopri) { res3 = c; lopri = c->node->prio; } } if (!res) res = res2; if (!res) res = res3; if (res) { res->release = false; res->mute = true; res->node = node; } return res; } byte IMuseChannel_Macintosh::_allocCur = 0; IMuseChannel_Macintosh::IMuseChannel_Macintosh(IMuseDriver_Macintosh *drv, int number) : MidiChannel(), _drv(drv), _number(number), _allocated(false), _polyphony(1), _usage(0), _sustain(false), _bank(0), _panPos(0x40), _pitchBendEff(0), _prio(0x80), _detune(0), _transpose(0), _pitchBendSet(0), _pitchBendSensitivity(2), _volume(0x7F), _overuse(false), _rpn(0), _pitchBendRange(0), _channels(drv ? drv->_channels : nullptr), _prog(0), _out(nullptr), _device(drv ? drv->_device : nullptr), _version(drv ? drv->_version : -1), _rtmChannel((drv && drv->_version == 1) ? drv->_channels[drv->_numChannels - 1] : nullptr), _numChannels(drv ? (drv->_version != 1 ? drv->_numChannels : drv->_numChannels - 1) : 0) { assert(_drv); assert(_channels); assert(_device); _allocCur = 0; } bool IMuseChannel_Macintosh::allocate() { if (_allocated) return false; _usage = 0; _overuse = false; return (_allocated = true); } void IMuseChannel_Macintosh::noteOff(byte note) { for (ChanControlNode *node = (_version == 1 && _number == kRhythmPart) ? _rtmChannel : _out; node; ) { ChanControlNode *n = node->next; if (node->note == note) { if (_sustain && node != _rtmChannel) { node->sustain = true; } else { _device->noteOff(node); disconnect(_out, node); if (_version == 2) _overuse = (--_usage > _polyphony); } } node = n; } } void IMuseChannel_Macintosh::noteOn(byte note, byte velocity) { ChanControlNode *node = allocateNode(_prio); if (node == nullptr) return; if (_version == 0 || _number != kRhythmPart) { node->in = this; node->prio = _prio; node->prog = _prog; node->pitchBend = _pitchBendEff; node->volume = _volume; node->rhythmPart = false; } else { if (_version == 2) { node->in = this; node->prio = _prio; } node->prog = 0; node->volume = _volume * 6 / 7; node->rhythmPart = true; } node->note = note; node->sustain = false; node->panPos = _panPos; node->velocity = velocity; if (_version == 2) _overuse = (++_usage > _polyphony); _device->noteOn(node); } void IMuseChannel_Macintosh::controlChange(byte control, byte value) { switch (control) { case 0: // The original MI2/INDY4 code doesn't have that. It will just call a different // programChange() method from the sysex handler. Only DOTT and SAMNMAX call a // bank select, but the actual selection is not implemented, since it is not needed. _bank = value; break; case 6: dataEntry(value); break; case 7: case 10: { byte ¶m = (control == 7) ? _volume : _panPos; param = value; updateVolume(); } break; case 17: if (_version == 2) _polyphony = value; else detune(value); break; case 18: priority(value); break; case 64: sustain(value); break; case 100: _rpn = value; break; case 101: _rpn = value | 0x80; break; case 123: allNotesOff(); break; default: break; } } void IMuseChannel_Macintosh::programChange(byte program) { if (_version == 0) _prog = (_bank ? 2000 : 1000) + program; else _prog = program; } void IMuseChannel_Macintosh::pitchBend(int16 bend) { _pitchBendSet = bend; if (_version == 2) bend = (((bend * _pitchBendSensitivity) >> 5) + _detune + (_transpose << 8)) << 1; // SAMNMAX formula else bend = ((bend * _pitchBendSensitivity) >> 6) + _detune + (_transpose << 7); // DOTT, INDY4 and MI2 formula if (_version > 0) { if (_version == 1) bend = CLIP(bend, -2048, 2047) << 2; if (bend > 7936) bend = 8192; else if (bend < -7936) bend = -8192; bend = (bend * _pitchBendRange) >> 13; } _pitchBendEff = bend; if (_pitchBendSet) debug(6, "PITCH BEND: ims part %d, pb para %d, pb snstvty %d, pb range %d, _detune %d, transpose %d, pb eff %d", _number, _pitchBendSet, _pitchBendSensitivity, _pitchBendRange, _detune, _transpose, _pitchBendEff); for (ChanControlNode *node = _out; node; node = node->next) { node->pitchBend = _pitchBendEff; _device->setPitchBend(node); } } void IMuseChannel_Macintosh::sustain(bool value) { _sustain = value; if (value) return; for (ChanControlNode *node = _out; node; ) { ChanControlNode *n = node->next; if (node->sustain) { _device->noteOff(node); disconnect(_out, node); if (_version == 2) _overuse = (--_usage > _polyphony); } node = n; } } void IMuseChannel_Macintosh::allNotesOff() { for (ChanControlNode *node = (_version == 1 && _number == kRhythmPart) ? _rtmChannel : _out; node; ) { _device->voiceOff(node); ChanControlNode *n = node->next; disconnect(_out, node); if (_version == 2) _overuse = (--_usage > _polyphony); node = n; } } void IMuseChannel_Macintosh::dataEntry(byte value) { uint16 m = 0x7F; uint16 v = value; if (_rpn & 0x80) { m <<= 7; v <<= 7; } if (!(_rpn & 0x7F)) { _pitchBendRange = (_pitchBendRange & ~m) + v; pitchBend(_pitchBendSet); } _rpn = 0; } void IMuseChannel_Macintosh::updateVolume() { for (ChanControlNode *node = _out; node; node = node->next) { node->panPos = _panPos; node->volume = (_version > 0 && _number == kRhythmPart) ? _volume * 6 / 7 : _volume; _device->setVolume(node); } } ChanControlNode *IMuseChannel_Macintosh::allocateNode(int prio) { if (_version > 0) { _allocCur = 0; if (_version == 1 && _number == kRhythmPart) return _rtmChannel; } if (_version < 1 && _prog == 0) return nullptr; IMuseChannel_Macintosh *best = this; ChanControlNode *res = nullptr; for (byte i = 0; i < _numChannels; ++i) { _allocCur = (_allocCur + 1) % _numChannels; ChanControlNode *node = _channels[_allocCur]; if (node->in == nullptr) { res = node; best = nullptr; break; } if ((_version == 2 && ((best->_overuse == node->in->_overuse && best->_prio >= node->prio) || (!best->_overuse && node->in->_overuse))) || (_version < 2 && (!node->next && node->in && node->in->_prio <= prio))) { res = node; best = node->in; prio = best->_prio; } } if (res && best) { if (_version == 2) best->_overuse = (--best->_usage > best->_polyphony); _device->voiceOff(res); disconnect(best->_out, res); } assert(!(_out && _out == res)); connect(_out, res); return res; } } // End of namespace IMSMacintosh namespace Scumm { using namespace IMSMacintosh; IMuseDriver_Macintosh::IMuseDriver_Macintosh(ScummEngine *vm, Audio::Mixer *mixer, byte gameID) : MidiDriver(), _isOpen(false), _device(nullptr), _imsParts(nullptr), _channels(nullptr), _numParts(32), _numChannels(8), _baseTempo(16666), _quality(1), _musicVolume(0xFFFFFFFF), _sfxVolume(0xFFFFFFFF), _version(-1) { switch (gameID) { case GID_TENTACLE: case GID_SAMNMAX: if (gameID == GID_SAMNMAX) { _version = 2; _numChannels = 12; } else { _version = 1; _numChannels = 16; } _baseTempo = 46439; _device = new NewMacSoundSystem(vm, mixer); break; case GID_INDY4: // TODO: Detect the PowerPC version. Which version should it be? if (vm->_isModernMacVersion) { _version = 1; _numChannels = 16; _baseTempo = 46439; _device = new NewMacSoundSystem(vm, mixer); } else { _version = 0; _device = new DJMSoundSystem(mixer); } break; case GID_MONKEY2: _version = 0; _device = new DJMSoundSystem(mixer); break; default: error("%s(): Unsupported game ID %d", __FUNCTION__, gameID); break; } } IMuseDriver_Macintosh::~IMuseDriver_Macintosh() { close(); delete _device; } int IMuseDriver_Macintosh::open() { if (_isOpen) return MERR_ALREADY_OPEN; if (_version < 0 || _version > 2) return MERR_DEVICE_NOT_AVAILABLE; createChannels(); static const char *const fileNames[3][4] = { { "iMUSE Setups", nullptr, nullptr, nullptr }, { "Instruments", "Day of the Tentacle Demo", "Day of the Tentacle", "Fate of Atlantis PowerPC" }, { "Instruments", "Sam & Max Demo", "Sam & Max", nullptr } }; if (!_device->init(fileNames[_version], ARRAYSIZE(fileNames[_version]), false, true) || !_device->start()) return MERR_DEVICE_NOT_AVAILABLE; _isOpen = true; for (int i = 0; i < _numParts; ++i) { _imsParts[i]->controlChange(0x64, 0x00); _imsParts[i]->controlChange(0x65, 0x00); _imsParts[i]->controlChange(0x06, 0x10); _imsParts[i]->controlChange(0x07, 0x7F); _imsParts[i]->controlChange(0x0A, 0x40); _imsParts[i]->controlChange(0x01, 0x00); _imsParts[i]->controlChange(0x40, 0x00); _imsParts[i]->controlChange(0x5B, 0x40); _imsParts[i]->controlChange(0x5D, 0x00); _imsParts[i]->controlChange(0x00, 0x00); _imsParts[i]->controlChange(0x7B, 0x00); _imsParts[i]->programChange(0); _imsParts[i]->pitchBend(0); } return 0; } void IMuseDriver_Macintosh::close() { if (!_isOpen) return; _isOpen = false; _device->deinit(); releaseChannels(); } uint32 IMuseDriver_Macintosh::property(int prop, uint32 param) { uint32 res = 0; switch (prop) { case IMuse::PROP_QUALITY: res = _quality; if (param != (uint32)-1 && param != _quality) { _quality = param; if (_device) _device->setQuality(param == MacSound::kQualityAuto || param == MacSound::kQualityHighest ? 2: 0); } break; case IMuse::PROP_MUSICVOLUME: res = _musicVolume; if (param != (uint32)-1 && param != _musicVolume) { _musicVolume = param; if (_device) _device->setMasterVolume(Audio::Mixer::kMusicSoundType, param); } break; case IMuse::PROP_SFXVOLUME: res = _sfxVolume; if (param != (uint32)-1 && param != _sfxVolume) { _sfxVolume = param; if (_device) _device->setMasterVolume(Audio::Mixer::kSFXSoundType, param); } break; default: break; } return res; } void IMuseDriver_Macintosh::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { if (_device) _device->setTimerCallback(timerParam, timerProc); } MidiChannel *IMuseDriver_Macintosh::allocateChannel() { if (!_isOpen) return nullptr; for (int i = 0; i < _numParts; ++i) { IMuseChannel_Macintosh *ch = _imsParts[i]; if (ch && !(_version > 0 && i == kRhythmPart) && ch->allocate()) return ch; } return nullptr; } MidiChannel *IMuseDriver_Macintosh::getPercussionChannel() { return (_isOpen && _version > 0) ? _imsParts[kRhythmPart] : nullptr; } void IMuseDriver_Macintosh::createChannels() { releaseChannels(); _channels = new ChanControlNode*[_numChannels]; assert(_channels); for (int i = 0; i < _numChannels; ++i) { _channels[i] = new ChanControlNode(i < kRhythmPart ? i : (i == _numChannels - 1 ? kRhythmPart : i + 1)); assert(_channels[i]); } _imsParts = new IMuseChannel_Macintosh*[_numParts]; assert(_imsParts); for (int i = 0; i < _numParts; ++i) _imsParts[i] = new IMuseChannel_Macintosh(this, i); } void IMuseDriver_Macintosh::releaseChannels() { if (_imsParts) { for (int i = 0; i < _numParts; ++i) delete _imsParts[i]; delete[] _imsParts; _imsParts = nullptr; } if (_channels) { for (int i = 0; i < _numChannels; ++i) delete _channels[i]; delete[] _channels; _channels = nullptr; } } } // End of namespace Scumm