aboutsummaryrefslogtreecommitdiff
path: root/audio/softsynth
diff options
context:
space:
mode:
authorathrxx2018-12-01 02:15:34 +0100
committerathrxx2019-03-07 21:28:41 +0100
commit49e85f64cf0470439de59ea87e342986b9779efc (patch)
tree297a6e277b36228f3280de683e25ad4ce570b0fb /audio/softsynth
parentb789d50a55cc22e481d286a1038d3ec4092b23ec (diff)
downloadscummvm-rg350-49e85f64cf0470439de59ea87e342986b9779efc.tar.gz
scummvm-rg350-49e85f64cf0470439de59ea87e342986b9779efc.tar.bz2
scummvm-rg350-49e85f64cf0470439de59ea87e342986b9779efc.zip
AUDIO: (FM-TOWNS/PC-98) - allow individual operator frequencies
(also add some sanity checks and make some more adjustments for SCI audio driver)
Diffstat (limited to 'audio/softsynth')
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp160
-rw-r--r--audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h2
2 files changed, 120 insertions, 42 deletions
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
index e3d86b3515..ce006b165c 100644
--- a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.cpp
@@ -34,7 +34,8 @@ public:
void keyOn();
void keyOff();
- void frequency(int freq);
+ void frequencyHi(uint8 frqH);
+ void frequencyLo(uint8 frqL);
void updatePhaseIncrement();
void recalculateRates();
void generateOutput(int32 phasebuf, int32 *feedbuf, int32 &out);
@@ -48,11 +49,14 @@ public:
void sustainRate(uint32 value);
void sustainLevel(uint32 value);
void releaseRate(uint32 value);
+ void envelopeShape(uint32 value);
void totalLevel(uint32 value);
void ampModulation(bool enable);
void reset();
protected:
+ void frequency(int freq);
+
EnvelopeState _state;
bool _holdKey;
uint32 _feedbackLevel;
@@ -64,14 +68,18 @@ protected:
uint32 _specifiedDecayRate;
uint32 _specifiedSustainRate;
uint32 _specifiedReleaseRate;
+ uint32 _envelopeShapeSpecs;
uint32 _tickCount;
uint32 _sustainLevel;
bool _ampMod;
uint32 _frequency;
+ uint16 _freqTemp;
uint8 _kcode;
uint32 _phase;
uint32 _phaseIncrement;
+ uint32 _shapeState;
+ uint8 _shapeScale;
const int32 *_detn;
const uint8 *_rateTbl;
@@ -98,9 +106,9 @@ TownsPC98_FmSynthOperator::TownsPC98_FmSynthOperator(const uint32 timerbase, con
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), _specifiedReleaseRate(0), _specifiedSustainRate(0),
- _sustainLevel(0), _phase(0), _state(kEnvReady), _holdKey(false), _timer(0), _keyScale1(0),
- _keyScale2(0), _currentLevel(1023), _ampMod(false), _tickCount(0) {
+ _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), _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;
@@ -114,6 +122,7 @@ void TownsPC98_FmSynthOperator::keyOn() {
_holdKey = true;
_state = kEnvAttacking;
_phase = 0;
+ _shapeState = _envelopeShapeSpecs;
}
void TownsPC98_FmSynthOperator::keyOff() {
@@ -125,6 +134,15 @@ void TownsPC98_FmSynthOperator::keyOff() {
_state = kEnvReleasing;
}
+void TownsPC98_FmSynthOperator::frequencyHi(uint8 frqH) {
+ _freqTemp = (_freqTemp & 0xff) | ((frqH & 0x3F) << 8);
+}
+
+void TownsPC98_FmSynthOperator::frequencyLo(uint8 frqL) {
+ _freqTemp = (_freqTemp & 0xff00) | frqL;
+ frequency(_freqTemp);
+}
+
void TownsPC98_FmSynthOperator::frequency(int freq) {
uint8 block = (freq >> 11);
uint16 pos = (freq & 0x7ff);
@@ -132,6 +150,7 @@ void TownsPC98_FmSynthOperator::frequency(int freq) {
_kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6));
_frequency = _fTbl[pos << 1] >> (7 - block);
+ _freqTemp = 0;
}
void TownsPC98_FmSynthOperator::updatePhaseIncrement() {
@@ -196,13 +215,13 @@ void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int3
targetTime = (1 << fs_d.shift) - 1;
nextState = kEnvSustaining;
targetLevel = _sustainLevel;
- levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)];
+ levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)] << _shapeScale;
break;
case kEnvSustaining:
targetTime = (1 << fs_s.shift) - 1;
nextState = kEnvSustaining;
- targetLevel = 1023;
- levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)];
+ targetLevel = _shapeScale ? 832 : 1023;
+ levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)] << _shapeScale;
break;
case kEnvReleasing:
targetTime = (1 << fs_r.shift) - 1;
@@ -219,13 +238,25 @@ void TownsPC98_FmSynthOperator::generateOutput(int32 phasebuf, int32 *feed, int3
if ((_state == kEnvAttacking && _currentLevel <= targetLevel) || (_state != kEnvAttacking && _currentLevel >= targetLevel)) {
if (_state != kEnvDecaying)
_currentLevel = targetLevel;
+ if (_state == kEnvSustaining && _shapeScale) {
+ _currentLevel += 191;
+ if (_shapeState & 1) {
+ if (!(_shapeState & 0x10))
+ _shapeState |= 0x40;
+ } else {
+ nextState = kEnvAttacking;
+ _phase = 0;
+ _currentLevel = 511;
+ _shapeState &= ~0x40;
+ }
+ }
_state = nextState;
}
}
}
- uint32 lvlout = _totalLevel + (uint32) _currentLevel;
-
+ uint32 lvlout = _totalLevel + ((uint32) _currentLevel ^ (_state != kEnvReleasing ? ((_shapeScale * (_shapeState & 4)) >> 3) * 1023 : 0));
+ _shapeState ^= (((_shapeState & 0x40) >> 2) | ((_shapeState & 2) << 1));
int32 outp = 0;
int32 *i = &outp, *o = &outp;
@@ -301,6 +332,11 @@ void TownsPC98_FmSynthOperator::releaseRate(uint32 value) {
recalculateRates();
}
+void TownsPC98_FmSynthOperator::envelopeShape(uint32 value) {
+ _envelopeShapeSpecs = value;
+ _shapeScale = (value & 8) >> 2;
+}
+
void TownsPC98_FmSynthOperator::totalLevel(uint32 value) {
_totalLevel = value << 3;
}
@@ -324,6 +360,7 @@ void TownsPC98_FmSynthOperator::reset() {
decayRate(0);
releaseRate(0);
sustainRate(0);
+ envelopeShape(0);
feedbackLevel(0);
totalLevel(127);
ampModulation(false);
@@ -369,6 +406,7 @@ private:
int _evpUpdateCnt;
uint8 _outN;
int _nTick;
+ uint8 _evpSwap;
int32 *_tlTable;
int32 *_tleTable;
@@ -389,6 +427,9 @@ private:
uint8 _noiseGenerator;
uint8 _chanEnable;
+ uint8 _envH;
+ uint8 _envL;
+ uint8 _flags;
uint8 **_reg;
@@ -473,12 +514,12 @@ private:
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),
_nTick(0), _evpUpdateCnt(0), _evpTimer(0x1f), _pReslt(0x1f), _attack(0), _cont(false), _evpUpdate(true),
- _timer(0), _noiseGenerator(0), _chanEnable(0), _volumeT(0x60),
+ _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) {
memset(_channels, 0, sizeof(_channels));
memset(_updateRequestBuf, 0, sizeof(_updateRequestBuf));
- _reg = new uint8 *[11];
+ _reg = new uint8 *[14];
_reg[0] = &_channels[0].frqL;
_reg[1] = &_channels[0].frqH;
@@ -491,6 +532,9 @@ TownsPC98_FmSynthSquareSineSource::TownsPC98_FmSynthSquareSineSource(const uint3
_reg[8] = &_channels[0].vol;
_reg[9] = &_channels[1].vol;
_reg[10] = &_channels[2].vol;
+ _reg[11] = &_envL;
+ _reg[12] = &_envH;
+ _reg[13] = &_flags;
reset();
}
@@ -547,6 +591,7 @@ void TownsPC98_FmSynthSquareSineSource::reset() {
_cont = false;
_evpUpdate = true;
_timer = 0;
+ _evpSwap = 0;
for (int i = 0; i < 3; i++) {
_channels[i].tick = 0;
@@ -563,13 +608,23 @@ void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, boo
if (!_ready)
return;
- if (address > 10 || *_reg[address] == value) {
- if ((address == 11 || address == 12 || address == 13) && value)
- warning("TownsPC98_FmSynthSquareSineSource: unsupported reg address: %d", address);
+ if (address > 13) {
+ warning("TownsPC98_FmSynthSquareSineSource: unsupported reg address: %d", address);
return;
}
if (!force) {
+ bool alreadyBuffered = false;
+ for (int i = 0; i < _updateRequest;) {
+ uint8 b = _updateRequestBuf[i++];
+ uint8 a = _updateRequestBuf[i++];
+ if (a == address)
+ alreadyBuffered = (b == value) ? true : false;
+ }
+
+ if (alreadyBuffered)
+ return;
+
if (_updateRequest >= 63) {
warning("TownsPC98_FmSynthSquareSineSource: event buffer overflow");
_updateRequest = -1;
@@ -583,7 +638,7 @@ void TownsPC98_FmSynthSquareSineSource::writeReg(uint8 address, uint8 value, boo
}
uint8 TownsPC98_FmSynthSquareSineSource::readReg(uint8 address) const {
- if (!_ready || address > 10)
+ if (!_ready || address > 13)
return 0;
return *_reg[address];
@@ -615,13 +670,15 @@ void TownsPC98_FmSynthSquareSineSource::nextTick(int32 *buffer, uint32 bufferSiz
}
if (_evpUpdate) {
- if (++_evpUpdateCnt >= 0) {
+ if (++_evpUpdateCnt >= (_envH << 8 | _envL)) {
_evpUpdateCnt = 0;
if (--_evpTimer < 0) {
if (_cont) {
- _evpTimer &= 0x1f;
+ _attack ^= (_evpSwap && _evpTimer & 0x20) ? 0x1F : 0;
+ _evpTimer &= 0x1F;
} else {
+ _attack ^= _evpSwap ? 0x1F : 0;
_evpUpdate = false;
_evpTimer = 0;
}
@@ -657,6 +714,15 @@ void TownsPC98_FmSynthSquareSineSource::updateRegs() {
uint8 b = _updateRequestBuf[i++];
uint8 a = _updateRequestBuf[i++];
writeReg(a, b, true);
+
+ if (a == 13) {
+ _attack = (_flags & 4) ? 0x1F : 0;
+ _cont = (_flags & 8) ? (_flags & 1) ^ 1: false;
+ _evpSwap = (_flags & 8) ? _flags & 2 : _attack;
+ _evpTimer = 0x1F;
+ _evpUpdate = true;
+ _pReslt = _evpTimer ^ _attack;
+ }
}
_updateRequest = -1;
}
@@ -959,7 +1025,6 @@ void TownsPC98_FmSynth::reset() {
_chanInternal[i].opr[ii]->reset();
memset(_chanInternal[i].feedbuf, 0, 3 * sizeof(int32));
_chanInternal[i].algorithm = 0;
- _chanInternal[i].frqTemp = 0;
_chanInternal[i].enableLeft = _chanInternal[i].enableRight = true;
_chanInternal[i].updateEnvelopeParameters = false;
}
@@ -982,9 +1047,9 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
if (!_ready)
return;
- if (part > 1) {
+ if (part > (_numChan >> 2)) {
warning("TownsPC98_FmSynth::writeReg(): invalid part argument '%d'", part);
- part = 1;
+ return;
}
Common::StackLock lock(_mutex);
@@ -1000,14 +1065,24 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
TownsPC98_FmSynthOperator **co = 0;
TownsPC98_FmSynthOperator *o = 0;
- if (regAddress > 0x2F) {
- c = &_chanInternal[(l & 3) + 3 * part];
- co = c->opr;
- o = c->opr[oprOrdr[(l - (l & 3)) >> 2]];
- } else if (regAddress == 0x28) {
+ bool checkAddress = false;
+ c = &_chanInternal[(l & 3) + 3 * part];
+ if (regAddress == 0x28) {
c = &_chanInternal[(value & 3) + ((value & 4) ? 3 : 0)];
- co = c->opr;
+ checkAddress = true;
+ } else if (h == 0xA0 && (_registers[0x27][0] & 0x40) && (l > 7 || l == 2 || l == 6)) {
+ c = &_chanInternal[3 * part + 2];
+ o = c->opr[l < 8 ? 3 : 2 - (l & 3)];
+ } else if (h != 0xA0 && h > 0x20) {
+ o = c->opr[oprOrdr[(l & ~3) >> 2]];
+ checkAddress = true;
+ }
+
+ if (checkAddress && (l & 3) == 3) {
+ warning("TownsPC98_FmSynth::writeReg(): invalid write attempt at reg address 0x%2x", regAddress);
+ return;
}
+ co = c->opr;
switch (h) {
case 0x00:
@@ -1027,9 +1102,9 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
// Key on/off
for (int i = 0; i < 4; i++) {
if ((value >> (4 + i)) & 1)
- co[oprOrdr[i]]->keyOn();
+ co[i]->keyOn();
else
- co[oprOrdr[i]]->keyOff();
+ co[i]->keyOff();
}
} else if (l == 4) {
// Timer A
@@ -1082,6 +1157,7 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
_timers[1].smpTillCb = _timers[1].smpPerCb;
_timers[1].smpTillCbRem = _timers[1].smpPerCbRem;
}
+
} else if (l == 2) {
// LFO
if (value & 8)
@@ -1130,26 +1206,30 @@ void TownsPC98_FmSynth::writeReg(uint8 part, uint8 regAddress, uint8 value) {
break;
case 0x90:
- warning("TownsPC98_FmSynth: TRYING TO USE SSG ENVELOPE SHAPES (NOT SUPPORTED)");
+ o->envelopeShape(value & 0x0f);
break;
case 0xa0:
// frequency
l &= ~3;
if (l == 0) {
- c->frqTemp = (c->frqTemp & 0xff00) | value;
c->updateEnvelopeParameters = true;
- c->fmIndex = (c->frqTemp >> 4 & 0x7f);
- for (int i = 0; i < 4; i++)
- co[i]->frequency(c->frqTemp);
+ if (o)
+ o->frequencyLo(value);
+ else
+ for (int i = 0; i < 4; i++)
+ co[i]->frequencyLo(value);
} else if (l == 4) {
- c->frqTemp = (c->frqTemp & 0xff) | (value << 8);
- } else if (l == 8) {
- // Ch 3/6 special mode frq
- warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
- } else if (l == 12) {
- // Ch 3/6 special mode frq
- warning("TownsPC98_FmSynth: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
+ if (o)
+ o->frequencyHi(value);
+ else
+ for (int i = 0; i < 4; i++)
+ co[i]->frequencyHi(value);
+ } else if (l == 8 && o) {
+ c->updateEnvelopeParameters = true;
+ o->frequencyLo(value);
+ } else if (l == 12 && o) {
+ o->frequencyHi(value);
}
break;
diff --git a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
index ba484e2413..58b7ccb230 100644
--- a/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
+++ b/audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h
@@ -116,8 +116,6 @@ private:
frqModSvty = value << 5;
}
- uint16 frqTemp;
- uint8 fmIndex;
bool enableLeft;
bool enableRight;
bool updateEnvelopeParameters;