SCUMM: (DOTT) - fix AdLib transpose/detune

Continued bug fixing for ticket no. 13460 ("Incorrect MIDI
pitch bending"). This time the AdLib part (which applies
to FM-Towns, too, since it is more or less the same driver).
This fixes the sound when the bird is dropping from the
sky in the DOTT intro.

Also separate the GM code for SAMNMAX a bit after
checking the original code.
This commit is contained in:
athrxx 2022-09-06 21:17:21 +02:00
parent 78a19f1d23
commit 162e1bb764
6 changed files with 79 additions and 29 deletions

View File

@ -84,7 +84,7 @@ protected:
AdLibVoice *_voice;
int16 _pitchBend;
byte _pitchBendFactor;
//int8 _transposeEff;
int8 _transposeEff;
byte _volEff;
int8 _detuneEff;
byte _modWheel;
@ -110,7 +110,7 @@ public:
_voice = nullptr;
_pitchBend = 0;
_pitchBendFactor = 2;
//_transposeEff = 0;
_transposeEff = 0;
_volEff = 0;
_detuneEff = 0;
_modWheel = 0;
@ -148,6 +148,7 @@ public:
void panPosition(byte value) override;
void pitchBendFactor(byte value) override;
void detune(byte value) override;
void transpose(int8 value) override;
void priority(byte value) override;
void sustain(bool value) override;
void effectLevel(byte value) override { return; } // Not supported
@ -1090,11 +1091,11 @@ void AdLibPart::pitchBend(int16 bend) {
#ifdef ENABLE_OPL3
if (!_owner->_opl3Mode) {
#endif
_owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/,
_owner->adlibNoteOn(voice->_channel, voice->_note + _transposeEff,
(_pitchBend * _pitchBendFactor >> 6) + _detuneEff);
#ifdef ENABLE_OPL3
} else {
_owner->adlibNoteOn(voice->_channel, voice->_note, (_pitchBend * _pitchBendFactor) >> 5);
_owner->adlibNoteOn(voice->_channel, voice->_note + _transposeEff, (_pitchBend * _pitchBendFactor) >> 5);
}
#endif
}
@ -1202,11 +1203,11 @@ void AdLibPart::pitchBendFactor(byte value) {
#ifdef ENABLE_OPL3
if (!_owner->_opl3Mode) {
#endif
_owner->adlibNoteOn(voice->_channel, voice->_note /* + _transposeEff*/,
_owner->adlibNoteOn(voice->_channel, voice->_note + _transposeEff,
(_pitchBend * _pitchBendFactor >> 6) + _detuneEff);
#ifdef ENABLE_OPL3
} else {
_owner->adlibNoteOn(voice->_channel, voice->_note, (_pitchBend * _pitchBendFactor) >> 5);
_owner->adlibNoteOn(voice->_channel, voice->_note + _transposeEff, (_pitchBend * _pitchBendFactor) >> 5);
}
#endif
}
@ -1229,11 +1230,19 @@ void AdLibPart::detune(byte value) {
_detuneEff = value;
for (voice = _voice; voice; voice = voice->_next) {
_owner->adlibNoteOn(voice->_channel, voice->_note/* + _transposeEff*/,
_owner->adlibNoteOn(voice->_channel, voice->_note + _transposeEff,
(_pitchBend * _pitchBendFactor >> 6) + _detuneEff);
}
}
void AdLibPart::transpose(int8 value) {
_transposeEff = value;
for (AdLibVoice *voice = _voice; voice; voice = voice->_next) {
_owner->adlibNoteOn(voice->_channel, voice->_note + _transposeEff,
(_pitchBend * _pitchBendFactor >> 6) + _detuneEff);
}
}
void AdLibPart::priority(byte value) {
_priEff = value;
}
@ -1566,11 +1575,11 @@ void MidiDriver_ADLIB::setPitchBendRange(byte channel, uint range) {
#ifdef ENABLE_OPL3
if (!_opl3Mode) {
#endif
adlibNoteOn(voice->_channel, voice->_note/* + part->_transposeEff*/,
adlibNoteOn(voice->_channel, voice->_note + part->_transposeEff,
(part->_pitchBend * part->_pitchBendFactor >> 6) + part->_detuneEff);
#ifdef ENABLE_OPL3
} else {
adlibNoteOn(voice->_channel, voice->_note, (part->_pitchBend * part->_pitchBendFactor) >> 5);
adlibNoteOn(voice->_channel, voice->_note + part->_transposeEff, (part->_pitchBend * part->_pitchBendFactor) >> 5);
}
#endif
}
@ -2076,7 +2085,7 @@ void MidiDriver_ADLIB::mcKeyOn(AdLibVoice *voice, const AdLibInstrument *instr,
#ifdef ENABLE_OPL3
if (!_opl3Mode) {
#endif
adlibNoteOnEx(voice->_channel, /*part->_transposeEff + */note, part->_detuneEff + (part->_pitchBend * part->_pitchBendFactor >> 6));
adlibNoteOnEx(voice->_channel, note + part->_transposeEff, part->_detuneEff + (part->_pitchBend * part->_pitchBendFactor >> 6));
if (instr->flagsA & 0x80) {
mcInitStuff(voice, &voice->_s10a, &voice->_s11a, instr->flagsA, &instr->extraA);
@ -2092,7 +2101,7 @@ void MidiDriver_ADLIB::mcKeyOn(AdLibVoice *voice, const AdLibInstrument *instr,
#ifdef ENABLE_OPL3
} else {
adlibSetupChannelSecondary(voice->_channel, second, secVol1, secVol2, pan);
adlibNoteOnEx(voice->_channel, note, (part->_pitchBend * part->_pitchBendFactor) >> 5);
adlibNoteOnEx(voice->_channel, note + part->_transposeEff, (part->_pitchBend * part->_pitchBendFactor) >> 5);
}
#endif
}

View File

@ -132,6 +132,8 @@ public:
void pitchBend(int16 bend) override;
void controlChange(byte control, byte value) override;
void pitchBendFactor(byte value) override;
void detune(byte value) override;
void transpose(int8 value) override;
void priority(byte value) override;
void sysEx_customInstrument(uint32 type, const byte *instr) override;
@ -766,6 +768,19 @@ void TownsMidiInputChannel::pitchBendFactor(byte value) {
oc->noteOnPitchBend(oc->_note + oc->_in->_transpose, _freqLSB);
}
void TownsMidiInputChannel::detune(byte value) {
_detune = 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;
}

View File

@ -1632,8 +1632,9 @@ void IMuseInternal::initGM() {
// These are the init messages from the DOTT General Midi
// driver. This is the major part of the bug fix for bug
// no. 13460 ("DOTT: Incorrect MIDI pitch bending").
// Might be worthwhile to check if other GM drivers (like
// SAM) have the same values...
// SAMNMAX has some less of the default settings (since
// the driver works a bit different), but it uses the same
// value for the pitch bend range.
MidiDriver *m = _midi_native;
for (int i = 0; i < 16; ++i) {
m->send(0x0064B0 | i);
@ -1641,6 +1642,7 @@ void IMuseInternal::initGM() {
m->send(0x1006B0 | i);
m->send(0x7F07B0 | i);
m->send(0x3F0AB0 | i);
m->send(0x0000C0 | i);
m->send(0x4000E0 | i);
m->send(0x0001B0 | i);
m->send(0x0040B0 | i);

View File

@ -221,6 +221,7 @@ protected:
bool _isMIDI;
bool _isMT32;
bool _isGM;
bool _isAdLibOrFMTowns;
bool _supportsPercussion;
protected:
@ -277,6 +278,7 @@ public:
bool isMIDI() const { return _isMIDI; }
bool isMT32() const { return _isMT32; }
bool isGM() const { return _isGM; }
bool isAdLibOrFMTowns() const { return _isAdLibOrFMTowns; }
bool jump(uint track, uint beat, uint tick);
void onTimer();
void removePart(Part *part);

View File

@ -116,6 +116,9 @@ void Part::set_detune(int8 detune) {
#endif
} else {
_detune_eff = clamp((_detune = detune) + _player->getDetune(), -128, 127);
// Some drivers handle the transpose and the detune in pitchBend()...
if (_mc && _player->isAdLibOrFMTowns())
_mc->detune(_detune_eff);
sendPitchBend();
}
}
@ -153,7 +156,10 @@ void Part::set_transpose(int8 transpose) {
sendTranspose();
} else {
_transpose_eff = (_transpose == -128) ? 0 : transpose_clamp(_transpose + _player->getTranspose(), -24, 24);
sendPitchBend();
if (_player->isAdLibOrFMTowns())
sendTranspose();
else
sendPitchBend();
}
}
@ -364,20 +370,35 @@ void Part::sendPitchBend() {
return;
int16 bend = _pitchbend;
// RPN-based pitchbend range doesn't work for the MT32,
// so we'll do the scaling ourselves.
if (_player->_se->isNativeMT32())
int8 transpose = _transpose_eff;
int8 detune = _detune_eff;
// For Amiga, AdLib and FM-Towns we send some values separately due to the way the drivers have
// been implemented (it must be avoided that the pitchbend factor gets applied on top). So we
// neutralize them here for the pitch bend calculation.
if (_se->_isAmiga) {
transpose = 0;
} else if (_player->isAdLibOrFMTowns()) {
transpose = detune = 0;
} else if (_player->_se->isNativeMT32()) {
// RPN-based pitchbend range doesn't work for the MT32, so we'll do the scaling ourselves.
bend = bend * _pitchbend_factor / 12;
}
// We send the transpose value separately for Amiga (which is more like the original handles this).
// Some rhythm instruments depend on this.
int8 transpose = _se->_isAmiga ? 0 : _transpose_eff;
if (_player->isGM())
// This is the DOTT formula. Might not be exactly like this for SAM...
bend = ((clamp(((bend * _pitchbend_factor) >> 6) + _detune_eff + (transpose << 7), -2048, 2047) + 0x800) << 2) - 0x2000;
else
if (_player->isGM()) {
if (_se->_game_id == GID_SAMNMAX) {
if (bend)
debug ("PB: _pitchbend %d, _pitchbend_factor %d", bend, _pitchbend_factor);
// SAMNMAX formula
bend = _pitchbend_factor ? (bend * _pitchbend_factor) >> 5 : bend >> 6;
bend = (bend + _detune_eff + (transpose << 8)) << 1;
} else {
// DOTT formula (from the DOTT GMIDI.IMS driver)
bend = clamp(((bend * _pitchbend_factor) >> 6) + _detune_eff + (transpose << 7), -2048, 2047) << 2;
}
} else {
bend = clamp(bend + (_detune_eff * 64 / 12) + (transpose * 8192 / 12), -8192, 8191);
}
_mc->pitchBend(bend);
}
@ -386,10 +407,8 @@ void Part::sendTranspose() {
if (!_mc)
return;
// See comment above. The transpose function was never implemented into our other drivers,
// since this seems to have been handled via pitchBend() instead. The original drivers do have
// such functions.
if (!_se->_isAmiga)
// Some drivers handle the transpose and the detune in pitchBend()...
if (!_se->_isAmiga && !_player->isAdLibOrFMTowns())
return;
_mc->transpose(_transpose_eff);

View File

@ -78,6 +78,7 @@ Player::Player() :
_isMT32(false),
_isMIDI(false),
_isGM(false),
_isAdLibOrFMTowns(false),
_supportsPercussion(false),
_se(nullptr),
_vol_chan(0),
@ -109,6 +110,7 @@ bool Player::startSound(int sound, MidiDriver *midi) {
_isMIDI = _se->isMIDI(sound);
_supportsPercussion = _se->supportsPercussion(sound);
_isGM = (_supportsPercussion && !_isMT32); // Unlike IMuseInternal::isMIDI(), IMuseInternal::supportsPercussion() really filters out all non-MIDI things...
_isAdLibOrFMTowns = (_se->_midi_adlib && !_isMIDI);
_parts = nullptr;
_active = true;
@ -1031,6 +1033,7 @@ void Player::fixAfterLoad() {
_isMIDI = _se->isMIDI(_id);
_supportsPercussion = _se->supportsPercussion(_id);
_isGM = (_supportsPercussion && !_isMT32); // Unlike IMuseInternal::isMIDI(), IMuseInternal::supportsPercussion() really filters out all non-MIDI things...
_isAdLibOrFMTowns = (_se->_midi_adlib && !_isMIDI);
}
}