1947 lines
49 KiB
C++
1947 lines
49 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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() override {}
|
|
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);
|
|
~SoundChannelNonSSG() override;
|
|
|
|
protected:
|
|
void parse() override;
|
|
uint8 _statusB4;
|
|
|
|
private:
|
|
virtual void finish();
|
|
void soundOff();
|
|
|
|
virtual void noteOn(uint8 note) = 0;
|
|
virtual void reduceVolume() {}
|
|
void updateVolume() override {}
|
|
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);
|
|
~MusicChannelFM() override;
|
|
|
|
void restore() override;
|
|
void keyOff() override;
|
|
|
|
private:
|
|
void clear() override;
|
|
void parse() override;
|
|
void noteOn(uint8 note) override;
|
|
void updateVolume() override;
|
|
void reduceVolume() override;
|
|
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();
|
|
|
|
void writeDevice(uint8 reg, uint8 val) override;
|
|
|
|
void op_programChange(uint8 *&data) override;
|
|
void op_setVolume(uint8 *&data) override;
|
|
void op_setSpecialMode(uint8 *&data) override;
|
|
void op_setPanPos(uint8 *&data) override;
|
|
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);
|
|
~MusicChannelSSG() override;
|
|
|
|
void keyOff() override;
|
|
|
|
private:
|
|
enum EnvState {
|
|
kAttack = 0x10,
|
|
kDecay = 0x20,
|
|
kSustain = 0x40,
|
|
kUpdate = 0x80
|
|
};
|
|
|
|
void parse() override;
|
|
void noteOff();
|
|
void noteOn(uint8 note);
|
|
uint8 processEnvelope();
|
|
uint8 envGetAttLevel();
|
|
void envSendAttLevel(uint8 val);
|
|
|
|
void updateVolume() override;
|
|
void updateVibrato() override;
|
|
|
|
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);
|
|
~MusicChannelRHY() override;
|
|
|
|
void keyOff() override;
|
|
|
|
private:
|
|
void noteOn(uint8 note) override;
|
|
void updateVolume() override;
|
|
|
|
void op_programChange(uint8 *&data) override;
|
|
void op_setVolume(uint8 *&data) override;
|
|
void op_setPanPos(uint8 *&data) override;
|
|
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);
|
|
~MusicChannelEXT() override;
|
|
|
|
void keyOff() override;
|
|
|
|
private:
|
|
void noteOn(uint8 note) override;
|
|
void updateVibrato() override;
|
|
void clear() override;
|
|
void writeDevice(uint8 reg, uint8 val) override;
|
|
|
|
void op_programChange(uint8 *&data) override;
|
|
void op_setVolume(uint8 *&data) override;
|
|
void op_setPanPos(uint8 *&data) override;
|
|
void op_setTranspose(uint8 *&data) override;
|
|
|
|
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);
|
|
~SoundEffectChannel() override;
|
|
|
|
void setData(uint8 *dataStart, uint8 *loopStart, const uint8 *dataEnd, uint8 *instrBuffer) override;
|
|
bool checkFinished() override;
|
|
|
|
private:
|
|
void finish() override;
|
|
bool usingSpecialMode() const override;
|
|
uint8 getSpecialFrequencyModifier(uint8 index) override;
|
|
void setSpecialFrequencyModifier(uint8 index, uint8 val) override;
|
|
void toggleSpecialMode(bool on) override;
|
|
void clear() override;
|
|
void writeDevice(uint8 reg, uint8 val) override;
|
|
|
|
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() override;
|
|
|
|
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
|
|
void timerCallbackA() override;
|
|
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(16), _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;
|
|
|
|
_fadeTicker = 16;
|
|
|
|
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]();
|
|
}
|
|
}
|
|
|
|
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
|