Files
scummvm/engines/scumm/players/player_ad.cpp
T
Filippos Karapetis 4f807ee53e SCUMM: More renaming for players
Rename "player" directory to "players", and reintroduce the "player_"
file prefix. This has been done after sev's request.
"players/player_foo.*" is more descriptive, and avoids potential name
clashes in libraries
2013-11-01 19:26:51 +02:00

960 lines
24 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 "scumm/players/player_ad.h"
#include "scumm/imuse/imuse.h"
#include "scumm/scumm.h"
#include "scumm/resource.h"
#include "audio/fmopl.h"
#include "common/textconsole.h"
#include "common/config-manager.h"
namespace Scumm {
#define AD_CALLBACK_FREQUENCY 472
Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer)
: _vm(scumm), _mixer(mixer), _rate(mixer->getOutputRate()) {
_opl2 = OPL::Config::create();
if (!_opl2->init(_rate)) {
error("Could not initialize OPL2 emulator");
}
_samplesPerCallback = _rate / AD_CALLBACK_FREQUENCY;
_samplesPerCallbackRemainder = _rate % AD_CALLBACK_FREQUENCY;
_samplesTillCallback = 0;
_samplesTillCallbackRemainder = 0;
memset(_registerBackUpTable, 0, sizeof(_registerBackUpTable));
writeReg(0x01, 0x00);
writeReg(0xBD, 0x00);
writeReg(0x08, 0x00);
writeReg(0x01, 0x20);
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
_engineMusicTimer = 0;
_soundPlaying = -1;
_curOffset = 0;
_sfxTimer = 4;
_rndSeed = 1;
memset(_channels, 0, sizeof(_channels));
memset(_sfxResource, 0, sizeof(_sfxResource));
memset(_sfxPriority, 0, sizeof(_sfxPriority));
}
Player_AD::~Player_AD() {
_mixer->stopHandle(_soundHandle);
stopAllSounds();
Common::StackLock lock(_mutex);
delete _opl2;
_opl2 = 0;
}
void Player_AD::setMusicVolume(int vol) {
// HACK: We ignore the parameter and set up the volume specified in the
// config manager. This allows us to differentiate between music and sfx
// volume changes.
setupVolume();
}
void Player_AD::startSound(int sound) {
Common::StackLock lock(_mutex);
// Query the sound resource
const byte *res = _vm->getResourceAddress(rtSound, sound);
if (res[2] == 0x80) {
// Stop the current sounds
stopAllSounds();
// Lock the new music resource
_soundPlaying = sound;
_vm->_res->lock(rtSound, _soundPlaying);
// Start the new music resource
_resource = res;
startMusic();
} else {
// Only try to start a sfx when no music is playing.
if (_soundPlaying == -1) {
const byte priority = res[0];
const byte channel = res[1];
// Check for out of bounds access
if (channel >= 3) {
warning("AdLib sfx resource %d uses channel %d", sound, channel);
return;
}
// Check whether the channel is free or the priority of the new
// sfx resource is above the old one.
if (_channels[channel * 3 + 0].state
|| _channels[channel * 3 + 1].state
|| _channels[channel * 3 + 2].state) {
if (_sfxPriority[channel] > priority) {
return;
}
}
// Lock the new resource
_sfxResource[channel] = sound;
_sfxPriority[channel] = priority;
_vm->_res->lock(rtSound, sound);
// Start the actual sfx resource
_resource = res;
startSfx();
}
}
// Setup the sound volume
setupVolume();
}
void Player_AD::stopSound(int sound) {
Common::StackLock lock(_mutex);
if (sound == _soundPlaying) {
stopAllSounds();
} else {
for (int i = 0; i < 3; ++i) {
if (_sfxResource[i] == sound) {
if (_channels[i * 3 + 0].state
|| _channels[i * 3 + 1].state
|| _channels[i * 3 + 2].state) {
// Unlock the sound resource
_vm->_res->unlock(rtSound, sound);
// Stop the actual sfx playback
_channels[i * 3 + 0].state = 0;
_channels[i * 3 + 1].state = 0;
_channels[i * 3 + 2].state = 0;
clearChannel(i * 3 + 0);
clearChannel(i * 3 + 1);
clearChannel(i * 3 + 2);
}
}
}
}
}
void Player_AD::stopAllSounds() {
Common::StackLock lock(_mutex);
// Unlock the music resource if present
if (_soundPlaying != -1) {
_vm->_res->unlock(rtSound, _soundPlaying);
_soundPlaying = -1;
}
// Stop the music playback
_curOffset = 0;
// Unloack all used sfx resources
for (int i = 0; i < 3; ++i) {
if (_channels[i * 3 + 0].state || _channels[i * 3 + 1].state || _channels[i * 3 + 2].state) {
_vm->_res->unlock(rtSound, _sfxResource[i]);
}
}
// Reset all the sfx channels
for (int i = 0; i < 9; ++i) {
_channels[i].state = 0;
clearChannel(i);
}
writeReg(0xBD, 0x00);
}
int Player_AD::getMusicTimer() {
return _engineMusicTimer;
}
int Player_AD::getSoundStatus(int sound) const {
return (sound == _soundPlaying);
}
void Player_AD::saveLoadWithSerializer(Serializer *ser) {
Common::StackLock lock(_mutex);
if (ser->getVersion() < VER(95)) {
IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL);
dummyImuse->save_or_load(ser, _vm, false);
delete dummyImuse;
return;
}
// TODO: Be nicer than the original and save the data to continue the
// currently played sound resources on load?
}
int Player_AD::readBuffer(int16 *buffer, const int numSamples) {
Common::StackLock lock(_mutex);
int len = numSamples;
while (len > 0) {
if (!_samplesTillCallback) {
// Run the update callback for music or sfx depending on which is
// active.
if (_curOffset) {
updateMusic();
} else {
updateSfx();
}
_samplesTillCallback = _samplesPerCallback;
_samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
if (_samplesTillCallbackRemainder >= AD_CALLBACK_FREQUENCY) {
++_samplesTillCallback;
_samplesTillCallbackRemainder -= AD_CALLBACK_FREQUENCY;
}
}
const int samplesToRead = MIN(len, _samplesTillCallback);
_opl2->readBuffer(buffer, samplesToRead);
buffer += samplesToRead;
len -= samplesToRead;
_samplesTillCallback -= samplesToRead;
}
return numSamples;
}
void Player_AD::setupVolume() {
// Setup the correct volume
int soundVolumeMusic = CLIP<int>(ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxChannelVolume);
int soundVolumeSfx = CLIP<int>(ConfMan.getInt("sfx_volume"), 0, Audio::Mixer::kMaxChannelVolume);
if (ConfMan.hasKey("mute")) {
if (ConfMan.getBool("mute")) {
soundVolumeMusic = 0;
soundVolumeSfx = 0;
}
}
// In case a music is being played set the music volume. Set the sfx
// volume otherwise. This is safe because in the latter case either
// sfx are playing or there is no sound being played at all.
if (_soundPlaying != -1) {
_mixer->setChannelVolume(_soundHandle, soundVolumeMusic);
} else {
_mixer->setChannelVolume(_soundHandle, soundVolumeSfx);
}
}
void Player_AD::writeReg(int r, int v) {
if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
_registerBackUpTable[r] = v;
}
_opl2->writeReg(r, v);
}
uint8 Player_AD::readReg(int r) const {
if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) {
return _registerBackUpTable[r];
} else {
return 0;
}
}
void Player_AD::setupChannel(const uint channel, const byte *instrOffset) {
instrOffset += 2;
writeReg(0xC0 + channel, *instrOffset++);
setupOperator(_operatorOffsetTable[channel * 2 + 0], instrOffset);
setupOperator(_operatorOffsetTable[channel * 2 + 1], instrOffset);
}
void Player_AD::setupOperator(const uint opr, const byte *&instrOffset) {
writeReg(0x20 + opr, *instrOffset++);
writeReg(0x40 + opr, *instrOffset++);
writeReg(0x60 + opr, *instrOffset++);
writeReg(0x80 + opr, *instrOffset++);
writeReg(0xE0 + opr, *instrOffset++);
}
const int Player_AD::_operatorOffsetTable[18] = {
0, 3, 1, 4,
2, 5, 8, 11,
9, 12, 10, 13,
16, 19, 17, 20,
18, 21
};
// Music
void Player_AD::startMusic() {
memset(_instrumentOffset, 0, sizeof(_instrumentOffset));
memset(_channelLastEvent, 0, sizeof(_channelLastEvent));
memset(_channelFrequency, 0, sizeof(_channelFrequency));
memset(_channelB0Reg, 0, sizeof(_channelB0Reg));
_voiceChannels = 0;
uint instruments = _resource[10];
for (uint i = 0; i < instruments; ++i) {
const int instrIndex = _resource[11 + i] - 1;
if (0 <= instrIndex && instrIndex < 16) {
_instrumentOffset[instrIndex] = i * 16 + 16 + 3;
_voiceChannels |= _resource[_instrumentOffset[instrIndex] + 13];
}
}
if (_voiceChannels) {
_mdvdrState = 0x20;
_voiceChannels = 6;
} else {
_mdvdrState = 0;
_voiceChannels = 9;
}
_curOffset = 0x93;
// TODO: is this the same for Loom?
_nextEventTimer = 40;
_engineMusicTimer = 0;
_internalMusicTimer = 0;
_musicTimer = 0;
writeReg(0xBD, _mdvdrState);
const bool isLoom = (_vm->_game.id == GID_LOOM);
_timerLimit = isLoom ? 473 : 256;
_musicTicks = _resource[3] * (isLoom ? 2 : 1);
_loopFlag = (_resource[4] == 0);
_musicLoopStart = READ_LE_UINT16(_resource + 5);
}
void Player_AD::updateMusic() {
_musicTimer += _musicTicks;
if (_musicTimer < _timerLimit) {
return;
}
_musicTimer -= _timerLimit;
++_internalMusicTimer;
if (_internalMusicTimer > 120) {
_internalMusicTimer = 0;
++_engineMusicTimer;
}
--_nextEventTimer;
if (_nextEventTimer) {
return;
}
while (true) {
uint command = _resource[_curOffset++];
if (command == 0xFF) {
// META EVENT
// Get the command number.
command = _resource[_curOffset++];
if (command == 47) {
// End of track
if (_loopFlag) {
// In case the track is looping jump to the start.
_curOffset = _musicLoopStart;
_nextEventTimer = 0;
} else {
// Otherwise completely stop playback.
stopAllSounds();
}
return;
} else if (command == 88) {
// This is proposedly a debug information insertion. The CMS
// player code handles this differently, but is still using
// the same resources...
_curOffset += 5;
} else if (command == 81) {
// Change tempo. This is used exclusively in Loom.
const uint timing = _resource[_curOffset + 2] | (_resource[_curOffset + 1] << 8);
_musicTicks = 0x73000 / timing;
command = _resource[_curOffset++];
_curOffset += command;
} else {
// In case an unknown meta event occurs just skip over the
// data by using the length supplied.
command = _resource[_curOffset++];
_curOffset += command;
}
} else {
if (command >= 0x90) {
// NOTE ON
// Extract the channel number and save it in command.
command -= 0x90;
const uint instrOffset = _instrumentOffset[command];
if (instrOffset) {
if (_resource[instrOffset + 13] != 0) {
setupRhythm(_resource[instrOffset + 13], instrOffset);
} else {
int channel = findFreeChannel();
if (channel != -1) {
noteOff(channel);
setupChannel(channel, instrOffset);
_channelLastEvent[channel] = command + 0x90;
_channelFrequency[channel] = _resource[_curOffset];
setupFrequency(channel, _resource[_curOffset]);
}
}
}
} else {
// NOTE OFF
const uint note = _resource[_curOffset];
command += 0x10;
// Find the output channel which plays the note.
uint channel = 0xFF;
for (uint i = 0; i < _voiceChannels; ++i) {
if (_channelFrequency[i] == note && _channelLastEvent[i] == command) {
channel = i;
break;
}
}
if (channel != 0xFF) {
// In case a output channel playing the note was found,
// stop it.
noteOff(channel);
} else {
// In case there is no such note this will disable the
// rhythm instrument played on the channel.
command -= 0x90;
const uint instrOffset = _instrumentOffset[command];
if (instrOffset && _resource[instrOffset + 13] != 0) {
const uint rhythmInstr = _resource[instrOffset + 13];
if (rhythmInstr < 6) {
_mdvdrState &= _mdvdrTable[rhythmInstr] ^ 0xFF;
writeReg(0xBD, _mdvdrState);
}
}
}
}
_curOffset += 2;
}
// In case there is a delay till the next event stop handling.
if (_resource[_curOffset] != 0) {
break;
}
++_curOffset;
}
_nextEventTimer = _resource[_curOffset++];
if (_nextEventTimer & 0x80) {
_nextEventTimer -= 0x80;
_nextEventTimer <<= 7;
_nextEventTimer |= _resource[_curOffset++];
}
_nextEventTimer >>= (_vm->_game.id == GID_LOOM) ? 2 : 1;
if (!_nextEventTimer) {
_nextEventTimer = 1;
}
}
void Player_AD::noteOff(uint channel) {
_channelLastEvent[channel] = 0;
writeReg(0xB0 + channel, _channelB0Reg[channel] & 0xDF);
}
int Player_AD::findFreeChannel() {
for (uint i = 0; i < _voiceChannels; ++i) {
if (!_channelLastEvent[i]) {
return i;
}
}
return -1;
}
void Player_AD::setupFrequency(uint channel, int8 frequency) {
frequency -= 31;
if (frequency < 0) {
frequency = 0;
}
uint octave = 0;
while (frequency >= 12) {
frequency -= 12;
++octave;
}
const uint noteFrequency = _noteFrequencies[frequency];
octave <<= 2;
octave |= noteFrequency >> 8;
octave |= 0x20;
writeReg(0xA0 + channel, noteFrequency & 0xFF);
_channelB0Reg[channel] = octave;
writeReg(0xB0 + channel, octave);
}
void Player_AD::setupRhythm(uint rhythmInstr, uint instrOffset) {
if (rhythmInstr == 1) {
setupChannel(6, instrOffset);
writeReg(0xA6, _resource[instrOffset++]);
writeReg(0xB6, _resource[instrOffset] & 0xDF);
_mdvdrState |= 0x10;
writeReg(0xBD, _mdvdrState);
} else if (rhythmInstr < 6) {
const byte *secondOperatorOffset = _resource + instrOffset + 8;
setupOperator(_rhythmOperatorTable[rhythmInstr], secondOperatorOffset);
writeReg(0xA0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++]);
writeReg(0xB0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset++] & 0xDF);
writeReg(0xC0 + _rhythmChannelTable[rhythmInstr], _resource[instrOffset]);
_mdvdrState |= _mdvdrTable[rhythmInstr];
writeReg(0xBD, _mdvdrState);
}
}
const uint Player_AD::_noteFrequencies[12] = {
0x200, 0x21E, 0x23F, 0x261,
0x285, 0x2AB, 0x2D4, 0x300,
0x32E, 0x35E, 0x390, 0x3C7
};
const uint Player_AD::_mdvdrTable[6] = {
0x00, 0x10, 0x08, 0x04, 0x02, 0x01
};
const uint Player_AD::_rhythmOperatorTable[6] = {
0x00, 0x00, 0x14, 0x12, 0x15, 0x11
};
const uint Player_AD::_rhythmChannelTable[6] = {
0x00, 0x00, 0x07, 0x08, 0x08, 0x07
};
// SFX
void Player_AD::startSfx() {
writeReg(0xBD, 0x00);
// The second byte of the resource defines the logical channel where
// the sound effect should be played.
const int startChannel = _resource[1] * 3;
// Clear the channel.
_channels[startChannel + 0].state = 0;
_channels[startChannel + 1].state = 0;
_channels[startChannel + 2].state = 0;
clearChannel(startChannel + 0);
clearChannel(startChannel + 1);
clearChannel(startChannel + 2);
// Set up the first channel to pick up playback.
_channels[startChannel].currentOffset = _channels[startChannel].startOffset = _resource + 2;
_channels[startChannel].state = 1;
// Scan for the start of the other channels and set them up if required.
int curChannel = startChannel + 1;
const byte *bufferPosition = _resource + 2;
uint8 command = 0;
while ((command = *bufferPosition) != 0xFF) {
switch (command) {
case 1:
// INSTRUMENT DEFINITION
bufferPosition += 15;
break;
case 2:
// NOTE DEFINITION
bufferPosition += 11;
break;
case 0x80:
// LOOP
bufferPosition += 1;
break;
default:
// START OF CHANNEL
bufferPosition += 1;
_channels[curChannel].currentOffset = bufferPosition;
_channels[curChannel].startOffset = bufferPosition;
_channels[curChannel].state = 1;
++curChannel;
break;
}
}
}
void Player_AD::updateSfx() {
if (--_sfxTimer) {
return;
}
_sfxTimer = 4;
for (int i = 0; i <= 9; ++i) {
if (!_channels[i].state) {
continue;
}
updateChannel(i);
}
}
void Player_AD::clearChannel(int channel) {
writeReg(0xA0 + channel, 0x00);
writeReg(0xB0 + channel, 0x00);
}
void Player_AD::updateChannel(int channel) {
if (_channels[channel].state == 1) {
parseSlot(channel);
} else {
updateSlot(channel);
}
}
void Player_AD::parseSlot(int channel) {
while (true) {
const byte *curOffset = _channels[channel].currentOffset;
switch (*curOffset) {
case 1:
// INSTRUMENT DEFINITION
++curOffset;
_channels[channel].instrumentData[0] = *(curOffset + 0);
_channels[channel].instrumentData[1] = *(curOffset + 2);
_channels[channel].instrumentData[2] = *(curOffset + 9);
_channels[channel].instrumentData[3] = *(curOffset + 8);
_channels[channel].instrumentData[4] = *(curOffset + 4);
_channels[channel].instrumentData[5] = *(curOffset + 3);
_channels[channel].instrumentData[6] = 0;
setupChannel(channel, curOffset);
writeReg(0xA0 + channel, *(curOffset + 0));
writeReg(0xB0 + channel, *(curOffset + 1) & 0xDF);
_channels[channel].currentOffset += 15;
break;
case 2:
// NOTE DEFINITION
++curOffset;
_channels[channel].state = 2;
noteOffOn(channel);
parseNote(channel, 0, curOffset);
parseNote(channel, 1, curOffset);
return;
case 0x80:
// LOOP
_channels[channel].currentOffset = _channels[channel].startOffset;
break;
default:
// START OF CHANNEL
// When we encounter a start of another channel while playback
// it means that the current channel is finished. Thus, we will
// stop it.
clearChannel(channel);
_channels[channel].state = 0;
// If no channel of the sound effect is playing anymore, unlock
// the resource.
channel /= 3;
if (!_channels[channel + 0].state
&& !_channels[channel + 1].state
&& !_channels[channel + 2].state) {
_vm->_res->unlock(rtSound, _sfxResource[channel]);
}
return;
}
}
}
void Player_AD::updateSlot(int channel) {
const byte *curOffset = _channels[channel].currentOffset + 1;
for (int num = 0; num <= 1; ++num, curOffset += 5) {
if (!(*curOffset & 0x80)) {
continue;
}
const int note = channel * 2 + num;
bool updateNote = false;
if (_notes[note].state == 2) {
if (!--_notes[note].sustainTimer) {
updateNote = true;
}
} else {
updateNote = processNoteEnvelope(note, _notes[note].instrumentValue);
if (_notes[note].bias) {
writeRegisterSpecial(note, _notes[note].bias - _notes[note].instrumentValue, *curOffset & 0x07);
} else {
writeRegisterSpecial(note, _notes[note].instrumentValue, *curOffset & 0x07);
}
}
if (updateNote) {
if (processNote(note, curOffset)) {
if (!(*curOffset & 0x08)) {
_channels[channel].currentOffset += 11;
_channels[channel].state = 1;
continue;
} else if (*curOffset & 0x10) {
noteOffOn(channel);
}
_notes[note].state = -1;
processNote(note, curOffset);
}
}
if ((*curOffset & 0x20) && !--_notes[note].playTime) {
_channels[channel].currentOffset += 11;
_channels[channel].state = 1;
}
}
}
void Player_AD::parseNote(int channel, int num, const byte *offset) {
if (num) {
offset += 5;
}
if (*offset & 0x80) {
const int note = channel * 2 + num;
_notes[note].state = -1;
processNote(note, offset);
_notes[note].playTime = 0;
if (*offset & 0x20) {
_notes[note].playTime = (*(offset + 4) >> 4) * 118;
_notes[note].playTime += (*(offset + 4) & 0x0F) * 8;
}
}
}
bool Player_AD::processNote(int note, const byte *offset) {
if (++_notes[note].state == 4) {
return true;
}
const int instrumentDataOffset = *offset & 0x07;
_notes[note].bias = _noteBiasTable[instrumentDataOffset];
uint8 instrumentDataValue = 0;
if (_notes[note].state == 0) {
instrumentDataValue = _channels[note / 2].instrumentData[instrumentDataOffset];
}
uint8 noteInstrumentValue = readRegisterSpecial(note, instrumentDataValue, instrumentDataOffset);
if (_notes[note].bias) {
noteInstrumentValue = _notes[note].bias - noteInstrumentValue;
}
_notes[note].instrumentValue = noteInstrumentValue;
if (_notes[note].state == 2) {
_notes[note].sustainTimer = _numStepsTable[*(offset + 3) >> 4];
if (*offset & 0x40) {
_notes[note].sustainTimer = (((getRnd() << 8) * _notes[note].sustainTimer) >> 16) + 1;
}
} else {
int timer1, timer2;
if (_notes[note].state == 3) {
timer1 = *(offset + 3) & 0x0F;
timer2 = 0;
} else {
timer1 = *(offset + _notes[note].state + 1) >> 4;
timer2 = *(offset + _notes[note].state + 1) & 0x0F;
}
int adjustValue = ((_noteAdjustTable[timer2] * _noteAdjustScaleTable[instrumentDataOffset]) >> 16) - noteInstrumentValue;
setupNoteEnvelopeState(note, _numStepsTable[timer1], adjustValue);
}
return false;
}
void Player_AD::noteOffOn(int channel) {
const uint8 regValue = readReg(0xB0 | channel);
writeReg(0xB0 | channel, regValue & 0xDF);
writeReg(0xB0 | channel, regValue | 0x20);
}
void Player_AD::writeRegisterSpecial(int note, uint8 value, int offset) {
if (offset == 6) {
return;
}
// Division by 2 extracts the channel number out of the note.
note /= 2;
uint8 regNum;
if (_useOperatorTable[offset]) {
regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
} else {
regNum = _channelOffsetTable[note];
}
regNum += _baseRegisterTable[offset];
uint8 regValue = readReg(regNum) & (~_registerMaskTable[offset]);
regValue |= value << _registerShiftTable[offset];
writeReg(regNum, regValue);
}
uint8 Player_AD::readRegisterSpecial(int note, uint8 defaultValue, int offset) {
if (offset == 6) {
return 0;
}
// Division by 2 extracts the channel number out of the note.
note /= 2;
uint8 regNum;
if (_useOperatorTable[offset]) {
regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + note * 2];
} else {
regNum = _channelOffsetTable[note];
}
regNum += _baseRegisterTable[offset];
uint8 regValue;
if (defaultValue) {
regValue = defaultValue;
} else {
regValue = readReg(regNum);
}
regValue &= _registerMaskTable[offset];
regValue >>= _registerShiftTable[offset];
return regValue;
}
void Player_AD::setupNoteEnvelopeState(int note, int steps, int adjust) {
_notes[note].preIncrease = 0;
if (ABS(adjust) > steps) {
_notes[note].preIncrease = 1;
_notes[note].adjust = adjust / steps;
_notes[note].envelope.stepIncrease = ABS(adjust % steps);
} else {
_notes[note].adjust = adjust;
_notes[note].envelope.stepIncrease = ABS(adjust);
}
_notes[note].envelope.step = steps;
_notes[note].envelope.stepCounter = 0;
_notes[note].envelope.timer = steps;
}
bool Player_AD::processNoteEnvelope(int note, int &instrumentValue) {
if (_notes[note].preIncrease) {
instrumentValue += _notes[note].adjust;
}
_notes[note].envelope.stepCounter += _notes[note].envelope.stepIncrease;
if (_notes[note].envelope.stepCounter >= _notes[note].envelope.step) {
_notes[note].envelope.stepCounter -= _notes[note].envelope.step;
if (_notes[note].adjust < 0) {
--instrumentValue;
} else {
++instrumentValue;
}
}
if (--_notes[note].envelope.timer) {
return false;
} else {
return true;
}
}
uint8 Player_AD::getRnd() {
if (_rndSeed & 1) {
_rndSeed >>= 1;
_rndSeed ^= 0xB8;
} else {
_rndSeed >>= 1;
}
return _rndSeed;
}
const uint Player_AD::_noteBiasTable[7] = {
0x00, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x00
};
const uint Player_AD::_numStepsTable[16] = {
1, 4, 6, 8,
10, 14, 18, 24,
36, 64, 100, 160,
240, 340, 600, 1200
};
const uint Player_AD::_noteAdjustScaleTable[7] = {
255, 7, 63, 15, 63, 15, 63
};
const uint Player_AD::_noteAdjustTable[16] = {
0, 4369, 8738, 13107,
17476, 21845, 26214, 30583,
34952, 39321, 43690, 48059,
52428, 46797, 61166, 65535
};
const bool Player_AD::_useOperatorTable[7] = {
false, false, true, true, true, true, false
};
const uint Player_AD::_channelOffsetTable[11] = {
0, 1, 2, 3,
4, 5, 6, 7,
8, 8, 7
};
const uint Player_AD::_channelOperatorOffsetTable[7] = {
0, 0, 1, 1, 0, 0, 0
};
const uint Player_AD::_baseRegisterTable[7] = {
0xA0, 0xC0, 0x40, 0x20, 0x40, 0x20, 0x00
};
const uint Player_AD::_registerMaskTable[7] = {
0xFF, 0x0E, 0x3F, 0x0F, 0x3F, 0x0F, 0x00
};
const uint Player_AD::_registerShiftTable[7] = {
0, 1, 0, 0, 0, 0, 0
};
} // End of namespace Scumm