290 lines
9.0 KiB
C++
290 lines
9.0 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 "got/musicdriver_adlib.h"
|
|
|
|
#include "common/config-manager.h"
|
|
#include "common/debug.h"
|
|
#include "audio/mididrv.h"
|
|
|
|
|
|
namespace Got {
|
|
|
|
MusicDriver_Got_AdLib::MusicDriver_Got_AdLib(uint8 timerFrequency) : MusicDriver_Got(timerFrequency),
|
|
_opl(nullptr),
|
|
_userMusicVolume(256),
|
|
_userMute(false) {
|
|
Common::fill(_channelOp0LevelRegisterValues, _channelOp0LevelRegisterValues + ARRAYSIZE(_channelOp0LevelRegisterValues), 0);
|
|
Common::fill(_channelOp1LevelRegisterValues, _channelOp1LevelRegisterValues + ARRAYSIZE(_channelOp1LevelRegisterValues), 0);
|
|
Common::fill(_channelBxRegisterValues, _channelBxRegisterValues + ARRAYSIZE(_channelBxRegisterValues), 0);
|
|
Common::fill(_channelConnectionValues, _channelConnectionValues + ARRAYSIZE(_channelConnectionValues), 0);
|
|
}
|
|
|
|
MusicDriver_Got_AdLib::~MusicDriver_Got_AdLib() {
|
|
close();
|
|
}
|
|
|
|
int MusicDriver_Got_AdLib::open() {
|
|
if (_isOpen)
|
|
return MidiDriver::MERR_ALREADY_OPEN;
|
|
|
|
int8 detectResult = OPL::Config::detect(OPL::Config::kOpl2);
|
|
if (detectResult == -1)
|
|
return MidiDriver::MERR_DEVICE_NOT_AVAILABLE;
|
|
|
|
// Create the emulator / hardware interface.
|
|
_opl = OPL::Config::create(OPL::Config::kOpl2);
|
|
|
|
if (_opl == nullptr)
|
|
return MidiDriver::MERR_CANNOT_CONNECT;
|
|
|
|
// Initialize emulator / hardware interface.
|
|
if (!_opl->init())
|
|
return MidiDriver::MERR_CANNOT_CONNECT;
|
|
|
|
_isOpen = true;
|
|
|
|
// Set default OPL register values.
|
|
initOpl();
|
|
|
|
// Start the emulator / hardware interface. This will also start the timer
|
|
// callbacks.
|
|
_opl->start(new Common::Functor0Mem<void, MusicDriver_Got_AdLib>(this, &MusicDriver_Got_AdLib::onTimer), _timerFrequency);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::close() {
|
|
if (!_isOpen)
|
|
return;
|
|
|
|
_isOpen = false;
|
|
|
|
stopAllNotes();
|
|
|
|
if (_opl) {
|
|
_opl->stop();
|
|
delete _opl;
|
|
_opl = nullptr;
|
|
}
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::syncSoundSettings() {
|
|
// Get user volume settings.
|
|
_userMusicVolume = MIN(256, ConfMan.getInt("music_volume"));
|
|
_userMute = ConfMan.getBool("mute") || ConfMan.getBool("music_mute");
|
|
|
|
// Apply the user volume.
|
|
recalculateVolumes();
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::send(uint16 b) {
|
|
uint8 oplRegister = b >> 8;
|
|
uint8 value = b & 0xFF;
|
|
|
|
if (oplRegister >= OPL_REGISTER_BASE_LEVEL && oplRegister < OPL_REGISTER_BASE_LEVEL + 0x20) {
|
|
// Write to a level register.
|
|
|
|
// Determine the channel and operator from the register number.
|
|
uint8 regOffset = oplRegister - OPL_REGISTER_BASE_LEVEL;
|
|
uint8 oplChannel = ((regOffset / 8) * 3) + ((regOffset % 8) % 3);
|
|
uint8 oplOperator = (regOffset % 8) / 3;
|
|
assert(oplChannel < OPL2_NUM_CHANNELS);
|
|
assert(oplOperator < 2);
|
|
|
|
// Store the new register value.
|
|
if (oplOperator == 0) {
|
|
_channelOp0LevelRegisterValues[oplChannel] = value;
|
|
} else {
|
|
_channelOp1LevelRegisterValues[oplChannel] = value;
|
|
}
|
|
|
|
// Apply user volume settings to the level.
|
|
uint8 scaledLevel = calculateVolume(oplChannel, oplOperator);
|
|
// Add the KSL bits to the new level value.
|
|
value = (value & 0xC0) | scaledLevel;
|
|
} else if ((oplRegister & 0xF0) == OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON) {
|
|
// Write to an F-num high / block / key on register.
|
|
|
|
uint8 oplChannel = oplRegister & 0x0F;
|
|
assert(oplChannel < OPL2_NUM_CHANNELS);
|
|
|
|
// Store the value, but clear the key on bit.
|
|
_channelBxRegisterValues[oplChannel] = value & 0x1F;
|
|
} else if ((oplRegister & 0xF0) == OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING) {
|
|
// Write to a connection register.
|
|
|
|
uint8 oplChannel = oplRegister & 0x0F;
|
|
assert(oplChannel < OPL2_NUM_CHANNELS);
|
|
|
|
// Store the connection bit.
|
|
_channelConnectionValues[oplChannel] = value & 0x01;
|
|
}
|
|
|
|
// Write the new register value to the OPL chip.
|
|
writeRegister(oplRegister, value);
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::stopAllNotes() {
|
|
// Clear the key on bit on all OPL channels.
|
|
for (int i = 0; i < OPL2_NUM_CHANNELS; i++) {
|
|
writeRegister(0xB0 | i, _channelBxRegisterValues[i]);
|
|
}
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::initOpl() {
|
|
// Clear test flags and enable waveform select.
|
|
writeRegister(0x01, 0x20);
|
|
|
|
// Clear, stop and mask the timers and reset the interrupt.
|
|
writeRegister(0x02, 0);
|
|
writeRegister(0x03, 0);
|
|
writeRegister(0x04, 0x60);
|
|
writeRegister(0x04, 0x80);
|
|
|
|
// Set note select mode 0 and disable CSM mode.
|
|
writeRegister(0x08, 0);
|
|
|
|
// Clear operator registers.
|
|
for (int i = 0; i < 5; i++) {
|
|
uint8 baseReg = 0;
|
|
switch (i) {
|
|
case 0:
|
|
baseReg = OPL_REGISTER_BASE_FREQMULT_MISC;
|
|
break;
|
|
case 1:
|
|
baseReg = OPL_REGISTER_BASE_LEVEL;
|
|
break;
|
|
case 2:
|
|
baseReg = OPL_REGISTER_BASE_DECAY_ATTACK;
|
|
break;
|
|
case 3:
|
|
baseReg = OPL_REGISTER_BASE_RELEASE_SUSTAIN;
|
|
break;
|
|
case 4:
|
|
baseReg = OPL_REGISTER_BASE_WAVEFORMSELECT;
|
|
break;
|
|
}
|
|
|
|
for (int j = 0; j < OPL2_NUM_CHANNELS; j++) {
|
|
writeRegister(baseReg + determineOperatorRegisterOffset(j, 0), 0);
|
|
writeRegister(baseReg + determineOperatorRegisterOffset(j, 1), 0);
|
|
}
|
|
}
|
|
|
|
// Clear channel registers.
|
|
for (int i = 0; i < 3; i++) {
|
|
uint8 baseReg = 0;
|
|
switch (i) {
|
|
case 0:
|
|
baseReg = OPL_REGISTER_BASE_FNUMLOW;
|
|
break;
|
|
case 1:
|
|
baseReg = OPL_REGISTER_BASE_FNUMHIGH_BLOCK_KEYON;
|
|
break;
|
|
case 2:
|
|
baseReg = OPL_REGISTER_BASE_CONNECTION_FEEDBACK_PANNING;
|
|
break;
|
|
}
|
|
|
|
for (int j = 0; j < OPL2_NUM_CHANNELS; j++) {
|
|
writeRegister(baseReg + j, 0);
|
|
}
|
|
}
|
|
|
|
// Disable rhythm mode and set modulation and vibrato depth to low.
|
|
writeRegister(0xBD, 0);
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::recalculateVolumes() {
|
|
// Determine the value for all level registers and write them out.
|
|
for (int i = 0; i < OPL2_NUM_CHANNELS; i++) {
|
|
uint8 oplRegister = OPL_REGISTER_BASE_LEVEL + determineOperatorRegisterOffset(i, 0);
|
|
uint8 value = (_channelOp0LevelRegisterValues[i] & 0xC0) | calculateVolume(i, 0);
|
|
writeRegister(oplRegister, value);
|
|
|
|
oplRegister = OPL_REGISTER_BASE_LEVEL + determineOperatorRegisterOffset(i, 1);
|
|
value = (_channelOp1LevelRegisterValues[i] & 0xC0) | calculateVolume(i, 1);
|
|
writeRegister(oplRegister, value);
|
|
}
|
|
}
|
|
|
|
uint8 MusicDriver_Got_AdLib::calculateVolume(uint8 channel, uint8 operatorNum) {
|
|
// Get the last written level for this operator.
|
|
uint8 operatorDefVolume = (operatorNum == 0 ? _channelOp0LevelRegisterValues[channel] : _channelOp1LevelRegisterValues[channel]) & 0x3F;
|
|
|
|
// Determine if volume settings should be applied to this operator.
|
|
if (!isVolumeApplicableToOperator(channel, operatorNum))
|
|
// No need to apply volume settings; just use the level as written.
|
|
return operatorDefVolume;
|
|
|
|
uint8 invertedVolume = 0x3F - operatorDefVolume;
|
|
|
|
// Scale by user volume.
|
|
if (_userMute) {
|
|
invertedVolume = 0;
|
|
} else {
|
|
invertedVolume = (invertedVolume * _userMusicVolume) >> 8;
|
|
}
|
|
uint8 scaledVolume = 0x3F - invertedVolume;
|
|
|
|
return scaledVolume;
|
|
}
|
|
|
|
bool MusicDriver_Got_AdLib::isVolumeApplicableToOperator(uint8 channel, uint8 operatorNum) {
|
|
// 2 operator instruments have 2 different operator connections:
|
|
// additive (0x01) or FM (0x00) synthesis. Carrier operators in FM
|
|
// synthesis and both operators in additive synthesis need to have
|
|
// volume settings applied; modulator operators just use the level
|
|
// as written. In FM synthesis connection, operator 1 is a carrier.
|
|
return _channelConnectionValues[channel] == 0x01 || operatorNum == 1;
|
|
}
|
|
|
|
uint16 MusicDriver_Got_AdLib::determineOperatorRegisterOffset(uint8 oplChannel, uint8 operatorNum) {
|
|
// 2 operator register offset for each channel and operator:
|
|
//
|
|
// Channel | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 3 | 4 | 5 | 6 | 7 | 8 | 6 | 7 | 8 |
|
|
// Operator | 0 | 1 | 0 | 1 | 0 | 1 |
|
|
// Register | 0 | 1 | 2 | 3 | 4 | 5 | 8 | 9 | A | B | C | D |10 |11 |12 |13 |14 |15 |
|
|
return (oplChannel / 3 * 8) + (oplChannel % 3) + (operatorNum * 3);
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::writeRegister(uint8 oplRegister, uint8 value) {
|
|
//debug("Writing register %X %X", oplRegister, value);
|
|
|
|
_opl->writeReg(oplRegister, value);
|
|
}
|
|
|
|
void MusicDriver_Got_AdLib::setTimerFrequency(uint8 timerFrequency) {
|
|
if (timerFrequency == _timerFrequency)
|
|
return;
|
|
|
|
MusicDriver_Got::setTimerFrequency(timerFrequency);
|
|
|
|
if (isOpen()) {
|
|
// Update OPL timer frequency.
|
|
_opl->stop();
|
|
_opl->start(new Common::Functor0Mem<void, MusicDriver_Got_AdLib>(this, &MusicDriver_Got_AdLib::onTimer), _timerFrequency);
|
|
}
|
|
}
|
|
|
|
} // namespace Got
|