diff options
author | athrxx | 2019-10-14 18:42:56 +0200 |
---|---|---|
committer | athrxx | 2019-12-18 20:50:40 +0100 |
commit | 5cea16658594355c35a0f83a65d4341be95e8da3 (patch) | |
tree | 29e15ccf9f14da602f100550ba45b0aee27b55f2 /engines/kyra/sound/drivers | |
parent | b8a44c957732165aabd0bbd99cdf0f2114450cd6 (diff) | |
download | scummvm-rg350-5cea16658594355c35a0f83a65d4341be95e8da3.tar.gz scummvm-rg350-5cea16658594355c35a0f83a65d4341be95e8da3.tar.bz2 scummvm-rg350-5cea16658594355c35a0f83a65d4341be95e8da3.zip |
KYRA: (EOB/PC98) - add sound driver
Diffstat (limited to 'engines/kyra/sound/drivers')
-rw-r--r-- | engines/kyra/sound/drivers/mlalf98.cpp | 1946 | ||||
-rw-r--r-- | engines/kyra/sound/drivers/mlalf98.h | 90 |
2 files changed, 2036 insertions, 0 deletions
diff --git a/engines/kyra/sound/drivers/mlalf98.cpp b/engines/kyra/sound/drivers/mlalf98.cpp new file mode 100644 index 0000000000..b2cb6d909c --- /dev/null +++ b/engines/kyra/sound/drivers/mlalf98.cpp @@ -0,0 +1,1946 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/kyra_v1.h" +#include "kyra/sound/drivers/mlalf98.h" + +#include "audio/softsynth/fmtowns_pc98/pc98_audio.h" + +namespace Kyra { + +class SoundChannel { +public: + SoundChannel(PC98AudioCore *pc98a, int part, int regOffset, int type); + virtual ~SoundChannel(); + + virtual void setData(uint8 *dataStart, uint8 *loopStart, const uint8 *dataEnd, uint8 *instrBuffer); + void globalBlock(); + void globalUnblock(); + + void toggleMute(bool mute); + virtual void restore() {}; + virtual bool checkFinished() { return false; } + + void update(); + + void startFadeOut(); + void updateFadeOut(); + void abortFadeOut(); + + virtual void keyOff() = 0; + + int idFlag() const; + +protected: + enum Flags { + kInternalMask = 0x0F, + kF1_10 = 1 << 4, + kFade = 1 << 5, + kTremolo = 1 << 6, + kEnvelope = 1 << 7 + }; + + enum Flags2 { + kEoT = 1 << 0, + kF2_02 = 1 << 1, + kF2_04 = 1 << 2, + kMute = 1 << 3, + kF2_10 = 1 << 4, + kVbrDelay = 1 << 5, + kNoSustain = 1 << 6, + kVbrEnable = 1 << 7 + }; + + template <class SoundChannelImpl> class SoundOpcode : public Common::Functor1Mem<uint8*&, void, SoundChannelImpl> { + public: + typedef Common::Functor1Mem<uint8*&, void, SoundChannelImpl> SCFunc; + SoundOpcode(SoundChannelImpl *sc, const typename SCFunc::FuncType &func, const char *dsc, int chanId, int chanType, int cDataBytes) : SCFunc(sc, func), _dataLen(cDataBytes) { + static const char tpstr[4][4] = { "FM ", "SSG", "RHY", "EXT" }; + Common::String args; + while (cDataBytes--) + args += "0x%02x "; + _msg = Common::String::format("%s Channel %d: %s() [ %s]", tpstr[chanType], chanId, dsc, args.c_str()); + memset(_dt, 0, 7); + } + ~SoundOpcode() {} + void run(uint8 *&arg) { + assert(arg); + memcpy(_dt, arg, _dataLen); + debugC(3, kDebugLevelSound, _msg.c_str(), _dt[0], _dt[1], _dt[2], _dt[3], _dt[4], _dt[5], _dt[6]); + if (SCFunc::isValid()) + SCFunc::operator()(arg); + } + private: + int _dataLen; + uint8 _dt[7]; + Common::String _msg; + }; + + void vbrResetDelay(); + void vbrReset(); + + virtual void clear(); + virtual void writeDevice(uint8 reg, uint8 val); + + uint8 _ticksLeft; + uint8 _program; + uint8 *_dataPtr; + const uint8 *_dataEnd; + uint8 *_loopStartPtr; + uint8 _volume; + uint8 _algorithm; + const uint8 _regOffset; + const uint8 _part; + int16 _transpose; + uint8 _envCurLvl; + uint8 _envRR; + uint8 _vbrDelay; + uint8 _vbrRem; + uint8 _vbrRate; + uint8 _vbrTicker; + int16 _vbrStepSize; + int16 _vbrModifier; + uint8 _vbrDepth; + uint8 _vbrState; + uint8 _duration; + uint16 _frequency; + uint8 _flags2; + uint8 _note; + uint8 _flags; + uint8 *_backupData; + + static bool _globalBlock; + + int8 _fadeVolModifier; + uint8 _fadeProgress; + uint8 _fadeTicker; + uint8 _trmCarrier; + uint8 *_instrBuffer; + +protected: + virtual void op_setTranspose(uint8 *&data); + void op_setDuration(uint8 *&data); + void op_setVibrato(uint8 *&data); + void op_repeatSectionBegin(uint8 *&data); + void op_repeatSectionJumpIf(uint8 *&data); + void op_jumpToSubroutine(uint8 *&data); + void op_sustain(uint8 *&data); + void op_repeatSectionAbort(uint8 *&data); + void op_runOpcode2(uint8 *&data); + +private: + virtual void op2_setExtPara(uint8 *&data); + void op2_setEnvGenFlags(uint8 *&data); + void op2_setEnvGenTimer(uint8 *&data); + void op2_beginFadeout(uint8 *&data); + void op2_4(uint8 *&data); + void op2_toggleFadeout(uint8 *&data); + void op2_returnFromSubroutine(uint8 *&data); + void op2_7(uint8 *&data); + + void op3_vbrInit(uint8 *&data); + void op3_vbrDisable(uint8 *&data); + void op3_vbrEnable(uint8 *&data); + void op3_vbrSetDelay(uint8 *&data); + void op3_vbrSetRate(uint8 *&data); + void op3_vbrSetStepSize(uint8 *&data); + void op3_vbrSetDepth(uint8 *&data); + void op3_vbrSetTremolo(uint8 *&data); + + typedef SoundOpcode<SoundChannel> Opcode; + Common::Array<Opcode*> _subOpcodes[2]; + +private: + virtual void parse() = 0; + virtual void updateVolume() = 0; + void updateSounds(); + virtual void updateVibrato() = 0; + + const int _type; + PC98AudioCore *_pc98a; + bool _mute; +}; + +class SoundChannelNonSSG : public SoundChannel { +public: + SoundChannelNonSSG(PC98AudioCore *pc98a, int part, int regOffset, int type); + virtual ~SoundChannelNonSSG(); + +protected: + virtual void parse() override; + uint8 _statusB4; + +private: + virtual void finish(); + void soundOff(); + + virtual void noteOn(uint8 note) = 0; + virtual void reduceVolume() {} + virtual void updateVolume() override {} + virtual void updateVibrato() override {} + + virtual void op_programChange(uint8 *&data) = 0; + virtual void op_setVolume(uint8 *&data) = 0; + virtual void op_setSpecialMode(uint8 *&data); + virtual void op_setPanPos(uint8 *&data) = 0; + virtual void op_writeDevice(uint8 *&data); + virtual void op_modifyVolume(uint8 *&data); + void op_enableLFO(uint8 *&data); + + typedef SoundOpcode<SoundChannelNonSSG> Opcode; + Common::Array<Opcode*> _opcodes; +}; + +class MusicChannelFM : public SoundChannelNonSSG { +public: + MusicChannelFM(PC98AudioCore *pc98a, int part, int regOffset); + virtual ~MusicChannelFM(); + + virtual void restore() override; + virtual void keyOff() override; + +private: + virtual void clear() override; + virtual void parse() override; + virtual void noteOn(uint8 note) override; + virtual void updateVolume() override; + virtual void reduceVolume() override; + virtual void updateVibrato() override; + + virtual bool usingSpecialMode() const; + virtual uint8 getSpecialFrequencyModifier(uint8 index); + virtual void setSpecialFrequencyModifier(uint8 index, uint8 val); + virtual void toggleSpecialMode(bool on); + + void sendVolume(uint8 volume); + void sendTrmVolume(uint8 volume); + void keyOn(); + + virtual void writeDevice(uint8 reg, uint8 val) override; + + virtual void op_programChange(uint8 *&data) override; + virtual void op_setVolume(uint8 *&data) override; + virtual void op_setSpecialMode(uint8 *&data) override; + virtual void op_setPanPos(uint8 *&data) override; + virtual void op_modifyVolume(uint8 *&data) override; + + static uint16 _frequency2; + static uint8 _specialModeModifier[4]; + static bool _specialMode; + static uint8 *_registers; +}; + +class MusicChannelSSG : public SoundChannel { +public: + MusicChannelSSG(PC98AudioCore *pc98a, int part, int regOffset); + virtual ~MusicChannelSSG(); + + virtual void keyOff() override; + +private: + enum EnvState { + kAttack = 0x10, + kDecay = 0x20, + kSustain = 0x40, + kUpdate = 0x80 + }; + + virtual void parse() override; + void noteOff(); + void noteOn(uint8 note); + uint8 processEnvelope(); + uint8 envGetAttLevel(); + void envSendAttLevel(uint8 val); + + virtual void updateVolume() override; + virtual void updateVibrato() override; + + virtual void clear() override; + + void op_programChange(uint8 *&data); + void op_setVolume(uint8 *&data); + void op_chanEnable(uint8 *&data); + void op_setNoiseGenerator(uint8 *&data); + void op_setInstrument(uint8 *&data); + void op_modifyVolume(uint8 *&data); + void op_loadInstrument(uint8 *&data); + + uint8 *getProgramData(uint8 program) const; + bool _externalPrograms; + + typedef SoundOpcode<MusicChannelSSG> Opcode; + Common::Array<Opcode*> _opcodes; + + uint8 _envStartLvl; + uint8 _envAR; + uint8 _envDR; + uint8 _envSL; + uint8 _envSR; + static uint8 _ngState; + static uint8 _enState; + + const uint8 _regOffsetAttn; + + uint8 *_envDataTemp; + static const uint8 _envDataPreset[96]; +}; + +class MusicChannelRHY : public SoundChannelNonSSG { +public: + MusicChannelRHY(PC98AudioCore *pc98a, int part, int regOffset); + virtual ~MusicChannelRHY(); + + virtual void keyOff() override; + +private: + virtual void noteOn(uint8 note) override; + virtual void updateVolume() override; + + virtual void op_programChange(uint8 *&data) override; + virtual void op_setVolume(uint8 *&data) override; + virtual void op_setPanPos(uint8 *&data) override; + virtual void op_modifyVolume(uint8 *&data) override; + + uint8 _instrLevel[6]; + + uint8 _activeInstruments; +}; + +class MusicChannelEXT : public SoundChannelNonSSG { +public: + MusicChannelEXT(PC98AudioCore *pc98a, int part, int regOffset, MLALF98::ADPCMData *const &data); + virtual ~MusicChannelEXT(); + + virtual void keyOff() override; + +private: + virtual void noteOn(uint8 note) override; + virtual void updateVibrato() override; + virtual void clear() override; + virtual void writeDevice(uint8 reg, uint8 val) override; + + virtual void op_programChange(uint8 *&data) override; + virtual void op_setVolume(uint8 *&data) override; + virtual void op_setPanPos(uint8 *&data) override; + virtual void op_setTranspose(uint8 *&data) override; + + virtual void op2_setExtPara(uint8 *&data) override; + + uint8 _panPos; + uint8 _useVolPreset; + uint8 _volume2; + uint8 _instrument; + MLALF98::ADPCMData *const &_extBuffer; + uint16 _smpStart; + uint16 _smpEnd; + + PC98AudioCore *_pc98a; +}; + +class SoundEffectChannel : public MusicChannelFM { +public: + SoundEffectChannel(PC98AudioCore *pc98a, int part, int regOffset, SoundChannel *replaceChannel); + virtual ~SoundEffectChannel(); + + void setData(uint8 *dataStart, uint8 *loopStart, const uint8 *dataEnd, uint8 *instrBuffer) override; + virtual bool checkFinished() override; + +private: + virtual void finish() override; + virtual bool usingSpecialMode() const override; + virtual uint8 getSpecialFrequencyModifier(uint8 index) override; + virtual void setSpecialFrequencyModifier(uint8 index, uint8 val) override; + virtual void toggleSpecialMode(bool on) override; + virtual void clear() override; + virtual void writeDevice(uint8 reg, uint8 val) override; + + virtual void op_writeDevice(uint8 *&data) override; + + uint8 _specialModeModifier[4]; + bool _specialMode; + bool _isFinished; + + SoundChannel *_replaceChannel; +}; + +class MLALF98Internal : public PC98AudioPluginDriver { +public: + MLALF98Internal(Audio::Mixer *mixer, EmuType emuType); + ~MLALF98Internal(); + + static MLALF98Internal *open(Audio::Mixer *mixer, EmuType emuType); + static void close(); + + void loadMusicData(Common::SeekableReadStream *data); + void loadSoundEffectData(Common::SeekableReadStream *data); + void loadExtData(MLALF98::ADPCMDataArray &data); + + void startMusic(int track); + void fadeOutMusic(); + void startSoundEffect(int track); + + void allChannelsOff(); + void resetExtUnit(); + + void setMusicVolume(int volume); + void setSoundEffectVolume(int volume); + + // Plugin driver interface + virtual void timerCallbackA() override; + virtual void timerCallbackB() override; + +private: + uint8 *_musicBuffer; + int _musicBufferSize; + uint8 *_sfxBuffer; + int _sfxBufferSize; + MLALF98::ADPCMData *_extBuffer; + int _extBufferSize; + + Common::Array<SoundChannel*> _musicChannels; + Common::Array<SoundChannel*> _sfxChannels; + + const bool _type86; + PC98AudioCore *_pc98a; + + static MLALF98Internal *_refInstance; + static int _refCount; + + int _sfxPlaying; + bool _ready; +}; + +bool SoundChannel::_globalBlock = false; + +SoundChannel::SoundChannel(PC98AudioCore *pc98a, int part, int regOffset, int type) : _pc98a(pc98a), _regOffset(regOffset), _part(part), +_ticksLeft(0), _program(0), _volume(0), _algorithm(0), _envRR(0), _vbrDelay(0), _vbrRem(0), _vbrRate(0), _vbrTicker(0), _vbrStepSize(0), _vbrModifier(0), +_vbrDepth(0), _vbrState(0), _duration(0), _frequency(0), _flags2(0), _note(0), _flags(0), +_transpose(0), _envCurLvl(0), _fadeVolModifier(0), _fadeProgress(0), _fadeTicker(0), _trmCarrier(1), +_dataPtr(0), _dataEnd(0), _loopStartPtr(0), _instrBuffer(0), _backupData(0), _mute(false), _type(type) { + _subOpcodes[0].reserve(8); + _subOpcodes[1].reserve(8); +#define OPCODE(x, y, z) _subOpcodes[x].push_back(new Opcode(this, &SoundChannel::y, #y, type == 1 ? (_regOffset >> 1) : (type == 0 ? (_regOffset + _part * 3) : 0), type, z)) + OPCODE(0, op2_setExtPara, 1); + OPCODE(0, op2_setEnvGenFlags, 1); + OPCODE(0, op2_setEnvGenTimer, 2); + OPCODE(0, op2_beginFadeout, 1); + OPCODE(0, op2_4, 1); + OPCODE(0, op2_toggleFadeout, 1); + OPCODE(0, op2_returnFromSubroutine, 0); + OPCODE(0, op2_7, 1); + OPCODE(1, op3_vbrInit, 5); + OPCODE(1, op3_vbrDisable, 0); + OPCODE(1, op3_vbrEnable, 0); + OPCODE(1, op3_vbrSetDelay, 1); + OPCODE(1, op3_vbrSetRate, 1); + OPCODE(1, op3_vbrSetStepSize, 2); + OPCODE(1, op3_vbrSetDepth, 1); + OPCODE(1, op3_vbrSetTremolo, 2); +#undef OPCODE +} + +SoundChannel::~SoundChannel() { + for (int c = 0; c < 2; ++c) { + for (Common::Array<Opcode*>::iterator i = _subOpcodes[c].begin(); i != _subOpcodes[c].end(); ++i) + delete (*i); + } +} + +void SoundChannel::setData(uint8 *dataStart, uint8 *loopStart, const uint8 *dataEnd, uint8 *instrBuffer) { + clear(); + _dataPtr = dataStart; + _dataEnd = dataEnd; + _loopStartPtr = loopStart; + _instrBuffer = instrBuffer; + _ticksLeft = 1; +} + +void SoundChannel::globalBlock() { + _globalBlock = true; +} + +void SoundChannel::globalUnblock() { + _globalBlock = false; +} + +void SoundChannel::toggleMute(bool mute) { + _mute = mute; +} + +void SoundChannel::update() { + if (!_dataPtr) + return; + + if (_flags2 & kMute) + globalBlock(); + + parse(); + updateSounds(); + + if (_flags2 & kMute) + globalUnblock(); +} + +void SoundChannel::startFadeOut() { + _fadeProgress = 16; +} + +void SoundChannel::updateFadeOut() { + if (--_fadeTicker) + return; + + if (!_fadeProgress) + return; + + _fadeVolModifier = (--_fadeProgress) - 16; + + updateVolume(); + + if (_fadeProgress) + return; + + _fadeVolModifier = 0; + keyOff(); +} + +void SoundChannel::abortFadeOut() { + _fadeProgress = 0; + _fadeVolModifier = 0; +} + +int SoundChannel::idFlag() const { + switch (_type) { + case 0: + return 1 << (_regOffset + _part * 3); + case 1: + return 1 << ((_regOffset >> 1) + 6); + case 2: + return 1 << 9; + case 3: + return 1 << 10; + default: + break; + } + return 0; +} + +void SoundChannel::vbrResetDelay() { + _vbrRem = _vbrDelay; + _flags2 &= ~kVbrDelay; +} + +void SoundChannel::vbrReset() { + _vbrState = _vbrDepth >> 1; + _vbrModifier = _vbrStepSize; + if (_flags & kTremolo) + _frequency = _envCurLvl; +} + +void SoundChannel::clear() { + _ticksLeft = _program = _volume = _algorithm = _duration = _envRR = _vbrDelay = _vbrRem = _vbrRate = _vbrTicker = _vbrDepth = _vbrState = _flags2 = _note = _flags = 0; + _frequency = _transpose = _vbrStepSize = _vbrModifier = 0; + _dataPtr = _loopStartPtr = 0; +} + +void SoundChannel::writeDevice(uint8 reg, uint8 val) { + if (!_mute) + _pc98a->writeReg(reg > 0x2F ? _part : 0, reg, val); +} + +void SoundChannel::op_setTranspose(uint8 *&data) { + _note = 0; + int16 trp = READ_LE_INT16(data); + data += 2; + _transpose = (*data++) ? _transpose + trp : trp; +} + +void SoundChannel::op_setDuration(uint8 *&data) { + _duration = *data++; +} + +void SoundChannel::op_setVibrato(uint8 *&data) { + uint8 cmd = (*data++) & 0x0F; + assert(cmd < _subOpcodes[1].size()); + _subOpcodes[1][cmd & 0x0F]->run(data); +} + +void SoundChannel::op_repeatSectionBegin(uint8 *&data) { + int16 offset = READ_LE_INT16(data); + assert(offset > 0); + // reset repeat counter + data[offset - 1] = data[offset]; + data += 2; +} + +void SoundChannel::op_repeatSectionJumpIf(uint8 *&data) { + // reduce the repeat counter + if (--data[0]) { + // If the counter has not yet reached zero we go back to the beginning of the repeat section. + data += 2; + int16 offset = READ_LE_INT16(data); + assert(offset > 0); + data -= offset; + } else { + // If the counter has reached zero we reset it to the original value and advance to the next section. + data[0] = data[1]; + data += 4; + } +} + +void SoundChannel::op_jumpToSubroutine(uint8 *&data) { +#if 0 + uint16 offset = READ_BE_UINT16(data); + _backupData = data + 2; + if (offset > 0x253D) + offset -= 0x253D; + else if (offset > 0x188F) + offset -= 0x188F; + else + warning("SoundChannel::op_jumpToSubroutine(): invalid offset"); + data = _buffer + offset; +#else + // This is a very ugly opcode which reads and sets an absolute 16 bit offset + // inside the music driver segment. Thus, the ip can jump anywhere, not only + // from one track or channel to another or from the music buffer to the sfx + // buffer or vice versa, but also to any other code or data location within + // the segment. Different driver versions are more or less doomed to mutual + // incompatibility. + // The music data buffer has offset 0x188F and the sfx data buffer 0x253D. So + // I could implement this if I have to. I'd prefer if this never comes up, + // though... + data += 2; + warning("SoundChannel::op_jumpToSubroutine(): not implemented"); +#endif +} + +void SoundChannel::op_sustain(uint8 *&data) { + _flags2 &= ~kNoSustain; +} + +void SoundChannel::op_repeatSectionAbort(uint8 *&data) { + int16 offset = READ_LE_INT16(data); + assert(offset > 0); + data = (data[offset] == 1) ? data + offset + 4 : data + 2; +} + +void SoundChannel::op_runOpcode2(uint8 *&data) { + uint8 cmd = (*data++) & 0x0F; + assert(cmd < _subOpcodes[0].size()); + _subOpcodes[0][cmd & 0x0F]->run(data); +} + +void SoundChannel::op2_setExtPara(uint8 *&data) { + data++; +} + +void SoundChannel::op2_setEnvGenFlags(uint8 *&data) { + _flags = *data++; + writeDevice(0x0D, _flags); + _flags |= kEnvelope; +} + +void SoundChannel::op2_setEnvGenTimer(uint8 *&data) { + writeDevice(0x0B, *data++); + writeDevice(0x0C, *data++); +} + +void SoundChannel::op2_beginFadeout(uint8 *&data) { + _envRR = *data++; + _flags |= kFade; +} + +void SoundChannel::op2_4(uint8 *&data) { + if (*data++) + _flags |= kF1_10; + else + _flags &= ~kF1_10; +} + +void SoundChannel::op2_toggleFadeout(uint8 *&data) { + if (*data++) { + _flags |= kFade; + } else { + _flags &= ~kFade; + updateVolume(); + } +} + +void SoundChannel::op2_returnFromSubroutine(uint8 *&data) { + assert(_backupData); + data = _backupData; +} + +void SoundChannel::op2_7(uint8 *&data) { + /*_unkbyte27 = */data++; +} + +void SoundChannel::op3_vbrInit(uint8 *&data) { + op3_vbrSetDelay(data); + op3_vbrSetRate(data); + _vbrStepSize = _vbrModifier = READ_LE_INT16(data); + data += 2; + op3_vbrSetDepth(data); + _flags2 |= kVbrEnable; +} + +void SoundChannel::op3_vbrDisable(uint8 *&data) { + _flags2 &= ~kVbrEnable; +} + +void SoundChannel::op3_vbrEnable(uint8 *&data) { + _flags2 |= kVbrEnable; +} + +void SoundChannel::op3_vbrSetDelay(uint8 *&data) { + _vbrDelay = _vbrRem = *data++; +} + +void SoundChannel::op3_vbrSetRate(uint8 *&data) { + _vbrRate = _vbrTicker = *data++; +} + +void SoundChannel::op3_vbrSetStepSize(uint8 *&data) { + _vbrStepSize = _vbrModifier = READ_LE_INT16(data); + data += 2; + vbrResetDelay(); +} + +void SoundChannel::op3_vbrSetDepth(uint8 *&data) { + _vbrDepth = *data++; + _vbrState = _vbrDepth >> 1; +} + +void SoundChannel::op3_vbrSetTremolo(uint8 *&data) { + uint8 flags = *data++; + if (!flags) { + _flags &= ~kTremolo; + return; + } + _flags |= kTremolo; + _trmCarrier = flags; + _frequency = _envCurLvl = *data++; +} + +void SoundChannel::updateSounds() { + if (!(_flags2 & kVbrEnable)) + return; + + if (!_dataPtr) + return; + + if (*(_dataPtr - 1) == 0xF0) + return; + + if (!(_flags2 & kVbrDelay)) { + vbrResetDelay(); + vbrReset(); + _vbrTicker = _vbrRate; + _flags2 |= kVbrDelay; + } + + if (_vbrRem) { + _vbrRem--; + return; + } + + if (--_vbrTicker) + return; + + _vbrTicker = _vbrRate; + if (!_vbrState) { + _vbrModifier *= -1; + _vbrState = _vbrDepth; + } + + _vbrState--; + updateVibrato(); +} + +SoundChannelNonSSG::SoundChannelNonSSG(PC98AudioCore *pc98a, int part, int regOffset, int type) : SoundChannel(pc98a, part, regOffset, type), _statusB4(0xC0) { + _opcodes.reserve(16); +#define OPCODE(y, z) _opcodes.push_back(new Opcode(this, &SoundChannelNonSSG::y, #y, _regOffset + _part * 3, type, z)) + OPCODE(op_programChange, 1); + OPCODE(op_setVolume, type == 2 ? 7 : 1); + OPCODE(op_setTranspose, 3); + OPCODE(op_setDuration, 1); + OPCODE(op_setVibrato, 1); + OPCODE(op_repeatSectionBegin, 2); + OPCODE(op_repeatSectionJumpIf, 4); + OPCODE(op_setSpecialMode, 4); + OPCODE(op_setPanPos, 1); + OPCODE(op_jumpToSubroutine, 2); + OPCODE(op_writeDevice, 2); + OPCODE(op_modifyVolume, 1); + OPCODE(op_enableLFO, 3); + OPCODE(op_sustain, 0); + OPCODE(op_repeatSectionAbort, 2); + OPCODE(op_runOpcode2, 1); +#undef OPCODE +} + +SoundChannelNonSSG::~SoundChannelNonSSG() { + for (Common::Array<Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) + delete (*i); +} + +void SoundChannelNonSSG::parse() { + if (!_ticksLeft && (!_dataPtr || _dataPtr >= _dataEnd)) + return; + + if (--_ticksLeft) { + if (_ticksLeft <= _duration) + soundOff(); + return; + } + + uint8 *pos = _dataPtr; + _flags2 |= kNoSustain; + + uint8 cmd = 0; + for (bool loop = true; loop && pos && pos < _dataEnd; ) { + if (*pos == 0) { + _flags2 |= kEoT; + if (!_loopStartPtr) { + _dataPtr = 0; + finish(); + return; + } + pos = _loopStartPtr; + } + + cmd = *pos++; + if (cmd < 0xF0) + break; + + _opcodes[cmd & 0x0F]->run(pos); + } + + _ticksLeft = cmd & 0x7F; + + if (cmd & 0x80) { + if ((_flags & kFade) && !(_flags & kF1_10)) + reduceVolume(); + keyOff(); + } else if (pos && pos < _dataEnd) { + if (_flags2 & kNoSustain) + keyOff(); + noteOn(*pos++); + } + _dataPtr = pos; +} + +void SoundChannelNonSSG::finish() { + keyOff(); +} + +void SoundChannelNonSSG::soundOff() { + if (*_dataPtr == 0xFD) + return; + if (_flags & kFade) + reduceVolume(); + else + keyOff(); +} + +void SoundChannelNonSSG::op_setSpecialMode(uint8 *&data) { + data += 4; +} + +void SoundChannelNonSSG::op_writeDevice(uint8 *&data) { + uint8 reg = *data++; + uint8 val = *data++; + writeDevice(reg, val); +} + +void SoundChannelNonSSG::op_modifyVolume(uint8 *&data) { + data++; +} + +void SoundChannelNonSSG::op_enableLFO(uint8 *&data) { + // This concerns only the fm channels, but the opcode may be called by other channels nonetheless + writeDevice(0x22, (*data++) | 8); + _statusB4 = (_statusB4 & 0xC0) | data[0] | (data[1] << 4 | data[1] >> 4); + data += 2; + writeDevice(0xB4, _statusB4); +} + +uint8 *MusicChannelFM::_registers = 0; +bool MusicChannelFM::_specialMode = false; +uint8 MusicChannelFM::_specialModeModifier[4] = { 0, 0, 0, 0 }; +uint16 MusicChannelFM::_frequency2 = 0; + +MusicChannelFM::MusicChannelFM(PC98AudioCore *pc98a, int part, int regOffset) : SoundChannelNonSSG(pc98a, part, regOffset, 0) { + if (!_registers) { + _registers = new uint8[512]; + memset(_registers, 0, 512); + } +} + +MusicChannelFM::~MusicChannelFM() { + delete[] _registers; + _registers = 0; +} + +void MusicChannelFM::restore() { + for (int i = 0x30 + _regOffset; i < 0xA0; i += 4) + writeDevice(i, _registers[(_part << 8) + i]); + writeDevice(0xB0 + _regOffset, _registers[(_part << 8) + 0xB0 + _regOffset]); + writeDevice(0xB4 + _regOffset, _registers[(_part << 8) + 0xB4 + _regOffset]); + _note = 0; +} + +void MusicChannelFM::keyOff() { + debugC(7, kDebugLevelSound, "FM Channel %d: keyOff() [Ticks: 0x%02x]", _part * 3 + _regOffset, _ticksLeft); + writeDevice(0x28, (_part << 2) + _regOffset); +} + +void MusicChannelFM::clear() { + SoundChannel::clear(); + _specialMode = false; + _statusB4 = 0xC0; +} + +void MusicChannelFM::parse() { + toggleSpecialMode(usingSpecialMode()); + SoundChannelNonSSG::parse(); +} +void MusicChannelFM::noteOn(uint8 note) { + static uint16 freqTableFM[12] = { + 0x026a, 0x028f, 0x02b6, 0x02df, 0x030b, 0x0339, 0x036a, 0x039e, 0x03d5, 0x0410, 0x044e, 0x048f + }; + + if (_note == note && !(_flags2 & kNoSustain)) + return; + + _note = note; + + if ((note & 0x0F) >= 12) + return; + + debugC(5, kDebugLevelSound, "FM Channel %d: noteOn() [Note: 0x%02x Ticks: 0x%02x]", _part * 3 + _regOffset, _note, _ticksLeft); + + uint16 frq = (((note & 0x70) << 7) | freqTableFM[note & 0x0F]) + _transpose; + if (!(_flags & kTremolo)) + _frequency = _frequency2 = frq; + + if (_flags2 & kNoSustain) + vbrResetDelay(); + + vbrReset(); + + if (usingSpecialMode()) + frq += getSpecialFrequencyModifier(0); + + writeDevice(0xA4 + _regOffset, frq >> 8); + writeDevice(0xA0 + _regOffset, frq & 0xFF); + keyOn(); + + if (usingSpecialMode()) { + for (int i = 1; i < 4; ++i) { + uint16 frqFin = _frequency2 + getSpecialFrequencyModifier(i); + writeDevice(0xA9 + i + _regOffset, frqFin >> 8); + writeDevice(0xA5 + i + _regOffset, frqFin & 0xFF); + keyOn(); + } + } +} + +void MusicChannelFM::updateVolume() { + uint8 val = _fadeVolModifier + _volume; + sendVolume(val < 20 ? val : 0); +} + +void MusicChannelFM::reduceVolume() { + sendVolume((uint8)(_volume + _envRR) >> 1); + _flags2 |= kNoSustain; +} + +void MusicChannelFM::updateVibrato() { + _frequency += _vbrModifier; + + if (_flags & kTremolo) { + sendTrmVolume(_frequency & 0xFF); + return; + } + + if (!usingSpecialMode()) { + writeDevice(0xA4 + _regOffset, _frequency >> 8); + writeDevice(0xA0 + _regOffset, _frequency & 0xFF); + return; + } + + for (int i = 0; i < 4; ++i) { + uint16 frqFin = _frequency + getSpecialFrequencyModifier(i); + writeDevice(0xA9 + i + _regOffset, frqFin >> 8); + writeDevice(0xA5 + i + _regOffset, frqFin & 0xFF); + } +} + +bool MusicChannelFM::usingSpecialMode() const { + return _specialMode == true && _regOffset == 2 && _part == 0; +} + +uint8 MusicChannelFM::getSpecialFrequencyModifier(uint8 index) { + assert(index < 4); + return _specialModeModifier[index]; +} + +void MusicChannelFM::setSpecialFrequencyModifier(uint8 index, uint8 val) { + assert(index < 4); + _specialModeModifier[index] = val; +} + +void MusicChannelFM::toggleSpecialMode(bool on) { + _specialMode = on; + uint8 flag = on ? 0x40 : 0; + writeDevice(0x27, 0x3D + flag); + writeDevice(0x27, 0x3F + flag); +} + +void MusicChannelFM::sendVolume(uint8 volume) { + static const uint8 volTable[20] = { + 0x36, 0x33, 0x30, 0x2d, 0x2a, 0x28, 0x25, 0x22, 0x20, 0x1d, + 0x1a, 0x18, 0x15, 0x12, 0x10, 0x0d, 0x0a, 0x08, 0x05, 0x02 + }; + + static const uint8 carrier[8] = { + 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0e, 0x0e, 0x0f + }; + + assert(volume < 20); + assert(_algorithm < 8); + + uint8 reg = 0x40 + _regOffset; + for (uint8 c = carrier[_algorithm]; c; c >>= 1) { + if (c & 1) + writeDevice(reg, volTable[volume]); + reg += 4; + } +} + +void MusicChannelFM::sendTrmVolume(uint8 volume) { + static uint8 cflg[4] = { 1, 4, 2, 8 }; + uint8 reg = 0x40 + _regOffset; + for (int i = 0; i < 4; ++i) { + if (_trmCarrier & cflg[i]) { + writeDevice(reg, volume); + reg += 4; + } + } +} + +void MusicChannelFM::keyOn() { + writeDevice(0x28, 0xF0 | ((_part << 2) + _regOffset)); + if (_flags & kFade) + updateVolume(); +} + +void MusicChannelFM::writeDevice(uint8 reg, uint8 val) { + _registers[(_part << 8) + reg] = val; + SoundChannel::writeDevice(reg, val); +} + +void MusicChannelFM::op_programChange(uint8 *&data) { + _program = *data++; + keyOff(); + + for (int i = 0x80 + _regOffset; i < 0x90; i += 4) + writeDevice(i, 0x0F); + + const uint8 *src = _instrBuffer + READ_LE_UINT16(_instrBuffer) + _program * 25; + + for (int i = 0x30 + _regOffset; i < 0x90; i += 4) + writeDevice(i, *src++); + + _algorithm = *src & 7; + writeDevice(0xB0 + _regOffset, *src); + + updateVolume(); +} + +void MusicChannelFM::op_setVolume(uint8 *&data) { + _volume = *data++; + updateVolume(); +} + +void MusicChannelFM::op_setSpecialMode(uint8 *&data) { + toggleSpecialMode(true); + for (int i = 0; i < 4; ++i) + setSpecialFrequencyModifier(i, *data++); +} + +void MusicChannelFM::op_setPanPos(uint8 *&data) { + uint8 val = *data++; + _statusB4 = (_statusB4 & 0x3f) | (val >> 2) | (val << 6); + writeDevice(0xB4 + _regOffset, _statusB4); +} + +void MusicChannelFM::op_modifyVolume(uint8 *&data) { + _volume += *data++; + updateVolume(); +} + +uint8 MusicChannelSSG::_enState = 0x38; +uint8 MusicChannelSSG::_ngState = 0; + +MusicChannelSSG::MusicChannelSSG(PC98AudioCore *pc98a, int part, int regOffset) : SoundChannel(pc98a, part, regOffset, 1), _regOffsetAttn(8 + (regOffset >> 1)), + _externalPrograms(0), _envDataTemp(0), _envStartLvl(0), _envAR(0), _envDR(0), _envSL(0), _envSR(0) { + _opcodes.reserve(16); +#define OPCODE(y, z) _opcodes.push_back(new Opcode(this, &MusicChannelSSG::y, #y, _regOffset >> 1, 1, z)) + OPCODE(op_programChange, 1); + OPCODE(op_setVolume, 1); + OPCODE(op_setTranspose, 3); + OPCODE(op_setDuration, 1); + OPCODE(op_setVibrato, 1); + OPCODE(op_repeatSectionBegin, 2); + OPCODE(op_repeatSectionJumpIf, 4); + OPCODE(op_chanEnable, 1); + OPCODE(op_setNoiseGenerator, 1); + OPCODE(op_jumpToSubroutine, 2); + OPCODE(op_setInstrument, 6); + OPCODE(op_modifyVolume, 1); + OPCODE(op_loadInstrument, 7); + OPCODE(op_sustain, 0); + OPCODE(op_repeatSectionAbort, 2); + OPCODE(op_runOpcode2, 1); +#undef OPCODE + _envDataTemp = new uint8[96]; + memcpy(_envDataTemp, _envDataPreset, 96); +} + +MusicChannelSSG::~MusicChannelSSG() { + for (Common::Array<Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) + delete (*i); + delete[] _envDataTemp; +} + +void MusicChannelSSG::keyOff() { + debugC(7, kDebugLevelSound, "SSG Channel %d: keyOff() [Ticks: 0x%02x]", _regOffset >> 1, _ticksLeft); + _volume = 0; + writeDevice(_regOffsetAttn, 0); +} + +void MusicChannelSSG::parse() { + if (!_ticksLeft && (!_dataPtr || _dataPtr >= _dataEnd)) + return; + + if (--_ticksLeft) { + if (_ticksLeft <= _duration) { + if (_dataPtr && _dataPtr < _dataEnd) { + if (*_dataPtr == 0xFD) + _flags2 &= ~kNoSustain; + else + noteOff(); + } + } + + if (_volume & kUpdate) { + uint8 val = processEnvelope(); + writeDevice(_regOffsetAttn, _globalBlock ? 0 : val); + } + return; + } + + uint8 *pos = _dataPtr; + if (pos && pos < _dataEnd) { + if (*pos == 0xFD) { + _flags2 &= ~kNoSustain; + pos++; + } else { + _flags2 |= kNoSustain; + } + } + + uint8 cmd = 0; + for (bool loop = true && pos && pos < _dataEnd; loop; ) { + if (*pos == 0) { + _flags2 |= kEoT; + if (!_loopStartPtr) { + _dataPtr = 0; + keyOff(); + _flags2 &= ~kVbrEnable; + return; + } + pos = _loopStartPtr; + } + + cmd = *pos++; + if (cmd < 0xF0) + break; + + _opcodes[cmd & 0x0F]->run(pos); + } + + _ticksLeft = cmd & 0x7F; + + if (cmd & 0x80) + noteOff(); + else if (pos && pos < _dataEnd) + noteOn(*pos++); + _dataPtr = pos; +} + +void MusicChannelSSG::noteOff() { + debugC(7, kDebugLevelSound, "SSG Channel %d: noteOff() [Ticks: 0x%02x]", _regOffset >> 1, _ticksLeft); + if (_flags & kEnvelope) + writeDevice(_regOffsetAttn, 0); + + if (_flags & kFade) { + _flags2 &= ~kNoSustain; + if (_volume & kUpdate) { + uint8 val = processEnvelope(); + writeDevice(_regOffsetAttn, _globalBlock ? 0 : val); + } + return; + } + + if (_volume & kUpdate) { + _volume &= ~(kAttack | kDecay | kSustain); + _envCurLvl = MAX<int>(_envCurLvl - _envRR, 0); + envSendAttLevel(envGetAttLevel()); + } else { + envSendAttLevel(0); + } +} + +void MusicChannelSSG::noteOn(uint8 note) { + static uint16 freqTableSSG[12] = { + 0x0EE8, 0x0E12, 0x0D48, 0x0C89, 0x0BD5, 0x0B2B, 0x0A8A, 0x09F3, 0x0964, 0x08DD, 0x085E, 0x07E6 + }; + + if (_note == note && !(_flags2 & kNoSustain)) + return; + + _note = note; + + debugC(5, kDebugLevelSound, "SSG Channel %d: noteOn() [Note: 0x%02x Ticks: 0x%02x]", _regOffset >> 1, _note, _ticksLeft); + + assert((note & 0x0F) < 12); + _frequency = freqTableSSG[note & 0x0F] + _transpose; + uint16 frq = _frequency >> (_note >> 4); + writeDevice(_regOffset, frq & 0xFF); + writeDevice(_regOffset + 1, frq >> 8); + + uint8 lv = 0; + if (_flags2 & kNoSustain) { + if (_flags & kEnvelope) { + writeDevice(_regOffsetAttn, 0x10); + writeDevice(0x0D, _flags & kInternalMask); + } else { + _volume = (kUpdate | kAttack) | (_volume & 0x0F); + _envCurLvl = _envStartLvl; + _flags2 &= ~kVbrDelay; + } + _vbrState = _vbrDepth >> 1; + _vbrRem = _vbrDelay; + lv = envGetAttLevel(); + } else { + lv = processEnvelope(); + } + envSendAttLevel(lv); +} + +uint8 MusicChannelSSG::processEnvelope() { + if (_volume & kAttack) { + _envCurLvl = (uint8)MIN<int>(_envCurLvl + _envAR, 0xFF); + if (_envCurLvl == 0xFF) + _volume ^= (kAttack | kDecay); + } else if (_volume & kDecay) { + _envCurLvl = (uint8)MAX<int>(_envCurLvl - _envDR, 0); + _envCurLvl = MAX<uint8>(_envCurLvl, _envSL); + if (_envCurLvl == _envSL) + _volume ^= (kDecay | kSustain); + } else if (_volume & kSustain) { + _envCurLvl = (uint8)MAX<int>(_envCurLvl - _envSR, 0); + if (_envCurLvl == 0) + _volume &= ~(kAttack | kDecay | kSustain); + } else { + _envCurLvl = (uint8)MAX<int>(_envCurLvl - _envRR, 0); + } + return envGetAttLevel(); +} + +uint8 MusicChannelSSG::envGetAttLevel() { + uint8 val = ((uint16)_envCurLvl * ((_volume & 0x0F) + 2)) >> 8; + return !(_flags2 & kNoSustain) && (_flags & kFade) ? (uint8)(val + _envRR) >> 1 : val; +} + +void MusicChannelSSG::envSendAttLevel(uint8 val) { + if (!(_flags & kEnvelope)) + writeDevice(_regOffsetAttn, _globalBlock ? 0 : val); +} + +void MusicChannelSSG::updateVolume() { + uint8 volNew = (_volume & 0x0F) + _fadeVolModifier; + _volume &= 0xF0; + if (volNew < 16) + _volume |= volNew; +} + +void MusicChannelSSG::updateVibrato() { + _frequency += _vbrModifier; + uint16 frq = _frequency >> (_note >> 4); + writeDevice(_regOffset, frq & 0xFF); + writeDevice(_regOffset + 1, frq >> 8); +} + +void MusicChannelSSG::clear() { + SoundChannel::clear(); + memcpy(_envDataTemp, _envDataPreset, 96); + _ngState = 0; + _enState = 0x38; + _envStartLvl = _envAR = _envDR = _envSL = _envSR = 0; +} + +void MusicChannelSSG::op_programChange(uint8 *&data) { + uint8 *src = getProgramData(*data++); + _envStartLvl = *src++; + _envAR = *src++; + _envDR = *src++; + _envSL = *src++; + _envSR = *src++; + _envRR = *src++; + _volume |= (kUpdate | kAttack); +} + +void MusicChannelSSG::op_setVolume(uint8 *&data) { + _flags &= ~kEnvelope; + _volume = (_volume & 0xF0) | (*data++); + updateVolume(); +} + +void MusicChannelSSG::op_chanEnable(uint8 *&data) { + uint8 c = (_regOffset >> 1) + 1; + uint8 val = *data++; + val = (val >> 1) | (val << 7); + val = (val << c) | (val >> (8 - c)); + _enState = (((0x7B << c) | (0x7B >> (8 - c))) & _enState) | val; + writeDevice(0x07, _enState); +} + +void MusicChannelSSG::op_setNoiseGenerator(uint8 *&data) { + _ngState = *data++; + writeDevice(0x06, _ngState); +} + +void MusicChannelSSG::op_setInstrument(uint8 *&data) { + _envStartLvl = *data++; + _envAR = *data++; + _envDR = *data++; + _envSL = *data++; + _envSR = *data++; + _envRR = *data++; + _volume |= (kUpdate | kAttack); +} + +void MusicChannelSSG::op_modifyVolume(uint8 *&data) { + if (_flags & kEnvelope) + return; + uint8 newVol = (_volume & 0x0F) + (*data++); + if (newVol > 15) + return; + _volume = (_volume & 0xF0) | newVol; +} + +void MusicChannelSSG::op_loadInstrument(uint8 *&data) { + uint8 *dst = getProgramData(*data++); + memcpy(dst, data, 6); + data += 6; +} + +uint8 *MusicChannelSSG::getProgramData(uint8 program) const { + return _externalPrograms ? _instrBuffer + READ_LE_UINT16(_instrBuffer + 2) + (program << 4) + 1 : _envDataTemp + program * 6; +} + +const uint8 MusicChannelSSG::_envDataPreset[96] = { + 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xc8, 0x00, 0x0a, 0xff, 0xff, 0xff, 0xc8, + 0x01, 0x0a, 0xff, 0xff, 0xff, 0xbe, 0x00, 0x0a, + 0xff, 0xff, 0xff, 0xbe, 0x01, 0x0a, 0xff, 0xff, + 0xff, 0xaa, 0x00, 0x0a, 0x28, 0x46, 0x0e, 0xbe, + 0x00, 0x0f, 0x78, 0x1e, 0xff, 0xff, 0x00, 0x0a, + 0xff, 0xff, 0xff, 0xe1, 0x08, 0x0f, 0xff, 0xff, + 0xff, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, + 0x08, 0xff, 0xff, 0xff, 0xff, 0xdc, 0x14, 0x08, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x0a, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x0a, 0x78, 0x50, 0xff, 0xff, + 0x00, 0xff, 0xff, 0xff, 0xff, 0xdc, 0x00, 0xff +}; + +MusicChannelRHY::MusicChannelRHY(PC98AudioCore *pc98a, int part, int regOffset) : SoundChannelNonSSG(pc98a, part, regOffset, 2), _activeInstruments(0) { + _instrLevel[0] = _instrLevel[1] = _instrLevel[2] = _instrLevel[3] = _instrLevel[4] = _instrLevel[5] = 0xC0; +} + +MusicChannelRHY::~MusicChannelRHY() { +} + +void MusicChannelRHY::keyOff() { + debugC(7, kDebugLevelSound, "RHY Channel 0: keyOff() [Ticks: 0x%02x]", _ticksLeft); + writeDevice(0x10, (_activeInstruments & 0x3F) | 0x80); +} + +void MusicChannelRHY::noteOn(uint8 note) { + if ((_flags2 & kNoSustain) && !_globalBlock) + writeDevice(0x10, _activeInstruments & 0x3F); + _note = note; + debugC(5, kDebugLevelSound, "RHY Channel 0: noteOn() [Note: 0x%02x Ticks: 0x%02x]", _note, _ticksLeft); +} + +void MusicChannelRHY::updateVolume() { + uint8 val = ((MAX<uint8>((uint8)_fadeVolModifier - 1, 239) + 1) << 2) + (_volume & 0x3F); + writeDevice(0x11, val < 64 ? val : 0); +} + +void MusicChannelRHY::op_programChange(uint8 *&data) { + _activeInstruments = _program = *data++; +} + +void MusicChannelRHY::op_setVolume(uint8 *&data) { + _volume = *data++; + updateVolume(); + uint8 *p = _instrLevel; + for (int i = 6; i; --i) { + *p = (*p & 0xC0) | *data++; + writeDevice(0x18 - (i - 6), *p++); + } +} + +void MusicChannelRHY::op_setPanPos(uint8 *&data) { + uint8 val = *data++; + uint8 offs = val & 0x0F; + _instrLevel[offs] = (((val << 2) | (val >> 6)) & 0xC0) | (_instrLevel[offs] & 0x1F); + writeDevice(0x18 + offs, _instrLevel[offs]); +} + +void MusicChannelRHY::op_modifyVolume(uint8 *&data) { + _volume += *data++; + updateVolume(); +} + +MusicChannelEXT::MusicChannelEXT(PC98AudioCore *pc98a, int part, int regOffset, MLALF98::ADPCMData *const &data) : SoundChannelNonSSG(pc98a, part, regOffset, 3), + _pc98a(pc98a), _extBuffer(data), _useVolPreset(0), _volume2(0), _instrument(0), _panPos(3), _smpStart(0), _smpEnd(0) { +} + +MusicChannelEXT::~MusicChannelEXT() { +} + +void MusicChannelEXT::keyOff() { + debugC(7, kDebugLevelSound, "EXT Channel 0: keyOff() [Ticks: 0x%02x]", _ticksLeft); + writeDevice(0x0B, 0x00); + writeDevice(0x01, 0x00); + writeDevice(0x00, 0x21); +} + +void MusicChannelEXT::noteOn(uint8 note) { + static uint16 freqTableEXT[12] = { + 0x4A82, 0x4EE4, 0x5389, 0x5875, 0x5DAC, 0x6332, 0x690C, 0x6F3F, 0x75D1, 0x7C76, 0x8426, 0x8BF5 + }; + + if (!(_flags2 & kNoSustain) && _note == note) + return; + + _note = note; + + debugC(5, kDebugLevelSound, "EXT Channel 0: noteOn() [Note: 0x%02x Ticks: 0x%02x]", _note, _ticksLeft); + + assert((note & 0x0F) < 12); + _frequency = (freqTableEXT[note & 0x0F] + _transpose) >> (note >> 4); + + if (!(_flags2 & kNoSustain)) + vbrResetDelay(); + vbrReset(); + + if (_globalBlock) + return; + + writeDevice(0x0B, 0x00); // vol zero + writeDevice(0x01, 0x00); // ram type default + writeDevice(0x00, 0x21); // 0x20: external memory, 0x01: clear busy flag + writeDevice(0x10, 0x08); // set irq flag mask + writeDevice(0x10, 0x80); // reset irq flag + writeDevice(0x02, _smpStart & 0xFF); + writeDevice(0x03, _smpStart >> 8); + writeDevice(0x04, _smpEnd & 0xFF); + writeDevice(0x05, _smpEnd >> 8); + writeDevice(0x09, _frequency & 0xFF); + writeDevice(0x0A, _frequency >> 8); + writeDevice(0x00, 0xA0); // 0x20: external memory, 0x80: start + + uint8 vol = (uint8)(MAX<int8>(_fadeVolModifier, -16) << 2) + _volume; + if (_volume < vol) + vol = 0; + + if (_useVolPreset) { + vol += _volume2; + if (_volume2 < vol) + vol = 0; + } + + writeDevice(0x0B, vol); + writeDevice(0x01, ((_panPos << 6) | (_panPos >> 2)) & 0xC0); +} + +void MusicChannelEXT::updateVibrato() { + _frequency += _vbrModifier; + writeDevice(0x09, _frequency & 0xFF); + writeDevice(0x0A, _frequency >> 8); +} + +void MusicChannelEXT::clear() { + SoundChannel::clear(); + _panPos = 3; + _useVolPreset = 0; + _volume2 = 0; +} + +void MusicChannelEXT::writeDevice(uint8 reg, uint8 val) { + _pc98a->writeReg(1, reg, val); +} + +void MusicChannelEXT::op_programChange(uint8 *&data) { + _instrument = *data++; + _program = _instrument - 1; + _smpStart = _extBuffer[_program].smpStart; + _smpEnd = _extBuffer[_program].smpEnd; + if (_useVolPreset) + _volume = _extBuffer[_program].volume; +} + +void MusicChannelEXT::op_setVolume(uint8 *&data) { + if (_useVolPreset) + _volume2 = *data; + else + _volume = *data; + data++; +} + +void MusicChannelEXT::op_setPanPos(uint8 *&data) { + _panPos = *data++; +} + +void MusicChannelEXT::op_setTranspose(uint8 *&data) { + _note = 0; + int16 trp = READ_LE_INT16(data); + data += 2; + _transpose = (*data++) ? _transpose + trp : trp; + uint16 val = _frequency + _transpose; + writeDevice(0x09, val & 0xFF); + writeDevice(0x0A, val >> 8); +} + +void MusicChannelEXT::op2_setExtPara(uint8 *&data) { + _useVolPreset = *data++; +} + +SoundEffectChannel::SoundEffectChannel(PC98AudioCore *pc98a, int part, int regOffset, SoundChannel *replaceChannel) : MusicChannelFM(pc98a, part, regOffset), + _replaceChannel(replaceChannel), _specialMode(false), _isFinished(false) { + _specialModeModifier[0] = _specialModeModifier[1] = _specialModeModifier[2] = _specialModeModifier[3] = 0; +} + +SoundEffectChannel::~SoundEffectChannel() { + +} + +void SoundEffectChannel::setData(uint8 *dataStart, uint8 *loopStart, const uint8 *dataEnd, uint8 *instrBuffer) { + _replaceChannel->toggleMute(dataStart); + SoundChannel::setData(dataStart, loopStart, dataEnd, instrBuffer); +} + +bool SoundEffectChannel::checkFinished() { + if (!_isFinished) + return false; + + _replaceChannel->toggleMute(false); + _replaceChannel->restore(); + + _isFinished = false; + return true; +} + +void SoundEffectChannel::finish() { + keyOff(); + _isFinished = true; +} + +bool SoundEffectChannel::usingSpecialMode() const { + return _specialMode; +} + +uint8 SoundEffectChannel::getSpecialFrequencyModifier(uint8 index) { + assert(index < 4); + return _specialModeModifier[index]; +} + +void SoundEffectChannel::setSpecialFrequencyModifier(uint8 index, uint8 val) { + assert(index < 4); + _specialModeModifier[index] = val; +} + +void SoundEffectChannel::toggleSpecialMode(bool on) { + _specialMode = on; + uint8 flag = on ? 0x40 : 0; + writeDevice(0x27, 0x3E + flag); + writeDevice(0x27, 0x3F + flag); +} + +void SoundEffectChannel::clear() { + SoundChannel::clear(); + _specialMode = false; + _statusB4 = 0xC0; +} + +void SoundEffectChannel::writeDevice(uint8 reg, uint8 val) { + SoundChannel::writeDevice(reg, val); +} + +void SoundEffectChannel::op_writeDevice(uint8 *&data) { + uint8 reg = *data++; + uint8 val = *data++; + if (reg != 0x26) + writeDevice(reg, val); + if (reg == 0x25 || reg == 0x26) + toggleSpecialMode(_specialMode); +} + +#define iterateChannels(arr) for (Common::Array<SoundChannel*>::iterator i = arr.begin(); i != arr.end(); ++i) + +MLALF98Internal *MLALF98Internal::_refInstance = 0; +int MLALF98Internal::_refCount = 0; + +MLALF98Internal::MLALF98Internal(Audio::Mixer *mixer, EmuType emuType) : PC98AudioPluginDriver(), _type86(emuType == kType86), + _musicBuffer(0), _sfxBuffer(0), _extBuffer(0), _musicBufferSize(0), _sfxBufferSize(0), _extBufferSize(0), _pc98a(0), _sfxPlaying(0), _ready(false) { + _pc98a = new PC98AudioCore(mixer, this, emuType); + assert(_pc98a); + + _extBuffer = new MLALF98::ADPCMData[8]; + + for (int i = 0; i < 3; ++i) + _musicChannels.push_back(new MusicChannelFM(_pc98a, 0, i)); + for (int i = 3; i < 6; ++i) + _musicChannels.push_back(new MusicChannelSSG(_pc98a, 0, (i - 3) << 1)); + + if (_type86) { + _musicChannels.push_back(new MusicChannelRHY(_pc98a, 0, 0)); + for (int i = 7; i < 10; ++i) + _musicChannels.push_back(new MusicChannelFM(_pc98a, 1, i - 7)); + _musicChannels.push_back(new MusicChannelEXT(_pc98a, 1, 0, _extBuffer)); + } + + int replChanIndex = 2; + _sfxChannels.push_back(new SoundEffectChannel(_pc98a, 0, 2, _musicChannels[replChanIndex++])); + + _pc98a->init(); + + _ready = true; +} + +MLALF98Internal::~MLALF98Internal() { + _ready = false; + + delete _pc98a; + + iterateChannels(_musicChannels) + delete (*i); + + iterateChannels(_sfxChannels) + delete (*i); + + delete[] _musicBuffer; + delete[] _sfxBuffer; + delete[] _extBuffer; +} + +MLALF98Internal *MLALF98Internal::open(Audio::Mixer *mixer, EmuType emuType) { + _refCount++; + + if (_refCount == 1 && _refInstance == 0) + _refInstance = new MLALF98Internal(mixer, emuType); + else if (_refCount < 2 || _refInstance == 0) + error("MLALF98Internal::open(): Internal instance management failure"); + + return _refInstance; +} + +void MLALF98Internal::close() { + if (!_refCount) + return; + + _refCount--; + + if (!_refCount) { + delete _refInstance; + _refInstance = 0; + } +} + +void MLALF98Internal::loadMusicData(Common::SeekableReadStream *data) { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + + if (!data) + error("MLALF98Internal::loadMusicData(): Invalid data."); + if (data->size() == 0) + error("MLALF98Internal::loadMusicData(): Invalid data size."); + + iterateChannels(_musicChannels) + (*i)->setData(0, 0, 0, 0); + + delete[] _musicBuffer; + _musicBufferSize = data->size(); + _musicBuffer = new uint8[_musicBufferSize]; + data->read(_musicBuffer, _musicBufferSize); +} + +void MLALF98Internal::loadSoundEffectData(Common::SeekableReadStream *data) { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + + if (!data) + error("MLALF98Internal::loadSoundEffectData(): Invalid data."); + if (data->size() == 0) + error("MLALF98Internal::loadSoundEffectData(): Invalid data size."); + + iterateChannels(_sfxChannels) + (*i)->setData(0, 0, 0, 0); + + delete[] _sfxBuffer; + _sfxBufferSize = data->size(); + _sfxBuffer = new uint8[_sfxBufferSize]; + data->read(_sfxBuffer, _sfxBufferSize); +} + +void MLALF98Internal::loadExtData(MLALF98::ADPCMDataArray &data) { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + if (data.empty()) + error("MLALF98Internal::loadExtData(): Invalid data."); + + delete[] _extBuffer; + _extBufferSize = data.size(); + _extBuffer = new MLALF98::ADPCMData[_extBufferSize]; + MLALF98::ADPCMData *dst = _extBuffer; + for (MLALF98::ADPCMDataArray::iterator i = data.begin(); i != data.end(); ++i) + *dst++ = *i; + } + +void MLALF98Internal::startMusic(int track) { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + iterateChannels(_musicChannels) { + (*i)->abortFadeOut(); + (*i)->toggleMute(false); + } + + _sfxPlaying = 0; + + _pc98a->writeReg(0, 0x27, 0x3C); + _pc98a->writeReg(0, 0x10, 0x80); + _pc98a->writeReg(0, 0x10, 0x00); + _pc98a->writeReg(0, 0x24, 0x18); + _pc98a->writeReg(0, 0x25, 0x02); + + iterateChannels(_sfxChannels) + (*i)->setData(0, 0, 0, 0); + + iterateChannels(_musicChannels) + (*i)->keyOff(); + iterateChannels(_sfxChannels) + (*i)->keyOff(); + + assert(track * 45 + 5 < _musicBufferSize); + const uint8 *header = _musicBuffer + (track * 45) + 5; + + uint8 tempo = *header++; + iterateChannels(_musicChannels) { + uint16 offset1 = READ_LE_UINT16(header); + assert(offset1 + 5 < _musicBufferSize); + header += 2; + uint16 offset2 = READ_LE_UINT16(header); + assert(offset2 + 5 <= _musicBufferSize); + header += 2; + (*i)->setData(_musicBuffer + 5 + offset1, offset2 ? _musicBuffer + 5 + offset2 : 0, _musicBuffer + _musicBufferSize, _musicBuffer + 1); + } + + debugC(3, kDebugLevelSound, "\nStarting music. Track: %03d", track); + + _pc98a->writeReg(0, 0x29, 0x83); + for (int i = 0; i < 6; ++i) + _pc98a->writeReg(0, i, 0); + _pc98a->writeReg(0, 0x07, 0x38); + _pc98a->writeReg(0, 0x26, tempo); + + for (int i = 0; i < 2; ++i) { + for (int ii = 0; ii < 3; ++ii) + _pc98a->writeReg(i, 0xB4 + ii, 0xC0); + } + + _pc98a->writeReg(0, 0x22, 0x00); + _pc98a->writeReg(0, 0x27, 0x3F); +} + +void MLALF98Internal::fadeOutMusic() { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + iterateChannels(_musicChannels) + (*i)->startFadeOut(); +} + +void MLALF98Internal::startSoundEffect(int track) { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + const uint8 *header = _sfxBuffer + (track << 1) + 3; + uint16 offset = READ_LE_UINT16(header); + assert(offset < _sfxBufferSize); + _sfxPlaying = 0; + int flags = 0; + + iterateChannels(_sfxChannels) { + (*i)->setData(_sfxBuffer + offset, 0, _sfxBuffer + _sfxBufferSize, _sfxBuffer + 1); + flags |= (*i)->idFlag(); + _sfxPlaying++; + } + + debugC(3, kDebugLevelSound, "\nStarting sound effect. Track: %03d", track); + + _pc98a->setSoundEffectChanMask(flags); + + _pc98a->writeReg(0, 0x28, 0x02); + _pc98a->writeReg(0, 0x24, 0x18); + _pc98a->writeReg(0, 0x25, 0x02); + _pc98a->writeReg(0, 0x82, 0x0F); + _pc98a->writeReg(0, 0x86, 0x0F); + _pc98a->writeReg(0, 0x8A, 0x0F); + _pc98a->writeReg(0, 0x8E, 0x0F); + _pc98a->writeReg(0, 0xB6, 0xC0); + _pc98a->writeReg(0, 0x27, 0x3F); +} + +void MLALF98Internal::allChannelsOff() { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + iterateChannels(_musicChannels) + (*i)->keyOff(); + iterateChannels(_sfxChannels) + (*i)->keyOff(); +} + +void MLALF98Internal::resetExtUnit() { + PC98AudioCore::MutexLock lock = _pc98a->stackLockMutex(); + _pc98a->writeReg(1, 0x0B, 0x00); // vol zero + _pc98a->writeReg(1, 0x01, 0x00); // ram type default + _pc98a->writeReg(1, 0x00, 0x21); // 0x20: external memory, 0x01: clear busy flag + _pc98a->writeReg(1, 0x10, 0x08); // set irq flag mask + _pc98a->writeReg(1, 0x10, 0x80); // reset irq flag + _pc98a->writeReg(1, 0x02, _extBuffer[0].smpStart & 0xFF); + _pc98a->writeReg(1, 0x03, _extBuffer[0].smpStart >> 8); + _pc98a->writeReg(1, 0x04, _extBuffer[0].smpEnd & 0xFF); + _pc98a->writeReg(1, 0x05, _extBuffer[0].smpEnd >> 8); + _pc98a->writeReg(1, 0x09, 0xCE); + _pc98a->writeReg(1, 0x0A, 0x49); + _pc98a->writeReg(1, 0x00, 0xA0); // 0x20: external memory, 0x80: start + _pc98a->writeReg(1, 0x0B, _extBuffer[0].volume); + _pc98a->writeReg(1, 0x01, 0xC0); +} + +void MLALF98Internal::setMusicVolume(int volume) { + _pc98a->setMusicVolume(volume); +} + +void MLALF98Internal::setSoundEffectVolume(int volume) { + _pc98a->setSoundEffectVolume(volume); +} + +void MLALF98Internal::timerCallbackA() { + if (!_ready || !_sfxPlaying) + return; + + iterateChannels(_sfxChannels) { + (*i)->update(); + if ((*i)->checkFinished()) { + if (!--_sfxPlaying) { + _pc98a->setSoundEffectChanMask(0); + debugC(3, kDebugLevelSound, "Finished sound effect.\n"); + } + } + } + //_updateCounterSfx++; +} + +void MLALF98Internal::timerCallbackB() { + if (!_ready) + return; + iterateChannels(_musicChannels) + (*i)->update(); + iterateChannels(_musicChannels) + (*i)->updateFadeOut(); + //_updateCounterMusic++; +} + +MLALF98::MLALF98(Audio::Mixer *mixer, EmuType emuType) { + _drv = MLALF98Internal::open(mixer, (PC98AudioPluginDriver::EmuType)emuType); +} + +MLALF98::~MLALF98() { + MLALF98Internal::close(); + _drv = 0; +} + +void MLALF98::loadMusicData(Common::SeekableReadStream *data) { + _drv->loadMusicData(data); +} + +void MLALF98::loadSoundEffectData(Common::SeekableReadStream *data) { + _drv->loadSoundEffectData(data); +} + +void MLALF98::loadExtData(ADPCMDataArray &data) { + _drv->loadExtData(data); +} + +void MLALF98::startMusic(int track) { + _drv->startMusic(track); +} + +void MLALF98::fadeOutMusic() { + _drv->fadeOutMusic(); +} + +void MLALF98::startSoundEffect(int track) { + _drv->startSoundEffect(track); +} + +void MLALF98::allChannelsOff() { + _drv->allChannelsOff(); +} + +void MLALF98::resetExtUnit() { + _drv->resetExtUnit(); +} + +void MLALF98::setMusicVolume(int volume) { + _drv->setMusicVolume(volume); +} + +void MLALF98::setSoundEffectVolume(int volume) { + _drv->setSoundEffectVolume(volume); +} + +#undef iterateChannels + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/sound/drivers/mlalf98.h b/engines/kyra/sound/drivers/mlalf98.h new file mode 100644 index 0000000000..b406f6c2ef --- /dev/null +++ b/engines/kyra/sound/drivers/mlalf98.h @@ -0,0 +1,90 @@ +/* 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. + * + */ + +#ifdef ENABLE_EOB + +#ifndef KYRA_SOUND_MLALF98_H +#define KYRA_SOUND_MLALF98_H + +#include "common/scummsys.h" +#include "common/array.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + class Mixer; +} + +namespace Kyra { + +class MLALF98Internal; + +class MLALF98 { +public: + enum EmuType { + kType9801_26 = 1, + kType9801_86 = 2 + }; + + struct ADPCMData { + ADPCMData() : smpStart(0), smpEnd(0), unk4(0), unk5(0), volume(0), unk7(0) {} + uint16 smpStart; + uint16 smpEnd; + uint8 unk4; + uint8 unk5; + uint8 volume; + uint8 unk7; + }; + + typedef Common::Array<ADPCMData> ADPCMDataArray; + +public: + MLALF98(Audio::Mixer *mixer, EmuType emuType); + ~MLALF98(); + + // The caller has to dispose of the stream. The stream can be discarded + // immediately after calling the respective loader function. + void loadMusicData(Common::SeekableReadStream *data); + void loadSoundEffectData(Common::SeekableReadStream *data); + void loadExtData(ADPCMDataArray &data); + + void startMusic(int track); + void fadeOutMusic(); + void startSoundEffect(int track); + + void allChannelsOff(); + void resetExtUnit(); + + void setMusicVolume(int volume); + void setSoundEffectVolume(int volume); + +private: + MLALF98Internal *_drv; +}; + +} // End of namespace Kyra + +#endif + +#endif |