Files
scummvm/engines/agos/drivers/accolade/adlib.cpp
T

881 lines
28 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "agos/drivers/accolade/mididriver.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "audio/fmopl.h"
#include "audio/mididrv.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