scummvm/engines/darkseed/adlib_dsf.cpp
2025-02-24 21:05:12 +01:00

232 lines
9 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 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 "darkseed/adlib_dsf.h"
namespace Darkseed {
// F-num values used for the 12 octave notes.
const uint16 MidiDriver_DarkSeedFloppy_AdLib::OPL_NOTE_FREQUENCIES[12] = {
0x157, 0x16B, 0x181, 0x198, 0x1B0, 0x1CA, 0x1E5, 0x202, 0x220, 0x241, 0x263, 0x287
};
MidiDriver_DarkSeedFloppy_AdLib::MidiDriver_DarkSeedFloppy_AdLib(OPL::Config::OplType oplType, int timerFrequency) :
MidiDriver_ADLIB_Multisource::MidiDriver_ADLIB_Multisource(oplType, timerFrequency) {
_dsfInstrumentBank = new OplInstrumentDefinition[128];
_instrumentBank = _dsfInstrumentBank;
Common::fill(_sourcePriority, _sourcePriority + sizeof(_sourcePriority), 0);
_defaultChannelVolume = 0x7F;
// Dark Seed uses rhythm instrument definitions with instrument numbers 0x0 - 0xE
_rhythmInstrumentMode = RHYTHM_INSTRUMENT_MODE_RHYTHM_TYPE;
_instrumentWriteMode = INSTRUMENT_WRITE_MODE_FIRST_NOTE_ON;
}
MidiDriver_DarkSeedFloppy_AdLib::~MidiDriver_DarkSeedFloppy_AdLib() {
delete[] _dsfInstrumentBank;
}
int MidiDriver_DarkSeedFloppy_AdLib::open() {
int result = MidiDriver_ADLIB_Multisource::open();
if (result == 0)
// Dark Seed has the OPL rhythm mode always on
setRhythmMode(true);
return result;
}
void MidiDriver_DarkSeedFloppy_AdLib::deinitSource(uint8 source) {
MidiDriver_ADLIB_Multisource::deinitSource(source);
_sourcePriority[source] = 0;
}
void MidiDriver_DarkSeedFloppy_AdLib::setSourcePriority(uint8 source, uint8 priority) {
assert(source < MAXIMUM_SOURCES);
_sourcePriority[source] = priority;
}
void MidiDriver_DarkSeedFloppy_AdLib::loadInstrumentBank(uint8 *instrumentBankData) {
// Dark Seed stores instruments in SIT files. Most music tracks have their
// own instrument bank, but there are only 2 significantly different banks.
// START loads the instrument bank for each of the 8 tracks it plays, but
// each bank is effectively the same. TOS only loads the TOS1.SIT
// instrument bank; the SIT files from the music tracks it plays are
// ignored.
//
// All SIT files contain 256 instruments in the 16 byte AdLib BNK format.
// Only the first 128 instruments are actually loaded; the rest is usually
// empty.
for (int i = 0; i < 128; i++) {
AdLibIbkInstrumentDefinition _ibkInstrument;
_ibkInstrument.o0FreqMultMisc = *instrumentBankData++;
_ibkInstrument.o1FreqMultMisc = *instrumentBankData++;
_ibkInstrument.o0Level = *instrumentBankData++;
_ibkInstrument.o1Level = *instrumentBankData++;
_ibkInstrument.o0DecayAttack = *instrumentBankData++;
_ibkInstrument.o1DecayAttack = *instrumentBankData++;
_ibkInstrument.o0ReleaseSustain = *instrumentBankData++;
_ibkInstrument.o1ReleaseSustain = *instrumentBankData++;
_ibkInstrument.o0WaveformSelect = *instrumentBankData++;
_ibkInstrument.o1WaveformSelect = *instrumentBankData++;
_ibkInstrument.connectionFeedback = *instrumentBankData++;
// The first 15 instruments are rhythm instrument definitions, meant
// for the 5 OPL rhythm mode instruments. 0-2 are bass drum instruments,
// 3-5 are snare drum instruments, etc.
uint8 rhythmType = 0;
if (i < 15) {
rhythmType = 6 + (i / 3);
}
_ibkInstrument.rhythmType = rhythmType;
_ibkInstrument.rhythmNote = 0;
_ibkInstrument.transpose = 0;
_ibkInstrument.toOplInstrumentDefinition(_dsfInstrumentBank[i]);
// Skip padding bytes
instrumentBankData += 5;
}
}
uint8 MidiDriver_DarkSeedFloppy_AdLib::allocateOplChannel(uint8 channel, uint8 source, InstrumentInfo &instrumentInfo) {
uint8 allocatedChannel = 0xFF;
_allocationMutex.lock();
_activeNotesMutex.lock();
if (instrumentInfo.instrumentId <= 0xE) {
// The first 15 instruments are rhythm instruments. These get assigned
// to the corresponding OPL rhythm instruments.
// Note: original code also processes instrument 0xF, leading to
// undefined behavior.
// The order of the rhythm instruments is flipped compared to the order
// in the _activeRhythmNotes array.
uint8 rhythmInstType = 4 - (instrumentInfo.instrumentId / 3);
allocatedChannel = OPL_RHYTHM_INSTRUMENT_CHANNELS[rhythmInstType];
if (_activeRhythmNotes[rhythmInstType].channelAllocated && _activeRhythmNotes[rhythmInstType].source != source) {
// OPL rhythm instrument is already allocated
if (_sourcePriority[_activeRhythmNotes[rhythmInstType].source] >= _sourcePriority[source]) {
// Current source priority is equal to or higher than the new
// source priority. Do not re-allocate this rhythm instrument.
allocatedChannel = 0xFF;
} else {
// Current source priority is lower than the new source
// priority. Deallocate the channel from the current source.
if (_activeRhythmNotes[rhythmInstType].noteActive)
writeKeyOff(allocatedChannel, static_cast<OplInstrumentRhythmType>(rhythmInstType + 1));
_channelAllocations[_activeRhythmNotes[rhythmInstType].source][_activeRhythmNotes[rhythmInstType].channel] = 0xFF;
}
}
if (allocatedChannel != 0xFF) {
// Allocate the OPL channel to the source and rhythm instrument.
_activeRhythmNotes[rhythmInstType].channelAllocated = true;
_activeRhythmNotes[rhythmInstType].source = source;
_activeRhythmNotes[rhythmInstType].channel = channel;
}
}
else {
// For melodic instruments, the following OPL channel is allocated:
// - The OPL channel already allocated to this data channel.
// - The OPL channel with the lowest priority, if it is lower than the
// priority of the new source.
uint8 lowestPriority = 0x64;
for (int i = 0; i < _numMelodicChannels; i++) {
uint8 oplChannel = _melodicChannels[i];
if (_activeNotes[oplChannel].channelAllocated && _activeNotes[oplChannel].source == source && _activeNotes[oplChannel].channel == channel) {
// This OPL channel is already allocated to this source and
// data channel. Use this OPL channel.
allocatedChannel = oplChannel;
lowestPriority = 0;
break;
}
// Unallocated channels are treated as having priority 0, the
// lowest priority.
uint8 currentChannelPriority = !_activeNotes[oplChannel].channelAllocated ? 0 : _sourcePriority[_activeNotes[oplChannel].source];
if (currentChannelPriority < lowestPriority) {
// Found an OPL channel with a lower priority than the
// previously found OPL channel.
allocatedChannel = i;
lowestPriority = currentChannelPriority;
}
}
if (_sourcePriority[source] <= lowestPriority) {
// New source priority is lower than the lowest found OPL channel
// priority. Do not re-allocate this OPL channel.
allocatedChannel = 0xFF;
}
if (allocatedChannel != 0xFF) {
if (_activeNotes[allocatedChannel].channelAllocated && _activeNotes[allocatedChannel].source != source) {
// OPL channel is already allocated. De-allocate it.
if (_activeNotes[allocatedChannel].noteActive)
writeKeyOff(allocatedChannel);
_channelAllocations[_activeRhythmNotes[allocatedChannel].source][_activeRhythmNotes[allocatedChannel].channel] = 0xFF;
}
// Allocate the OPL channel to the source and data channel.
_activeNotes[allocatedChannel].channelAllocated = true;
_activeNotes[allocatedChannel].source = source;
_activeNotes[allocatedChannel].channel = channel;
}
}
if (allocatedChannel != 0xFF) {
_channelAllocations[source][channel] = allocatedChannel;
}
_allocationMutex.unlock();
_activeNotesMutex.unlock();
return allocatedChannel;
}
uint16 MidiDriver_DarkSeedFloppy_AdLib::calculateFrequency(uint8 channel, uint8 source, uint8 note) {
uint8 octaveNote = ((note >= 120) ? 11 : (note % 12));
uint8 block;
if (note < 12) {
block = 0;
} else if (note >= 108) {
block = 7;
} else {
block = (note / 12) - 1;
}
uint16 fnum = OPL_NOTE_FREQUENCIES[octaveNote];
return fnum | (block << 10);
}
uint8 MidiDriver_DarkSeedFloppy_AdLib::calculateUnscaledVolume(uint8 channel, uint8 source, uint8 velocity, const OplInstrumentDefinition &instrumentDef, uint8 operatorNum) {
uint8 instrumentLevel = instrumentDef.getOperatorDefinition(operatorNum).level & 0x3F;
if (instrumentDef.getNumberOfOperators() >= 2 && operatorNum == 0) {
// For operator 0 of a 2 operator instrument, the level from the
// instrument definition is used without scaling, even if the
// connection type is additive.
return instrumentLevel;
}
return 0x3F - (((velocity + 0x80) * (0x3F - instrumentLevel)) >> 8);
}
} // namespace Darkseed