/* 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 .
*
*/
#include "mididrv_m_adlib.h"
namespace Ultima {
namespace Nuvie {
const uint16 MidiDriver_M_AdLib::FNUM_VALUES[24] = {
0x0, 0x158, 0x182, 0x1B0, 0x1CC, 0x203, 0x241, 0x286,
0x0, 0x16A, 0x196, 0x1C7, 0x1E4, 0x21E, 0x25F, 0x2A8,
0x0, 0x147, 0x16E, 0x19A, 0x1B5, 0x1E9, 0x224, 0x266
};
MidiDriver_M_AdLib::MidiDriver_M_AdLib() : MidiDriver_ADLIB_Multisource(OPL::Config::kOpl2, 60) {
_modulationDepth = MODULATION_DEPTH_LOW;
_vibratoDepth = VIBRATO_DEPTH_LOW;
_allocationMode = ALLOCATION_MODE_STATIC;
_instrumentWriteMode = INSTRUMENT_WRITE_MODE_PROGRAM_CHANGE;
Common::fill(_slideValues, _slideValues + ARRAYSIZE(_slideValues), 0);
Common::fill(_vibratoDepths, _vibratoDepths + ARRAYSIZE(_vibratoDepths), 0);
Common::fill(_vibratoFactors, _vibratoFactors + ARRAYSIZE(_vibratoFactors), 0);
Common::fill(_vibratoCurrentDepths, _vibratoCurrentDepths + ARRAYSIZE(_vibratoCurrentDepths), 0);
Common::fill(_vibratoDirections, _vibratoDirections + ARRAYSIZE(_vibratoDirections), VIBRATO_DIRECTION_RISING);
Common::fill(_fadeDirections, _fadeDirections + ARRAYSIZE(_fadeDirections), FADE_DIRECTION_NONE);
Common::fill(_fadeStepDelays, _fadeStepDelays + ARRAYSIZE(_fadeStepDelays), 0);
Common::fill(_fadeCurrentDelays, _fadeCurrentDelays + ARRAYSIZE(_fadeCurrentDelays), 0);
_instrumentBank = new OplInstrumentDefinition[16];
}
MidiDriver_M_AdLib::~MidiDriver_M_AdLib() {
delete[] _instrumentBank;
}
void MidiDriver_M_AdLib::send(int8 source, uint32 b) {
byte command = b & 0xF0;
byte channel = b & 0x0F;
byte data = (b >> 8) & 0xFF;
ActiveNote *activeNote;
//uint16 channelOffset;
//uint16 frequency;
switch (command) {
case 0x00: // Note off
// The original driver always writes both F-num registers with the
// supplied note value; it does not check what the active note value
// is. The standard noteOff implementation checks if the active note
// value matches an active note on the data channel. If the note off
// does not match the active note, this could cause a hanging note.
// None of the Ultima 6 tracks seem to have this problem however.
/* DEBUG: Write Ax register
// Calculate the frequency.
channelOffset = determineChannelRegisterOffset(channel);
frequency = calculateFrequency(channel, source, data);
// Write the low 8 frequency bits.
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
*/
noteOff(channel, data, 0, source);
break;
case 0x10: // Note on
// Stop vibrato (if active)
_vibratoDirections[channel] = VIBRATO_DIRECTION_RISING;
_vibratoCurrentDepths[channel] = 0;
// The original driver always writes a note off before a note on, even
// if there is no note active. The standard noteOn implementation only
// writes a note off if a note is active. This causes no audible
// difference however.
/* DEBUG: Write note off
_activeNotesMutex.lock();
// Melodic instrument.
activeNote = &_activeNotes[channel];
// Calculate the frequency.
channelOffset = determineChannelRegisterOffset(channel);
frequency = calculateFrequency(channel, source, data);
activeNote->oplFrequency = frequency;
// Write the low 8 frequency bits.
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset, frequency >> 8);
// Update the active note data.
activeNote->noteActive = false;
activeNote->noteSustained = false;
// Register the current note counter value when turning off a note.
activeNote->noteCounterValue = _noteCounter;
_activeNotesMutex.unlock();
*/
noteOn(channel, data, 0x7F, source);
break;
case 0x20: // Set pitch
// If a note is already active on this channel, this will just update
// the pitch. Otherwise it is the same as a Note on.
_activeNotesMutex.lock();
// Determine the OPL channel to use and the active note data to update.
uint8 oplChannel;
oplChannel = 0xFF;
activeNote = nullptr;
// Allocate a melodic OPL channel.
InstrumentInfo instrumentInfo;
instrumentInfo = { };
oplChannel = allocateOplChannel(channel, source, instrumentInfo);
if (oplChannel != 0xFF)
activeNote = &_activeNotes[oplChannel];
if (activeNote != nullptr) {
if (!activeNote->noteActive) {
// If there is no note active currently, treat this as a
// regular note on.
noteOn(channel, data, 0x7F, source);
} else {
// If there is a note active, only update the frequency.
activeNote->note = data;
activeNote->oplNote = data;
// Calculate and write frequency and block and write key on bit.
writeFrequency(oplChannel);
}
}
_activeNotesMutex.unlock();
break;
case 0x30: // Set level
// This directly writes the OPL level register of the carrier operator.
// This can also write the key scale level bits.
// Note that the control data volume field is used for an OPL level
// value, not for a MIDI channel volume value as usual.
// Stop fade (if active)
_fadeDirections[channel] = FADE_DIRECTION_NONE;
_controlData[source][channel].volume = data;
if (_activeNotes[channel].instrumentDef)
writeVolume(channel, 1);
break;
case 0x40: // Set modulation
modulation(channel, data, source);
break;
case 0x50: // Set slide
// Start or stop a pitch slide. The slide is processed by onTimer.
_slideValues[channel] = (int8)data;
break;
case 0x60: // Set vibrato
// Turns vibrato on or off or modifies the parameters. High nibble
// is the vibrato depth, low nibble is the vibrato factor. The vibrato
// is processed by onTimer.
_vibratoDepths[channel] = data >> 4;
_vibratoFactors[channel] = data & 0xF;
break;
case 0x70: // Program change
programChange(channel, data, source);
break;
case 0x80: // Subcommand
uint8 subcommand;
subcommand = channel;
switch (subcommand) {
case 0x1: // Call subroutine
case 0x2: // Delay
// These are handled by the parser.
break;
case 0x3: // Load instrument
// This should be sent to the driver as a meta event.
warning("MidiDriver_M_AdLib::send - Received load instrument as command");
break;
case 0x5: // Fade out
case 0x6: // Fade in
// Starts a volume fade in or out. The high nibble of the data byte
// is the channel, the low nibble is the fade delay. The fade is
// processed by onTimer.
channel = data >> 4;
_fadeDirections[channel] = (subcommand == 0x5 ? FADE_DIRECTION_FADE_OUT : FADE_DIRECTION_FADE_IN);
uint8 delay;
delay = (data & 0xF) + 1;
_fadeStepDelays[channel] = _fadeCurrentDelays[channel] = delay;
break;
default: // Unknown subcommand
break;
}
break;
case 0xE0: // Set loop point
case 0xF0: // Return
// These are handled by the parser.
break;
default: // Unknown command
break;
}
}
void MidiDriver_M_AdLib::metaEvent(int8 source, byte type, const byte* data, uint16 length) {
if (type == 0x3) {
// Load instrument
// This loads an OPL instrument definition into the bank. The first
// byte is the instrument bank number. The next 11 bytes contain the
// instrument parameters.
if (length < 12) {
warning("Received a load instrument event with insufficient data length");
return;
}
byte instrumentNumber = data[0];
assert(instrumentNumber < 16);
// This was allocated in the constructor so it's not really const
OplInstrumentDefinition *instrument = const_cast(&_instrumentBank[instrumentNumber]);
instrument->fourOperator = false;
instrument->rhythmType = RHYTHM_TYPE_UNDEFINED;
instrument->operator0.freqMultMisc = data[1];
instrument->operator0.level = data[2];
instrument->operator0.decayAttack = data[3];
instrument->operator0.releaseSustain = data[4];
instrument->operator0.waveformSelect = data[5];
instrument->operator1.freqMultMisc = data[6];
instrument->operator1.level = data[7];
instrument->operator1.decayAttack = data[8];
instrument->operator1.releaseSustain = data[9];
instrument->operator1.waveformSelect = data[10];
instrument->connectionFeedback0 = data[11];
instrument->transpose = 0;
}
}
void MidiDriver_M_AdLib::programChange(uint8 channel, uint8 program, uint8 source) {
assert(program < 16);
// Changing the instrument overwrites the current volume and modulation
// settings.
_controlData[source][channel].volume = _instrumentBank[program].operator1.level;
_controlData[source][channel].modulation = _instrumentBank[program].operator0.level;
// Note that this will turn off an active note on the channel if there is
// one. The original driver does not do this. Changing the instrument while
// a note is playing would be strange though; none of the tracks in
// Ultima 6 seem to do this.
MidiDriver_ADLIB_Multisource::programChange(channel, program, source);
}
void MidiDriver_M_AdLib::modulation(uint8 channel, uint8 modulation, uint8 source) {
// This directly writes the OPL level register of the modulator
// operator.
// Note that the control data modulation field is used for an OPL level
// value, not for a MIDI channel modulation value as usual.
_controlData[source][channel].modulation = modulation;
uint16 registerOffset = determineOperatorRegisterOffset(channel, 0);
writeRegister(OPL_REGISTER_BASE_LEVEL + registerOffset, modulation);
}
uint8 MidiDriver_M_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
// Allocation of M data channels to OPL output channels is simply 1 on 1.
return channel;
}
void MidiDriver_M_AdLib::writeFrequency(uint8 oplChannel, OplInstrumentRhythmType rhythmType) {
Common::StackLock lock(_activeNotesMutex);
ActiveNote *activeNote = &_activeNotes[oplChannel];
uint8 mNote = activeNote->oplNote;
// Calculate the frequency.
uint16 channelOffset = determineChannelRegisterOffset(oplChannel, activeNote->instrumentDef->fourOperator);
uint16 frequency = calculateFrequency(activeNote->channel, activeNote->source, mNote);
activeNote->oplFrequency = frequency;
// Write the low 8 frequency bits.
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(frequency >> 8) | (activeNote->noteActive ? OPL_MASK_KEYON : 0));
}
uint16 MidiDriver_M_AdLib::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
// An M note value consist of a note lookup value in the low 5 bits and
// a block (octave) value in the high 3 bits.
uint8 fnumIndex = note & 0x1F;
assert(fnumIndex < 24);
uint16 oplFrequency = FNUM_VALUES[fnumIndex];
uint8 block = note >> 5;
return oplFrequency | (block << 10);
}
uint8 MidiDriver_M_AdLib::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition& instrumentDef, uint8 operatorNum) {
// M directy uses OPL level values, so no calculation is necessary.
return _controlData[source][channel].volume & OPL_MASK_LEVEL;
}
void MidiDriver_M_AdLib::writeVolume(uint8 oplChannel, uint8 operatorNum, OplInstrumentRhythmType rhythmType) {
ActiveNote *activeNote = (rhythmType == RHYTHM_TYPE_UNDEFINED ? &_activeNotes[oplChannel] : &_activeRhythmNotes[rhythmType - 1]);
// Calculate operator volume.
uint16 registerOffset = determineOperatorRegisterOffset(
oplChannel, operatorNum, rhythmType, activeNote->instrumentDef->fourOperator);
uint8 level = calculateVolume(activeNote->channel, activeNote->source, activeNote->velocity,
*activeNote->instrumentDef, operatorNum);
// Add key scaling level from the last written volume or modulation value
// to the calculated level.
MidiChannelControlData *controlData = &_controlData[activeNote->source][activeNote->channel];
uint8 ksl = (operatorNum == 0 ? controlData->modulation : controlData->volume) & ~OPL_MASK_LEVEL;
writeRegister(OPL_REGISTER_BASE_LEVEL + registerOffset, level | ksl);
}
void MidiDriver_M_AdLib::deinitSource(uint8 source) {
// Reset effects status.
Common::fill(_slideValues, _slideValues + ARRAYSIZE(_slideValues), 0);
Common::fill(_vibratoFactors, _vibratoFactors + ARRAYSIZE(_vibratoFactors), 0);
Common::fill(_vibratoCurrentDepths, _vibratoCurrentDepths + ARRAYSIZE(_vibratoCurrentDepths), 0);
Common::fill(_vibratoDirections, _vibratoDirections + ARRAYSIZE(_vibratoDirections), VIBRATO_DIRECTION_RISING);
Common::fill(_fadeDirections, _fadeDirections + ARRAYSIZE(_fadeDirections), FADE_DIRECTION_NONE);
MidiDriver_ADLIB_Multisource::deinitSource(source);
}
void MidiDriver_M_AdLib::onTimer() {
MidiDriver_ADLIB_Multisource::onTimer();
_activeNotesMutex.lock();
// Process effects.
for (int i = 8; i >= 0; i--) {
ActiveNote *activeNote = &_activeNotes[i];
if (_slideValues[i] != 0) {
// Process slide. A slide continually increases or decreases the
// note frequency until it is turned off.
// Increase or decrease the OPL frequency by the slide value.
// Note that this can potentially over- or underflow the OPL
// frequency, but there is no bounds checking in the original
// driver either.
activeNote->oplFrequency += _slideValues[i];
// Write the low 8 frequency bits.
uint16 channelOffset = determineChannelRegisterOffset(i);
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, activeNote->oplFrequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(activeNote->oplFrequency >> 8) | (activeNote->noteActive ? OPL_MASK_KEYON : 0));
} else if (_vibratoFactors[i] > 0 && activeNote->noteActive) {
// Process vibrato. Vibrato will alternately increase and decrease
// the frequency up to the maximum depth.
// The depth is the difference between the minimum and maximum
// frequency change, so a positive number, twice the amplitude.
// The current depth is converted to the actual frequency offset by
// subtracting half the total depth. The offset is then multiplied
// by the vibrato factor.
// Note that current depth starts at 0, so minimum depth, rather
// than at neutral (half depth).
// Flip vibrato direction if the maximum or minimum depth has been reached.
if (_vibratoCurrentDepths[i] >= _vibratoDepths[i]) {
_vibratoDirections[i] = VIBRATO_DIRECTION_FALLING;
} else if (_vibratoCurrentDepths[i] == 0) {
_vibratoDirections[i] = VIBRATO_DIRECTION_RISING;
}
// Update current depth.
if (_vibratoDirections[i] == VIBRATO_DIRECTION_FALLING) {
_vibratoCurrentDepths[i]--;
} else {
_vibratoCurrentDepths[i]++;
}
// Convert the depth to an OPL frequency offset.
int vibratoOffset = _vibratoCurrentDepths[i] - (_vibratoDepths[i] >> 1);
vibratoOffset *= _vibratoFactors[i];
uint16 frequency = activeNote->oplFrequency + vibratoOffset;
// Write the low 8 frequency bits.
uint16 channelOffset = determineChannelRegisterOffset(i);
writeRegister(OPL_REGISTER_BASE_FNUMLOW + channelOffset, frequency & 0xFF);
// Write the high 2 frequency bits and block and add the key on bit.
writeRegister(OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON + channelOffset,
(frequency >> 8) | (activeNote->noteActive ? OPL_MASK_KEYON : 0));
}
if (_fadeDirections[i] != FADE_DIRECTION_NONE && --_fadeCurrentDelays[i] == 0) {
// Process fade. A fade will continually increase or decrease the
// level (volume) until the maximum or minimum volume is reached.
// Then the fade is stopped. A delay determines the speed of the
// fade by increasing the number of ticks between each increase or
// decrease.
// Reset delay.
_fadeCurrentDelays[i] = _fadeStepDelays[i];
// Calculate new channel level.
int newChannelLevel = _controlData[activeNote->source][i].volume + (_fadeDirections[i] == FADE_DIRECTION_FADE_IN ? -1 : 1);
if (newChannelLevel < 0 || newChannelLevel > 0x3F) {
// Minimum or maximum level reached. Stop the fade.
newChannelLevel = (newChannelLevel < 0) ? 0 : 0x3F;
_fadeDirections[i] = FADE_DIRECTION_NONE;
}
// Apply the new volume.
_controlData[activeNote->source][i].volume = newChannelLevel;
writeVolume(i, 1);
}
}
_activeNotesMutex.unlock();
}
} // End of namespace Nuvie
} // End of namespace Ultima