mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
(fix bug #14618: Inaccurate fades in INDY4, "Test 1") This part of the bug ticket is not about fades. The reason for the glitch is that the driver has no unused channels left and the newer allocation mechanism is not able to free up some of the used channels here.
1049 lines
27 KiB
C++
1049 lines
27 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/>.
|
|
*
|
|
*/
|
|
|
|
#include "scumm/imuse/drivers/fmtowns.h"
|
|
#include "audio/softsynth/fmtowns_pc98/towns_pc98_fmsynth.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/system.h"
|
|
|
|
namespace Scumm {
|
|
|
|
class TownsMidiOutputChannel {
|
|
friend class TownsMidiInputChannel;
|
|
public:
|
|
TownsMidiOutputChannel(IMuseDriver_FMTowns *driver, int chanId);
|
|
~TownsMidiOutputChannel();
|
|
|
|
void noteOn(uint8 msb, uint16 lsb);
|
|
void noteOnPitchBend(uint8 msb, uint16 lsb);
|
|
void setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara);
|
|
void setupEffects(int index, uint8 flags, const uint8 *effectData);
|
|
void setModWheel(uint8 value);
|
|
|
|
void connect(TownsMidiInputChannel *chan);
|
|
void disconnect();
|
|
|
|
bool update();
|
|
|
|
enum CheckPriorityStatus {
|
|
kDisconnected = -2,
|
|
kHighPriority = -1
|
|
};
|
|
|
|
int checkPriority(int pri);
|
|
|
|
private:
|
|
struct EffectEnvelope {
|
|
uint8 state;
|
|
int32 currentLevel;
|
|
int32 duration;
|
|
int32 maxLevel;
|
|
int32 startLevel;
|
|
uint8 loop;
|
|
uint8 stateTargetLevels[4];
|
|
uint8 stateModWheelLevels[4];
|
|
int8 modWheelSensitivity;
|
|
int8 modWheelState;
|
|
int8 modWheelLast;
|
|
uint16 numSteps;
|
|
uint32 stepCounter;
|
|
int32 incrPerStep;
|
|
int8 dir;
|
|
uint32 incrPerStepRem;
|
|
uint32 incrCountRem;
|
|
} *_effectEnvelopes;
|
|
|
|
struct EffectDef {
|
|
int32 phase;
|
|
uint8 type;
|
|
uint8 useModWheel;
|
|
uint8 loopRefresh;
|
|
EffectEnvelope *s;
|
|
} *_effectDefs;
|
|
|
|
void startEffect(EffectEnvelope *s, const uint8 *effectData);
|
|
void updateEffectGenerator(EffectEnvelope *s, EffectDef *d);
|
|
int advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d);
|
|
void initNextEnvelopeState(EffectEnvelope *s);
|
|
int16 getEffectStartLevel(uint8 type);
|
|
int getEffectModLevel(int lvl, int mod);
|
|
|
|
void keyOn();
|
|
void keyOff();
|
|
void keyOnSetFreq(uint16 frq);
|
|
void out(uint8 reg, uint8 val);
|
|
|
|
TownsMidiInputChannel *_in;
|
|
TownsMidiOutputChannel *_prev;
|
|
TownsMidiOutputChannel *_next;
|
|
uint8 _adjustModTl;
|
|
uint8 _chan;
|
|
uint8 _note;
|
|
uint8 _operator2Tl;
|
|
uint8 _operator1Tl;
|
|
uint8 _sustainNoteOff;
|
|
int16 _duration;
|
|
|
|
uint16 _freq;
|
|
int16 _freqAdjust;
|
|
|
|
IMuseDriver_FMTowns *_driver;
|
|
|
|
static const uint8 _chanMap[];
|
|
static const uint8 _chanMap2[];
|
|
static const uint8 _effectDefaults[];
|
|
static const uint16 _effectEnvStepTable[];
|
|
static const uint8 _freqMSB[];
|
|
static const uint16 _freqLSB[];
|
|
};
|
|
|
|
class TownsMidiInputChannel : public MidiChannel {
|
|
friend class TownsMidiOutputChannel;
|
|
public:
|
|
TownsMidiInputChannel(IMuseDriver_FMTowns *driver, int16 number);
|
|
~TownsMidiInputChannel() override;
|
|
|
|
MidiDriver *device() override { return _driver; }
|
|
byte getNumber() override { return _number; }
|
|
|
|
bool allocate();
|
|
void release() override;
|
|
|
|
void send(uint32 b) override;
|
|
|
|
void noteOff(byte note) override;
|
|
void noteOn(byte note, byte velocity) override;
|
|
void programChange(byte program) override;
|
|
void pitchBend(int16 bend) override;
|
|
void controlChange(byte control, byte value) override;
|
|
void pitchBendFactor(byte value) override;
|
|
void detune(int16 value) override;
|
|
void transpose(int8 value) override;
|
|
void priority(byte value) override;
|
|
void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override;
|
|
|
|
private:
|
|
void controlModulationWheel(byte value);
|
|
void controlVolume(byte value);
|
|
void controlPanPos(byte value);
|
|
void controlSustain(byte value);
|
|
|
|
void releasePedal();
|
|
|
|
TownsMidiOutputChannel *_out;
|
|
|
|
const byte _number;
|
|
uint8 *_instrument;
|
|
uint8 _priority;
|
|
uint8 _tl;
|
|
int8 _transpose;
|
|
int8 _detune;
|
|
int8 _modWheel;
|
|
uint8 _sustain;
|
|
uint8 _pitchBendFactor;
|
|
int16 _pitchBend;
|
|
uint16 _freqLSB;
|
|
|
|
bool _allocated;
|
|
|
|
IMuseDriver_FMTowns *_driver;
|
|
|
|
static const uint8 _programAdjustLevel[];
|
|
};
|
|
|
|
class TownsMidiChanState {
|
|
public:
|
|
TownsMidiChanState();
|
|
~TownsMidiChanState() {}
|
|
uint8 get(uint8 type);
|
|
|
|
uint8 unk1;
|
|
uint8 mulAmsFms;
|
|
uint8 tl;
|
|
uint8 attDec;
|
|
uint8 sus;
|
|
uint8 fgAlg;
|
|
uint8 unk2;
|
|
};
|
|
|
|
TownsMidiChanState::TownsMidiChanState() {
|
|
unk1 = mulAmsFms = tl = attDec = sus = fgAlg = unk2 = 0;
|
|
}
|
|
|
|
uint8 TownsMidiChanState::get(uint8 type) {
|
|
switch (type) {
|
|
case 0:
|
|
return unk1;
|
|
case 1:
|
|
return mulAmsFms;
|
|
case 2:
|
|
return tl;
|
|
case 3:
|
|
return attDec;
|
|
case 4:
|
|
return sus;
|
|
case 5:
|
|
return fgAlg;
|
|
case 6:
|
|
return unk2;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
TownsMidiOutputChannel::TownsMidiOutputChannel(IMuseDriver_FMTowns *driver, int number) : _driver(driver), _chan(number),
|
|
_in(nullptr), _prev(nullptr), _next(nullptr), _adjustModTl(0), _operator2Tl(0), _note(0), _operator1Tl(0), _sustainNoteOff(0), _duration(0), _freq(0), _freqAdjust(0) {
|
|
_effectEnvelopes = new EffectEnvelope[2]();
|
|
_effectDefs = new EffectDef[2]();
|
|
|
|
_effectDefs[0].s = &_effectEnvelopes[1];
|
|
_effectDefs[1].s = &_effectEnvelopes[0];
|
|
}
|
|
|
|
TownsMidiOutputChannel::~TownsMidiOutputChannel() {
|
|
delete[] _effectEnvelopes;
|
|
delete[] _effectDefs;
|
|
}
|
|
|
|
void TownsMidiOutputChannel::noteOn(uint8 msb, uint16 lsb) {
|
|
_freq = (msb << 7) + lsb;
|
|
_freqAdjust = 0;
|
|
keyOnSetFreq(_freq);
|
|
}
|
|
|
|
void TownsMidiOutputChannel::noteOnPitchBend(uint8 msb, uint16 lsb) {
|
|
_freq = (msb << 7) + lsb;
|
|
keyOnSetFreq(_freq + _freqAdjust);
|
|
}
|
|
|
|
void TownsMidiOutputChannel::setupProgram(const uint8 *data, uint8 mLevelPara, uint8 tLevelPara) {
|
|
// This driver uses only 2 operators and 2 algorithms (algorithm 5 and 7),
|
|
// since it is just a modified AdLib driver. It also uses AdLib programs.
|
|
// There are no FM-TOWNS specific programs. This is the reason for the low quality of the FM-TOWNS
|
|
// music (unsuitable data is just forced into the wrong audio device).
|
|
|
|
static const uint8 mul[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 };
|
|
uint8 chan = _chanMap[_chan];
|
|
|
|
uint8 mulAmsFms1 = _driver->_chanState[chan].mulAmsFms = data[0];
|
|
uint8 tl1 = _driver->_chanState[chan].tl = (data[1] | 0x3f) - mLevelPara;
|
|
uint8 attDec1 = _driver->_chanState[chan].attDec = ~data[2];
|
|
uint8 sus1 = _driver->_chanState[chan].sus = ~data[3];
|
|
_driver->_chanState[chan].unk2 = data[4];
|
|
chan += 3;
|
|
|
|
out(0x30, mul[mulAmsFms1 & 0x0f]);
|
|
out(0x40, (tl1 & 0x3f) + 15);
|
|
out(0x50, ((attDec1 >> 4) << 1) | ((attDec1 >> 4) & 1));
|
|
out(0x60, ((attDec1 << 1) | (attDec1 & 1)) & 0x1f);
|
|
out(0x70, (mulAmsFms1 & 0x20) ^ 0x20 ? (((sus1 & 0x0f) << 1) | 1) : 0);
|
|
out(0x80, sus1);
|
|
|
|
uint8 mulAmsFms2 = _driver->_chanState[chan].mulAmsFms = data[5];
|
|
uint8 tl2 = _driver->_chanState[chan].tl = (data[6] | 0x3f) - tLevelPara;
|
|
uint8 attDec2 = _driver->_chanState[chan].attDec = ~data[7];
|
|
uint8 sus2 = _driver->_chanState[chan].sus = ~data[8];
|
|
_driver->_chanState[chan].unk2 = data[9];
|
|
|
|
uint8 mul2 = mul[mulAmsFms2 & 0x0f];
|
|
tl2 = (tl2 & 0x3f) + 15;
|
|
uint8 ar2 = ((attDec2 >> 4) << 1) | ((attDec2 >> 4) & 1);
|
|
uint8 dec2 = ((attDec2 << 1) | (attDec2 & 1)) & 0x1f;
|
|
uint8 sus2r = (mulAmsFms2 & 0x20) ^ 0x20 ? (((sus2 & 0x0f) << 1) | 1) : 0;
|
|
|
|
for (int i = 4; i < 16; i += 4) {
|
|
out(0x30 + i, mul2);
|
|
out(0x40 + i, tl2);
|
|
out(0x50 + i, ar2);
|
|
out(0x60 + i, dec2);
|
|
out(0x70 + i, sus2r);
|
|
out(0x80 + i, sus2);
|
|
}
|
|
|
|
_driver->_chanState[chan].fgAlg = data[10];
|
|
|
|
uint8 alg = 5 + 2 * (data[10] & 1);
|
|
uint8 fb = 4 * (data[10] & 0x0e);
|
|
out(0xb0, fb | alg);
|
|
uint8 t = mulAmsFms1 | mulAmsFms2;
|
|
out(0xb4, (0xc0 | ((t & 0x80) >> 3) | ((t & 0x40) >> 5)));
|
|
}
|
|
|
|
void TownsMidiOutputChannel::setupEffects(int index, uint8 flags, const uint8 *effectData) {
|
|
uint16 effectMaxLevel[] = { 0x2FF, 0x1F, 0x07, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3F, 0x0F, 0x0F, 0x0F, 0x03, 0x3E, 0x1F };
|
|
uint8 effectType[] = { 0x1D, 0x1C, 0x1B, 0x00, 0x03, 0x04, 0x07, 0x08, 0x0D, 0x10, 0x11, 0x14, 0x15, 0x1e, 0x1f, 0x00 };
|
|
|
|
EffectEnvelope *s = &_effectEnvelopes[index];
|
|
EffectDef *d = &_effectDefs[index];
|
|
|
|
d->phase = 0;
|
|
d->useModWheel = flags & 0x40;
|
|
s->loop = flags & 0x20;
|
|
d->loopRefresh = flags & 0x10;
|
|
d->type = effectType[flags & 0x0f];
|
|
s->maxLevel = effectMaxLevel[flags & 0x0f];
|
|
s->modWheelSensitivity = 31;
|
|
s->modWheelState = d->useModWheel ? _in->_modWheel >> 2 : 31;
|
|
|
|
switch (d->type) {
|
|
case 0:
|
|
s->startLevel = _operator2Tl;
|
|
break;
|
|
case 13:
|
|
s->startLevel = _operator1Tl;
|
|
break;
|
|
case 30:
|
|
s->startLevel = 31;
|
|
d->s->modWheelState = 0;
|
|
break;
|
|
case 31:
|
|
s->startLevel = 0;
|
|
d->s->modWheelSensitivity = 0;
|
|
break;
|
|
default:
|
|
s->startLevel = getEffectStartLevel(d->type);
|
|
break;
|
|
}
|
|
|
|
startEffect(s, effectData);
|
|
}
|
|
|
|
void TownsMidiOutputChannel::setModWheel(uint8 value) {
|
|
if (_effectEnvelopes[0].state != kEnvReady && _effectDefs[0].type)
|
|
_effectEnvelopes[0].modWheelState = value >> 2;
|
|
|
|
if (_effectEnvelopes[1].state != kEnvReady && _effectDefs[1].type)
|
|
_effectEnvelopes[1].modWheelState = value >> 2;
|
|
}
|
|
|
|
void TownsMidiOutputChannel::connect(TownsMidiInputChannel *chan) {
|
|
if (!chan)
|
|
return;
|
|
|
|
_in = chan;
|
|
_next = chan->_out;
|
|
_prev = nullptr;
|
|
chan->_out = this;
|
|
if (_next)
|
|
_next->_prev = this;
|
|
}
|
|
|
|
void TownsMidiOutputChannel::disconnect() {
|
|
keyOff();
|
|
|
|
TownsMidiOutputChannel *p = _prev;
|
|
TownsMidiOutputChannel *n = _next;
|
|
|
|
if (n)
|
|
n->_prev = p;
|
|
if (p)
|
|
p->_next = n;
|
|
else
|
|
_in->_out = n;
|
|
_in = nullptr;
|
|
}
|
|
|
|
bool TownsMidiOutputChannel::update() {
|
|
if (!_in)
|
|
return false;
|
|
|
|
if (_duration) {
|
|
_duration -= 17;
|
|
if (_duration <= 0) {
|
|
disconnect();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (_effectEnvelopes[i].state != kEnvReady)
|
|
updateEffectGenerator(&_effectEnvelopes[i], &_effectDefs[i]);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int TownsMidiOutputChannel::checkPriority(int pri) {
|
|
if (!_in)
|
|
return kDisconnected;
|
|
|
|
if (!_next && pri >= _in->_priority)
|
|
return _in->_priority;
|
|
|
|
return kHighPriority;
|
|
}
|
|
|
|
void TownsMidiOutputChannel::startEffect(EffectEnvelope *s, const uint8 *effectData) {
|
|
s->state = kEnvAttacking;
|
|
s->currentLevel = 0;
|
|
s->modWheelLast = 31;
|
|
s->duration = effectData[0] * 63;
|
|
s->stateTargetLevels[0] = effectData[1];
|
|
s->stateTargetLevels[1] = effectData[3];
|
|
s->stateTargetLevels[2] = effectData[5];
|
|
s->stateTargetLevels[3] = effectData[6];
|
|
s->stateModWheelLevels[0] = effectData[2];
|
|
s->stateModWheelLevels[1] = effectData[4];
|
|
s->stateModWheelLevels[2] = 0;
|
|
s->stateModWheelLevels[3] = effectData[7];
|
|
initNextEnvelopeState(s);
|
|
}
|
|
|
|
void TownsMidiOutputChannel::updateEffectGenerator(EffectEnvelope *s, EffectDef *d) {
|
|
uint8 f = advanceEffectEnvelope(s, d);
|
|
|
|
if (f & 1) {
|
|
switch (d->type) {
|
|
case 0:
|
|
_operator2Tl = s->startLevel + d->phase;
|
|
break;
|
|
case 13:
|
|
_operator1Tl = s->startLevel + d->phase;
|
|
break;
|
|
case 30:
|
|
d->s->modWheelState = d->phase;
|
|
break;
|
|
case 31:
|
|
d->s->modWheelSensitivity = d->phase;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (f & 2) {
|
|
if (d->loopRefresh)
|
|
keyOn();
|
|
}
|
|
}
|
|
|
|
int TownsMidiOutputChannel::advanceEffectEnvelope(EffectEnvelope *s, EffectDef *d) {
|
|
if (s->duration) {
|
|
s->duration -= 17;
|
|
if (s->duration <= 0) {
|
|
s->state = kEnvReady;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int32 t = s->currentLevel + s->incrPerStep;
|
|
|
|
s->incrCountRem += s->incrPerStepRem;
|
|
if (s->incrCountRem >= s->numSteps) {
|
|
s->incrCountRem -= s->numSteps;
|
|
t += s->dir;
|
|
}
|
|
|
|
int retFlags = 0;
|
|
|
|
if (t != s->currentLevel || (s->modWheelState != s->modWheelLast)) {
|
|
s->currentLevel = t;
|
|
s->modWheelLast = s->modWheelState;
|
|
t = getEffectModLevel(t, s->modWheelState);
|
|
if (t != d->phase) {
|
|
d->phase = t;
|
|
retFlags |= 1;
|
|
}
|
|
}
|
|
|
|
if (--s->stepCounter)
|
|
return retFlags;
|
|
|
|
if (++s->state > kEnvReleasing) {
|
|
if (!s->loop) {
|
|
s->state = kEnvReady;
|
|
return retFlags;
|
|
}
|
|
s->state = kEnvAttacking;
|
|
retFlags |= 2;
|
|
}
|
|
|
|
initNextEnvelopeState(s);
|
|
|
|
return retFlags;
|
|
}
|
|
|
|
void TownsMidiOutputChannel::initNextEnvelopeState(EffectEnvelope *s) {
|
|
uint8 v = s->stateTargetLevels[s->state - 1];
|
|
int32 e = _effectEnvStepTable[_driver->_operatorLevelTable[((v & 0x7f) << 5) + s->modWheelSensitivity]];
|
|
|
|
if (v & 0x80)
|
|
e = _driver->randomValue(e);
|
|
|
|
if (!e)
|
|
e = 1;
|
|
|
|
s->numSteps = s->stepCounter = e;
|
|
int32 d = 0;
|
|
|
|
if (s->state != kEnvSustaining) {
|
|
v = s->stateModWheelLevels[s->state - 1];
|
|
e = getEffectModLevel(s->maxLevel, (v & 0x7f) - 31);
|
|
|
|
if (v & 0x80)
|
|
e = _driver->randomValue(e);
|
|
|
|
if (e + s->startLevel > s->maxLevel) {
|
|
e = s->maxLevel - s->startLevel;
|
|
} else {
|
|
if (e + s->startLevel < 0)
|
|
e = -s->startLevel;
|
|
}
|
|
|
|
d = e - s->currentLevel;
|
|
}
|
|
|
|
s->incrPerStep = d / s->numSteps;
|
|
s->dir = (d < 0) ? -1 : 1;
|
|
d *= s->dir;
|
|
s->incrPerStepRem = d % s->numSteps;
|
|
s->incrCountRem = 0;
|
|
}
|
|
|
|
int16 TownsMidiOutputChannel::getEffectStartLevel(uint8 type) {
|
|
uint8 chan = (type < 13) ? _chanMap2[_chan] : ((type < 26) ? _chanMap[_chan] : _chan);
|
|
|
|
if (type == 28)
|
|
return 15;
|
|
else if (type == 29)
|
|
return 383;
|
|
else if (type > 29)
|
|
return 0;
|
|
else if (type > 12)
|
|
type -= 13;
|
|
|
|
const uint8 *def = &_effectDefaults[type << 2];
|
|
uint8 res = (_driver->_chanState[chan].get(def[0] >> 5) & def[2]) >> def[1];
|
|
if (def[3])
|
|
res = def[3] - res;
|
|
|
|
return res;
|
|
}
|
|
|
|
int TownsMidiOutputChannel::getEffectModLevel(int lvl, int mod) {
|
|
if (mod == 0)
|
|
return 0;
|
|
|
|
if (mod == 31)
|
|
return lvl;
|
|
|
|
if (lvl > 63 || lvl < -63)
|
|
return ((lvl + 1) * mod) >> 5;
|
|
|
|
if (mod < 0) {
|
|
if (lvl < 0)
|
|
return _driver->_operatorLevelTable[((-lvl) << 5) - mod];
|
|
else
|
|
return -_driver->_operatorLevelTable[(lvl << 5) - mod];
|
|
} else {
|
|
if (lvl < 0)
|
|
return -_driver->_operatorLevelTable[((-lvl) << 5) + mod];
|
|
else
|
|
return _driver->_operatorLevelTable[(lvl << 5) + mod];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void TownsMidiOutputChannel::keyOn() {
|
|
out(0x28, 0x30);
|
|
}
|
|
|
|
void TownsMidiOutputChannel::keyOff() {
|
|
out(0x28, 0);
|
|
}
|
|
|
|
void TownsMidiOutputChannel::keyOnSetFreq(uint16 frq) {
|
|
uint16 note = (frq << 1) >> 8;
|
|
frq = (_freqMSB[note] << 11) | _freqLSB[note];
|
|
out(0xa4, frq >> 8);
|
|
out(0xa0, frq & 0xff);
|
|
//out(0x28, 0x00);
|
|
out(0x28, 0x30);
|
|
}
|
|
|
|
void TownsMidiOutputChannel::out(uint8 reg, uint8 val) {
|
|
static const uint8 chanRegOffs[] = { 0, 1, 2, 0, 1, 2 };
|
|
static const uint8 keyValOffs[] = { 0, 1, 2, 4, 5, 6 };
|
|
|
|
if (reg == 0x28)
|
|
val = (val & 0xf0) | keyValOffs[_chan];
|
|
if (reg < 0x30)
|
|
_driver->_intf->callback(17, 0, reg, val);
|
|
else
|
|
_driver->_intf->callback(17, _chan / 3, (reg & ~3) | chanRegOffs[_chan], val);
|
|
}
|
|
|
|
const uint8 TownsMidiOutputChannel::_chanMap[] = {
|
|
0, 1, 2, 8, 9, 10
|
|
};
|
|
|
|
const uint8 TownsMidiOutputChannel::_chanMap2[] = {
|
|
3, 4, 5, 11, 12, 13
|
|
};
|
|
|
|
const uint8 TownsMidiOutputChannel::_effectDefaults[] = {
|
|
0x40, 0x00, 0x3F, 0x3F, 0xE0, 0x02, 0x00, 0x00, 0x40, 0x06, 0xC0, 0x00,
|
|
0x20, 0x00, 0x0F, 0x00, 0x60, 0x04, 0xF0, 0x0F, 0x60, 0x00, 0x0F, 0x0F,
|
|
0x80, 0x04, 0xF0, 0x0F, 0x80, 0x00, 0x0F, 0x0F, 0xE0, 0x00, 0x03, 0x00,
|
|
0x20, 0x07, 0x80, 0x00, 0x20, 0x06, 0x40, 0x00, 0x20, 0x05, 0x20, 0x00,
|
|
0x20, 0x04, 0x10, 0x00, 0xC0, 0x00, 0x01, 0x00, 0xC0, 0x01, 0x0E, 0x00
|
|
};
|
|
|
|
const uint16 TownsMidiOutputChannel::_effectEnvStepTable[] = {
|
|
0x0001, 0x0002, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009,
|
|
0x000A, 0x000C, 0x000E, 0x0010, 0x0012, 0x0015, 0x0018, 0x001E,
|
|
0x0024, 0x0032, 0x0040, 0x0052, 0x0064, 0x0088, 0x00A0, 0x00C0,
|
|
0x00F0, 0x0114, 0x0154, 0x01CC, 0x0258, 0x035C, 0x04B0, 0x0640
|
|
};
|
|
|
|
const uint8 TownsMidiOutputChannel::_freqMSB[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
|
0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
|
0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
|
|
0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
|
|
0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
|
|
0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
|
|
0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
|
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
|
|
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x80, 0x81, 0x83, 0x85,
|
|
0x87, 0x88, 0x8A, 0x8C, 0x8E, 0x8F, 0x91, 0x93, 0x95, 0x96, 0x98, 0x9A,
|
|
0x9C, 0x9E, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xA8, 0xAA, 0xAC, 0xAD, 0xAF,
|
|
0xB1, 0xB3, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBD, 0xBF, 0xC1, 0xC3, 0xC4,
|
|
0xC6, 0xC8, 0xCA, 0xCB, 0xCD, 0xCF, 0xD1, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA,
|
|
0xDB, 0xDD, 0xDF, 0xE1, 0xE2, 0xE4, 0xE6, 0xE8, 0xE9, 0xEB, 0xED, 0xEF
|
|
};
|
|
|
|
const uint16 TownsMidiOutputChannel::_freqLSB[] = {
|
|
0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6,
|
|
0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x02D6, 0x0301, 0x032F,
|
|
0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
|
|
0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
|
|
0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
|
|
0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
|
|
0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
|
|
0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
|
|
0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
|
|
0x055B, 0x02D6, 0x0301, 0x032F, 0x0360, 0x0393, 0x03C9, 0x0403,
|
|
0x0440, 0x0481, 0x04C6, 0x050E, 0x055B, 0x02D6, 0x0301, 0x032F,
|
|
0x0360, 0x0393, 0x03C9, 0x0403, 0x0440, 0x0481, 0x04C6, 0x050E,
|
|
0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
|
|
0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
|
|
0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B,
|
|
0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B, 0x055B
|
|
};
|
|
|
|
TownsMidiInputChannel::TownsMidiInputChannel(IMuseDriver_FMTowns *driver, int16 number) : MidiChannel(), _driver(driver), _number(number), _out(nullptr),
|
|
_priority(0), _tl(0), _transpose(0), _pitchBendFactor(0), _pitchBend(0), _sustain(0), _freqLSB(0), _detune(0), _modWheel(0), _allocated(false) {
|
|
_instrument = new uint8[30]();
|
|
}
|
|
|
|
TownsMidiInputChannel::~TownsMidiInputChannel() {
|
|
delete[] _instrument;
|
|
}
|
|
|
|
bool TownsMidiInputChannel::allocate() {
|
|
if (_allocated)
|
|
return false;
|
|
_allocated = true;
|
|
return true;
|
|
}
|
|
|
|
void TownsMidiInputChannel::release() {
|
|
_allocated = false;
|
|
}
|
|
|
|
void TownsMidiInputChannel::send(uint32 b) {
|
|
device()->send(b | _number);
|
|
}
|
|
|
|
void TownsMidiInputChannel::noteOff(byte note) {
|
|
if (!_out)
|
|
return;
|
|
|
|
for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
|
|
if (oc->_note != note)
|
|
continue;
|
|
|
|
if (_sustain)
|
|
oc->_sustainNoteOff = 1;
|
|
else
|
|
oc->disconnect();
|
|
}
|
|
}
|
|
|
|
void TownsMidiInputChannel::noteOn(byte note, byte velocity) {
|
|
TownsMidiOutputChannel *oc = _driver->allocateOutputChannel(_priority);
|
|
|
|
if (!oc)
|
|
return;
|
|
|
|
oc->connect(this);
|
|
|
|
oc->_adjustModTl = _instrument[10] & 1;
|
|
oc->_note = note;
|
|
oc->_sustainNoteOff = 0;
|
|
oc->_duration = _instrument[29] * 63;
|
|
|
|
oc->_operator1Tl = (_instrument[1] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[4] >> 2)];
|
|
if (oc->_operator1Tl > 63)
|
|
oc->_operator1Tl = 63;
|
|
|
|
oc->_operator2Tl = (_instrument[6] & 0x3f) + _driver->_operatorLevelTable[((velocity >> 1) << 5) + (_instrument[9] >> 2)];
|
|
if (oc->_operator2Tl > 63)
|
|
oc->_operator2Tl = 63;
|
|
|
|
oc->setupProgram(_instrument, oc->_adjustModTl == 1 ? _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator1Tl << 5)]] : oc->_operator1Tl, _programAdjustLevel[_driver->_operatorLevelTable[(_tl >> 2) + (oc->_operator2Tl << 5)]]);
|
|
oc->noteOn(note + _transpose, _freqLSB);
|
|
|
|
if (_instrument[11] & 0x80)
|
|
oc->setupEffects(0, _instrument[11], &_instrument[12]);
|
|
else
|
|
oc->_effectEnvelopes[0].state = kEnvReady;
|
|
|
|
if (_instrument[20] & 0x80)
|
|
oc->setupEffects(1, _instrument[20], &_instrument[21]);
|
|
else
|
|
oc->_effectEnvelopes[1].state = kEnvReady;
|
|
}
|
|
|
|
void TownsMidiInputChannel::programChange(byte program) {
|
|
// Not implemented (The loading and assignment of programs
|
|
// is handled externally by the SCUMM engine. The programs
|
|
// get sent via sysEx_customInstrument.)
|
|
}
|
|
|
|
void TownsMidiInputChannel::pitchBend(int16 bend) {
|
|
_pitchBend = bend;
|
|
_freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
|
|
for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
|
|
oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
|
|
}
|
|
|
|
void TownsMidiInputChannel::controlChange(byte control, byte value) {
|
|
switch (control) {
|
|
case 1:
|
|
controlModulationWheel(value);
|
|
break;
|
|
case 7:
|
|
controlVolume(value);
|
|
break;
|
|
case 10:
|
|
controlPanPos(value);
|
|
break;
|
|
case 64:
|
|
controlSustain(value);
|
|
break;
|
|
case 123:
|
|
while (_out)
|
|
_out->disconnect();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TownsMidiInputChannel::pitchBendFactor(byte value) {
|
|
_pitchBendFactor = value;
|
|
_freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
|
|
for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
|
|
oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
|
|
}
|
|
|
|
void TownsMidiInputChannel::detune(int16 value) {
|
|
_detune = (int8)value;
|
|
_freqLSB = ((_pitchBend * _pitchBendFactor) >> 6) + _detune;
|
|
for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
|
|
oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
|
|
}
|
|
|
|
void TownsMidiInputChannel::transpose(int8 value) {
|
|
_transpose = value;
|
|
for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
|
|
oc->noteOnPitchBend(oc->_note + _transpose, _freqLSB);
|
|
}
|
|
|
|
void TownsMidiInputChannel::priority(byte value) {
|
|
_priority = value;
|
|
}
|
|
|
|
void TownsMidiInputChannel::sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) {
|
|
const uint8 instrSize = 30;
|
|
if (instr && dataSize == instrSize)
|
|
memcpy(_instrument, instr, instrSize);
|
|
else if (type != 'ADL ') // FM-Towns is actually supposed to get ADL type data.
|
|
warning("TownsMidiInputChannel: Receiving '%c%c%c%c' instrument data. Probably loading a savegame with that sound setting", (type >> 24) & 0xFF, (type >> 16) & 0xFF, (type >> 8) & 0xFF, type & 0xFF);
|
|
}
|
|
|
|
void TownsMidiInputChannel::controlModulationWheel(byte value) {
|
|
_modWheel = value;
|
|
for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next)
|
|
oc->setModWheel(value);
|
|
}
|
|
|
|
void TownsMidiInputChannel::controlVolume(byte value) {
|
|
/* This is all done inside the imuse code
|
|
uint16 v1 = _ctrlVolume + 1;
|
|
uint16 v2 = value;
|
|
if (_chanIndex != 16) {
|
|
_ctrlVolume = value;
|
|
v2 = _player->getEffectiveVolume();
|
|
}
|
|
_tl = (v1 * v2) >> 7;*/
|
|
|
|
_tl = value;
|
|
}
|
|
|
|
void TownsMidiInputChannel::controlPanPos(byte value) {
|
|
// not implemented
|
|
}
|
|
|
|
void TownsMidiInputChannel::controlSustain(byte value) {
|
|
_sustain = value;
|
|
if (!value)
|
|
releasePedal();
|
|
}
|
|
|
|
void TownsMidiInputChannel::releasePedal() {
|
|
for (TownsMidiOutputChannel *oc = _out; oc; oc = oc->_next) {
|
|
if (oc->_sustainNoteOff)
|
|
oc->disconnect();
|
|
}
|
|
}
|
|
|
|
const uint8 TownsMidiInputChannel::_programAdjustLevel[] = {
|
|
0x00, 0x04, 0x07, 0x0B, 0x0D, 0x10, 0x12, 0x14,
|
|
0x16, 0x18, 0x1A, 0x1B, 0x1D, 0x1E, 0x1F, 0x21,
|
|
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
|
|
0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F,
|
|
0x30, 0x31, 0x31, 0x32, 0x33, 0x33, 0x34, 0x35,
|
|
0x35, 0x36, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39,
|
|
0x39, 0x3A, 0x3A, 0x3B, 0x3B, 0x3C, 0x3C, 0x3C,
|
|
0x3D, 0x3D, 0x3E, 0x3E, 0x3E, 0x3F, 0x3F, 0x3F
|
|
};
|
|
|
|
IMuseDriver_FMTowns::IMuseDriver_FMTowns(Audio::Mixer *mixer) : _timerProc(nullptr), _timerProcPara(nullptr), _channels(nullptr), _out(nullptr),
|
|
_baseTempo(10080), _chanState(nullptr), _operatorLevelTable(nullptr), _tickCounter(0), _rand(1), _allocCurPos(0), _isOpen(false), _numParts(24) {
|
|
_intf = new TownsAudioInterface(mixer, this, true);
|
|
|
|
_channels = new TownsMidiInputChannel*[_numParts];
|
|
for (int i = 0; i < 24; i++)
|
|
_channels[i] = new TownsMidiInputChannel(this, i > 8 ? (i + 1) : i);
|
|
|
|
_out = new TownsMidiOutputChannel*[6];
|
|
for (int i = 0; i < 6; i++)
|
|
_out[i] = new TownsMidiOutputChannel(this, i);
|
|
|
|
_chanState = new TownsMidiChanState[_numParts];
|
|
|
|
_operatorLevelTable = new uint8[2048];
|
|
for (int i = 0; i < 64; i++) {
|
|
for (int ii = 0; ii < 32; ii++)
|
|
_operatorLevelTable[(i << 5) + ii] = ((i * (ii + 1)) >> 5) & 0xff;
|
|
}
|
|
for (int i = 0; i < 64; i++)
|
|
_operatorLevelTable[i << 5] = 0;
|
|
}
|
|
|
|
IMuseDriver_FMTowns::~IMuseDriver_FMTowns() {
|
|
close();
|
|
delete _intf;
|
|
|
|
if (_channels) {
|
|
for (int i = 0; i < _numParts; i++)
|
|
delete _channels[i];
|
|
delete[] _channels;
|
|
}
|
|
_channels = nullptr;
|
|
|
|
if (_out) {
|
|
for (int i = 0; i < 6; i++)
|
|
delete _out[i];
|
|
delete[] _out;
|
|
}
|
|
_out = nullptr;
|
|
|
|
delete[] _chanState;
|
|
_chanState = nullptr;
|
|
delete[] _operatorLevelTable;
|
|
_operatorLevelTable = nullptr;
|
|
}
|
|
|
|
int IMuseDriver_FMTowns::open() {
|
|
if (_isOpen)
|
|
return MERR_ALREADY_OPEN;
|
|
|
|
if (!_intf->init())
|
|
return MERR_CANNOT_CONNECT;
|
|
|
|
_intf->callback(0);
|
|
|
|
_intf->callback(21, 255, 1);
|
|
_intf->callback(21, 0, 1);
|
|
_intf->callback(22, 255, 221);
|
|
|
|
_intf->callback(33, 8);
|
|
_intf->setSoundEffectChanMask(~0x3f);
|
|
|
|
_allocCurPos = 0;
|
|
|
|
_isOpen = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void IMuseDriver_FMTowns::close() {
|
|
if (!_isOpen)
|
|
return;
|
|
|
|
_isOpen = false;
|
|
|
|
setTimerCallback(nullptr, nullptr);
|
|
g_system->delayMillis(20);
|
|
}
|
|
|
|
void IMuseDriver_FMTowns::send(uint32 b) {
|
|
if (!_isOpen)
|
|
return;
|
|
|
|
byte param2 = (b >> 16) & 0xFF;
|
|
byte param1 = (b >> 8) & 0xFF;
|
|
byte cmd = b & 0xF0;
|
|
|
|
TownsMidiInputChannel *c = _channels[b & 0x0F];
|
|
|
|
switch (cmd) {
|
|
case 0x80:
|
|
c->noteOff(param1);
|
|
break;
|
|
case 0x90:
|
|
if (param2)
|
|
c->noteOn(param1, param2);
|
|
else
|
|
c->noteOff(param1);
|
|
break;
|
|
case 0xB0:
|
|
c->controlChange(param1, param2);
|
|
break;
|
|
case 0xC0:
|
|
c->programChange(param1);
|
|
break;
|
|
case 0xE0:
|
|
c->pitchBend((param1 | (param2 << 7)) - 0x2000);
|
|
break;
|
|
case 0xF0:
|
|
warning("MidiDriver_TOWNS: Receiving SysEx command on a send() call");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void IMuseDriver_FMTowns::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
|
|
_timerProc = timer_proc;
|
|
_timerProcPara = timer_param;
|
|
}
|
|
|
|
uint32 IMuseDriver_FMTowns::getBaseTempo() {
|
|
return _baseTempo;
|
|
}
|
|
|
|
MidiChannel *IMuseDriver_FMTowns::allocateChannel() {
|
|
if (!_isOpen)
|
|
return nullptr;
|
|
|
|
for (int i = 0; i < _numParts; ++i) {
|
|
TownsMidiInputChannel *chan = _channels[i];
|
|
if (chan->allocate())
|
|
return chan;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
MidiChannel *IMuseDriver_FMTowns::getPercussionChannel() {
|
|
return nullptr;
|
|
}
|
|
|
|
void IMuseDriver_FMTowns::timerCallback(int timerId) {
|
|
if (!_isOpen)
|
|
return;
|
|
|
|
switch (timerId) {
|
|
case 1:
|
|
updateParser();
|
|
updateOutputChannels();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void IMuseDriver_FMTowns::updateParser() {
|
|
if (_timerProc)
|
|
_timerProc(_timerProcPara);
|
|
}
|
|
|
|
void IMuseDriver_FMTowns::updateOutputChannels() {
|
|
_tickCounter += _baseTempo;
|
|
while (_tickCounter >= 16667) {
|
|
_tickCounter -= 16667;
|
|
for (int i = 0; i < 6; i++) {
|
|
if (_out[i]->update())
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
TownsMidiOutputChannel *IMuseDriver_FMTowns::allocateOutputChannel(uint8 pri) {
|
|
TownsMidiOutputChannel *res = nullptr;
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
if (++_allocCurPos == 6)
|
|
_allocCurPos = 0;
|
|
|
|
int s = _out[_allocCurPos]->checkPriority(pri);
|
|
if (s == TownsMidiOutputChannel::kDisconnected)
|
|
return _out[_allocCurPos];
|
|
|
|
if (s != TownsMidiOutputChannel::kHighPriority) {
|
|
pri = s;
|
|
res = _out[_allocCurPos];
|
|
}
|
|
}
|
|
|
|
if (res)
|
|
res->disconnect();
|
|
|
|
return res;
|
|
}
|
|
|
|
int IMuseDriver_FMTowns::randomValue(int para) {
|
|
_rand = (_rand & 1) ? (_rand >> 1) ^ 0xb8 : (_rand >> 1);
|
|
return (_rand * para) >> 8;
|
|
}
|
|
|
|
} // end of namespace Scumm
|