diff options
author | Johannes Schickel | 2015-07-23 22:33:56 +0200 |
---|---|---|
committer | Johannes Schickel | 2015-07-23 22:33:56 +0200 |
commit | 979a885ef9785afbed650dfffc8c5af6af8ea8e5 (patch) | |
tree | 547e6abcaf02bb687902d83104a98496c73cf800 /engines/agos/drivers/simon1 | |
parent | 773305498c672ce528eddc8bfd5fb5702d6abb5e (diff) | |
download | scummvm-rg350-979a885ef9785afbed650dfffc8c5af6af8ea8e5.tar.gz scummvm-rg350-979a885ef9785afbed650dfffc8c5af6af8ea8e5.tar.bz2 scummvm-rg350-979a885ef9785afbed650dfffc8c5af6af8ea8e5.zip |
AGOS: Add initial version of Simon1 DOS AdLib output.
Testing so far has not really happened. Only the first part of the intro has
been tested.
Diffstat (limited to 'engines/agos/drivers/simon1')
-rw-r--r-- | engines/agos/drivers/simon1/adlib.cpp | 494 | ||||
-rw-r--r-- | engines/agos/drivers/simon1/adlib.h | 116 |
2 files changed, 610 insertions, 0 deletions
diff --git a/engines/agos/drivers/simon1/adlib.cpp b/engines/agos/drivers/simon1/adlib.cpp new file mode 100644 index 0000000000..d331b868fc --- /dev/null +++ b/engines/agos/drivers/simon1/adlib.cpp @@ -0,0 +1,494 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "agos/drivers/simon1/adlib.h" + +#include "common/textconsole.h" +#include "common/util.h" + +namespace AGOS { + +enum { + kChannelUnused = 0xFF, + kChannelOrphanedFlag = 0x80, + + kOPLVoicesCount = 9 +}; + +MidiDriver_Simon1_AdLib::Voice::Voice() + : channel(kChannelUnused), note(0), instrTotalLevel(0), instrScalingLevel(0), frequency(0) { +} + +MidiDriver_Simon1_AdLib::MidiDriver_Simon1_AdLib(const byte *instrumentData) + : _isOpen(false), _opl(nullptr), _timerProc(nullptr), _timerParam(nullptr), + _melodyVoices(0), _amvdrBits(0), _rhythmEnabled(false), _voices(), _midiPrograms(), + _instruments(instrumentData) { +} + +MidiDriver_Simon1_AdLib::~MidiDriver_Simon1_AdLib() { + close(); + delete[] _instruments; +} + +int MidiDriver_Simon1_AdLib::open() { + if (_isOpen) { + return MERR_ALREADY_OPEN; + } + + _opl = OPL::Config::create(); + if (!_opl) { + return MERR_DEVICE_NOT_AVAILABLE; + } + + if (!_opl->init()) { + delete _opl; + _opl = nullptr; + + return MERR_CANNOT_CONNECT; + } + + _opl->start(new Common::Functor0Mem<void, MidiDriver_Simon1_AdLib>(this, &MidiDriver_Simon1_AdLib::onTimer)); + + _opl->writeReg(0x01, 0x20); + _opl->writeReg(0x08, 0x40); + _opl->writeReg(0xBD, 0xC0); + reset(); + + _isOpen = true; + return 0; +} + +bool MidiDriver_Simon1_AdLib::isOpen() const { + return _isOpen; +} + +void MidiDriver_Simon1_AdLib::close() { + setTimerCallback(nullptr, nullptr); + + if (_isOpen) { + _opl->stop(); + delete _opl; + _opl = nullptr; + + _isOpen = false; + } +} + +void MidiDriver_Simon1_AdLib::send(uint32 b) { + int channel = b & 0x0F; + int command = b & 0xF0; + int param1 = (b >> 8) & 0xFF; + int param2 = (b >> 16) & 0xFF; + + // The percussion channel is handled specially. The AdLib output uses + // channels 11 to 15 for percussions. For this, the original converted + // note on on the percussion channel to note on channels 11 to 15 before + // giving it to the AdLib output. We do this in here for simplicity. + if (command == 0x90 && channel == 9) { + param1 -= 36; + if (param1 < 0 || param1 >= ARRAYSIZE(_rhythmMap)) { + return; + } + + channel = _rhythmMap[param1].channel; + MidiDriver::send(0xC0 | channel, _rhythmMap[param1].program, 0); + + param1 = _rhythmMap[param1].note; + MidiDriver::send(0x80 | channel, param1, param2); + + param2 >>= 1; + } + + switch (command) { + case 0x80: // note OFF + noteOff(channel, param1); + break; + + case 0x90: // note ON + if (param2 == 0) { + noteOff(channel, param1); + } else { + noteOn(channel, param1, param2); + } + break; + + case 0xB0: // control change + controlChange(channel, param1, param2); + break; + + case 0xC0: // program change + programChange(channel, param1); + break; + + default: + break; + } +} + +void MidiDriver_Simon1_AdLib::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + _timerParam = timer_param; + _timerProc = timer_proc; +} + +uint32 MidiDriver_Simon1_AdLib::getBaseTempo() { + return 1000000 / OPL::OPL::kDefaultCallbackFrequency; +} + +void MidiDriver_Simon1_AdLib::onTimer() { + if (_timerProc) { + (*_timerProc)(_timerParam); + } +} + +void MidiDriver_Simon1_AdLib::reset() { + resetOPLVoices(); + resetRhythm(); + for (int i = 0; i < kNumberOfVoices; ++i) { + _voices[i].channel = kChannelUnused; + } + resetVoices(); +} + +void MidiDriver_Simon1_AdLib::resetOPLVoices() { + _amvdrBits &= 0xE0; + _opl->writeReg(0xBD, _amvdrBits); + for (int i = 8; i >= 0; --i) { + _opl->writeReg(0xB0 + i, 0); + } +} + +void MidiDriver_Simon1_AdLib::resetRhythm() { + _melodyVoices = 9; + _amvdrBits = 0xC0; + _opl->writeReg(0xBD, _amvdrBits); +} + +void MidiDriver_Simon1_AdLib::resetVoices() { + memset(_midiPrograms, 0, sizeof(_midiPrograms)); + for (int i = 0; i < kNumberOfVoices; ++i) { + _voices[i].channel = kChannelUnused; + } + + for (int i = 0; i < kOPLVoicesCount; ++i) { + resetRhythm(); + _opl->writeReg(0x08, 0x00); + + int oplRegister = _operatorMap[i]; + for (int j = 0; j < 4; ++j) { + oplRegister += 0x20; + + _opl->writeReg(oplRegister + 0, _operatorDefaults[2 * j + 0]); + _opl->writeReg(oplRegister + 3, _operatorDefaults[2 * j + 1]); + } + + _opl->writeReg(oplRegister + 0x60, 0x00); + _opl->writeReg(oplRegister + 0x63, 0x00); + + // This seems to be serious bug but the original does it the same way. + _opl->writeReg(_operatorMap[i] + i, 0x08); + } +} + +int MidiDriver_Simon1_AdLib::allocateVoice(uint channel) { + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].channel == (channel | kChannelOrphanedFlag)) { + return i; + } + } + + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].channel == kChannelUnused) { + return i; + } + } + + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].channel > 0x7F) { + return i; + } + } + + // The original had some logic for a priority based reuse of channels. + // However, the priority value is always 0, which causes the first channel + // to be picked all the time. + const int voice = 0; + _opl->writeReg(0xA0 + voice, (_voices[voice].frequency ) & 0xFF); + _opl->writeReg(0xB0 + voice, (_voices[voice].frequency >> 8) & 0xFF); + return voice; +} + +void MidiDriver_Simon1_AdLib::noteOff(uint channel, uint note) { + if (_melodyVoices <= 6 && channel >= 11) { + _amvdrBits &= ~(_rhythmInstrumentMask[channel - 11]); + _opl->writeReg(0xBD, _amvdrBits); + } else { + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].note == note && _voices[i].channel == channel) { + _voices[i].channel |= kChannelOrphanedFlag; + _opl->writeReg(0xA0 + i, (_voices[i].frequency ) & 0xFF); + _opl->writeReg(0xB0 + i, (_voices[i].frequency >> 8) & 0xFF); + return; + } + } + } +} + +void MidiDriver_Simon1_AdLib::noteOn(uint channel, uint note, uint velocity) { + if (_rhythmEnabled && channel >= 11) { + noteOnRhythm(channel, note, velocity); + return; + } + + const int voiceNum = allocateVoice(channel); + Voice &voice = _voices[voiceNum]; + + if ((voice.channel & 0x7F) != channel) { + setupInstrument(voiceNum, _midiPrograms[channel]); + } + voice.channel = channel; + + _opl->writeReg(0x43 + _operatorMap[voiceNum], (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel); + + voice.note = note; + if (note >= 0x80) { + note = 0; + } + + const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note]; + const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F]; + + uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2); + uint lowByte = frequency & 0x00FF; + voice.frequency = (highByte << 8) | lowByte; + + _opl->writeReg(0xA0 + voiceNum, lowByte); + _opl->writeReg(0xB0 + voiceNum, highByte | 0x20); +} + +void MidiDriver_Simon1_AdLib::noteOnRhythm(uint channel, uint note, uint velocity) { + const uint voiceNum = channel - 5; + Voice &voice = _voices[voiceNum]; + + _amvdrBits |= _rhythmInstrumentMask[voiceNum - 6]; + + const uint level = (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel; + if (voiceNum == 6) { + _opl->writeReg(0x43 + _rhythmOperatorMap[voiceNum - 6], level); + } else { + _opl->writeReg(0x40 + _rhythmOperatorMap[voiceNum - 6], level); + } + + voice.note = note; + if (note >= 0x80) { + note = 0; + } + + const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note]; + const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F]; + + uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2); + uint lowByte = frequency & 0x00FF; + voice.frequency = (highByte << 8) | lowByte; + + const uint oplOperator = _rhythmVoiceMap[voiceNum - 6]; + _opl->writeReg(0xA0 + oplOperator, lowByte); + _opl->writeReg(0xB0 + oplOperator, highByte); + + _opl->writeReg(0xBD, _amvdrBits); +} + +void MidiDriver_Simon1_AdLib::controlChange(uint channel, uint controller, uint value) { + // Enable/Disable Rhythm Section + if (controller == 0x67) { + resetVoices(); + _rhythmEnabled = (value != 0); + + if (_rhythmEnabled) { + _melodyVoices = 6; + _amvdrBits = 0xE0; + } else { + _melodyVoices = 9; + _amvdrBits = 0xC0; + } + + _voices[6].channel = kChannelUnused; + _voices[7].channel = kChannelUnused; + _voices[8].channel = kChannelUnused; + + _opl->writeReg(0xBD, _amvdrBits); + } +} + +void MidiDriver_Simon1_AdLib::programChange(uint channel, uint program) { + _midiPrograms[channel] = program; + + if (_rhythmEnabled && channel >= 11) { + setupInstrument(channel - 5, program); + } else { + // Fully unallocate all previously allocated but now unused voices for + // this MIDI channel. + for (uint i = 0; i < kOPLVoicesCount; ++i) { + if (_voices[i].channel == (channel | kChannelOrphanedFlag)) { + _voices[i].channel = kChannelUnused; + } + } + + // Set the program for all voices allocted for this MIDI channel. + for (uint i = 0; i < kOPLVoicesCount; ++i) { + if (_voices[i].channel == channel) { + setupInstrument(i, program); + } + } + } +} + +void MidiDriver_Simon1_AdLib::setupInstrument(uint voice, uint instrument) { + const byte *instrumentData = _instruments + instrument * 16; + + int scaling = instrumentData[3]; + if (_rhythmEnabled && voice >= 7) { + scaling = instrumentData[2]; + } + + const int scalingLevel = scaling & 0xC0; + const int totalLevel = scaling & 0x3F; + + _voices[voice].instrScalingLevel = scalingLevel; + _voices[voice].instrTotalLevel = (-(totalLevel - 0x3F)) & 0xFF; + + if (!_rhythmEnabled || voice <= 6) { + int oplRegister = _operatorMap[voice]; + for (int j = 0; j < 4; ++j) { + oplRegister += 0x20; + _opl->writeReg(oplRegister + 0, *instrumentData++); + _opl->writeReg(oplRegister + 3, *instrumentData++); + } + oplRegister += 0x60; + _opl->writeReg(oplRegister + 0, *instrumentData++); + _opl->writeReg(oplRegister + 3, *instrumentData++); + + _opl->writeReg(0xC0 + voice, *instrumentData++); + } else { + voice -= 7; + + int oplRegister = _rhythmOperatorMap[voice + 1]; + for (int j = 0; j < 4; ++j) { + oplRegister += 0x20; + _opl->writeReg(oplRegister + 0, *instrumentData++); + ++instrumentData; + } + oplRegister += 0x60; + _opl->writeReg(oplRegister + 0, *instrumentData++); + ++instrumentData; + + _opl->writeReg(0xC0 + _rhythmVoiceMap[voice + 1], *instrumentData++); + } +} + +const int MidiDriver_Simon1_AdLib::_operatorMap[9] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, + 0x12 +}; + +const int MidiDriver_Simon1_AdLib::_operatorDefaults[8] = { + 0x01, 0x11, 0x4F, 0x00, 0xF1, 0xF2, 0x53, 0x74 +}; + +const int MidiDriver_Simon1_AdLib::_rhythmOperatorMap[5] = { + 0x10, 0x14, 0x12, 0x15, 0x11 +}; + +const uint MidiDriver_Simon1_AdLib::_rhythmInstrumentMask[5] = { + 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +const int MidiDriver_Simon1_AdLib::_rhythmVoiceMap[5] = { + 6, 7, 8, 8, 7 +}; + +const int MidiDriver_Simon1_AdLib::_frequencyIndexAndOctaveTable[128] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x60, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, + 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B +}; + +const int MidiDriver_Simon1_AdLib::_frequencyTable[16] = { + 0x0157, 0x016B, 0x0181, 0x0198, 0x01B0, 0x01CA, 0x01E5, 0x0202, + 0x0220, 0x0241, 0x0263, 0x0287, 0x2100, 0xD121, 0xA307, 0x46A4 +}; + +const MidiDriver_Simon1_AdLib::RhythmMap MidiDriver_Simon1_AdLib::_rhythmMap[39] = { + { 11, 123, 40 }, + { 12, 127, 50 }, + { 12, 124, 1 }, + { 12, 124, 90 }, + { 13, 125, 50 }, + { 13, 125, 25 }, + { 15, 127, 80 }, + { 13, 125, 25 }, + { 15, 127, 40 }, + { 13, 125, 35 }, + { 15, 127, 90 }, + { 13, 125, 35 }, + { 13, 125, 45 }, + { 14, 126, 90 }, + { 13, 125, 45 }, + { 15, 127, 90 }, + { 0, 0, 0 }, + { 15, 127, 60 }, + { 0, 0, 0 }, + { 13, 125, 60 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 13, 125, 45 }, + { 13, 125, 40 }, + { 13, 125, 35 }, + { 13, 125, 30 }, + { 13, 125, 25 }, + { 13, 125, 80 }, + { 13, 125, 40 }, + { 13, 125, 80 }, + { 13, 125, 40 }, + { 14, 126, 40 }, + { 15, 127, 60 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 14, 126, 80 }, + { 0, 0, 0 }, + { 13, 125, 100 } +}; + +} // End of namespace AGOS diff --git a/engines/agos/drivers/simon1/adlib.h b/engines/agos/drivers/simon1/adlib.h new file mode 100644 index 0000000000..b92c1dde24 --- /dev/null +++ b/engines/agos/drivers/simon1/adlib.h @@ -0,0 +1,116 @@ +/* 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. + * + */ + +#ifndef AGOS_SIMON1_ADLIB_H +#define AGOS_SIMON1_ADLIB_H + +#include "audio/mididrv.h" +#include "audio/fmopl.h" + +namespace AGOS { + +class MidiDriver_Simon1_AdLib : public MidiDriver { +public: + MidiDriver_Simon1_AdLib(const byte *instrumentData); + virtual ~MidiDriver_Simon1_AdLib(); + + // MidiDriver API + virtual int open(); + virtual bool isOpen() const; + virtual void close(); + + virtual void send(uint32 b); + + virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); + virtual uint32 getBaseTempo(); + + virtual MidiChannel *allocateChannel() { return 0; } + virtual MidiChannel *getPercussionChannel() { return 0; } +private: + bool _isOpen; + + OPL::OPL *_opl; + + Common::TimerManager::TimerProc _timerProc; + void *_timerParam; + void onTimer(); + + void reset(); + void resetOPLVoices(); + + void resetRhythm(); + int _melodyVoices; + uint8 _amvdrBits; + bool _rhythmEnabled; + + enum { + kNumberOfVoices = 11, + kNumberOfMidiChannels = 16 + }; + + struct Voice { + Voice(); + + uint channel; + uint note; + uint instrTotalLevel; + uint instrScalingLevel; + uint frequency; + }; + + void resetVoices(); + int allocateVoice(uint channel); + + Voice _voices[kNumberOfVoices]; + uint _midiPrograms[kNumberOfMidiChannels]; + + void noteOff(uint channel, uint note); + void noteOn(uint channel, uint note, uint velocity); + void noteOnRhythm(uint channel, uint note, uint velocity); + void controlChange(uint channel, uint controller, uint value); + void programChange(uint channel, uint program); + + void setupInstrument(uint voice, uint instrument); + const byte *_instruments; + + static const int _operatorMap[9]; + static const int _operatorDefaults[8]; + + static const int _rhythmOperatorMap[5]; + static const uint _rhythmInstrumentMask[5]; + static const int _rhythmVoiceMap[5]; + + static const int _frequencyIndexAndOctaveTable[128]; + static const int _frequencyTable[16]; + + struct RhythmMap { + int channel; + int program; + int note; + }; + + static const RhythmMap _rhythmMap[39]; +}; + +} // End of namespace AGOS + +#endif |