aboutsummaryrefslogtreecommitdiff
path: root/audio
diff options
context:
space:
mode:
authorathrxx2019-09-09 22:11:29 +0200
committerathrxx2019-12-18 20:50:39 +0100
commit711034b74dd291286943a5b4aa17077ef05a39b2 (patch)
treea621b47a363244ab84f0c71ae220d5fe98c15b98 /audio
parentd67885f4f3d1be173f6b8e8688da5f18f34b9350 (diff)
downloadscummvm-rg350-711034b74dd291286943a5b4aa17077ef05a39b2.tar.gz
scummvm-rg350-711034b74dd291286943a5b4aa17077ef05a39b2.tar.bz2
scummvm-rg350-711034b74dd291286943a5b4aa17077ef05a39b2.zip
AUDIO: (FM-TOWNS/PC-98) - improve sound quality
- Increase internal sample rate to dividers of the actual chip clocks and fix other related things. This seems to improve certain sfx/noise generator like sounds. The performance still seems to be okay. - Fix feedback glitch that caused some noise with certain instrument patches when playing short notes. - Fix squarewave sound glitch (mute channels when volume is zero; this could also cause unnecessary noise). - Some cleanup.
Diffstat (limited to 'audio')
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_audio.cpp20
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp639
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h63
3 files changed, 434 insertions, 288 deletions
diff --git a/audio/softsynth/fmtowns_pc98/towns_audio.cpp b/audio/softsynth/fmtowns_pc98/towns_audio.cpp
index 58a3a804f9..cc847e39fe 100644
--- a/audio/softsynth/fmtowns_pc98/towns_audio.cpp
+++ b/audio/softsynth/fmtowns_pc98/towns_audio.cpp
@@ -253,9 +253,8 @@ private:
uint8 _outputMute[16];
bool _updateOutputVol;
- const float _baserate;
- uint32 _timerBase;
- uint32 _tickLength;
+ const uint32 _tickLength;
+ const uint32 _envDuration;
uint32 _timer;
uint16 _musicVolume;
@@ -276,11 +275,9 @@ private:
};
TownsAudioInterfaceInternal::TownsAudioInterfaceInternal(Audio::Mixer *mixer, TownsAudioInterface *owner, TownsAudioInterfacePluginDriver *driver) :
- TownsPC98_FmSynth(mixer, kTypeTowns),
- _fmInstruments(0), _pcmInstruments(0), _pcmChan(0), _waveTables(0), _waveTablesTotalDataSize(0),
- _baserate(55125.0f / (float)mixer->getOutputRate()), _tickLength(0), _timer(0), _drv(driver), _drvOwner(owner),
- _pcmSfxChanMask(0), _musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume),
- _outputVolumeFlags(0), _fmChanPlaying(0),
+ TownsPC98_FmSynth(mixer, kTypeTowns), _fmInstruments(0), _pcmInstruments(0), _pcmChan(0), _waveTables(0), _waveTablesTotalDataSize(0),
+ _tickLength(0x08), _envDuration(0x30), _timer(0), _drv(driver), _drvOwner(owner), _pcmSfxChanMask(0), _outputVolumeFlags(0),
+ _fmChanPlaying(0), _musicVolume(Audio::Mixer::kMaxMixerVolume), _sfxVolume(Audio::Mixer::kMaxMixerVolume),
_numReservedChannels(0), _numWaveTables(0), _updateOutputVol(false), _ready(false) {
#define INTCB(x) &TownsAudioInterfaceInternal::intf_##x
@@ -398,9 +395,6 @@ TownsAudioInterfaceInternal::TownsAudioInterfaceInternal(Audio::Mixer *mixer, To
memset(_fmChanPitch, 0, sizeof(_fmChanPitch));
memset(_outputLevel, 0, sizeof(_outputLevel));
memset(_outputMute, 0, sizeof(_outputMute));
-
- _timerBase = (uint32)(_baserate * 1000000.0f);
- _tickLength = 2 * _timerBase;
}
TownsAudioInterfaceInternal::~TownsAudioInterfaceInternal() {
@@ -546,8 +540,8 @@ void TownsAudioInterfaceInternal::nextTickEx(int32 *buffer, uint32 bufferSize) {
for (uint32 i = 0; i < bufferSize; i++) {
_timer += _tickLength;
- while (_timer > 0x514767) {
- _timer -= 0x514767;
+ while (_timer >= _envDuration) {
+ _timer -= _envDuration;
for (int ii = 0; ii < 8; ii++)
_pcmChan[ii].updateOutput();
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
index 540c1d943a..0ecf0e45c9 100644
--- a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
@@ -27,7 +27,7 @@
class TownsPC98_FmSynthOperator {
public:
- TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt, const uint8 *rateTable, const uint8 *shiftTable,
+ TownsPC98_FmSynthOperator(const uint32 tickLength, const uint32 envduration, const uint8 *rateTable, const uint8 *shiftTable,
const uint8 *attackDecayTable, const uint32 *frqTable, const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable);
~TownsPC98_FmSynthOperator() {}
@@ -57,7 +57,7 @@ protected:
void frequency(int freq);
EnvelopeState _state;
- bool _holdKey;
+ bool _keyOn;
uint32 _feedbackLevel;
uint32 _multiple;
uint32 _totalLevel;
@@ -90,9 +90,9 @@ protected:
const int32 *_detnTbl;
const uint32 _tickLength;
- uint32 _timer;
- const uint32 _rtt;
+ const uint32 _envDuration;
int32 _currentLevel;
+ uint32 _timer;
struct EvpState {
uint8 rate;
@@ -100,12 +100,12 @@ protected:
} fs_a, fs_d, fs_s, fs_r;
};
-TownsPC98_FmSynthOperator::TownsPC98_FmSynthOperator(const uint32 timerbase, const uint32 rtt, const uint8 *rateTable, const uint8 *shiftTable,
+TownsPC98_FmSynthOperator::TownsPC98_FmSynthOperator(const uint32 tickLength, const uint32 envduration, const uint8 *rateTable, const uint8 *shiftTable,
const uint8 *attackDecayTable, const uint32 *frqTable, const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) :
- _rtt(rtt), _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable), _sinTbl(sineTable),
- _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(timerbase * 2), _specifiedAttackRate(0), _specifiedDecayRate(0),
+ _envDuration(envduration), _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable), _sinTbl(sineTable),
+ _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(tickLength), _specifiedAttackRate(0), _specifiedDecayRate(0),
_specifiedReleaseRate(0), _envelopeShapeSpecs(0), _specifiedSustainRate(0), _sustainLevel(0), _phase(0), _shapeState(0),
- _shapeScale(0), _state(kEnvReady), _holdKey(false), _timer(0), _keyScale1(0), _keyScale2(0), _freqTemp(0),
+ _shapeScale(0), _state(kEnvReady), _keyOn(false), _timer(0), _keyScale1(0), _keyScale2(0), _freqTemp(0),
_currentLevel(1023), _ampMod(false), _tickCount(0), _phaseIncrement(0) {
fs_a.rate = fs_a.shift = fs_d.rate = fs_d.shift = fs_s.rate = fs_s.shift = fs_r.rate = fs_r.shift = 0;
@@ -113,20 +113,25 @@ TownsPC98_FmSynthOperator::TownsPC98_FmSynthOperator(const uint32 timerbase, con
}
void TownsPC98_FmSynthOperator::keyOn() {
- if (_holdKey)
+ if (_keyOn)
return;
-
- _holdKey = true;
+
+ _keyOn = true;
+ //if (_state == kEnvReady)
+ _phase = 0;
+ //if (_state == kEnvReleasing) {
+ // int32 unused = 0;
+ // generateOutput(0, 0, unused);
+ //}
_state = kEnvAttacking;
- _phase = 0;
_shapeState = _envelopeShapeSpecs;
}
void TownsPC98_FmSynthOperator::keyOff() {
- if (!_holdKey)
+ if (!_keyOn)
return;
- _holdKey = false;
+ _keyOn = false;
if (_state != kEnvReady)
_state = kEnvReleasing;
}
@@ -183,8 +188,8 @@ void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int3
return;
_timer += _tickLength;
- while (_timer > _rtt) {
- _timer -= _rtt;
+ while (_timer >= _envDuration) {
+ _timer -= _envDuration;
++_tickCount;
int32 levelIncrement = 0;
@@ -266,7 +271,7 @@ void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int3
int32 outp = 0;
int32 *i = &outp, *o = &outp;
- int phaseShift = 0;
+ int32 phaseShift = 0;
if (feed) {
o = &feed[0];
@@ -278,8 +283,7 @@ void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int3
}
if (lvlout < 832) {
- uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000)
- + phaseShift)) >> 16) & 0x3ff];
+ uint32 index = (lvlout << 3) + _sinTbl[(((int32)(_phase & ~0xffff) + phaseShift) >> 16) & 0x3ff];
*i = ((index < 6656) ? _tLvlTbl[index] : 0);
} else {
*i = 0;
@@ -372,10 +376,10 @@ void TownsPC98_FmSynthOperator::reset() {
ampModulation(false);
}
-class TownsPC98_FmSynthSquareSineSource {
+class TownsPC98_FmSynthSquareWaveSource {
public:
- TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt);
- ~TownsPC98_FmSynthSquareSineSource();
+ TownsPC98_FmSynthSquareWaveSource(const uint32 tickLength, const uint32 envduration);
+ ~TownsPC98_FmSynthSquareWaveSource();
void init(const int *rsTable, const int *rseTable);
void reset();
@@ -418,8 +422,8 @@ private:
int32 *_tleTable;
const uint32 _tickLength;
+ const uint32 _envDuration;
uint32 _timer;
- const uint32 _rtt;
struct Channel {
int tick;
@@ -451,7 +455,7 @@ private:
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
class TownsPC98_FmSynthPercussionSource {
public:
- TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt);
+ TownsPC98_FmSynthPercussionSource(const uint32 tickLength, const uint32 envduration);
~TownsPC98_FmSynthPercussionSource() {
delete[] _reg;
}
@@ -503,8 +507,8 @@ private:
uint8 _totalLevel;
const uint32 _tickLength;
+ const uint32 _envDuration;
uint32 _timer;
- const uint32 _rtt;
uint8 **_reg;
@@ -517,8 +521,8 @@ private:
};
#endif // DISABLE_PC98_RHYTHM_CHANNEL
-TownsPC98_FmSynthSquareSineSource::TownsPC98_FmSynthSquareSineSource(const uint32 timerbase, const uint32 rtt) : _tlTable(0),
- _rtt(rtt), _tleTable(0), _updateRequest(-1), _tickLength(timerbase * 27), _ready(0), _reg(0), _rand(1), _outN(1),
+TownsPC98_FmSynthSquareWaveSource::TownsPC98_FmSynthSquareWaveSource(const uint32 tickLength, const uint32 envduration) : _tlTable(0),
+ _envDuration(envduration), _tleTable(0), _updateRequest(-1), _tickLength(tickLength), _ready(0), _reg(0), _rand(1), _outN(1),
_nTick(0), _evpUpdateCnt(0), _evpTimer(0x1f), _pReslt(0x1f), _attack(0), _cont(false), _evpUpdate(true),
_timer(0), _noiseGenerator(0), _chanEnable(0), _envH(0), _envL(0), _flags(0), _evpSwap(0), _volumeT(0x60),
_volMaskA(0), _volMaskB(0), _volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume) {
@@ -545,13 +549,13 @@ TownsPC98_FmSynthSquareSineSource::TownsPC98_FmSynthSquareSineSource(const uint3
reset();
}
-TownsPC98_FmSynthSquareSineSource::~TownsPC98_FmSynthSquareSineSource() {
+TownsPC98_FmSynthSquareWaveSource::~TownsPC98_FmSynthSquareWaveSource() {
delete[] _tlTable;
delete[] _tleTable;
delete[] _reg;
}
-void TownsPC98_FmSynthSquareSineSource::init(const int *rsTable, const int *rseTable) {
+void TownsPC98_FmSynthSquareWaveSource::init(const int *rsTable, const int *rseTable) {
if (_ready) {
reset();
return;
@@ -586,7 +590,7 @@ void TownsPC98_FmSynthSquareSineSource::init(const int *rsTable, const int *rseT
_ready = true;
}
-void TownsPC98_FmSynthSquareSineSource::reset() {
+void TownsPC98_FmSynthSquareWaveSource::reset() {
_rand = 1;
_outN = 1;
_updateRequest = -1;
@@ -610,12 +614,12 @@ void TownsPC98_FmSynthSquareSineSource::reset() {
writeReg(7, 0xbf, true);
}
-void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, bool force) {
+void TownsPC98_FmSynthSquareWaveSource::writeReg(uint8 address, uint8 value, bool force) {
if (!_ready)
return;
if (address > 13) {
- warning("TownsPC98_FmSynthSquareSineSource: unsupported reg address: %d", address);
+ warning("TownsPC98_FmSynthSquareWaveSource: unsupported reg address: %d", address);
return;
}
@@ -632,7 +636,7 @@ void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, boo
return;
if (_updateRequest >= 63) {
- warning("TownsPC98_FmSynthSquareSineSource: event buffer overflow");
+ warning("TownsPC98_FmSynthSquareWaveSource: event buffer overflow");
_updateRequest = -1;
}
_updateRequestBuf[++_updateRequest] = value;
@@ -643,21 +647,21 @@ void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, boo
*_reg[address] = value;
}
-uint8 TownsPC98_FmSynthSquareSineSource::readReg(uint8 address) const {
+uint8 TownsPC98_FmSynthSquareWaveSource::readReg(uint8 address) const {
if (!_ready || address > 13)
return 0;
return *_reg[address];
}
-void TownsPC98_FmSynthSquareSineSource::nextTick(int32 *buffer, uint32 bufferSize) {
+void TownsPC98_FmSynthSquareWaveSource::nextTick(int32 *buffer, uint32 bufferSize) {
if (!_ready)
return;
for (uint32 i = 0; i < bufferSize; i++) {
_timer += _tickLength;
- while (_timer > _rtt) {
- _timer -= _rtt;
+ while (_timer >= _envDuration) {
+ _timer -= _envDuration;
if (++_nTick >= (_noiseGenerator & 0x1f)) {
if ((_rand + 1) & 2)
@@ -697,6 +701,9 @@ void TownsPC98_FmSynthSquareSineSource::nextTick(int32 *buffer, uint32 bufferSiz
int32 finOut = 0;
for (int ii = 0; ii < 3; ii++) {
+ if (!_channels[ii].vol)
+ continue;
+
int32 finOutTemp = ((_channels[ii].vol >> 4) & 1) ? _tleTable[_channels[ii].out ? _pReslt : 0] : _tlTable[_channels[ii].out ? (_channels[ii].vol & 0x0f) : 0];
if ((1 << ii) & _volMaskA)
@@ -715,7 +722,7 @@ void TownsPC98_FmSynthSquareSineSource::nextTick(int32 *buffer, uint32 bufferSiz
}
}
-void TownsPC98_FmSynthSquareSineSource::updateRegs() {
+void TownsPC98_FmSynthSquareWaveSource::updateRegs() {
for (int i = 0; i < _updateRequest;) {
uint8 b = _updateRequestBuf[i++];
uint8 a = _updateRequestBuf[i++];
@@ -734,8 +741,8 @@ void TownsPC98_FmSynthSquareSineSource::updateRegs() {
}
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
-TownsPC98_FmSynthPercussionSource::TownsPC98_FmSynthPercussionSource(const uint32 timerbase, const uint32 rtt) :
- _rtt(rtt), _tickLength(timerbase * 2), _timer(0), _totalLevel(0), _volMaskA(0), _volMaskB(0),
+TownsPC98_FmSynthPercussionSource::TownsPC98_FmSynthPercussionSource(const uint32 tickLength, const uint32 envduration) :
+ _envDuration(envduration), _tickLength(tickLength), _timer(0), _totalLevel(0), _volMaskA(0), _volMaskB(0),
_volumeA(Audio::Mixer::kMaxMixerVolume), _volumeB(Audio::Mixer::kMaxMixerVolume), _ready(false) {
memset(_rhChan, 0, sizeof(RhtChannel) * 6);
@@ -876,8 +883,8 @@ void TownsPC98_FmSynthPercussionSource::nextTick(int32 *buffer, uint32 bufferSiz
for (uint32 i = 0; i < bufferSize; i++) {
_timer += _tickLength;
- while (_timer > _rtt) {
- _timer -= _rtt;
+ while (_timer >= _envDuration) {
+ _timer -= _envDuration;
for (int ii = 0; ii < 6; ii++) {
RhtChannel *s = &_rhChan[ii];
@@ -949,7 +956,13 @@ TownsPC98_FmSynth::TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type) :
_numChan(type == kType26 ? 3 : 6), _numSSG(type == kTypeTowns ? 0 : 3),
_hasPercussion(type == kType86 ? true : false),
_oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0), _oprSinTbl(0), _oprLevelOut(0), _oprDetune(0),
- _rtt(type == kTypeTowns ? 0x514767 : 0x5B8D80), _baserate(55125.0f / (float)mixer->getOutputRate()),
+ _internalRate((type == kTypeTowns ? 7670454 : (type == kType86 ? 7987000 : 3993600))),
+ _renderBuffer(0), _renderBufferSize(0), _numPending(0), _offsPending(0),
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ _waitCycleRemainder(0),
+ _samplesPerWaitCycle(type == kType26 ? 72 : 144),
+#endif
+ _outputRate(mixer->getOutputRate()), _rateScale(type == kType26 ? 0 : 1),
_volMaskA(0), _volMaskB(0), _volumeA(255), _volumeB(255), _ready(false) {
memset(&_timers[0], 0, sizeof(ChipTimer));
@@ -958,8 +971,13 @@ TownsPC98_FmSynth::TownsPC98_FmSynth(Audio::Mixer *mixer, EmuType type) :
memset(_registers[0], 0, 255);
memset(_registers[1], 0, 255);
- _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback;
- _timerbase = (uint32)(_baserate * 1000000.0f);
+ _timerProcIdle = new ChipTimerProc(this, &TownsPC98_FmSynth::idleTimerCallback);
+ _timerProcA = new ChipTimerProc(this, &TownsPC98_FmSynth::timerCallbackA);
+ _timerProcB = new ChipTimerProc(this, &TownsPC98_FmSynth::timerCallbackB);
+
+ _timers[0].cb = _timers[1].cb = _timerProcIdle;
+ _rateConvCnt = _outRateMult = _outputRate * 72;
+ _predSmpCount = ((float)_internalRate / 72.0f) / (float)_outputRate;
}
TownsPC98_FmSynth::~TownsPC98_FmSynth() {
@@ -980,6 +998,13 @@ TownsPC98_FmSynth::~TownsPC98_FmSynth() {
delete[] _oprSinTbl;
delete[] _oprLevelOut;
delete[] _oprDetune;
+
+ delete[] _renderBuffer;
+
+ _timers[0].cb = _timers[1].cb = 0;
+ delete _timerProcA;
+ delete _timerProcB;
+ delete _timerProcIdle;
}
bool TownsPC98_FmSynth::init() {
@@ -993,23 +1018,23 @@ bool TownsPC98_FmSynth::init() {
_chanInternal = new ChanInternal[_numChan];
for (int i = 0; i < _numChan; i++) {
for (int j = 0; j < 4; ++j)
- _chanInternal[i].opr[j] = new TownsPC98_FmSynthOperator(_timerbase, _rtt, _oprRates, _oprRateshift, _oprAttackDecay, _oprFrq, _oprSinTbl, _oprLevelOut, _oprDetune);
+ _chanInternal[i].opr[j] = new TownsPC98_FmSynthOperator(48 >> _rateScale, 0x90, _oprRates, _oprRateshift, _oprAttackDecay, _oprFrq, _oprSinTbl, _oprLevelOut, _oprDetune);
}
if (_numSSG) {
- _ssg = new TownsPC98_FmSynthSquareSineSource(_timerbase, _rtt);
+ _ssg = new TownsPC98_FmSynthSquareWaveSource((48 >> _rateScale) * 9, 0x60);
_ssg->init(&_ssgTables[0], &_ssgTables[16]);
}
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
if (_hasPercussion) {
- _prc = new TownsPC98_FmSynthPercussionSource(_timerbase, _rtt);
+ _prc = new TownsPC98_FmSynthPercussionSource(48 >> _rateScale, 0x90);
_prc->init(_percussionData);
}
#endif
- _timers[0].cb = &TownsPC98_FmSynth::timerCallbackA;
- _timers[1].cb = &TownsPC98_FmSynth::timerCallbackB;
+ _timers[0].cb = _timerProcA;
+ _timers[1].cb = _timerProcB;
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
@@ -1026,7 +1051,7 @@ void TownsPC98_FmSynth::reset() {
for (int i = 0; i < _numChan; i++) {
for (int ii = 0; ii < 4; ii++)
_chanInternal[i].opr[ii]->reset();
- memset(_chanInternal[i].feedbuf, 0, 3 * sizeof(int32));
+ _chanInternal[i].fbClear();
_chanInternal[i].algorithm = 0;
_chanInternal[i].enableLeft = _chanInternal[i].enableRight = true;
_chanInternal[i].updateEnvelopeParameters = false;
@@ -1035,6 +1060,11 @@ void TownsPC98_FmSynth::reset() {
memset(_registers[0], 0, 255);
memset(_registers[1], 0, 255);
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ _waitCycleElapsedWrites.clear();
+ _waitCycleRemainder = 0;
+#endif
+
writeReg(0, 0x27, 0x33);
if (_ssg)
@@ -1056,6 +1086,282 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
return;
}
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ if (_waitCycleRemainder && regAddress > 0x0f) {
+ _waitCycleElapsedWrites.push_back(RegEntry(part, regAddress, value));
+ return;
+ }
+#endif
+
+ writeRegInternal(part, regAddress, value);
+
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ startWaitCycle();
+#endif
+}
+
+uint8 TownsPC98_FmSynth::readReg(uint8 part, uint8 regAddress) {
+ Common::StackLock lock(_mutex);
+ if (!_ready || part > 1)
+ return 0;
+
+ if (!(regAddress & 0xF0) && _ssg)
+ return _ssg->readReg(regAddress & 0x0F);
+#ifdef DISABLE_PC98_RHYTHM_CHANNEL
+ else if ((regAddress & 0xF0) == 0x10)
+ return 0;
+#else
+ else if ((regAddress & 0xF0) == 0x10 && _prc)
+ return _prc->readReg(regAddress & 0x0F);
+#endif
+
+ return _registers[regAddress][part];
+}
+
+int TownsPC98_FmSynth::readBuffer(int16 *buffer, const int numSamples) {
+ Common::StackLock lock(_mutex);
+ if (!_ready)
+ return 0;
+
+ // This assumes that the numSamples parameter will be the same on most (if not on all) calls to readBuffer(),
+ // although I don't know whether this is true for all backends. There is no need to reallocate the temp
+ // buffer every time, unless its size needs to be increased.
+ int requiredSize = MAX(numSamples, (int)(_predSmpCount * numSamples) + 2);
+ if (_renderBufferSize < requiredSize) {
+ if (_numPending) {
+ // I seriously doubt that this will ever come up (if not alone for the fact that there shouldn't
+ // actually be any reallocations). But if it does I'd like to implement a fix for it.
+ warning("TownsPC98_FmSynth::readBuffer(): %d samples lost during buffer reallocation.", _numPending);
+ _numPending = _offsPending = 0;
+ }
+ delete[] _renderBuffer;
+ _renderBufferSize = requiredSize;
+ _renderBuffer = new int32[_renderBufferSize];
+ memset(_renderBuffer, 0, sizeof(int32) * _renderBufferSize);
+ }
+
+ int outSamplesLeft = numSamples >> 1;
+ int inSamplesLeft = (int)(_predSmpCount * outSamplesLeft) + 1;
+
+ while (_ready && outSamplesLeft) {
+ int render = inSamplesLeft;
+
+ for (int i = 0; i < 2; i++) {
+ if (_timers[i].enabled && _timers[i].cb) {
+ if (!_timers[i].smpTillCb) {
+ if (_timers[i].cb) {
+ if (_timers[i].cb->isValid())
+ (*_timers[i].cb)();
+ }
+ _timers[i].smpTillCb = _timers[i].smpPerCb;
+ _timers[i].smpTillCbRem += _timers[i].smpPerCbRem;
+ if (_timers[i].smpTillCbRem >= 1000000) {
+ _timers[i].smpTillCb++;
+ _timers[i].smpTillCbRem -= 1000000;
+ }
+ }
+ render = MIN(render, _timers[i].smpTillCb);
+ }
+ }
+
+ render = MAX(render - (_offsPending + _numPending), 0);
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ if (_waitCycleRemainder)
+ render = MIN(render, _waitCycleRemainder);
+#endif
+ int32 *pos = _renderBuffer;
+ if (_numPending)
+ pos += ((_offsPending + _numPending) << 1);
+
+ for (int i = 0; i < 2; i++) {
+ if (_timers[i].enabled && _timers[i].cb)
+ _timers[i].smpTillCb -= render;
+ }
+
+ if (render) {
+ // All rendering passes add their newly created samples on top of the currently buffered samples.
+ // So the buffer has to be cleared before the first pass...
+ memset(pos, 0, sizeof(int32) * (render << 1));
+
+ nextTick(pos, render);
+
+ if (_ssg)
+ _ssg->nextTick(pos, render);
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_prc)
+ _prc->nextTick(pos, render);
+#endif
+ nextTickEx(pos, render);
+ }
+
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ if (_waitCycleRemainder) {
+ _waitCycleRemainder -= render;
+ while (!_waitCycleElapsedWrites.empty() && !_waitCycleRemainder) {
+ RegEntry r = _waitCycleElapsedWrites.remove_at(0);
+ if (r.part == 0xFF)
+ _waitCycleRemainder = _samplesPerWaitCycle;
+ else
+ writeRegInternal(r.part, r.reg, r.val);
+ }
+ }
+#endif
+ pos = _renderBuffer;
+ if (_numPending)
+ pos += (_offsPending << 1);
+
+ int consumed = 0;
+ // Convert rate to the mixer output rate. I don't want to leave that to the mixer, since doing
+ // it here allows me the best control over the precise invocation of the timer callbacks.
+ for (; consumed < render + _numPending && outSamplesLeft; ++consumed) {
+ for (_rateConvCnt -= _outRateMult; _rateConvCnt <= 0 && outSamplesLeft; --outSamplesLeft) {
+ _rateConvCnt += _internalRate;
+ *buffer++ = (int16)CLIP<int32>(pos[consumed << 1], -32767, 32767);
+ *buffer++ = (int16)CLIP<int32>(pos[(consumed << 1) + 1], -32767, 32767);
+ }
+ }
+
+ inSamplesLeft -= render;
+ _offsPending = consumed - _numPending;
+ _numPending = render + _numPending - consumed;
+ }
+
+ return numSamples;
+}
+
+bool TownsPC98_FmSynth::isStereo() const {
+ return true;
+}
+
+bool TownsPC98_FmSynth::endOfData() const {
+ return false;
+}
+
+int TownsPC98_FmSynth::getRate() const {
+ return _outputRate;
+}
+
+void TownsPC98_FmSynth::deinit() {
+ _mixer->stopHandle(_soundHandle);
+ _ready = false;
+ _timers[0].cb = _timers[1].cb = _timerProcIdle;
+}
+
+void TownsPC98_FmSynth::setVolumeIntern(int volA, int volB) {
+ Common::StackLock lock(_mutex);
+ _volumeA = CLIP<uint16>(volA, 0, Audio::Mixer::kMaxMixerVolume);
+ _volumeB = CLIP<uint16>(volB, 0, Audio::Mixer::kMaxMixerVolume);
+ if (_ssg)
+ _ssg->setVolumeIntern(_volumeA, _volumeB);
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_prc)
+ _prc->setVolumeIntern(_volumeA, _volumeB);
+#endif
+}
+
+void TownsPC98_FmSynth::setVolumeChannelMasks(int channelMaskA, int channelMaskB) {
+ Common::StackLock lock(_mutex);
+ _volMaskA = channelMaskA;
+ _volMaskB = channelMaskB;
+ if (_ssg)
+ _ssg->setVolumeChannelMasks(_volMaskA >> _numChan, _volMaskB >> _numChan);
+#ifndef DISABLE_PC98_RHYTHM_CHANNEL
+ if (_prc)
+ _prc->setVolumeChannelMasks(_volMaskA >> (_numChan + _numSSG), _volMaskB >> (_numChan + _numSSG));
+#endif
+}
+
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+void TownsPC98_FmSynth::startWaitCycle() {
+ if (_waitCycleRemainder)
+ _waitCycleElapsedWrites.push_back(RegEntry(0xFF, 0xFF, 0xFF));
+ else
+ _waitCycleRemainder = _samplesPerWaitCycle;
+}
+#endif
+
+void TownsPC98_FmSynth::setLevelSSG(int vol) {
+ Common::StackLock lock(_mutex);
+ if (_ssg)
+ _ssg->setOutputLevel(vol);
+}
+
+void TownsPC98_FmSynth::generateTables() {
+ delete[] _oprRates;
+ _oprRates = new uint8[130];
+
+ WRITE_BE_UINT32(_oprRates + 32, _numChan == 6 ? 0x90900000 : 0x00081018);
+ WRITE_BE_UINT32(_oprRates + 36, _numChan == 6 ? 0x00001010 : 0x00081018);
+ memset(_oprRates, 0x90, 32);
+ memset(&_oprRates[96], 0x80, 34);
+ uint8 *dst = (uint8 *)_oprRates + 40;
+ for (int i = 0; i < 40; i += 4)
+ WRITE_BE_UINT32(dst + i, 0x00081018);
+ dst += 40;
+ for (uint8 i = 0; i < 16; i ++) {
+ uint8 v = (i < 12) ? i : 12;
+ *dst++ = ((4 + v) << 3);
+ }
+
+ delete[] _oprRateshift;
+ _oprRateshift = new uint8[130];
+ memset(_oprRateshift, 0, 130);
+ dst = (uint8 *)_oprRateshift + 32;
+ for (int i = 11; i; i--) {
+ memset(dst, i, 4);
+ dst += 4;
+ }
+
+ delete[] _oprFrq;
+ _oprFrq = new uint32[0x1000];
+ for (uint32 i = 0; i < 0x1000; i++)
+ _oprFrq[i] = (uint32)i << (11 - _rateScale);
+
+ delete[] _oprAttackDecay;
+ _oprAttackDecay = new uint8[152];
+ memset(_oprAttackDecay, 0, 152);
+ for (int i = 0; i < 36; i++)
+ WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]);
+
+ delete[] _oprSinTbl;
+ _oprSinTbl = new uint32[1024];
+ for (int i = 0; i < 1024; i++) {
+ double val = sin((double)(((i << 1) + 1) * M_PI / 1024.0));
+ double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0;
+ int32 i_dcb = (int32)(2.0 * d_dcb);
+ i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1);
+ _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1);
+ }
+
+ delete[] _oprLevelOut;
+ _oprLevelOut = new int32[0x1a00];
+ for (int i = 0; i < 256; i++) {
+ double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i)));
+ int32 val_int = ((int32) val) >> 4;
+ _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2;
+ _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1];
+ for (int ii = 1; ii < 13; ++ii) {
+ _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii;
+ _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)];
+ }
+ }
+
+ uint8 *dtt = new uint8[128];
+ memset(dtt, 0, 36);
+ memset(&dtt[36], 1, 8);
+ memcpy(&dtt[44], _detSrc, 84);
+
+ delete[] _oprDetune;
+ _oprDetune = new int32[256];
+ for (int i = 0; i < 128; i++) {
+ _oprDetune[i] = (int32)dtt[i] << (6 - _rateScale);
+ _oprDetune[i + 128] = -_oprDetune[i];
+ }
+
+ delete[] dtt;
+}
+
+void TownsPC98_FmSynth::writeRegInternal(uint8 part, uint8 regAddress, uint8 value) {
static const uint8 oprOrdr[] = { 0, 2, 1, 3 };
_registers[regAddress][part] = value;
@@ -1088,25 +1394,29 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
switch (h) {
case 0x00:
- // ssg
- if (_ssg)
+ if (part) { /*extadpcm*/ }
+ else if (_ssg)
_ssg->writeReg(l, value);
break;
case 0x10:
+ if (part) { /*extadpcm*/ }
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
- // pcm rhythm channel
- if (_prc)
+ else if (_prc)
_prc->writeReg(l, value);
#endif
break;
case 0x20:
- if (l == 8) {
+ if (part) {}
+ else if (l == 8) {
// Key on/off
for (int i = 0; i < 4; i++) {
- if ((value >> (4 + i)) & 1)
+ if ((value >> (4 + i)) & 1) {
+ if (i == 0)
+ c->fbClear();
co[i]->keyOn();
- else
+ } else {
co[i]->keyOff();
+ }
}
} else if (l == 4) {
// Timer A
@@ -1119,13 +1429,13 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
_timers[1].value = value & 0xff;
} else if (l == 7) {
if (value & 1) {
- float spc = (float)(0x400 - _timers[0].value) / _baserate;
+ int spc = (0x400 - _timers[0].value) << _rateScale;
if (spc < 1) {
warning("TownsPC98_FmSynth: Invalid Timer A setting: %d", _timers[0].value);
spc = 1;
}
- _timers[0].smpPerCb = (int32) spc;
+ _timers[0].smpPerCb = (int32)spc;
_timers[0].smpPerCbRem = (uint32)((spc - (float)_timers[0].smpPerCb) * 1000000.0f);
_timers[0].smpTillCb = _timers[0].smpPerCb;
_timers[0].smpTillCbRem = _timers[0].smpPerCbRem;
@@ -1135,13 +1445,13 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
}
if (value & 2) {
- float spc = (float)(0x100 - _timers[1].value) * 16.0f / _baserate;
+ int spc = ((0x100 - _timers[1].value) << 4) << _rateScale;
if (spc < 1) {
warning("TownsPC98_FmSynth: Invalid Timer B setting: %d", _timers[1].value);
spc = 1;
}
- _timers[1].smpPerCb = (int32) spc;
+ _timers[1].smpPerCb = (int32)spc;
_timers[1].smpPerCbRem = (uint32)((spc - (float)_timers[1].smpPerCb) * 1000000.0f);
_timers[1].smpTillCb = _timers[1].smpPerCb;
_timers[1].smpTillCbRem = _timers[1].smpPerCbRem;
@@ -1255,211 +1565,6 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
}
}
-uint8 TownsPC98_FmSynth::readReg(uint8 part, uint8 regAddress) {
- Common::StackLock lock(_mutex);
- if (!_ready || part > 1)
- return 0;
-
- if (!(regAddress & 0xF0) && _ssg)
- return _ssg->readReg(regAddress & 0x0F);
-#ifdef DISABLE_PC98_RHYTHM_CHANNEL
- else if ((regAddress & 0xF0) == 0x10)
- return 0;
-#else
- else if ((regAddress & 0xF0) == 0x10 && _prc)
- return _prc->readReg(regAddress & 0x0F);
-#endif
-
- return _registers[regAddress][part];
-}
-
-int TownsPC98_FmSynth::readBuffer(int16 *buffer, const int numSamples) {
- Common::StackLock lock(_mutex);
- memset(buffer, 0, sizeof(int16) * numSamples);
- int32 *tmp = new int32[numSamples];
- int32 *tmpStart = tmp;
- memset(tmp, 0, sizeof(int32) * numSamples);
- int32 samplesLeft = numSamples >> 1;
-
- while (_ready && samplesLeft) {
- int32 render = samplesLeft;
-
- for (int i = 0; i < 2; i++) {
- if (_timers[i].enabled && _timers[i].cb) {
- if (!_timers[i].smpTillCb) {
-
- (this->*_timers[i].cb)();
-
- _timers[i].smpTillCb = _timers[i].smpPerCb;
-
- _timers[i].smpTillCbRem += _timers[i].smpPerCbRem;
- if (_timers[i].smpTillCbRem >= _timerbase) {
- _timers[i].smpTillCb++;
- _timers[i].smpTillCbRem -= _timerbase;
- }
- }
- render = MIN(render, _timers[i].smpTillCb);
- }
- }
-
- samplesLeft -= render;
-
- for (int i = 0; i < 2; i++) {
- if (_timers[i].enabled && _timers[i].cb) {
- _timers[i].smpTillCb -= render;
- }
- }
-
- nextTick(tmp, render);
-
- if (_ssg)
- _ssg->nextTick(tmp, render);
-#ifndef DISABLE_PC98_RHYTHM_CHANNEL
- if (_prc)
- _prc->nextTick(tmp, render);
-#endif
-
- nextTickEx(tmp, render);
-
- for (int i = 0; i < render; ++i) {
- int32 l = CLIP<int32>(tmp[i << 1], -32767, 32767);
- buffer[i << 1] = (int16) l;
- int32 r = CLIP<int32>(tmp[(i << 1) + 1], -32767, 32767);
- buffer[(i << 1) + 1] = (int16) r;
- }
-
- buffer += (render << 1);
- tmp += (render << 1);
- }
-
- delete[] tmpStart;
-
- return numSamples;
-}
-
-bool TownsPC98_FmSynth::isStereo() const {
- return true;
-}
-
-bool TownsPC98_FmSynth::endOfData() const {
- return false;
-}
-
-int TownsPC98_FmSynth::getRate() const {
- return _mixer->getOutputRate();
-}
-
-void TownsPC98_FmSynth::deinit() {
- _mixer->stopHandle(_soundHandle);
- _ready = false;
- _timers[0].cb = _timers[1].cb = &TownsPC98_FmSynth::idleTimerCallback;
-}
-
-void TownsPC98_FmSynth::setVolumeIntern(int volA, int volB) {
- Common::StackLock lock(_mutex);
- _volumeA = CLIP<uint16>(volA, 0, Audio::Mixer::kMaxMixerVolume);
- _volumeB = CLIP<uint16>(volB, 0, Audio::Mixer::kMaxMixerVolume);
- if (_ssg)
- _ssg->setVolumeIntern(_volumeA, _volumeB);
-#ifndef DISABLE_PC98_RHYTHM_CHANNEL
- if (_prc)
- _prc->setVolumeIntern(_volumeA, _volumeB);
-#endif
-}
-
-void TownsPC98_FmSynth::setVolumeChannelMasks(int channelMaskA, int channelMaskB) {
- Common::StackLock lock(_mutex);
- _volMaskA = channelMaskA;
- _volMaskB = channelMaskB;
- if (_ssg)
- _ssg->setVolumeChannelMasks(_volMaskA >> _numChan, _volMaskB >> _numChan);
-#ifndef DISABLE_PC98_RHYTHM_CHANNEL
- if (_prc)
- _prc->setVolumeChannelMasks(_volMaskA >> (_numChan + _numSSG), _volMaskB >> (_numChan + _numSSG));
-#endif
-}
-
-void TownsPC98_FmSynth::setLevelSSG(int vol) {
- Common::StackLock lock(_mutex);
- if (_ssg)
- _ssg->setOutputLevel(vol);
-}
-
-void TownsPC98_FmSynth::generateTables() {
- delete[] _oprRates;
- _oprRates = new uint8[130];
-
- WRITE_BE_UINT32(_oprRates + 32, _numChan == 6 ? 0x90900000 : 0x00081018);
- WRITE_BE_UINT32(_oprRates + 36, _numChan == 6 ? 0x00001010 : 0x00081018);
- memset(_oprRates, 0x90, 32);
- memset(&_oprRates[96], 0x80, 34);
- uint8 *dst = (uint8 *)_oprRates + 40;
- for (int i = 0; i < 40; i += 4)
- WRITE_BE_UINT32(dst + i, 0x00081018);
- dst += 40;
- for (uint8 i = 0; i < 16; i ++) {
- uint8 v = (i < 12) ? i : 12;
- *dst++ = ((4 + v) << 3);
- }
-
- delete[] _oprRateshift;
- _oprRateshift = new uint8[130];
- memset(_oprRateshift, 0, 130);
- dst = (uint8 *)_oprRateshift + 32;
- for (int i = 11; i; i--) {
- memset(dst, i, 4);
- dst += 4;
- }
-
- delete[] _oprFrq;
- _oprFrq = new uint32[0x1000];
- for (uint32 i = 0; i < 0x1000; i++)
- _oprFrq[i] = (uint32)(_baserate * (float)(i << 11));
-
- delete[] _oprAttackDecay;
- _oprAttackDecay = new uint8[152];
- memset(_oprAttackDecay, 0, 152);
- for (int i = 0; i < 36; i++)
- WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]);
-
- delete[] _oprSinTbl;
- _oprSinTbl = new uint32[1024];
- for (int i = 0; i < 1024; i++) {
- double val = sin((double)(((i << 1) + 1) * M_PI / 1024.0));
- double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0;
- int32 i_dcb = (int32)(2.0 * d_dcb);
- i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1);
- _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1);
- }
-
- delete[] _oprLevelOut;
- _oprLevelOut = new int32[0x1a00];
- for (int i = 0; i < 256; i++) {
- double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i)));
- int32 val_int = ((int32) val) >> 4;
- _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2;
- _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1];
- for (int ii = 1; ii < 13; ++ii) {
- _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii;
- _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)];
- }
- }
-
- uint8 *dtt = new uint8[128];
- memset(dtt, 0, 36);
- memset(&dtt[36], 1, 8);
- memcpy(&dtt[44], _detSrc, 84);
-
- delete[] _oprDetune;
- _oprDetune = new int32[256];
- for (int i = 0; i < 128; i++) {
- _oprDetune[i] = (int32)((float)dtt[i] * _baserate * 64.0);
- _oprDetune[i + 128] = -_oprDetune[i];
- }
-
- delete[] dtt;
-}
-
void TownsPC98_FmSynth::nextTick(int32 *buffer, uint32 bufferSize) {
if (!_ready)
return;
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
index 730dbecb16..05f83867bf 100644
--- a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
@@ -26,6 +26,8 @@
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "common/mutex.h"
+#include "common/func.h"
+#include "common/array.h"
#ifdef __DS__
/* This disables the rhythm channel when emulating the PC-98 type 86 sound card.
@@ -34,11 +36,23 @@
* (very rare) PC-98 versions of Legend of Kyrandia 2 and Lands of Lore. Music will
* still be okay, just missing a couple of rhythm instruments.
*/
-#define DISABLE_PC98_RHYTHM_CHANNEL
+#define DISABLE_PC98_RHYTHM_CHANNEL
#endif
+/* Experimental code for emulation of the chip's busy flag wait cycle.
+ * Explanation:
+ * Before attempting a port write a client application would usually read the chip's
+ * busy flag and remain in a loop until the flag is cleared. This does not work with
+ * an emulator that is on the same thread as the client code (the busy flag will never
+ * clear). Instead, I emulate a wait cycle by withholding (enqueueing) incoming register
+ * writes for the duration of the wait cycle.
+ * For now I have disabled this with an #ifdef since I haven't seen any impact on the
+ * sound.
+ */
+//#define ENABLE_SNDTOWNS98_WAITCYCLES
+
class TownsPC98_FmSynthOperator;
-class TownsPC98_FmSynthSquareSineSource;
+class TownsPC98_FmSynthSquareWaveSource;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
class TownsPC98_FmSynthPercussionSource;
#endif
@@ -92,6 +106,7 @@ protected:
void setVolumeIntern(int volA, int volB);
void setVolumeChannelMasks(int channelMaskA, int channelMaskB);
+ // This allows to balance out the fm/ssg levels.
void setLevelSSG(int vol);
const int _numChan;
@@ -102,8 +117,13 @@ protected:
private:
void generateTables();
+ void writeRegInternal(uint8 part, uint8 regAddress, uint8 value);
void nextTick(int32 *buffer, uint32 bufferSize);
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ void startWaitCycle();
+#endif
+
struct ChanInternal {
ChanInternal();
~ChanInternal();
@@ -114,6 +134,9 @@ private:
void frqModSensitivity(uint32 value) {
frqModSvty = value << 5;
}
+ void fbClear() {
+ feedbuf[0] = feedbuf[1] = feedbuf[2] = 0;
+ }
bool enableLeft;
bool enableRight;
@@ -127,7 +150,7 @@ private:
TownsPC98_FmSynthOperator *opr[4];
};
- TownsPC98_FmSynthSquareSineSource *_ssg;
+ TownsPC98_FmSynthSquareWaveSource *_ssg;
#ifndef DISABLE_PC98_RHYTHM_CHANNEL
TownsPC98_FmSynthPercussionSource *_prc;
#endif
@@ -141,7 +164,10 @@ private:
int32 *_oprLevelOut;
int32 *_oprDetune;
- typedef void (TownsPC98_FmSynth::*ChipTimerProc)();
+ typedef Common::Functor0Mem<void, TownsPC98_FmSynth> ChipTimerProc;
+ ChipTimerProc *_timerProcIdle;
+ ChipTimerProc *_timerProcA;
+ ChipTimerProc *_timerProcB;
void idleTimerCallback() {}
struct ChipTimer {
@@ -153,7 +179,7 @@ private:
int32 smpPerCb;
uint32 smpPerCbRem;
- ChipTimerProc cb;
+ ChipTimerProc *cb;
};
ChipTimer _timers[2];
@@ -161,9 +187,30 @@ private:
int _volMaskA, _volMaskB;
uint16 _volumeA, _volumeB;
- const float _baserate;
- uint32 _timerbase;
- uint32 _rtt;
+ int32 *_renderBuffer;
+ int _renderBufferSize;
+ int _numPending;
+ int _offsPending;
+ int _rateScale;
+ int _outRateMult;
+ int _rateConvCnt;
+ float _predSmpCount;
+ const int _internalRate;
+ const int _outputRate;
+
+#ifdef ENABLE_SNDTOWNS98_WAITCYCLES
+ int _waitCycleRemainder;
+ const int _samplesPerWaitCycle;
+
+ struct RegEntry {
+ RegEntry(uint8 p, uint8 r, uint8 v) : part(p), reg(r), val(v) {}
+ uint8 part;
+ uint8 reg;
+ uint8 val;
+ };
+
+ Common::Array<RegEntry> _waitCycleElapsedWrites;
+#endif
uint8 _registers[255][2];