diff options
author | Willem Jan Palenstijn | 2015-07-22 22:37:40 +0200 |
---|---|---|
committer | Willem Jan Palenstijn | 2015-07-22 22:43:42 +0200 |
commit | 6ec9c81b575f13b2c4b30aeac592ebf2557b5890 (patch) | |
tree | 503d50902bad2d800165593039d08d5ccf0c98ab /engines/agos/drivers/accolade/adlib.cpp | |
parent | 5ec05f6b647c5ea41418be7ed19ad381f97cabd8 (diff) | |
parent | 4e5c8d35f7e133e2e72a846fdbd54900c91eeb73 (diff) | |
download | scummvm-rg350-6ec9c81b575f13b2c4b30aeac592ebf2557b5890.tar.gz scummvm-rg350-6ec9c81b575f13b2c4b30aeac592ebf2557b5890.tar.bz2 scummvm-rg350-6ec9c81b575f13b2c4b30aeac592ebf2557b5890.zip |
Merge branch 'master' into mm
Conflicts:
engines/access/access.cpp
engines/access/asurface.h
engines/access/bubble_box.cpp
engines/access/bubble_box.h
engines/access/martian/martian_game.cpp
engines/access/player.cpp
engines/access/player.h
engines/access/resources.cpp
engines/access/screen.cpp
engines/access/screen.h
engines/access/sound.cpp
engines/access/sound.h
Diffstat (limited to 'engines/agos/drivers/accolade/adlib.cpp')
-rw-r--r-- | engines/agos/drivers/accolade/adlib.cpp | 883 |
1 files changed, 883 insertions, 0 deletions
diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp new file mode 100644 index 0000000000..294be2b8a7 --- /dev/null +++ b/engines/agos/drivers/accolade/adlib.cpp @@ -0,0 +1,883 @@ +/* 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/agos.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" + +namespace AGOS { + +#define AGOS_ADLIB_VOICES_COUNT 11 +#define AGOS_ADLIB_VOICES_MELODIC_COUNT 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_START 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_COUNT 5 +#define AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL 9 + +// 5 instruments on top of the regular MIDI ones +// used by the MUSIC.DRV variant for percussion instruments +#define AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT 5 + +const byte operator1Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11 +}; + +const byte operator2Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// percussion: +// voice 6 - base drum - also uses operator 13h +// voice 7 - snare drum +// voice 8 - tom tom +// voice 9 - cymbal +// voice 10 - hi hat +const byte percussionBits[AGOS_ADLIB_VOICES_PERCUSSION_COUNT] = { + 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +// hardcoded, dumped from Accolade music system +// same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently +const byte percussionKeyNoteChannelTable[] = { + 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0A, 0x08, 0x0A, 0x08, + 0x0A, 0x08, 0x08, 0x09, 0x08, 0x09, 0x0F, 0x0F, 0x0A, 0x0F, + 0x0A, 0x0F, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x0A, 0x0F, 0x0F, 0x08, 0x0F, 0x08 +}; + +struct InstrumentEntry { + byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op1; // Level Key Scaling / Total Level + byte reg60op1; // Attack Rate / Decay Rate + byte reg80op1; // Sustain Level / Release Rate + byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op2; // Level Key Scaling / Total Level + byte reg60op2; // Attack Rate / Decay Rate + byte reg80op2; // Sustain Level / Release Rate + byte regC0; // Feedback / Algorithm, bit 0 - set -> both operators in use +}; + +// hardcoded, dumped from Accolade music system (INSTR.DAT variant) +const uint16 frequencyLookUpTable[12] = { + 0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF, + 0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B +}; + +// hardcoded, dumped from Accolade music system (MUSIC.DRV variant) +const uint16 frequencyLookUpTableMusicDrv[12] = { + 0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2, 0x02DB, + 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF +}; + +// +// Accolade adlib music driver +// +// Remarks: +// +// There are at least 2 variants of this sound system. +// One for the games Elvira 1 + Elvira 2 +// It seems it was also used for the game "Altered Destiny" +// Another one for the games Waxworks + Simon, the Sorcerer 1 Demo +// +// First one uses the file INSTR.DAT for instrument data, channel mapping etc. +// Second one uses the file MUSIC.DRV, which actually contains driver code + instrument data + channel mapping, etc. +// +// The second variant supported dynamic channel allocation for the FM voice channels, but this +// feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too. +// +// I have currently not implemented dynamic channel allocation. + +class MidiDriver_Accolade_AdLib : public MidiDriver { +public: + MidiDriver_Accolade_AdLib(); + virtual ~MidiDriver_Accolade_AdLib(); + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); + + void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); + +private: + bool _musicDrvMode; + + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel + byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments + byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + // from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument + signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT]; + // simple mapping between MIDI key notes and MT32 key notes + byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT]; + + // from INSTR.DAT/MUSIC.DRV - adlib instrument data + InstrumentEntry *_instrumentTable; + byte _instrumentCount; + + struct ChannelEntry { + const InstrumentEntry *currentInstrumentPtr; + byte currentNote; + byte currentA0hReg; + byte currentB0hReg; + int16 volumeAdjust; + + ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0), + currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { } + }; + + byte _percussionReg; + + OPL::OPL *_opl; + int _masterVolume; + + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + + bool _isOpen; + + // stores information about all FM voice channels + ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT]; + + void onTimer(); + + void resetAdLib(); + void resetAdLibOperatorRegisters(byte baseRegister, byte value); + void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value); + + void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void setRegister(int reg, int value); + void noteOn(byte FMvoiceChannel, byte note, byte velocity); + void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte adjustedVelocity); + void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote); +}; + +MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib() + : _masterVolume(15), _opl(0), + _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) { + memset(_channelMapping, 0, sizeof(_channelMapping)); + memset(_instrumentMapping, 0, sizeof(_instrumentMapping)); + memset(_instrumentVolumeAdjust, 0, sizeof(_instrumentVolumeAdjust)); + memset(_percussionKeyNoteMapping, 0, sizeof(_percussionKeyNoteMapping)); + + _instrumentTable = NULL; + _instrumentCount = 0; + _musicDrvMode = false; + _percussionReg = 0x20; +} + +MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() { + if (_instrumentTable) { + delete[] _instrumentTable; + _instrumentCount = 0; + } +} + +int MidiDriver_Accolade_AdLib::open() { +// debugC(kDebugLevelAdLibDriver, "AdLib: starting driver"); + + _opl = OPL::Config::create(OPL::Config::kOpl2); + + if (!_opl) + return -1; + + _opl->init(); + + _isOpen = true; + + _opl->start(new Common::Functor0Mem<void, MidiDriver_Accolade_AdLib>(this, &MidiDriver_Accolade_AdLib::onTimer)); + + resetAdLib(); + + // Finally set up default instruments + for (byte FMvoiceNr = 0; FMvoiceNr < AGOS_ADLIB_VOICES_COUNT; FMvoiceNr++) { + if (FMvoiceNr < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voices with instrument 0 + programChangeSetInstrument(FMvoiceNr, 0, 0); + } else { + byte percussionInstrument; + if (!_musicDrvMode) { + // INSTR.DAT: percussion voices with instrument 1, 2, 3, 4 and 5 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion voices with instrument 0x80, 0x81, 0x82, 0x83 and 0x84 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + programChangeSetInstrument(FMvoiceNr, percussionInstrument, percussionInstrument); + } + } + + // driver initialization does this here: + // INSTR.DAT + // noteOn(9, 0x29, 0); + // noteOff(9, 0x26, false); + // MUSIC.DRV + // noteOn(9, 0x26, 0); + // noteOff(9, 0x26, false); + + return 0; +} + +void MidiDriver_Accolade_AdLib::close() { + delete _opl; + _isOpen = false; +} + +void MidiDriver_Accolade_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +void MidiDriver_Accolade_AdLib::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); +} + +void MidiDriver_Accolade_AdLib::resetAdLib() { + // The original driver sent 0x00 to register 0x00 up to 0xF5 + setRegister(0xBD, 0x00); // Disable rhythm + + // reset FM voice instrument data + resetAdLibOperatorRegisters(0x20, 0); + resetAdLibOperatorRegisters(0x60, 0); + resetAdLibOperatorRegisters(0x80, 0); + resetAdLibFMVoiceChannelRegisters(0xA0, 0); + resetAdLibFMVoiceChannelRegisters(0xB0, 0); + resetAdLibFMVoiceChannelRegisters(0xC0, 0); + resetAdLibOperatorRegisters(0xE0, 0); + resetAdLibOperatorRegisters(0x40, 0x3F); // original driver sent 0x00 + + setRegister(0x01, 0x20); // enable waveform control on both operators + setRegister(0x04, 0x60); // Timer control + + setRegister(0x08, 0); // select FM music mode + setRegister(0xBD, 0x20); // Enable rhythm + + // reset our percussion register + _percussionReg = 0x20; +} + +void MidiDriver_Accolade_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) { + byte operatorIndex; + + for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) { + switch (operatorIndex) { + case 0x06: + case 0x07: + case 0x0E: + case 0x0F: + break; + default: + setRegister(baseRegister + operatorIndex, value); + } + } +} + +void MidiDriver_Accolade_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) { + byte FMvoiceChannel; + + for (FMvoiceChannel = 0; FMvoiceChannel < AGOS_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + setRegister(baseRegister + FMvoiceChannel, value); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Accolade_AdLib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + byte mappedChannel = _channelMapping[channel]; + byte mappedInstrument = 0; + + // Ignore everything that is outside of our channel range + if (mappedChannel >= AGOS_ADLIB_VOICES_COUNT) + return; + + switch (command) { + case 0x80: + noteOff(mappedChannel, op1, false); + break; + case 0x90: + // Convert noteOn with velocity 0 to a noteOff + if (op2 == 0) + return noteOff(mappedChannel, op1, false); + + noteOn(mappedChannel, op1, op2); + break; + case 0xb0: // Control change + // Doesn't seem to be implemented + break; + case 0xc0: // Program Change + mappedInstrument = _instrumentMapping[op1]; + programChange(mappedChannel, mappedInstrument, op1); + break; + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + // Aftertouch doesn't seem to be implemented + break; + case 0xe0: + // No pitch bend change + break; + case 0xf0: // SysEx + warning("ADLIB: SysEx: %x", b); + break; + default: + warning("ADLIB: Unknown event %02x", command); + } +} + +void MidiDriver_Accolade_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + +void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velocity) { + byte adjustedNote = note; + byte adjustedVelocity = velocity; + byte regValueA0h = 0; + byte regValueB0h = 0; + + // adjust velocity + int16 channelVolumeAdjust = _channels[FMvoiceChannel].volumeAdjust; + channelVolumeAdjust += adjustedVelocity; + channelVolumeAdjust = CLIP<int16>(channelVolumeAdjust, 0, 0x7F); + + // TODO: adjust to global volume + // original drivers had a global volume variable, which was 0 for full volume, -64 for half volume + // and -128 for mute + + adjustedVelocity = channelVolumeAdjust; + + if (!_musicDrvMode) { + // INSTR.DAT + // force note-off + noteOff(FMvoiceChannel, note, true); + + } else { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // force note-off, but only for actual FM voice channels + noteOff(FMvoiceChannel, note, true); + } + } + + if (FMvoiceChannel != 9) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + } else { + // percussion channel + // MUSIC.DRV variant didn't do this adjustment, it directly used a pointer + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel >= AGOS_ADLIB_VOICES_COUNT) + return; // INSTR.DAT variant checked for ">" instead of ">=", which seems to have been a bug + + // Map the keynote accordingly + adjustedNote = _percussionKeyNoteMapping[adjustedNote]; + // Now overwrite the FM voice channel + FMvoiceChannel = percussionChannel; + } + + if (!_musicDrvMode) { + // INSTR.DAT + + // Save this key note + _channels[FMvoiceChannel].currentNote = adjustedNote; + + adjustedVelocity += 24; + if (adjustedVelocity > 120) + adjustedVelocity = 120; + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + + } else { + // MUSIC.DRV + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + } + + // Set volume of voice channel + noteOnSetVolume(FMvoiceChannel, 1, adjustedVelocity); + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Set second operator for FM voices + first percussion + noteOnSetVolume(FMvoiceChannel, 2, adjustedVelocity); + } + + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Percussion + byte percussionIdx = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Enable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg |= percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL) { + // FM voice, Base Drum, Snare Drum + Tom Tom + byte adlibNote = adjustedNote; + byte adlibOctave = 0; + byte adlibFrequencyIdx = 0; + uint16 adlibFrequency = 0; + + if (!_musicDrvMode) { + // INSTR.DAT + if (adlibNote >= 0x60) + adlibNote = 0x5F; + + adlibOctave = (adlibNote / 12) - 1; + adlibFrequencyIdx = adlibNote % 12; + adlibFrequency = frequencyLookUpTable[adlibFrequencyIdx]; + + if (adlibFrequency & 0x8000) + adlibOctave++; + if (adlibOctave & 0x80) { + adlibOctave++; + adlibFrequency = adlibFrequency >> 1; + } + + } else { + // MUSIC.DRV variant + if (adlibNote >= 19) + adlibNote -= 19; + + adlibOctave = (adlibNote / 12); + adlibFrequencyIdx = adlibNote % 12; + // additional code, that will lookup octave and do a multiplication with it + // noteOn however calls the frequency calculation in a way that it multiplies with 0 + adlibFrequency = frequencyLookUpTableMusicDrv[adlibFrequencyIdx]; + } + + regValueA0h = adlibFrequency & 0xFF; + regValueB0h = ((adlibFrequency & 0x300) >> 8) | (adlibOctave << 2); + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Key-On flag for regular FM voices, but not for percussion + regValueB0h |= 0x20; + } + + setRegister(0xA0 + FMvoiceChannel, regValueA0h); + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + _channels[FMvoiceChannel].currentA0hReg = regValueA0h; + _channels[FMvoiceChannel].currentB0hReg = regValueB0h; + + if (_musicDrvMode) { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_MELODIC_COUNT) { + _channels[FMvoiceChannel].currentNote = adjustedNote; + } + } + } +} + +// 100% the same for INSTR.DAT and MUSIC.DRV variants +// except for a bug, that was introduced for MUSIC.DRV +void MidiDriver_Accolade_AdLib::noteOnSetVolume(byte FMvoiceChannel, byte operatorNr, byte adjustedVelocity) { + byte operatorReg = 0; + byte regValue40h = 0; + const InstrumentEntry *curInstrument = NULL; + + regValue40h = (63 - adjustedVelocity) & 0x3F; + + if ((operatorNr == 1) && (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START)) { + // first operator of FM voice channels or first percussion channel + curInstrument = _channels[FMvoiceChannel].currentInstrumentPtr; + if (!(curInstrument->regC0 & 0x01)) { // check, if both operators produce sound + // only one does, instrument wants fixed volume + if (operatorNr == 1) { + regValue40h = curInstrument->reg40op1; + } else { + regValue40h = curInstrument->reg40op2; + } + + // not sure, if we are supposed to implement these bugs, or not +#if 0 + if (!_musicDrvMode) { + // Table is 16 bytes instead of 18 bytes + if ((FMvoiceChannel == 7) || (FMvoiceChannel == 9)) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } + if (_musicDrvMode) { + // MUSIC.DRV variant has a bug, which will overwrite these registers + // for all operators above 11 / 0Bh, which means percussion will always + // get a value of 0 (the table holding those bytes was 12 bytes instead of 18 + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } +#endif + } + } + + if (operatorNr == 1) { + operatorReg = operator1Register[FMvoiceChannel]; + } else { + operatorReg = operator2Register[FMvoiceChannel]; + } + assert(operatorReg != 0xFF); // Security check + setRegister(0x40 + operatorReg, regValue40h); +} + +void MidiDriver_Accolade_AdLib::noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote) { + byte adjustedNote = note; + byte regValueB0h = 0; + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + if (!dontCheckNote) { + // check, if current note is also the current actually playing channel note + if (_channels[FMvoiceChannel].currentNote != adjustedNote) + return; // not the same -> ignore this note off command + } + + regValueB0h = _channels[FMvoiceChannel].currentB0hReg & 0xDF; // Remove "key on" bit + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + + } else { + // percussion + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel > AGOS_ADLIB_VOICES_COUNT) + return; + + byte percussionIdx = percussionChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Disable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg &= ~percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } +} + +void MidiDriver_Accolade_AdLib::programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + //warning("ADLIB: program change for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voice + programChangeSetInstrument(FMvoiceChannel, mappedInstrumentNr, MIDIinstrumentNr); + + } else { + // Percussion + // set default instrument (again) + byte percussionInstrumentNr = 0; + const InstrumentEntry *instrumentPtr; + + if (!_musicDrvMode) { + // INSTR.DAT: percussion default instruments start at instrument 1 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion default instruments start at instrument 0x80 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + if (percussionInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; + } + instrumentPtr = &_instrumentTable[percussionInstrumentNr]; + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[percussionInstrumentNr]; + } +} + +void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + const InstrumentEntry *instrumentPtr; + byte op1Reg = 0; + byte op2Reg = 0; + + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + instrumentPtr = &_instrumentTable[mappedInstrumentNr]; + //warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + op1Reg = operator1Register[FMvoiceChannel]; + op2Reg = operator2Register[FMvoiceChannel]; + + setRegister(0x20 + op1Reg, instrumentPtr->reg20op1); + setRegister(0x40 + op1Reg, instrumentPtr->reg40op1); + setRegister(0x60 + op1Reg, instrumentPtr->reg60op1); + setRegister(0x80 + op1Reg, instrumentPtr->reg80op1); + + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set 2nd operator as well for FM voices and first percussion voice + setRegister(0x20 + op2Reg, instrumentPtr->reg20op2); + setRegister(0x40 + op2Reg, instrumentPtr->reg40op2); + setRegister(0x60 + op2Reg, instrumentPtr->reg60op2); + setRegister(0x80 + op2Reg, instrumentPtr->reg80op2); + + if (!_musicDrvMode) { + // set Feedback / Algorithm as well + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } else { + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Feedback / Algorithm as well for regular FM voices only + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } + } + } + + // Remember instrument + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[MIDIinstrumentNr]; +} + +void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) { + _opl->writeReg(reg, value); + //warning("OPL %x %x (%d)", reg, value, value); +} + +uint32 MidiDriver_Accolade_AdLib::property(int prop, uint32 param) { + return 0; +} + +// Called right at the start, we get an INSTR.DAT entry +bool MidiDriver_Accolade_AdLib::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + uint16 channelMappingOffset = 0; + uint16 channelMappingSize = 0; + uint16 instrumentMappingOffset = 0; + uint16 instrumentMappingSize = 0; + uint16 instrumentVolumeAdjustOffset = 0; + uint16 instrumentVolumeAdjustSize = 0; + uint16 keyNoteMappingOffset = 0; + uint16 keyNoteMappingSize = 0; + uint16 instrumentCount = 0; + uint16 instrumentDataOffset = 0; + uint16 instrumentDataSize = 0; + uint16 instrumentEntrySize = 0; + + if (!useMusicDrvFile) { + // INSTR.DAT: we expect at least 354 bytes + if (driverDataSize < 354) + return false; + + // Data is like this: + // 128 bytes instrument mapping + // 128 bytes instrument volume adjust (signed!) + // 16 bytes unknown + // 16 bytes channel mapping + // 64 bytes key note mapping (not used for MT32) + // 1 byte instrument count + // 1 byte bytes per instrument + // x bytes no instruments used for MT32 + + channelMappingOffset = 256 + 16; + channelMappingSize = 16; + instrumentMappingOffset = 0; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 256 + 16 + 16; + keyNoteMappingSize = 64; + + byte instrDatInstrumentCount = driverData[256 + 16 + 16 + 64]; + byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1]; + + // We expect 9 bytes per instrument + if (instrDatBytesPerInstrument != 9) + return false; + // And we also expect at least one adlib instrument + if (!instrDatInstrumentCount) + return false; + + instrumentCount = instrDatInstrumentCount; + instrumentDataOffset = 256 + 16 + 16 + 64 + 2; + instrumentDataSize = instrDatBytesPerInstrument * instrDatInstrumentCount; + instrumentEntrySize = instrDatBytesPerInstrument; + + } else { + // MUSIC.DRV: we expect at least 468 bytes + if (driverDataSize < 468) + return false; + + // music.drv is basically a driver, but with a few fixed locations for certain data + + channelMappingOffset = 396; + channelMappingSize = 16; + instrumentMappingOffset = 140; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 140 + 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 376 + 36; // adjust by 36, because we adjust keyNote before mapping (see noteOn) + keyNoteMappingSize = 64; + + // seems to have used 128 + 5 instruments + // 128 regular ones and an additional 5 for percussion + instrumentCount = 128 + AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT; + instrumentDataOffset = 722; + instrumentEntrySize = 9; + instrumentDataSize = instrumentCount * instrumentEntrySize; + } + + // Channel mapping + if (channelMappingSize) { + // Get these 16 bytes for MIDI channel mapping + if (channelMappingSize != sizeof(_channelMapping)) + return false; + + memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) { + _channelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_instrumentMapping)) + return false; + + memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) { + _instrumentMapping[instrumentNr] = instrumentNr; + } + + if (instrumentVolumeAdjustSize) { + if (instrumentVolumeAdjustSize != sizeof(_instrumentVolumeAdjust)) + return false; + + memcpy(_instrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize); + } + + // Get key note mapping, if available + if (keyNoteMappingSize) { + if (keyNoteMappingSize != sizeof(_percussionKeyNoteMapping)) + return false; + + memcpy(_percussionKeyNoteMapping, driverData + keyNoteMappingOffset, keyNoteMappingSize); + } + + // Check, if there are enough bytes left to hold all instrument data + if (driverDataSize < (instrumentDataOffset + instrumentDataSize)) + return false; + + // We release previous instrument data, just in case + if (_instrumentTable) + delete[] _instrumentTable; + + _instrumentTable = new InstrumentEntry[instrumentCount]; + _instrumentCount = instrumentCount; + + byte *instrDATReadPtr = driverData + instrumentDataOffset; + InstrumentEntry *instrumentWritePtr = _instrumentTable; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(InstrumentEntry)); + instrDATReadPtr += instrumentEntrySize; + instrumentWritePtr++; + } + + // Enable MUSIC.DRV-Mode (slightly different behaviour) + if (useMusicDrvFile) + _musicDrvMode = true; + + if (_musicDrvMode) { + // Extra code for MUSIC.DRV + + // This was done during "programChange" in the original driver + instrumentWritePtr = _instrumentTable; + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + instrumentWritePtr->reg80op1 |= 0x03; // set release rate + instrumentWritePtr->reg80op2 |= 0x03; + instrumentWritePtr++; + } + } + return true; +} + +MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename) { + byte *driverData = NULL; + uint16 driverDataSize = 0; + bool isMusicDrvFile = false; + + MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, isMusicDrvFile); + if (!driverData) + error("ACCOLADE-ADLIB: error during readDriver()"); + + MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib(); + if (driver) { + if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) { + delete driver; + driver = nullptr; + } + } + + delete[] driverData; + return driver; +} + +} // End of namespace AGOS |