2019-04-14 21:50:26 +02:00
|
|
|
/* 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.
|
|
|
|
*
|
|
|
|
* LGPL License
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2020-01-26 17:45:51 +01:00
|
|
|
// Basic AdLib Programming:
|
|
|
|
// https://web.archive.org/web/20050322080425/http://www.gamedev.net/reference/articles/article446.asp
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-01-26 17:45:51 +01:00
|
|
|
#include "kyra/sound/drivers/pc_base.h"
|
2019-04-14 21:50:26 +02:00
|
|
|
#include "audio/fmopl.h"
|
2020-01-26 17:45:51 +01:00
|
|
|
#include "common/mutex.h"
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
#define CALLBACKS_PER_SECOND 72
|
|
|
|
|
|
|
|
namespace Kyra {
|
|
|
|
|
2020-01-26 17:45:51 +01:00
|
|
|
class AdLibDriver : public PCSoundDriver {
|
|
|
|
public:
|
|
|
|
AdLibDriver(Audio::Mixer *mixer, int version);
|
2020-02-09 12:05:29 +01:00
|
|
|
~AdLibDriver() override;
|
2020-01-26 17:45:51 +01:00
|
|
|
|
2020-02-09 12:05:29 +01:00
|
|
|
void initDriver() override;
|
|
|
|
void setSoundData(uint8 *data, uint32 size) override;
|
|
|
|
void startSound(int track, int volume) override;
|
|
|
|
bool isChannelPlaying(int channel) const override;
|
|
|
|
void stopAllChannels() override;
|
2020-02-05 15:38:39 +01:00
|
|
|
int getSoundTrigger() const override { return _soundTrigger; }
|
|
|
|
void resetSoundTrigger() override { _soundTrigger = 0; }
|
2020-01-26 17:45:51 +01:00
|
|
|
|
2020-01-26 19:26:53 +01:00
|
|
|
void callback();
|
2020-01-26 17:45:51 +01:00
|
|
|
|
2020-02-09 12:05:29 +01:00
|
|
|
void setSyncJumpMask(uint16 mask) override { _syncJumpMask = mask; }
|
2020-01-26 17:45:51 +01:00
|
|
|
|
2020-02-09 12:05:29 +01:00
|
|
|
void setMusicVolume(uint8 volume) override;
|
|
|
|
void setSfxVolume(uint8 volume) override;
|
2020-01-26 17:45:51 +01:00
|
|
|
|
|
|
|
private:
|
|
|
|
struct Channel {
|
|
|
|
bool lock; // New to ScummVM
|
|
|
|
uint8 opExtraLevel2;
|
|
|
|
const uint8 *dataptr;
|
|
|
|
uint8 duration;
|
|
|
|
uint8 repeatCounter;
|
|
|
|
int8 baseOctave;
|
|
|
|
uint8 priority;
|
|
|
|
uint8 dataptrStackPos;
|
|
|
|
const uint8 *dataptrStack[4];
|
|
|
|
int8 baseNote;
|
2020-12-17 02:32:41 +01:00
|
|
|
uint8 slideTempo;
|
|
|
|
uint8 slideTimer;
|
|
|
|
int16 slideStep;
|
2020-12-18 00:10:43 +01:00
|
|
|
int16 vibratoStep;
|
|
|
|
uint8 vibratoStepRange;
|
|
|
|
uint8 vibratoStepsCountdown;
|
|
|
|
uint8 vibratoNumSteps;
|
|
|
|
uint8 vibratoDelay;
|
|
|
|
uint8 vibratoTempo;
|
|
|
|
uint8 vibratoTimer;
|
|
|
|
uint8 vibratoDelayCountdown;
|
2020-01-26 17:45:51 +01:00
|
|
|
uint8 opExtraLevel1;
|
|
|
|
uint8 spacing2;
|
|
|
|
uint8 baseFreq;
|
|
|
|
uint8 tempo;
|
2020-12-31 02:15:30 +01:00
|
|
|
uint8 timer;
|
2020-01-26 17:45:51 +01:00
|
|
|
uint8 regAx;
|
|
|
|
uint8 regBx;
|
|
|
|
typedef void (AdLibDriver::*Callback)(Channel&);
|
|
|
|
Callback primaryEffect;
|
|
|
|
Callback secondaryEffect;
|
|
|
|
uint8 fractionalSpacing;
|
|
|
|
uint8 opLevel1;
|
|
|
|
uint8 opLevel2;
|
|
|
|
uint8 opExtraLevel3;
|
|
|
|
uint8 twoChan;
|
|
|
|
uint8 unk39;
|
|
|
|
uint8 unk40;
|
|
|
|
uint8 spacing1;
|
|
|
|
uint8 durationRandomness;
|
2020-12-18 02:25:38 +01:00
|
|
|
uint8 secondaryEffectTempo;
|
|
|
|
uint8 secondaryEffectTimer;
|
|
|
|
int8 secondaryEffectSize;
|
|
|
|
int8 secondaryEffectPos;
|
|
|
|
uint8 secondaryEffectRegbase;
|
|
|
|
uint16 secondaryEffectData;
|
2020-01-26 17:45:51 +01:00
|
|
|
uint8 tempoReset;
|
|
|
|
uint8 rawNote;
|
|
|
|
int8 pitchBend;
|
|
|
|
uint8 volumeModifier;
|
|
|
|
};
|
|
|
|
|
2020-12-17 02:32:41 +01:00
|
|
|
void primaryEffectSlide(Channel &channel);
|
2020-12-18 00:10:43 +01:00
|
|
|
void primaryEffectVibrato(Channel &channel);
|
2020-01-26 17:45:51 +01:00
|
|
|
void secondaryEffect1(Channel &channel);
|
|
|
|
|
|
|
|
void resetAdLibState();
|
|
|
|
void writeOPL(byte reg, byte val);
|
|
|
|
void initChannel(Channel &channel);
|
|
|
|
void noteOff(Channel &channel);
|
2020-12-18 01:24:49 +01:00
|
|
|
void initAdlibChannel(uint8 num);
|
2020-01-26 17:45:51 +01:00
|
|
|
|
|
|
|
uint16 getRandomNr();
|
|
|
|
void setupDuration(uint8 duration, Channel &channel);
|
|
|
|
|
|
|
|
void setupNote(uint8 rawNote, Channel &channel, bool flag = false);
|
|
|
|
void setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel);
|
|
|
|
void noteOn(Channel &channel);
|
|
|
|
|
|
|
|
void adjustVolume(Channel &channel);
|
|
|
|
|
|
|
|
uint8 calculateOpLevel1(Channel &channel);
|
|
|
|
uint8 calculateOpLevel2(Channel &channel);
|
|
|
|
|
2020-12-31 02:15:30 +01:00
|
|
|
static uint16 checkValue(int16 val) { return CLIP<int16>(val, 0, 0x3F); }
|
|
|
|
|
|
|
|
// The driver uses timer/tempo pairs in several places. On every
|
|
|
|
// callback, the tempo is added to the timer. This will frequently
|
|
|
|
// cause the timer to "wrap around", which is the signal to go ahead
|
|
|
|
// and do more stuff.
|
|
|
|
static bool advance(uint8 &timer, uint8 tempo) {
|
|
|
|
uint8 old = timer;
|
|
|
|
timer += tempo;
|
|
|
|
return timer < old;
|
|
|
|
}
|
2020-01-26 17:45:51 +01:00
|
|
|
|
2021-01-02 01:31:17 +01:00
|
|
|
const uint8 *checkDataOffset(const uint8 *ptr, long n) {
|
|
|
|
if (ptr) {
|
|
|
|
long offset = ptr - _soundData;
|
|
|
|
if (n >= -offset && n <= (long)_soundDataSize - offset)
|
|
|
|
return ptr + n;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2020-12-31 02:29:31 +01:00
|
|
|
// The sound data has two lookup tables:
|
2020-01-26 17:45:51 +01:00
|
|
|
// * One for programs, starting at offset 0.
|
2020-12-31 02:29:31 +01:00
|
|
|
// * One for instruments, starting at offset 300, 500, or 1000.
|
2020-01-26 17:45:51 +01:00
|
|
|
const uint8 *getInstrument(int instrumentId) {
|
|
|
|
return getProgram(_numPrograms + instrumentId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void setupPrograms();
|
|
|
|
void executePrograms();
|
|
|
|
|
|
|
|
struct ParserOpcode {
|
2021-01-03 23:33:56 +01:00
|
|
|
typedef int (AdLibDriver::*POpcode)(Channel &channel, const uint8 *values);
|
2020-01-26 17:45:51 +01:00
|
|
|
POpcode function;
|
|
|
|
const char *name;
|
2020-12-15 21:45:11 +01:00
|
|
|
int values;
|
2020-01-26 17:45:51 +01:00
|
|
|
};
|
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
static const ParserOpcode _parserOpcodeTable[];
|
|
|
|
static const int _parserOpcodeTableSize;
|
2020-01-26 17:45:51 +01:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int update_setRepeat(Channel &channel, const uint8 *values);
|
|
|
|
int update_checkRepeat(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupProgram(Channel &channel, const uint8 *values);
|
|
|
|
int update_setNoteSpacing(Channel &channel, const uint8 *values);
|
|
|
|
int update_jump(Channel &channel, const uint8 *values);
|
|
|
|
int update_jumpToSubroutine(Channel &channel, const uint8 *values);
|
|
|
|
int update_returnFromSubroutine(Channel &channel, const uint8 *values);
|
|
|
|
int update_setBaseOctave(Channel &channel, const uint8 *values);
|
|
|
|
int update_stopChannel(Channel &channel, const uint8 *values);
|
|
|
|
int update_playRest(Channel &channel, const uint8 *values);
|
|
|
|
int update_writeAdLib(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupNoteAndDuration(Channel &channel, const uint8 *values);
|
|
|
|
int update_setBaseNote(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupSecondaryEffect1(Channel &channel, const uint8 *values);
|
|
|
|
int update_stopOtherChannel(Channel &channel, const uint8 *values);
|
|
|
|
int update_waitForEndOfProgram(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupInstrument(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupPrimaryEffectSlide(Channel &channel, const uint8 *values);
|
|
|
|
int update_removePrimaryEffectSlide(Channel &channel, const uint8 *values);
|
|
|
|
int update_setBaseFreq(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupPrimaryEffectVibrato(Channel &channel, const uint8 *values);
|
|
|
|
int update_setPriority(Channel &channel, const uint8 *values);
|
|
|
|
int update_setBeat(Channel &channel, const uint8 *values);
|
|
|
|
int update_waitForNextBeat(Channel &channel, const uint8 *values);
|
|
|
|
int update_setExtraLevel1(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupDuration(Channel &channel, const uint8 *values);
|
|
|
|
int update_playNote(Channel &channel, const uint8 *values);
|
|
|
|
int update_setFractionalNoteSpacing(Channel &channel, const uint8 *values);
|
|
|
|
int update_setTempo(Channel &channel, const uint8 *values);
|
|
|
|
int update_removeSecondaryEffect1(Channel &channel, const uint8 *values);
|
|
|
|
int update_setChannelTempo(Channel &channel, const uint8 *values);
|
|
|
|
int update_setExtraLevel3(Channel &channel, const uint8 *values);
|
|
|
|
int update_setExtraLevel2(Channel &channel, const uint8 *values);
|
|
|
|
int update_changeExtraLevel2(Channel &channel, const uint8 *values);
|
|
|
|
int update_setAMDepth(Channel &channel, const uint8 *values);
|
|
|
|
int update_setVibratoDepth(Channel &channel, const uint8 *values);
|
|
|
|
int update_changeExtraLevel1(Channel &channel, const uint8 *values);
|
|
|
|
int update_clearChannel(Channel &channel, const uint8 *values);
|
|
|
|
int update_changeNoteRandomly(Channel &channel, const uint8 *values);
|
|
|
|
int update_removePrimaryEffectVibrato(Channel &channel, const uint8 *values);
|
|
|
|
int update_pitchBend(Channel &channel, const uint8 *values);
|
|
|
|
int update_resetToGlobalTempo(Channel &channel, const uint8 *values);
|
|
|
|
int update_nop(Channel &channel, const uint8 *values);
|
|
|
|
int update_setDurationRandomness(Channel &channel, const uint8 *values);
|
|
|
|
int update_changeChannelTempo(Channel &channel, const uint8 *values);
|
|
|
|
int updateCallback46(Channel &channel, const uint8 *values);
|
|
|
|
int update_setupRhythmSection(Channel &channel, const uint8 *values);
|
|
|
|
int update_playRhythmSection(Channel &channel, const uint8 *values);
|
|
|
|
int update_removeRhythmSection(Channel &channel, const uint8 *values);
|
|
|
|
int update_setRhythmLevel2(Channel &channel, const uint8 *values);
|
|
|
|
int update_changeRhythmLevel1(Channel &channel, const uint8 *values);
|
|
|
|
int update_setRhythmLevel1(Channel &channel, const uint8 *values);
|
|
|
|
int update_setSoundTrigger(Channel &channel, const uint8 *values);
|
|
|
|
int update_setTempoReset(Channel &channel, const uint8 *values);
|
|
|
|
int updateCallback56(Channel &channel, const uint8 *values);
|
2020-01-26 17:45:51 +01:00
|
|
|
|
2021-01-27 18:45:31 +01:00
|
|
|
private:
|
2020-01-26 17:45:51 +01:00
|
|
|
int _curChannel;
|
|
|
|
uint8 _soundTrigger;
|
|
|
|
|
|
|
|
uint16 _rnd;
|
|
|
|
|
2020-12-18 19:08:50 +01:00
|
|
|
uint8 _beatDivider;
|
|
|
|
uint8 _beatDivCnt;
|
2020-01-26 17:45:51 +01:00
|
|
|
uint8 _callbackTimer;
|
2020-12-18 19:08:50 +01:00
|
|
|
uint8 _beatCounter;
|
|
|
|
uint8 _beatWaiting;
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
uint8 _opLevelBD;
|
|
|
|
uint8 _opLevelHH;
|
|
|
|
uint8 _opLevelSD;
|
|
|
|
uint8 _opLevelTT;
|
|
|
|
uint8 _opLevelCY;
|
|
|
|
uint8 _opExtraLevel1HH;
|
|
|
|
uint8 _opExtraLevel2HH;
|
|
|
|
uint8 _opExtraLevel1CY;
|
|
|
|
uint8 _opExtraLevel2CY;
|
|
|
|
uint8 _opExtraLevel2TT;
|
|
|
|
uint8 _opExtraLevel1TT;
|
|
|
|
uint8 _opExtraLevel1SD;
|
|
|
|
uint8 _opExtraLevel2SD;
|
|
|
|
uint8 _opExtraLevel1BD;
|
|
|
|
uint8 _opExtraLevel2BD;
|
2020-01-26 17:45:51 +01:00
|
|
|
|
|
|
|
OPL::OPL *_adlib;
|
|
|
|
|
|
|
|
struct QueueEntry {
|
|
|
|
QueueEntry() : data(0), id(0), volume(0) {}
|
|
|
|
QueueEntry(uint8 *ptr, uint8 track, uint8 vol) : data(ptr), id(track), volume(vol) {}
|
|
|
|
uint8 *data;
|
|
|
|
uint8 id;
|
|
|
|
uint8 volume;
|
|
|
|
};
|
|
|
|
|
|
|
|
QueueEntry _programQueue[16];
|
|
|
|
int _programStartTimeout;
|
|
|
|
int _programQueueStart, _programQueueEnd;
|
|
|
|
bool _retrySounds;
|
|
|
|
|
|
|
|
void adjustSfxData(uint8 *data, int volume);
|
|
|
|
uint8 *_sfxPointer;
|
|
|
|
int _sfxPriority;
|
|
|
|
int _sfxVelocity;
|
|
|
|
|
|
|
|
Channel _channels[10];
|
|
|
|
|
|
|
|
uint8 _vibratoAndAMDepthBits;
|
|
|
|
uint8 _rhythmSectionBits;
|
|
|
|
|
|
|
|
uint8 _curRegOffset;
|
|
|
|
uint8 _tempo;
|
|
|
|
|
|
|
|
const uint8 *_tablePtr1;
|
|
|
|
const uint8 *_tablePtr2;
|
|
|
|
|
|
|
|
static const uint8 _regOffset[];
|
|
|
|
static const uint16 _freqTable[];
|
|
|
|
static const uint8 *const _unkTable2[];
|
2020-12-16 02:21:23 +01:00
|
|
|
static const int _unkTable2Size;
|
2020-01-26 17:45:51 +01:00
|
|
|
static const uint8 _unkTable2_1[];
|
|
|
|
static const uint8 _unkTable2_2[];
|
|
|
|
static const uint8 _unkTable2_3[];
|
|
|
|
static const uint8 _pitchBendTables[][32];
|
|
|
|
|
|
|
|
uint16 _syncJumpMask;
|
|
|
|
|
|
|
|
Common::Mutex _mutex;
|
|
|
|
Audio::Mixer *_mixer;
|
|
|
|
|
|
|
|
uint8 _musicVolume, _sfxVolume;
|
|
|
|
|
|
|
|
int _numPrograms;
|
|
|
|
int _version;
|
|
|
|
};
|
|
|
|
|
|
|
|
AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) : PCSoundDriver() {
|
2019-04-14 21:50:26 +02:00
|
|
|
_version = version;
|
|
|
|
_numPrograms = (_version == 1) ? 150 : ((_version == 4) ? 500 : 250);
|
|
|
|
|
|
|
|
_mixer = mixer;
|
|
|
|
|
|
|
|
_adlib = OPL::Config::create();
|
|
|
|
if (!_adlib || !_adlib->init())
|
|
|
|
error("Failed to create OPL");
|
|
|
|
|
|
|
|
memset(_channels, 0, sizeof(_channels));
|
|
|
|
|
|
|
|
_vibratoAndAMDepthBits = _curRegOffset = 0;
|
|
|
|
|
|
|
|
_curChannel = _rhythmSectionBits = 0;
|
|
|
|
_rnd = 0x1234;
|
|
|
|
|
|
|
|
_tempo = 0;
|
|
|
|
_soundTrigger = 0;
|
|
|
|
_programStartTimeout = 0;
|
|
|
|
|
|
|
|
_callbackTimer = 0xFF;
|
2020-12-18 19:08:50 +01:00
|
|
|
_beatDivider = _beatDivCnt = _beatCounter = _beatWaiting = 0;
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
_opLevelBD = _opLevelHH = _opLevelSD = _opLevelTT = _opLevelCY = 0;
|
|
|
|
_opExtraLevel1HH = _opExtraLevel2HH =
|
|
|
|
_opExtraLevel1CY = _opExtraLevel2CY =
|
|
|
|
_opExtraLevel2TT = _opExtraLevel1TT =
|
|
|
|
_opExtraLevel1SD = _opExtraLevel2SD =
|
|
|
|
_opExtraLevel1BD = _opExtraLevel2BD = 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
_tablePtr1 = _tablePtr2 = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
_syncJumpMask = 0;
|
|
|
|
|
|
|
|
_musicVolume = 0;
|
|
|
|
_sfxVolume = 0;
|
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
_sfxPointer = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
_programQueueStart = _programQueueEnd = 0;
|
|
|
|
_retrySounds = false;
|
|
|
|
|
|
|
|
_adlib->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::callback), CALLBACKS_PER_SECOND);
|
|
|
|
}
|
|
|
|
|
|
|
|
AdLibDriver::~AdLibDriver() {
|
|
|
|
delete _adlib;
|
2020-12-16 02:21:23 +01:00
|
|
|
_adlib = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::setMusicVolume(uint8 volume) {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
_musicVolume = volume;
|
|
|
|
|
|
|
|
for (uint i = 0; i < 6; ++i) {
|
|
|
|
Channel &chan = _channels[i];
|
|
|
|
chan.volumeModifier = volume;
|
|
|
|
|
|
|
|
const uint8 regOffset = _regOffset[i];
|
|
|
|
|
|
|
|
// Level Key Scaling / Total Level
|
|
|
|
writeOPL(0x40 + regOffset, calculateOpLevel1(chan));
|
|
|
|
writeOPL(0x43 + regOffset, calculateOpLevel2(chan));
|
|
|
|
}
|
|
|
|
|
|
|
|
// For now we use the music volume for both sfx and music in Kyra1 and EoB
|
|
|
|
if (_version < 4) {
|
|
|
|
_sfxVolume = volume;
|
|
|
|
|
|
|
|
for (uint i = 6; i < 9; ++i) {
|
|
|
|
Channel &chan = _channels[i];
|
|
|
|
chan.volumeModifier = volume;
|
|
|
|
|
|
|
|
const uint8 regOffset = _regOffset[i];
|
|
|
|
|
|
|
|
// Level Key Scaling / Total Level
|
|
|
|
writeOPL(0x40 + regOffset, calculateOpLevel1(chan));
|
|
|
|
writeOPL(0x43 + regOffset, calculateOpLevel2(chan));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::setSfxVolume(uint8 volume) {
|
|
|
|
// We only support sfx volume in version 4 games.
|
|
|
|
if (_version < 4)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
_sfxVolume = volume;
|
|
|
|
|
|
|
|
for (uint i = 6; i < 9; ++i) {
|
|
|
|
Channel &chan = _channels[i];
|
|
|
|
chan.volumeModifier = volume;
|
|
|
|
|
|
|
|
const uint8 regOffset = _regOffset[i];
|
|
|
|
|
|
|
|
// Level Key Scaling / Total Level
|
|
|
|
writeOPL(0x40 + regOffset, calculateOpLevel1(chan));
|
|
|
|
writeOPL(0x43 + regOffset, calculateOpLevel2(chan));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::initDriver() {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
resetAdLibState();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::setSoundData(uint8 *data, uint32 size) {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
// Drop all tracks that are still queued. These would point to the old
|
|
|
|
// sound data.
|
|
|
|
_programQueueStart = _programQueueEnd = 0;
|
2021-05-01 19:57:45 +02:00
|
|
|
for (int i = 0; i < ARRAYSIZE(_programQueue); ++i)
|
|
|
|
_programQueue[i] = QueueEntry();
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
_sfxPointer = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
_soundData = data;
|
|
|
|
_soundDataSize = size;
|
|
|
|
}
|
|
|
|
|
2020-01-26 19:26:53 +01:00
|
|
|
void AdLibDriver::startSound(int track, int volume) {
|
2019-04-14 21:50:26 +02:00
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
uint8 *trackData = getProgram(track);
|
|
|
|
if (!trackData)
|
|
|
|
return;
|
|
|
|
|
2021-05-01 19:59:49 +02:00
|
|
|
// We used to drop the new sound here, but that isn't the behavior of the original code.
|
|
|
|
// It would cause more issues than do any good. Now, we just have a debug message and
|
|
|
|
// then drop the oldest sound, like the original driver...
|
|
|
|
if (_programQueueEnd == _programQueueStart && _programQueue[_programQueueEnd].data != 0)
|
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver: Program queue full, dropping track %d", _programQueue[_programQueueEnd].id);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
_programQueue[_programQueueEnd] = QueueEntry(trackData, track, volume);
|
2020-12-15 21:45:11 +01:00
|
|
|
++_programQueueEnd &= 15;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool AdLibDriver::isChannelPlaying(int channel) const {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
assert(channel >= 0 && channel <= 9);
|
|
|
|
return (_channels[channel].dataptr != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::stopAllChannels() {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
|
|
|
|
for (int channel = 0; channel <= 9; ++channel) {
|
|
|
|
_curChannel = channel;
|
|
|
|
|
|
|
|
Channel &chan = _channels[_curChannel];
|
|
|
|
chan.priority = 0;
|
|
|
|
chan.dataptr = 0;
|
|
|
|
|
|
|
|
if (channel != 9)
|
|
|
|
noteOff(chan);
|
|
|
|
}
|
|
|
|
_retrySounds = false;
|
2020-12-15 21:45:11 +01:00
|
|
|
|
|
|
|
_programQueueStart = _programQueueEnd = 0;
|
|
|
|
_programQueue[0] = QueueEntry();
|
|
|
|
_programStartTimeout = 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// timer callback
|
2020-12-18 19:08:50 +01:00
|
|
|
//
|
|
|
|
// Starts and executes programs and maintains a global beat that channels
|
|
|
|
// can synchronize on.
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
void AdLibDriver::callback() {
|
|
|
|
Common::StackLock lock(_mutex);
|
|
|
|
if (_programStartTimeout)
|
|
|
|
--_programStartTimeout;
|
|
|
|
else
|
|
|
|
setupPrograms();
|
|
|
|
executePrograms();
|
|
|
|
|
2020-12-31 02:15:30 +01:00
|
|
|
if (advance(_callbackTimer, _tempo)) {
|
2020-12-18 19:08:50 +01:00
|
|
|
if (!(--_beatDivCnt)) {
|
|
|
|
_beatDivCnt = _beatDivider;
|
|
|
|
++_beatCounter;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::setupPrograms() {
|
2020-12-15 21:45:11 +01:00
|
|
|
QueueEntry &entry = _programQueue[_programQueueStart];
|
|
|
|
uint8 *ptr = entry.data;
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
// If there is no program queued, we skip this.
|
2020-12-15 21:45:11 +01:00
|
|
|
if (_programQueueStart == _programQueueEnd && !ptr)
|
2019-04-14 21:50:26 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
// The AdLib driver (in its old versions used for EOB) is not suitable for modern (fast) CPUs.
|
|
|
|
// The stop sound track (track 0 which has a priority of 50) will often still be busy when the
|
|
|
|
// next sound (with a lower priority) starts which will cause that sound to be skipped. We simply
|
|
|
|
// restart incoming sounds during stop sound execution.
|
2020-12-15 21:45:11 +01:00
|
|
|
// UPDATE: This still applies after introduction of the _programQueue.
|
2020-01-03 01:58:29 +01:00
|
|
|
// UPDATE: This can also happen with the HOF main menu, so I commented out the version < 3 limitation.
|
2019-04-14 21:50:26 +02:00
|
|
|
QueueEntry retrySound;
|
2020-12-15 21:45:11 +01:00
|
|
|
if (/*_version < 3 &&*/ entry.id == 0)
|
2019-04-14 21:50:26 +02:00
|
|
|
_retrySounds = true;
|
|
|
|
else if (_retrySounds)
|
2020-12-15 21:45:11 +01:00
|
|
|
retrySound = entry;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Clear the queue entry
|
2020-12-16 02:21:23 +01:00
|
|
|
entry.data = nullptr;
|
2020-12-15 21:45:11 +01:00
|
|
|
++_programQueueStart &= 15;
|
|
|
|
|
|
|
|
// Safety check: 2 bytes (channel, priority) are required for each
|
|
|
|
// program, plus 2 more bytes (opcode, _sfxVelocity) for sound effects.
|
|
|
|
// More data is needed, but executePrograms() checks for that.
|
|
|
|
// Also ignore request for invalid channel number.
|
2021-01-02 01:31:17 +01:00
|
|
|
if (!checkDataOffset(ptr, 2))
|
2020-12-15 21:45:11 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
const int chan = *ptr;
|
2021-01-02 01:31:17 +01:00
|
|
|
if (chan > 9 || (chan < 9 && !checkDataOffset(ptr, 4)))
|
2020-12-15 21:45:11 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
Channel &channel = _channels[chan];
|
|
|
|
|
|
|
|
// Adjust data in case we hit a sound effect.
|
|
|
|
adjustSfxData(ptr++, entry.volume);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
const int priority = *ptr++;
|
|
|
|
|
|
|
|
// Only start this sound if its priority is higher than the one
|
|
|
|
// already playing.
|
|
|
|
|
|
|
|
if (priority >= channel.priority) {
|
|
|
|
initChannel(channel);
|
|
|
|
channel.priority = priority;
|
|
|
|
channel.dataptr = ptr;
|
|
|
|
channel.tempo = 0xFF;
|
2020-12-31 02:15:30 +01:00
|
|
|
channel.timer = 0xFF;
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.duration = 1;
|
|
|
|
|
|
|
|
if (chan <= 5)
|
|
|
|
channel.volumeModifier = _musicVolume;
|
|
|
|
else
|
|
|
|
channel.volumeModifier = _sfxVolume;
|
|
|
|
|
2020-12-18 01:24:49 +01:00
|
|
|
initAdlibChannel(chan);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// We need to wait two callback calls till we can start another track.
|
|
|
|
// This is (probably) required to assure that the sfx are started with
|
|
|
|
// the correct priority and velocity.
|
|
|
|
_programStartTimeout = 2;
|
|
|
|
|
|
|
|
retrySound = QueueEntry();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (retrySound.data) {
|
|
|
|
debugC(9, kDebugLevelSound, "AdLibDriver::setupPrograms(): WORKAROUND - Restarting skipped sound %d)", retrySound.id);
|
2020-01-26 19:26:53 +01:00
|
|
|
startSound(retrySound.id, retrySound.volume);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::adjustSfxData(uint8 *ptr, int volume) {
|
|
|
|
// Check whether we need to reset the data of an old sfx which has been
|
|
|
|
// started.
|
|
|
|
if (_sfxPointer) {
|
|
|
|
_sfxPointer[1] = _sfxPriority;
|
|
|
|
_sfxPointer[3] = _sfxVelocity;
|
2020-12-16 02:21:23 +01:00
|
|
|
_sfxPointer = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only music tracks are started on channel 9, thus we need to make sure
|
|
|
|
// we do not have a music track here.
|
|
|
|
if (*ptr == 9)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Store the pointer so we can reset the data when a new program is started.
|
|
|
|
_sfxPointer = ptr;
|
|
|
|
|
|
|
|
// Store the old values.
|
|
|
|
_sfxPriority = ptr[1];
|
|
|
|
_sfxVelocity = ptr[3];
|
|
|
|
|
|
|
|
// Adjust the values.
|
|
|
|
if (volume != 0xFF) {
|
|
|
|
if (_version >= 3) {
|
|
|
|
int newVal = ((((ptr[3]) + 63) * volume) >> 8) & 0xFF;
|
|
|
|
ptr[3] = -newVal + 63;
|
|
|
|
ptr[1] = ((ptr[1] * volume) >> 8) & 0xFF;
|
|
|
|
} else {
|
|
|
|
int newVal = ((_sfxVelocity << 2) ^ 0xFF) * volume;
|
|
|
|
ptr[3] = (newVal >> 10) ^ 0x3F;
|
|
|
|
ptr[1] = newVal >> 11;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// A few words on opcode parsing and timing:
|
|
|
|
//
|
2020-12-31 02:15:30 +01:00
|
|
|
// First of all, we simulate a timer callback 72 times per second. Each timeout
|
2019-04-14 21:50:26 +02:00
|
|
|
// we update each channel that has something to play.
|
|
|
|
//
|
2020-12-31 02:15:30 +01:00
|
|
|
// Each channel has its own individual tempo and timer. The timer is updated,
|
|
|
|
// and when it wraps around, we go ahead and do more stuff with that channel.
|
|
|
|
// Otherwise we skip straiht to the effect callbacks.
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
2020-12-31 02:15:30 +01:00
|
|
|
// Each channel also has a duration, indicating how much time is left on its
|
|
|
|
// current task. This duration is decreased by one. As long as it still has
|
2019-04-14 21:50:26 +02:00
|
|
|
// not reached zero, the only thing that can happen is that the note is turned
|
|
|
|
// off depending on manual or automatic note spacing. Once the duration reaches
|
|
|
|
// zero, a new set of musical opcodes are executed.
|
|
|
|
//
|
2021-01-03 23:33:56 +01:00
|
|
|
// An opcode is one byte, followed by a variable number of parameters.
|
2019-04-14 21:50:26 +02:00
|
|
|
// If the most significant bit of the opcode is 1, it's a function; call it.
|
2021-01-03 23:33:56 +01:00
|
|
|
// An opcode function can change control flow by updating the channel's data
|
|
|
|
// pointer (which is set to the next opcode before the call). The function's
|
|
|
|
// return value is either 0 (continue), 1 (stop) or 2 (stop, and do not run
|
|
|
|
// the effects callbacks).
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
|
|
|
// If the most significant bit of the opcode is 0, it's a note, and the first
|
|
|
|
// parameter is its duration. (There are cases where the duration is modified
|
|
|
|
// but that's an exception.) The note opcode is assumed to return 1, and is the
|
|
|
|
// last opcode unless its duration is zero.
|
|
|
|
//
|
|
|
|
// Finally, most of the times that the callback is called, it will invoke the
|
|
|
|
// effects callbacks. The final opcode in a set can prevent this, if it's a
|
|
|
|
// function and it returns anything other than 1.
|
|
|
|
|
|
|
|
void AdLibDriver::executePrograms() {
|
|
|
|
// Each channel runs its own program. There are ten channels: One for
|
|
|
|
// each AdLib channel (0-8), plus one "control channel" (9) which is
|
|
|
|
// the one that tells the other channels what to do.
|
|
|
|
|
|
|
|
if (_syncJumpMask) {
|
2020-12-31 18:29:39 +01:00
|
|
|
// This is where we ensure that channels that are made to jump
|
|
|
|
// "in sync" do so.
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
for (_curChannel = 9; _curChannel >= 0; --_curChannel) {
|
2020-12-31 18:29:39 +01:00
|
|
|
if ((_syncJumpMask & (1 << _curChannel)) && _channels[_curChannel].dataptr && !_channels[_curChannel].lock)
|
|
|
|
break; // don't unlock
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2020-12-31 18:29:39 +01:00
|
|
|
if (_curChannel < 0) {
|
|
|
|
// force unlock
|
2019-04-14 21:50:26 +02:00
|
|
|
for (_curChannel = 9; _curChannel >= 0; --_curChannel)
|
|
|
|
if (_syncJumpMask & (1 << _curChannel))
|
|
|
|
_channels[_curChannel].lock = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (_curChannel = 9; _curChannel >= 0; --_curChannel) {
|
2020-12-15 21:45:11 +01:00
|
|
|
Channel &channel = _channels[_curChannel];
|
2021-01-03 23:33:56 +01:00
|
|
|
const uint8 *&dataptr = channel.dataptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (!dataptr)
|
2019-04-14 21:50:26 +02:00
|
|
|
continue;
|
|
|
|
|
2020-12-15 21:45:11 +01:00
|
|
|
if (channel.lock && (_syncJumpMask & (1 << _curChannel)))
|
2019-04-14 21:50:26 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (_curChannel == 9)
|
|
|
|
_curRegOffset = 0;
|
|
|
|
else
|
|
|
|
_curRegOffset = _regOffset[_curChannel];
|
|
|
|
|
|
|
|
if (channel.tempoReset)
|
|
|
|
channel.tempo = _tempo;
|
|
|
|
|
2020-12-31 18:29:39 +01:00
|
|
|
int result = 1;
|
2020-12-31 02:15:30 +01:00
|
|
|
if (advance(channel.timer, channel.tempo)) {
|
2019-04-14 21:50:26 +02:00
|
|
|
if (--channel.duration) {
|
|
|
|
if (channel.duration == channel.spacing2)
|
|
|
|
noteOff(channel);
|
|
|
|
if (channel.duration == channel.spacing1 && _curChannel != 9)
|
|
|
|
noteOff(channel);
|
|
|
|
} else {
|
2020-12-31 18:29:39 +01:00
|
|
|
// Process some opcodes.
|
|
|
|
result = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
while (result == 0 && dataptr) {
|
2020-12-31 18:29:39 +01:00
|
|
|
uint8 opcode = 0xFF;
|
|
|
|
// Safety check to avoid illegal access.
|
|
|
|
// Stop channel if not enough data.
|
2021-01-02 01:31:17 +01:00
|
|
|
if (checkDataOffset(dataptr, 1))
|
2020-12-31 18:29:39 +01:00
|
|
|
opcode = *dataptr++;
|
|
|
|
|
|
|
|
if (opcode & 0x80) {
|
|
|
|
opcode = CLIP(opcode & 0x7F, 0, _parserOpcodeTableSize - 1);
|
|
|
|
const ParserOpcode &op = _parserOpcodeTable[opcode];
|
2021-01-03 23:33:56 +01:00
|
|
|
|
|
|
|
// Safety check for end of data.
|
|
|
|
if (!checkDataOffset(dataptr, op.values)) {
|
|
|
|
result = update_stopChannel(channel, dataptr);
|
|
|
|
break;
|
|
|
|
}
|
2020-12-31 18:29:39 +01:00
|
|
|
|
|
|
|
debugC(9, kDebugLevelSound, "Calling opcode '%s' (%d) (channel: %d)", op.name, opcode, _curChannel);
|
2021-01-03 23:33:56 +01:00
|
|
|
|
|
|
|
dataptr += op.values;
|
|
|
|
result = (this->*(op.function))(channel, dataptr - op.values);
|
2020-12-31 18:29:39 +01:00
|
|
|
} else {
|
2021-01-03 23:33:56 +01:00
|
|
|
// Safety check for end of data.
|
|
|
|
if (!checkDataOffset(dataptr, 1)) {
|
|
|
|
result = update_stopChannel(channel, dataptr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8 duration = *dataptr++;
|
|
|
|
debugC(9, kDebugLevelSound, "Note on opcode 0x%02X (duration: %d) (channel: %d)", opcode, duration, _curChannel);
|
|
|
|
|
2020-12-31 18:29:39 +01:00
|
|
|
setupNote(opcode, channel);
|
|
|
|
noteOn(channel);
|
2021-01-03 23:33:56 +01:00
|
|
|
setupDuration(duration, channel);
|
2020-12-31 18:29:39 +01:00
|
|
|
// We need to make sure we are always running the
|
|
|
|
// effects after this. Otherwise some sounds are
|
|
|
|
// wrong. Like the sfx when bumping into a wall in
|
|
|
|
// LoL.
|
2021-01-03 23:33:56 +01:00
|
|
|
result = duration != 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result == 1) {
|
|
|
|
if (channel.primaryEffect)
|
|
|
|
(this->*(channel.primaryEffect))(channel);
|
|
|
|
if (channel.secondaryEffect)
|
|
|
|
(this->*(channel.secondaryEffect))(channel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
void AdLibDriver::resetAdLibState() {
|
|
|
|
debugC(9, kDebugLevelSound, "resetAdLibState()");
|
|
|
|
_rnd = 0x1234;
|
|
|
|
|
|
|
|
// Authorize the control of the waveforms
|
|
|
|
writeOPL(0x01, 0x20);
|
|
|
|
|
|
|
|
// Select FM music mode
|
|
|
|
writeOPL(0x08, 0x00);
|
|
|
|
|
|
|
|
// I would guess the main purpose of this is to turn off the rhythm,
|
|
|
|
// thus allowing us to use 9 melodic voices instead of 6.
|
|
|
|
writeOPL(0xBD, 0x00);
|
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
initChannel(_channels[9]);
|
|
|
|
for (int loop = 8; loop >= 0; loop--) {
|
|
|
|
// Silence the channel
|
|
|
|
writeOPL(0x40 + _regOffset[loop], 0x3F);
|
|
|
|
writeOPL(0x43 + _regOffset[loop], 0x3F);
|
2019-04-14 21:50:26 +02:00
|
|
|
initChannel(_channels[loop]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::writeOPL(byte reg, byte val) {
|
|
|
|
_adlib->writeReg(reg, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::initChannel(Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "initChannel(%lu)", (long)(&channel - _channels));
|
2021-01-02 01:59:35 +01:00
|
|
|
uint8 backupEL2 = channel.opExtraLevel2;
|
|
|
|
memset(&channel, 0, sizeof(Channel));
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-02 01:59:35 +01:00
|
|
|
channel.opExtraLevel2 = backupEL2;
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.tempo = 0xFF;
|
|
|
|
channel.priority = 0;
|
2020-12-16 02:21:23 +01:00
|
|
|
// normally here are nullfuncs but we set nullptr for now
|
|
|
|
channel.primaryEffect = nullptr;
|
|
|
|
channel.secondaryEffect = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.spacing1 = 1;
|
|
|
|
channel.lock = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::noteOff(Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "noteOff(%lu)", (long)(&channel - _channels));
|
|
|
|
|
|
|
|
// The control channel has no corresponding AdLib channel
|
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// When the rhythm section is enabled, channels 6, 7 and 8 are special.
|
|
|
|
|
|
|
|
if (_rhythmSectionBits && _curChannel >= 6)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// This means the "Key On" bit will always be 0
|
|
|
|
channel.regBx &= 0xDF;
|
|
|
|
|
|
|
|
// Octave / F-Number / Key-On
|
|
|
|
writeOPL(0xB0 + _curChannel, channel.regBx);
|
|
|
|
}
|
|
|
|
|
2020-12-18 01:24:49 +01:00
|
|
|
void AdLibDriver::initAdlibChannel(uint8 chan) {
|
|
|
|
debugC(9, kDebugLevelSound, "initAdlibChannel(%d)", chan);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// The control channel has no corresponding AdLib channel
|
|
|
|
|
|
|
|
if (chan >= 9)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// I believe this has to do with channels 6, 7, and 8 being special
|
|
|
|
// when AdLib's rhythm section is enabled.
|
|
|
|
|
|
|
|
if (_rhythmSectionBits && chan >= 6)
|
|
|
|
return;
|
|
|
|
|
|
|
|
uint8 offset = _regOffset[chan];
|
|
|
|
|
|
|
|
// The channel is cleared: First the attack/delay rate, then the
|
|
|
|
// sustain level/release rate, and finally the note is turned off.
|
|
|
|
|
|
|
|
writeOPL(0x60 + offset, 0xFF);
|
|
|
|
writeOPL(0x63 + offset, 0xFF);
|
|
|
|
|
|
|
|
writeOPL(0x80 + offset, 0xFF);
|
|
|
|
writeOPL(0x83 + offset, 0xFF);
|
|
|
|
|
|
|
|
writeOPL(0xB0 + chan, 0x00);
|
|
|
|
|
|
|
|
// ...and then the note is turned on again, with whatever value is
|
|
|
|
// still lurking in the A0 + chan register, but everything else -
|
|
|
|
// including the two most significant frequency bit, and the octave -
|
|
|
|
// set to zero.
|
|
|
|
//
|
|
|
|
// This is very strange behavior, and causes problems with the ancient
|
|
|
|
// FMOPL code we borrowed from AdPlug. I've added a workaround. See
|
|
|
|
// audio/softsynth/opl/mame.cpp for more details.
|
|
|
|
//
|
|
|
|
// Fortunately, the more modern DOSBox FMOPL code does not seem to have
|
|
|
|
// any trouble with this.
|
|
|
|
|
|
|
|
writeOPL(0xB0 + chan, 0x20);
|
|
|
|
}
|
|
|
|
|
|
|
|
// I believe this is a random number generator. It actually does seem to
|
|
|
|
// generate an even distribution of almost all numbers from 0 through 65535,
|
|
|
|
// though in my tests some numbers were never generated.
|
|
|
|
|
|
|
|
uint16 AdLibDriver::getRandomNr() {
|
|
|
|
_rnd += 0x9248;
|
|
|
|
uint16 lowBits = _rnd & 7;
|
|
|
|
_rnd >>= 3;
|
|
|
|
_rnd |= (lowBits << 13);
|
|
|
|
return _rnd;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::setupDuration(uint8 duration, Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "setupDuration(%d, %lu)", duration, (long)(&channel - _channels));
|
|
|
|
if (channel.durationRandomness) {
|
|
|
|
channel.duration = duration + (getRandomNr() & channel.durationRandomness);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (channel.fractionalSpacing)
|
|
|
|
channel.spacing2 = (duration >> 3) * channel.fractionalSpacing;
|
|
|
|
channel.duration = duration;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function may or may not play the note. It's usually followed by a call
|
|
|
|
// to noteOn(), which will always play the current note.
|
|
|
|
|
|
|
|
void AdLibDriver::setupNote(uint8 rawNote, Channel &channel, bool flag) {
|
|
|
|
debugC(9, kDebugLevelSound, "setupNote(%d, %lu)", rawNote, (long)(&channel - _channels));
|
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
|
|
|
channel.rawNote = rawNote;
|
|
|
|
|
|
|
|
int8 note = (rawNote & 0x0F) + channel.baseNote;
|
|
|
|
int8 octave = ((rawNote + channel.baseOctave) >> 4) & 0x0F;
|
|
|
|
|
|
|
|
// There are only twelve notes. If we go outside that, we have to
|
|
|
|
// adjust the note and octave.
|
|
|
|
|
|
|
|
if (note >= 12) {
|
2020-12-15 21:45:11 +01:00
|
|
|
octave += note / 12;
|
|
|
|
note %= 12;
|
2019-04-14 21:50:26 +02:00
|
|
|
} else if (note < 0) {
|
2020-12-15 21:45:11 +01:00
|
|
|
int8 octaves = -(note + 1) / 12 + 1;
|
|
|
|
octave -= octaves;
|
|
|
|
note += 12 * octaves;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// The calculation of frequency looks quite different from the original
|
|
|
|
// disassembly at a first glance, but when you consider that the
|
|
|
|
// largest possible value would be 0x0246 + 0xFF + 0x47 (and that's if
|
|
|
|
// baseFreq is unsigned), freq is still a 10-bit value, just as it
|
|
|
|
// should be to fit in the Ax and Bx registers.
|
|
|
|
//
|
|
|
|
// If it were larger than that, it could have overflowed into the
|
|
|
|
// octave bits, and that could possibly have been used in some sound.
|
|
|
|
// But as it is now, I can't see any way it would happen.
|
|
|
|
|
|
|
|
uint16 freq = _freqTable[note] + channel.baseFreq;
|
|
|
|
|
|
|
|
// When called from callback 41, the behavior is slightly different:
|
|
|
|
// We adjust the frequency, even when channel.pitchBend is 0.
|
|
|
|
|
|
|
|
if (channel.pitchBend || flag) {
|
|
|
|
const uint8 *table;
|
2020-12-15 21:45:11 +01:00
|
|
|
// For safety, limit the values used to index the tables.
|
2020-12-16 23:43:46 +01:00
|
|
|
uint8 indexNote = CLIP(rawNote & 0x0F, 0, 11);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
if (channel.pitchBend >= 0) {
|
2020-12-16 23:43:46 +01:00
|
|
|
table = _pitchBendTables[indexNote + 2];
|
2020-12-15 21:45:11 +01:00
|
|
|
freq += table[CLIP(+channel.pitchBend, 0, 31)];
|
2019-04-14 21:50:26 +02:00
|
|
|
} else {
|
2020-12-16 23:43:46 +01:00
|
|
|
table = _pitchBendTables[indexNote];
|
2020-12-15 21:45:11 +01:00
|
|
|
freq -= table[CLIP(-channel.pitchBend, 0, 31)];
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-16 23:43:46 +01:00
|
|
|
// Update octave & frequency, but keep on/off state.
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.regAx = freq & 0xFF;
|
2021-04-30 00:22:23 +02:00
|
|
|
channel.regBx = (channel.regBx & 0x20) | (octave << 2) | ((freq >> 8) & 0x03);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
writeOPL(0xA0 + _curChannel, channel.regAx);
|
|
|
|
writeOPL(0xB0 + _curChannel, channel.regBx);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::setupInstrument(uint8 regOffset, const uint8 *dataptr, Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "setupInstrument(%d, %p, %lu)", regOffset, (const void *)dataptr, (long)(&channel - _channels));
|
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check: need 11 bytes of data.
|
2021-01-02 01:31:17 +01:00
|
|
|
if (!checkDataOffset(dataptr, 11))
|
2020-12-15 21:45:11 +01:00
|
|
|
return;
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
// Amplitude Modulation / Vibrato / Envelope Generator Type /
|
|
|
|
// Keyboard Scaling Rate / Modulator Frequency Multiple
|
|
|
|
writeOPL(0x20 + regOffset, *dataptr++);
|
|
|
|
writeOPL(0x23 + regOffset, *dataptr++);
|
|
|
|
|
|
|
|
uint8 temp = *dataptr++;
|
|
|
|
|
|
|
|
// Feedback / Algorithm
|
|
|
|
|
|
|
|
// It is very likely that _curChannel really does refer to the same
|
|
|
|
// channel as regOffset, but there's only one Cx register per channel.
|
|
|
|
|
|
|
|
writeOPL(0xC0 + _curChannel, temp);
|
|
|
|
|
|
|
|
// The algorithm bit. I don't pretend to understand this fully, but
|
|
|
|
// "If set to 0, operator 1 modulates operator 2. In this case,
|
|
|
|
// operator 2 is the only one producing sound. If set to 1, both
|
|
|
|
// operators produce sound directly. Complex sounds are more easily
|
|
|
|
// created if the algorithm is set to 0."
|
|
|
|
|
|
|
|
channel.twoChan = temp & 1;
|
|
|
|
|
|
|
|
// Waveform Select
|
|
|
|
writeOPL(0xE0 + regOffset, *dataptr++);
|
|
|
|
writeOPL(0xE3 + regOffset, *dataptr++);
|
|
|
|
|
|
|
|
channel.opLevel1 = *dataptr++;
|
|
|
|
channel.opLevel2 = *dataptr++;
|
|
|
|
|
|
|
|
// Level Key Scaling / Total Level
|
|
|
|
writeOPL(0x40 + regOffset, calculateOpLevel1(channel));
|
|
|
|
writeOPL(0x43 + regOffset, calculateOpLevel2(channel));
|
|
|
|
|
|
|
|
// Attack Rate / Decay Rate
|
|
|
|
writeOPL(0x60 + regOffset, *dataptr++);
|
|
|
|
writeOPL(0x63 + regOffset, *dataptr++);
|
|
|
|
|
|
|
|
// Sustain Level / Release Rate
|
|
|
|
writeOPL(0x80 + regOffset, *dataptr++);
|
|
|
|
writeOPL(0x83 + regOffset, *dataptr++);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apart from playing the note, this function also updates the variables for
|
2020-12-18 00:10:43 +01:00
|
|
|
// the vibrato primary effect.
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
void AdLibDriver::noteOn(Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "noteOn(%lu)", (long)(&channel - _channels));
|
|
|
|
|
|
|
|
// The "note on" bit is set, and the current note is played.
|
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
|
|
|
channel.regBx |= 0x20;
|
|
|
|
writeOPL(0xB0 + _curChannel, channel.regBx);
|
|
|
|
|
2020-12-18 00:10:43 +01:00
|
|
|
// Update vibrato effect variables: vibratoStep is set to a
|
|
|
|
// vibratoStepRange+1-bit value proportional to the note's f-number.
|
|
|
|
// Reinitalize delay countdown; vibratoStepsCountdown reinitialization omitted.
|
|
|
|
int8 shift = 9 - CLIP<int8>(channel.vibratoStepRange, 0, 9);
|
|
|
|
uint16 freq = ((channel.regBx << 8) | channel.regAx) & 0x3FF;
|
|
|
|
channel.vibratoStep = (freq >> shift) & 0xFF;
|
|
|
|
channel.vibratoDelayCountdown = channel.vibratoDelay;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void AdLibDriver::adjustVolume(Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "adjustVolume(%lu)", (long)(&channel - _channels));
|
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Level Key Scaling / Total Level
|
|
|
|
|
|
|
|
writeOPL(0x43 + _regOffset[_curChannel], calculateOpLevel2(channel));
|
|
|
|
if (channel.twoChan)
|
|
|
|
writeOPL(0x40 + _regOffset[_curChannel], calculateOpLevel1(channel));
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is presumably only used for some sound effects, e.g. Malcolm blowing up
|
|
|
|
// the trees in the intro (but not the effect where he "booby-traps" the big
|
|
|
|
// tree) and turning Kallak to stone. Related functions and variables:
|
|
|
|
//
|
2020-12-17 02:32:41 +01:00
|
|
|
// update_setupPrimaryEffectSlide()
|
|
|
|
// - Initializes slideTempo, slideStep and slideTimer
|
|
|
|
// - slideTempo is not further modified
|
|
|
|
// - slideStep is not further modified, except by update_removePrimaryEffectSlide()
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
2020-12-17 02:32:41 +01:00
|
|
|
// update_removePrimaryEffectSlide()
|
|
|
|
// - Deinitializes slideStep
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
2020-12-17 02:32:41 +01:00
|
|
|
// slideTempo - determines how often the frequency is updated
|
|
|
|
// slideStep - amount the frequency changes each update
|
|
|
|
// slideTimer - keeps track of time
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-17 02:32:41 +01:00
|
|
|
void AdLibDriver::primaryEffectSlide(Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "Calling primaryEffectSlide (channel: %d)", _curChannel);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
2020-12-31 02:15:30 +01:00
|
|
|
// Time for next frequency update?
|
|
|
|
if (!advance(channel.slideTimer, channel.slideTempo))
|
2019-04-14 21:50:26 +02:00
|
|
|
return;
|
|
|
|
|
2020-12-17 02:32:41 +01:00
|
|
|
// Extract current frequency, (shifted) octave, and "note on" bit into
|
|
|
|
// separate variable so calculations can't overflow into other fields.
|
|
|
|
int16 freq = ((channel.regBx & 0x03) << 8) | channel.regAx;
|
|
|
|
uint8 octave = channel.regBx & 0x1C;
|
|
|
|
uint8 note_on = channel.regBx & 0x20;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-17 02:32:41 +01:00
|
|
|
// Limit slideStep to prevent integer overflow.
|
|
|
|
freq += CLIP<int16>(channel.slideStep, -0x3FF, 0x3FF);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-17 02:32:41 +01:00
|
|
|
if (channel.slideStep >= 0 && freq >= 734) {
|
2020-12-16 02:21:23 +01:00
|
|
|
// The new frequency is too high. Shift it down and go
|
|
|
|
// up one octave.
|
2020-12-17 02:32:41 +01:00
|
|
|
freq >>= 1;
|
|
|
|
if (!(freq & 0x3FF))
|
|
|
|
++freq;
|
|
|
|
octave += 4;
|
|
|
|
} else if (channel.slideStep < 0 && freq < 388) {
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check: a negative frequency triggers undefined
|
|
|
|
// behavior for the left shift operator below.
|
2020-12-17 02:32:41 +01:00
|
|
|
if (freq < 0)
|
|
|
|
freq = 0;
|
2020-12-15 21:45:11 +01:00
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
// The new frequency is too low. Shift it up and go
|
|
|
|
// down one octave.
|
2020-12-17 02:32:41 +01:00
|
|
|
freq <<= 1;
|
|
|
|
if (!(freq & 0x3FF))
|
|
|
|
--freq;
|
|
|
|
octave -= 4;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2020-12-17 02:32:41 +01:00
|
|
|
// Set new frequency and octave.
|
|
|
|
channel.regAx = freq & 0xFF;
|
|
|
|
channel.regBx = note_on | (octave & 0x1C) | ((freq >> 8) & 0x03);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-17 02:32:41 +01:00
|
|
|
writeOPL(0xA0 + _curChannel, channel.regAx);
|
|
|
|
writeOPL(0xB0 + _curChannel, channel.regBx);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// This is presumably only used for some sound effects, e.g. Malcolm entering
|
|
|
|
// and leaving Kallak's hut. Related functions and variables:
|
|
|
|
//
|
2020-12-18 00:10:43 +01:00
|
|
|
// update_setupPrimaryEffectVibrato()
|
|
|
|
// - Initializes vibratoTempo, vibratoStepRange, vibratoStepsCountdown,
|
|
|
|
// vibratoNumSteps, and vibratoDelay
|
|
|
|
// - vibratoTempo is not further modified
|
|
|
|
// - vibratoStepRange is not further modified
|
|
|
|
// - vibratoStepsCountdown is a countdown that gets reinitialized to
|
|
|
|
// vibratoNumSteps on zero, but is initially only half as much
|
|
|
|
// - vibratoNumSteps is not further modified
|
|
|
|
// - vibratoDelay is not further modified
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
|
|
|
// noteOn()
|
|
|
|
// - Plays the current note
|
2020-12-18 00:10:43 +01:00
|
|
|
// - Sets vibratoStep depending on vibratoStepRange and the note's f-number
|
|
|
|
// - Initializes vibratoDelayCountdown with vibratoDelay
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
2020-12-18 00:10:43 +01:00
|
|
|
// vibratoTempo - determines how often the frequency is updated
|
|
|
|
// vibratoStepRange - determines frequency step size depending on f-number
|
|
|
|
// vibratoStepsCountdown - reverses slide direction on zero
|
|
|
|
// vibratoNumSteps - initializer for vibratoStepsCountdown countdown
|
|
|
|
// vibratoDelay - initializer for vibratoDelayCountdown
|
|
|
|
// vibratoStep - amount the frequency changes each update
|
|
|
|
// vibratoDelayCountdown - effect starts when it reaches zero
|
|
|
|
// vibratoTimer - keeps track of time
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
2020-12-18 00:10:43 +01:00
|
|
|
// Note that vibratoTimer is never initialized. Not that it should matter much,
|
|
|
|
// but it is a bit sloppy. Also vibratoStepsCountdown should be reset to its
|
|
|
|
// initial value in noteOn() but isn't.
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-18 00:10:43 +01:00
|
|
|
void AdLibDriver::primaryEffectVibrato(Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "Calling primaryEffectVibrato (channel: %d)", _curChannel);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
2020-12-18 00:10:43 +01:00
|
|
|
// When a new note is played the effect doesn't start immediately.
|
|
|
|
if (channel.vibratoDelayCountdown) {
|
|
|
|
--channel.vibratoDelayCountdown;
|
2019-04-14 21:50:26 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-31 02:15:30 +01:00
|
|
|
// Time for an update?
|
|
|
|
if (advance(channel.vibratoTimer, channel.vibratoTempo)) {
|
2020-12-18 00:10:43 +01:00
|
|
|
// Reverse direction every vibratoNumSteps updates
|
|
|
|
if (!(--channel.vibratoStepsCountdown)) {
|
|
|
|
channel.vibratoStep = -channel.vibratoStep;
|
|
|
|
channel.vibratoStepsCountdown = channel.vibratoNumSteps;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2020-12-18 00:10:43 +01:00
|
|
|
// Update frequency.
|
|
|
|
uint16 freq = ((channel.regBx << 8) | channel.regAx) & 0x3FF;
|
|
|
|
freq += channel.vibratoStep;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-18 00:10:43 +01:00
|
|
|
channel.regAx = freq & 0xFF;
|
|
|
|
channel.regBx = (channel.regBx & 0xFC) | (freq >> 8);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Octave / F-Number / Key-On
|
|
|
|
writeOPL(0xA0 + _curChannel, channel.regAx);
|
|
|
|
writeOPL(0xB0 + _curChannel, channel.regBx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-18 02:25:38 +01:00
|
|
|
// I don't know where this is used. An OPL register is regularly updated
|
|
|
|
// with data from a chunk of the _soundData[] buffer, i.e., one instrument
|
|
|
|
// parameter register is modulated with data from the chunk. The data is
|
|
|
|
// reused repeatedly starting from the end of the chunk.
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
|
|
|
// Since we use _curRegOffset to specify the final register, it's quite
|
|
|
|
// unlikely that this function is ever used to play notes. It's probably only
|
|
|
|
// used to modify the sound. Another thing that supports this idea is that it
|
|
|
|
// can be combined with any of the effects callbacks above.
|
|
|
|
//
|
|
|
|
// Related functions and variables:
|
|
|
|
//
|
|
|
|
// update_setupSecondaryEffect1()
|
2020-12-18 02:25:38 +01:00
|
|
|
// - Initialies secondaryEffectTimer, secondaryEffectTempo,
|
|
|
|
// secondaryEffectSize, secondaryEffectPos, secondaryEffectRegbase,
|
|
|
|
// and secondaryEffectData
|
|
|
|
// - secondaryEffectTempo is not further modified
|
|
|
|
// - secondaryEffectSize is not further modified
|
|
|
|
// - secondaryEffectRegbase is not further modified
|
|
|
|
// - secondaryEffectData is not further modified
|
2019-04-14 21:50:26 +02:00
|
|
|
//
|
2020-12-18 02:25:38 +01:00
|
|
|
// secondaryEffectTimer - keeps track of time
|
|
|
|
// secondaryEffectTempo - determines how often the operation is performed
|
|
|
|
// secondaryEffectSize - the size of the data chunk
|
|
|
|
// secondaryEffectPos - the current index into the data chunk
|
|
|
|
// secondaryEffectRegbase - the operation to perform
|
|
|
|
// secondaryEffectData - the offset of the data chunk
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
void AdLibDriver::secondaryEffect1(Channel &channel) {
|
|
|
|
debugC(9, kDebugLevelSound, "Calling secondaryEffect1 (channel: %d)", _curChannel);
|
|
|
|
|
|
|
|
if (_curChannel >= 9)
|
|
|
|
return;
|
|
|
|
|
2020-12-31 02:15:30 +01:00
|
|
|
if (advance(channel.secondaryEffectTimer, channel.secondaryEffectTempo)) {
|
2020-12-18 02:25:38 +01:00
|
|
|
if (--channel.secondaryEffectPos < 0)
|
|
|
|
channel.secondaryEffectPos = channel.secondaryEffectSize;
|
|
|
|
writeOPL(channel.secondaryEffectRegbase + _curRegOffset,
|
|
|
|
_soundData[channel.secondaryEffectData + channel.secondaryEffectPos]);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8 AdLibDriver::calculateOpLevel1(Channel &channel) {
|
2020-01-18 21:35:06 +01:00
|
|
|
uint8 value = channel.opLevel1 & 0x3F;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
if (channel.twoChan) {
|
|
|
|
value += channel.opExtraLevel1;
|
|
|
|
value += channel.opExtraLevel2;
|
|
|
|
|
|
|
|
uint16 level3 = (channel.opExtraLevel3 ^ 0x3F) * channel.volumeModifier;
|
|
|
|
if (level3) {
|
|
|
|
level3 += 0x3F;
|
|
|
|
level3 >>= 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
value += level3 ^ 0x3F;
|
|
|
|
}
|
|
|
|
|
2020-01-18 21:35:06 +01:00
|
|
|
// The clipping as signed instead of unsigned caused very ugly noises in LOK when the music
|
|
|
|
// was fading out in certain situations (bug #11303). The bug seems to come to surface only
|
|
|
|
// when the volume is not set to the maximum.
|
|
|
|
// I have confirmed that the noise bug also appears in LOL floppy (Westwood logo sound). It has
|
|
|
|
// been reported to be present in EOB 1 (intro music), but I haven't been able to confirm it.
|
|
|
|
// The original AdLib drivers all do the same wrong clipping. At least in the original EOB and
|
|
|
|
// LOK games this wouldn't cause issues, since the original drivers (and games) do not have
|
|
|
|
// volume settings and use a simpler calculation of the total level (just adding the three
|
|
|
|
// opExtraLevels to the opLevel).
|
|
|
|
// The later (HOF/LOL) original drivers do the same wrong clipping, too. But original LOL floppy
|
2020-02-05 15:38:39 +01:00
|
|
|
// doesn't have volume settings either. And with max volume the logo sound is okay...
|
2020-01-18 21:35:06 +01:00
|
|
|
if (value & 0x80)
|
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::calculateOpLevel1(): WORKAROUND - total level clipping uint/int bug encountered");
|
|
|
|
value = CLIP<uint8>(value, 0, 0x3F);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
if (!channel.volumeModifier)
|
|
|
|
value = 0x3F;
|
|
|
|
|
|
|
|
// Preserve the scaling level bits from opLevel1
|
2020-01-18 21:35:06 +01:00
|
|
|
return value | (channel.opLevel1 & 0xC0);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8 AdLibDriver::calculateOpLevel2(Channel &channel) {
|
2020-01-18 21:35:06 +01:00
|
|
|
uint8 value = channel.opLevel2 & 0x3F;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
value += channel.opExtraLevel1;
|
|
|
|
value += channel.opExtraLevel2;
|
|
|
|
|
|
|
|
uint16 level3 = (channel.opExtraLevel3 ^ 0x3F) * channel.volumeModifier;
|
|
|
|
if (level3) {
|
|
|
|
level3 += 0x3F;
|
|
|
|
level3 >>= 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
value += level3 ^ 0x3F;
|
|
|
|
|
2020-01-18 00:05:26 +01:00
|
|
|
// See comment in calculateOpLevel1()
|
2020-01-18 21:35:06 +01:00
|
|
|
if (value & 0x80)
|
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::calculateOpLevel2(): WORKAROUND - total level clipping uint/int bug encountered");
|
|
|
|
value = CLIP<uint8>(value, 0, 0x3F);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
if (!channel.volumeModifier)
|
|
|
|
value = 0x3F;
|
|
|
|
|
|
|
|
// Preserve the scaling level bits from opLevel2
|
2020-01-18 21:35:06 +01:00
|
|
|
return value | (channel.opLevel2 & 0xC0);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// parser opcodes
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setRepeat(Channel &channel, const uint8 *values) {
|
|
|
|
channel.repeatCounter = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_checkRepeat(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
if (--channel.repeatCounter) {
|
2021-01-03 23:33:56 +01:00
|
|
|
int16 add = READ_LE_UINT16(values);
|
2020-12-15 21:45:11 +01:00
|
|
|
|
|
|
|
// Safety check: ignore jump to invalid address
|
2021-01-03 23:33:56 +01:00
|
|
|
if (!checkDataOffset(channel.dataptr, add))
|
2020-12-15 21:45:11 +01:00
|
|
|
warning("AdlibDriver::update_checkRepeat: Ignoring invalid offset %i", add);
|
|
|
|
else
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr += add;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupProgram(Channel &channel, const uint8 *values) {
|
|
|
|
if (values[0] == 0xFF)
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
const uint8 *ptr = getProgram(values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// In case we encounter an invalid program we simply ignore it and do
|
|
|
|
// nothing instead. The original did not care about invalid programs and
|
|
|
|
// simply tried to play them anyway... But to avoid crashes due we ingore
|
|
|
|
// them.
|
|
|
|
// This, for example, happens in the Lands of Lore intro when Scotia gets
|
|
|
|
// the ring in the intro.
|
2021-01-02 01:31:17 +01:00
|
|
|
if (!checkDataOffset(ptr, 2)) {
|
2021-01-03 23:33:56 +01:00
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::update_setupProgram: Invalid program %d specified", values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8 chan = *ptr++;
|
|
|
|
uint8 priority = *ptr++;
|
|
|
|
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check: ignore programs with invalid channel number.
|
|
|
|
if (chan > 9) {
|
|
|
|
warning("AdLibDriver::update_setupProgram: Invalid channel %d", chan);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
Channel &channel2 = _channels[chan];
|
|
|
|
|
|
|
|
if (priority >= channel2.priority) {
|
2021-01-03 23:33:56 +01:00
|
|
|
// The opcode is not allowed to modify its own data pointer.
|
|
|
|
// To enforce that, we make a copy and restore it later.
|
|
|
|
//
|
|
|
|
// This fixes a subtle music bug where the wrong music would
|
|
|
|
// play when getting the quill in Kyra 1.
|
|
|
|
const uint8 *dataptrBackUp = channel.dataptr;
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
// We keep new tracks from being started for two further iterations of
|
|
|
|
// the callback. This assures the correct velocity is used for this
|
|
|
|
// program.
|
|
|
|
_programStartTimeout = 2;
|
2021-01-03 23:33:56 +01:00
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
initChannel(channel2);
|
|
|
|
channel2.priority = priority;
|
|
|
|
channel2.dataptr = ptr;
|
|
|
|
channel2.tempo = 0xFF;
|
2020-12-31 02:15:30 +01:00
|
|
|
channel2.timer = 0xFF;
|
2019-04-14 21:50:26 +02:00
|
|
|
channel2.duration = 1;
|
|
|
|
|
|
|
|
if (chan <= 5)
|
|
|
|
channel2.volumeModifier = _musicVolume;
|
|
|
|
else
|
|
|
|
channel2.volumeModifier = _sfxVolume;
|
|
|
|
|
2020-12-18 01:24:49 +01:00
|
|
|
initAdlibChannel(chan);
|
2021-01-03 23:33:56 +01:00
|
|
|
|
|
|
|
channel.dataptr = dataptrBackUp;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setNoteSpacing(Channel &channel, const uint8 *values) {
|
|
|
|
channel.spacing1 = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_jump(Channel &channel, const uint8 *values) {
|
|
|
|
int16 add = READ_LE_UINT16(values);
|
2021-01-02 01:31:17 +01:00
|
|
|
// Safety check: ignore jump to invalid address
|
|
|
|
if (_version == 1)
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr = checkDataOffset(_soundData, add - 191);
|
2021-01-02 01:31:17 +01:00
|
|
|
else
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr = checkDataOffset(channel.dataptr, add);
|
2021-01-02 01:31:17 +01:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (!channel.dataptr) {
|
2020-12-15 21:45:11 +01:00
|
|
|
warning("AdlibDriver::update_jump: Invalid offset %i, stopping channel", add);
|
2021-01-03 23:33:56 +01:00
|
|
|
return update_stopChannel(channel, values);
|
2020-12-15 21:45:11 +01:00
|
|
|
}
|
2019-04-14 21:50:26 +02:00
|
|
|
if (_syncJumpMask & (1 << (&channel - _channels)))
|
|
|
|
channel.lock = true;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_jumpToSubroutine(Channel &channel, const uint8 *values) {
|
|
|
|
int16 add = READ_LE_UINT16(values);
|
2020-12-15 21:45:11 +01:00
|
|
|
|
|
|
|
// Safety checks: ignore jumps when stack is full or address is invalid.
|
|
|
|
if (channel.dataptrStackPos >= ARRAYSIZE(channel.dataptrStack)) {
|
|
|
|
warning("AdLibDriver::update_jumpToSubroutine: Stack overlow");
|
|
|
|
return 0;
|
|
|
|
}
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptrStack[channel.dataptrStackPos++] = channel.dataptr;
|
2021-01-02 01:31:17 +01:00
|
|
|
if (_version < 3)
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr = checkDataOffset(_soundData, add - 191);
|
2021-01-02 01:31:17 +01:00
|
|
|
else
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr = checkDataOffset(channel.dataptr, add);
|
2021-01-02 01:31:17 +01:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (!channel.dataptr)
|
|
|
|
channel.dataptr = channel.dataptrStack[--channel.dataptrStackPos];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_returnFromSubroutine(Channel &channel, const uint8 *values) {
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check: stop track when stack is empty.
|
|
|
|
if (!channel.dataptrStackPos) {
|
|
|
|
warning("AdLibDriver::update_returnFromSubroutine: Stack underflow");
|
2021-01-03 23:33:56 +01:00
|
|
|
return update_stopChannel(channel, values);
|
2020-12-15 21:45:11 +01:00
|
|
|
}
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr = channel.dataptrStack[--channel.dataptrStackPos];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setBaseOctave(Channel &channel, const uint8 *values) {
|
|
|
|
channel.baseOctave = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_stopChannel(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.priority = 0;
|
|
|
|
if (_curChannel != 9)
|
|
|
|
noteOff(channel);
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_playRest(Channel &channel, const uint8 *values) {
|
|
|
|
setupDuration(values[0], channel);
|
2019-04-14 21:50:26 +02:00
|
|
|
noteOff(channel);
|
2021-01-03 23:33:56 +01:00
|
|
|
return values[0] != 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_writeAdLib(Channel &channel, const uint8 *values) {
|
|
|
|
writeOPL(values[0], values[1]);
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupNoteAndDuration(Channel &channel, const uint8 *values) {
|
|
|
|
setupNote(values[0], channel);
|
|
|
|
setupDuration(values[1], channel);
|
|
|
|
return values[1] != 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setBaseNote(Channel &channel, const uint8 *values) {
|
|
|
|
channel.baseNote = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupSecondaryEffect1(Channel &channel, const uint8 *values) {
|
|
|
|
channel.secondaryEffectTimer = channel.secondaryEffectTempo = values[0];
|
|
|
|
channel.secondaryEffectSize = channel.secondaryEffectPos = values[1];
|
|
|
|
channel.secondaryEffectRegbase = values[2];
|
2019-04-14 21:50:26 +02:00
|
|
|
// WORKAROUND: The original code reads a true offset which later gets translated via xlat (in
|
|
|
|
// the current segment). This means that the outcome depends on the sound data offset.
|
|
|
|
// Unfortunately this offset is different in most implementations of the audio driver and
|
|
|
|
// probably also different from the offset assumed by the sequencer.
|
|
|
|
// It seems that the driver assumes an offset of 191 which is wrong for all the game driver
|
|
|
|
// implementations.
|
|
|
|
// This bug has probably not been noticed, since the effect is hardly used and the sounds are
|
|
|
|
// not necessarily worse. I noticed the difference between ScummVM and DOSBox for the EOB II
|
|
|
|
// teleporter sound. I also found the location of the table which is supposed to be used here
|
|
|
|
// (simple enough: it is located at the end of the track after the 0x88 ending opcode).
|
|
|
|
// Teleporters in EOB I and II now sound exactly the same which I am sure was the intended way,
|
|
|
|
// since the sound data is exactly the same.
|
|
|
|
// In DOSBox the teleporters will sound different in EOB I and II, due to different sound
|
|
|
|
// data offsets.
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.secondaryEffectData = READ_LE_UINT16(&values[3]) - 191;
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.secondaryEffect = &AdLibDriver::secondaryEffect1;
|
2020-12-15 21:45:11 +01:00
|
|
|
|
|
|
|
// Safety check: don't enable effect when table location is invalid.
|
2020-12-18 02:25:38 +01:00
|
|
|
int start = channel.secondaryEffectData + channel.secondaryEffectSize;
|
2020-12-19 03:03:12 +01:00
|
|
|
if (start < 0 || start >= (int)_soundDataSize) {
|
2020-12-15 21:45:11 +01:00
|
|
|
warning("AdLibDriver::update_setupSecondaryEffect1: Ignoring due to invalid table location");
|
|
|
|
channel.secondaryEffect = nullptr;
|
|
|
|
}
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_stopOtherChannel(Channel &channel, const uint8 *values) {
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check
|
2021-01-03 23:33:56 +01:00
|
|
|
if (values[0] > 9) {
|
|
|
|
warning("AdLibDriver::update_stopOtherChannel: Ignoring invalid channel %d", values[0]);
|
2020-12-15 21:45:11 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
// Don't change our own dataptr!
|
|
|
|
const uint8 *dataptrBackUp = channel.dataptr;
|
|
|
|
|
|
|
|
Channel &channel2 = _channels[values[0]];
|
2019-04-14 21:50:26 +02:00
|
|
|
channel2.duration = 0;
|
|
|
|
channel2.priority = 0;
|
2020-12-16 02:21:23 +01:00
|
|
|
channel2.dataptr = nullptr;
|
2021-01-03 23:33:56 +01:00
|
|
|
|
|
|
|
channel.dataptr = dataptrBackUp;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_waitForEndOfProgram(Channel &channel, const uint8 *values) {
|
|
|
|
const uint8 *ptr = getProgram(values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Safety check in case an invalid program is specified. This would make
|
|
|
|
// getProgram return a nullptr and thus cause invalid memory reads.
|
|
|
|
if (!ptr) {
|
2021-01-03 23:33:56 +01:00
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::update_waitForEndOfProgram: Invalid program %d specified", values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8 chan = *ptr;
|
|
|
|
|
2020-12-15 21:45:11 +01:00
|
|
|
if (chan > 9 || !_channels[chan].dataptr)
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr -= 2;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupInstrument(Channel &channel, const uint8 *values) {
|
|
|
|
const uint8 *instrument = getInstrument(values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// We add a safety check to avoid setting up invalid instruments. This is
|
|
|
|
// not done in the original. However, to avoid crashes due to invalid
|
|
|
|
// memory reads we simply ignore the request.
|
|
|
|
// This happens, for example, in Hand of Fate when using the swampsnake
|
|
|
|
// potion on Zanthia to scare off the rat in the cave in the first chapter
|
|
|
|
// of the game.
|
|
|
|
if (!instrument) {
|
2021-01-03 23:33:56 +01:00
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::update_setupInstrument: Invalid instrument %d specified", values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
setupInstrument(_curRegOffset, instrument, channel);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupPrimaryEffectSlide(Channel &channel, const uint8 *values) {
|
|
|
|
channel.slideTempo = values[0];
|
|
|
|
channel.slideStep = READ_BE_UINT16(&values[1]);
|
2020-12-17 02:32:41 +01:00
|
|
|
channel.primaryEffect = &AdLibDriver::primaryEffectSlide;
|
|
|
|
channel.slideTimer = 0xFF;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_removePrimaryEffectSlide(Channel &channel, const uint8 *values) {
|
2020-12-16 02:21:23 +01:00
|
|
|
channel.primaryEffect = nullptr;
|
2020-12-17 02:32:41 +01:00
|
|
|
channel.slideStep = 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setBaseFreq(Channel &channel, const uint8 *values) {
|
|
|
|
channel.baseFreq = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupPrimaryEffectVibrato(Channel &channel, const uint8 *values) {
|
|
|
|
channel.vibratoTempo = values[0];
|
|
|
|
channel.vibratoStepRange = values[1];
|
|
|
|
channel.vibratoStepsCountdown = values[2] + 1;
|
|
|
|
channel.vibratoNumSteps = values[2] << 1;
|
|
|
|
channel.vibratoDelay = values[3];
|
2020-12-18 00:10:43 +01:00
|
|
|
channel.primaryEffect = &AdLibDriver::primaryEffectVibrato;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setPriority(Channel &channel, const uint8 *values) {
|
|
|
|
channel.priority = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-12-18 19:08:50 +01:00
|
|
|
// This provides a way to synchronize channels with a global beat:
|
|
|
|
//
|
|
|
|
// update_setBeat()
|
|
|
|
// - Initializes _beatDivider, _beatDivCnt, _beatCounter, and _beatWaiting;
|
|
|
|
// resets _callbackTimer
|
|
|
|
// - _beatDivider is not further modified
|
|
|
|
//
|
|
|
|
// callback()
|
2021-01-27 18:45:31 +01:00
|
|
|
// - _beatDivCnt is a countdown, gets reinitialized to _beatDivider on zero
|
2020-12-18 19:08:50 +01:00
|
|
|
// - _beatCounter is incremented when _beatDivCnt is reset, i.e., it's a
|
|
|
|
// counter which updates with the global _tempo divided by _beatDivider.
|
|
|
|
//
|
|
|
|
// update_waitForNextBeat()
|
|
|
|
// - _beatWaiting is updated if some bits are 0 in _beatCounter (off beat)
|
|
|
|
// - the program is stopped until some of the masked bits in _beatCounter
|
|
|
|
// become 1 and _beatWaiting is non-zero (on beat), then _beatWaiting is
|
|
|
|
// cleared
|
|
|
|
//
|
|
|
|
// _beatDivider - determines how fast _beatCounter is incremented
|
|
|
|
// _beatDivCnt - countdown for the divider
|
|
|
|
// _beatCounter - counter updated with global _tempo divided by _beatDivider
|
|
|
|
// _beatWaiting - flags that waiting started before watched counter bit got 1
|
|
|
|
//
|
|
|
|
// Note that in theory _beatWaiting could wrap around to zero while waiting,
|
|
|
|
// then the rising edge wouldn't trigger. That's probably not a big issue
|
|
|
|
// in practice sice it can only happen for long delays (big _beatDivider and
|
|
|
|
// waiting on one of the higher bits) but could have been prevented easily.
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setBeat(Channel &channel, const uint8 *values) {
|
|
|
|
_beatDivider = _beatDivCnt = values[0] >> 1;
|
2019-04-14 21:50:26 +02:00
|
|
|
_callbackTimer = 0xFF;
|
2020-12-18 19:08:50 +01:00
|
|
|
_beatCounter = _beatWaiting = 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_waitForNextBeat(Channel &channel, const uint8 *values) {
|
|
|
|
if ((_beatCounter & values[0]) && _beatWaiting) {
|
2020-12-18 19:08:50 +01:00
|
|
|
_beatWaiting = 0;
|
2020-12-16 02:21:23 +01:00
|
|
|
return 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (!(_beatCounter & values[0]))
|
2020-12-18 19:08:50 +01:00
|
|
|
++_beatWaiting;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr -= 2;
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.duration = 1;
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setExtraLevel1(Channel &channel, const uint8 *values) {
|
|
|
|
channel.opExtraLevel1 = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
adjustVolume(channel);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupDuration(Channel &channel, const uint8 *values) {
|
|
|
|
setupDuration(values[0], channel);
|
|
|
|
return values[0] != 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_playNote(Channel &channel, const uint8 *values) {
|
|
|
|
setupDuration(values[0], channel);
|
2019-04-14 21:50:26 +02:00
|
|
|
noteOn(channel);
|
2021-01-03 23:33:56 +01:00
|
|
|
return values[0] != 0;
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setFractionalNoteSpacing(Channel &channel, const uint8 *values) {
|
|
|
|
channel.fractionalSpacing = values[0] & 7;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setTempo(Channel &channel, const uint8 *values) {
|
|
|
|
_tempo = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_removeSecondaryEffect1(Channel &channel, const uint8 *values) {
|
2020-12-16 02:21:23 +01:00
|
|
|
channel.secondaryEffect = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setChannelTempo(Channel &channel, const uint8 *values) {
|
|
|
|
channel.tempo = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setExtraLevel3(Channel &channel, const uint8 *values) {
|
|
|
|
channel.opExtraLevel3 = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setExtraLevel2(Channel &channel, const uint8 *values) {
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check
|
2021-01-03 23:33:56 +01:00
|
|
|
if (values[0] > 9) {
|
|
|
|
warning("AdLibDriver::update_setExtraLevel2: Ignore invalid channel %d", values[0]);
|
2020-12-15 21:45:11 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
int channelBackUp = _curChannel;
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
_curChannel = values[0];
|
|
|
|
Channel &channel2 = _channels[_curChannel];
|
|
|
|
channel2.opExtraLevel2 = values[1];
|
2019-04-14 21:50:26 +02:00
|
|
|
adjustVolume(channel2);
|
|
|
|
|
|
|
|
_curChannel = channelBackUp;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_changeExtraLevel2(Channel &channel, const uint8 *values) {
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check
|
2021-01-03 23:33:56 +01:00
|
|
|
if (values[0] > 9) {
|
|
|
|
warning("AdLibDriver::update_changeExtraLevel2: Ignore invalid channel %d", values[0]);
|
2020-12-15 21:45:11 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
int channelBackUp = _curChannel;
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
_curChannel = values[0];
|
|
|
|
Channel &channel2 = _channels[_curChannel];
|
|
|
|
channel2.opExtraLevel2 += values[1];
|
2019-04-14 21:50:26 +02:00
|
|
|
adjustVolume(channel2);
|
|
|
|
|
|
|
|
_curChannel = channelBackUp;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apart from initializing to zero, these two functions are the only ones that
|
|
|
|
// modify _vibratoAndAMDepthBits.
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setAMDepth(Channel &channel, const uint8 *values) {
|
|
|
|
if (values[0] & 1)
|
2019-04-14 21:50:26 +02:00
|
|
|
_vibratoAndAMDepthBits |= 0x80;
|
|
|
|
else
|
|
|
|
_vibratoAndAMDepthBits &= 0x7F;
|
|
|
|
|
|
|
|
writeOPL(0xBD, _vibratoAndAMDepthBits);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setVibratoDepth(Channel &channel, const uint8 *values) {
|
|
|
|
if (values[0] & 1)
|
2019-04-14 21:50:26 +02:00
|
|
|
_vibratoAndAMDepthBits |= 0x40;
|
|
|
|
else
|
|
|
|
_vibratoAndAMDepthBits &= 0xBF;
|
|
|
|
|
|
|
|
writeOPL(0xBD, _vibratoAndAMDepthBits);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_changeExtraLevel1(Channel &channel, const uint8 *values) {
|
|
|
|
channel.opExtraLevel1 += values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
adjustVolume(channel);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_clearChannel(Channel &channel, const uint8 *values) {
|
2020-12-15 21:45:11 +01:00
|
|
|
// Safety check
|
2021-01-03 23:33:56 +01:00
|
|
|
if (values[0] > 9) {
|
|
|
|
warning("AdLibDriver::update_clearChannel: Ignore invalid channel %d", values[0]);
|
2020-12-15 21:45:11 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
int channelBackUp = _curChannel;
|
2021-01-03 23:33:56 +01:00
|
|
|
_curChannel = values[0];
|
|
|
|
// Don't modify our own dataptr!
|
|
|
|
const uint8 *dataptrBackUp = channel.dataptr;
|
2020-12-18 21:16:48 +01:00
|
|
|
|
|
|
|
// Stop channel
|
2021-01-03 23:33:56 +01:00
|
|
|
Channel &channel2 = _channels[_curChannel];
|
2019-04-14 21:50:26 +02:00
|
|
|
channel2.duration = channel2.priority = 0;
|
|
|
|
channel2.dataptr = 0;
|
|
|
|
channel2.opExtraLevel2 = 0;
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (_curChannel != 9) {
|
2020-12-18 21:16:48 +01:00
|
|
|
// Silence channel
|
2021-01-03 23:33:56 +01:00
|
|
|
uint8 regOff = _regOffset[_curChannel];
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Feedback strength / Connection type
|
|
|
|
writeOPL(0xC0 + _curChannel, 0x00);
|
|
|
|
|
|
|
|
// Key scaling level / Operator output level
|
2020-12-18 21:16:48 +01:00
|
|
|
writeOPL(0x43 + regOff, 0x3F);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Sustain Level / Release Rate
|
2020-12-18 21:16:48 +01:00
|
|
|
writeOPL(0x83 + regOff, 0xFF);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Key On / Octave / Frequency
|
|
|
|
writeOPL(0xB0 + _curChannel, 0x00);
|
|
|
|
}
|
|
|
|
|
|
|
|
_curChannel = channelBackUp;
|
2021-01-03 23:33:56 +01:00
|
|
|
channel.dataptr = dataptrBackUp;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_changeNoteRandomly(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
if (_curChannel >= 9)
|
|
|
|
return 0;
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
uint16 mask = READ_BE_UINT16(values);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-18 21:33:03 +01:00
|
|
|
uint16 note = ((channel.regBx & 0x1F) << 8) | channel.regAx;
|
|
|
|
|
|
|
|
note += mask & getRandomNr();
|
|
|
|
note |= ((channel.regBx & 0x20) << 8);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Frequency
|
2020-12-18 21:33:03 +01:00
|
|
|
writeOPL(0xA0 + _curChannel, note & 0xFF);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Key On / Octave / Frequency
|
2020-12-18 21:33:03 +01:00
|
|
|
writeOPL(0xB0 + _curChannel, (note & 0xFF00) >> 8);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_removePrimaryEffectVibrato(Channel &channel, const uint8 *values) {
|
2020-12-16 02:21:23 +01:00
|
|
|
channel.primaryEffect = nullptr;
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_pitchBend(Channel &channel, const uint8 *values) {
|
|
|
|
channel.pitchBend = (int8)values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
setupNote(channel.rawNote, channel, true);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_resetToGlobalTempo(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
channel.tempo = _tempo;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_nop(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setDurationRandomness(Channel &channel, const uint8 *values) {
|
|
|
|
channel.durationRandomness = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_changeChannelTempo(Channel &channel, const uint8 *values) {
|
|
|
|
channel.tempo = CLIP(channel.tempo + (int8)values[0], 1, 255);
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::updateCallback46(Channel &channel, const uint8 *values) {
|
|
|
|
uint8 entry = values[1];
|
2020-12-15 21:45:11 +01:00
|
|
|
|
|
|
|
// Safety check: prevent illegal table access
|
2020-12-16 02:21:23 +01:00
|
|
|
if (entry + 2 > _unkTable2Size)
|
2020-12-15 21:45:11 +01:00
|
|
|
return 0;
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
_tablePtr1 = _unkTable2[entry];
|
|
|
|
_tablePtr2 = _unkTable2[entry + 1];
|
|
|
|
if (values[0] == 2) {
|
2019-04-14 21:50:26 +02:00
|
|
|
// Frequency
|
|
|
|
writeOPL(0xA0, _tablePtr2[0]);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setupRhythmSection(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
int channelBackUp = _curChannel;
|
|
|
|
int regOffsetBackUp = _curRegOffset;
|
|
|
|
|
|
|
|
_curChannel = 6;
|
|
|
|
_curRegOffset = _regOffset[6];
|
|
|
|
|
|
|
|
const uint8 *instrument;
|
2021-01-03 23:33:56 +01:00
|
|
|
instrument = getInstrument(values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
if (instrument) {
|
|
|
|
setupInstrument(_curRegOffset, instrument, channel);
|
|
|
|
} else {
|
2021-01-03 23:33:56 +01:00
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 6 specified", values[0]);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
_opLevelBD = channel.opLevel2;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
_curChannel = 7;
|
|
|
|
_curRegOffset = _regOffset[7];
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
instrument = getInstrument(values[1]);
|
2019-04-14 21:50:26 +02:00
|
|
|
if (instrument) {
|
|
|
|
setupInstrument(_curRegOffset, instrument, channel);
|
|
|
|
} else {
|
2021-01-03 23:33:56 +01:00
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 7 specified", values[1]);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
_opLevelHH = channel.opLevel1;
|
|
|
|
_opLevelSD = channel.opLevel2;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
_curChannel = 8;
|
|
|
|
_curRegOffset = _regOffset[8];
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
instrument = getInstrument(values[2]);
|
2019-04-14 21:50:26 +02:00
|
|
|
if (instrument) {
|
|
|
|
setupInstrument(_curRegOffset, instrument, channel);
|
|
|
|
} else {
|
2021-01-03 23:33:56 +01:00
|
|
|
debugC(3, kDebugLevelSound, "AdLibDriver::update_setupRhythmSection: Invalid instrument %d for channel 8 specified", values[2]);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
_opLevelTT = channel.opLevel1;
|
|
|
|
_opLevelCY = channel.opLevel2;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Octave / F-Number / Key-On for channels 6, 7 and 8
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
_channels[6].regBx = values[3] & 0x2F;
|
2019-04-14 21:50:26 +02:00
|
|
|
writeOPL(0xB6, _channels[6].regBx);
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0xA6, values[4]);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
_channels[7].regBx = values[5] & 0x2F;
|
2019-04-14 21:50:26 +02:00
|
|
|
writeOPL(0xB7, _channels[7].regBx);
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0xA7, values[6]);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
_channels[8].regBx = values[7] & 0x2F;
|
2019-04-14 21:50:26 +02:00
|
|
|
writeOPL(0xB8, _channels[8].regBx);
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0xA8, values[8]);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
_rhythmSectionBits = 0x20;
|
|
|
|
|
|
|
|
_curRegOffset = regOffsetBackUp;
|
|
|
|
_curChannel = channelBackUp;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_playRhythmSection(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
// Any instrument that we want to play, and which was already playing,
|
|
|
|
// is temporarily keyed off. Instruments that were off already, or
|
|
|
|
// which we don't want to play, retain their old on/off status. This is
|
|
|
|
// probably so that the instrument's envelope is played from its
|
|
|
|
// beginning again...
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0xBD, (_rhythmSectionBits & ~(values[0] & 0x1F)) | 0x20);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// ...but since we only set the rhythm instrument bits, and never clear
|
|
|
|
// them (until the entire rhythm section is disabled), I'm not sure how
|
|
|
|
// useful the cleverness above is. We could perhaps simply turn off all
|
|
|
|
// the rhythm instruments instead.
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
_rhythmSectionBits |= values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
writeOPL(0xBD, _vibratoAndAMDepthBits | 0x20 | _rhythmSectionBits);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_removeRhythmSection(Channel &channel, const uint8 *values) {
|
2019-04-14 21:50:26 +02:00
|
|
|
_rhythmSectionBits = 0;
|
|
|
|
|
|
|
|
// All the rhythm bits are cleared. The AM and Vibrato depth bits
|
|
|
|
// remain unchanged.
|
|
|
|
|
|
|
|
writeOPL(0xBD, _vibratoAndAMDepthBits);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setRhythmLevel2(Channel &channel, const uint8 *values) {
|
|
|
|
uint8 ops = values[0], v = values[1];
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 1) {
|
|
|
|
_opExtraLevel2HH = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 7, op1: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x51, checkValue(v + _opLevelHH + _opExtraLevel1HH + _opExtraLevel2HH));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 2) {
|
|
|
|
_opExtraLevel2CY = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 8, op2: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x55, checkValue(v + _opLevelCY + _opExtraLevel1CY + _opExtraLevel2CY));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 4) {
|
|
|
|
_opExtraLevel2TT = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 8, op1: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x52, checkValue(v + _opLevelTT + _opExtraLevel1TT + _opExtraLevel2TT));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 8) {
|
|
|
|
_opExtraLevel2SD = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 7, op2: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x54, checkValue(v + _opLevelSD + _opExtraLevel1SD + _opExtraLevel2SD));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 16) {
|
|
|
|
_opExtraLevel2BD = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 6, op2: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x53, checkValue(v + _opLevelBD + _opExtraLevel1BD + _opExtraLevel2BD));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_changeRhythmLevel1(Channel &channel, const uint8 *values) {
|
|
|
|
uint8 ops = values[0], v = values[1];
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 1) {
|
|
|
|
_opExtraLevel1HH = checkValue(v + _opLevelHH + _opExtraLevel1HH + _opExtraLevel2HH);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 7, op1: Level Key Scaling / Total Level
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
writeOPL(0x51, _opExtraLevel1HH);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 2) {
|
|
|
|
_opExtraLevel1CY = checkValue(v + _opLevelCY + _opExtraLevel1CY + _opExtraLevel2CY);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 8, op2: Level Key Scaling / Total Level
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
writeOPL(0x55, _opExtraLevel1CY);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 4) {
|
|
|
|
_opExtraLevel1TT = checkValue(v + _opLevelTT + _opExtraLevel1TT + _opExtraLevel2TT);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 8, op1: Level Key Scaling / Total Level
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
writeOPL(0x52, _opExtraLevel1TT);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 8) {
|
|
|
|
_opExtraLevel1SD = checkValue(v + _opLevelSD + _opExtraLevel1SD + _opExtraLevel2SD);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 7, op2: Level Key Scaling / Total Level
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
writeOPL(0x54, _opExtraLevel1SD);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 16) {
|
|
|
|
_opExtraLevel1BD = checkValue(v + _opLevelBD + _opExtraLevel1BD + _opExtraLevel2BD);
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 6, op2: Level Key Scaling / Total Level
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
writeOPL(0x53, _opExtraLevel1BD);
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setRhythmLevel1(Channel &channel, const uint8 *values) {
|
|
|
|
uint8 ops = values[0], v = values[1];
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 1) {
|
|
|
|
_opExtraLevel1HH = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 7, op1: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x51, checkValue(v + _opLevelHH + _opExtraLevel2HH));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 2) {
|
|
|
|
_opExtraLevel1CY = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 8, op2: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x55, checkValue(v + _opLevelCY + _opExtraLevel2CY));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 4) {
|
|
|
|
_opExtraLevel1TT = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 8, op1: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x52, checkValue(v + _opLevelTT + _opExtraLevel2TT));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 8) {
|
|
|
|
_opExtraLevel1SD = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 7, op2: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x54, checkValue(v + _opLevelSD + _opExtraLevel2SD));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
if (ops & 16) {
|
|
|
|
_opExtraLevel1BD = v;
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
// Channel 6, op2: Level Key Scaling / Total Level
|
2021-01-03 23:33:56 +01:00
|
|
|
writeOPL(0x53, checkValue(v + _opLevelBD + _opExtraLevel2BD));
|
2019-04-14 21:50:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setSoundTrigger(Channel &channel, const uint8 *values) {
|
|
|
|
_soundTrigger = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::update_setTempoReset(Channel &channel, const uint8 *values) {
|
|
|
|
channel.tempoReset = values[0];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-03 23:33:56 +01:00
|
|
|
int AdLibDriver::updateCallback56(Channel &channel, const uint8 *values) {
|
|
|
|
channel.unk39 = values[0];
|
|
|
|
channel.unk40 = values[1];
|
2019-04-14 21:50:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static res
|
|
|
|
|
2020-12-15 21:45:11 +01:00
|
|
|
#define COMMAND(x, n) { &AdLibDriver::x, #x, n }
|
2019-04-14 21:50:26 +02:00
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
const AdLibDriver::ParserOpcode AdLibDriver::_parserOpcodeTable[] = {
|
|
|
|
// 0
|
|
|
|
COMMAND(update_setRepeat, 1),
|
|
|
|
COMMAND(update_checkRepeat, 2),
|
|
|
|
COMMAND(update_setupProgram, 1),
|
|
|
|
COMMAND(update_setNoteSpacing, 1),
|
|
|
|
|
|
|
|
// 4
|
|
|
|
COMMAND(update_jump, 2),
|
|
|
|
COMMAND(update_jumpToSubroutine, 2),
|
|
|
|
COMMAND(update_returnFromSubroutine, 0),
|
|
|
|
COMMAND(update_setBaseOctave, 1),
|
|
|
|
|
|
|
|
// 8
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_playRest, 1),
|
|
|
|
COMMAND(update_writeAdLib, 2),
|
|
|
|
COMMAND(update_setupNoteAndDuration, 2),
|
|
|
|
|
|
|
|
// 12
|
|
|
|
COMMAND(update_setBaseNote, 1),
|
|
|
|
COMMAND(update_setupSecondaryEffect1, 5),
|
|
|
|
COMMAND(update_stopOtherChannel, 1),
|
|
|
|
COMMAND(update_waitForEndOfProgram, 1),
|
|
|
|
|
|
|
|
// 16
|
|
|
|
COMMAND(update_setupInstrument, 1),
|
2020-12-17 02:32:41 +01:00
|
|
|
COMMAND(update_setupPrimaryEffectSlide, 3),
|
|
|
|
COMMAND(update_removePrimaryEffectSlide, 0),
|
2020-12-16 02:21:23 +01:00
|
|
|
COMMAND(update_setBaseFreq, 1),
|
|
|
|
|
|
|
|
// 20
|
|
|
|
COMMAND(update_stopChannel, 0),
|
2020-12-18 00:10:43 +01:00
|
|
|
COMMAND(update_setupPrimaryEffectVibrato, 4),
|
2020-12-16 02:21:23 +01:00
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
|
|
|
|
// 24
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_setPriority, 1),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
|
|
|
|
// 28
|
2020-12-18 19:08:50 +01:00
|
|
|
COMMAND(update_setBeat, 1),
|
|
|
|
COMMAND(update_waitForNextBeat, 1),
|
2020-12-16 02:21:23 +01:00
|
|
|
COMMAND(update_setExtraLevel1, 1),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
|
|
|
|
// 32
|
|
|
|
COMMAND(update_setupDuration, 1),
|
|
|
|
COMMAND(update_playNote, 1),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
|
|
|
|
// 36
|
|
|
|
COMMAND(update_setFractionalNoteSpacing, 1),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_setTempo, 1),
|
|
|
|
COMMAND(update_removeSecondaryEffect1, 0),
|
|
|
|
|
|
|
|
// 40
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_setChannelTempo, 1),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_setExtraLevel3, 1),
|
|
|
|
|
|
|
|
// 44
|
|
|
|
COMMAND(update_setExtraLevel2, 2),
|
|
|
|
COMMAND(update_changeExtraLevel2, 2),
|
|
|
|
COMMAND(update_setAMDepth, 1),
|
|
|
|
COMMAND(update_setVibratoDepth, 1),
|
|
|
|
|
|
|
|
// 48
|
|
|
|
COMMAND(update_changeExtraLevel1, 1),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
2020-12-18 21:16:48 +01:00
|
|
|
COMMAND(update_clearChannel, 1),
|
2020-12-16 02:21:23 +01:00
|
|
|
|
|
|
|
// 52
|
|
|
|
COMMAND(update_stopChannel, 0),
|
2020-12-18 21:33:03 +01:00
|
|
|
COMMAND(update_changeNoteRandomly, 2),
|
2020-12-18 00:10:43 +01:00
|
|
|
COMMAND(update_removePrimaryEffectVibrato, 0),
|
2020-12-16 02:21:23 +01:00
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
|
|
|
|
// 56
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(update_pitchBend, 1),
|
|
|
|
COMMAND(update_resetToGlobalTempo, 0),
|
|
|
|
COMMAND(update_nop, 0),
|
|
|
|
|
|
|
|
// 60
|
|
|
|
COMMAND(update_setDurationRandomness, 1),
|
|
|
|
COMMAND(update_changeChannelTempo, 1),
|
|
|
|
COMMAND(update_stopChannel, 0),
|
|
|
|
COMMAND(updateCallback46, 2),
|
|
|
|
|
|
|
|
// 64
|
|
|
|
COMMAND(update_nop, 0),
|
|
|
|
COMMAND(update_setupRhythmSection, 9),
|
|
|
|
COMMAND(update_playRhythmSection, 1),
|
|
|
|
COMMAND(update_removeRhythmSection, 0),
|
|
|
|
|
|
|
|
// 68
|
KYRA: Name variables/methods for rhythm section volume
Rename updateCallback51(), updateCallback52(), and updateCallback53()
to update_setRhythmLevel2(), update_changeRhythmLevel1(), and
update_setRhythmLevel1(), respectively. Name the variables
_unkValue6, _unkValue7, _unkValue8, _unkValue9, _unkValue10,
_unkValue11, _unkValue12, _unkValue13, _unkValue14, _unkValue15,
_unkValue16, _unkValue17, _unkValue18, _unkValue19, _unkValue20
that hold volume levels for the rhythm instruments.
Note that while update_setRhythmLevel1() behaves as expected (it
sets ExtraLevel1 for a subset of the rhythm intruments and updates
the total level in the OPL chip to the sum of the 3 driver levels)
the other two methods look a bit unusual: update_setRhythmLevel2()
changes ExtraLevel2, but then adds the new value _twice_ to the
total level, and update_changeRhythmLevel1() increases the total
level and stores the new total in ExtraLevel1.
2020-12-18 23:23:43 +01:00
|
|
|
COMMAND(update_setRhythmLevel2, 2),
|
|
|
|
COMMAND(update_changeRhythmLevel1, 2),
|
|
|
|
COMMAND(update_setRhythmLevel1, 2),
|
2020-12-16 02:21:23 +01:00
|
|
|
COMMAND(update_setSoundTrigger, 1),
|
|
|
|
|
|
|
|
// 72
|
|
|
|
COMMAND(update_setTempoReset, 1),
|
|
|
|
COMMAND(updateCallback56, 2),
|
|
|
|
COMMAND(update_stopChannel, 0)
|
|
|
|
};
|
2019-04-14 21:50:26 +02:00
|
|
|
|
|
|
|
#undef COMMAND
|
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
const int AdLibDriver::_parserOpcodeTableSize = ARRAYSIZE(AdLibDriver::_parserOpcodeTable);
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
// This table holds the register offset for operator 1 for each of the nine
|
|
|
|
// channels. To get the register offset for operator 2, simply add 3.
|
|
|
|
|
|
|
|
const uint8 AdLibDriver::_regOffset[] = {
|
|
|
|
0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11,
|
|
|
|
0x12
|
|
|
|
};
|
|
|
|
|
2020-12-15 21:45:11 +01:00
|
|
|
// These are the F-Numbers (10 bits) for the notes of the 12-tone scale.
|
2019-04-14 21:50:26 +02:00
|
|
|
// However, it does not match the table in the AdLib documentation I've seen.
|
|
|
|
|
|
|
|
const uint16 AdLibDriver::_freqTable[] = {
|
|
|
|
0x0134, 0x0147, 0x015A, 0x016F, 0x0184, 0x019C, 0x01B4, 0x01CE, 0x01E9,
|
|
|
|
0x0207, 0x0225, 0x0246
|
|
|
|
};
|
|
|
|
|
|
|
|
// These tables are currently only used by updateCallback46(), which only ever
|
|
|
|
// uses the first element of one of the sub-tables.
|
|
|
|
|
|
|
|
const uint8 *const AdLibDriver::_unkTable2[] = {
|
|
|
|
AdLibDriver::_unkTable2_1,
|
|
|
|
AdLibDriver::_unkTable2_2,
|
|
|
|
AdLibDriver::_unkTable2_1,
|
|
|
|
AdLibDriver::_unkTable2_2,
|
|
|
|
AdLibDriver::_unkTable2_3,
|
|
|
|
AdLibDriver::_unkTable2_2
|
|
|
|
};
|
|
|
|
|
2020-12-16 02:21:23 +01:00
|
|
|
const int AdLibDriver::_unkTable2Size = ARRAYSIZE(AdLibDriver::_unkTable2);
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
const uint8 AdLibDriver::_unkTable2_1[] = {
|
|
|
|
0x50, 0x50, 0x4F, 0x4F, 0x4E, 0x4E, 0x4D, 0x4D,
|
|
|
|
0x4C, 0x4C, 0x4B, 0x4B, 0x4A, 0x4A, 0x49, 0x49,
|
|
|
|
0x48, 0x48, 0x47, 0x47, 0x46, 0x46, 0x45, 0x45,
|
|
|
|
0x44, 0x44, 0x43, 0x43, 0x42, 0x42, 0x41, 0x41,
|
|
|
|
0x40, 0x40, 0x3F, 0x3F, 0x3E, 0x3E, 0x3D, 0x3D,
|
|
|
|
0x3C, 0x3C, 0x3B, 0x3B, 0x3A, 0x3A, 0x39, 0x39,
|
|
|
|
0x38, 0x38, 0x37, 0x37, 0x36, 0x36, 0x35, 0x35,
|
|
|
|
0x34, 0x34, 0x33, 0x33, 0x32, 0x32, 0x31, 0x31,
|
|
|
|
0x30, 0x30, 0x2F, 0x2F, 0x2E, 0x2E, 0x2D, 0x2D,
|
|
|
|
0x2C, 0x2C, 0x2B, 0x2B, 0x2A, 0x2A, 0x29, 0x29,
|
|
|
|
0x28, 0x28, 0x27, 0x27, 0x26, 0x26, 0x25, 0x25,
|
|
|
|
0x24, 0x24, 0x23, 0x23, 0x22, 0x22, 0x21, 0x21,
|
|
|
|
0x20, 0x20, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D,
|
|
|
|
0x1C, 0x1C, 0x1B, 0x1B, 0x1A, 0x1A, 0x19, 0x19,
|
|
|
|
0x18, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15,
|
|
|
|
0x14, 0x14, 0x13, 0x13, 0x12, 0x12, 0x11, 0x11,
|
|
|
|
0x10, 0x10
|
|
|
|
};
|
|
|
|
|
|
|
|
// no don't ask me WHY this table exsits!
|
|
|
|
const uint8 AdLibDriver::_unkTable2_2[] = {
|
|
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
|
|
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
|
|
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|
|
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
|
|
|
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|
|
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
|
|
|
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|
|
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
|
|
|
|
0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|
|
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
|
|
|
0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x6F,
|
|
|
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
|
|
|
|
0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|
|
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
|
|
|
|
0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F
|
|
|
|
};
|
|
|
|
|
|
|
|
const uint8 AdLibDriver::_unkTable2_3[] = {
|
|
|
|
0x40, 0x40, 0x40, 0x3F, 0x3F, 0x3F, 0x3E, 0x3E,
|
|
|
|
0x3E, 0x3D, 0x3D, 0x3D, 0x3C, 0x3C, 0x3C, 0x3B,
|
|
|
|
0x3B, 0x3B, 0x3A, 0x3A, 0x3A, 0x39, 0x39, 0x39,
|
|
|
|
0x38, 0x38, 0x38, 0x37, 0x37, 0x37, 0x36, 0x36,
|
|
|
|
0x36, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x33,
|
|
|
|
0x33, 0x33, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31,
|
|
|
|
0x30, 0x30, 0x30, 0x2F, 0x2F, 0x2F, 0x2E, 0x2E,
|
|
|
|
0x2E, 0x2D, 0x2D, 0x2D, 0x2C, 0x2C, 0x2C, 0x2B,
|
|
|
|
0x2B, 0x2B, 0x2A, 0x2A, 0x2A, 0x29, 0x29, 0x29,
|
|
|
|
0x28, 0x28, 0x28, 0x27, 0x27, 0x27, 0x26, 0x26,
|
|
|
|
0x26, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x23,
|
|
|
|
0x23, 0x23, 0x22, 0x22, 0x22, 0x21, 0x21, 0x21,
|
|
|
|
0x20, 0x20, 0x20, 0x1F, 0x1F, 0x1F, 0x1E, 0x1E,
|
|
|
|
0x1E, 0x1D, 0x1D, 0x1D, 0x1C, 0x1C, 0x1C, 0x1B,
|
|
|
|
0x1B, 0x1B, 0x1A, 0x1A, 0x1A, 0x19, 0x19, 0x19,
|
|
|
|
0x18, 0x18, 0x18, 0x17, 0x17, 0x17, 0x16, 0x16,
|
|
|
|
0x16, 0x15
|
|
|
|
};
|
|
|
|
|
|
|
|
// This table is used to modify the frequency of the notes, depending on the
|
|
|
|
// note value and the pitch bend value. In theory, we could very well try to
|
|
|
|
// access memory outside this table, but in reality that probably won't happen.
|
|
|
|
//
|
|
|
|
|
|
|
|
const uint8 AdLibDriver::_pitchBendTables[][32] = {
|
|
|
|
// 0
|
|
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
|
|
|
|
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
|
|
|
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x19,
|
|
|
|
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21 },
|
|
|
|
// 1
|
|
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x07, 0x09,
|
|
|
|
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11,
|
|
|
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x1A,
|
|
|
|
0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x22, 0x24 },
|
|
|
|
// 2
|
|
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x09,
|
|
|
|
0x0A, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13,
|
|
|
|
0x14, 0x15, 0x16, 0x17, 0x19, 0x1A, 0x1C, 0x1D,
|
|
|
|
0x1E, 0x1F, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26 },
|
|
|
|
// 3
|
|
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A,
|
|
|
|
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x12, 0x13,
|
|
|
|
0x14, 0x15, 0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1D,
|
|
|
|
0x1E, 0x1F, 0x20, 0x21, 0x23, 0x25, 0x27, 0x28 },
|
|
|
|
// 4
|
|
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x08, 0x0A,
|
|
|
|
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11, 0x13, 0x15,
|
|
|
|
0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20,
|
|
|
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x28, 0x2A },
|
|
|
|
// 5
|
|
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B,
|
|
|
|
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15,
|
|
|
|
0x16, 0x17, 0x18, 0x19, 0x1B, 0x1D, 0x1F, 0x20,
|
|
|
|
0x21, 0x22, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D },
|
|
|
|
// 6
|
|
|
|
{ 0x00, 0x01, 0x02, 0x03, 0x05, 0x07, 0x09, 0x0B,
|
|
|
|
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15,
|
|
|
|
0x16, 0x17, 0x18, 0x1A, 0x1C, 0x1E, 0x21, 0x24,
|
|
|
|
0x25, 0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30 },
|
|
|
|
// 7
|
|
|
|
{ 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C,
|
|
|
|
0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x13, 0x15, 0x18,
|
|
|
|
0x19, 0x1A, 0x1C, 0x1D, 0x1F, 0x21, 0x23, 0x25,
|
|
|
|
0x26, 0x27, 0x29, 0x2B, 0x2D, 0x2F, 0x30, 0x32 },
|
|
|
|
// 8
|
|
|
|
{ 0x00, 0x01, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0D,
|
|
|
|
0x0E, 0x0F, 0x10, 0x11, 0x12, 0x14, 0x17, 0x1A,
|
|
|
|
0x19, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x25, 0x28,
|
|
|
|
0x29, 0x2A, 0x2B, 0x2D, 0x2F, 0x31, 0x33, 0x35 },
|
|
|
|
// 9
|
|
|
|
{ 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E,
|
|
|
|
0x0F, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1B,
|
|
|
|
0x1C, 0x1D, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x29,
|
|
|
|
0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x39 },
|
|
|
|
// 10
|
|
|
|
{ 0x00, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0E,
|
|
|
|
0x0F, 0x10, 0x12, 0x14, 0x16, 0x19, 0x1B, 0x1E,
|
|
|
|
0x1F, 0x21, 0x23, 0x25, 0x27, 0x29, 0x2B, 0x2D,
|
|
|
|
0x2E, 0x2F, 0x31, 0x32, 0x34, 0x36, 0x39, 0x3C },
|
|
|
|
// 11
|
|
|
|
{ 0x00, 0x01, 0x03, 0x05, 0x07, 0x0A, 0x0C, 0x0F,
|
|
|
|
0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1E,
|
|
|
|
0x1F, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2B, 0x2E,
|
|
|
|
0x2F, 0x30, 0x32, 0x34, 0x36, 0x39, 0x3C, 0x3F },
|
|
|
|
// 12
|
|
|
|
{ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x10,
|
|
|
|
0x11, 0x12, 0x14, 0x16, 0x18, 0x1B, 0x1E, 0x21,
|
|
|
|
0x22, 0x23, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32,
|
|
|
|
0x33, 0x34, 0x36, 0x38, 0x3B, 0x34, 0x41, 0x44 },
|
|
|
|
// 13
|
|
|
|
{ 0x00, 0x02, 0x04, 0x06, 0x08, 0x0B, 0x0D, 0x11,
|
|
|
|
0x12, 0x13, 0x15, 0x17, 0x1A, 0x1D, 0x20, 0x23,
|
|
|
|
0x24, 0x25, 0x27, 0x29, 0x2C, 0x2F, 0x32, 0x35,
|
|
|
|
0x36, 0x37, 0x39, 0x3B, 0x3E, 0x41, 0x44, 0x47 }
|
|
|
|
};
|
|
|
|
|
2020-01-26 17:45:51 +01:00
|
|
|
PCSoundDriver *PCSoundDriver::createAdLib(Audio::Mixer *mixer, int version) {
|
|
|
|
return new AdLibDriver(mixer, version);
|
|
|
|
}
|
|
|
|
|
2019-04-14 21:50:26 +02:00
|
|
|
} // End of namespace Kyra
|
|
|
|
|
|
|
|
#undef CALLBACKS_PER_SECOND
|