aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Gilbert2016-09-14 20:53:35 -0400
committerPaul Gilbert2016-09-14 20:53:35 -0400
commitb93b8f8245400c937f0d571f9a076d552b6fd0ce (patch)
treecdedf865ebbdaa02755615ac7bbc1a128b55ac07
parent87168baf323c9db3acd90d154848fd4f3a554df6 (diff)
downloadscummvm-rg350-b93b8f8245400c937f0d571f9a076d552b6fd0ce.tar.gz
scummvm-rg350-b93b8f8245400c937f0d571f9a076d552b6fd0ce.tar.bz2
scummvm-rg350-b93b8f8245400c937f0d571f9a076d552b6fd0ce.zip
XEEN: Fleshing out music player
-rw-r--r--engines/xeen/music.cpp551
-rw-r--r--engines/xeen/music.h261
2 files changed, 562 insertions, 250 deletions
diff --git a/engines/xeen/music.cpp b/engines/xeen/music.cpp
index 72ff3866cf..932cca1ccb 100644
--- a/engines/xeen/music.cpp
+++ b/engines/xeen/music.cpp
@@ -27,277 +27,492 @@
namespace Xeen {
-#define CALLBACKS_PER_SECOND 72
+#define CALLBACKS_PER_SECOND 73
-Music::Music(Audio::Mixer *mixer) : _mixer(mixer), _effectsData(nullptr),
- _musicPtr1(nullptr), _musicPtr2(nullptr), _dataPtr(nullptr),
- _lowMusicIgnored(false),
- _fieldF(false), _field1C(false), _field1E(false), _field109(0),
- _field10B(0), _field114(0), _field115(0), _field116(0), _field117(0) {
- _channels.resize(ADLIB_CHANNEL_COUNT);
- Common::fill(&_fieldFB[0], &_fieldFB[7], 0);
- Common::fill(&_field10D[0], &_field10D[7], 0);
+/*------------------------------------------------------------------------*/
- _mixer = mixer;
- _opl = OPL::Config::create();
- _opl->init();
- _opl->start(new Common::Functor0Mem<void, Music>(this, &Music::onTimer), CALLBACKS_PER_SECOND);
- initialize();
-
- loadEffectsData();
+MusicDriver::MusicDriver() : _fieldF(false), _field1E(false), _lowMusicIgnored(false),
+ _musCountdownTimer(0), _fxCountdownTimer(0), _musDataPtr(nullptr),
+ _fxDataPtr(nullptr), _fxStartPtr(nullptr), _musStartPtr(nullptr) {
+ Common::fill(&_flags[0], &_flags[CHANNEL_COUNT], false);
+ Common::fill(&_field15C[0], &_field15C[CHANNEL_COUNT], 0);
+ Common::fill(&_field165[0], &_field165[CHANNEL_COUNT], 0);
+ Common::fill(&_field177[0], &_field177[CHANNEL_COUNT], 0);
}
-Music::~Music() {
- _opl->stop();
- delete _opl;
- delete[] _effectsData;
+void MusicDriver::execute() {
+ bool isFX = false;
+ bool flag = !_field1E;
+ const byte *srcP = _musDataPtr;
+
+ if (!flag) {
+ if (_musCountdownTimer && --_musCountdownTimer == 0)
+ flag = true;
+ }
+ if (flag && _lowMusicIgnored) {
+ srcP = _fxDataPtr;
+ isFX = true;
+ if (!_fxCountdownTimer || --_fxCountdownTimer == 0)
+ flag = false;
+ }
+
+ if (flag) {
+ postProcess();
+ return;
+ }
+
+ // Main loop
+ bool breakFlag = false;
+ while (!breakFlag) {
+ byte nextByte = *srcP++;
+ int cmd = (nextByte >> 4) & 15;
+ int param = (nextByte & 15);
+
+ CommandFn fn = isFX ? FX_COMMANDS[cmd] : MUSIC_COMMANDS[cmd];
+ breakFlag = (this->*fn)(srcP, param);
+ }
}
-void Music::initialize() {
- write(1, 0x20);
- write(8, 0);
- write(0xBD, 0);
- resetFrequencies();
- reset();
+bool MusicDriver::musCallSubroutine(const byte *&srcP, byte param) {
+ if (_musSubroutines.size() < 16) {
+ const byte *returnP = srcP + 2;
+ srcP = _musStartPtr + READ_LE_UINT16(srcP);
+
+ _musSubroutines.push(Subroutine(returnP, srcP));
+ }
+
+ return false;
}
-void Music::loadEffectsData() {
- File file("admus");
- Common::String md5str = Common::computeStreamMD5AsString(file, 8192);
-
- if (md5str != "be8989a5e868913f0e53963046e3ea13")
- error("Unknown music driver encountered");
+bool MusicDriver::musSetCountdown(const byte *&srcP, byte param) {
+ // Set the countdown timer
+ if (!param)
+ param = *++srcP;
+ _musCountdownTimer = param;
+ _musDataPtr = srcP;
- // Load in the driver data
- const int EFFECTS_OFFSET = 0x91D;
- byte *effectsData = new byte[file.size() - EFFECTS_OFFSET];
- file.seek(EFFECTS_OFFSET);
- file.read(effectsData, file.size() - EFFECTS_OFFSET);
- file.close();
- _effectsData = effectsData;
+ // Do post-processing and stop processing
+ postProcess();
+ return true;
+}
- // Extract the effects offsets
- _effectsOffsets.resize(180);
- for (int idx = 0; idx < 180; ++idx)
- _effectsOffsets[idx] = READ_LE_UINT16(&effectsData[idx * 2]) - EFFECTS_OFFSET;
+bool MusicDriver::cmdNoOperation(const byte *&srcP, byte param) {
+ return false;
}
-void Music::onTimer() {
- Common::StackLock slock(_driverMutex);
- update();
- flush();
+bool MusicDriver::musSkipWord(const byte *&srcP, byte param) {
+ srcP += 2;
+ return false;
}
-void Music::write(int reg, int val) {
- _queue.push(RegisterValue(reg, val));
+
+bool MusicDriver::cmdClearFlag(const byte *&srcP, byte param) {
+ _flags[param] = false;
+ return false;
}
-void Music::flush() {
- Common::StackLock slock(_driverMutex);
+bool MusicDriver::cmdWibbly(const byte *&srcP, byte param) {
+ if (param != 7 || !_fieldF) {
+ _field15C[param] = *srcP++;
+ _field177[param] = 0xFF;
+ _flags[param] = true;
+ _field165[param] = READ_BE_UINT16(srcP);
+ srcP += 2;
+ } else {
+ srcP += 3;
+ }
- while (!_queue.empty()) {
- RegisterValue v = _queue.pop();
- _opl->writeReg(v._regNum, v._value);
+ return true;
+}
+
+bool MusicDriver::musEndSubroutine(const byte *&srcP, byte param) {
+ if (param != 15) {
+ _field1E = 0;
+ return true;
}
+
+ srcP = _musSubroutines.empty() ? _musStartPtr : _musSubroutines.pop()._returnP;
+ return false;
}
-void Music::update() {
- const byte *srcP = _dataPtr;
+bool MusicDriver::fxCallSubroutine(const byte *&srcP, byte param) {
+ if (_fxSubroutines.size() < 16) {
+ const byte *startP = srcP + 2;
+ srcP = _musStartPtr + READ_LE_UINT16(srcP);
- bool flag = !_field1E;
- if (!flag) {
- _field1C = 0;
- if (_field116 && --_field116 == 0)
- flag = true;
- }
- if (flag && _lowMusicIgnored) {
- srcP = _musicPtr1;
- _field1C = 1;
- if (!_field117 || --_field117 == 0)
- flag = false;
+ _fxSubroutines.push(Subroutine(startP, srcP));
}
- if (flag) {
- postProcess();
- return;
- }
+ return false;
+}
- // Main loop
- bool breakFlag = false;
- while (!breakFlag) {
- byte nextByte = *srcP++;
- int cmd = (nextByte >> 3) & 15;
+bool MusicDriver::fxSetCountdown(const byte *&srcP, byte param) {
+ // Set the countdown timer
+ if (!param)
+ param = *++srcP;
+ _fxCountdownTimer = param;
+ _musDataPtr = srcP;
+
+ // Do post-processing and stop processing
+ postProcess();
+ return true;
+}
- CommandFn fn = (_field1C == 1) ? COMMAND_TABLE2[cmd] : COMMAND_TABLE1[cmd];
- breakFlag = (this->*fn)(srcP, nextByte);
+bool MusicDriver::fxEndSubroutine(const byte *&srcP, byte param) {
+ if (param != 15) {
+ _lowMusicIgnored = false;
+ return true;
}
+
+ srcP = _fxSubroutines.empty() ? _fxStartPtr : _fxSubroutines.pop()._returnP;
+ return false;
}
-void Music::playEffect(uint effectId) {
+void MusicDriver::playFX(uint effectId, const byte *data) {
if (!_lowMusicIgnored || effectId < 7 || effectId >= 11) {
- if (effectId < _effectsOffsets.size()) {
- _musicPtr1 = _musicPtr2 = &_effectsData[_effectsOffsets[effectId]];
- _field117 = 0;
- _field115 = 0;
- _field114 = 0;
- reset();
- _lowMusicIgnored = true;
- }
+ _musStartPtr = nullptr;
+ _fxDataPtr = _fxStartPtr = data;
+ _fxCountdownTimer = 0;
+ _flags[7] = _flags[8] = 0;
+ resetFX();
+ _lowMusicIgnored = true;
+ }
+}
+
+
+const CommandFn MusicDriver::MUSIC_COMMANDS[16] = {
+ &MusicDriver::musCallSubroutine, &MusicDriver::musSetCountdown,
+ &MusicDriver::musSetInstrument, &MusicDriver::cmdNoOperation,
+ &MusicDriver::musSetPitchWheel, &MusicDriver::musSkipWord,
+ &MusicDriver::musSetPanning, &MusicDriver::cmdNoOperation,
+ &MusicDriver::musFade, &MusicDriver::musStartNote,
+ &MusicDriver::musSetVolume, &MusicDriver::musInjectMidi,
+ &MusicDriver::musPlayInstrument, &MusicDriver::cmdClearFlag,
+ &MusicDriver::cmdWibbly, &MusicDriver::musEndSubroutine
+};
+
+const CommandFn MusicDriver::FX_COMMANDS[16] = {
+ &MusicDriver::fxCallSubroutine, &MusicDriver::fxSetCountdown,
+ &MusicDriver::fxSetInstrument, &MusicDriver::fxSetVolume,
+ &MusicDriver::fxMidiReset, &MusicDriver::fxMidiDword,
+ &MusicDriver::fxSetPanning, &MusicDriver::fxChannelOff,
+ &MusicDriver::fxFade, &MusicDriver::fxStartNote,
+ &MusicDriver::cmdNoOperation, &MusicDriver::fxInjectMidi,
+ &MusicDriver::fxPlayInstrument, &MusicDriver::cmdClearFlag,
+ &MusicDriver::cmdWibbly, &MusicDriver::fxEndSubroutine
+};
+
+/*------------------------------------------------------------------------*/
+
+AdlibMusicDriver::AdlibMusicDriver() : _field180(0), _field182(0), _volume(127) {
+ Common::fill(&_musInstrumentPtrs[0], &_musInstrumentPtrs[16], (const byte *)nullptr);
+ Common::fill(&_fxInstrumentPtrs[0], &_fxInstrumentPtrs[16], (const byte *)nullptr);
+ Common::fill(&_frequencies[0], &_frequencies[7], 0);
+ Common::fill(&_volumes[0], &_volumes[CHANNEL_COUNT], 0);
+ Common::fill(&_scalingValues[0], &_scalingValues[CHANNEL_COUNT], 0);
+
+ _opl = OPL::Config::create();
+ _opl->init();
+ _opl->start(new Common::Functor0Mem<void, AdlibMusicDriver>(this, &AdlibMusicDriver::onTimer), CALLBACKS_PER_SECOND);
+ initialize();
+}
+
+AdlibMusicDriver::~AdlibMusicDriver() {
+ _opl->stop();
+ delete _opl;
+}
+
+void AdlibMusicDriver::onTimer() {
+ Common::StackLock slock(_driverMutex);
+ execute();
+ flush();
+}
+
+void AdlibMusicDriver::initialize() {
+ write(1, 0x20);
+ write(8, 0);
+ write(0xBD, 0);
+
+ resetFrequencies();
+ AdlibMusicDriver::resetFX();
+}
+
+void AdlibMusicDriver::write(int reg, int val) {
+ _queue.push(RegisterValue(reg, val));
+}
+
+void AdlibMusicDriver::flush() {
+ Common::StackLock slock(_driverMutex);
+
+ while (!_queue.empty()) {
+ RegisterValue v = _queue.pop();
+ _opl->writeReg(v._regNum, v._value);
}
}
-void Music::reset() {
+void AdlibMusicDriver::resetFX() {
if (!_fieldF) {
- _field109 = 0;
+ _frequencies[7] = 0;
setFrequency(7, 0);
- _channels[7]._outputLevel = 63;
+ _volumes[7] = 63;
setOutputLevel(7, 63);
}
- _field10B = 0;
+ _frequencies[8] = 0;
setFrequency(8, 0);
- _channels[8]._outputLevel = 63;
+ _volumes[8] = 63;
setOutputLevel(8, 63);
}
-void Music::resetFrequencies() {
+void AdlibMusicDriver::resetFrequencies() {
for (int opNum = 6; opNum >= 0; --opNum) {
- _fieldFB[opNum] = 0;
+ _frequencies[opNum] = 0;
setFrequency(opNum, 0);
}
}
-void Music::setFrequency(byte operatorNum, uint frequency) {
+void AdlibMusicDriver::setFrequency(byte operatorNum, uint frequency) {
write(0xA0 + operatorNum, frequency & 0xff);
write(0xB0 + operatorNum, (frequency >> 8));
}
-void Music::postProcess() {
- // TODO
+uint AdlibMusicDriver::calcFrequency(byte note) {
+ return WAVEFORMS[note & 0x1F] + ((note & 0xE0) << 5);
}
-bool Music::cmd1(const byte *&srcP, byte nextByte) {
- return false; // TODO
+void AdlibMusicDriver::setOutputLevel(byte channelNum, uint level) {
+ write(0x40 + OPERATOR2_INDEXES[channelNum], level |
+ (_scalingValues[channelNum] & 0xC0));
}
-bool Music::cmd2(const byte *&srcP, byte nextByte) {
- return false; // TODO
-}
+void AdlibMusicDriver::playInstrument(byte channelNum, const byte *data) {
+ byte op1 = OPERATOR1_INDEXES[channelNum];
+ byte op2 = OPERATOR2_INDEXES[channelNum];
-bool Music::cmd3(const byte *&srcP, byte nextByte) {
- return false; // TODO
-}
+ write(0x20 + op1, *data++);
+ write(0x40 + op1, *data++);
+ write(0x60 + op1, *data++);
+ write(0x80 + op1, *data++);
+ write(0xE0 + op1, *data++);
+ write(0x20 + op2, *data++);
-bool Music::cmd4(const byte *&srcP, byte nextByte) {
- return false; // TODO
-}
+ int scalingVal = *data++;
+ _scalingValues[channelNum] = scalingVal;
+ scalingVal += (127 - _volume) / 2;
-bool Music::cmd5(const byte *&srcP, byte nextByte) {
- return false; // TODO
-}
+ if (scalingVal > 63) {
+ scalingVal = 63;
+ if (_field180)
+ scalingVal = (scalingVal & 0xC0) | _field182;
+ }
+ write(0x40 + op2, scalingVal);
-bool Music::cmd6(const byte *&srcP, byte nextByte) {
- return false; // TODO
+ write(0x60 + op2, *data++);
+ write(0x80 + op2, *data++);
+ write(0xE0 + op2, *data++);
+ write(0xC0 + op2, *data++);
}
-bool Music::cmd7(const byte *&srcP, byte nextByte) {
- return false; // TODO
-}
+bool AdlibMusicDriver::musSetInstrument(const byte *&srcP, byte param) {
+ _musInstrumentPtrs[param] = srcP;
+ srcP += 26;
-bool Music::cmd8(const byte *&srcP, byte nextByte) {
- return false; // TODO
+ return false;
}
-bool Music::cmd9(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::musSetPitchWheel(const byte *&srcP, byte param) {
+ // Adlib does not support this
+ srcP += 2;
+ return false;
}
-bool Music::cmd10(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::musSetPanning(const byte *&srcP, byte param) {
+ // Adlib does not support this
+ ++srcP;
+ return false;
}
-bool Music::cmd11(const byte *&srcP, byte nextByte) {
- return false; // TODO
-}
+bool AdlibMusicDriver::musFade(const byte *&srcP, byte param) {
+ ++srcP;
+ if (param < 7)
+ setFrequency(param, _frequencies[param]);
-bool Music::cmd12(const byte *&srcP, byte nextByte) {
- return false; // TODO
+ return false;
}
-bool Music::cmd13(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::musStartNote(const byte *&srcP, byte param) {
+ if (param < 7) {
+ byte note = *srcP++;
+ ++srcP; // Second byte is fade, which is unused by Adlib
+ uint freq = calcFrequency(note);
+ setFrequency(param, freq);
+ _frequencies[param] = freq | 0x2000;
+ setFrequency(param, freq);
+ } else {
+ srcP += 2;
+ }
+
+ return false;
}
-bool Music::cmd14(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::musSetVolume(const byte *&srcP, byte param) {
+ if (*srcP++ == 2 && !_field180) {
+ _volumes[param] = *srcP;
+ setOutputLevel(param, *srcP);
+ }
+
+ ++srcP;
+ return false;
}
-bool Music::cmd15(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::musInjectMidi(const byte *&srcP, byte param) {
+ // Adlib does not support MIDI. So simply keep skipping over bytes
+ // until an 'F7' byte is found that flags the end of the MIDI data
+ while (*srcP++ != 0xF7)
+ ;
+
+ return false;
}
-bool Music::cmd16(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::musPlayInstrument(const byte *&srcP, byte param) {
+ if (param < 7)
+ playInstrument(param, _musInstrumentPtrs[param]);
+
+ return false;
}
-bool Music::cmd17(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxSetInstrument(const byte *&srcP, byte param) {
+ _fxInstrumentPtrs[param] = srcP;
+ srcP += 11;
+
+ return false;
}
-bool Music::cmd18(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxSetVolume(const byte *&srcP, byte param) {
+ if (!_field180 && (!_fieldF || param != 7)) {
+ _volumes[param] = *srcP;
+ setOutputLevel(param, *srcP);
+ }
+
+ ++srcP;
+ return false;
}
-bool Music::cmd19(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxMidiReset(const byte *&srcP, byte param) {
+ return false;
}
-bool Music::cmd20(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxMidiDword(const byte *&srcP, byte param) {
+ return false;
}
-bool Music::cmd21(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxSetPanning(const byte *&srcP, byte param) {
+ byte note = *srcP++;
+ if (!_fieldF || param != 7) {
+ uint freq = calcFrequency(note);
+ setFrequency(param, freq);
+ _frequencies[param] = freq;
+ }
+
+ return false;
}
-bool Music::cmd22(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxChannelOff(const byte *&srcP, byte param) {
+ _frequencies[param] &= ~0x2000;
+ write(0xB0 + param, _frequencies[param]);
+ return false;
}
-bool Music::cmd23(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxFade(const byte *&srcP, byte param) {
+ uint freq = calcFrequency(*srcP++);
+ if (!_fieldF || param != 7) {
+ _frequencies[param] = freq;
+ setFrequency(param, freq);
+ }
+
+ return false;
}
-bool Music::cmd24(const byte *&srcP, byte nextByte) {
- return false; // TODO
+bool AdlibMusicDriver::fxStartNote(const byte *&srcP, byte param) {
+ if (!_fieldF || param != 7) {
+ byte note = *srcP++;
+ uint freq = calcFrequency(note);
+ setFrequency(param, freq);
+ _frequencies[param] = freq | 0x2000;
+ setFrequency(param, freq);
+ } else {
+ ++srcP;
+ }
+
+ return false;
}
-const CommandFn Music::COMMAND_TABLE1[16] = {
- &Music::cmd1, &Music::cmd2, &Music::cmd3, &Music::cmd4,
- &Music::cmd5, &Music::cmd5, &Music::cmd6, &Music::cmd4,
- &Music::cmd7, &Music::cmd8, &Music::cmd9, &Music::cmd10,
- &Music::cmd11, &Music::cmd12, &Music::cmd13, &Music::cmd14
-};
+bool AdlibMusicDriver::fxInjectMidi(const byte *&srcP, byte param) {
+ // Surpringly, unlike the musInjectMidi, this version doesn't have
+ // any logic to skip over following MIDI data. Which must mean the opcode
+ // and/or it's data aren't present in the admus driver file
+ return false;
+}
-const CommandFn Music::COMMAND_TABLE2[16] = {
- &Music::cmd15, &Music::cmd16, &Music::cmd17, &Music::cmd18,
- &Music::cmd4, &Music::cmd4, &Music::cmd19, &Music::cmd20,
- &Music::cmd21, &Music::cmd22, &Music::cmd4, &Music::cmd4,
- &Music::cmd23, &Music::cmd12, &Music::cmd13, &Music::cmd24
-};
+bool AdlibMusicDriver::fxPlayInstrument(const byte *&srcP, byte param) {
+ if (!_fieldF || param != 7)
+ playInstrument(param, _fxInstrumentPtrs[param]);
-void Music::setOutputLevel(byte channelNum, uint level) {
- write(0x40 + OPERATOR2_INDEXES[channelNum], level |
- (_channels[channelNum]._scalingValue & 0xC0));
+ return false;
}
-const byte Music::OPERATOR1_INDEXES[ADLIB_CHANNEL_COUNT] = {
+const byte AdlibMusicDriver::OPERATOR1_INDEXES[CHANNEL_COUNT] = {
0, 1, 2, 8, 9, 0xA, 0x10, 0x11, 0x12
};
-const byte Music::OPERATOR2_INDEXES[ADLIB_CHANNEL_COUNT] = {
+const byte AdlibMusicDriver::OPERATOR2_INDEXES[CHANNEL_COUNT] = {
3, 4, 5, 0xB, 0xC, 0xD, 0x13, 0x14, 0x15
};
+const uint AdlibMusicDriver::WAVEFORMS[24] = {
+ 0, 347, 388, 436, 462, 519, 582, 646,
+ 0, 362, 406, 455, 484, 542, 607, 680,
+ 0, 327, 367, 412, 436, 489, 549, 618
+};
+
+/*------------------------------------------------------------------------*/
+
+Music::Music(Audio::Mixer *mixer) : _mixer(mixer), _musicDriver(nullptr) {
+ _mixer = mixer;
+ loadEffectsData();
+}
+
+Music::~Music() {
+ delete[] _effectsData;
+}
+
+void Music::loadEffectsData() {
+ File file("admus");
+ Common::String md5str = Common::computeStreamMD5AsString(file, 8192);
+
+ if (md5str != "be8989a5e868913f0e53963046e3ea13")
+ error("Unknown music driver encountered");
+
+ // Load in the driver data
+ byte *effectsData = new byte[file.size()];
+ file.seek(0);
+ file.read(effectsData, file.size());
+ file.close();
+ _effectsData = effectsData;
+
+ // Extract the effects offsets
+ _effectsOffsets.resize(180);
+ const int EFFECTS_OFFSET = 0x91D;
+ for (int idx = 0; idx < 180; ++idx)
+ _effectsOffsets[idx] = READ_LE_UINT16(&effectsData[EFFECTS_OFFSET + idx * 2]);
+}
+
+void Music::playEffect(uint effectId) {
+ if (effectId < _effectsOffsets.size()) {
+ const byte *dataP = &_effectsData[_effectsOffsets[effectId]];
+ _musicDriver->playFX(effectId, dataP);
+ }
+}
+
} // End of namespace Xeen
diff --git a/engines/xeen/music.h b/engines/xeen/music.h
index b1fa43daa2..0d5188e697 100644
--- a/engines/xeen/music.h
+++ b/engines/xeen/music.h
@@ -28,8 +28,9 @@
#include "common/array.h"
#include "common/mutex.h"
#include "common/queue.h"
+#include "common/stack.h"
-#define ADLIB_CHANNEL_COUNT 9
+#define CHANNEL_COUNT 9
namespace OPL {
class OPL;
@@ -37,52 +38,132 @@ namespace OPL {
namespace Xeen {
-class Music;
+class MusicDriver;
-typedef bool (Music::*CommandFn)(const byte *&srcP, byte nextByte);
+typedef bool (MusicDriver::*CommandFn)(const byte *&srcP, byte param);
-struct RegisterValue {
- uint8 _regNum;
- uint8 _value;
+/**
+ * Base class for music drivers
+ */
+class MusicDriver {
+ struct Subroutine {
+ const byte *_returnP;
+ const byte *_jumpP;
+ Subroutine() : _returnP(nullptr), _jumpP(nullptr) {}
+ Subroutine(const byte *returnP, const byte *endP) :
+ _returnP(returnP), _jumpP(endP) {}
+ };
+private:
+ static const CommandFn FX_COMMANDS[16];
+ static const CommandFn MUSIC_COMMANDS[16];
+private:
+ Common::Stack<Subroutine> _musSubroutines, _fxSubroutines;
+ bool _field1E;
+ int _musCountdownTimer;
+ int _fxCountdownTimer;
+ bool _lowMusicIgnored;
+ const byte *_fxDataPtr, *_musDataPtr;
+ const byte *_fxStartPtr;
+ const byte *_musStartPtr;
+ bool _flags[CHANNEL_COUNT];
+ byte _field15C[CHANNEL_COUNT];
+ byte _field165[CHANNEL_COUNT];
+ byte _field177[CHANNEL_COUNT];
+private:
+ /**
+ * Executes the next command
+ * @param srcP Command data pointer
+ * @returns If true, execution of commands for the current timer call stops
+ */
+ bool command(const byte *&srcP);
+protected:
+ bool _fieldF;
+protected:
+ /**
+ * Executes a series of commands until instructed to stop
+ */
+ void execute();
+
+ // Music commands (with some also used by FX)
+ virtual bool musCallSubroutine(const byte *&srcP, byte param);
+ virtual bool musSetCountdown(const byte *&srcP, byte param);
+ virtual bool musSetInstrument(const byte *&srcP, byte param) = 0;
+ virtual bool cmdNoOperation(const byte *&srcP, byte param);
+ virtual bool musSetPitchWheel(const byte *&srcP, byte param) = 0;
+ virtual bool musSkipWord(const byte *&srcP, byte param);
+ virtual bool musSetPanning(const byte *&srcP, byte param) = 0;
+ virtual bool musFade(const byte *&srcP, byte param) = 0;
+ virtual bool musStartNote(const byte *&srcP, byte param) = 0;
+ virtual bool musSetVolume(const byte *&srcP, byte param) = 0;
+ virtual bool musInjectMidi(const byte *&srcP, byte param) = 0;
+ virtual bool musPlayInstrument(const byte *&srcP, byte param) = 0;
+ virtual bool cmdClearFlag(const byte *&srcP, byte param);
+ virtual bool cmdWibbly(const byte *&srcP, byte param);
+ virtual bool musEndSubroutine(const byte *&srcP, byte param);
+
+ // FX commands
+ virtual bool fxCallSubroutine(const byte *&srcP, byte param);
+ virtual bool fxSetCountdown(const byte *&srcP, byte param);
+ virtual bool fxSetInstrument(const byte *&srcP, byte param) = 0;
+ virtual bool fxSetVolume(const byte *&srcP, byte param) = 0;
+ virtual bool fxMidiReset(const byte *&srcP, byte param) = 0;
+ virtual bool fxMidiDword(const byte *&srcP, byte param) = 0;
+ virtual bool fxSetPanning(const byte *&srcP, byte param) = 0;
+ virtual bool fxChannelOff(const byte *&srcP, byte param) = 0;
+ virtual bool fxFade(const byte *&srcP, byte param) = 0;
+ virtual bool fxStartNote(const byte *&srcP, byte param) = 0;
+ virtual bool fxInjectMidi(const byte *&srcP, byte param) = 0;
+ virtual bool fxPlayInstrument(const byte *&srcP, byte param) = 0;
+ virtual bool fxEndSubroutine(const byte *&srcP, byte param);
- RegisterValue(int regNum, int value) {
- _regNum = regNum; _value = value;
- }
+ virtual void postProcess() = 0;
+
+ /**
+ * Does a reset of any sound effect
+ */
+ virtual void resetFX() = 0;
+public:
+ /**
+ * Constructor
+ */
+ MusicDriver();
+
+ /**
+ * Destructor
+ */
+ virtual ~MusicDriver() {}
+
+ /**
+ * Starts an special effect playing
+ */
+ void playFX(uint effectId, const byte *data);
};
-class Music {
- struct Channel {
- byte _outputLevel;
- byte _scalingValue;
+class AdlibMusicDriver : public MusicDriver {
+ struct RegisterValue {
+ uint8 _regNum;
+ uint8 _value;
- Channel() : _outputLevel(0), _scalingValue(0) {}
+ RegisterValue(int regNum, int value) {
+ _regNum = regNum; _value = value;
+ }
};
private:
- static const byte OPERATOR1_INDEXES[ADLIB_CHANNEL_COUNT];
- static const byte OPERATOR2_INDEXES[ADLIB_CHANNEL_COUNT];
- static const CommandFn COMMAND_TABLE1[16];
- static const CommandFn COMMAND_TABLE2[16];
+ static const byte OPERATOR1_INDEXES[CHANNEL_COUNT];
+ static const byte OPERATOR2_INDEXES[CHANNEL_COUNT];
+ static const uint WAVEFORMS[24];
private:
OPL::OPL *_opl;
- Common::Mutex _driverMutex;
- Common::Array<Channel> _channels;
Common::Queue<RegisterValue> _queue;
- const byte *_effectsData;
- Common::Array<uint16> _effectsOffsets;
- const byte *_musicPtr1, *_musicPtr2;
- const byte *_dataPtr;
- bool _fieldF;
- bool _field1C;
- bool _field1E;
- uint _fieldFB[7];
- int _field109;
- int _field10B;
- byte _field10D[7];
- int _field114;
- int _field115;
- int _field116;
- int _field117;
- bool _lowMusicIgnored;
+ Common::Mutex _driverMutex;
+ byte _volumes[CHANNEL_COUNT];
+ byte _scalingValues[CHANNEL_COUNT];
+ const byte *_musInstrumentPtrs[16];
+ const byte *_fxInstrumentPtrs[16];
+ uint _frequencies[7];
+ int _field180;
+ int _field182;
+ int _volume;
private:
/**
* Initializes the state of the Adlib OPL driver
@@ -90,11 +171,6 @@ private:
void initialize();
/**
- * Loads effects data that was embedded in the music driver
- */
- void loadEffectsData();
-
- /**
* Adds a register write to the pending queue that will be flushed
* out to the OPL on the next timer call
*/
@@ -111,16 +187,6 @@ private:
void flush();
/**
- * Updates any playing music
- */
- void update();
-
- /**
- * Does a reset
- */
- void reset();
-
- /**
* Resets all the output frequencies
*/
void resetFrequencies();
@@ -131,42 +197,73 @@ private:
void setFrequency(byte operatorNum, uint frequency);
/**
+ * Calculates the frequency for a note
+ */
+ uint calcFrequency(byte note);
+
+ /**
* Sets the output level for a channel
*/
void setOutputLevel(byte channelNum, uint level);
/**
- * Post-process
- */
- void postProcess();
-
- /**
- * Update command methods
- */
- bool cmd1(const byte *&srcP, byte nextByte);
- bool cmd2(const byte *&srcP, byte nextByte);
- bool cmd3(const byte *&srcP, byte nextByte);
- bool cmd4(const byte *&srcP, byte nextByte);
- bool cmd5(const byte *&srcP, byte nextByte);
- bool cmd6(const byte *&srcP, byte nextByte);
- bool cmd7(const byte *&srcP, byte nextByte);
- bool cmd8(const byte *&srcP, byte nextByte);
- bool cmd9(const byte *&srcP, byte nextByte);
- bool cmd10(const byte *&srcP, byte nextByte);
- bool cmd11(const byte *&srcP, byte nextByte);
- bool cmd12(const byte *&srcP, byte nextByte);
- bool cmd13(const byte *&srcP, byte nextByte);
- bool cmd14(const byte *&srcP, byte nextByte);
- bool cmd15(const byte *&srcP, byte nextByte);
- bool cmd16(const byte *&srcP, byte nextByte);
- bool cmd17(const byte *&srcP, byte nextByte);
- bool cmd18(const byte *&srcP, byte nextByte);
- bool cmd19(const byte *&srcP, byte nextByte);
- bool cmd20(const byte *&srcP, byte nextByte);
- bool cmd21(const byte *&srcP, byte nextByte);
- bool cmd22(const byte *&srcP, byte nextByte);
- bool cmd23(const byte *&srcP, byte nextByte);
- bool cmd24(const byte *&srcP, byte nextByte);
+ * Starts playing an instrument
+ */
+ void playInstrument(byte channelNum, const byte *data);
+protected:
+ virtual bool musSetInstrument(const byte *&srcP, byte param);
+ virtual bool musSetPitchWheel(const byte *&srcP, byte param);
+ virtual bool musSetPanning(const byte *&srcP, byte param);
+ virtual bool musFade(const byte *&srcP, byte param);
+ virtual bool musStartNote(const byte *&srcP, byte param);
+ virtual bool musSetVolume(const byte *&srcP, byte param);
+ virtual bool musInjectMidi(const byte *&srcP, byte param);
+ virtual bool musPlayInstrument(const byte *&srcP, byte param);
+
+ virtual bool fxSetInstrument(const byte *&srcP, byte param);
+ virtual bool fxSetVolume(const byte *&srcP, byte param);
+ virtual bool fxMidiReset(const byte *&srcP, byte param);
+ virtual bool fxMidiDword(const byte *&srcP, byte param);
+ virtual bool fxSetPanning(const byte *&srcP, byte param);
+ virtual bool fxChannelOff(const byte *&srcP, byte param);
+ virtual bool fxFade(const byte *&srcP, byte param);
+ virtual bool fxStartNote(const byte *&srcP, byte param);
+ virtual bool fxInjectMidi(const byte *&srcP, byte param);
+ virtual bool fxPlayInstrument(const byte *&srcP, byte param);
+
+ /**
+ * Does a reset of any sound effect
+ */
+ virtual void resetFX();
+public:
+ /**
+ * Constructor
+ */
+ AdlibMusicDriver();
+
+ /**
+ * Destructor
+ */
+ virtual ~AdlibMusicDriver();
+};
+
+
+class Music {
+private:
+ MusicDriver *_musicDriver;
+ const byte *_effectsData;
+ Common::Array<uint16> _effectsOffsets;
+private:
+ /**
+ * Loads effects data that was embedded in the music driver
+ */
+ void loadEffectsData();
+
+ /**
+ * Updates any playing music
+ */
+ void update();
+
protected:
Audio::Mixer *_mixer;
public: