scummvm/engines/gob/sound/adlib.cpp
Sven Hesse 2c81602454 GOB: Don't completely reset the OPL in AdLib::initOPL()
It shouldn't be necessary anymore, since initOPL() cleans everything.
If, however, suddenly a piece of music sounds weird in a Gob game,
this is the place to look for.

This fixes a race condition between OPL::reset() and the callback
timer.
2016-02-28 21:04:23 +01:00

663 lines
18 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/util.h"
#include "common/textconsole.h"
#include "common/debug.h"
#include "common/config-manager.h"
#include "audio/fmopl.h"
#include "gob/gob.h"
#include "gob/sound/adlib.h"
namespace Gob {
static const int kPitchTom = 24;
static const int kPitchTomToSnare = 7;
static const int kPitchSnareDrum = kPitchTom + kPitchTomToSnare;
// Attenuation map for GUI volume slider
// Note: no volume control in the original engine
const uint8 AdLib::kVolumeTable[Audio::Mixer::kMaxMixerVolume + 1] = {
63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 62, 61, 59, 57, 56, 55,
53, 52, 51, 50, 49, 48, 47, 46, 46, 45, 44, 43, 43, 42, 41, 41,
40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 33,
32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27,
27, 26, 26, 26, 26, 25, 25, 25, 24, 24, 24, 24, 23, 23, 23, 23,
22, 22, 22, 22, 21, 21, 21, 21, 21, 20, 20, 20, 20, 19, 19, 19,
19, 19, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 16, 16, 16,
16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 13,
13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11,
11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9,
9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7,
7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0
};
// Is the operator a modulator (0) or a carrier (1)?
const uint8 AdLib::kOperatorType[kOperatorCount] = {
0, 0, 0, 1, 1, 1,
0, 0, 0, 1, 1, 1,
0, 0, 0, 1, 1, 1
};
// Operator number to register offset on the OPL
const uint8 AdLib::kOperatorOffset[kOperatorCount] = {
0, 1, 2, 3, 4, 5,
8, 9, 10, 11, 12, 13,
16, 17, 18, 19, 20, 21
};
// For each operator, the voice it belongs to
const uint8 AdLib::kOperatorVoice[kOperatorCount] = {
0, 1, 2,
0, 1, 2,
3, 4, 5,
3, 4, 5,
6, 7, 8,
6, 7, 8,
};
// Voice to operator set, for the 9 melodyvoices (only 6 useable in percussion mode)
const uint8 AdLib::kVoiceMelodyOperator[kOperatorsPerVoice][kMelodyVoiceCount] = {
{0, 1, 2, 6, 7, 8, 12, 13, 14},
{3, 4, 5, 9, 10, 11, 15, 16, 17}
};
// Voice to operator set, for the 5 percussion voices (only useable in percussion mode)
const uint8 AdLib::kVoicePercussionOperator[kOperatorsPerVoice][kPercussionVoiceCount] = {
{12, 16, 14, 17, 13},
{15, 0, 0, 0, 0}
};
// Mask bits to set each percussion instrument on/off
const byte AdLib::kPercussionMasks[kPercussionVoiceCount] = {0x10, 0x08, 0x04, 0x02, 0x01};
// Default instrument presets
const uint16 AdLib::kPianoParams [kOperatorsPerVoice][kParamCount] = {
{ 1, 1, 3, 15, 5, 0, 1, 3, 15, 0, 0, 0, 1, 0},
{ 0, 1, 1, 15, 7, 0, 2, 4, 0, 0, 0, 1, 0, 0} };
const uint16 AdLib::kBaseDrumParams[kOperatorsPerVoice][kParamCount] = {
{ 0, 0, 0, 10, 4, 0, 8, 12, 11, 0, 0, 0, 1, 0 },
{ 0, 0, 0, 13, 4, 0, 6, 15, 0, 0, 0, 0, 1, 0 } };
const uint16 AdLib::kSnareDrumParams[kParamCount] = {
0, 12, 0, 15, 11, 0, 8, 5, 0, 0, 0, 0, 0, 0 };
const uint16 AdLib::kTomParams [kParamCount] = {
0, 4, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 };
const uint16 AdLib::kCymbalParams [kParamCount] = {
0, 1, 0, 15, 11, 0, 5, 5, 0, 0, 0, 0, 0, 0 };
const uint16 AdLib::kHihatParams [kParamCount] = {
0, 1, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 };
AdLib::AdLib(int callbackFreq) : _opl(0),
_toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true), _volume(0) {
initFreqs();
createOPL();
initOPL();
syncVolume();
_opl->start(new Common::Functor0Mem<void, AdLib>(this, &AdLib::onTimer), callbackFreq);
}
AdLib::~AdLib() {
delete _opl;
}
// Creates the OPL. Try to use the DOSBox emulator, unless that one is not compiled in,
// or the user explicitly wants the MAME emulator. The MAME one is slightly buggy, leading
// to some wrong sounds, especially noticeable in the title music of Gobliins 2, so we
// really don't want to use it, if we can help it.
void AdLib::createOPL() {
Common::String oplDriver = ConfMan.get("opl_driver");
if (oplDriver.empty() || (oplDriver == "auto") || (OPL::Config::parse(oplDriver) == -1)) {
// User has selected OPL driver auto detection or an invalid OPL driver.
// Set it to our preferred driver (DOSBox), if we can.
if (OPL::Config::parse("db") <= 0) {
warning("The DOSBox AdLib emulator is not compiled in. Please keep in mind that the MAME one is buggy");
} else
oplDriver = "db";
} else if (oplDriver == "mame") {
// User has selected the MAME OPL driver. It is buggy, so warn the user about that.
warning("You have selected the MAME AdLib emulator. It is buggy; AdLib music might be slightly glitchy now");
}
_opl = OPL::Config::create(OPL::Config::parse(oplDriver), OPL::Config::kOpl2);
if (!_opl || !_opl->init()) {
delete _opl;
error("Could not create an AdLib emulator");
}
}
void AdLib::onTimer() {
Common::StackLock slock(_mutex);
// Nothing to do
if (!_playing)
return;
// Check if there's anything to do on this step
// If not, decrease the poll number and move on
if (_toPoll > 0) {
_toPoll--;
return;
}
// Poll until we have to delay until the next poll
while (_toPoll == 0 && _playing) {
// Song ended, break out
if (_ended) {
_toPoll = 0;
break;
}
// Poll more music
_toPoll = pollMusic(_first);
_first = false;
}
// Song ended, loop if requested
if (_ended) {
_toPoll = 0;
// _repCount == 0: No looping (anymore); _repCount < 0: Infinite looping
if (_repCount != 0) {
if (_repCount > 0)
_repCount--;
_first = true;
_ended = false;
reset();
rewind();
} else
_playing = false;
}
}
bool AdLib::isPlaying() const {
return _playing;
}
int32 AdLib::getRepeating() const {
Common::StackLock slock(_mutex);
return _repCount;
}
void AdLib::setRepeating(int32 repCount) {
Common::StackLock slock(_mutex);
_repCount = repCount;
}
void AdLib::startPlay() {
Common::StackLock slock(_mutex);
_playing = true;
_ended = false;
_first = true;
reset();
rewind();
}
void AdLib::stopPlay() {
Common::StackLock slock(_mutex);
end(true);
_playing = false;
}
void AdLib::writeOPL(byte reg, byte val) {
debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val);
_opl->writeReg(reg, val);
}
void AdLib::reset() {
allOff();
initOPL();
}
void AdLib::allOff() {
// NOTE: Explicit casts are necessary, because of 5.16 paragraph 4 of the C++ standard
int numVoices = isPercussionMode() ? (int)kMaxVoiceCount : (int)kMelodyVoiceCount;
for (int i = 0; i < numVoices; i++)
noteOff(i);
}
void AdLib::end(bool killRepeat) {
reset();
_ended = true;
if (killRepeat)
_repCount = 0;
}
void AdLib::initOPL() {
_tremoloDepth = false;
_vibratoDepth = false;
_keySplit = false;
_enableWaveSelect = true;
for (int i = 0; i < kMaxVoiceCount; i++) {
_voiceNote[i] = 0;
_voiceOn [i] = 0;
}
/* NOTE: We used to completely reset the OPL here, via _opl->reset(). However,
* with the OPL timer change in 73e8ac2a, reset() must not be called while
* the callback is still active. With the Gob AdLib rewrite in 03ef6689,
* this reset shouldn't be necessary anymore either, since this function
* here cleans everything properly anyway. If suddenly a certain piece of
* music in a Gob game sounds weird, we need to re-examine that. */
initOperatorVolumes();
resetFreqs();
setPercussionMode(false);
setTremoloDepth(false);
setVibratoDepth(false);
setKeySplit(false);
for(int i = 0; i < kMelodyVoiceCount; i++)
voiceOff(i);
setPitchRange(1);
enableWaveSelect(true);
}
bool AdLib::isPercussionMode() const {
return _percussionMode;
}
void AdLib::setPercussionMode(bool percussion) {
if (percussion) {
voiceOff(kVoiceBaseDrum);
voiceOff(kVoiceSnareDrum);
voiceOff(kVoiceTom);
/* set the frequency for the last 4 percussion voices: */
setFreq(kVoiceTom, kPitchTom, 0);
setFreq(kVoiceSnareDrum, kPitchSnareDrum, 0);
}
_percussionMode = percussion;
_percussionBits = 0;
initOperatorParams();
writeTremoloVibratoDepthPercMode();
}
void AdLib::enableWaveSelect(bool enable) {
_enableWaveSelect = enable;
for (int i = 0; i < kOperatorCount; i++)
writeOPL(0xE0 + kOperatorOffset[i], 0);
writeOPL(0x011, _enableWaveSelect ? 0x20 : 0);
}
void AdLib::setPitchRange(uint8 range) {
_pitchRange = CLIP<uint8>(range, 0, 12);
_pitchRangeStep = _pitchRange * kPitchStepCount;
}
void AdLib::setTremoloDepth(bool tremoloDepth) {
_tremoloDepth = tremoloDepth;
writeTremoloVibratoDepthPercMode();
}
void AdLib::setVibratoDepth(bool vibratoDepth) {
_vibratoDepth = vibratoDepth;
writeTremoloVibratoDepthPercMode();
}
void AdLib::setKeySplit(bool keySplit) {
_keySplit = keySplit;
writeKeySplit();
}
void AdLib::setVoiceTimbre(uint8 voice, const uint16 *params) {
const uint16 *params0 = params;
const uint16 *params1 = params + kParamCount - 1;
const uint16 *waves = params + 2 * (kParamCount - 1);
const int voicePerc = voice - kVoiceBaseDrum;
if (!isPercussionMode() || (voice < kVoiceBaseDrum)) {
if (voice < kMelodyVoiceCount) {
setOperatorParams(kVoiceMelodyOperator[0][voice], params0, waves[0]);
setOperatorParams(kVoiceMelodyOperator[1][voice], params1, waves[1]);
}
} else if (voice == kVoiceBaseDrum) {
setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]);
setOperatorParams(kVoicePercussionOperator[1][voicePerc], params1, waves[1]);
} else {
setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]);
}
}
void AdLib::setVoiceVolume(uint8 voice, uint8 volume) {
int oper;
const int voicePerc = voice - kVoiceBaseDrum;
if (!isPercussionMode() || (voice < kVoiceBaseDrum))
oper = kVoiceMelodyOperator[1][ voice];
else
oper = kVoicePercussionOperator[voice == kVoiceBaseDrum ? 1 : 0][voicePerc];
_operatorVolume[oper] = MIN<uint8>(volume, kMaxVolume);
writeKeyScaleLevelVolume(oper);
}
void AdLib::bendVoicePitch(uint8 voice, uint16 pitchBend) {
if (isPercussionMode() && (voice > kVoiceBaseDrum))
return;
changePitch(voice, MIN<uint16>(pitchBend, kMaxPitch));
setFreq(voice, _voiceNote[voice], _voiceOn[voice]);
}
void AdLib::noteOn(uint8 voice, uint8 note) {
note = MAX<int>(0, note - (kStandardMidC - kOPLMidC));
if (isPercussionMode() && (voice >= kVoiceBaseDrum)) {
if (voice == kVoiceBaseDrum) {
setFreq(kVoiceBaseDrum , note , false);
} else if (voice == kVoiceTom) {
setFreq(kVoiceTom , note , false);
setFreq(kVoiceSnareDrum, note + kPitchTomToSnare, false);
}
_percussionBits |= kPercussionMasks[voice - kVoiceBaseDrum];
writeTremoloVibratoDepthPercMode();
} else
setFreq(voice, note, true);
}
void AdLib::noteOff(uint8 voice) {
if (isPercussionMode() && (voice >= kVoiceBaseDrum)) {
_percussionBits &= ~kPercussionMasks[voice - kVoiceBaseDrum];
writeTremoloVibratoDepthPercMode();
} else
setFreq(voice, _voiceNote[voice], false);
}
void AdLib::writeKeyScaleLevelVolume(uint8 oper) {
uint16 volume = 0;
volume = (63 - (_operatorParams[oper][kParamLevel] & 0x3F)) * _operatorVolume[oper];
volume = 63 - ((2 * volume + kMaxVolume) / (2 * kMaxVolume));
// Adjust carriers for GUI volume slider
if (kOperatorType[oper] == 1) {
volume += kVolumeTable[_volume];
if (volume > 63)
volume = 63;
}
uint8 keyScale = _operatorParams[oper][kParamKeyScaleLevel] << 6;
writeOPL(0x40 + kOperatorOffset[oper], volume | keyScale);
}
void AdLib::writeKeySplit() {
writeOPL(0x08, _keySplit ? 0x40 : 0);
}
void AdLib::writeFeedbackFM(uint8 oper) {
if (kOperatorType[oper] == 1)
return;
uint8 value = 0;
value |= _operatorParams[oper][kParamFeedback] << 1;
value |= _operatorParams[oper][kParamFM] ? 0 : 1;
writeOPL(0xC0 + kOperatorVoice[oper], value);
}
void AdLib::writeAttackDecay(uint8 oper) {
uint8 value = 0;
value |= _operatorParams[oper][kParamAttack] << 4;
value |= _operatorParams[oper][kParamDecay] & 0x0F;
writeOPL(0x60 + kOperatorOffset[oper], value);
}
void AdLib::writeSustainRelease(uint8 oper) {
uint8 value = 0;
value |= _operatorParams[oper][kParamSustain] << 4;
value |= _operatorParams[oper][kParamRelease] & 0x0F;
writeOPL(0x80 + kOperatorOffset[oper], value);
}
void AdLib::writeTremoloVibratoSustainingKeyScaleRateFreqMulti(uint8 oper) {
uint8 value = 0;
value |= _operatorParams[oper][kParamAM] ? 0x80 : 0;
value |= _operatorParams[oper][kParamVib] ? 0x40 : 0;
value |= _operatorParams[oper][kParamSustaining] ? 0x20 : 0;
value |= _operatorParams[oper][kParamKeyScaleRate] ? 0x10 : 0;
value |= _operatorParams[oper][kParamFreqMulti] & 0x0F;
writeOPL(0x20 + kOperatorOffset[oper], value);
}
void AdLib::writeTremoloVibratoDepthPercMode() {
uint8 value = 0;
value |= _tremoloDepth ? 0x80 : 0;
value |= _vibratoDepth ? 0x40 : 0;
value |= isPercussionMode() ? 0x20 : 0;
value |= _percussionBits;
writeOPL(0xBD, value);
}
void AdLib::writeWaveSelect(uint8 oper) {
uint8 wave = 0;
if (_enableWaveSelect)
wave = _operatorParams[oper][kParamWaveSelect] & 0x03;
writeOPL(0xE0 + kOperatorOffset[ oper], wave);
}
void AdLib::writeAllParams(uint8 oper) {
writeTremoloVibratoDepthPercMode();
writeKeySplit();
writeKeyScaleLevelVolume(oper);
writeFeedbackFM(oper);
writeAttackDecay(oper);
writeSustainRelease(oper);
writeTremoloVibratoSustainingKeyScaleRateFreqMulti(oper);
writeWaveSelect(oper);
}
void AdLib::initOperatorParams() {
for (int i = 0; i < kOperatorCount; i++)
setOperatorParams(i, kPianoParams[kOperatorType[i]], kPianoParams[kOperatorType[i]][kParamCount - 1]);
if (isPercussionMode()) {
setOperatorParams(12, kBaseDrumParams [0], kBaseDrumParams [0][kParamCount - 1]);
setOperatorParams(15, kBaseDrumParams [1], kBaseDrumParams [1][kParamCount - 1]);
setOperatorParams(16, kSnareDrumParams , kSnareDrumParams [kParamCount - 1]);
setOperatorParams(14, kTomParams , kTomParams [kParamCount - 1]);
setOperatorParams(17, kCymbalParams , kCymbalParams [kParamCount - 1]);
setOperatorParams(13, kHihatParams , kHihatParams [kParamCount - 1]);
}
}
void AdLib::initOperatorVolumes() {
for(int i = 0; i < kOperatorCount; i++)
_operatorVolume[i] = kMaxVolume;
}
void AdLib::setOperatorParams(uint8 oper, const uint16 *params, uint8 wave) {
byte *operParams = _operatorParams[oper];
for (int i = 0; i < (kParamCount - 1); i++)
operParams[i] = params[i];
operParams[kParamCount - 1] = wave & 0x03;
writeAllParams(oper);
}
void AdLib::voiceOff(uint8 voice) {
writeOPL(0xA0 + voice, 0);
writeOPL(0xB0 + voice, 0);
}
int32 AdLib::calcFreq(int32 deltaDemiToneNum, int32 deltaDemiToneDenom) {
int32 freq = 0;
freq = ((deltaDemiToneDenom * 100) + 6 * deltaDemiToneNum) * 52088;
freq /= deltaDemiToneDenom * 2500;
return (freq * 147456) / 111875;
}
void AdLib::setFreqs(uint16 *freqs, int32 num, int32 denom) {
int32 val = calcFreq(num, denom);
*freqs++ = (4 + val) >> 3;
for (int i = 1; i < kHalfToneCount; i++) {
val = (val * 106) / 100;
*freqs++ = (4 + val) >> 3;
}
}
void AdLib::initFreqs() {
const int numStep = 100 / kPitchStepCount;
for (int i = 0; i < kPitchStepCount; i++)
setFreqs(_freqs[i], i * numStep, 100);
resetFreqs();
}
void AdLib::resetFreqs() {
for (int i = 0; i < kMaxVoiceCount; i++) {
_freqPtr [i] = _freqs[0];
_halfToneOffset[i] = 0;
}
}
void AdLib::changePitch(uint8 voice, uint16 pitchBend) {
int full = 0;
int frac = 0;
int amount = ((pitchBend - kMidPitch) * _pitchRangeStep) / kMidPitch;
if (amount >= 0) {
// Bend up
full = amount / kPitchStepCount;
frac = amount % kPitchStepCount;
} else {
// Bend down
amount = kPitchStepCount - 1 - amount;
full = -(amount / kPitchStepCount);
frac = (amount - kPitchStepCount + 1) % kPitchStepCount;
if (frac)
frac = kPitchStepCount - frac;
}
_halfToneOffset[voice] = full;
_freqPtr [voice] = _freqs[frac];
}
void AdLib::setFreq(uint8 voice, uint16 note, bool on) {
_voiceOn [voice] = on;
_voiceNote[voice] = note;
note = CLIP<int>(note + _halfToneOffset[voice], 0, kNoteCount - 1);
uint16 freq = _freqPtr[voice][note % kHalfToneCount];
uint8 value = 0;
value |= on ? 0x20 : 0;
value |= ((note / kHalfToneCount) << 2) | ((freq >> 8) & 0x03);
writeOPL(0xA0 + voice, freq);
writeOPL(0xB0 + voice, value);
}
void AdLib::setTimerFrequency(int timerFrequency) {
_opl->setCallbackFrequency(timerFrequency);
}
void AdLib::syncVolume() {
Common::StackLock slock(_mutex);
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_volume = (mute ? 0 : ConfMan.getInt("music_volume"));
if (_playing) {
for(int i = 0; i < kOperatorCount; i++)
writeKeyScaleLevelVolume(i);
}
}
} // End of namespace Gob