mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-30 14:14:43 +00:00
9c8b465505
- FM-Towns euphony driver completely rewritten based on KYRA FM-Towns and LOOM towns disasm. - Split all the emu and driver code from sound_towns.cpp into different files to make things a bit less confusing. - Move the driver code to common space since the exact same euphony driver is used by LOOM which means we could get rid of the outdated and incomplete ym2612 driver/emu implementation (which doesn't even do things like instrument loading, pan position, etc). I haven't tried to add this to the Scumm engine yet, since I am not familiar with it and my priority was to get the driver finished first. But from the look of disasm it shouldn't be difficult to do. - Introduce a generic FM-Towns audio interface based on FM-Towns system file disasm which was necessary for the euphony driver rewrite. Every FM-Towns game I have seen so far seems to access the audio hardware via these system functions. This interface implementation will also allow reasonably simple creation of new FM-Towns audio drivers (e.g. this could be used for Kings Quest 5 FM-Towns or others). - Move the PC98 driver to common space, too, since I have a strong feeling that this driver is also used in the PC98 version of Future Wars - This also improves KYRA FM-Towns music quality, sound effects accuracy and music fading. svn-id: r51645
1404 lines
34 KiB
C++
1404 lines
34 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 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.
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
#include "sound/softsynth/fmtowns_pc98/towns_pc98_driver.h"
|
|
#include "common/endian.h"
|
|
|
|
class TownsPC98_MusicChannel {
|
|
public:
|
|
TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
|
|
uint8 key, uint8 prt, uint8 id);
|
|
virtual ~TownsPC98_MusicChannel();
|
|
virtual void init();
|
|
|
|
typedef enum channelState {
|
|
CHS_RECALCFREQ = 0x01,
|
|
CHS_KEYOFF = 0x02,
|
|
CHS_SSGOFF = 0x04,
|
|
CHS_VBROFF = 0x08,
|
|
CHS_ALLOFF = 0x0f,
|
|
CHS_PROTECT = 0x40,
|
|
CHS_EOT = 0x80
|
|
} ChannelState;
|
|
|
|
virtual void loadData(uint8 *data);
|
|
virtual void processEvents();
|
|
virtual void processFrequency();
|
|
virtual bool processControlEvent(uint8 cmd);
|
|
|
|
virtual void keyOn();
|
|
void keyOff();
|
|
|
|
void setOutputLevel();
|
|
virtual void fadeStep();
|
|
virtual void reset();
|
|
|
|
const uint8 _idFlag;
|
|
|
|
protected:
|
|
void setupVibrato();
|
|
bool processVibrato();
|
|
|
|
bool control_dummy(uint8 para);
|
|
bool control_f0_setPatch(uint8 para);
|
|
bool control_f1_presetOutputLevel(uint8 para);
|
|
bool control_f2_setKeyOffTime(uint8 para);
|
|
bool control_f3_setFreqLSB(uint8 para);
|
|
bool control_f4_setOutputLevel(uint8 para);
|
|
bool control_f5_setTempo(uint8 para);
|
|
bool control_f6_repeatSection(uint8 para);
|
|
bool control_f7_setupVibrato(uint8 para);
|
|
bool control_f8_toggleVibrato(uint8 para);
|
|
bool control_fa_writeReg(uint8 para);
|
|
virtual bool control_fb_incOutLevel(uint8 para);
|
|
virtual bool control_fc_decOutLevel(uint8 para);
|
|
bool control_fd_jump(uint8 para);
|
|
virtual bool control_ff_endOfTrack(uint8 para);
|
|
|
|
uint8 _ticksLeft;
|
|
uint8 _algorithm;
|
|
uint8 _instr;
|
|
uint8 _totalLevel;
|
|
uint8 _frqBlockMSB;
|
|
int8 _frqLSB;
|
|
uint8 _keyOffTime;
|
|
bool _hold;
|
|
uint8 *_dataPtr;
|
|
uint8 _vbrInitDelayHi;
|
|
uint8 _vbrInitDelayLo;
|
|
int16 _vbrModInitVal;
|
|
uint8 _vbrDuration;
|
|
uint8 _vbrCurDelay;
|
|
int16 _vbrModCurVal;
|
|
uint8 _vbrDurLeft;
|
|
uint16 _frequency;
|
|
uint8 _block;
|
|
uint8 _regOffset;
|
|
uint8 _flags;
|
|
uint8 _ssgTl;
|
|
uint8 _ssgStep;
|
|
uint8 _ssgTicksLeft;
|
|
uint8 _ssgTargetLvl;
|
|
uint8 _ssgStartLvl;
|
|
|
|
const uint8 _chanNum;
|
|
const uint8 _keyNum;
|
|
const uint8 _part;
|
|
|
|
TownsPC98_AudioDriver *_drv;
|
|
|
|
typedef bool (TownsPC98_MusicChannel::*ControlEventFunc)(uint8 para);
|
|
const ControlEventFunc *controlEvents;
|
|
};
|
|
|
|
class TownsPC98_MusicChannelSSG : public TownsPC98_MusicChannel {
|
|
public:
|
|
TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs,
|
|
uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
|
|
virtual ~TownsPC98_MusicChannelSSG() {}
|
|
void init();
|
|
|
|
virtual void loadData(uint8 *data);
|
|
void processEvents();
|
|
void processFrequency();
|
|
bool processControlEvent(uint8 cmd);
|
|
|
|
void keyOn();
|
|
void nextShape();
|
|
|
|
void protect();
|
|
void restore();
|
|
virtual void reset();
|
|
|
|
void fadeStep();
|
|
|
|
protected:
|
|
void setOutputLevel(uint8 lvl);
|
|
|
|
bool control_f0_setPatch(uint8 para);
|
|
bool control_f1_setTotalLevel(uint8 para);
|
|
bool control_f4_setAlgorithm(uint8 para);
|
|
bool control_f9_loadCustomPatch(uint8 para);
|
|
bool control_fb_incOutLevel(uint8 para);
|
|
bool control_fc_decOutLevel(uint8 para);
|
|
bool control_ff_endOfTrack(uint8 para);
|
|
|
|
typedef bool (TownsPC98_MusicChannelSSG::*ControlEventFunc)(uint8 para);
|
|
const ControlEventFunc *controlEvents;
|
|
};
|
|
|
|
class TownsPC98_SfxChannel : public TownsPC98_MusicChannelSSG {
|
|
public:
|
|
TownsPC98_SfxChannel(TownsPC98_AudioDriver *driver, uint8 regOffs,
|
|
uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
|
|
TownsPC98_MusicChannelSSG(driver, regOffs, flgs, num, key, prt, id) {}
|
|
~TownsPC98_SfxChannel() {}
|
|
|
|
void loadData(uint8 *data);
|
|
void reset();
|
|
};
|
|
|
|
class TownsPC98_MusicChannelPCM : public TownsPC98_MusicChannel {
|
|
public:
|
|
TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs,
|
|
uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
|
|
~TownsPC98_MusicChannelPCM() {}
|
|
void init();
|
|
|
|
void loadData(uint8 *data);
|
|
void processEvents();
|
|
bool processControlEvent(uint8 cmd);
|
|
|
|
private:
|
|
bool control_f1_prcStart(uint8 para);
|
|
bool control_ff_endOfTrack(uint8 para);
|
|
|
|
typedef bool (TownsPC98_MusicChannelPCM::*ControlEventFunc)(uint8 para);
|
|
const ControlEventFunc *controlEvents;
|
|
};
|
|
|
|
TownsPC98_MusicChannel::TownsPC98_MusicChannel(TownsPC98_AudioDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
|
|
uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key),
|
|
_part(prt), _idFlag(id), controlEvents(0) {
|
|
|
|
_ticksLeft = _algorithm = _instr = _totalLevel = _frqBlockMSB = _keyOffTime = 0;
|
|
_ssgStartLvl = _ssgTl = _ssgStep = _ssgTicksLeft = _ssgTargetLvl = _block = 0;
|
|
_vbrInitDelayHi = _vbrInitDelayLo = _vbrDuration = _vbrCurDelay = _vbrDurLeft = 0;
|
|
_frqLSB = 0;
|
|
_hold = false;
|
|
_dataPtr = 0;
|
|
_vbrModInitVal = _vbrModCurVal = 0;
|
|
_frequency = 0;
|
|
}
|
|
|
|
TownsPC98_MusicChannel::~TownsPC98_MusicChannel() {
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::init() {
|
|
#define Control(x) &TownsPC98_MusicChannel::control_##x
|
|
static const ControlEventFunc ctrlEvents[] = {
|
|
Control(f0_setPatch),
|
|
Control(f1_presetOutputLevel),
|
|
Control(f2_setKeyOffTime),
|
|
Control(f3_setFreqLSB),
|
|
Control(f4_setOutputLevel),
|
|
Control(f5_setTempo),
|
|
Control(f6_repeatSection),
|
|
Control(f7_setupVibrato),
|
|
Control(f8_toggleVibrato),
|
|
Control(dummy),
|
|
Control(fa_writeReg),
|
|
Control(fb_incOutLevel),
|
|
Control(fc_decOutLevel),
|
|
Control(fd_jump),
|
|
Control(dummy),
|
|
Control(ff_endOfTrack)
|
|
};
|
|
#undef Control
|
|
|
|
controlEvents = ctrlEvents;
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::keyOff() {
|
|
// all operators off
|
|
uint8 value = _keyNum & 0x0f;
|
|
if (_part)
|
|
value |= 4;
|
|
uint8 regAddress = 0x28;
|
|
_drv->writeReg(0, regAddress, value);
|
|
_flags |= CHS_KEYOFF;
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::keyOn() {
|
|
// all operators on
|
|
uint8 value = _keyNum | 0xf0;
|
|
if (_part)
|
|
value |= 4;
|
|
uint8 regAddress = 0x28;
|
|
_drv->writeReg(0, regAddress, value);
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::loadData(uint8 *data) {
|
|
_flags = (_flags & ~CHS_EOT) | CHS_ALLOFF;
|
|
_ticksLeft = 1;
|
|
_dataPtr = data;
|
|
_totalLevel = 0x7F;
|
|
|
|
uint8 *tmp = _dataPtr;
|
|
for (bool loop = true; loop; ) {
|
|
uint8 cmd = *tmp++;
|
|
if (cmd < 0xf0) {
|
|
tmp++;
|
|
} else if (cmd == 0xff) {
|
|
if (READ_LE_UINT16(tmp)) {
|
|
_drv->_looping |= _idFlag;
|
|
tmp += _drv->_opnFxCmdLen[cmd - 240];
|
|
} else
|
|
loop = false;
|
|
} else if (cmd == 0xf6) {
|
|
// reset repeat section countdown
|
|
tmp[0] = tmp[1];
|
|
tmp += 4;
|
|
} else {
|
|
tmp += _drv->_opnFxCmdLen[cmd - 240];
|
|
}
|
|
}
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::processEvents() {
|
|
if (_flags & CHS_EOT)
|
|
return;
|
|
|
|
if (!_hold && _ticksLeft == _keyOffTime)
|
|
keyOff();
|
|
|
|
if (--_ticksLeft)
|
|
return;
|
|
|
|
if (!_hold)
|
|
keyOff();
|
|
|
|
uint8 cmd = 0;
|
|
bool loop = true;
|
|
|
|
while (loop) {
|
|
cmd = *_dataPtr++;
|
|
if (cmd < 0xf0)
|
|
loop = false;
|
|
else if (!processControlEvent(cmd))
|
|
return;
|
|
}
|
|
|
|
uint8 para = *_dataPtr++;
|
|
|
|
if (cmd == 0x80) {
|
|
keyOff();
|
|
_hold = false;
|
|
} else {
|
|
keyOn();
|
|
|
|
if (_hold == false || cmd != _frqBlockMSB)
|
|
_flags |= CHS_RECALCFREQ;
|
|
|
|
_hold = (para & 0x80) ? true : false;
|
|
_frqBlockMSB = cmd;
|
|
}
|
|
|
|
_ticksLeft = para & 0x7f;
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::processFrequency() {
|
|
if (_flags & CHS_RECALCFREQ) {
|
|
|
|
_frequency = (((const uint16 *)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f] + _frqLSB) | (((_frqBlockMSB & 0x70) >> 1) << 8);
|
|
|
|
_drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8));
|
|
_drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff));
|
|
|
|
setupVibrato();
|
|
}
|
|
|
|
if (!(_flags & CHS_VBROFF)) {
|
|
if (!processVibrato())
|
|
return;
|
|
|
|
_drv->writeReg(_part, _regOffset + 0xa4, (_frequency >> 8));
|
|
_drv->writeReg(_part, _regOffset + 0xa0, (_frequency & 0xff));
|
|
}
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::setupVibrato() {
|
|
_vbrCurDelay = _vbrInitDelayHi;
|
|
if (_flags & CHS_KEYOFF) {
|
|
_vbrModCurVal = _vbrModInitVal;
|
|
_vbrCurDelay += _vbrInitDelayLo;
|
|
}
|
|
_vbrDurLeft = (_vbrDuration >> 1);
|
|
_flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::processVibrato() {
|
|
if (--_vbrCurDelay)
|
|
return false;
|
|
|
|
_vbrCurDelay = _vbrInitDelayHi;
|
|
_frequency += _vbrModCurVal;
|
|
|
|
if (!--_vbrDurLeft) {
|
|
_vbrDurLeft = _vbrDuration;
|
|
_vbrModCurVal = -_vbrModCurVal;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::processControlEvent(uint8 cmd) {
|
|
uint8 para = *_dataPtr++;
|
|
return (this->*controlEvents[cmd & 0x0f])(para);
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::setOutputLevel() {
|
|
uint8 outopr = _drv->_opnCarrier[_algorithm];
|
|
uint8 reg = 0x40 + _regOffset;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
if (outopr & 1)
|
|
_drv->writeReg(_part, reg, _totalLevel);
|
|
outopr >>= 1;
|
|
reg += 4;
|
|
}
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::fadeStep() {
|
|
_totalLevel += 3;
|
|
if (_totalLevel > 0x7f)
|
|
_totalLevel = 0x7f;
|
|
setOutputLevel();
|
|
}
|
|
|
|
void TownsPC98_MusicChannel::reset() {
|
|
_hold = false;
|
|
_keyOffTime = 0;
|
|
_ticksLeft = 1;
|
|
|
|
_flags = (_flags & ~CHS_EOT) | CHS_ALLOFF;
|
|
|
|
_totalLevel = 0;
|
|
_algorithm = 0;
|
|
_flags = CHS_EOT;
|
|
_algorithm = 0;
|
|
|
|
_block = 0;
|
|
_frequency = 0;
|
|
_frqBlockMSB = 0;
|
|
_frqLSB = 0;
|
|
|
|
_ssgTl = 0;
|
|
_ssgStartLvl = 0;
|
|
_ssgTargetLvl = 0;
|
|
_ssgStep = 0;
|
|
_ssgTicksLeft = 0;
|
|
|
|
_vbrInitDelayHi = 0;
|
|
_vbrInitDelayLo = 0;
|
|
_vbrModInitVal = 0;
|
|
_vbrDuration = 0;
|
|
_vbrCurDelay = 0;
|
|
_vbrModCurVal = 0;
|
|
_vbrDurLeft = 0;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f0_setPatch(uint8 para) {
|
|
_instr = para;
|
|
uint8 reg = _regOffset + 0x80;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
// set release rate for each operator
|
|
_drv->writeReg(_part, reg, 0x0f);
|
|
reg += 4;
|
|
}
|
|
|
|
const uint8 *tptr = _drv->_patches + ((uint32)_instr << 5);
|
|
reg = _regOffset + 0x30;
|
|
|
|
// write registers 0x30 to 0x8f
|
|
for (int i = 0; i < 6; i++) {
|
|
_drv->writeReg(_part, reg, tptr[0]);
|
|
reg += 4;
|
|
_drv->writeReg(_part, reg, tptr[2]);
|
|
reg += 4;
|
|
_drv->writeReg(_part, reg, tptr[1]);
|
|
reg += 4;
|
|
_drv->writeReg(_part, reg, tptr[3]);
|
|
reg += 4;
|
|
tptr += 4;
|
|
}
|
|
|
|
reg = _regOffset + 0xB0;
|
|
_algorithm = tptr[0] & 7;
|
|
// set feedback and algorithm
|
|
_drv->writeReg(_part, reg, tptr[0]);
|
|
|
|
setOutputLevel();
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f1_presetOutputLevel(uint8 para) {
|
|
if (_drv->_fading)
|
|
return true;
|
|
|
|
_totalLevel = _drv->_opnLvlPresets[para];
|
|
setOutputLevel();
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f2_setKeyOffTime(uint8 para) {
|
|
_keyOffTime = para;
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f3_setFreqLSB(uint8 para) {
|
|
_frqLSB = (int8) para;
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f4_setOutputLevel(uint8 para) {
|
|
if (_drv->_fading)
|
|
return true;
|
|
|
|
_totalLevel = para;
|
|
setOutputLevel();
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f5_setTempo(uint8 para) {
|
|
_drv->setMusicTempo(para);
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f6_repeatSection(uint8 para) {
|
|
_dataPtr--;
|
|
_dataPtr[0]--;
|
|
|
|
if (*_dataPtr) {
|
|
// repeat section until counter has reached zero
|
|
_dataPtr = _drv->_trackPtr + READ_LE_UINT16(_dataPtr + 2);
|
|
} else {
|
|
// reset counter, advance to next section
|
|
_dataPtr[0] = _dataPtr[1];
|
|
_dataPtr += 4;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f7_setupVibrato(uint8 para) {
|
|
_vbrInitDelayHi = _dataPtr[0];
|
|
_vbrInitDelayLo = para;
|
|
_vbrModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1);
|
|
_vbrDuration = _dataPtr[3];
|
|
_dataPtr += 4;
|
|
_flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF | CHS_RECALCFREQ;
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_f8_toggleVibrato(uint8 para) {
|
|
if (para == 0x10) {
|
|
if (*_dataPtr++) {
|
|
_flags = (_flags & ~CHS_VBROFF) | CHS_KEYOFF;
|
|
} else {
|
|
_flags |= CHS_VBROFF;
|
|
}
|
|
} else {
|
|
/* NOT IMPLEMENTED
|
|
uint8 skipChannels = para / 36;
|
|
uint8 entry = para % 36;
|
|
TownsPC98_AudioDriver::TownsPC98_MusicChannel *t = &chan[skipChannels];
|
|
|
|
t->unnamedEntries[entry] = *_dataPtr++;*/
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_fa_writeReg(uint8 para) {
|
|
_drv->writeReg(_part, para, *_dataPtr++);
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_fb_incOutLevel(uint8 para) {
|
|
_dataPtr--;
|
|
if (_drv->_fading)
|
|
return true;
|
|
|
|
uint8 val = (_totalLevel + 3);
|
|
if (val > 0x7f)
|
|
val = 0x7f;
|
|
|
|
_totalLevel = val;
|
|
setOutputLevel();
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_fc_decOutLevel(uint8 para) {
|
|
_dataPtr--;
|
|
if (_drv->_fading)
|
|
return true;
|
|
|
|
int8 val = (int8) (_totalLevel - 3);
|
|
if (val < 0)
|
|
val = 0;
|
|
|
|
_totalLevel = (uint8) val;
|
|
setOutputLevel();
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_fd_jump(uint8 para) {
|
|
uint8 *tmp = _drv->_trackPtr + READ_LE_UINT16(_dataPtr - 1);
|
|
_dataPtr = (tmp[1] == 1) ? tmp : (_dataPtr + 1);
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_dummy(uint8 para) {
|
|
_dataPtr--;
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannel::control_ff_endOfTrack(uint8 para) {
|
|
uint16 val = READ_LE_UINT16(--_dataPtr);
|
|
if (val) {
|
|
// loop
|
|
_dataPtr = _drv->_trackPtr + val;
|
|
return true;
|
|
} else {
|
|
// quit parsing for active channel
|
|
--_dataPtr;
|
|
_flags |= CHS_EOT;
|
|
_drv->_finishedChannelsFlag |= _idFlag;
|
|
keyOff();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
TownsPC98_MusicChannelSSG::TownsPC98_MusicChannelSSG(TownsPC98_AudioDriver *driver, uint8 regOffs,
|
|
uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
|
|
TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) {
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::init() {
|
|
_algorithm = 0x80;
|
|
|
|
#define Control(x) &TownsPC98_MusicChannelSSG::control_##x
|
|
static const ControlEventFunc ctrlEventsSSG[] = {
|
|
Control(f0_setPatch),
|
|
Control(f1_setTotalLevel),
|
|
Control(f2_setKeyOffTime),
|
|
Control(f3_setFreqLSB),
|
|
Control(f4_setAlgorithm),
|
|
Control(f5_setTempo),
|
|
Control(f6_repeatSection),
|
|
Control(f7_setupVibrato),
|
|
Control(f8_toggleVibrato),
|
|
Control(f9_loadCustomPatch),
|
|
Control(fa_writeReg),
|
|
Control(fb_incOutLevel),
|
|
Control(fc_decOutLevel),
|
|
Control(fd_jump),
|
|
Control(dummy),
|
|
Control(ff_endOfTrack)
|
|
};
|
|
#undef Control
|
|
|
|
controlEvents = ctrlEventsSSG;
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::processEvents() {
|
|
if (_flags & CHS_EOT)
|
|
return;
|
|
|
|
_drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false);
|
|
|
|
if (!_hold && _ticksLeft == _keyOffTime)
|
|
nextShape();
|
|
|
|
if (!--_ticksLeft) {
|
|
|
|
uint8 cmd = 0;
|
|
bool loop = true;
|
|
|
|
while (loop) {
|
|
cmd = *_dataPtr++;
|
|
if (cmd < 0xf0)
|
|
loop = false;
|
|
else if (!processControlEvent(cmd))
|
|
return;
|
|
}
|
|
|
|
uint8 para = *_dataPtr++;
|
|
|
|
if (cmd == 0x80) {
|
|
nextShape();
|
|
_hold = false;
|
|
} else {
|
|
if (!_hold) {
|
|
_instr &= 0xf0;
|
|
_ssgStep = _drv->_ssgPatches[_instr];
|
|
_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
|
|
_ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
|
|
_ssgStartLvl = _drv->_ssgPatches[_instr + 3];
|
|
_flags = (_flags & ~CHS_SSGOFF) | CHS_KEYOFF;
|
|
}
|
|
|
|
keyOn();
|
|
|
|
if (_hold == false || cmd != _frqBlockMSB)
|
|
_flags |= CHS_RECALCFREQ;
|
|
|
|
_hold = (para & 0x80) ? true : false;
|
|
_frqBlockMSB = cmd;
|
|
}
|
|
|
|
_ticksLeft = para & 0x7f;
|
|
}
|
|
|
|
if (!(_flags & CHS_SSGOFF)) {
|
|
if (--_ssgTicksLeft) {
|
|
if (!_drv->_fading)
|
|
setOutputLevel(_ssgStartLvl);
|
|
return;
|
|
}
|
|
|
|
_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
|
|
|
|
if (_drv->_ssgPatches[_instr + 1] & 0x80) {
|
|
uint8 t = _ssgStartLvl - _ssgStep;
|
|
|
|
if (_ssgStep <= _ssgStartLvl && _ssgTargetLvl < t) {
|
|
if (!_drv->_fading)
|
|
setOutputLevel(t);
|
|
return;
|
|
}
|
|
} else {
|
|
int t = _ssgStartLvl + _ssgStep;
|
|
uint8 p = (uint8) (t & 0xff);
|
|
|
|
if (t < 256 && _ssgTargetLvl > p) {
|
|
if (!_drv->_fading)
|
|
setOutputLevel(p);
|
|
return;
|
|
}
|
|
}
|
|
|
|
setOutputLevel(_ssgTargetLvl);
|
|
if (_ssgStartLvl && !(_instr & 8)){
|
|
_instr += 4;
|
|
_ssgStep = _drv->_ssgPatches[_instr];
|
|
_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
|
|
_ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
|
|
} else {
|
|
_flags |= CHS_SSGOFF;
|
|
setOutputLevel(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::processFrequency() {
|
|
if (_algorithm & 0x40)
|
|
return;
|
|
|
|
if (_flags & CHS_RECALCFREQ) {
|
|
_block = _frqBlockMSB >> 4;
|
|
_frequency = ((const uint16 *)_drv->_opnFreqTableSSG)[_frqBlockMSB & 0x0f] + _frqLSB;
|
|
|
|
uint16 f = _frequency >> _block;
|
|
_drv->writeReg(_part, _regOffset << 1, f & 0xff);
|
|
_drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8);
|
|
|
|
setupVibrato();
|
|
}
|
|
|
|
if (!(_flags & (CHS_EOT | CHS_VBROFF | CHS_SSGOFF))) {
|
|
if (!processVibrato())
|
|
return;
|
|
|
|
uint16 f = _frequency >> _block;
|
|
_drv->writeReg(_part, _regOffset << 1, f & 0xff);
|
|
_drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8);
|
|
}
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::processControlEvent(uint8 cmd) {
|
|
uint8 para = *_dataPtr++;
|
|
return (this->*controlEvents[cmd & 0x0f])(para);
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::nextShape() {
|
|
_instr = (_instr & 0xf0) + 0x0c;
|
|
_ssgStep = _drv->_ssgPatches[_instr];
|
|
_ssgTicksLeft = _drv->_ssgPatches[_instr + 1] & 0x7f;
|
|
_ssgTargetLvl = _drv->_ssgPatches[_instr + 2];
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::keyOn() {
|
|
uint8 c = 0x7b;
|
|
uint8 t = (_algorithm & 0xC0) << 1;
|
|
if (_algorithm & 0x80)
|
|
t |= 4;
|
|
|
|
c = (c << (_regOffset + 1)) | (c >> (7 - _regOffset));
|
|
t = (t << (_regOffset + 1)) | (t >> (7 - _regOffset));
|
|
|
|
if (!(_algorithm & 0x80))
|
|
_drv->writeReg(_part, 6, _algorithm & 0x7f);
|
|
|
|
uint8 e = (_drv->readSSGStatus() & c) | t;
|
|
_drv->writeReg(_part, 7, e);
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::protect() {
|
|
_flags |= CHS_PROTECT;
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::restore() {
|
|
_flags &= ~CHS_PROTECT;
|
|
keyOn();
|
|
_drv->writeReg(_part, 8 + _regOffset, _ssgTl);
|
|
uint16 f = _frequency >> _block;
|
|
_drv->writeReg(_part, _regOffset << 1, f & 0xff);
|
|
_drv->writeReg(_part, (_regOffset << 1) + 1, f >> 8);
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::loadData(uint8 *data) {
|
|
_drv->toggleRegProtection(_flags & CHS_PROTECT ? true : false);
|
|
TownsPC98_MusicChannel::loadData(data);
|
|
setOutputLevel(0);
|
|
_algorithm = 0x80;
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::setOutputLevel(uint8 lvl) {
|
|
_ssgStartLvl = lvl;
|
|
uint16 newTl = (((uint16)_totalLevel + 1) * (uint16)lvl) >> 8;
|
|
if (newTl == _ssgTl)
|
|
return;
|
|
_ssgTl = newTl;
|
|
_drv->writeReg(_part, 8 + _regOffset, _ssgTl);
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::reset() {
|
|
TownsPC98_MusicChannel::reset();
|
|
|
|
// Unlike the original we restore the default patch data. This fixes a bug
|
|
// where certain sound effects would bring each other out of tune (e.g. the
|
|
// dragon's fire in Darm's house in Kyra 1 would sound different each time
|
|
// you triggered another sfx by dropping an item etc.)
|
|
uint8 i = (10 + _regOffset) << 4;
|
|
const uint8 *src = &_drv->_drvTables[156];
|
|
_drv->_ssgPatches[i] = src[i];
|
|
_drv->_ssgPatches[i + 3] = src[i + 3];
|
|
_drv->_ssgPatches[i + 4] = src[i + 4];
|
|
_drv->_ssgPatches[i + 6] = src[i + 6];
|
|
_drv->_ssgPatches[i + 8] = src[i + 8];
|
|
_drv->_ssgPatches[i + 12] = src[i + 12];
|
|
}
|
|
|
|
void TownsPC98_MusicChannelSSG::fadeStep() {
|
|
_totalLevel--;
|
|
if ((int8)_totalLevel < 0)
|
|
_totalLevel = 0;
|
|
setOutputLevel(_ssgStartLvl);
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::control_f0_setPatch(uint8 para) {
|
|
_instr = para << 4;
|
|
para = (para >> 3) & 0x1e;
|
|
if (para)
|
|
return control_f4_setAlgorithm(para | 0x40);
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::control_f1_setTotalLevel(uint8 para) {
|
|
if (!_drv->_fading)
|
|
_totalLevel = para;
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::control_f4_setAlgorithm(uint8 para) {
|
|
_algorithm = para;
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::control_f9_loadCustomPatch(uint8 para) {
|
|
_instr = (_drv->_sfxOffs + 10 + _regOffset) << 4;
|
|
_drv->_ssgPatches[_instr] = *_dataPtr++;
|
|
_drv->_ssgPatches[_instr + 3] = para;
|
|
_drv->_ssgPatches[_instr + 4] = *_dataPtr++;
|
|
_drv->_ssgPatches[_instr + 6] = *_dataPtr++;
|
|
_drv->_ssgPatches[_instr + 8] = *_dataPtr++;
|
|
_drv->_ssgPatches[_instr + 12] = *_dataPtr++;
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::control_fb_incOutLevel(uint8 para) {
|
|
_dataPtr--;
|
|
if (_drv->_fading)
|
|
return true;
|
|
|
|
_totalLevel--;
|
|
if ((int8)_totalLevel < 0)
|
|
_totalLevel = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::control_fc_decOutLevel(uint8 para) {
|
|
_dataPtr--;
|
|
if (_drv->_fading)
|
|
return true;
|
|
|
|
if (_totalLevel + 1 < 0x10)
|
|
_totalLevel++;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelSSG::control_ff_endOfTrack(uint8 para) {
|
|
if (!_drv->_sfxOffs) {
|
|
uint16 val = READ_LE_UINT16(--_dataPtr);
|
|
if (val) {
|
|
// loop
|
|
_dataPtr = _drv->_trackPtr + val;
|
|
return true;
|
|
} else {
|
|
// stop parsing
|
|
if (!_drv->_fading)
|
|
setOutputLevel(0);
|
|
--_dataPtr;
|
|
_flags |= CHS_EOT;
|
|
_drv->_finishedSSGFlag |= _idFlag;
|
|
}
|
|
} else {
|
|
// end of sfx track - restore ssg music channel
|
|
_flags |= CHS_EOT;
|
|
_drv->_finishedSfxFlag |= _idFlag;
|
|
_drv->_ssgChannels[_chanNum]->restore();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TownsPC98_SfxChannel::loadData(uint8 *data) {
|
|
_flags = CHS_ALLOFF;
|
|
_ticksLeft = 1;
|
|
_dataPtr = data;
|
|
_ssgTl = 0xff;
|
|
_algorithm = 0x80;
|
|
|
|
uint8 *tmp = _dataPtr;
|
|
for (bool loop = true; loop; ) {
|
|
uint8 cmd = *tmp++;
|
|
if (cmd < 0xf0) {
|
|
tmp++;
|
|
} else if (cmd == 0xff) {
|
|
loop = false;
|
|
} else if (cmd == 0xf6) {
|
|
// reset repeat section countdown
|
|
tmp[0] = tmp[1];
|
|
tmp += 4;
|
|
} else {
|
|
tmp += _drv->_opnFxCmdLen[cmd - 240];
|
|
}
|
|
}
|
|
}
|
|
|
|
void TownsPC98_SfxChannel::reset() {
|
|
TownsPC98_MusicChannel::reset();
|
|
|
|
// Unlike the original we restore the default patch data. This fixes a bug
|
|
// where certain sound effects would bring each other out of tune (e.g. the
|
|
// dragon's fire in Darm's house in Kyra 1 would sound different each time
|
|
// you triggered another sfx by dropping an item etc.)
|
|
uint8 i = (13 + _regOffset) << 4;
|
|
const uint8 *src = &_drv->_drvTables[156];
|
|
_drv->_ssgPatches[i] = src[i];
|
|
_drv->_ssgPatches[i + 3] = src[i + 3];
|
|
_drv->_ssgPatches[i + 4] = src[i + 4];
|
|
_drv->_ssgPatches[i + 6] = src[i + 6];
|
|
_drv->_ssgPatches[i + 8] = src[i + 8];
|
|
_drv->_ssgPatches[i + 12] = src[i + 12];
|
|
}
|
|
|
|
TownsPC98_MusicChannelPCM::TownsPC98_MusicChannelPCM(TownsPC98_AudioDriver *driver, uint8 regOffs,
|
|
uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
|
|
TownsPC98_MusicChannel(driver, regOffs, flgs, num, key, prt, id), controlEvents(0) {
|
|
}
|
|
|
|
void TownsPC98_MusicChannelPCM::init() {
|
|
_algorithm = 0x80;
|
|
|
|
#define Control(x) &TownsPC98_MusicChannelPCM::control_##x
|
|
static const ControlEventFunc ctrlEventsPCM[] = {
|
|
Control(dummy),
|
|
Control(f1_prcStart),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(f6_repeatSection),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(fa_writeReg),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(dummy),
|
|
Control(ff_endOfTrack)
|
|
};
|
|
#undef Control
|
|
|
|
controlEvents = ctrlEventsPCM;
|
|
}
|
|
|
|
void TownsPC98_MusicChannelPCM::loadData(uint8 *data) {
|
|
_flags = (_flags & ~CHS_EOT) | CHS_ALLOFF;
|
|
_ticksLeft = 1;
|
|
_dataPtr = data;
|
|
_totalLevel = 0x7F;
|
|
}
|
|
|
|
void TownsPC98_MusicChannelPCM::processEvents() {
|
|
if (_flags & CHS_EOT)
|
|
return;
|
|
|
|
if (--_ticksLeft)
|
|
return;
|
|
|
|
uint8 cmd = 0;
|
|
bool loop = true;
|
|
|
|
while (loop) {
|
|
cmd = *_dataPtr++;
|
|
if (cmd == 0x80) {
|
|
loop = false;
|
|
} else if (cmd < 0xf0) {
|
|
_drv->writeReg(_part, 0x10, cmd);
|
|
} else if (!processControlEvent(cmd)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
_ticksLeft = *_dataPtr++;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelPCM::processControlEvent(uint8 cmd) {
|
|
uint8 para = *_dataPtr++;
|
|
return (this->*controlEvents[cmd & 0x0f])(para);
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelPCM::control_f1_prcStart(uint8 para) {
|
|
_totalLevel = para;
|
|
_drv->writeReg(_part, 0x11, para);
|
|
return true;
|
|
}
|
|
|
|
bool TownsPC98_MusicChannelPCM::control_ff_endOfTrack(uint8 para) {
|
|
uint16 val = READ_LE_UINT16(--_dataPtr);
|
|
if (val) {
|
|
// loop
|
|
_dataPtr = _drv->_trackPtr + val;
|
|
return true;
|
|
} else {
|
|
// quit parsing for active channel
|
|
--_dataPtr;
|
|
_flags |= CHS_EOT;
|
|
_drv->_finishedRhythmFlag |= _idFlag;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
TownsPC98_AudioDriver::TownsPC98_AudioDriver(Audio::Mixer *mixer, EmuType type) : TownsPC98_FmSynth(mixer, type),
|
|
_channels(0), _ssgChannels(0), _sfxChannels(0), _rhythmChannel(0),
|
|
_trackPtr(0), _sfxData(0), _sfxOffs(0), _ssgPatches(0),
|
|
_patches(0), _sfxBuffer(0), _musicBuffer(0),
|
|
|
|
_opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 108), _opnFreqTableSSG(_drvTables + 132),
|
|
_opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == kTypeTowns ? 52 : 84)),
|
|
|
|
_updateChannelsFlag(type == kType26 ? 0x07 : 0x3F), _finishedChannelsFlag(0),
|
|
_updateSSGFlag(type == kTypeTowns ? 0x00 : 0x07), _finishedSSGFlag(0),
|
|
_updateRhythmFlag(type == kType86 ? 0x01 : 0x00), _finishedRhythmFlag(0),
|
|
_updateSfxFlag(0), _finishedSfxFlag(0),
|
|
|
|
_musicTickCounter(0),
|
|
|
|
_musicVolume(255), _sfxVolume(255),
|
|
|
|
_musicPlaying(false), _sfxPlaying(false), _fading(false), _looping(0), _ready(false) {
|
|
|
|
_sfxOffsets[0] = _sfxOffsets[1] = 0;
|
|
}
|
|
|
|
TownsPC98_AudioDriver::~TownsPC98_AudioDriver() {
|
|
reset();
|
|
|
|
if (_channels) {
|
|
for (int i = 0; i < _numChan; i++)
|
|
delete _channels[i];
|
|
delete[] _channels;
|
|
}
|
|
|
|
if (_ssgChannels) {
|
|
for (int i = 0; i < _numSSG; i++)
|
|
delete _ssgChannels[i];
|
|
delete[] _ssgChannels;
|
|
}
|
|
|
|
if (_sfxChannels) {
|
|
for (int i = 0; i < 2; i++)
|
|
delete _sfxChannels[i];
|
|
delete[] _sfxChannels;
|
|
}
|
|
|
|
delete _rhythmChannel;
|
|
|
|
delete[] _ssgPatches;
|
|
}
|
|
|
|
bool TownsPC98_AudioDriver::init() {
|
|
if (_ready) {
|
|
reset();
|
|
return true;
|
|
}
|
|
|
|
TownsPC98_FmSynth::init();
|
|
|
|
setVolumeChannelMasks(-1, 0);
|
|
|
|
_channels = new TownsPC98_MusicChannel *[_numChan];
|
|
for (int i = 0; i < _numChan; i++) {
|
|
int ii = i * 6;
|
|
_channels[i] = new TownsPC98_MusicChannel(this, _drvTables[ii], _drvTables[ii + 1],
|
|
_drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
|
|
_channels[i]->init();
|
|
}
|
|
|
|
if (_numSSG) {
|
|
_ssgPatches = new uint8[256];
|
|
memcpy(_ssgPatches, _drvTables + 156, 256);
|
|
|
|
_ssgChannels = new TownsPC98_MusicChannelSSG *[_numSSG];
|
|
for (int i = 0; i < _numSSG; i++) {
|
|
int ii = i * 6;
|
|
_ssgChannels[i] = new TownsPC98_MusicChannelSSG(this, _drvTables[ii], _drvTables[ii + 1],
|
|
_drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
|
|
_ssgChannels[i]->init();
|
|
}
|
|
|
|
_sfxChannels = new TownsPC98_SfxChannel *[2];
|
|
for (int i = 0; i < 2; i++) {
|
|
int ii = (i + 1) * 6;
|
|
_sfxChannels[i] = new TownsPC98_SfxChannel(this, _drvTables[ii], _drvTables[ii + 1],
|
|
_drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
|
|
_sfxChannels[i]->init();
|
|
}
|
|
}
|
|
|
|
if (_hasPercussion) {
|
|
_rhythmChannel = new TownsPC98_MusicChannelPCM(this, 0, 0, 0, 0, 0, 1);
|
|
_rhythmChannel->init();
|
|
}
|
|
|
|
setMusicTempo(84);
|
|
setSfxTempo(654);
|
|
|
|
_ready = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::loadMusicData(uint8 *data, bool loadPaused) {
|
|
if (!_ready) {
|
|
warning("TownsPC98_AudioDriver: Driver must be initialized before loading data");
|
|
return;
|
|
}
|
|
|
|
if (!data) {
|
|
warning("TownsPC98_AudioDriver: Invalid music file data");
|
|
return;
|
|
}
|
|
|
|
reset();
|
|
|
|
Common::StackLock lock(_mutex);
|
|
uint8 *src_a = _trackPtr = _musicBuffer = data;
|
|
|
|
for (uint8 i = 0; i < 3; i++) {
|
|
_channels[i]->loadData(data + READ_LE_UINT16(src_a));
|
|
src_a += 2;
|
|
}
|
|
|
|
for (int i = 0; i < _numSSG; i++) {
|
|
_ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a));
|
|
src_a += 2;
|
|
}
|
|
|
|
for (uint8 i = 3; i < _numChan; i++) {
|
|
_channels[i]->loadData(data + READ_LE_UINT16(src_a));
|
|
src_a += 2;
|
|
}
|
|
|
|
if (_hasPercussion) {
|
|
_rhythmChannel->loadData(data + READ_LE_UINT16(src_a));
|
|
src_a += 2;
|
|
}
|
|
|
|
toggleRegProtection(false);
|
|
|
|
_patches = src_a + 4;
|
|
_finishedChannelsFlag = _finishedSSGFlag = _finishedRhythmFlag = 0;
|
|
|
|
_musicPlaying = (loadPaused ? false : true);
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::loadSoundEffectData(uint8 *data, uint8 trackNum) {
|
|
if (!_ready) {
|
|
warning("TownsPC98_AudioDriver: Driver must be initialized before loading data");
|
|
return;
|
|
}
|
|
|
|
if (!_sfxChannels) {
|
|
warning("TownsPC98_AudioDriver: Sound effects not supported by this configuration");
|
|
return;
|
|
}
|
|
|
|
if (!data) {
|
|
warning("TownsPC98_AudioDriver: Invalid sound effects file data");
|
|
return;
|
|
}
|
|
|
|
Common::StackLock lock(_mutex);
|
|
_sfxData = _sfxBuffer = data;
|
|
_sfxOffsets[0] = READ_LE_UINT16(&_sfxData[(trackNum << 2)]);
|
|
_sfxOffsets[1] = READ_LE_UINT16(&_sfxData[(trackNum << 2) + 2]);
|
|
_sfxPlaying = true;
|
|
_finishedSfxFlag = 0;
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::reset() {
|
|
Common::StackLock lock(_mutex);
|
|
|
|
_musicPlaying = false;
|
|
_sfxPlaying = false;
|
|
_fading = false;
|
|
_looping = 0;
|
|
_musicTickCounter = 0;
|
|
_sfxData = 0;
|
|
|
|
TownsPC98_FmSynth::reset();
|
|
|
|
for (int i = 0; i < _numChan; i++)
|
|
_channels[i]->reset();
|
|
for (int i = 0; i < _numSSG; i++)
|
|
_ssgChannels[i]->reset();
|
|
|
|
if (_numSSG) {
|
|
for (int i = 0; i < 2; i++)
|
|
_sfxChannels[i]->reset();
|
|
|
|
memcpy(_ssgPatches, _drvTables + 156, 256);
|
|
}
|
|
|
|
if (_rhythmChannel)
|
|
_rhythmChannel->reset();
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::fadeStep() {
|
|
if (!_musicPlaying)
|
|
return;
|
|
|
|
Common::StackLock lock(_mutex);
|
|
for (int j = 0; j < _numChan; j++) {
|
|
if (_updateChannelsFlag & _channels[j]->_idFlag)
|
|
_channels[j]->fadeStep();
|
|
}
|
|
|
|
for (int j = 0; j < _numSSG; j++) {
|
|
if (_updateSSGFlag & _ssgChannels[j]->_idFlag)
|
|
_ssgChannels[j]->fadeStep();
|
|
}
|
|
|
|
if (!_fading) {
|
|
_fading = 19;
|
|
if (_hasPercussion) {
|
|
if (_updateRhythmFlag & _rhythmChannel->_idFlag)
|
|
_rhythmChannel->reset();
|
|
}
|
|
} else {
|
|
if (!--_fading)
|
|
reset();
|
|
}
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::timerCallbackB() {
|
|
_sfxOffs = 0;
|
|
|
|
if (_musicPlaying) {
|
|
_musicTickCounter++;
|
|
|
|
for (int i = 0; i < _numChan; i++) {
|
|
if (_updateChannelsFlag & _channels[i]->_idFlag) {
|
|
_channels[i]->processEvents();
|
|
_channels[i]->processFrequency();
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < _numSSG; i++) {
|
|
if (_updateSSGFlag & _ssgChannels[i]->_idFlag) {
|
|
_ssgChannels[i]->processEvents();
|
|
_ssgChannels[i]->processFrequency();
|
|
}
|
|
}
|
|
|
|
if (_hasPercussion)
|
|
if (_updateRhythmFlag & _rhythmChannel->_idFlag)
|
|
_rhythmChannel->processEvents();
|
|
}
|
|
|
|
toggleRegProtection(false);
|
|
|
|
if (_finishedChannelsFlag == _updateChannelsFlag && _finishedSSGFlag == _updateSSGFlag && _finishedRhythmFlag == _updateRhythmFlag)
|
|
_musicPlaying = false;
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::timerCallbackA() {
|
|
if (_sfxChannels && _sfxPlaying) {
|
|
if (_sfxData)
|
|
startSoundEffect();
|
|
|
|
_sfxOffs = 3;
|
|
_trackPtr = _sfxBuffer;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (_updateSfxFlag & _sfxChannels[i]->_idFlag) {
|
|
_sfxChannels[i]->processEvents();
|
|
_sfxChannels[i]->processFrequency();
|
|
}
|
|
}
|
|
|
|
_trackPtr = _musicBuffer;
|
|
}
|
|
|
|
if (_updateSfxFlag && _finishedSfxFlag == _updateSfxFlag) {
|
|
_sfxPlaying = false;
|
|
_updateSfxFlag = 0;
|
|
setVolumeChannelMasks(-1, 0);
|
|
}
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::setMusicTempo(uint8 tempo) {
|
|
writeReg(0, 0x26, tempo);
|
|
writeReg(0, 0x27, 0x33);
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::setSfxTempo(uint16 tempo) {
|
|
writeReg(0, 0x24, tempo & 0xff);
|
|
writeReg(0, 0x25, tempo >> 8);
|
|
writeReg(0, 0x27, 0x33);
|
|
}
|
|
|
|
void TownsPC98_AudioDriver::startSoundEffect() {
|
|
int volFlags = 0;
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (_sfxOffsets[i]) {
|
|
_ssgChannels[i + 1]->protect();
|
|
_sfxChannels[i]->reset();
|
|
_sfxChannels[i]->loadData(_sfxData + _sfxOffsets[i]);
|
|
_updateSfxFlag |= _sfxChannels[i]->_idFlag;
|
|
volFlags |= (_sfxChannels[i]->_idFlag << _numChan);
|
|
} else {
|
|
_ssgChannels[i + 1]->restore();
|
|
_updateSfxFlag &= ~_sfxChannels[i]->_idFlag;
|
|
}
|
|
}
|
|
|
|
setVolumeChannelMasks(~volFlags, volFlags);
|
|
_sfxData = 0;
|
|
}
|
|
|
|
const uint8 TownsPC98_AudioDriver::_drvTables[] = {
|
|
// channel presets
|
|
0x00, 0x80, 0x00, 0x00, 0x00, 0x01,
|
|
0x01, 0x80, 0x01, 0x01, 0x00, 0x02,
|
|
0x02, 0x80, 0x02, 0x02, 0x00, 0x04,
|
|
0x00, 0x80, 0x03, 0x04, 0x01, 0x08,
|
|
0x01, 0x80, 0x04, 0x05, 0x01, 0x10,
|
|
0x02, 0x80, 0x05, 0x06, 0x01, 0x20,
|
|
|
|
// control event size
|
|
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05,
|
|
0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02,
|
|
|
|
// fmt level presets
|
|
0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38,
|
|
0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18,
|
|
0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90,
|
|
|
|
// carriers
|
|
0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F,
|
|
|
|
// pc98 level presets
|
|
0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25,
|
|
0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10,
|
|
0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90,
|
|
|
|
// frequencies
|
|
0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02, 0xDF, 0x02,
|
|
0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03,
|
|
0xD5, 0x03, 0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04,
|
|
|
|
// ssg frequencies
|
|
0xE8, 0x0E, 0x12, 0x0E, 0x48, 0x0D, 0x89, 0x0C,
|
|
0xD5, 0x0B, 0x2B, 0x0B, 0x8A, 0x0A, 0xF3, 0x09,
|
|
0x64, 0x09, 0xDD, 0x08, 0x5E, 0x08, 0xE6, 0x07,
|
|
|
|
// ssg patch data
|
|
0x00, 0x00, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
|
|
0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00,
|
|
0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0x37, 0x81, 0xC8, 0x00,
|
|
0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
|
|
0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
|
|
0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
|
|
0x04, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xBE, 0x00,
|
|
0x0A, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0x01, 0x00,
|
|
0xFF, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
|
|
0xFF, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0x00,
|
|
0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x64, 0x01, 0xFF, 0x64, 0xFF, 0x81, 0xFF, 0x00,
|
|
0x01, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
|
|
0x02, 0x01, 0xFF, 0x28, 0xFF, 0x81, 0xF0, 0x00,
|
|
0x00, 0x81, 0x00, 0x00, 0x0A, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x81, 0xC8, 0x00,
|
|
0x01, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0x78, 0x5F, 0x81, 0xA0, 0x00,
|
|
0x05, 0x81, 0x00, 0x00, 0x28, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
|
|
0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
|
|
0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00,
|
|
0x00, 0x01, 0xFF, 0xFF, 0x00, 0x81, 0x00, 0x00,
|
|
0x00, 0x81, 0x00, 0x00, 0xFF, 0x81, 0x00, 0x00
|
|
};
|
|
|
|
#undef EUPHONY_FADEOUT_TICKS
|
|
|