Initial commit
This commit is contained in:
568
engines/agos/drivers/accolade/adlib.cpp
Normal file
568
engines/agos/drivers/accolade/adlib.cpp
Normal 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
|
||||
100
engines/agos/drivers/accolade/adlib.h
Normal file
100
engines/agos/drivers/accolade/adlib.h
Normal 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
|
||||
110
engines/agos/drivers/accolade/casio.cpp
Normal file
110
engines/agos/drivers/accolade/casio.cpp
Normal 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
|
||||
49
engines/agos/drivers/accolade/casio.h
Normal file
49
engines/agos/drivers/accolade/casio.h
Normal 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
|
||||
263
engines/agos/drivers/accolade/cms.cpp
Normal file
263
engines/agos/drivers/accolade/cms.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
98
engines/agos/drivers/accolade/cms.h
Normal file
98
engines/agos/drivers/accolade/cms.h
Normal 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
|
||||
167
engines/agos/drivers/accolade/driverfile.cpp
Normal file
167
engines/agos/drivers/accolade/driverfile.cpp
Normal 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
|
||||
49
engines/agos/drivers/accolade/mididriver.h
Normal file
49
engines/agos/drivers/accolade/mididriver.h
Normal 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
|
||||
175
engines/agos/drivers/accolade/mt32.cpp
Normal file
175
engines/agos/drivers/accolade/mt32.cpp
Normal 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
|
||||
66
engines/agos/drivers/accolade/mt32.h
Normal file
66
engines/agos/drivers/accolade/mt32.h
Normal 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
|
||||
723
engines/agos/drivers/accolade/pc98.cpp
Normal file
723
engines/agos/drivers/accolade/pc98.cpp
Normal 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
|
||||
Reference in New Issue
Block a user