Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,568 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "agos/drivers/accolade/adlib.h"
#include "agos/drivers/accolade/mididriver.h"
namespace AGOS {
// hardcoded, dumped from Accolade music system
// same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently
// Numbers 6-A correspond to MIDI channels in the original driver, but here they
// are directly mapped to rhythm instrument types (bass drum, snare drum,
// tom tom, cymbal and hi-hat respectively). F means there is no instrument
// defined for the rhythm note.
const byte MidiDriver_Accolade_AdLib::RHYTHM_NOTE_INSTRUMENT_TYPES[] = {
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
};
// hardcoded, dumped from Accolade music system (INSTR.DAT variant)
const uint16 MidiDriver_Accolade_AdLib::OPL_NOTE_FREQUENCIES_INSTR_DAT[] = {
0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399,
0x03CF, 0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B
};
// hardcoded, dumped from Accolade music system (MUSIC.DRV variant)
const uint16 MidiDriver_Accolade_AdLib::OPL_NOTE_FREQUENCIES_MUSIC_DRV[] = {
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 game Elvira 1
// It seems it was also used for the game "Altered Destiny"
// Another one for the games Elvira 2 + 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.
MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion, int timerFrequency) :
MidiDriver_ADLIB_Multisource(oplType, timerFrequency) {
_instrumentBank = nullptr;
_rhythmBank = nullptr;
_newVersion = newVersion;
_oplNoteFrequencies = _newVersion ? OPL_NOTE_FREQUENCIES_MUSIC_DRV : OPL_NOTE_FREQUENCIES_INSTR_DAT;
Common::fill(_channelRemapping, _channelRemapping + ARRAYSIZE(_channelRemapping), 0);
Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0);
Common::fill(_volumeAdjustments, _volumeAdjustments + ARRAYSIZE(_volumeAdjustments), 0);
Common::fill(_sfxNoteFractions, _sfxNoteFractions + ARRAYSIZE(_sfxNoteFractions), 0);
memset(_sfxInstruments, 0, sizeof(_sfxInstruments));
}
MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() {
if (_instrumentBank)
delete[] _instrumentBank;
if (_rhythmBank)
delete[] _rhythmBank;
}
int MidiDriver_Accolade_AdLib::open() {
_modulationDepth = MODULATION_DEPTH_LOW;
_vibratoDepth = VIBRATO_DEPTH_LOW;
int result = MidiDriver_ADLIB_Multisource::open();
if (result == 0) {
// Rhythm mode is always on.
setRhythmMode(true);
// The original driver writes out default instruments to all channels
// here. This implementation writes instruments before note on, so this
// is not necessary.
// 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 result;
}
void MidiDriver_Accolade_AdLib::send(int8 source, uint32 b) {
// Remap the MIDI channel according to the channel map.
// (Seems to be 1 on 1 for AdLib...)
byte channel = b & 0xF;
channel = _channelRemapping[channel];
b &= 0xFFFFFFF0;
b |= channel;
byte command = b & 0xF0;
if (_oplType != OPL::Config::kOpl3 && _sources[source].type != SOURCE_TYPE_SFX && command != MIDI_COMMAND_PROGRAM_CHANGE) {
// Filter out events for channels used by SFX.
// Program change events are always accepted; they just set the program
// for the music source and do not affect the SFX notes.
if (_activeNotes[channel].channelAllocated)
return;
}
MidiDriver_ADLIB_Multisource::send(source, b);
}
void MidiDriver_Accolade_AdLib::deinitSource(uint8 source) {
if (_sources[source].type == SOURCE_TYPE_SFX) {
// When a sound effect ends, the original driver will immediately
// rewrite the music instrument for the channel used by this sound
// effect. This has the effect of stopping the release phase of the
// sound effect. This is reproduced here to make sure the sound effects
// sound the same.
byte channel = _channelAllocations[source][0];
// OPL3 mode has no fixed instrument assignment to the OPL channel, so
// just use instrument 0.
byte program = 0;
if (_oplType != OPL::Config::kOpl3) {
// For OPL2, get the current music instrument for this OPL channel.
program = _controlData[0][channel].program;
// Apply instrument remapping to instrument channels.
program = _instrumentRemapping[program];
}
InstrumentInfo instrument { };
instrument.instrumentId = program;
instrument.instrumentDef = &_instrumentBank[program];
instrument.oplNote = 0;
writeInstrument(channel, instrument);
// Clear other SFX data.
_sfxNoteFractions[source - 1] = 0;
}
MidiDriver_ADLIB_Multisource::deinitSource(source);
}
uint8 MidiDriver_Accolade_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
Common::StackLock lock(_allocationMutex);
if (_sources[source].type == SOURCE_TYPE_SFX) {
if (_channelAllocations[source][0] == 0xFF) {
// Allocate a channel for this SFX source.
byte allocatedChannel;
if (_oplType != OPL::Config::kOpl3) {
// For OPL2, use channels 5 and 4.
allocatedChannel = 6 - source;
} else {
// For OPL3, use the dynamic allocation algorithm.
allocatedChannel = MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentInfo);
}
_activeNotesMutex.lock();
ActiveNote *activeNote = &_activeNotes[allocatedChannel];
if (activeNote->noteActive) {
// Turn off the note currently playing on this OPL channel.
writeKeyOff(allocatedChannel, activeNote->instrumentDef->rhythmType);
}
_channelAllocations[source][0] = allocatedChannel;
activeNote->channelAllocated = true;
activeNote->source = source;
activeNote->channel = channel;
activeNote->oplNote = 0;
_activeNotesMutex.unlock();
}
// Return the allocated channel.
return _channelAllocations[source][0];
}
// Channel allocation for music sources.
if (_oplType != OPL::Config::kOpl3) {
// Use the regular allocation algorithm for rhythm instruments.
if (channel == MIDI_RHYTHM_CHANNEL)
return MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentInfo);
// For OPL2, discard events for channels 6 and 7 and channels allocated
// for SFX.
if (channel >= 6 || _activeNotes[channel].channelAllocated)
return 0xFF;
// Then just map MIDI channels 0-5 to OPL channels 0-5.
return channel;
} else {
// For OPL3, use the dynamic allocation algorithm.
return MidiDriver_ADLIB_Multisource::allocateOplChannel(channel, source, instrumentInfo);
}
}
byte MidiDriver_Accolade_AdLib::getNumberOfSfxSources() {
// With OPL3 more channels are available for SFX.
return _oplType == OPL::Config::kOpl3 ? 4 : 2;
}
void MidiDriver_Accolade_AdLib::loadSfxInstrument(uint8 source, byte *instrumentData) {
if (source > (_oplType == OPL::Config::kOpl3 ? 4 : 2))
return;
// Copy instrument data into SFX instruments bank.
loadInstrumentData(_sfxInstruments[source - 1], instrumentData, RHYTHM_TYPE_UNDEFINED, 0, _newVersion);
_activeNotesMutex.lock();
// Allocate a channel
programChange(0, 0, source);
InstrumentInfo instrument = determineInstrument(0, source, 0);
uint8 oplChannel = allocateOplChannel(0, source, instrument);
// Update the active note data.
ActiveNote *activeNote = &_activeNotes[oplChannel];
activeNote->instrumentId = instrument.instrumentId;
activeNote->instrumentDef = instrument.instrumentDef;
_activeNotesMutex.unlock();
}
void MidiDriver_Accolade_AdLib::setSfxNoteFraction(uint8 source, uint16 noteFraction) {
// Note is in the upper byte.
_activeNotes[_channelAllocations[source][0]].oplNote = noteFraction >> 8;
// Note fraction is in the lower byte.
_sfxNoteFractions[source - 1] = noteFraction & 0xFF;
}
void MidiDriver_Accolade_AdLib::updateSfxNote(uint8 source) {
writeFrequency(_channelAllocations[source][0]);
}
void MidiDriver_Accolade_AdLib::patchE1Instruments() {
// WORKAROUND One instrument in Elvira 1 has a very slow attack. This
// causes a problem in OPL3 mode (see patchWwInstruments for more details).
// This is fixed by shortening the attack and compensating by making the
// decay longer.
if (_oplType != OPL::Config::kOpl3)
// This workaround is only needed for OPL3 mode.
return;
// This is allocated in readDriverData so it's not really const
OplInstrumentDefinition *instrumentBank = const_cast<OplInstrumentDefinition *>(_instrumentBank);
// Patch the attack and decay of instrument 0x18.
instrumentBank[0x18].operator0.decayAttack = 0x42; // Was 0x24
}
void MidiDriver_Accolade_AdLib::patchWwInstruments() {
// WORKAROUND Several instruments in Waxworks have a very slow attack (it
// takes a long time for a note to reach maximum volume). When a note
// played by this instrument is very short, only a small part of the attack
// phase is played and the note is barely audible. Example: the rapid notes
// in track 10 (played at the start of the London scenario).
// This problem only occurs in OPL3 mode. In OPL2 mode, these notes are all
// played on the same OPL channel. This means that each successive note
// builds on the volume reached by the previous note and apart from the
// first couple of notes they can be heard clearly. In OPL3 mode, each note
// is played on its own channel, so each note starts from 0 volume.
// This is fixed here by patching the attack value of this instrument to be
// 1/4th of the original length (from 3 to 5). The notes do not sound
// exactly as on OPL2, but they are clearly audible.
if (_oplType != OPL::Config::kOpl3)
// This workaround is only needed for OPL3 mode.
return;
// This is allocated in readDriverData so it's not really const
OplInstrumentDefinition *instrumentBank = const_cast<OplInstrumentDefinition *>(_instrumentBank);
// Patch the attack of instrument 0x22.
instrumentBank[0x22].operator1.decayAttack &= 0x0F;
instrumentBank[0x22].operator1.decayAttack |= 0x50;
// Patch the attack of instrument 0x25.
instrumentBank[0x25].operator1.decayAttack &= 0x0F;
instrumentBank[0x25].operator1.decayAttack |= 0x60;
// Patch the attack of instrument 0x7F.
instrumentBank[0x7F].operator0.decayAttack &= 0x0F;
instrumentBank[0x7F].operator0.decayAttack |= 0x60;
instrumentBank[0x7F].operator1.decayAttack &= 0x0F;
instrumentBank[0x7F].operator1.decayAttack |= 0x90;
}
MidiDriver_Accolade_AdLib::InstrumentInfo MidiDriver_Accolade_AdLib::determineInstrument(uint8 channel, uint8 source, uint8 note) {
if (_sources[source].type == SOURCE_TYPE_SFX) {
// For SFX sources, return an instrument from the SFX bank.
InstrumentInfo instrument { };
instrument.instrumentId = 0xFFFF - source;
instrument.instrumentDef = &_sfxInstruments[source - 1];
instrument.oplNote = note;
return instrument;
} else {
return MidiDriver_ADLIB_Multisource::determineInstrument(channel, source, note);
}
}
uint16 MidiDriver_Accolade_AdLib::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
if (!_newVersion) {
// Elvira 1 version.
if (channel != MIDI_RHYTHM_CHANNEL) {
// All melodic notes are lowered by 1 octave, except the lowest notes.
while (note < 0x18)
note += 0xC;
note -= 0xC;
}
// Highest 32 notes are clipped.
if (note > 0x5F)
note = 0x5F;
} else {
// Elvira 2 / Waxworks version.
// Notes 19 and higher are transposed down 19 semitones.
// Note that this is about 1.5 octave, which implies that notes 0-18 are
// not played accurately by this driver.
if (note >= 0x13)
note -= 0x13;
}
// Determine octave and note within octave, and look up the matching OPL
// frequency.
int8 block = note / 12;
if (!_newVersion)
// Elvira 1 version lowers the octave by 1 (note that melodic notes
// were lowered 1 octave earlier).
block--;
uint8 octaveNote = note % 12;
// Look up the note frequency.
uint16 baseFrequency = _oplNoteFrequencies[octaveNote];
uint16 frequency;
if (!_newVersion) {
// Elvira 1 version has a negative frequency lookup value for notes
// which are in a higher octave than the others.
if (baseFrequency & 0x8000)
block++;
// Clear the high bits of the negative lookup values.
frequency = baseFrequency & 0x3FF;
if (block < 0) {
// If octave is now negative, halve the frequency and increase
// octave.
frequency >>= 1;
block++;
}
} else {
// Elvira 2 / Waxworks version adds the note fraction for SFX.
uint16 fractionFrequency = 0;
if (_sources[source].type == SOURCE_TYPE_SFX) {
// Because the frequency differences between notes are not constant
// the fraction is multiplied by a factor depending on the note.
fractionFrequency = (((octaveNote + 1) / 6) + 2) * (_sfxNoteFractions[source - 1] >> 4);
}
frequency = baseFrequency + fractionFrequency;
}
// Note that when processing sound effects, the note can be higher than the
// MIDI maximum value of 0x7F. The original interpreter depends on this for
// correct playback of the sound effect. However, this can cause block to
// overflow the 3 bit range available to it in the OPL registers.
block &= 0x7;
return block << 10 | frequency;
}
uint8 MidiDriver_Accolade_AdLib::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
// A volume adjustment is applied to the velocity of melodic notes.
int8 volumeAdjustment = 0;
if (_sources[source].type != SOURCE_TYPE_SFX) {
if (instrumentDef.rhythmType == RHYTHM_TYPE_UNDEFINED) {
byte program = _controlData[source][channel].program;
volumeAdjustment = _volumeAdjustments[program];
} else if (!_newVersion) {
// For rhythm notes, the Elvira 1 version of the driver checks the
// current "instrument" of channel 9. In this driver channel 9
// corresponds to the cymbal rhythm instrument, which is set to
// instrument definition 4. It then reads the volume adjustment
// for instrument 4 and applies this to all rhythm notes. This
// seems quite dubious and might be a bug, but it is reproduced
// here so rhythm volume is the same as the original interpreter.
// The Elvira 2 / Waxworks driver skips volume adjustment
// completely for rhythm notes.
volumeAdjustment = _volumeAdjustments[4];
}
}
// Note velocity and the volume adjustment are added, clipped to normal
// velocity range and divided by 2 to get an OPL volume value.
uint8 vol = CLIP(velocity + volumeAdjustment, 0, 0x7F);
if (!_newVersion) {
// The Elvira 1 version raises the volume a bit and clips the highest
// values.
vol += 0x18;
if (vol > 0x78)
vol = 0x78;
}
// Invert the volume.
return 0x3F - (vol >> 1);
}
void MidiDriver_Accolade_AdLib::writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
// The Elvira 1 driver does not write the Cx register for rhythm
// instruments except the bass drum; the Elvira 2 / Waxworks driver does
// not write it for the bass drum either.
if (rhythmType == RHYTHM_TYPE_UNDEFINED || (rhythmType == RHYTHM_TYPE_BASS_DRUM && !_newVersion))
MidiDriver_ADLIB_Multisource::writePanning(oplChannel, rhythmType);
}
void MidiDriver_Accolade_AdLib::writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
// The original driver does not write the frequency for the cymbal and
// hi-hat instruments.
if (rhythmType != RHYTHM_TYPE_HI_HAT && rhythmType != RHYTHM_TYPE_CYMBAL)
MidiDriver_ADLIB_Multisource::writeFrequency(oplChannel, rhythmType);
}
void MidiDriver_Accolade_AdLib::loadInstrumentData(OplInstrumentDefinition &definition, byte *instrumentData,
OplInstrumentRhythmType rhythmType, byte rhythmNote, bool newVersion) {
definition.fourOperator = false;
definition.connectionFeedback0 = instrumentData[8];
definition.operator0.freqMultMisc = instrumentData[0];
// The original driver does not add the KSL bits to the calculated
// volume when writing the level registers. To replicate this, the KSL
// bits are set to 0 for operators affected by volume.
// Note that the Elvira 2 / Waxworks driver has a bug which will cause
// the operator 0 level register of the bass drum instrument to be
// overwritten by the connection bit (usually 0) of another instrument
// if the bass drum connection is FM (and it is). This is fixed here by
// setting the correct value. The Elvira 1 version does not have this
// bug.
definition.operator0.level = (definition.connectionFeedback0 & 1) ? 0 : instrumentData[1];
definition.operator0.decayAttack = instrumentData[2];
definition.operator0.releaseSustain = instrumentData[3];
// The original driver only writes 0 to the waveform select registers
// during initialization, so only sine waveform is used.
definition.operator0.waveformSelect = 0;
definition.operator1.freqMultMisc = instrumentData[4];
definition.operator1.level = 0;
definition.operator1.decayAttack = instrumentData[6];
definition.operator1.releaseSustain = instrumentData[7];
definition.operator1.waveformSelect = 0;
if (newVersion) {
// The Elvira 2 / Waxworks driver always sets the last two bits of
// the sustain value.
// This was done during "programChange" in the original driver
definition.operator0.releaseSustain |= 3;
definition.operator1.releaseSustain |= 3;
}
definition.rhythmType = rhythmType;
definition.rhythmNote = rhythmNote;
definition.transpose = 0;
}
void MidiDriver_Accolade_AdLib::readDriverData(byte *driverData, uint16 driverDataSize, bool newVersion) {
uint16 minDataSize = newVersion ? 468 : 354;
if (driverDataSize < minDataSize)
error("ACCOLADE-ADLIB: Expected minimum driver data size of %d - got %d", minDataSize, driverDataSize);
// INSTR.DAT 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
// music.drv is basically a driver, but with a few fixed locations for certain data
uint16 channelMappingOffset = newVersion ? 396 : 256 + 16;
Common::copy(driverData + channelMappingOffset, driverData + channelMappingOffset + ARRAYSIZE(_channelRemapping), _channelRemapping);
uint16 instrumentMappingOffset = newVersion ? 140 : 0;
Common::copy(driverData + instrumentMappingOffset, driverData + instrumentMappingOffset + ARRAYSIZE(_instrumentRemapping), _instrumentRemapping);
setInstrumentRemapping(_instrumentRemapping);
uint16 volumeAdjustmentsOffset = newVersion ? 140 + 128 : 128;
int8 *volumeAdjustmentsData = (int8 *)driverData + volumeAdjustmentsOffset;
Common::copy(volumeAdjustmentsData, volumeAdjustmentsData + ARRAYSIZE(_volumeAdjustments), _volumeAdjustments);
if (!newVersion) {
byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1];
// We expect 9 bytes per instrument
if (instrDatBytesPerInstrument != 9)
error("ACCOLADE-ADLIB: Expected instrument definitions of length 9 - got length %d", instrDatBytesPerInstrument);
}
byte instrumentDefinitionCount = newVersion ? 128 : driverData[256 + 16 + 16 + 64];
uint16 rhythmNoteOffset = newVersion ? 376 + 36 : 256 + 16 + 16;
uint16 instrumentDataOffset = newVersion ? 722 : 256 + 16 + 16 + 64 + 2;
OplInstrumentDefinition *instrumentBank = new OplInstrumentDefinition[instrumentDefinitionCount];
for (int i = 0; i < instrumentDefinitionCount; i++) {
byte *instrumentData = driverData + instrumentDataOffset + (i * 9);
loadInstrumentData(instrumentBank[i], instrumentData, RHYTHM_TYPE_UNDEFINED, 0, newVersion);
}
OplInstrumentDefinition *rhythmBank = new OplInstrumentDefinition[40];
_rhythmBankFirstNote = 36;
_rhythmBankLastNote = 75;
// Elvira 1 version uses instruments 1-5 for rhythm, Elvira 2 / Waxworks
// version uses 0x80-0x84.
byte *rhythmInstrumentDefinitions = driverData + instrumentDataOffset + ((newVersion ? 0x80 : 1) * 9);
byte *rhythmNotes = driverData + rhythmNoteOffset;
for (int i = 0; i < 40; i++) {
byte instrumentDefNumber = RHYTHM_NOTE_INSTRUMENT_TYPES[i] > 0xA ? 0 : RHYTHM_NOTE_INSTRUMENT_TYPES[i] - 6;
OplInstrumentRhythmType rhythmType = RHYTHM_NOTE_INSTRUMENT_TYPES[i] > 0xA ? RHYTHM_TYPE_UNDEFINED :
static_cast<OplInstrumentRhythmType>(11 - RHYTHM_NOTE_INSTRUMENT_TYPES[i]);
byte *instrumentData = rhythmInstrumentDefinitions + (instrumentDefNumber * 9);
loadInstrumentData(rhythmBank[i], instrumentData, rhythmType, rhythmNotes[i], newVersion);
}
// Set the const class variables with our just allocated banks
_instrumentBank = instrumentBank;
_rhythmBank = rhythmBank;
}
MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType, int timerFrequency) {
byte *driverData = nullptr;
uint16 driverDataSize = 0;
bool newVersion = false;
MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, newVersion);
if (!driverData)
error("ACCOLADE-ADLIB: error during readDriver()");
MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib(oplType, newVersion, timerFrequency);
if (!driver)
error("ACCOLADE-ADLIB: could not create driver");
driver->readDriverData(driverData, driverDataSize, newVersion);
delete[] driverData;
return driver;
}
} // End of namespace AGOS

View File

@@ -0,0 +1,100 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGOS_DRIVERS_ACCOLADE_ADLIB_H
#define AGOS_DRIVERS_ACCOLADE_ADLIB_H
#include "audio/adlib_ms.h"
namespace AGOS {
class MidiDriver_Accolade_AdLib : public MidiDriver_ADLIB_Multisource {
protected:
static const byte RHYTHM_NOTE_INSTRUMENT_TYPES[40];
static const uint16 OPL_NOTE_FREQUENCIES_INSTR_DAT[12];
static const uint16 OPL_NOTE_FREQUENCIES_MUSIC_DRV[12];
public:
MidiDriver_Accolade_AdLib(OPL::Config::OplType oplType, bool newVersion, int timerFrequency);
~MidiDriver_Accolade_AdLib() override;
int open() override;
using MidiDriver_ADLIB_Multisource::send;
void send(int8 source, uint32 b) override;
void deinitSource(uint8 source) override;
// Read the specified data from INSTR.DAT or MUSIC.DRV.
void readDriverData(byte *driverData, uint16 driverDataSize, bool isMusicDrv);
// Returns the number of simultaneous SFX sources supported by the current
// driver configuration.
byte getNumberOfSfxSources();
// Loads the specified instrument for the specified instrument source.
void loadSfxInstrument(uint8 source, byte *instrumentData);
// Sets the note (upper byte) and note fraction (lower byte; 1/256th notes)
// for the specified SFX source.
void setSfxNoteFraction(uint8 source, uint16 noteFraction);
// Writes out the current frequency for the specified SFX source.
void updateSfxNote(uint8 source);
// Applies a workaround for an Elvira 1 OPL3 instrument issue.
void patchE1Instruments();
// Applies a workaround for a Waxworks OPL3 instrument issue.
void patchWwInstruments();
protected:
InstrumentInfo determineInstrument(uint8 channel, uint8 source, uint8 note) override;
uint8 allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) override;
uint16 calculateFrequency(uint8 channel, uint8 source, uint8 note) override;
uint8 calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) override;
void writePanning(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
void writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType = RHYTHM_TYPE_UNDEFINED) override;
// Copies the specified instrument data (in INSTR.DAT/MUSIC.DRV format)
// into the specified instrument definition.
void loadInstrumentData(OplInstrumentDefinition &definition, byte *instrumentData,
OplInstrumentRhythmType rhythmType, byte rhythmNote, bool newVersion);
// False if the driver should have the behavior of the Elvira 1 driver;
// true if it should have the behavior of the Elvira 2 / Waxworks version.
bool _newVersion;
// from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument
int8 _volumeAdjustments[128];
// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and AdLib channel
byte _channelRemapping[16];
// from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and AdLib instruments
byte _instrumentRemapping[128];
// Points to one of the OPL_NOTE_FREQUENCIES arrays, depending on the driver version
const uint16 *_oplNoteFrequencies;
// Data used by AdLib SFX (Elvira 2 / Waxworks)
// Instrument definition for each SFX source
OplInstrumentDefinition _sfxInstruments[4];
// Current MIDI note fraction (1/256th notes) for each SFX source
byte _sfxNoteFractions[4];
};
} // End of namespace AGOS
#endif

View File

@@ -0,0 +1,110 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "agos/drivers/accolade/casio.h"
#include "agos/drivers/accolade/mididriver.h"
namespace AGOS {
MidiDriver_Accolade_Casio::MidiDriver_Accolade_Casio() : MidiDriver_Casio(MT_CT460) {
Common::fill(_channelRemapping, _channelRemapping + ARRAYSIZE(_channelRemapping), 0);
Common::fill(_instrumentRemappingData, _instrumentRemappingData + ARRAYSIZE(_instrumentRemappingData), 0);
Common::fill(_rhythmNoteRemappingData, _rhythmNoteRemappingData + ARRAYSIZE(_rhythmNoteRemappingData), 0);
}
int MidiDriver_Accolade_Casio::open() {
int result = MidiDriver_Casio::open();
// Apply instrument and rhythm note remapping after device initialization.
_instrumentRemapping = _instrumentRemappingData;
// WORKAROUND The MT-32 and Casio devices use a different mapping of notes
// to instruments on the rhythm channel. The INSTR.DAT file of Elvira 1
// contains a remapping of the rhythm notes in the game's MT-32 MIDI data
// to the Casio's rhythm notes, but this does not seem to be used - the
// game outputs the MT-32 rhythm notes when CT-460 is selected. As a result
// the wrong rhythm instruments are played. This is fixed here by using the
// game's remapping data to properly remap the rhythm notes to the notes
// that the Casio devices use.
_rhythmNoteRemapping = _rhythmNoteRemappingData;
return result;
}
int8 MidiDriver_Accolade_Casio::mapSourceChannel(uint8 source, uint8 dataChannel) {
if (!_isOpen)
// Use 1 on 1 mapping during device initialization.
return dataChannel;
return _channelRemapping[dataChannel];
}
void MidiDriver_Accolade_Casio::readDriverData(byte *driverData, uint16 driverDataSize) {
uint16 minDataSize = 354;
if (driverDataSize < minDataSize)
error("MidiDriver_Accolade_Casio::readDriverData - Expected minimum driver data size of %d - got %d", minDataSize, driverDataSize);
// INSTR.DAT Data is like this:
// 128 bytes instrument mapping
// 128 bytes instrument volume adjust (signed!) (not used for Casio)
// 16 bytes unknown
// 16 bytes channel mapping
// 64 bytes key note mapping
// 1 byte instrument count
// 1 byte bytes per instrument
// x bytes no instruments used for Casio
uint16 channelMappingOffset = 256 + 16;
Common::copy(driverData + channelMappingOffset, driverData + channelMappingOffset + ARRAYSIZE(_channelRemapping), _channelRemapping);
uint16 instrumentMappingOffset = 0;
Common::copy(driverData + instrumentMappingOffset, driverData + instrumentMappingOffset + ARRAYSIZE(_instrumentRemappingData), _instrumentRemappingData);
uint16 rhythmNoteMappingOffset = 256 + 32;
// 64 bytes are reserved for the rhythm note mapping, but only 40 seem to
// be used. The first mapping is for note 0x24.
Common::copy(driverData + rhythmNoteMappingOffset, driverData + rhythmNoteMappingOffset + 40, _rhythmNoteRemappingData + 0x24);
}
MidiDriver_Multisource *MidiDriver_Accolade_Casio_create(Common::String driverFilename) {
byte *driverData = nullptr;
uint16 driverDataSize = 0;
bool newVersion = false;
MidiDriver_Accolade_readDriver(driverFilename, MT_CT460, driverData, driverDataSize, newVersion);
if (!driverData)
error("MidiDriver_Accolade_Casio_create - Error during readDriver()");
if (newVersion)
// Only Elvira 1 has support for Casio and this uses the old drivers.
error("MidiDriver_Accolade_Casio_create - Driver not supported for Elvira 2 / Waxworks / Simon 1 demo");
MidiDriver_Accolade_Casio *driver = new MidiDriver_Accolade_Casio();
if (!driver)
error("MidiDriver_Accolade_Casio_create - Could not create driver");
driver->readDriverData(driverData, driverDataSize);
delete[] driverData;
return driver;
}
} // End of namespace AGOS

View File

@@ -0,0 +1,49 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGOS_DRIVERS_ACCOLADE_CASIO_H
#define AGOS_DRIVERS_ACCOLADE_CASIO_H
#include "audio/casio.h"
namespace AGOS {
class MidiDriver_Accolade_Casio : public MidiDriver_Casio {
public:
MidiDriver_Accolade_Casio();
int open() override;
int8 mapSourceChannel(uint8 source, uint8 dataChannel) override;
void readDriverData(byte *driverData, uint16 driverDataSize);
protected:
// Mapping between MT-32 data MIDI channels and Casio channels.
byte _channelRemapping[16];
// Mapping between MT-32 data instruments and Casio instruments.
byte _instrumentRemappingData[128];
// Mapping between MT-32 data rhythm notes and Casio rhythm notes.
byte _rhythmNoteRemappingData[128];
};
} // End of namespace AGOS
#endif

View File

@@ -0,0 +1,263 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "agos/drivers/accolade/cms.h"
#include "common/debug.h"
namespace AGOS {
// Hardcoded in RUNVGA.EXE.
const byte MidiDriver_Accolade_Cms::CMS_NOTE_FREQUENCIES[] = {
0x03, 0x1F, 0x3A, 0x53, 0x6B, 0x82,
0x97, 0xAC, 0xBF, 0xD1, 0xE2, 0xF2
};
// Hardcoded in RUNVGA.EXE; probably also used for Tandy.
const byte MidiDriver_Accolade_Cms::CMS_VOLUME_ADJUSTMENTS[] = {
0xF7, 0xFD, 0xFB, 0x00, 0xEC, 0xEE, 0x00, 0x1E, 0x08, 0x07, 0x02, 0x03, 0x08, 0x0A, 0x00, 0x00,
0xF6, 0xF4, 0xEF, 0x05, 0xFB, 0xFC, 0xF5, 0xF5, 0xF6, 0x28, 0x00, 0xEF, 0x00, 0x00, 0x00, 0x00,
0x29, 0x03, 0xE9, 0x00, 0xF5, 0x50, 0xF9, 0xF9, 0xF2, 0x1E, 0xFE, 0x0B, 0xF7, 0xF7, 0xF5, 0xF8,
0xFD, 0xFD, 0xFD, 0xE4, 0xED, 0xFB, 0xFE, 0xFB, 0xD1, 0x1F, 0x00, 0xF1, 0xF1, 0xF6, 0x00, 0x0A,
0xEE, 0xDB, 0x0E, 0xE5, 0x0B, 0x00, 0x0A, 0x0D, 0x03, 0x06, 0xF3, 0xF5, 0x1E, 0x1E, 0x0A, 0xFA,
0xF8, 0xF9, 0x0A, 0x00, 0xFA, 0xFA, 0xF9, 0x00, 0xF2, 0xF9, 0x00, 0xF9, 0x00, 0x04, 0xF8, 0xF7,
0xFC, 0xF1, 0x0A, 0xF1, 0x20, 0x20, 0xF7, 0xF2, 0xF6, 0xF7, 0xFA, 0xFF, 0x20, 0x20, 0x00, 0x00,
0xEA, 0x15, 0x00, 0xFB, 0x0E, 0x00, 0x14, 0x12, 0x00, 0xE2, 0xF7, 0xFB, 0x00, 0x00, 0xF1, 0xFF
};
MidiDriver_Accolade_Cms::MidiDriver_Accolade_Cms() : _cms(nullptr), _isOpen(false), _timer_proc(nullptr), _timer_param(nullptr) {
Common::fill(_instruments, _instruments + ARRAYSIZE(_instruments), 0);
Common::fill(_activeNotes, _activeNotes + ARRAYSIZE(_activeNotes), 0xFF);
Common::fill(_octaveRegisterValues, _octaveRegisterValues + ARRAYSIZE(_octaveRegisterValues), 0);
}
MidiDriver_Accolade_Cms::~MidiDriver_Accolade_Cms() {
if (_isOpen)
close();
}
int MidiDriver_Accolade_Cms::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
_cms = CMS::Config::create();
if (!_cms || !_cms->init())
return MERR_CANNOT_CONNECT;
_isOpen = true;
cmsInit();
_cms->start(new Common::Functor0Mem<void, MidiDriver_Accolade_Cms>(this, &MidiDriver_Accolade_Cms::onTimer));
return 0;
}
void MidiDriver_Accolade_Cms::close() {
if (_cms) {
_cms->stop();
delete _cms;
_cms = nullptr;
}
_isOpen = false;
}
bool MidiDriver_Accolade_Cms::isOpen() const {
return _isOpen;
}
uint32 MidiDriver_Accolade_Cms::getBaseTempo() {
return 1000000 / CMS::CMS::DEFAULT_CALLBACK_FREQUENCY;
}
MidiChannel *MidiDriver_Accolade_Cms::allocateChannel() {
return nullptr;
}
MidiChannel *MidiDriver_Accolade_Cms::getPercussionChannel() {
return nullptr;
}
void MidiDriver_Accolade_Cms::cmsInit() {
for (int i = 0; i < 2; i++) {
int chipOffset = i * 0x100;
// Set registers 0x00 - 0x19 to 0 (note that this includes a few unused
// registers).
for (int j = 0; j <= 0x19; j++) {
writeRegister(j + chipOffset, 0);
}
// Frequency reset.
writeRegister(REGISTER_RESET_SOUND_ENABLE + chipOffset, 0x02);
// Sound enable.
writeRegister(REGISTER_RESET_SOUND_ENABLE + chipOffset, 0x01);
}
}
void MidiDriver_Accolade_Cms::send(uint32 b) {
byte channel = b & 0x0F;
// WORKAROUND This check is bugged in the original interpreter. The channel
// is first resolved through the channel mapping, which is 1 on 1 for
// channels 0-5, and the default FF for the other channels, which means
// they should not be played. Next, the mapped channel is checked against
// the maximum channels for the device, >= 6. However, this check is
// signed, which means FF is interpreted as -1, and it passes the check.
// This comes into play when drums are played on channel 9. The code for
// handling events does not expect channel FF and this results in writes to
// invalid registers and interference with notes on the other channels.
// This check is fixed here to restore the intended behavior.
// Note that this does result in an extended period of silence during the
// intro track where only drums are played.
if (channel >= 6)
return;
byte command = b & 0xF0;
byte op1 = (b >> 8) & 0xFF;
byte op2 = (b >> 16) & 0xFF;
switch (command) {
case MIDI_COMMAND_NOTE_OFF:
noteOff(channel, op1);
break;
case MIDI_COMMAND_NOTE_ON:
noteOn(channel, op1, op2);
break;
case MIDI_COMMAND_CONTROL_CHANGE:
controlChange(channel, op1, op2);
break;
case MIDI_COMMAND_PROGRAM_CHANGE:
programChange(channel, op1);
break;
default:
// Other MIDI events are ignored.
break;
}
}
void MidiDriver_Accolade_Cms::noteOff(uint8 channel, uint8 note) {
if (_activeNotes[channel] != note)
return;
// Remove the note from the active note registry.
_activeNotes[channel] = 0xFF;
// Turn off the frequency enable bit for the channel.
byte freqEnableRegValue = determineFrequencyEnableRegisterValue();
writeRegister(REGISTER_FREQUENCY_ENABLE, freqEnableRegValue);
}
void MidiDriver_Accolade_Cms::noteOn(uint8 channel, uint8 note, uint8 velocity) {
if (velocity == 0) {
// Note on with velocity 0 is a note off.
noteOff(channel, note);
return;
}
// Add the note to the active note registry.
_activeNotes[channel] = note;
// Add octaves to bring the note up into the 0x15 - 0x7F range, then lower
// the note with 0x15.
while (note <= 0x15)
note += 0xC;
note -= 0x15;
// Determine the octave and note within the octave.
byte octave = note / 0xC;
byte octaveNote = note % 0xC;
// Apply the volume adjustment for the current instrument on the channel.
int8 volumeAdjustment = static_cast<int8>(CMS_VOLUME_ADJUSTMENTS[_instruments[channel]]);
uint8 volume = CLIP(velocity + volumeAdjustment, 0, 0x7F);
// Calculate and write the amplitude register value.
byte amplitude = volume >> 4;
writeRegister(REGISTER_BASE_AMPLITUDE + channel, amplitude | (amplitude << 4));
// Look up and write the note frequecy.
byte frequency = CMS_NOTE_FREQUENCIES[octaveNote];
writeRegister(REGISTER_BASE_FREQUENCY + channel, frequency);
// An octave register contains the octaves for 2 channels.
// Get the current value of the register containing the octave value for
// this channel.
byte octaveRegisterValue = _octaveRegisterValues[channel / 2];
// Clear the bits containing the octave value for this channel.
if (channel & 1) {
// Octave is in the upper nibble.
octave <<= 4;
octaveRegisterValue &= 0x0F;
} else {
// Octave is in the lower nibble.
octaveRegisterValue &= 0xF0;
}
// Set and write the new octave value.
octaveRegisterValue |= octave;
_octaveRegisterValues[channel / 2] = octaveRegisterValue;
writeRegister(REGISTER_BASE_OCTAVE + (channel / 2), octaveRegisterValue);
// Turn on the frequency enable bit for the channel.
byte freqEnableRegValue = determineFrequencyEnableRegisterValue();
writeRegister(REGISTER_FREQUENCY_ENABLE, freqEnableRegValue);
}
void MidiDriver_Accolade_Cms::programChange(uint8 channel, uint8 instrument) {
// Just keep track of the current instrument.
_instruments[channel] = instrument;
}
void MidiDriver_Accolade_Cms::controlChange(uint8 channel, uint8 controller, uint8 value) {
// Only All Note Off is processed; it will turn off the active note on this
// channel (if there is one).
if (controller != MIDI_CONTROLLER_ALL_NOTES_OFF || _activeNotes[channel] == 0xFF)
return;
noteOff(channel, _activeNotes[channel]);
}
byte MidiDriver_Accolade_Cms::determineFrequencyEnableRegisterValue() {
byte freqEnableRegValue = 0;
for (int i = 0; i < 6; i++) {
// If a note is active on a channel, set the frequency enable bit for
// that channel.
if (_activeNotes[i] != 0xFF)
freqEnableRegValue |= 1 << i;
}
return freqEnableRegValue;
}
void MidiDriver_Accolade_Cms::writeRegister(uint16 reg, uint8 value) {
//debug("Writing register %02X %02X", reg, value);
_cms->writeReg(reg, value);
}
void MidiDriver_Accolade_Cms::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) {
_timer_param = timer_param;
_timer_proc = timer_proc;
}
void MidiDriver_Accolade_Cms::onTimer() {
if (_timer_proc && _timer_param)
_timer_proc(_timer_param);
}
}

View File

@@ -0,0 +1,98 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGOS_DRIVERS_ACCOLADE_CMS_H
#define AGOS_DRIVERS_ACCOLADE_CMS_H
#include "audio/mididrv.h"
#include "audio/cms.h"
namespace AGOS {
// MIDI driver for the Creative Music System / Gameblaster.
// This driver uses only 6 of the available 12 channels and does not support
// stereo. It only supports note on and off and program change; the selected
// instrument only affects the note volume.
class MidiDriver_Accolade_Cms : public MidiDriver {
protected:
static const byte REGISTER_BASE_AMPLITUDE = 0x00;
static const byte REGISTER_BASE_FREQUENCY = 0x08;
static const byte REGISTER_BASE_OCTAVE = 0x10;
static const byte REGISTER_FREQUENCY_ENABLE = 0x14;
static const byte REGISTER_RESET_SOUND_ENABLE = 0x1C;
// Frequency register values for octave notes.
static const byte CMS_NOTE_FREQUENCIES[12];
// Volume adjustments for all instruments.
static const byte CMS_VOLUME_ADJUSTMENTS[128];
public:
MidiDriver_Accolade_Cms();
~MidiDriver_Accolade_Cms() override;
int open() override;
void close() override;
bool isOpen() const override;
uint32 getBaseTempo() override;
// This driver does not support MidiChannel objects.
MidiChannel *allocateChannel() override;
// This driver does not support MidiChannel objects.
MidiChannel *getPercussionChannel() override;
using MidiDriver::send;
void send(uint32 b) override;
void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) override;
void onTimer();
protected:
void cmsInit();
void noteOff(uint8 channel, uint8 note);
void noteOn(uint8 channel, uint8 note, uint8 velocity);
void programChange(uint8 channel, uint8 instrument);
void controlChange(uint8 channel, uint8 controller, uint8 value);
// Returns the value for the frequency enable register of the first CMS
// chip based on the _activeNotes array (if a note is active, the bit for
// the corresponding channel is set).
byte determineFrequencyEnableRegisterValue();
void writeRegister(uint16 reg, uint8 value);
CMS::CMS *_cms;
bool _isOpen;
// The selected instrument on each MIDI channel.
byte _instruments[16];
// The active note on each CMS channel (0xFF if no note is active).
byte _activeNotes[12];
// The current values of the CMS octave registers (0x10 - 0x12).
byte _octaveRegisterValues[6];
// External timer callback
void *_timer_param;
Common::TimerManager::TimerProc _timer_proc;
};
} // End of namespace AGOS
#endif

View File

@@ -0,0 +1,167 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/mididrv.h"
#include "common/error.h"
#include "common/file.h"
namespace AGOS {
// this reads and gets Accolade driver data
// we need it for channel mapping, instrument mapping and other things
// this driver data chunk gets passed to the actual music driver (MT32 / AdLib)
void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile) {
Common::File *driverStream = new Common::File();
isMusicDrvFile = false;
if (!driverStream->open(Common::Path(filename))) {
error("%s: unable to open file", filename.c_str());
}
if (filename == "INSTR.DAT") {
// INSTR.DAT: used by Elvira 1
uint32 streamSize = driverStream->size();
uint32 streamLeft = streamSize;
uint16 skipChunks = 0; // 1 for MT32, 0 for AdLib
uint16 chunkSize = 0;
switch (requestedDriverType) {
case MT_ADLIB:
skipChunks = 0;
break;
case MT_MT32:
skipChunks = 1; // Skip one entry for MT32
break;
case MT_CT460:
skipChunks = 2; // CT-460 data is the third entry
break;
default:
assert(0);
break;
}
do {
if (streamLeft < 2)
error("%s: unexpected EOF", filename.c_str());
chunkSize = driverStream->readUint16LE();
streamLeft -= 2;
if (streamLeft < chunkSize)
error("%s: unexpected EOF", filename.c_str());
if (skipChunks) {
// Skip the chunk
driverStream->skip(chunkSize);
streamLeft -= chunkSize;
skipChunks--;
}
} while (skipChunks);
// Seek over the ASCII string until there is a NUL terminator
byte curByte = 0;
do {
if (chunkSize == 0)
error("%s: no actual instrument data found", filename.c_str());
curByte = driverStream->readByte();
chunkSize--;
} while (curByte);
driverDataSize = chunkSize;
// Read the requested instrument data entry
driverData = new byte[driverDataSize];
driverStream->read(driverData, driverDataSize);
} else if (filename == "MUSIC.DRV") {
// MUSIC.DRV / used by Elvira 2 / Waxworks / Simon 1 demo
uint32 streamSize = driverStream->size();
uint32 streamLeft = streamSize;
uint16 getChunk = 0; // 4 for MT32, 2 for AdLib
switch (requestedDriverType) {
case MT_ADLIB:
getChunk = 2;
break;
case MT_MT32:
getChunk = 4;
break;
default:
assert(0);
break;
}
if (streamLeft < 2)
error("%s: unexpected EOF", filename.c_str());
uint16 chunkCount = driverStream->readUint16LE();
streamLeft -= 2;
if (getChunk >= chunkCount)
error("%s: required chunk not available", filename.c_str());
uint16 headerOffset = 2 + (28 * getChunk);
streamLeft -= (28 * getChunk);
if (streamLeft < 28)
error("%s: unexpected EOF", filename.c_str());
// Seek to required chunk
driverStream->seek(headerOffset);
driverStream->skip(20); // skip over name
streamLeft -= 20;
uint16 musicDrvSignature = driverStream->readUint16LE();
uint16 musicDrvType = driverStream->readUint16LE();
uint16 chunkOffset = driverStream->readUint16LE();
uint16 chunkSize = driverStream->readUint16LE();
// Security checks
if (musicDrvSignature != 0xFEDC)
error("%s: chunk signature mismatch", filename.c_str());
if (musicDrvType != 1)
error("%s: not a music driver", filename.c_str());
if (chunkOffset >= streamSize)
error("%s: driver chunk points outside of file", filename.c_str());
streamLeft = streamSize - chunkOffset;
if (streamLeft < chunkSize)
error("%s: driver chunk is larger than file", filename.c_str());
driverDataSize = chunkSize;
// Read the requested instrument data entry
driverData = new byte[driverDataSize];
driverStream->seek(chunkOffset);
driverStream->read(driverData, driverDataSize);
isMusicDrvFile = true;
}
driverStream->close();
delete driverStream;
}
} // End of namespace AGOS

View File

@@ -0,0 +1,49 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H
#define AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H
#include "agos/agos.h"
#include "audio/fmopl.h"
#include "audio/mididrv.h"
#include "audio/mididrv_ms.h"
#include "common/error.h"
namespace AGOS {
#define AGOS_MIDI_CHANNEL_COUNT 16
#define AGOS_MIDI_INSTRUMENT_COUNT 128
#define AGOS_MIDI_KEYNOTE_COUNT 64
extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile);
extern MidiDriver_Multisource *MidiDriver_Accolade_AdLib_create(Common::String driverFilename, OPL::Config::OplType oplType, int callbackFrequency = OPL::OPL::kDefaultCallbackFrequency);
extern MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename);
extern MidiDriver_Multisource *MidiDriver_Accolade_Casio_create(Common::String driverFilename);
extern MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev);
} // End of namespace AGOS
#endif // AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H

View File

@@ -0,0 +1,175 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "agos/drivers/accolade/mt32.h"
#include "agos/drivers/accolade/mididriver.h"
#include "agos/sfxparser_accolade.h"
namespace AGOS {
const uint8 MidiDriver_Accolade_MT32::SYSEX_INSTRUMENT_ASSIGNMENT[7] = { 0x02, 0x00, 0x18, 0x32, 0x01, 0x00, 0x01 };
MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() : MidiDriver_MT32GM(MT_MT32) {
Common::fill(_channelRemapping, _channelRemapping + ARRAYSIZE(_channelRemapping), 0);
Common::fill(_instrumentRemapping, _instrumentRemapping + ARRAYSIZE(_instrumentRemapping), 0);
Common::fill(_channelLocks, _channelLocks + ARRAYSIZE(_channelLocks), false);
}
int MidiDriver_Accolade_MT32::open(MidiDriver *driver, bool nativeMT32) {
int result = MidiDriver_MT32GM::open(driver, nativeMT32);
setInstrumentRemapping(_instrumentRemapping);
return result;
}
void MidiDriver_Accolade_MT32::send(int8 source, uint32 b) {
byte dataChannel = b & 0xf;
int8 outputChannel = mapSourceChannel(source, dataChannel);
MidiChannelControlData &controlData = *_controlData[outputChannel];
// Check if this event is sent by a music source and the channel is locked
// by an SFX source.
bool channelLockedByOtherSource = _sources[source].type != SOURCE_TYPE_SFX && _channelLocks[outputChannel];
processEvent(source, b, outputChannel, controlData, channelLockedByOtherSource);
}
int8 MidiDriver_Accolade_MT32::mapSourceChannel(uint8 source, uint8 dataChannel) {
if (!_isOpen)
// Use 1 on 1 mapping during device initialization.
return dataChannel;
if (_sources[source].type == SOURCE_TYPE_SFX) {
// Use channels 7 and 8 for SFX (sources 1 and 2).
uint8 sfxChannel = 9 - source;
_allocationMutex.lock();
if (!_channelLocks[sfxChannel]) {
// Lock channel
stopAllNotes(0xFF, sfxChannel);
_channelLocks[sfxChannel] = true;
}
_allocationMutex.unlock();
return sfxChannel;
} else {
return _channelRemapping[dataChannel];
}
}
void MidiDriver_Accolade_MT32::deinitSource(uint8 source) {
_allocationMutex.lock();
if (_sources[source].type == SOURCE_TYPE_SFX) {
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
if (_controlData[i]->source == source) {
// Restore the music instrument.
programChange(i, _controlData[i]->program, 0, *_controlData[i], false);
// Unlock the channel.
_channelLocks[i] = false;
}
}
}
_allocationMutex.unlock();
MidiDriver_MT32GM::deinitSource(source);
}
void MidiDriver_Accolade_MT32::loadSfxInstrument(uint8 source, byte *instrumentData) {
if (!(source == 1 || source == 2)) {
warning("MidiDriver_Accolade_MT32::loadSfxInstrument - unexpected source %d", source);
return;
}
// Send the instrument data to the timbre memory (patch 1 or 2).
uint32 address = (0x08 << 14) | (((source - 1) * 2) << 7);
sysExMT32(instrumentData + 3, SfxParser_Accolade::INSTRUMENT_SIZE_MT32 - 3, address, true, true, source);
// Allocate the new patch to instrument number 0x75 or 0x76.
byte instrNum = SFX_PROGRAM_BASE + source - 1;
address = (0x05 << 14) | instrNum << 3;
byte instrAssignData[7];
Common::copy(SYSEX_INSTRUMENT_ASSIGNMENT, SYSEX_INSTRUMENT_ASSIGNMENT + ARRAYSIZE(instrAssignData), instrAssignData);
instrAssignData[1] = source - 1;
sysExMT32(instrAssignData, 7, address, true, true, source);
}
void MidiDriver_Accolade_MT32::changeSfxInstrument(uint8 source) {
// Change to the newly loaded instrument.
byte channel = mapSourceChannel(source, 0);
MidiChannelControlData &controlData = *_controlData[channel];
byte originalInstrument = controlData.program;
programChange(channel, SFX_PROGRAM_BASE + source - 1, source, controlData);
// Store the original instrument so it can be used when deinitializing
// the source.
controlData.program = originalInstrument;
}
void MidiDriver_Accolade_MT32::readDriverData(byte *driverData, uint16 driverDataSize, bool newVersion) {
uint16 minDataSize = newVersion ? 468 : 354;
if (driverDataSize < minDataSize)
error("ACCOLADE-ADLIB: Expected minimum driver data size of %d - got %d", minDataSize, driverDataSize);
// INSTR.DAT Data is like this:
// 128 bytes instrument mapping
// 128 bytes instrument volume adjust (signed!) (not used for MT32)
// 16 bytes unknown
// 16 bytes channel mapping
// 64 bytes key note mapping (not really used for MT32)
// 1 byte instrument count
// 1 byte bytes per instrument
// x bytes no instruments used for MT32
// music.drv is basically a driver, but with a few fixed locations for certain data
uint16 channelMappingOffset = newVersion ? 396 : 256 + 16;
Common::copy(driverData + channelMappingOffset, driverData + channelMappingOffset + ARRAYSIZE(_channelRemapping), _channelRemapping);
uint16 instrumentMappingOffset = newVersion ? 140 : 0;
Common::copy(driverData + instrumentMappingOffset, driverData + instrumentMappingOffset + ARRAYSIZE(_instrumentRemapping), _instrumentRemapping);
}
MidiDriver_Multisource *MidiDriver_Accolade_MT32_create(Common::String driverFilename) {
byte *driverData = nullptr;
uint16 driverDataSize = 0;
bool newVersion = false;
MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, newVersion);
if (!driverData)
error("ACCOLADE-MT32: error during readDriver()");
MidiDriver_Accolade_MT32 *driver = new MidiDriver_Accolade_MT32();
if (!driver)
error("ACCOLADE-MT32: could not create driver");
driver->readDriverData(driverData, driverDataSize, newVersion);
delete[] driverData;
return driver;
}
} // End of namespace AGOS

View File

@@ -0,0 +1,66 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef AGOS_DRIVERS_ACCOLADE_MT32_H
#define AGOS_DRIVERS_ACCOLADE_MT32_H
#include "audio/mt32gm.h"
namespace AGOS {
class MidiDriver_Accolade_MT32 : public MidiDriver_MT32GM {
protected:
static const uint8 SFX_PROGRAM_BASE = 0x75;
static const uint8 SYSEX_INSTRUMENT_ASSIGNMENT[7];
public:
MidiDriver_Accolade_MT32();
int open(MidiDriver *driver, bool nativeMT32) override;
using MidiDriver_MT32GM::send;
void send(int8 source, uint32 b) override;
int8 mapSourceChannel(uint8 source, uint8 dataChannel) override;
void deinitSource(uint8 source) override;
// Read the specified data from INSTR.DAT or MUSIC.DRV.
void readDriverData(byte *driverData, uint16 driverDataSize, bool isMusicDrv);
// Loads the specified instrument for the specified instrument source.
void loadSfxInstrument(uint8 source, byte *instrumentData);
// Changes the channel assigned to the specified SFX source to the SFX
// program number.
void changeSfxInstrument(uint8 source);
protected:
// simple mapping between MIDI channel and MT32 channel
byte _channelRemapping[16];
// simple mapping between MIDI instruments and MT32 instruments
byte _instrumentRemapping[128];
// Indicates if a MIDI channel is locked by an SFX source and unavailable
// for music.
bool _channelLocks[MIDI_CHANNEL_COUNT];
};
} // End of namespace AGOS
#endif

View File

@@ -0,0 +1,723 @@
/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/softsynth/fmtowns_pc98/pc98_audio.h"
#include "audio/mididrv.h"
#include "audio/mixer.h"
#include "engines/engine.h"
#include "common/config-manager.h"
#include "common/func.h"
namespace AGOS {
class PC98CommonDriver : public MidiDriver {
public:
enum PC98DriverProperties {
kPropMusicVolume = 0x10,
kPropSfxVolume = 0x20,
kPropPause = 0x30
};
public:
PC98CommonDriver();
~PC98CommonDriver() override {};
bool isOpen() const override { return _isOpen; }
void send(uint32 b) override;
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) override;
uint32 property(int prop, uint32 param) override;
uint32 getBaseTempo() override { return _baseTempo; }
MidiChannel *allocateChannel() override { return nullptr; }
MidiChannel *getPercussionChannel() override { return nullptr; }
protected:
void updateSounds();
void updateParser();
void reset();
uint32 _baseTempo;
bool _isOpen;
Audio::Mixer *_mixer;
const uint8 *_instrumentsRemap;
const int8 *_instrumentLevelAdjust;
const uint8 *_partsRemap;
uint8 _chanUse[16];
uint8 _ngDelay;
bool _allNotes;
bool _programLock;
bool _noFadeRemap;
bool _delayedProgramChange;
private:
virtual void noteOn(uint8 part, uint8 note, uint8 velo) = 0;
virtual void noteOff(uint8 part, uint8 note) = 0;
virtual void programChange(uint8 part, uint8 prog) = 0;
virtual void processSounds() = 0;
virtual void setVolume(int musicVolume, int sfxVolume) = 0;
virtual void pause(bool paused) = 0;
class TimerCb {
public:
typedef void(*FuncType)(void*);
TimerCb(const FuncType func, void *arg) : _func(func), _arg(arg) {}
bool isValid() const { return _func && _arg; }
void operator()() const { (*_func)(_arg); }
private:
const FuncType _func;
void *_arg;
} *_timerCb;
uint32 _internalUpdateTimer;
uint16 _musicVolume;
uint16 _sfxVolume;
int8 _fadeVolumeAdjust;
uint8 _partPrograms[16];
};
class PC98FMDriver : public PC98CommonDriver, private PC98AudioPluginDriver {
public:
PC98FMDriver();
~PC98FMDriver() override;
int open() override;
void close() override;
private:
void noteOn(uint8 part, uint8 note, uint8 velo) override;
void noteOff(uint8 part, uint8 note) override;
void programChange(uint8 part, uint8 prog) override;
void processSounds() override;
void setVolume(int musicVolume, int sfxVolume) override;
void pause(bool paused) override {}
void loadInstrument(uint8 chan, uint8 prg);
void startNote(uint8 chan, uint8 note, uint8 velo);
void stopNote(uint8 chan, uint8 note);
void timerCallbackA() override {}
void timerCallbackB() override;
PC98AudioCore *_pc98a;
uint8 _chanAssign[3];
uint8 _chanNotes[3];
uint8 _partProgramsInternal[16];
uint8 _partNotes[16];
static const uint8 _instrumentsRemapFM[128];
static const uint8 _instrumentLevelAdjustFM[128];
static const uint8 _partsRemapFM[16];
static const uint8 _instrumentPatches[16][25];
static const uint8 _ngMapping[76];
static const uint8 _carrier[8];
static const uint16 _frequency[12];
};
class PC98MidiDriver : public PC98CommonDriver {
public:
PC98MidiDriver(DeviceHandle dev);
~PC98MidiDriver() override;
int open() override;
void close() override;
static void timerCallback(void *obj);
private:
void noteOn(uint8 part, uint8 note, uint8 velo) override;
void noteOff(uint8 part, uint8 note) override;
void programChange(uint8 part, uint8 prog) override;
void processSounds() override {}
void setVolume(int musicVolume, int sfxVolume) override;
void pause(bool paused) override;
void sendSysexWithCheckSum(uint8 *data);
MidiDriver *_drv;
DeviceHandle _dev;
MusicType _devType;
uint8 _volSysex[9];
uint8 _partAssignSysexGS[9];
uint8 _partAssignSysexMT32[9];
static const uint8 _instrumentsRemapMT32[128];
static const uint8 _instrumentsRemapGM[128];
static const uint8 _partsRemapMidi[16];
static const uint8 _sysexMsg[3][9];
};
PC98CommonDriver::PC98CommonDriver() : _mixer(g_engine->_mixer), _baseTempo(0), _fadeVolumeAdjust(0), _allNotes(false), _programLock(false), _isOpen(false), _noFadeRemap(false), _delayedProgramChange(false), _ngDelay(0), _timerCb(nullptr), _musicVolume(0xff), _sfxVolume(0xff), _internalUpdateTimer(0) {
memset(_partPrograms, 0, sizeof(_partPrograms));
memset(_chanUse, 0, sizeof(_chanUse));
}
void PC98CommonDriver::send(uint32 b) {
if (!_isOpen)
return;
byte para2 = (b >> 16) & 0xFF;
byte para1 = (b >> 8) & 0xFF;
byte ch = b & 0x0F;
switch (b & 0xF0) {
case 0x80:
noteOff(ch, para1);
break;
case 0x90:
if (para2) {
int16 velo = para2;
if (ch != 9)
velo = CLIP<int16>(velo + _instrumentLevelAdjust[_partPrograms[ch]], 0, 127);
velo = CLIP<int16>(velo + _fadeVolumeAdjust, 0, 127);
noteOn(ch, para1, velo);
} else {
noteOff(ch, para1);
}
break;
case 0xC0:
_partPrograms[ch] = para1;
programChange(ch, ch == 9 ? 0 : _instrumentsRemap[para1 & 0x07F]);
break;
default:
// 0xA0 and 0xB0 are parsing related and will be filtered and handled in MidiParser_S1D.
if (!((b & 0xF0) == 0xB0 && (para1 == 0x7b || para1 == 0x07)))
warning("PC98CommonDriver::send(): Unsupported Midi Message: 0x%02x 0x%02x 0x%02x", b & 0xFF, para1, para2);
break;
}
}
void PC98CommonDriver::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
delete _timerCb;
_timerCb = (_isOpen && timerParam && timerProc) ? new TimerCb(timerProc, timerParam) : nullptr;
}
uint32 PC98CommonDriver::property(int prop, uint32 param) {
uint32 res = 0;
switch (prop) {
case kPropMusicVolume:
case kPropSfxVolume: {
uint16 &v = (prop == kPropMusicVolume) ? _musicVolume : _sfxVolume;
res = v;
if ((int32)param != -1)
v = param & 0x1ff;
if (_isOpen)
setVolume(_musicVolume, _sfxVolume);
break;
}
case kPropPause: {
if (_isOpen)
pause(param);
break;
}
default:
break;
}
return res;
}
void PC98CommonDriver::updateSounds() {
if (!_isOpen)
return;
_internalUpdateTimer += _baseTempo;
if (_internalUpdateTimer >= 16667) {
_internalUpdateTimer -= 16667;
// I haven't implemented music fading in and out, since Elvira 1 (the
// only game for this sound driver) does not use the feature at all.
// The fade volume would have to be updated here...
for (int i = 0; i < 16; ++i)
_chanUse[i] = 0;
processSounds();
}
}
void PC98CommonDriver::updateParser() {
if (_isOpen && _timerCb && _timerCb->isValid())
(*_timerCb)();
}
void PC98CommonDriver::reset() {
memset(_partPrograms, 0, sizeof(_partPrograms));
memset(_chanUse, 0, sizeof(_chanUse));
_allNotes = false;
_programLock = false;
_noFadeRemap = false;
_delayedProgramChange = false;
_ngDelay = 0;
}
PC98FMDriver::PC98FMDriver() : PC98CommonDriver(), _pc98a(nullptr) {
_baseTempo = 10080;
_instrumentsRemap = _instrumentsRemapFM;
_instrumentLevelAdjust = (const int8*)_instrumentLevelAdjustFM;
_partsRemap = _partsRemapFM;
memset(_partProgramsInternal, 0, sizeof(_partProgramsInternal));
memset(_partNotes, 0, sizeof(_partNotes));
memset(_chanAssign, 0, sizeof(_chanAssign));
memset(_chanNotes, 0, sizeof(_chanNotes));
}
PC98FMDriver::~PC98FMDriver() {
_mixer->stopAll();
close();
}
int PC98FMDriver::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
delete _pc98a;
_pc98a = new PC98AudioCore(g_engine->_mixer, this, kType26);
if (_pc98a && _pc98a->init()) {
_pc98a->writeReg(0, 0x06, 0x0a);
_pc98a->writeReg(0, 0x07, 0x9c);
for (int i = 8; i < 11; ++i)
_pc98a->writeReg(0, i, 0);
_pc98a->writeReg(0, 0x27, 0x3a);
} else {
return MERR_DEVICE_NOT_AVAILABLE;
}
memset(_partProgramsInternal, 0, sizeof(_partProgramsInternal));
memset(_partNotes, 0, sizeof(_partNotes));
memset(_chanAssign, 0, sizeof(_chanAssign));
memset(_chanNotes, 0, sizeof(_chanNotes));
reset();
_isOpen = true;
return 0;
}
void PC98FMDriver::close() {
_isOpen = false;
delete _pc98a;
_pc98a = nullptr;
setTimerCallback(nullptr, nullptr);
}
void PC98FMDriver::noteOn(uint8 part, uint8 note, uint8 velo) {
if (_delayedProgramChange && part != 9) {
int ch = 0x80;
uint8 high = 0x80;
for (int i = 2; i >= 0; --i) {
if (_chanAssign[i] == 0x80) {
ch = i;
break;
}
if (part < _chanAssign[i] && high > _chanAssign[i]) {
ch = i;
high = _chanAssign[i];
}
}
if (ch == 0x80)
return;
loadInstrument(ch, _partProgramsInternal[part]);
_partNotes[ch] = note;
_chanAssign[ch] = part;
part = ch;
}
startNote(part, note, velo);
}
void PC98FMDriver::noteOff(uint8 part, uint8 note) {
if (_delayedProgramChange) {
if (part == 9) {
_pc98a->writeReg(0, 6, 0);
stopNote(part, note);
} else {
for (int i = 2; i >= 0; --i) {
if (_chanAssign[i] != part || (note != _partNotes[i] && !_allNotes))
continue;
_chanAssign[i] = 0x80;
_partNotes[i] = 0;
stopNote(i, note);
}
}
} else {
stopNote(part, note);
}
}
void PC98FMDriver::programChange(uint8 part, uint8 prog) {
if (!_delayedProgramChange)
loadInstrument(part, prog);
_partProgramsInternal[part] = prog;
}
void PC98FMDriver::processSounds() {
if (_ngDelay)
--_ngDelay;
if (!_ngDelay)
_pc98a->writeReg(0, 0x0a, 0);
}
void PC98FMDriver::setVolume(int musicVolume, int sfxVolume) {
_pc98a->setMusicVolume(musicVolume);
_pc98a->setSoundEffectVolume(sfxVolume);
}
void PC98FMDriver::loadInstrument(uint8 chan, uint8 prg) {
if (chan > 2)
return;
assert(prg < ARRAYSIZE(_instrumentPatches));
const uint8 *src = _instrumentPatches[prg];
_pc98a->writeReg(0, 0xB0 | chan, *src++);
for (uint8 reg = 0x30 | chan; reg < 0x40; reg += 4) {
_pc98a->writeReg(0, reg, *src++);
_pc98a->writeReg(0, reg + 0x10, *src++);
_pc98a->writeReg(0, reg + 0x20, *src++);
_pc98a->writeReg(0, reg + 0x30, (*src++) & 0x1F);
_pc98a->writeReg(0, reg + 0x40, (*src++) & 0x1F);
_pc98a->writeReg(0, reg + 0x50, *src++);
}
}
void PC98FMDriver::startNote(uint8 chan, uint8 note, uint8 velo) {
if (chan == 9) {
if (note >= sizeof(_ngMapping) || _ngMapping[note] == 0xff)
return;
_pc98a->writeReg(0, 0x06, _ngMapping[note]);
_pc98a->writeReg(0, 0x0a, 0x0a);
_ngDelay = 3;
}
if (chan > 2)
return;
if (_chanUse[chan] && note < _chanNotes[chan])
return;
_allNotes = true;
stopNote(chan, 0);
_allNotes = false;
_chanNotes[chan] = note;
_chanUse[chan]++;
const uint8 *instr = _instrumentPatches[_partProgramsInternal[chan]];
uint8 c = _carrier[*instr & 7];
instr += 2;
const uint8 *pos = instr;
uint8 instvl = 0x7F;
for (int i = 0; i < 4; ++i) {
if (((c >> i) & 1) && *pos < instvl)
instvl = *pos;
pos += 6;
}
pos = instr;
velo = 0x7f - (0x57 + (velo >> 2)) - instvl;
for (uint8 i = 0x40 | chan; i < 0x50; i += 4) {
if (c & 1)
_pc98a->writeReg(0, i, MIN<uint8>(*pos + velo, 0x7f));
pos += 6;
c >>= 1;
}
if (note > 18)
note -= 12;
uint16 frq = _frequency[note % 12];
uint8 bl = (note / 12) << 3;
_pc98a->writeReg(0, 0xa4 | chan, (frq >> 8) | bl);
_pc98a->writeReg(0, 0xa0 | chan, frq & 0xff);
_pc98a->writeReg(0, 0x28, 0xF0 | chan);
}
void PC98FMDriver::stopNote(uint8 chan, uint8 note) {
if (chan > 2)
return;
if (_allNotes || note == _chanNotes[chan])
_pc98a->writeReg(0, 0x28, chan);
}
void PC98FMDriver::timerCallbackB() {
updateSounds();
PC98AudioCore::MutexLock tempUnlock = _pc98a->stackUnlockMutex();
updateParser();
}
const uint8 PC98FMDriver::_instrumentsRemapFM[128] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x09, 0x0a, 0x0b, 0x0c, 0x01, 0x02, 0x0f,
0x0f, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x19, 0x1b, 0x1c, 0x1d, 0x1e, 0x03,
0x04, 0x21, 0x22, 0x23, 0x05, 0x25, 0x06, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2e,
0x30, 0x31, 0x35, 0x07, 0x35, 0x35, 0x36, 0x37, 0x38, 0x08, 0x3a, 0x3b, 0x3c, 0x3e, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x44, 0x44, 0x45, 0x09, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x0a, 0x51, 0x51,
0x51, 0x54, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5a, 0x5a, 0x5e, 0x5f,
0x60, 0x61, 0x67, 0x63, 0x0c, 0x65, 0x66, 0x67, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c,
0x0c, 0x0c, 0x0c, 0x0d, 0x0e, 0x0f, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
};
const uint8 PC98FMDriver::_instrumentLevelAdjustFM[128] = {
0x28, 0x0f, 0x28, 0x1d, 0x14, 0x14, 0x23, 0x19, 0x28, 0x13, 0x23, 0x23, 0x30, 0x23, 0x17, 0x16,
0x23, 0x14, 0x15, 0x13, 0x23, 0x23, 0x19, 0x19, 0x32, 0x19, 0x16, 0x23, 0x05, 0x0a, 0x05, 0x0a,
0x35, 0x28, 0x2d, 0x23, 0x19, 0x1c, 0x22, 0x23, 0x23, 0x1a, 0x2d, 0x23, 0x23, 0x23, 0x23, 0x1e,
0x32, 0x1e, 0x37, 0x23, 0x18, 0x2e, 0x2b, 0x32, 0x11, 0x14, 0x0f, 0x0f, 0x14, 0x14, 0x14, 0x14,
0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x0a, 0x28, 0x0d, 0x14, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
0x23, 0x0a, 0x19, 0x19, 0x14, 0x14, 0x12, 0x14, 0x2b, 0x2c, 0x34, 0x2e, 0x30, 0x31, 0x15, 0x29,
0x32, 0x23, 0x23, 0x23, 0x23, 0x0b, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x14, 0x14, 0x1e, 0x23,
0x23, 0x23, 0x23, 0x23, 0x2c, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x00, 0x23, 0x23, 0x23
};
const uint8 PC98FMDriver::_partsRemapFM[16] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};
const uint8 PC98FMDriver::_instrumentPatches[16][25] = {
{ 0x3f, 0x06, 0x6b, 0x1f, 0x0e, 0x00, 0xff, 0x03, 0x0a, 0x1f, 0x02, 0x01, 0x0f, 0x03, 0x0a, 0x1f, 0x02, 0x01, 0x0f, 0x02, 0x00, 0x1f, 0x02, 0x01, 0x0f },
{ 0x3f, 0x38, 0x75, 0x1f, 0x81, 0x01, 0x0a, 0x01, 0x00, 0x14, 0x82, 0x01, 0x0a, 0x73, 0x00, 0x14, 0x82, 0x01, 0x0a, 0x62, 0x00, 0x14, 0x82, 0x01, 0x0a },
{ 0x07, 0x08, 0x3e, 0xdf, 0x15, 0x00, 0x0f, 0x03, 0x11, 0x5f, 0x1f, 0x00, 0x0a, 0x02, 0x25, 0x5d, 0x1f, 0x00, 0x0a, 0x02, 0x00, 0x92, 0x1f, 0x00, 0x0a },
{ 0x3d, 0x4a, 0x23, 0x1b, 0x11, 0x00, 0xfa, 0x41, 0x00, 0x14, 0x00, 0x00, 0x0a, 0x42, 0x00, 0x14, 0x00, 0x00, 0x0a, 0x40, 0x00, 0x14, 0x00, 0x00, 0x0a },
{ 0x3c, 0x0b, 0x2b, 0x5a, 0x02, 0x01, 0x35, 0x63, 0x2a, 0x55, 0x01, 0x00, 0x24, 0x03, 0x19, 0x5c, 0x09, 0x05, 0x44, 0x21, 0x00, 0x4d, 0x06, 0x00, 0x44 },
{ 0x3c, 0x01, 0x20, 0x52, 0x0c, 0x01, 0x5a, 0x22, 0x17, 0x4f, 0x0a, 0x01, 0x2a, 0x26, 0x05, 0x45, 0x0a, 0x00, 0x0f, 0x31, 0x00, 0x47, 0x02, 0x00, 0x0f },
{ 0x2c, 0x3a, 0x2d, 0x58, 0x0e, 0x00, 0xf7, 0x05, 0x39, 0x5a, 0x0e, 0x00, 0xf4, 0x02, 0x00, 0x58, 0x08, 0x00, 0xf4, 0x01, 0x05, 0x9a, 0x08, 0x00, 0xf4 },
{ 0x3c, 0x01, 0x20, 0x52, 0x0c, 0x01, 0x2a, 0x21, 0x17, 0x4f, 0x0a, 0x01, 0x5a, 0x11, 0x00, 0x12, 0x8a, 0x01, 0x3a, 0x61, 0x07, 0x14, 0x82, 0x01, 0x3a },
{ 0x00, 0x01, 0x7f, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x23, 0x5f, 0x05, 0x00, 0xf3, 0x71, 0x1b, 0x5f, 0x0c, 0x01, 0xf5, 0x01, 0x07, 0x5f, 0x0c, 0x00, 0xf5 },
{ 0x18, 0x37, 0x2c, 0x9e, 0x0d, 0x08, 0xb6, 0x31, 0x18, 0x1c, 0x04, 0x03, 0x36, 0x30, 0x22, 0xdc, 0x06, 0x0a, 0xb6, 0x32, 0x00, 0x9c, 0x01, 0x05, 0x26 },
{ 0x3b, 0x0a, 0x00, 0x1f, 0x00, 0x00, 0x0a, 0x02, 0x22, 0x18, 0x0e, 0x00, 0x0a, 0x60, 0x3a, 0x18, 0x0c, 0x00, 0xfa, 0x31, 0x00, 0x51, 0x0b, 0x00, 0x38 },
{ 0x2c, 0x34, 0x21, 0x58, 0x0e, 0x00, 0xf7, 0x76, 0x2f, 0x58, 0x14, 0x00, 0xfa, 0x30, 0x00, 0xd8, 0x84, 0x00, 0xf2, 0x72, 0x0b, 0x98, 0x8c, 0x00, 0xf6 },
{ 0x3b, 0x08, 0x06, 0x5f, 0x00, 0x00, 0x1a, 0x01, 0x19, 0x4e, 0x0e, 0x00, 0x0a, 0x61, 0x30, 0x58, 0x18, 0x00, 0x5a, 0x30, 0x00, 0x50, 0x0b, 0x00, 0x38 },
{ 0x3b, 0x0a, 0x0d, 0x16, 0x00, 0x00, 0x0a, 0x00, 0x0b, 0x1a, 0x16, 0x40, 0xfb, 0x0d, 0x13, 0x1a, 0x1a, 0xc0, 0xfa, 0x01, 0x00, 0x5e, 0x8e, 0x00, 0xf7 },
{ 0x3b, 0x0e, 0x0c, 0x1f, 0x00, 0x00, 0x05, 0x0a, 0x25, 0x1b, 0x1b, 0x80, 0xfa, 0x00, 0x31, 0x1f, 0x0a, 0xc0, 0xf5, 0x00, 0x00, 0x5c, 0x8e, 0x40, 0xf7 },
{ 0x32, 0x02, 0x15, 0x58, 0x14, 0x00, 0xfa, 0x31, 0x25, 0x5f, 0x0a, 0x40, 0xf4, 0x01, 0x17, 0x5a, 0x0c, 0x80, 0xf6, 0x01, 0x00, 0x9a, 0x8b, 0x00, 0xf5 }
};
const uint8 PC98FMDriver::_ngMapping[76] = {
0x18, 0x1c, 0x3c, 0x20, 0x3e, 0x24, 0x40, 0x28, 0x2c, 0x38, 0x30, 0x3c, 0x00, 0x00, 0x36, 0x00, 0x38, 0x00, 0x00,
0x00, 0x16, 0x11, 0x16, 0x16, 0x11, 0x16, 0x11, 0x16, 0x11, 0x0f, 0x32, 0x00, 0x00, 0x0d, 0x00, 0x10, 0x1f, 0x1f,
0x0a, 0x08, 0x0a, 0x19, 0x04, 0x19, 0x04, 0x14, 0x04, 0x14, 0x0f, 0x0c, 0x0f, 0x0c, 0xff, 0xff, 0x0d, 0xff, 0x12,
0xff, 0xff, 0xff, 0x0f, 0x19, 0x0f, 0x0f, 0x19, 0x0f, 0x19, 0x0f, 0x19, 0x14, 0x06, 0xff, 0xff, 0x0f, 0xff, 0x00
};
const uint8 PC98FMDriver::_carrier[8] = {
0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F
};
const uint16 PC98FMDriver::_frequency[12] = {
0x0267, 0x028d, 0x02b3, 0x02dd, 0x0308, 0x0337, 0x0368, 0x039c, 0x03d3, 0x040e, 0x044b, 0x048b
};
#define MIDIMSG32(s, p1, p2) (p2 << 16 | p1 << 8 | s)
PC98MidiDriver::PC98MidiDriver(MidiDriver::DeviceHandle dev) : _dev(dev), _drv(nullptr) {
_devType = (getMusicType(dev) == MT_MT32 || ConfMan.getBool("native_mt32")) ? MT_MT32 : MT_GM;
_instrumentsRemap = (_devType == MT_MT32) ? _instrumentsRemapMT32 : (_devType == MT_GM ? _instrumentsRemapGM : nullptr);
int8 *tbl2 = new int8[128]();
_instrumentLevelAdjust = tbl2;
_partsRemap = _partsRemapMidi;
memcpy(_volSysex, _sysexMsg[0], 9);
memcpy(_partAssignSysexGS, _sysexMsg[1], 9);
memcpy(_partAssignSysexMT32, _sysexMsg[2], 9);
}
PC98MidiDriver::~PC98MidiDriver() {
close();
delete[] _instrumentLevelAdjust;
}
int PC98MidiDriver::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
delete _drv;
_drv = MidiDriver::createMidi(_dev);
if (!_drv || !_instrumentsRemap)
return MERR_DEVICE_NOT_AVAILABLE;
_baseTempo = _drv->getBaseTempo();
int res = _drv->open();
if (!res) {
_drv->setTimerCallback(this, &timerCallback);
for (byte i = 0xB1; i < 0xBA; ++i)
_drv->send(MIDIMSG32(i, 0x79, 0));
property(kPropMusicVolume, Audio::Mixer::kMaxChannelVolume);
if (_devType == MT_MT32) {
_partAssignSysexGS[7] = 0x10;
for (uint8 i = 0x10; i < 0x20; ++i) {
_partAssignSysexGS[5] = i;
sendSysexWithCheckSum(_partAssignSysexGS);
}
for (uint8 i = 0x01; i < 0x0A; ++i) {
_partAssignSysexMT32[6] = 0x0C + i;
_partAssignSysexMT32[7] = i;
sendSysexWithCheckSum(_partAssignSysexMT32);
}
} else if (_devType == MT_GM) {
_partAssignSysexGS[5] = 0x10;
_partAssignSysexGS[7] = 9;
sendSysexWithCheckSum(_partAssignSysexGS);
uint8 p = 0;
for (uint8 i = 0x11; i < 0x20; ++i) {
_partAssignSysexGS[5] = i;
_partAssignSysexGS[7] = p++;
if (p == 9)
p++;
sendSysexWithCheckSum(_partAssignSysexGS);
}
_partAssignSysexMT32[7] = 0x10;
for (uint8 i = 0x0D; i < 0x16; ++i) {
_partAssignSysexMT32[6] = i;
sendSysexWithCheckSum(_partAssignSysexMT32);
}
_drv->send(MIDIMSG32(0xB9, 0x07, 0x46));
}
reset();
_isOpen = true;
}
return res;
}
void PC98MidiDriver::close() {
_isOpen = false;
if (_drv) {
_drv->setTimerCallback(nullptr, nullptr);
_mixer->stopAll();
_drv->close();
delete _drv;
_drv = nullptr;
}
setTimerCallback(nullptr, nullptr);
}
void PC98MidiDriver::timerCallback(void *obj) {
PC98MidiDriver *drv = static_cast<PC98MidiDriver*>(obj);
drv->updateSounds();
drv->updateParser();
}
void PC98MidiDriver::noteOn(uint8 part, uint8 note, uint8 velo) {
_drv->send(MIDIMSG32(0x90 | _partsRemap[part & 0x0F], note, velo));
}
void PC98MidiDriver::noteOff(uint8 part, uint8 note) {
if (_allNotes)
_drv->send(MIDIMSG32(0xB0 | _partsRemap[part & 0x0F], 0x7B, 0));
else
_drv->send(MIDIMSG32(0x80 | _partsRemap[part & 0x0F], note, 0));
}
void PC98MidiDriver::programChange(uint8 part, uint8 prog) {
if (!_programLock)
_drv->send(MIDIMSG32(0xC0 | _partsRemap[part & 0x0F], prog, 0));
}
void PC98MidiDriver::setVolume(int musicVolume, int sfxVolume) {
if (!_isOpen)
return;
if (_devType == MT_MT32) {
_volSysex[7] = musicVolume * 100 / Audio::Mixer::kMaxChannelVolume;
sendSysexWithCheckSum(_volSysex);
} else {
uint8 vol = musicVolume * 127 / Audio::Mixer::kMaxChannelVolume;
for (int i = 0; i < 16; ++i)
_drv->send(MIDIMSG32(0xB0 | _partsRemap[i], 0x07, vol));
}
}
void PC98MidiDriver::pause(bool paused) {
if (paused) {
_allNotes = true;
for (int i = 0; i < 16; ++i)
noteOff(i, 0);
_allNotes = false;
}
}
void PC98MidiDriver::sendSysexWithCheckSum(uint8 *data) {
uint8 chk = 0;
for (int i = 4; i < 8; ++i)
chk += data[i];
data[8] = 0x80 - (chk & 0x7f);
_drv->sysEx(data, 9);
}
const uint8 PC98MidiDriver::_instrumentsRemapMT32[128] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x6e, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
};
const uint8 PC98MidiDriver::_instrumentsRemapGM[128] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x10, 0x09, 0x0a, 0x0b, 0x0c, 0x10, 0x10, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x26,
0x58, 0x21, 0x22, 0x23, 0x61, 0x25, 0x0b, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x2d, 0x34, 0x35, 0x36, 0x37, 0x38, 0x2e, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x23, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4c, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x4c, 0x65, 0x66, 0x67, 0x0c, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x75, 0x76, 0x74, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
};
const uint8 PC98MidiDriver::_partsRemapMidi[16] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0f, 0x09, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f
};
const uint8 PC98MidiDriver::_sysexMsg[3][9] = {
{ 0x41, 0x10, 0x16, 0x12, 0x10, 0x00, 0x16, 0x64, 0x00 },
{ 0x41, 0x10, 0x42, 0x12, 0x40, 0x10, 0x02, 0x10, 0x00 },
{ 0x41, 0x10, 0x16, 0x12, 0x10, 0x00, 0x00, 0x00, 0x00 }
};
MidiDriver *MidiDriverPC98_create(MidiDriver::DeviceHandle dev) {
MusicType type = MidiDriver::getMusicType(dev);
if (type == MT_PC98)
return new PC98FMDriver();
else if (type == MT_GM || type == MT_MT32)
return new PC98MidiDriver(dev);
return nullptr;
}
#undef MIDIMSG32
} // End of namespace AGOS