scummvm/audio/miles_adlib.cpp
Coen Rampen b571a50e19 AUDIO: Fix ineffective version prop on Miles AdLib
Setting the Miles driver version property to 3 was meant to set the pitch bend
range to 2. In the AdLib driver, the pitch bend range was set during
construction, before the version property could be set, so this did not work
properly. This is fixed by moving the MIDI data initialization from the
constructor to the open function.
2021-11-04 22:16:02 +01:00

1448 lines
48 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "audio/miles.h"
#include "common/file.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "audio/fmopl.h"
#include "audio/adlib_ms.h"
namespace Audio {
// Miles Audio AdLib/OPL3 driver
//
// TODO: currently missing: OPL3 4-op voices
//
// Special cases (great for testing):
// - sustain feature is used by Return To Zork (demo) right at the start
// - sherlock holmes 2 does lots of priority sorts right at the start of the intro
#define MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX 20
#define MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX 18
#define MILES_ADLIB_PERCUSSION_BANK 127
#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT 27
#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT 100
enum kMilesAdLibUpdateFlags {
kMilesAdLibUpdateFlags_None = 0,
kMilesAdLibUpdateFlags_Reg_20 = 1 << 0,
kMilesAdLibUpdateFlags_Reg_40 = 1 << 1,
kMilesAdLibUpdateFlags_Reg_60 = 1 << 2, // register 0x6x + 0x8x
kMilesAdLibUpdateFlags_Reg_C0 = 1 << 3,
kMilesAdLibUpdateFlags_Reg_E0 = 1 << 4,
kMilesAdLibUpdateFlags_Reg_A0 = 1 << 5, // register 0xAx + 0xBx
kMilesAdLibUpdateFlags_Reg_All = 0x3F
};
uint16 milesAdLibOperator1Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
0x0000, 0x0001, 0x0002, 0x0008, 0x0009, 0x000A, 0x0010, 0x0011, 0x0012,
0x0100, 0x0101, 0x0102, 0x0108, 0x0109, 0x010A, 0x0110, 0x0111, 0x0112
};
uint16 milesAdLibOperator2Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
0x0003, 0x0004, 0x0005, 0x000B, 0x000C, 0x000D, 0x0013, 0x0014, 0x0015,
0x0103, 0x0104, 0x0105, 0x010B, 0x010C, 0x010D, 0x0113, 0x0114, 0x0115
};
uint16 milesAdLibChannelRegister[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008,
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108
};
struct InstrumentEntry {
byte bankId;
byte patchId;
int16 transposition;
byte reg20op1;
byte reg40op1;
byte reg60op1;
byte reg80op1;
byte regE0op1;
byte reg20op2;
byte reg40op2;
byte reg60op2;
byte reg80op2;
byte regE0op2;
byte regC0;
};
// hardcoded, dumped from ADLIB.MDI
uint16 milesAdLibFrequencyLookUpTable[] = {
0x02B2, 0x02B4, 0x02B7, 0x02B9, 0x02BC, 0x02BE, 0x02C1, 0x02C3, 0x02C6, 0x02C9, 0x02CB, 0x02CE,
0x02D0, 0x02D3, 0x02D6, 0x02D8, 0x02DB, 0x02DD, 0x02E0, 0x02E3, 0x02E5, 0x02E8, 0x02EB, 0x02ED,
0x02F0, 0x02F3, 0x02F6, 0x02F8, 0x02FB, 0x02FE, 0x0301, 0x0303, 0x0306, 0x0309, 0x030C, 0x030F,
0x0311, 0x0314, 0x0317, 0x031A, 0x031D, 0x0320, 0x0323, 0x0326, 0x0329, 0x032B, 0x032E, 0x0331,
0x0334, 0x0337, 0x033A, 0x033D, 0x0340, 0x0343, 0x0346, 0x0349, 0x034C, 0x034F, 0x0352, 0x0356,
0x0359, 0x035C, 0x035F, 0x0362, 0x0365, 0x0368, 0x036B, 0x036F, 0x0372, 0x0375, 0x0378, 0x037B,
0x037F, 0x0382, 0x0385, 0x0388, 0x038C, 0x038F, 0x0392, 0x0395, 0x0399, 0x039C, 0x039F, 0x03A3,
0x03A6, 0x03A9, 0x03AD, 0x03B0, 0x03B4, 0x03B7, 0x03BB, 0x03BE, 0x03C1, 0x03C5, 0x03C8, 0x03CC,
0x03CF, 0x03D3, 0x03D7, 0x03DA, 0x03DE, 0x03E1, 0x03E5, 0x03E8, 0x03EC, 0x03F0, 0x03F3, 0x03F7,
0x03FB, 0x03FE, 0xFE01, 0xFE03, 0xFE05, 0xFE07, 0xFE08, 0xFE0A, 0xFE0C, 0xFE0E, 0xFE10, 0xFE12,
0xFE14, 0xFE16, 0xFE18, 0xFE1A, 0xFE1C, 0xFE1E, 0xFE20, 0xFE21, 0xFE23, 0xFE25, 0xFE27, 0xFE29,
0xFE2B, 0xFE2D, 0xFE2F, 0xFE31, 0xFE34, 0xFE36, 0xFE38, 0xFE3A, 0xFE3C, 0xFE3E, 0xFE40, 0xFE42,
0xFE44, 0xFE46, 0xFE48, 0xFE4A, 0xFE4C, 0xFE4F, 0xFE51, 0xFE53, 0xFE55, 0xFE57, 0xFE59, 0xFE5C,
0xFE5E, 0xFE60, 0xFE62, 0xFE64, 0xFE67, 0xFE69, 0xFE6B, 0xFE6D, 0xFE6F, 0xFE72, 0xFE74, 0xFE76,
0xFE79, 0xFE7B, 0xFE7D, 0xFE7F, 0xFE82, 0xFE84, 0xFE86, 0xFE89, 0xFE8B, 0xFE8D, 0xFE90, 0xFE92,
0xFE95, 0xFE97, 0xFE99, 0xFE9C, 0xFE9E, 0xFEA1, 0xFEA3, 0xFEA5, 0xFEA8, 0xFEAA, 0xFEAD, 0xFEAF
};
// hardcoded, dumped from ADLIB.MDI
uint16 milesAdLibVolumeSensitivityTable[] = {
82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127
};
// MIDI panning to register volume table for dual OPL2
// hardcoded, dumped from ADLIB.MDI
uint8 milesAdLibPanningVolumeLookUpTable[] = {
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94,
96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127
};
class MidiDriver_Miles_AdLib : public MidiDriver_Multisource {
public:
MidiDriver_Miles_AdLib(InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount);
virtual ~MidiDriver_Miles_AdLib();
// MidiDriver
int open() override;
void close() override;
void send(uint32 b) override;
void send(int8 source, uint32 b) override;
void metaEvent(int8 source, byte type, byte *data, uint16 length) override;
MidiChannel *allocateChannel() override { return NULL; }
MidiChannel *getPercussionChannel() override { return NULL; }
bool isOpen() const override { return _isOpen; }
uint32 getBaseTempo() override { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; }
void stopAllNotes(uint8 source, uint8 channel) override;
void applySourceVolume(uint8 source) override;
void deinitSource(uint8 source) override;
uint32 property(int prop, uint32 param) override;
void setVolume(byte volume);
private:
OPL::Config::OplType _oplType;
byte _modePhysicalFmVoicesCount;
byte _modeVirtualFmVoicesCount;
bool _modeStereo;
// the version of Miles AIL/MSS to emulate
MilesVersion _milesVersion;
// Structure to hold information about current status of MIDI Channels
struct MidiChannelEntry {
byte currentPatchBank;
const InstrumentEntry *currentInstrumentPtr;
uint16 currentPitchBender;
byte currentPitchRange;
byte currentVoiceProtection;
byte currentVolume;
byte currentVolumeExpression;
byte currentPanning;
byte currentModulation;
byte currentSustain;
byte currentActiveVoicesCount;
MidiChannelEntry() : currentPatchBank(0),
currentInstrumentPtr(NULL),
currentPitchBender(MIDI_PITCH_BEND_DEFAULT),
currentPitchRange(0),
currentVoiceProtection(0),
currentVolume(0), currentVolumeExpression(0),
currentPanning(0),
currentModulation(0),
currentSustain(0),
currentActiveVoicesCount(0) { }
};
// Structure to hold information about current status of virtual FM Voices
struct VirtualFmVoiceEntry {
bool inUse;
byte actualMidiChannel;
const InstrumentEntry *currentInstrumentPtr;
bool isPhysical;
byte physicalFmVoice;
uint16 currentPriority;
byte currentOriginalMidiNote;
byte currentNote;
int16 currentTransposition;
byte currentVelocity;
bool sustained;
VirtualFmVoiceEntry(): inUse(false),
actualMidiChannel(0),
currentInstrumentPtr(NULL),
isPhysical(false), physicalFmVoice(0),
currentPriority(0),
currentOriginalMidiNote(0),
currentNote(0),
currentTransposition(0),
currentVelocity(0),
sustained(false) { }
};
// Structure to hold information about current status of physical FM Voices
struct PhysicalFmVoiceEntry {
bool inUse;
byte virtualFmVoice;
byte currentB0hReg;
PhysicalFmVoiceEntry(): inUse(false),
virtualFmVoice(0),
currentB0hReg(0) { }
};
OPL::OPL *_opl;
int _masterVolume;
bool _isOpen;
// stores information about all MIDI channels (not the actual OPL FM voice channels!)
MidiChannelEntry _midiChannels[MIDI_CHANNEL_COUNT];
// stores information about all virtual OPL FM voices
VirtualFmVoiceEntry _virtualFmVoices[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX];
// stores information about all physical OPL FM voices
PhysicalFmVoiceEntry _physicalFmVoices[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX];
// holds all instruments
InstrumentEntry *_instrumentTablePtr;
uint16 _instrumentTableCount;
bool circularPhysicalAssignment;
byte circularPhysicalAssignmentFmVoice;
void resetData();
void resetAdLib();
void resetAdLibOperatorRegisters(byte baseRegister, byte value);
void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value);
void setRegister(int reg, int value);
void setRegisterStereo(uint8 reg, uint8 valueLeft, uint8 valueRight);
int16 searchFreeVirtualFmVoiceChannel();
int16 searchFreePhysicalFmVoiceChannel();
void noteOn(byte midiChannel, byte note, byte velocity);
void noteOff(byte midiChannel, byte note);
void prioritySort();
void releaseFmVoice(byte virtualFmVoice);
void releaseSustain(byte midiChannel);
void updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags);
void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue);
void programChange(byte midiChannel, byte patchId);
const InstrumentEntry *searchInstrument(byte bankId, byte patchId);
void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2);
};
MidiDriver_Miles_AdLib::MidiDriver_Miles_AdLib(InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount)
: _masterVolume(15), _opl(0), _isOpen(false) {
_instrumentTablePtr = instrumentTablePtr;
_instrumentTableCount = instrumentTableCount;
// Set up for OPL3, we will downgrade in case we can't create OPL3 emulator
// regular AdLib (OPL2) card
_oplType = OPL::Config::kOpl3;
_modeVirtualFmVoicesCount = 20;
_modePhysicalFmVoicesCount = 18;
_modeStereo = true;
// Default to Miles v2
_milesVersion = MILES_VERSION_2;
// Older Miles Audio drivers did not do a circular assign for physical FM-voices
// Sherlock Holmes 2 used the circular assign
circularPhysicalAssignment = true;
// this way the first circular physical FM-voice search will start at FM-voice 0
circularPhysicalAssignmentFmVoice = MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX;
}
MidiDriver_Miles_AdLib::~MidiDriver_Miles_AdLib() {
delete[] _instrumentTablePtr; // is created in factory MidiDriver_Miles_AdLib_create()
}
int MidiDriver_Miles_AdLib::open() {
if (_oplType == OPL::Config::kOpl3) {
// Try to create OPL3 first
_opl = OPL::Config::create(OPL::Config::kOpl3);
}
if (!_opl) {
// not created yet, downgrade to dual OPL2
_oplType = OPL::Config::kDualOpl2;
_opl = OPL::Config::create(OPL::Config::kDualOpl2);
}
if (!_opl) {
// not created yet, downgrade to OPL2
_oplType = OPL::Config::kOpl2;
_opl = OPL::Config::create(OPL::Config::kOpl2);
}
if (!_opl) {
// We still got nothing -> can't do anything anymore
return -1;
}
if (_oplType != OPL::Config::kOpl3) {
_modeVirtualFmVoicesCount = 16;
_modePhysicalFmVoicesCount = 9;
_modeStereo = false;
}
_opl->init();
_isOpen = true;
resetData();
_timerRate = getBaseTempo();
_opl->start(new Common::Functor0Mem<void, MidiDriver_Miles_AdLib>(this, &MidiDriver_Miles_AdLib::onTimer));
resetAdLib();
return 0;
}
void MidiDriver_Miles_AdLib::close() {
delete _opl;
_isOpen = false;
}
void MidiDriver_Miles_AdLib::setVolume(byte volume) {
_masterVolume = volume;
//renewNotes(-1, true);
}
void MidiDriver_Miles_AdLib::resetData() {
ARRAYCLEAR(_midiChannels);
ARRAYCLEAR(_virtualFmVoices);
ARRAYCLEAR(_physicalFmVoices);
for (byte midiChannel = 0; midiChannel < MIDI_CHANNEL_COUNT; midiChannel++) {
// defaults, were sent to driver during driver initialization
_midiChannels[midiChannel].currentVolume = 0x7F;
_midiChannels[midiChannel].currentPanning = 0x40; // center
_midiChannels[midiChannel].currentVolumeExpression = 127;
// Miles Audio 2: hardcoded pitch range as a global (not channel specific), set to 12
// Miles Audio 3: pitch range per MIDI channel; default 2 semitones
_midiChannels[midiChannel].currentPitchBender = MIDI_PITCH_BEND_DEFAULT;
_midiChannels[midiChannel].currentPitchRange = _milesVersion == MILES_VERSION_3 ? 2 : 12;
}
}
void MidiDriver_Miles_AdLib::resetAdLib() {
if (_oplType == OPL::Config::kOpl3) {
setRegister(0x105, 1); // enable OPL3
setRegister(0x104, 0); // activate 18 2-operator FM-voices
}
// enable waveform control on both operators
setRegister(0x01, _oplType == OPL::Config::kOpl3 ? 0 : 0x20);
if (_oplType == OPL::Config::kOpl3)
setRegister(0x101, 0);
// Timer control
setRegister(0x02, 0);
setRegister(0x03, 0);
setRegister(0x04, 0x60);
setRegister(0x04, 0x80);
// Set note select and disable CSM mode
setRegister(0x08, 0);
// disable Rhythm; set vibrato and modulation depth to 1
setRegister(0xBD, 0xC0);
// reset FM voice instrument data
resetAdLibOperatorRegisters(0x20, 1);
resetAdLibOperatorRegisters(0x40, 0x3F);
resetAdLibOperatorRegisters(0x60, 0xFF);
resetAdLibOperatorRegisters(0x80, 0x0F);
resetAdLibFMVoiceChannelRegisters(0xA0, 0);
resetAdLibFMVoiceChannelRegisters(0xB0, 0);
resetAdLibFMVoiceChannelRegisters(0xC0, 0);
resetAdLibOperatorRegisters(0xE0, 0);
}
void MidiDriver_Miles_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) {
byte physicalFmVoice = 0;
for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
setRegister(baseRegister + milesAdLibOperator1Register[physicalFmVoice], value);
setRegister(baseRegister + milesAdLibOperator2Register[physicalFmVoice], value);
}
}
void MidiDriver_Miles_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) {
byte physicalFmVoice = 0;
for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
setRegister(baseRegister + milesAdLibChannelRegister[physicalFmVoice], value);
}
}
// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php
void MidiDriver_Miles_AdLib::send(uint32 b) {
byte command = b & 0xf0;
byte channel = b & 0xf;
byte op1 = (b >> 8) & 0xff;
byte op2 = (b >> 16) & 0xff;
switch (command) {
case 0x80:
noteOff(channel, op1);
break;
case 0x90:
noteOn(channel, op1, op2);
break;
case 0xb0: // Control change
controlChange(channel, op1, op2);
break;
case 0xc0: // Program Change
programChange(channel, op1);
break;
case 0xa0: // Polyphonic key pressure (aftertouch)
case 0xd0: // Channel pressure (aftertouch)
// Aftertouch doesn't seem to be implemented in the Miles Audio AdLib driver
break;
case 0xe0:
pitchBendChange(channel, op1, op2);
break;
case 0xf0: // SysEx
warning("MILES-ADLIB: SysEx: %x", b);
break;
default:
warning("MILES-ADLIB: Unknown event %02x", command);
}
}
void MidiDriver_Miles_AdLib::send(int8 source, uint32 b) {
// TODO Implement proper multisource support.
if (source == -1 || source == 0)
send(b);
}
void MidiDriver_Miles_AdLib::stopAllNotes(uint8 source, uint8 channel) {
if (!(source == 0 || source == 0xFF))
return;
for (int i = 0; i < _modeVirtualFmVoicesCount; i++) {
if (_virtualFmVoices[i].inUse && (channel == 0xFF || _virtualFmVoices[i].actualMidiChannel == channel)) {
releaseFmVoice(i);
}
}
}
uint32 MidiDriver_Miles_AdLib::property(int prop, uint32 param) {
switch (prop) {
case PROP_MILES_VERSION:
if (param == 0xFFFF)
return _milesVersion;
switch (param) {
case MILES_VERSION_3:
_milesVersion = MILES_VERSION_3;
break;
case MILES_VERSION_2:
default:
_milesVersion = MILES_VERSION_2;
}
break;
default:
return MidiDriver_Multisource::property(prop, param);
}
return 0;
}
void MidiDriver_Miles_AdLib::applySourceVolume(uint8 source) {
if (!(source == 0 || source == 0xFF))
return;
for (int i = 0; i < _modeVirtualFmVoicesCount; i++) {
if (_virtualFmVoices[i].inUse) {
updatePhysicalFmVoice(i, true, kMilesAdLibUpdateFlags_Reg_40);
}
}
}
int16 MidiDriver_Miles_AdLib::searchFreeVirtualFmVoiceChannel() {
for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (!_virtualFmVoices[virtualFmVoice].inUse)
return virtualFmVoice;
}
return -1;
}
int16 MidiDriver_Miles_AdLib::searchFreePhysicalFmVoiceChannel() {
if (!circularPhysicalAssignment) {
// Older assign logic
for (byte physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) {
if (!_physicalFmVoices[physicalFmVoice].inUse)
return physicalFmVoice;
}
} else {
// Newer one
// Remembers last physical FM-voice and searches from that spot
byte physicalFmVoice = circularPhysicalAssignmentFmVoice;
for (byte physicalFmVoiceCount = 0; physicalFmVoiceCount < _modePhysicalFmVoicesCount; physicalFmVoiceCount++) {
physicalFmVoice++;
if (physicalFmVoice >= _modePhysicalFmVoicesCount)
physicalFmVoice = 0;
if (!_physicalFmVoices[physicalFmVoice].inUse) {
circularPhysicalAssignmentFmVoice = physicalFmVoice;
return physicalFmVoice;
}
}
}
return -1;
}
void MidiDriver_Miles_AdLib::noteOn(byte midiChannel, byte note, byte velocity) {
const InstrumentEntry *instrumentPtr = NULL;
if (velocity == 0) {
noteOff(midiChannel, note);
return;
}
if (midiChannel == 9) {
// percussion channel
// search for instrument according to given note
instrumentPtr = searchInstrument(MILES_ADLIB_PERCUSSION_BANK, note);
} else {
// directly get instrument of channel
instrumentPtr = _midiChannels[midiChannel].currentInstrumentPtr;
}
if (!instrumentPtr) {
warning("MILES-ADLIB: noteOn: invalid instrument");
return;
}
//warning("Note On: channel %d, note %d, velocity %d, instrument %d/%d", midiChannel, note, velocity, instrumentPtr->bankId, instrumentPtr->patchId);
// look for free virtual FM voice
int16 virtualFmVoice = searchFreeVirtualFmVoiceChannel();
if (virtualFmVoice == -1) {
// Out of virtual voices, can't do anything about it
return;
}
// Scale back velocity
velocity = (velocity & 0x7F) >> 3;
velocity = milesAdLibVolumeSensitivityTable[velocity];
if (midiChannel != 9) {
_virtualFmVoices[virtualFmVoice].currentNote = note;
_virtualFmVoices[virtualFmVoice].currentTransposition = instrumentPtr->transposition;
} else {
// Percussion channel
_virtualFmVoices[virtualFmVoice].currentNote = instrumentPtr->transposition;
_virtualFmVoices[virtualFmVoice].currentTransposition = 0;
}
_virtualFmVoices[virtualFmVoice].inUse = true;
_virtualFmVoices[virtualFmVoice].actualMidiChannel = midiChannel;
_virtualFmVoices[virtualFmVoice].currentOriginalMidiNote = note;
_virtualFmVoices[virtualFmVoice].currentInstrumentPtr = instrumentPtr;
_virtualFmVoices[virtualFmVoice].currentVelocity = velocity;
_virtualFmVoices[virtualFmVoice].isPhysical = false;
_virtualFmVoices[virtualFmVoice].sustained = false;
_virtualFmVoices[virtualFmVoice].currentPriority = 32767;
int16 physicalFmVoice = searchFreePhysicalFmVoiceChannel();
if (physicalFmVoice == -1) {
// None found
// go through priorities and reshuffle voices
prioritySort();
return;
}
// Another voice active on this MIDI channel
_midiChannels[midiChannel].currentActiveVoicesCount++;
// Mark virtual FM-Voice as being connected to physical FM-Voice
_virtualFmVoices[virtualFmVoice].isPhysical = true;
_virtualFmVoices[virtualFmVoice].physicalFmVoice = physicalFmVoice;
// Mark physical FM-Voice as being connected to virtual FM-Voice
_physicalFmVoices[physicalFmVoice].inUse = true;
_physicalFmVoices[physicalFmVoice].virtualFmVoice = virtualFmVoice;
// Update the physical FM-Voice
updatePhysicalFmVoice(virtualFmVoice, true, kMilesAdLibUpdateFlags_Reg_All);
}
void MidiDriver_Miles_AdLib::noteOff(byte midiChannel, byte note) {
//warning("Note Off: channel %d, note %d", midiChannel, note);
// Search through all virtual FM-Voices for current midiChannel + note
for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
if ((_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) && (_virtualFmVoices[virtualFmVoice].currentOriginalMidiNote == note)) {
// found one
if (_midiChannels[midiChannel].currentSustain >= 64) {
_virtualFmVoices[virtualFmVoice].sustained = true;
continue;
}
//
releaseFmVoice(virtualFmVoice);
}
}
}
}
void MidiDriver_Miles_AdLib::prioritySort() {
byte virtualFmVoice = 0;
uint16 virtualPriority = 0;
uint16 virtualPriorities[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX];
uint16 virtualFmVoicesCount = 0;
byte midiChannel = 0;
ARRAYCLEAR(virtualPriorities);
//warning("prioritysort");
// First calculate priorities for all virtual FM voices, that are in use
for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
virtualFmVoicesCount++;
midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
if (_midiChannels[midiChannel].currentVoiceProtection >= 64) {
// Voice protection enabled
virtualPriority = 0xFFFF;
} else {
virtualPriority = _virtualFmVoices[virtualFmVoice].currentPriority;
}
byte currentActiveVoicesCount = _midiChannels[midiChannel].currentActiveVoicesCount;
if (virtualPriority >= currentActiveVoicesCount) {
virtualPriority -= _midiChannels[midiChannel].currentActiveVoicesCount;
} else {
virtualPriority = 0; // overflow, should never happen
}
virtualPriorities[virtualFmVoice] = virtualPriority;
}
}
//
while (virtualFmVoicesCount) {
uint16 unvoicedHighestPriority = 0;
byte unvoicedHighestFmVoice = 0;
uint16 voicedLowestPriority = 65535;
byte voicedLowestFmVoice = 0;
for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
virtualPriority = virtualPriorities[virtualFmVoice];
if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
// currently not physical, so unvoiced
if (virtualPriority >= unvoicedHighestPriority) {
unvoicedHighestPriority = virtualPriority;
unvoicedHighestFmVoice = virtualFmVoice;
}
} else {
// currently physical, so voiced
if (virtualPriority <= voicedLowestPriority) {
voicedLowestPriority = virtualPriority;
voicedLowestFmVoice = virtualFmVoice;
}
}
}
}
if (unvoicedHighestPriority < voicedLowestPriority)
break; // We are done
if (unvoicedHighestPriority == 0)
break;
// Safety checks
assert(_virtualFmVoices[voicedLowestFmVoice].isPhysical);
assert(!_virtualFmVoices[unvoicedHighestFmVoice].isPhysical);
// Steal this physical voice
byte physicalFmVoice = _virtualFmVoices[voicedLowestFmVoice].physicalFmVoice;
//warning("MILES-ADLIB: stealing physical FM-Voice %d from virtual FM-Voice %d for virtual FM-Voice %d", physicalFmVoice, voicedLowestFmVoice, unvoicedHighestFmVoice);
//warning("priority old %d, priority new %d", unvoicedHighestPriority, voicedLowestPriority);
releaseFmVoice(voicedLowestFmVoice);
// Get some data of the unvoiced highest priority virtual FM Voice
midiChannel = _virtualFmVoices[unvoicedHighestFmVoice].actualMidiChannel;
// Another voice active on this MIDI channel
_midiChannels[midiChannel].currentActiveVoicesCount++;
// Mark virtual FM-Voice as being connected to physical FM-Voice
_virtualFmVoices[unvoicedHighestFmVoice].isPhysical = true;
_virtualFmVoices[unvoicedHighestFmVoice].physicalFmVoice = physicalFmVoice;
// Mark physical FM-Voice as being connected to virtual FM-Voice
_physicalFmVoices[physicalFmVoice].inUse = true;
_physicalFmVoices[physicalFmVoice].virtualFmVoice = unvoicedHighestFmVoice;
// Update the physical FM-Voice
updatePhysicalFmVoice(unvoicedHighestFmVoice, true, kMilesAdLibUpdateFlags_Reg_All);
virtualFmVoicesCount--;
}
}
void MidiDriver_Miles_AdLib::releaseFmVoice(byte virtualFmVoice) {
// virtual Voice not actually played? -> exit
if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
_virtualFmVoices[virtualFmVoice].inUse = false;
return;
}
byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
byte physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice;
// stop note from playing
updatePhysicalFmVoice(virtualFmVoice, false, kMilesAdLibUpdateFlags_Reg_A0);
// this virtual FM voice isn't physical anymore
_virtualFmVoices[virtualFmVoice].isPhysical = false;
_virtualFmVoices[virtualFmVoice].inUse = false;
// Remove physical FM-Voice from being active
_physicalFmVoices[physicalFmVoice].inUse = false;
// One less voice active on this MIDI channel
assert(_midiChannels[midiChannel].currentActiveVoicesCount);
_midiChannels[midiChannel].currentActiveVoicesCount--;
}
void MidiDriver_Miles_AdLib::releaseSustain(byte midiChannel) {
// Search through all virtual FM-Voices for currently sustained notes and call noteOff on them
for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
if ((_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) && (_virtualFmVoices[virtualFmVoice].sustained)) {
// is currently sustained
// so do a noteOff (which will check current sustain controller)
noteOff(midiChannel, _virtualFmVoices[virtualFmVoice].currentOriginalMidiNote);
}
}
}
}
void MidiDriver_Miles_AdLib::updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags) {
byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel;
if (!_virtualFmVoices[virtualFmVoice].isPhysical) {
// virtual FM-Voice has no physical FM-Voice assigned? -> ignore
return;
}
byte physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice;
const InstrumentEntry *instrumentPtr = _virtualFmVoices[virtualFmVoice].currentInstrumentPtr;
uint16 op1Reg = milesAdLibOperator1Register[physicalFmVoice];
uint16 op2Reg = milesAdLibOperator2Register[physicalFmVoice];
uint16 channelReg = milesAdLibChannelRegister[physicalFmVoice];
uint16 compositeVolume = 0;
uint8 leftVolume = 0;
uint8 rightVolume = 0;
if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) {
// Calculate new volume
byte midiVolume = _midiChannels[midiChannel].currentVolume;
byte midiVolumeExpression = _midiChannels[midiChannel].currentVolumeExpression;
compositeVolume = midiVolume * midiVolumeExpression * 2;
compositeVolume = compositeVolume >> 8; // get upmost 8 bits
if (compositeVolume)
compositeVolume++; // round up in case result wasn't 0
compositeVolume = compositeVolume * _virtualFmVoices[virtualFmVoice].currentVelocity * 2;
compositeVolume = compositeVolume >> 8; // get upmost 8 bits
if (compositeVolume)
compositeVolume++; // round up in case result wasn't 0
// Scale by source volume.
compositeVolume = compositeVolume * _sources[0].volume / _sources[0].neutralVolume;
if (_userVolumeScaling) {
if (_userMute) {
compositeVolume = 0;
} else {
// Scale by user volume.
uint16 userVolume = (_sources[0].type == SOURCE_TYPE_SFX ? _userSfxVolume : _userMusicVolume); // Treat SOURCE_TYPE_UNDEFINED as music
compositeVolume = (compositeVolume * userVolume) >> 8;
}
}
// Source volume scaling might clip volume, so reduce to maximum.
compositeVolume = MIN(compositeVolume, (uint16)0x7F);
if (_oplType == OPL::Config::kDualOpl2) {
// For dual OPL2, Miles pans the notes by playing the same note on
// the left and right OPL2 chips at different volume levels.
// Calculate the volume for each chip based on the panning value.
leftVolume = (milesAdLibPanningVolumeLookUpTable[_midiChannels[midiChannel].currentPanning] * compositeVolume) >> 7;
if (leftVolume)
leftVolume++; // round up in case result wasn't 0
uint8 invertedPanning = 0 - (_midiChannels[midiChannel].currentPanning - 127);
rightVolume = (milesAdLibPanningVolumeLookUpTable[invertedPanning] * compositeVolume) >> 7;
if (rightVolume)
rightVolume++; // round up in case result wasn't 0
}
}
if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_20) {
// Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple
byte reg20op1 = instrumentPtr->reg20op1;
byte reg20op2 = instrumentPtr->reg20op2;
if (_midiChannels[midiChannel].currentModulation >= 64) {
// set bit 6 (Vibrato)
reg20op1 |= 0x40;
reg20op2 |= 0x40;
}
setRegister(0x20 + op1Reg, reg20op1);
setRegister(0x20 + op2Reg, reg20op2);
}
if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) {
// Volume (Level Key Scaling / Total Level)
byte reg40op1 = instrumentPtr->reg40op1;
byte reg40op2 = instrumentPtr->reg40op2;
uint16 volumeOp1 = (~reg40op1) & 0x3F;
uint16 volumeOp2 = (~reg40op2) & 0x3F;
if (_oplType != OPL::Config::kDualOpl2) {
if (instrumentPtr->regC0 & 1) {
// operator 2 enabled
// scale volume factor
volumeOp1 = (volumeOp1 * compositeVolume) / 127;
// 2nd operator always scaled
}
volumeOp2 = (volumeOp2 * compositeVolume) / 127;
volumeOp1 = (~volumeOp1) & 0x3F; // negate it, so we get the proper value for the register
volumeOp2 = (~volumeOp2) & 0x3F; // ditto
reg40op1 = (reg40op1 & 0xC0) | volumeOp1; // keep "scaling level" and merge in our volume
reg40op2 = (reg40op2 & 0xC0) | volumeOp2;
setRegister(0x40 + op1Reg, reg40op1);
setRegister(0x40 + op2Reg, reg40op2);
} else {
// For dual OPL2, separate register values are calculated for the
// left and right OPL2 chip.
uint8 volumeLeftOp1 = volumeOp1;
uint8 volumeRightOp1 = volumeOp1;
if (instrumentPtr->regC0 & 1) {
// operator 2 enabled
// scale volume factor
volumeLeftOp1 = (volumeLeftOp1 * leftVolume) / 127;
volumeRightOp1 = (volumeRightOp1 * rightVolume) / 127;
// 2nd operator always scaled
}
uint8 volumeLeftOp2 = (volumeOp2 * leftVolume) / 127;
uint8 volumeRightOp2 = (volumeOp2 * rightVolume) / 127;
volumeLeftOp1 = (~volumeLeftOp1) & 0x3F; // negate it, so we get the proper value for the register
volumeRightOp1 = (~volumeRightOp1) & 0x3F;
volumeLeftOp2 = (~volumeLeftOp2) & 0x3F; // ditto
volumeRightOp2 = (~volumeRightOp2) & 0x3F;
uint8 reg40op1left = (reg40op1 & 0xC0) | volumeLeftOp1; // keep "scaling level" and merge in our volume
uint8 reg40op1right = (reg40op1 & 0xC0) | volumeRightOp1;
uint8 reg40op2left = (reg40op2 & 0xC0) | volumeLeftOp2;
uint8 reg40op2right = (reg40op2 & 0xC0) | volumeRightOp2;
setRegisterStereo(0x40 + op1Reg, reg40op1left, reg40op1right);
setRegisterStereo(0x40 + op2Reg, reg40op2left, reg40op2right);
}
}
if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_60) {
// Attack Rate / Decay Rate
// Sustain Level / Release Rate
byte reg60op1 = instrumentPtr->reg60op1;
byte reg60op2 = instrumentPtr->reg60op2;
byte reg80op1 = instrumentPtr->reg80op1;
byte reg80op2 = instrumentPtr->reg80op2;
setRegister(0x60 + op1Reg, reg60op1);
setRegister(0x60 + op2Reg, reg60op2);
setRegister(0x80 + op1Reg, reg80op1);
setRegister(0x80 + op2Reg, reg80op2);
}
if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_E0) {
// Waveform Select
byte regE0op1 = instrumentPtr->regE0op1;
byte regE0op2 = instrumentPtr->regE0op2;
setRegister(0xE0 + op1Reg, regE0op1);
setRegister(0xE0 + op2Reg, regE0op2);
}
if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_C0) {
// Feedback / Algorithm
byte regC0 = instrumentPtr->regC0;
if (_oplType == OPL::Config::kOpl3) {
// Panning for OPL3
byte panning = _midiChannels[midiChannel].currentPanning;
if (panning <= MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT) {
regC0 |= 0x20; // left speaker only
} else if (panning >= MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT) {
regC0 |= 0x10; // right speaker only
} else {
regC0 |= 0x30; // center
}
}
setRegister(0xC0 + channelReg, regC0);
}
if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_A0) {
// Frequency / Key-On
// Octave / F-Number / Key-On
if (!keyOn) {
// turn off note
byte regB0 = _physicalFmVoices[physicalFmVoice].currentB0hReg & 0x1F; // remove bit 5 "key on"
setRegister(0xB0 + channelReg, regB0);
} else {
// turn on note, calculate frequency, octave...
int16 pitchBender = _midiChannels[midiChannel].currentPitchBender;
byte pitchRange = _midiChannels[midiChannel].currentPitchRange;
int16 currentNote = _virtualFmVoices[virtualFmVoice].currentNote;
int16 physicalNote = 0;
int16 halfTone = 0;
uint16 frequency = 0;
uint16 frequencyIdx = 0;
byte octave = 0;
pitchBender -= 0x2000;
pitchBender = pitchBender >> 5; // divide by 32
pitchBender = pitchBender * pitchRange; // pitchrange 12: now +0x0C00 to -0xC00
// difference between Miles Audio 2 + 3
// Miles Audio 2 used a pitch range of 12, which was basically hardcoded
// Miles Audio 3 used an array, which got set by control change events
currentNote += _virtualFmVoices->currentTransposition;
// Normalize note
currentNote -= 24;
do {
currentNote += 12;
} while (currentNote < 0);
currentNote += 12;
do {
currentNote -= 12;
} while (currentNote > 95);
// combine note + pitchbender, also adjust by 8 for rounding
currentNote = (currentNote << 8) + pitchBender + 8;
currentNote = currentNote >> 4; // get actual note
// Normalize
currentNote -= (12 * 16);
do {
currentNote += (12 * 16);
} while (currentNote < 0);
currentNote += (12 * 16);
do {
currentNote -= (12 * 16);
} while (currentNote > ((96 * 16) - 1));
physicalNote = currentNote >> 4;
halfTone = physicalNote % 12; // remainder of physicalNote / 12
frequencyIdx = (halfTone << 4) + (currentNote & 0x0F);
assert(frequencyIdx < sizeof(milesAdLibFrequencyLookUpTable));
frequency = milesAdLibFrequencyLookUpTable[frequencyIdx];
octave = (physicalNote / 12) - 1;
if (frequency & 0x8000)
octave++;
if (octave & 0x80) {
octave++;
frequency = frequency >> 1;
}
byte regA0 = frequency & 0xFF;
byte regB0 = ((frequency >> 8) & 0x03) | (octave << 2) | 0x20;
setRegister(0xA0 + channelReg, regA0);
setRegister(0xB0 + channelReg, regB0);
_physicalFmVoices[physicalFmVoice].currentB0hReg = regB0;
}
}
//warning("end of update voice");
}
void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) {
uint16 registerUpdateFlags = kMilesAdLibUpdateFlags_None;
switch (controllerNumber) {
case MILES_CONTROLLER_SELECT_PATCH_BANK:
//warning("patch bank channel %d, bank %x", midiChannel, controllerValue);
_midiChannels[midiChannel].currentPatchBank = controllerValue;
break;
case MILES_CONTROLLER_PROTECT_VOICE:
_midiChannels[midiChannel].currentVoiceProtection = controllerValue;
break;
case MILES_CONTROLLER_PROTECT_TIMBRE:
// It seems that this can get ignored, because we don't cache timbres at all
break;
case MIDI_CONTROLLER_MODULATION:
_midiChannels[midiChannel].currentModulation = controllerValue;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20;
break;
case MIDI_CONTROLLER_VOLUME:
_midiChannels[midiChannel].currentVolume = controllerValue;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
break;
case MIDI_CONTROLLER_EXPRESSION:
_midiChannels[midiChannel].currentVolumeExpression = controllerValue;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40;
break;
case MIDI_CONTROLLER_PANNING:
_midiChannels[midiChannel].currentPanning = controllerValue;
if (_modeStereo) {
// Update register only in case we are in stereo mode
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_C0;
}
break;
case MIDI_CONTROLLER_SUSTAIN:
_midiChannels[midiChannel].currentSustain = controllerValue;
if (controllerValue < 64) {
releaseSustain(midiChannel);
}
break;
case MILES_CONTROLLER_PITCH_RANGE:
// Note that this is in fact the MIDI data entry MSB controller. To use
// this to set pitch bend range, the pitch bend range RPN should first
// be selected using the RPN MSB and LSB controllers.
// MSS does not support the RPN controllers and assumes that any use of
// the data entry MSB controller is to set the pitch bend range.
// Miles Audio 3 feature
if (_milesVersion == MILES_VERSION_3)
_midiChannels[midiChannel].currentPitchRange = controllerValue;
break;
case MIDI_CONTROLLER_RESET_ALL_CONTROLLERS:
_midiChannels[midiChannel].currentSustain = 0;
releaseSustain(midiChannel);
_midiChannels[midiChannel].currentModulation = 0;
_midiChannels[midiChannel].currentVolumeExpression = 127;
_midiChannels[midiChannel].currentPitchBender = MIDI_PITCH_BEND_DEFAULT;
registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20 | kMilesAdLibUpdateFlags_Reg_40 | kMilesAdLibUpdateFlags_Reg_A0;
break;
case MIDI_CONTROLLER_ALL_NOTES_OFF:
for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
// used
if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) {
// by our current MIDI channel -> noteOff
noteOff(midiChannel, _virtualFmVoices[virtualFmVoice].currentNote);
}
}
}
break;
default:
//warning("MILES-ADLIB: Unsupported control change %d", controllerNumber);
break;
}
if (registerUpdateFlags) {
for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
// used
if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) {
// by our current MIDI channel -> update
updatePhysicalFmVoice(virtualFmVoice, true, registerUpdateFlags);
}
}
}
}
}
void MidiDriver_Miles_AdLib::programChange(byte midiChannel, byte patchId) {
const InstrumentEntry *instrumentPtr = NULL;
byte patchBank = _midiChannels[midiChannel].currentPatchBank;
//warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, patchBank);
// we check, if we actually have data for the requested instrument...
instrumentPtr = searchInstrument(patchBank, patchId);
if (!instrumentPtr) {
warning("MILES-ADLIB: unknown instrument requested (%d, %d)", patchBank, patchId);
return;
}
// and remember it in that case for the current MIDI-channel
_midiChannels[midiChannel].currentInstrumentPtr = instrumentPtr;
}
const InstrumentEntry *MidiDriver_Miles_AdLib::searchInstrument(byte bankId, byte patchId) {
const InstrumentEntry *instrumentPtr = _instrumentTablePtr;
for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) {
if ((instrumentPtr->bankId == bankId) && (instrumentPtr->patchId == patchId)) {
return instrumentPtr;
}
instrumentPtr++;
}
return NULL;
}
void MidiDriver_Miles_AdLib::pitchBendChange(byte midiChannel, byte parameter1, byte parameter2) {
// Miles Audio actually didn't shift parameter 2 1 down in here
// which means in memory it used a 15-bit pitch bender, which also means the default was 0x4000
if ((parameter1 & 0x80) || (parameter2 & 0x80)) {
warning("MILES-ADLIB: invalid pitch bend change");
return;
}
_midiChannels[midiChannel].currentPitchBender = parameter1 | (parameter2 << 7);
for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) {
if (_virtualFmVoices[virtualFmVoice].inUse) {
// used
if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) {
// by our current MIDI channel -> update
updatePhysicalFmVoice(virtualFmVoice, true, kMilesAdLibUpdateFlags_Reg_A0);
}
}
}
}
void MidiDriver_Miles_AdLib::metaEvent(int8 source, byte type, byte *data, uint16 length) {
if (type == MIDI_META_END_OF_TRACK && source >= 0)
// Stop hanging notes and release resources used by this source.
deinitSource(source);
}
void MidiDriver_Miles_AdLib::deinitSource(uint8 source) {
if (!(source == 0 || source == 0xFF))
return;
// Turn off sustained notes.
for (int i = 0; i < MIDI_CHANNEL_COUNT; i++) {
controlChange(i, MIDI_CONTROLLER_SUSTAIN, 0);
}
// Stop fades and turn off non-sustained notes.
MidiDriver_Multisource::deinitSource(source);
}
void MidiDriver_Miles_AdLib::setRegister(int reg, int value) {
if (!(reg & 0x100) || _oplType == OPL::Config::kDualOpl2) {
_opl->write(0x220, reg);
_opl->write(0x221, value);
//warning("OPL write %x %x (%d)", reg, value, value);
}
if ((reg & 0x100) || _oplType == OPL::Config::kDualOpl2) {
_opl->write(0x222, reg & 0xFF);
_opl->write(0x223, value);
//warning("OPL3 write %x %x (%d)", reg & 0xFF, value, value);
}
}
void MidiDriver_Miles_AdLib::setRegisterStereo(uint8 reg, uint8 valueLeft, uint8 valueRight) {
_opl->write(0x220, reg);
_opl->write(0x221, valueLeft);
_opl->write(0x222, reg);
_opl->write(0x223, valueRight);
}
MidiDriver_Multisource *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib, Common::SeekableReadStream *streamOPL3) {
// Load adlib instrument data from file SAMPLE.AD (OPL3: SAMPLE.OPL)
Common::String timbreFilename;
Common::SeekableReadStream *timbreStream = nullptr;
bool preferOPL3 = false;
Common::File *fileStream = new Common::File();
uint32 fileSize = 0;
uint32 fileDataOffset = 0;
uint32 fileDataLeft = 0;
uint32 streamSize = 0;
byte *streamDataPtr = nullptr;
byte curBankId = 0;
byte curPatchId = 0;
InstrumentEntry *instrumentTablePtr = nullptr;
uint16 instrumentTableCount = 0;
InstrumentEntry *instrumentPtr = nullptr;
uint32 instrumentOffset = 0;
uint16 instrumentDataSize = 0;
// Logic:
// We prefer OPL3 timbre data in case OPL3 is available in ScummVM
// If it's not or OPL3 timbre data is not available, we go for AdLib timbre data
// And if OPL3 is not available in ScummVM and also AdLib timbre data is not available,
// we then still go for OPL3 timbre data.
//
// Note: for most games OPL3 timbre data + AdLib timbre data is the same.
// And at least in theory we should still be able to use OPL3 timbre data even for AdLib.
// However there is a special OPL3-specific timbre format, which is currently not supported.
// In this case the error message "unsupported instrument size" should appear. I haven't found
// a game that uses it, which is why I haven't implemented it yet.
if (OPL::Config::detect(OPL::Config::kOpl3) >= 0) {
// OPL3 available, prefer OPL3 timbre data because of this
preferOPL3 = true;
}
// Check if streams were passed to us and select one of them
if ((streamAdLib) || (streamOPL3)) {
// At least one stream was passed by caller
if (preferOPL3) {
// Prefer OPL3 timbre stream in case OPL3 is available
timbreStream = streamOPL3;
}
if (!timbreStream) {
// Otherwise prefer AdLib timbre stream first
if (streamAdLib) {
timbreStream = streamAdLib;
} else {
// If not available, use OPL3 timbre stream
if (streamOPL3) {
timbreStream = streamOPL3;
}
}
}
}
// Now check if any filename was passed to us
if ((!filenameAdLib.empty()) || (!filenameOPL3.empty())) {
// If that's the case, check if one of those exists
if (preferOPL3) {
// OPL3 available
if (!filenameOPL3.empty()) {
if (fileStream->exists(filenameOPL3)) {
// If OPL3 available, prefer OPL3 timbre file in case file exists
timbreFilename = filenameOPL3;
}
}
if (timbreFilename.empty()) {
if (!filenameAdLib.empty()) {
if (fileStream->exists(filenameAdLib)) {
// otherwise use AdLib timbre file, if it exists
timbreFilename = filenameAdLib;
}
}
}
} else {
// OPL3 not available
// Prefer the AdLib one for now
if (!filenameAdLib.empty()) {
if (fileStream->exists(filenameAdLib)) {
// if AdLib file exists, use it
timbreFilename = filenameAdLib;
}
}
if (timbreFilename.empty()) {
if (!filenameOPL3.empty()) {
if (fileStream->exists(filenameOPL3)) {
// if OPL3 file exists, use it
timbreFilename = filenameOPL3;
}
}
}
}
if (timbreFilename.empty() && (!timbreStream)) {
// If none of them exists and also no stream was passed, we can't do anything about it
if (!filenameAdLib.empty()) {
if (!filenameOPL3.empty()) {
error("MILES-ADLIB: could not open timbre file (%s or %s)", filenameAdLib.c_str(), filenameOPL3.c_str());
} else {
error("MILES-ADLIB: could not open timbre file (%s)", filenameAdLib.c_str());
}
} else {
error("MILES-ADLIB: could not open timbre file (%s)", filenameOPL3.c_str());
}
}
}
if (!timbreFilename.empty()) {
// Filename was passed to us and file exists (this is the common case for most games)
// We prefer this situation
if (!fileStream->open(timbreFilename))
error("MILES-ADLIB: could not open timbre file (%s)", timbreFilename.c_str());
streamSize = fileStream->size();
streamDataPtr = new byte[streamSize];
if (fileStream->read(streamDataPtr, streamSize) != streamSize)
error("MILES-ADLIB: error while reading timbre file (%s)", timbreFilename.c_str());
fileStream->close();
} else if (timbreStream) {
// Timbre data was passed directly (possibly read from resource file by caller)
// Currently used by "Amazon Guardians of Eden", "Simon 2" and "Return To Zork"
streamSize = timbreStream->size();
streamDataPtr = new byte[streamSize];
if (timbreStream->read(streamDataPtr, streamSize) != streamSize)
error("MILES-ADLIB: error while reading timbre stream");
} else {
error("MILES-ADLIB: timbre filenames nor timbre stream were passed");
}
delete fileStream;
// File is like this:
// [patch:BYTE] [bank:BYTE] [patchoffset:UINT32]
// ...
// until patch + bank are both 0xFF, which signals end of header
// First we check how many entries there are
fileDataOffset = 0;
fileDataLeft = streamSize;
while (1) {
if (fileDataLeft < 6)
error("MILES-ADLIB: unexpected EOF in instrument file");
curPatchId = streamDataPtr[fileDataOffset++];
curBankId = streamDataPtr[fileDataOffset++];
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
break;
fileDataOffset += 4; // skip over offset
instrumentTableCount++;
}
if (instrumentTableCount == 0)
error("MILES-ADLIB: no instruments in instrument file");
// Allocate space for instruments
instrumentTablePtr = new InstrumentEntry[instrumentTableCount];
// Now actually read all entries
instrumentPtr = instrumentTablePtr;
fileDataOffset = 0;
fileDataLeft = fileSize;
while (1) {
curPatchId = streamDataPtr[fileDataOffset++];
curBankId = streamDataPtr[fileDataOffset++];
if ((curBankId == 0xFF) && (curPatchId == 0xFF))
break;
instrumentOffset = READ_LE_UINT32(streamDataPtr + fileDataOffset);
fileDataOffset += 4;
instrumentPtr->bankId = curBankId;
instrumentPtr->patchId = curPatchId;
instrumentDataSize = READ_LE_UINT16(streamDataPtr + instrumentOffset);
if (instrumentDataSize != 14)
error("MILES-ADLIB: unsupported instrument size");
instrumentPtr->transposition = (signed char)streamDataPtr[instrumentOffset + 2];
instrumentPtr->reg20op1 = streamDataPtr[instrumentOffset + 3];
instrumentPtr->reg40op1 = streamDataPtr[instrumentOffset + 4];
instrumentPtr->reg60op1 = streamDataPtr[instrumentOffset + 5];
instrumentPtr->reg80op1 = streamDataPtr[instrumentOffset + 6];
instrumentPtr->regE0op1 = streamDataPtr[instrumentOffset + 7];
instrumentPtr->regC0 = streamDataPtr[instrumentOffset + 8];
instrumentPtr->reg20op2 = streamDataPtr[instrumentOffset + 9];
instrumentPtr->reg40op2 = streamDataPtr[instrumentOffset + 10];
instrumentPtr->reg60op2 = streamDataPtr[instrumentOffset + 11];
instrumentPtr->reg80op2 = streamDataPtr[instrumentOffset + 12];
instrumentPtr->regE0op2 = streamDataPtr[instrumentOffset + 13];
// Instrument read, next instrument please
instrumentPtr++;
}
// Free instrument file/stream data
delete[] streamDataPtr;
return new MidiDriver_Miles_AdLib(instrumentTablePtr, instrumentTableCount);
}
} // End of namespace Audio