/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h" #include "common/endian.h" #include "common/textconsole.h" #include "common/func.h" #include "common/array.h" class TownsPC98_MusicChannel { public: TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); virtual ~TownsPC98_MusicChannel(); typedef enum channelState { CHS_RECALCFREQ = 0x01, CHS_KEYOFF = 0x02, CHS_SSGOFF = 0x04, CHS_VBROFF = 0x08, CHS_ALLOFF = 0x0f, CHS_PROTECT = 0x40, CHS_EOT = 0x80 } ChannelState; virtual void reset(); virtual void loadData(uint8 *data); virtual void processEvents(); virtual void processFrequency(); virtual void fadeStep(); const uint8 _idFlag; protected: void setupVibrato(); bool processVibrato(); uint8 readReg(uint8 part, uint8 reg); void writeReg(uint8 part, uint8 reg, uint8 val); bool control_dummy(uint8 para); bool control_f2_duration(uint8 para); bool control_f3_pitchBend(uint8 para); bool control_f5_tempo(uint8 para); bool control_f6_repeatSection(uint8 para); bool control_f7_setupVibrato(uint8 para); bool control_f8_toggleVibrato(uint8 para); bool control_fa_writeReg(uint8 para); bool control_fd_jump(uint8 para); uint8 _ticksLeft; uint8 _duration; uint8 _instr; uint8 _totalLevel; uint8 _frqBlockMSB; uint16 _frequency; uint8 _block; int8 _pitchBend; uint8 _regOffset; uint8 _flags; uint8 *_dataPtr; bool _sustain; bool _fading; TownsPC98_AudioDriver *_driver; const uint8 _chanNum; static const uint8 _controlEventSize[16]; private: void keyOn(); void keyOff(); void setOutputLevel(); bool processControlEvent(uint8 cmd); bool control_f0_setPatch(uint8 para); bool control_f1_presetOutputLevel(uint8 para); bool control_f4_setOutputLevel(uint8 para); bool control_fb_incOutLevel(uint8 para); bool control_fc_decOutLevel(uint8 para); bool control_ff_endOfTrack(uint8 para); uint8 _vbrInitDelayHi; uint8 _vbrInitDelayLo; int16 _vbrModInitVal; uint8 _vbrDuration; uint8 _vbrCurDelay; int16 _vbrModCurVal; uint8 _vbrDurLeft; uint8 _algorithm; const uint8 _keyNum; const uint8 _part; typedef Common::Functor1Mem ControlEvent; Common::Array _controlEvents; }; class TownsPC98_MusicChannelSSG : public TownsPC98_MusicChannel { public: TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); virtual ~TownsPC98_MusicChannelSSG(); virtual void reset(); virtual void loadData(uint8 *data); void processEvents(); void processFrequency(); void protect(); void restore(); void fadeStep(); protected: void keyOn(); void nextShape(); void setOutputLevel(uint8 lvl); bool processControlEvent(uint8 cmd); bool control_f0_setPatch(uint8 para); bool control_f1_setTotalLevel(uint8 para); bool control_f4_setAlgorithm(uint8 para); bool control_f9_loadCustomPatch(uint8 para); bool control_fb_incOutLevel(uint8 para); bool control_fc_decOutLevel(uint8 para); bool control_ff_endOfTrack(uint8 para); uint8 _ssgTl; uint8 _ssgStep; uint8 _ssgTicksLeft; uint8 _ssgTargetLvl; uint8 _ssgStartLvl; uint8 _algorithm; static uint8 *_envPatchData; static const uint8 _envData[256]; typedef Common::Functor1Mem ControlEvent; Common::Array _controlEvents; }; class TownsPC98_SfxChannel : public TownsPC98_MusicChannelSSG { public: TownsPC98_SfxChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : TownsPC98_MusicChannelSSG(driver, regOffs, flgs, num, key, prt, id) {} virtual ~TownsPC98_SfxChannel() {} void reset(); void loadData(uint8 *data); }; #ifndef DISABLE_PC98_RHYTHM_CHANNEL class TownsPC98_MusicChannelPCM : public TownsPC98_MusicChannel { public: TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id); virtual ~TownsPC98_MusicChannelPCM(); void loadData(uint8 *data); void processEvents(); private: bool processControlEvent(uint8 cmd); bool control_f1_prcStart(uint8 para); bool control_ff_endOfTrack(uint8 para); typedef Common::Functor1Mem ControlEvent; Common::Array _controlEvents; }; #endif #define CONTROL(x) _controlEvents.push_back(new ControlEvent(this, &TownsPC98_MusicChannel::control_##x)) TownsPC98_MusicChannel::TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : _driver(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key), _part(prt), _idFlag(id), _ticksLeft(0), _algorithm(0), _instr(0), _totalLevel(0), _frqBlockMSB(0), _duration(0), _block(0), _vbrInitDelayHi(0), _vbrInitDelayLo(0), _vbrDuration(0), _vbrCurDelay(0), _vbrDurLeft(0), _pitchBend(0), _sustain(false), _fading(false), _dataPtr(0), _vbrModInitVal(0), _vbrModCurVal(0), _frequency(0) { CONTROL(f0_setPatch); CONTROL(f1_presetOutputLevel); CONTROL(f2_duration); CONTROL(f3_pitchBend); CONTROL(f4_setOutputLevel); CONTROL(f5_tempo); CONTROL(f6_repeatSection); CONTROL(f7_setupVibrato); CONTROL(f8_toggleVibrato); CONTROL(dummy); CONTROL(fa_writeReg); CONTROL(fb_incOutLevel); CONTROL(fc_decOutLevel); CONTROL(fd_jump); CONTROL(dummy); CONTROL(ff_endOfTrack); } #undef CONTROL TownsPC98_MusicChannel::~TownsPC98_MusicChannel() { for (Common::Array::iterator i = _controlEvents.begin(); i != _controlEvents.end(); ++i) delete *i; } void TownsPC98_MusicChannel::reset() { _sustain = false; _duration = 0; _fading = false; _ticksLeft = 1; _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; _totalLevel = 0; _algorithm = 0; _block = 0; _frequency = 0; _frqBlockMSB = 0; _pitchBend = 0; _vbrInitDelayHi = 0; _vbrInitDelayLo = 0; _vbrModInitVal = 0; _vbrDuration = 0; _vbrCurDelay = 0; _vbrModCurVal = 0; _vbrDurLeft = 0; } void TownsPC98_MusicChannel::loadData(uint8 *data) { _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; _ticksLeft = 1; _dataPtr = data; _totalLevel = 0x7F; uint8 *tmp = _dataPtr; for (bool loop = true; loop;) { uint8 cmd = *tmp++; if (cmd < 0xf0) { tmp++; } else if (cmd == 0xff) { if (READ_LE_UINT16(tmp)) { _driver->_looping |= _idFlag; tmp += _controlEventSize[cmd - 240]; } else loop = false; } else if (cmd == 0xf6) { // reset repeat section countdown tmp[0] = tmp[1]; tmp += 4; } else { tmp += _controlEventSize[cmd - 240]; } } } void TownsPC98_MusicChannel::processEvents() { if (_flags & CHS_EOT) return; if (!_sustain && _ticksLeft == _duration) keyOff(); if (--_ticksLeft) return; if (!_sustain) keyOff(); uint8 cmd = 0; bool loop = true; while (loop) { cmd = *_dataPtr++; if (cmd < 0xf0) loop = false; else if (!processControlEvent(cmd)) return; } uint8 para = *_dataPtr++; if (cmd == 0x80) { keyOff(); _sustain = false; } else { keyOn(); if (_sustain == false || cmd != _frqBlockMSB) _flags |= CHS_RECALCFREQ; _sustain = (para & 0x80) ? true : false; _frqBlockMSB = cmd; } _ticksLeft = para & 0x7f; } void TownsPC98_MusicChannel::processFrequency() { static const uint16 noteFrequencies[] = { 0x26a, 0x28f, 0x2b6, 0x2df, 0x30b, 0x339, 0x36a, 0x39e, 0x3d5, 0x410, 0x44e, 0x48f }; if (_flags & CHS_RECALCFREQ) { _frequency = (noteFrequencies[_frqBlockMSB & 0x0f] + _pitchBend) | (((_frqBlockMSB & 0x70) >> 1) << 8); writeReg(_part, _regOffset + 0xa4, (_frequency >> 8)); writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff)); setupVibrato(); } if (!(_flags & CHS_VBROFF)) { if (!processVibrato()) return; writeReg(_part, _regOffset + 0xa4, (_frequency >> 8)); writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff)); } } void TownsPC98_MusicChannel::fadeStep() { _fading = true; _totalLevel += 3; if (_totalLevel > 0x7f) _totalLevel = 0x7f; setOutputLevel(); } void TownsPC98_MusicChannel::setupVibrato() { _vbrCurDelay = _vbrInitDelayHi; if (_flags & CHS_KEYOFF) { _vbrModCurVal = _vbrModInitVal; _vbrCurDelay += _vbrInitDelayLo; } _vbrDurLeft = (_vbrDuration >> 1); _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ); } bool TownsPC98_MusicChannel::processVibrato() { if (--_vbrCurDelay) return false; _vbrCurDelay = _vbrInitDelayHi; _frequency += _vbrModCurVal; if (!--_vbrDurLeft) { _vbrDurLeft = _vbrDuration; _vbrModCurVal = -_vbrModCurVal; } return true; } uint8 TownsPC98_MusicChannel::readReg(uint8 part, uint8 reg) { return _driver->readReg(part, reg); } void TownsPC98_MusicChannel::writeReg(uint8 part, uint8 reg, uint8 val) { _driver->writeReg(part, reg, val); } bool TownsPC98_MusicChannel::control_dummy(uint8 para) { _dataPtr--; return true; } bool TownsPC98_MusicChannel::control_f2_duration(uint8 para) { _duration = para; return true; } bool TownsPC98_MusicChannel::control_f3_pitchBend(uint8 para) { _pitchBend = (int8) para; return true; } bool TownsPC98_MusicChannel::control_f5_tempo(uint8 para) { _driver->setMusicTempo(para); return true; } bool TownsPC98_MusicChannel::control_f6_repeatSection(uint8 para) { _dataPtr--; _dataPtr[0]--; if (*_dataPtr) { // repeat section until counter has reached zero _dataPtr = _driver->_trackPtr + READ_LE_UINT16(_dataPtr + 2); } else { // reset counter, advance to next section _dataPtr[0] = _dataPtr[1]; _dataPtr += 4; } return true; } bool TownsPC98_MusicChannel::control_f7_setupVibrato(uint8 para) { _vbrInitDelayHi = _dataPtr[0]; _vbrInitDelayLo = para; _vbrModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1); _vbrDuration = _dataPtr[3]; _dataPtr += 4; _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF | CHS_RECALCFREQ; return true; } bool TownsPC98_MusicChannel::control_f8_toggleVibrato(uint8 para) { if (para == 0x10) { if (*_dataPtr++) { _flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF; } else { _flags |= CHS_VBROFF; } } else { /* NOT IMPLEMENTED uint8 skipChannels = para / 36; uint8 entry = para % 36; TownsPC98_AudioDriver::TownsPC98_MusicChannel *t = &chan[skipChannels]; t->unnamedEntries[entry] = *_dataPtr++;*/ } return true; } bool TownsPC98_MusicChannel::control_fa_writeReg(uint8 para) { writeReg(_part, para, *_dataPtr++); return true; } bool TownsPC98_MusicChannel::control_fd_jump(uint8 para) { uint8 *tmp = _driver->_trackPtr + READ_LE_UINT16(_dataPtr - 1); _dataPtr = (tmp[1] == 1) ? tmp : (_dataPtr + 1); return true; } void TownsPC98_MusicChannel::keyOn() { // all operators on uint8 value = _keyNum | 0xf0; if (_part) value |= 4; uint8 regAddress = 0x28; writeReg(0, regAddress, value); } void TownsPC98_MusicChannel::keyOff() { // all operators off uint8 value = _keyNum & 0x0f; if (_part) value |= 4; uint8 regAddress = 0x28; writeReg(0, regAddress, value); _flags |= CHS_KEYOFF; } void TownsPC98_MusicChannel::setOutputLevel() { static const uint8 carrier[] = { 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F }; uint8 outopr = carrier[_algorithm]; uint8 reg = 0x40 + _regOffset; for (int i = 0; i < 4; i++) { if (outopr & 1) writeReg(_part, reg, _totalLevel); outopr >>= 1; reg += 4; } } bool TownsPC98_MusicChannel::processControlEvent(uint8 cmd) { uint8 para = *_dataPtr++; return (*_controlEvents[cmd & 0x0f])(para); } bool TownsPC98_MusicChannel::control_f0_setPatch(uint8 para) { _instr = para; uint8 reg = _regOffset + 0x80; for (int i = 0; i < 4; i++) { // set release rate for each operator writeReg(_part, reg, 0x0f); reg += 4; } const uint8 *tptr = _driver->_patchData + ((uint32)_instr << 5); reg = _regOffset + 0x30; // write registers 0x30 to 0x8f for (int i = 0; i < 6; i++) { writeReg(_part, reg, tptr[0]); reg += 4; writeReg(_part, reg, tptr[2]); reg += 4; writeReg(_part, reg, tptr[1]); reg += 4; writeReg(_part, reg, tptr[3]); reg += 4; tptr += 4; } reg = _regOffset + 0xB0; _algorithm = tptr[0] & 7; // set feedback and algorithm writeReg(_part, reg, tptr[0]); setOutputLevel(); return true; } bool TownsPC98_MusicChannel::control_f1_presetOutputLevel(uint8 para) { if (_fading) return true; _totalLevel = _driver->_levelPresets[para]; setOutputLevel(); return true; } bool TownsPC98_MusicChannel::control_f4_setOutputLevel(uint8 para) { if (_fading) return true; _totalLevel = para; setOutputLevel(); return true; } bool TownsPC98_MusicChannel::control_fb_incOutLevel(uint8 para) { _dataPtr--; if (_fading) return true; uint8 val = (_totalLevel + 3); if (val > 0x7f) val = 0x7f; _totalLevel = val; setOutputLevel(); return true; } bool TownsPC98_MusicChannel::control_fc_decOutLevel(uint8 para) { _dataPtr--; if (_fading) return true; int8 val = (int8)(_totalLevel - 3); if (val < 0) val = 0; _totalLevel = (uint8)val; setOutputLevel(); return true; } bool TownsPC98_MusicChannel::control_ff_endOfTrack(uint8 para) { uint16 val = READ_LE_UINT16(--_dataPtr); if (val) { // loop _dataPtr = _driver->_trackPtr + val; return true; } else { // quit parsing for active channel --_dataPtr; _flags |= CHS_EOT; _driver->_finishedChannelsFlag |= _idFlag; keyOff(); return false; } } const uint8 TownsPC98_MusicChannel::_controlEventSize[16] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05, 0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02 }; #define CONTROL(x) _controlEvents.push_back(new ControlEvent(this, &TownsPC98_MusicChannelSSG::control_##x)) TownsPC98_MusicChannelSSG::TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), _algorithm(0x80), _ssgStartLvl(0), _ssgTl(0), _ssgStep(0), _ssgTicksLeft(0), _ssgTargetLvl(0) { CONTROL(f0_setPatch); CONTROL(f1_setTotalLevel); CONTROL(f2_duration); CONTROL(f3_pitchBend); CONTROL(f4_setAlgorithm); CONTROL(f5_tempo); CONTROL(f6_repeatSection); CONTROL(f7_setupVibrato); CONTROL(f8_toggleVibrato); CONTROL(f9_loadCustomPatch); CONTROL(fa_writeReg); CONTROL(fb_incOutLevel); CONTROL(fc_decOutLevel); CONTROL(fd_jump); CONTROL(dummy); CONTROL(ff_endOfTrack); if (!_envPatchData) { _envPatchData = new uint8[256]; memcpy(_envPatchData, _envData, 256); } } #undef CONTROL TownsPC98_MusicChannelSSG::~TownsPC98_MusicChannelSSG() { for (Common::Array::iterator i = _controlEvents.begin(); i != _controlEvents.end(); ++i) delete *i; delete[] _envPatchData; _envPatchData = 0; } void TownsPC98_MusicChannelSSG::reset() { TownsPC98_MusicChannel::reset(); _ssgStartLvl = _ssgTl = _ssgStep = _ssgTicksLeft = _ssgTargetLvl = 0; // Unlike the original we restore the default patch data. This fixes a bug // where certain sound effects would bring each other out of tune (e.g. the // dragon's fire in Darm's house in Kyra 1 would sound different each time // you triggered another sfx by dropping an item etc.) uint8 i = (10 + _regOffset) << 4; const uint8 *src = _envData; _envPatchData[i] = src[i]; _envPatchData[i + 3] = src[i + 3]; _envPatchData[i + 4] = src[i + 4]; _envPatchData[i + 6] = src[i + 6]; _envPatchData[i + 8] = src[i + 8]; _envPatchData[i + 12] = src[i + 12]; } void TownsPC98_MusicChannelSSG::loadData(uint8 *data) { _driver->preventRegisterWrite(_flags & CHS_PROTECT ? true : false); TownsPC98_MusicChannel::loadData(data); setOutputLevel(0); _algorithm = 0x80; } void TownsPC98_MusicChannelSSG::processEvents() { if (_flags & CHS_EOT) return; _driver->preventRegisterWrite(_flags & CHS_PROTECT ? true : false); if (!_sustain && _ticksLeft == _duration) nextShape(); if (!--_ticksLeft) { uint8 cmd = 0; bool loop = true; while (loop) { cmd = *_dataPtr++; if (cmd < 0xf0) loop = false; else if (!processControlEvent(cmd)) return; } uint8 para = *_dataPtr++; if (cmd == 0x80) { nextShape(); _sustain = false; } else { if (!_sustain) { _instr &= 0xf0; _ssgStep = _envPatchData[_instr]; _ssgTicksLeft = _envPatchData[_instr + 1] & 0x7f; _ssgTargetLvl = _envPatchData[_instr + 2]; _ssgStartLvl = _envPatchData[_instr + 3]; _flags = (_flags & ~CHS_SSGOFF) | CHS_KEYOFF; } keyOn(); if (_sustain == false || cmd != _frqBlockMSB) _flags |= CHS_RECALCFREQ; _sustain = (para & 0x80) ? true : false; _frqBlockMSB = cmd; } _ticksLeft = para & 0x7f; } if (!(_flags & CHS_SSGOFF)) { if (--_ssgTicksLeft) { if (!_driver->_fading) setOutputLevel(_ssgStartLvl); return; } _ssgTicksLeft = _envPatchData[_instr + 1] & 0x7f; if (_envPatchData[_instr + 1] & 0x80) { uint8 t = _ssgStartLvl - _ssgStep; if (_ssgStep <= _ssgStartLvl && _ssgTargetLvl < t) { if (!_driver->_fading) setOutputLevel(t); return; } } else { int t = _ssgStartLvl + _ssgStep; uint8 p = (uint8)(t & 0xff); if (t < 256 && _ssgTargetLvl > p) { if (!_driver->_fading) setOutputLevel(p); return; } } setOutputLevel(_ssgTargetLvl); if (_ssgStartLvl && !(_instr & 8)) { _instr += 4; _ssgStep = _envPatchData[_instr]; _ssgTicksLeft = _envPatchData[_instr + 1] & 0x7f; _ssgTargetLvl = _envPatchData[_instr + 2]; } else { _flags |= CHS_SSGOFF; setOutputLevel(0); } } } void TownsPC98_MusicChannelSSG::processFrequency() { static const uint16 noteFrequencies[] = { 0xee8, 0xe12, 0xd48, 0xc89, 0xbd5, 0xb2b, 0xa8a, 0x9f3, 0x964, 0x8dd, 0x85e, 0x7e6 }; if (_algorithm & 0x40) return; if (_flags & CHS_RECALCFREQ) { _block = _frqBlockMSB >> 4; _frequency = noteFrequencies[_frqBlockMSB & 0x0f] + _pitchBend; uint16 f = _frequency >> _block; writeReg(0, _regOffset << 1, f & 0xff); writeReg(0, (_regOffset << 1) + 1, f >> 8); setupVibrato(); } if (!(_flags & (CHS_EOT | CHS_VBROFF | CHS_SSGOFF))) { if (!processVibrato()) return; uint16 f = _frequency >> _block; writeReg(0, _regOffset << 1, f & 0xff); writeReg(0, (_regOffset << 1) + 1, f >> 8); } } void TownsPC98_MusicChannelSSG::protect() { _flags |= CHS_PROTECT; } void TownsPC98_MusicChannelSSG::restore() { _flags &= ~CHS_PROTECT; keyOn(); writeReg(0, 8 + _regOffset, _ssgTl); uint16 f = _frequency >> _block; writeReg(0, _regOffset << 1, f & 0xff); writeReg(0, (_regOffset << 1) + 1, f >> 8); } void TownsPC98_MusicChannelSSG::fadeStep() { _fading = true; _totalLevel--; if ((int8)_totalLevel < 0) _totalLevel = 0; setOutputLevel(_ssgStartLvl); } void TownsPC98_MusicChannelSSG::keyOn() { uint8 c = 0x7b; uint8 t = (_algorithm & 0xC0) << 1; if (_algorithm & 0x80) t |= 4; c = (c << (_regOffset + 1)) | (c >> (7 - _regOffset)); t = (t << (_regOffset + 1)) | (t >> (7 - _regOffset)); if (!(_algorithm & 0x80)) writeReg(0, 6, _algorithm & 0x7f); uint8 e = (readReg(0, 7) & c) | t; writeReg(0, 7, e); } void TownsPC98_MusicChannelSSG::nextShape() { _instr = (_instr & 0xf0) + 0x0c; _ssgStep = _envPatchData[_instr]; _ssgTicksLeft = _envPatchData[_instr + 1] & 0x7f; _ssgTargetLvl = _envPatchData[_instr + 2]; } void TownsPC98_MusicChannelSSG::setOutputLevel(uint8 lvl) { _ssgStartLvl = lvl; uint16 newTl = (((uint16)_totalLevel + 1) * (uint16)lvl) >> 8; if (newTl == _ssgTl) return; _ssgTl = newTl; writeReg(0, 8 + _regOffset, _ssgTl); } bool TownsPC98_MusicChannelSSG::processControlEvent(uint8 cmd) { uint8 para = *_dataPtr++; return (*_controlEvents[cmd & 0x0f])(para); } bool TownsPC98_MusicChannelSSG::control_f0_setPatch(uint8 para) { _instr = para << 4; para = (para >> 3) & 0x1e; if (para) return control_f4_setAlgorithm(para | 0x40); return true; } bool TownsPC98_MusicChannelSSG::control_f1_setTotalLevel(uint8 para) { if (!_fading) _totalLevel = para; return true; } bool TownsPC98_MusicChannelSSG::control_f4_setAlgorithm(uint8 para) { _algorithm = para; return true; } bool TownsPC98_MusicChannelSSG::control_f9_loadCustomPatch(uint8 para) { _instr = (_driver->_sfxOffs + 10 + _regOffset) << 4; _envPatchData[_instr] = *_dataPtr++; _envPatchData[_instr + 3] = para; _envPatchData[_instr + 4] = *_dataPtr++; _envPatchData[_instr + 6] = *_dataPtr++; _envPatchData[_instr + 8] = *_dataPtr++; _envPatchData[_instr + 12] = *_dataPtr++; return true; } bool TownsPC98_MusicChannelSSG::control_fb_incOutLevel(uint8 para) { _dataPtr--; if (_fading) return true; _totalLevel--; if ((int8)_totalLevel < 0) _totalLevel = 0; return true; } bool TownsPC98_MusicChannelSSG::control_fc_decOutLevel(uint8 para) { _dataPtr--; if (_fading) return true; if (_totalLevel + 1 < 0x10) _totalLevel++; return true; } bool TownsPC98_MusicChannelSSG::control_ff_endOfTrack(uint8 para) { if (!_driver->_sfxOffs) { uint16 val = READ_LE_UINT16(--_dataPtr); if (val) { // loop _dataPtr = _driver->_trackPtr + val; return true; } else { // stop parsing if (!_driver->_fading) setOutputLevel(0); --_dataPtr; _flags |= CHS_EOT; _driver->_finishedSSGFlag |= _idFlag; } } else { // end of sfx track - restore ssg music channel _flags |= CHS_EOT; _driver->_finishedSfxFlag |= _idFlag; _driver->_ssgChannels[_chanNum]->restore(); } return false; } uint8 *TownsPC98_MusicChannelSSG::_envPatchData = 0; const uint8 TownsPC98_MusicChannelSSG::_envData[256] = { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00, 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00, 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, 0x04, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0x01, 0x00, 0xFF, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, 0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x00, 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x64, 0x01, 0xFF, 0x64, 0xFF, 0x81, 0xFF, 0x00, 0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x02, 0x01, 0xFF, 0x28, 0xFF, 0x81, 0xF0, 0x00, 0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xC8, 0x00, 0x01, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x78, 0x5F, 0x81, 0xA0, 0x00, 0x05, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00 }; void TownsPC98_SfxChannel::loadData(uint8 *data) { _flags = CHS_ALLOFF; _ticksLeft = 1; _dataPtr = data; _ssgTl = 0xff; _algorithm = 0x80; uint8 *tmp = _dataPtr; for (bool loop = true; loop;) { uint8 cmd = *tmp++; if (cmd < 0xf0) { tmp++; } else if (cmd == 0xff) { loop = false; } else if (cmd == 0xf6) { // reset repeat section countdown tmp[0] = tmp[1]; tmp += 4; } else { tmp += _controlEventSize[cmd - 240]; } } } void TownsPC98_SfxChannel::reset() { TownsPC98_MusicChannel::reset(); _ssgStartLvl = _ssgTl = _ssgStep = _ssgTicksLeft = _ssgTargetLvl = 0; // Unlike the original we restore the default patch data. This fixes a bug // where certain sound effects would bring each other out of tune (e.g. the // dragon's fire in Darm's house in Kyra 1 would sound different each time // you triggered another sfx by dropping an item etc.) uint8 i = (13 + _regOffset) << 4; const uint8 *src = _envData; _envPatchData[i] = src[i]; _envPatchData[i + 3] = src[i + 3]; _envPatchData[i + 4] = src[i + 4]; _envPatchData[i + 6] = src[i + 6]; _envPatchData[i + 8] = src[i + 8]; _envPatchData[i + 12] = src[i + 12]; } #ifndef DISABLE_PC98_RHYTHM_CHANNEL #define CONTROL(x) _controlEvents.push_back(new ControlEvent(this, &TownsPC98_MusicChannelPCM::control_##x)) TownsPC98_MusicChannelPCM::TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) : TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id) { CONTROL(dummy); CONTROL(f1_prcStart); CONTROL(dummy); CONTROL(dummy); CONTROL(dummy); CONTROL(dummy); CONTROL(f6_repeatSection); CONTROL(dummy); CONTROL(dummy); CONTROL(dummy); CONTROL(fa_writeReg); CONTROL(dummy); CONTROL(dummy); CONTROL(dummy); CONTROL(dummy); CONTROL(ff_endOfTrack); } #undef CONTROL TownsPC98_MusicChannelPCM::~TownsPC98_MusicChannelPCM() { for (Common::Array::iterator i = _controlEvents.begin(); i != _controlEvents.end(); ++i) delete *i; } void TownsPC98_MusicChannelPCM::loadData(uint8 *data) { _flags = (_flags & ~CHS_EOT) | CHS_ALLOFF; _ticksLeft = 1; _dataPtr = data; _totalLevel = 0x7F; } void TownsPC98_MusicChannelPCM::processEvents() { if (_flags & CHS_EOT) return; if (--_ticksLeft) return; uint8 cmd = 0; bool loop = true; while (loop) { cmd = *_dataPtr++; if (cmd == 0x80) { loop = false; } else if (cmd < 0xf0) { writeReg(0, 0x10, cmd); } else if (!processControlEvent(cmd)) { return; } } _ticksLeft = *_dataPtr++; } bool TownsPC98_MusicChannelPCM::processControlEvent(uint8 cmd) { uint8 para = *_dataPtr++; return (*_controlEvents[cmd & 0x0f])(para); } bool TownsPC98_MusicChannelPCM::control_f1_prcStart(uint8 para) { _totalLevel = para; writeReg(0, 0x11, para); return true; } bool TownsPC98_MusicChannelPCM::control_ff_endOfTrack(uint8 para) { uint16 val = READ_LE_UINT16(--_dataPtr); if (val) { // loop _dataPtr = _driver->_trackPtr + val; return true; } else { // quit parsing for active channel --_dataPtr; _flags |= CHS_EOT; _driver->_finishedRhythmFlag |= _idFlag; return false; } } #endif // DISABLE_PC98_RHYTHM_CHANNEL TownsPC98_AudioDriver::TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type) : _channels(0), _ssgChannels(0), _sfxChannels(0), #ifndef DISABLE_PC98_RHYTHM_CHANNEL _rhythmChannel(0), #endif _sfxData(0), _sfxOffs(0), _patchData(0), _sfxBuffer(0), _musicBuffer(0), _trackPtr(0), _levelPresets(type == kTypeTowns ? _levelPresetFMTOWNS : _levelPresetPC98), _updateChannelsFlag(type == kType26 ? 0x07 : 0x3F), _finishedChannelsFlag(0), _updateSSGFlag(type == kTypeTowns ? 0x00 : 0x07), _finishedSSGFlag(0), _updateRhythmFlag(type == kType86 ? #ifndef DISABLE_PC98_RHYTHM_CHANNEL 0x01 #else 0x00 #endif : 0x00), _numChanFM(type == kType26 ? 3 : 6), _numChanSSG(type == kTypeTowns ? 0 : 3), _numChanRHY(type == kType86 ? 1 : 0), _finishedRhythmFlag(0), _updateSfxFlag(0), _finishedSfxFlag(0), _musicTickCounter(0), _regWriteProtect(false), _musicPlaying(false), _sfxPlaying(false), _fading(false), _looping(0), _ready(false) { _sfxOffsets[0] = _sfxOffsets[1] = 0; _pc98a = new PC98AudioCore(mixer, this, type); } TownsPC98_AudioDriver::~TownsPC98_AudioDriver() { _ready = false; delete _pc98a; if (_channels) { for (int i = 0; i < _numChanFM; i++) delete _channels[i]; delete[] _channels; } if (_ssgChannels) { for (int i = 0; i < _numChanSSG; i++) delete _ssgChannels[i]; delete[] _ssgChannels; } if (_sfxChannels) { for (int i = 0; i < 2; i++) delete _sfxChannels[i]; delete[] _sfxChannels; } #ifndef DISABLE_PC98_RHYTHM_CHANNEL delete _rhythmChannel; #endif } bool TownsPC98_AudioDriver::init() { if (_ready) { reset(); return true; } if (!_pc98a->init()) return false; _pc98a->setSoundEffectChanMask(0); _channels = new TownsPC98_MusicChannel *[_numChanFM]; for (int i = 0; i < _numChanFM; i++) { int ii = i * 6; _channels[i] = new TownsPC98_MusicChannel(this, _channelPreset[ii], _channelPreset[ii + 1], _channelPreset[ii + 2], _channelPreset[ii + 3], _channelPreset[ii + 4], _channelPreset[ii + 5]); } if (_numChanSSG) { _ssgChannels = new TownsPC98_MusicChannelSSG *[_numChanSSG]; for (int i = 0; i < _numChanSSG; i++) { int ii = i * 6; _ssgChannels[i] = new TownsPC98_MusicChannelSSG(this, _channelPreset[ii], _channelPreset[ii + 1], _channelPreset[ii + 2], _channelPreset[ii + 3], _channelPreset[ii + 4], _channelPreset[ii + 5]); } _sfxChannels = new TownsPC98_SfxChannel *[2]; for (int i = 0; i < 2; i++) { int ii = (i + 1) * 6; _sfxChannels[i] = new TownsPC98_SfxChannel(this, _channelPreset[ii], _channelPreset[ii + 1], _channelPreset[ii + 2], _channelPreset[ii + 3], _channelPreset[ii + 4], _channelPreset[ii + 5]); } } #ifndef DISABLE_PC98_RHYTHM_CHANNEL if (_numChanRHY) _rhythmChannel = new TownsPC98_MusicChannelPCM(this, 0, 0, 0, 0, 0, 1); #endif setMusicTempo(84); setSfxTempo(654); _ready = true; return true; } void TownsPC98_AudioDriver::loadMusicData(uint8 *data, bool loadPaused) { if (!_ready) { warning("TownsPC98_AudioDriver: Driver must be initialized before loading data"); return; } if (!data) { warning("TownsPC98_AudioDriver: Invalid music file data"); return; } reset(); PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); const uint8 *src_a = _trackPtr = _musicBuffer = data; for (uint8 i = 0; i < 3; i++) { _channels[i]->loadData(data + READ_LE_UINT16(src_a)); src_a += 2; } for (int i = 0; i < _numChanSSG; i++) { _ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a)); src_a += 2; } for (uint8 i = 3; i < _numChanFM; i++) { _channels[i]->loadData(data + READ_LE_UINT16(src_a)); src_a += 2; } if (_numChanRHY) { #ifndef DISABLE_PC98_RHYTHM_CHANNEL _rhythmChannel->loadData(data + READ_LE_UINT16(src_a)); #endif src_a += 2; } preventRegisterWrite(false); _patchData = src_a + 4; _finishedChannelsFlag = _finishedSSGFlag = _finishedRhythmFlag = 0; _musicPlaying = !loadPaused; } void TownsPC98_AudioDriver::loadSoundEffectData(uint8 *data, uint8 trackNum) { if (!_ready) { warning("TownsPC98_AudioDriver: Driver must be initialized before loading data"); return; } if (!_sfxChannels) { warning("TownsPC98_AudioDriver: Sound effects not supported by this configuration"); return; } if (!data) { warning("TownsPC98_AudioDriver: Invalid sound effects file data"); return; } PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); _sfxData = _sfxBuffer = data; _sfxOffsets[0] = READ_LE_UINT16(&_sfxData[(trackNum << 2)]); _sfxOffsets[1] = READ_LE_UINT16(&_sfxData[(trackNum << 2) + 2]); _sfxPlaying = true; _finishedSfxFlag = 0; } void TownsPC98_AudioDriver::reset() { PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); _musicPlaying = false; _sfxPlaying = false; _fading = 0; _looping = 0; _musicTickCounter = 0; _sfxData = 0; _pc98a->reset(); for (int i = 0; i < _numChanFM; i++) _channels[i]->reset(); for (int i = 0; i < _numChanSSG; i++) _ssgChannels[i]->reset(); if (_numChanSSG) { for (int i = 0; i < 2; i++) _sfxChannels[i]->reset(); } #ifndef DISABLE_PC98_RHYTHM_CHANNEL if (_rhythmChannel) _rhythmChannel->reset(); #endif } void TownsPC98_AudioDriver::fadeStep() { if (!_musicPlaying) return; for (int j = 0; j < _numChanFM; j++) { if (_updateChannelsFlag & _channels[j]->_idFlag) _channels[j]->fadeStep(); } for (int j = 0; j < _numChanSSG; j++) { if (_updateSSGFlag & _ssgChannels[j]->_idFlag) _ssgChannels[j]->fadeStep(); } if (!_fading) { _fading = 19; #ifndef DISABLE_PC98_RHYTHM_CHANNEL if (_numChanRHY) { if (_updateRhythmFlag & _rhythmChannel->_idFlag) _rhythmChannel->reset(); } #endif } else { if (!--_fading) reset(); } } void TownsPC98_AudioDriver::pause() { _musicPlaying = false; } void TownsPC98_AudioDriver::cont() { _musicPlaying = true; } bool TownsPC98_AudioDriver::looping() const { return _looping == _updateChannelsFlag ? true : false; } bool TownsPC98_AudioDriver::musicPlaying() const { return _musicPlaying; } void TownsPC98_AudioDriver::setMusicVolume(int volume) { _pc98a->setMusicVolume(volume); } void TownsPC98_AudioDriver::setSoundEffectVolume(int volume) { _pc98a->setSoundEffectVolume(volume); } uint8 TownsPC98_AudioDriver::readReg(uint8 part, uint8 reg) { return _pc98a->readReg(part, reg); } void TownsPC98_AudioDriver::writeReg(uint8 part, uint8 reg, uint8 val) { if (!_regWriteProtect) _pc98a->writeReg(part, reg, val); } void TownsPC98_AudioDriver::preventRegisterWrite(bool prevent) { _regWriteProtect = prevent; } void TownsPC98_AudioDriver::timerCallbackA() { if (_sfxChannels && _sfxPlaying) { if (_sfxData) startSoundEffect(); _sfxOffs = 3; _trackPtr = _sfxBuffer; for (int i = 0; i < 2; i++) { if (_updateSfxFlag & _sfxChannels[i]->_idFlag) { _sfxChannels[i]->processEvents(); _sfxChannels[i]->processFrequency(); } } _trackPtr = _musicBuffer; } if (_updateSfxFlag && _finishedSfxFlag == _updateSfxFlag) { _sfxPlaying = false; _updateSfxFlag = 0; _pc98a->setSoundEffectChanMask(0); } } void TownsPC98_AudioDriver::timerCallbackB() { _sfxOffs = 0; if (_musicPlaying) { _musicTickCounter++; for (int i = 0; i < _numChanFM; i++) { if (_updateChannelsFlag & _channels[i]->_idFlag) { _channels[i]->processEvents(); _channels[i]->processFrequency(); } } for (int i = 0; i < _numChanSSG; i++) { if (_updateSSGFlag & _ssgChannels[i]->_idFlag) { _ssgChannels[i]->processEvents(); _ssgChannels[i]->processFrequency(); } } #ifndef DISABLE_PC98_RHYTHM_CHANNEL if (_numChanRHY) if (_updateRhythmFlag & _rhythmChannel->_idFlag) _rhythmChannel->processEvents(); #endif } preventRegisterWrite(false); if (_finishedChannelsFlag == _updateChannelsFlag && _finishedSSGFlag == _updateSSGFlag && _finishedRhythmFlag == _updateRhythmFlag) _musicPlaying = false; } void TownsPC98_AudioDriver::startSoundEffect() { int volFlags = 0; for (int i = 0; i < 2; i++) { if (_sfxOffsets[i]) { _ssgChannels[i + 1]->protect(); _sfxChannels[i]->reset(); _sfxChannels[i]->loadData(_sfxData + _sfxOffsets[i]); _updateSfxFlag |= _sfxChannels[i]->_idFlag; volFlags |= (_sfxChannels[i]->_idFlag << _numChanFM); } else { _ssgChannels[i + 1]->restore(); _updateSfxFlag &= ~_sfxChannels[i]->_idFlag; } } _pc98a->setSoundEffectChanMask(volFlags); _sfxData = 0; } void TownsPC98_AudioDriver::setMusicTempo(uint8 tempo) { writeReg(0, 0x26, tempo); writeReg(0, 0x27, 0x33); } void TownsPC98_AudioDriver::setSfxTempo(uint16 tempo) { writeReg(0, 0x24, tempo & 0xff); writeReg(0, 0x25, tempo >> 8); writeReg(0, 0x27, 0x33); } const uint8 TownsPC98_AudioDriver::_channelPreset[36] = { 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, 0x01, 0x80, 0x01, 0x01, 0x00, 0x02, 0x02, 0x80, 0x02, 0x02, 0x00, 0x04, 0x00, 0x80, 0x03, 0x04, 0x01, 0x08, 0x01, 0x80, 0x04, 0x05, 0x01, 0x10, 0x02, 0x80, 0x05, 0x06, 0x01, 0x20 }; const uint8 TownsPC98_AudioDriver::_levelPresetFMTOWNS[24] = { 0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38, 0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18, 0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90 }; const uint8 TownsPC98_AudioDriver::_levelPresetPC98[24] = { 0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25, 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10, 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90 };