/* 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 "scumm/saveload.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(_sfx, 0, sizeof(_sfx)); for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { _sfx[i].resource = -1; for (int j = 0; j < ARRAYSIZE(_sfx[i].channels); ++j) { _sfx[i].channels[j].hardwareChannel = -1; } } memset(_hwChannels, 0, sizeof(_hwChannels)); _numHWChannels = ARRAYSIZE(_hwChannels); memset(_voiceChannels, 0, sizeof(_voiceChannels)); _musicVolume = _sfxVolume = 255; _isSeeking = false; } 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); // Setup the sound volume setupVolume(); // Query the sound resource const byte *res = _vm->getResourceAddress(rtSound, sound); if (res[2] == 0x80) { // Stop the current sounds stopMusic(); // Lock the new music resource _soundPlaying = sound; _vm->_res->lock(rtSound, _soundPlaying); // Start the new music resource _musicData = res; startMusic(); } else { const byte priority = res[0]; // The original specified the channel to use in the sound // resource. However, since we play as much as possible we sill // ignore it and simply use the priority value to determine // whether the sfx can be played or not. //const byte channel = res[1]; // Try to allocate a sfx slot for playback. SfxSlot *sfx = allocateSfxSlot(priority); if (!sfx) { ::debugC(3, DEBUG_SOUND, "AdLib: No free sfx slot for sound %d", sound); return; } // Try to start sfx playback sfx->resource = sound; sfx->priority = priority; if (startSfx(sfx, res)) { // Lock the new resource _vm->_res->lock(rtSound, sound); } } } void Player_AD::stopSound(int sound) { Common::StackLock lock(_mutex); if (sound == _soundPlaying) { stopMusic(); } else { for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { if (_sfx[i].resource == sound) { stopSfx(&_sfx[i]); } } } } void Player_AD::stopAllSounds() { Common::StackLock lock(_mutex); // Stop the music stopMusic(); // Stop all the sfx playback for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { stopSfx(&_sfx[i]); } } 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; } if (ser->getVersion() >= VER(96)) { int32 res[4] = { _soundPlaying, _sfx[0].resource, _sfx[1].resource, _sfx[2].resource }; // The first thing we save is a list of sound resources being played // at the moment. ser->saveLoadArrayOf(res, 4, sizeof(res[0]), sleInt32); // If we are loading start the music again at this point. if (ser->isLoading()) { if (res[0] != -1) { startSound(res[0]); } } uint32 musicOffset = _curOffset; static const SaveLoadEntry musicData[] = { MKLINE(Player_AD, _engineMusicTimer, sleInt32, VER(96)), MKLINE(Player_AD, _musicTimer, sleUint32, VER(96)), MKLINE(Player_AD, _internalMusicTimer, sleUint32, VER(96)), MKLINE(Player_AD, _curOffset, sleUint32, VER(96)), MKLINE(Player_AD, _nextEventTimer, sleUint32, VER(96)), MKEND() }; ser->saveLoadEntries(this, musicData); // We seek back to the old music position. if (ser->isLoading()) { SWAP(musicOffset, _curOffset); musicSeekTo(musicOffset); } // Finally start up the SFX. This makes sure that they are not // accidently stopped while seeking to the old music position. if (ser->isLoading()) { for (int i = 1; i < ARRAYSIZE(res); ++i) { if (res[i] != -1) { startSound(res[i]); } } } } } int Player_AD::readBuffer(int16 *buffer, const int numSamples) { Common::StackLock lock(_mutex); int len = numSamples; while (len > 0) { if (!_samplesTillCallback) { if (_curOffset) { updateMusic(); } 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 _musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxChannelVolume); _sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, Audio::Mixer::kMaxChannelVolume); if (ConfMan.hasKey("mute")) { if (ConfMan.getBool("mute")) { _musicVolume = 0; _sfxVolume = 0; } } // Update current output levels for (int i = 0; i < ARRAYSIZE(_operatorOffsetTable); ++i) { const uint reg = 0x40 + _operatorOffsetTable[i]; writeReg(reg, readReg(reg)); } // Reset note on status for (int i = 0; i < ARRAYSIZE(_hwChannels); ++i) { const uint reg = 0xB0 + i; writeReg(reg, readReg(reg)); } } int Player_AD::allocateHWChannel(int priority, SfxSlot *owner) { // First pass: Check whether there's any unallocated channel for (int i = 0; i < _numHWChannels; ++i) { if (!_hwChannels[i].allocated) { _hwChannels[i].allocated = true; _hwChannels[i].priority = priority; _hwChannels[i].sfxOwner = owner; return i; } } // Second pass: Reassign channels based on priority for (int i = 0; i < _numHWChannels; ++i) { if (_hwChannels[i].priority <= priority) { // In case the HW channel belongs to a SFX we will completely // stop playback of that SFX. // TODO: Maybe be more fine grained in the future and allow // detachment of individual channels of a SFX? if (_hwChannels[i].sfxOwner) { stopSfx(_hwChannels[i].sfxOwner); } _hwChannels[i].allocated = true; _hwChannels[i].priority = priority; _hwChannels[i].sfxOwner = owner; return i; } } return -1; } void Player_AD::freeHWChannel(int channel) { assert(_hwChannels[channel].allocated); _hwChannels[channel].allocated = false; _hwChannels[channel].sfxOwner = nullptr; } void Player_AD::limitHWChannels(int newCount) { for (int i = newCount; i < ARRAYSIZE(_hwChannels); ++i) { if (_hwChannels[i].allocated) { freeHWChannel(i); } } _numHWChannels = newCount; } const int Player_AD::_operatorOffsetToChannel[22] = { 0, 1, 2, 0, 1, 2, -1, -1, 3, 4, 5, 3, 4, 5, -1, -1, 6, 7, 8, 6, 7, 8 }; void Player_AD::writeReg(int r, int v) { if (r >= 0 && r < ARRAYSIZE(_registerBackUpTable)) { _registerBackUpTable[r] = v; } // Handle volume scaling depending on the sound type. if (r >= 0x40 && r <= 0x55) { const int operatorOffset = r - 0x40; const int channel = _operatorOffsetToChannel[operatorOffset]; if (channel != -1) { const bool twoOPOutput = (readReg(0xC0 + channel) & 0x01) != 0; int scale = Audio::Mixer::kMaxChannelVolume; // We only scale the volume of operator 2 unless both operators // are set to directly produce sound. if (twoOPOutput || operatorOffset == _operatorOffsetTable[channel * 2 + 1]) { if (_hwChannels[channel].sfxOwner) { scale = _sfxVolume; } else { scale = _musicVolume; } } int vol = 0x3F - (v & 0x3F); vol = vol * scale / Audio::Mixer::kMaxChannelVolume; v &= 0xC0; v |= (0x3F - vol); } } // Since AdLib's lowest volume level does not imply that the sound is // completely silent we ignore key on in such a case. // We also ignore key on for music whenever we do seeking. if (r >= 0xB0 && r <= 0xB8) { const int channel = r - 0xB0; bool mute = false; if (_hwChannels[channel].sfxOwner) { if (!_sfxVolume) { mute = true; } } else { if (!_musicVolume) { mute = true; } else { mute = _isSeeking; } } if (mute) { v &= ~0x20; } } _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)); bool hasRhythmData = false; uint instruments = _musicData[10]; for (uint i = 0; i < instruments; ++i) { const int instrIndex = _musicData[11 + i] - 1; if (0 <= instrIndex && instrIndex < 16) { _instrumentOffset[instrIndex] = i * 16 + 16 + 3; hasRhythmData |= (_musicData[_instrumentOffset[instrIndex] + 13] != 0); } } if (hasRhythmData) { _mdvdrState = 0x20; limitHWChannels(6); } else { _mdvdrState = 0; limitHWChannels(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 = _musicData[3] * (isLoom ? 2 : 1); _loopFlag = (_musicData[4] == 0); _musicLoopStart = _curOffset + READ_LE_UINT16(_musicData + 5); } void Player_AD::stopMusic() { if (_soundPlaying == -1) { return; } // Unlock the music resource if present _vm->_res->unlock(rtSound, _soundPlaying); _soundPlaying = -1; // Stop the music playback _curOffset = 0; // Stop all music voice channels for (int i = 0; i < ARRAYSIZE(_voiceChannels); ++i) { if (_voiceChannels[i].lastEvent) { noteOff(i); } } // Reset rhythm state writeReg(0xBD, 0x00); limitHWChannels(9); } 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) { if (parseCommand()) { // We received an EOT command. In case there's no music playing // we know there was no looping enabled. Thus, we stop further // handling. Otherwise we will just continue parsing. It is // important to note that we need to parse a command directly // at the new position, i.e. there is no time value we need to // parse. if (_soundPlaying == -1) { return; } else { continue; } } // In case there is a delay till the next event stop handling. if (_musicData[_curOffset] != 0) { break; } ++_curOffset; } _nextEventTimer = parseVLQ(); _nextEventTimer >>= (_vm->_game.id == GID_LOOM) ? 2 : 1; if (!_nextEventTimer) { _nextEventTimer = 1; } } bool Player_AD::parseCommand() { uint command = _musicData[_curOffset++]; if (command == 0xFF) { // META EVENT // Get the command number. command = _musicData[_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. stopMusic(); } return true; } 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 = _musicData[_curOffset + 2] | (_musicData[_curOffset + 1] << 8); _musicTicks = 0x73000 / timing; command = _musicData[_curOffset++]; _curOffset += command; } else { // In case an unknown meta event occurs just skip over the // data by using the length supplied. command = _musicData[_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 (_musicData[instrOffset + 13] != 0) { setupRhythm(_musicData[instrOffset + 13], instrOffset); } else { // Priority 256 makes sure we always prefer music // channels over SFX channels. int channel = allocateHWChannel(256); if (channel != -1) { setupChannel(channel, _musicData + instrOffset); _voiceChannels[channel].lastEvent = command + 0x90; _voiceChannels[channel].frequency = _musicData[_curOffset]; setupFrequency(channel, _musicData[_curOffset]); } } } } else { // NOTE OFF const uint note = _musicData[_curOffset]; command += 0x10; // Find the output channel which plays the note. uint channel = 0xFF; for (int i = 0; i < ARRAYSIZE(_voiceChannels); ++i) { if (_voiceChannels[i].frequency == note && _voiceChannels[i].lastEvent == 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 && _musicData[instrOffset + 13] != 0) { const uint rhythmInstr = _musicData[instrOffset + 13]; if (rhythmInstr < 6) { _mdvdrState &= _mdvdrTable[rhythmInstr] ^ 0xFF; writeReg(0xBD, _mdvdrState); } } } } _curOffset += 2; } return false; } uint Player_AD::parseVLQ() { uint vlq = _musicData[_curOffset++]; if (vlq & 0x80) { vlq -= 0x80; vlq <<= 7; vlq |= _musicData[_curOffset++]; } return vlq; } void Player_AD::noteOff(uint channel) { writeReg(0xB0 + channel, _voiceChannels[channel].b0Reg & 0xDF); freeVoiceChannel(channel); } 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); _voiceChannels[channel].b0Reg = octave; writeReg(0xB0 + channel, octave); } void Player_AD::setupRhythm(uint rhythmInstr, uint instrOffset) { if (rhythmInstr == 1) { setupChannel(6, _musicData + instrOffset); writeReg(0xA6, _musicData[instrOffset++]); writeReg(0xB6, _musicData[instrOffset] & 0xDF); _mdvdrState |= 0x10; writeReg(0xBD, _mdvdrState); } else if (rhythmInstr < 6) { const byte *secondOperatorOffset = _musicData + instrOffset + 8; setupOperator(_rhythmOperatorTable[rhythmInstr], secondOperatorOffset); writeReg(0xA0 + _rhythmChannelTable[rhythmInstr], _musicData[instrOffset++]); writeReg(0xB0 + _rhythmChannelTable[rhythmInstr], _musicData[instrOffset++] & 0xDF); writeReg(0xC0 + _rhythmChannelTable[rhythmInstr], _musicData[instrOffset]); _mdvdrState |= _mdvdrTable[rhythmInstr]; writeReg(0xBD, _mdvdrState); } } void Player_AD::freeVoiceChannel(uint channel) { VoiceChannel &vChannel = _voiceChannels[channel]; assert(vChannel.lastEvent); freeHWChannel(channel); vChannel.lastEvent = 0; vChannel.b0Reg = 0; vChannel.frequency = 0; } void Player_AD::musicSeekTo(const uint position) { // This method is actually dangerous to use and should only be used for // loading save games because it does not set up anything like the engine // music timer or similar. _isSeeking = true; // Seek until the given position. while (_curOffset != position) { if (parseCommand()) { // We encountered an EOT command. This should not happen unless // we try to seek to an illegal position. In this case just abort // seeking. ::debugC(3, DEBUG_SOUND, "AD illegal seek to %u", position); break; } parseVLQ(); } _isSeeking = false; // Turn on all notes. for (int i = 0; i < ARRAYSIZE(_voiceChannels); ++i) { if (_voiceChannels[i].lastEvent != 0) { const int reg = 0xB0 + i; writeReg(reg, readReg(reg)); } } } 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 Player_AD::SfxSlot *Player_AD::allocateSfxSlot(int priority) { // First pass: Check whether there's a unused slot for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { if (_sfx[i].resource == -1) { return &_sfx[i]; } } // Second pass: Look for a slot with lower priority for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { if (_sfx[i].priority <= priority) { // Stop the old sfx stopSfx(&_sfx[i]); return &_sfx[i]; } } return nullptr; } bool Player_AD::startSfx(SfxSlot *sfx, const byte *resource) { writeReg(0xBD, 0x00); // Clear the channels. sfx->channels[0].state = kChannelStateOff; sfx->channels[1].state = kChannelStateOff; sfx->channels[2].state = kChannelStateOff; // Set up the first channel to pick up playback. // Try to allocate a hardware channel. sfx->channels[0].hardwareChannel = allocateHWChannel(sfx->priority, sfx); if (sfx->channels[0].hardwareChannel == -1) { ::debugC(3, DEBUG_SOUND, "AD No hardware channel available"); return false; } sfx->channels[0].currentOffset = sfx->channels[0].startOffset = resource + 2; sfx->channels[0].state = kChannelStateParse; // Scan for the start of the other channels and set them up if required. int curChannel = 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; if (curChannel >= 3) { error("AD SFX resource %d uses more than 3 channels", sfx->resource); } sfx->channels[curChannel].hardwareChannel = allocateHWChannel(sfx->priority, sfx); if (sfx->channels[curChannel].hardwareChannel == -1) { ::debugC(3, DEBUG_SOUND, "AD No hardware channel available"); return false; } sfx->channels[curChannel].currentOffset = bufferPosition; sfx->channels[curChannel].startOffset = bufferPosition; sfx->channels[curChannel].state = kChannelStateParse; ++curChannel; break; } } return true; } void Player_AD::stopSfx(SfxSlot *sfx) { if (sfx->resource == -1) { return; } // 1. step: Clear all the channels. for (int i = 0; i < ARRAYSIZE(sfx->channels); ++i) { if (sfx->channels[i].state) { clearChannel(sfx->channels[i]); sfx->channels[i].state = kChannelStateOff; } if (sfx->channels[i].hardwareChannel != -1) { freeHWChannel(sfx->channels[i].hardwareChannel); sfx->channels[i].hardwareChannel = -1; } } // 2. step: Unlock the resource. _vm->_res->unlock(rtSound, sfx->resource); sfx->resource = -1; } void Player_AD::updateSfx() { if (--_sfxTimer) { return; } _sfxTimer = 4; for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { if (_sfx[i].resource == -1) { continue; } bool hasActiveChannel = false; for (int j = 0; j < ARRAYSIZE(_sfx[i].channels); ++j) { if (_sfx[i].channels[j].state) { hasActiveChannel = true; updateChannel(&_sfx[i].channels[j]); } } // In case no channel is active we will stop the sfx. if (!hasActiveChannel) { stopSfx(&_sfx[i]); } } } void Player_AD::clearChannel(const Channel &channel) { writeReg(0xA0 + channel.hardwareChannel, 0x00); writeReg(0xB0 + channel.hardwareChannel, 0x00); } void Player_AD::updateChannel(Channel *channel) { if (channel->state == kChannelStateParse) { parseSlot(channel); } else { updateSlot(channel); } } void Player_AD::parseSlot(Channel *channel) { while (true) { const byte *curOffset = channel->currentOffset; switch (*curOffset) { case 1: // INSTRUMENT DEFINITION ++curOffset; channel->instrumentData[0] = *(curOffset + 0); channel->instrumentData[1] = *(curOffset + 2); channel->instrumentData[2] = *(curOffset + 9); channel->instrumentData[3] = *(curOffset + 8); channel->instrumentData[4] = *(curOffset + 4); channel->instrumentData[5] = *(curOffset + 3); channel->instrumentData[6] = 0; setupChannel(channel->hardwareChannel, curOffset); writeReg(0xA0 + channel->hardwareChannel, *(curOffset + 0)); writeReg(0xB0 + channel->hardwareChannel, *(curOffset + 1) & 0xDF); channel->currentOffset += 15; break; case 2: // NOTE DEFINITION ++curOffset; channel->state = kChannelStatePlay; noteOffOn(channel->hardwareChannel); parseNote(&channel->notes[0], *channel, curOffset + 0); parseNote(&channel->notes[1], *channel, curOffset + 5); return; case 0x80: // LOOP channel->currentOffset = 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); channel->state = kChannelStateOff; return; } } } void Player_AD::updateSlot(Channel *channel) { const byte *curOffset = channel->currentOffset + 1; for (int num = 0; num <= 1; ++num, curOffset += 5) { if (!(*curOffset & 0x80)) { continue; } Note *const note = &channel->notes[num]; bool updateNote = false; if (note->state == kNoteStateSustain) { if (!--note->sustainTimer) { updateNote = true; } } else { updateNote = processNoteEnvelope(note); if (note->bias) { writeRegisterSpecial(channel->hardwareChannel, note->bias - note->instrumentValue, *curOffset & 0x07); } else { writeRegisterSpecial(channel->hardwareChannel, note->instrumentValue, *curOffset & 0x07); } } if (updateNote) { if (processNote(note, *channel, curOffset)) { if (!(*curOffset & 0x08)) { channel->currentOffset += 11; channel->state = kChannelStateParse; continue; } else if (*curOffset & 0x10) { noteOffOn(channel->hardwareChannel); } note->state = kNoteStatePreInit; processNote(note, *channel, curOffset); } } if ((*curOffset & 0x20) && !--note->playTime) { channel->currentOffset += 11; channel->state = kChannelStateParse; } } } void Player_AD::parseNote(Note *note, const Channel &channel, const byte *offset) { if (*offset & 0x80) { note->state = kNoteStatePreInit; processNote(note, channel, offset); note->playTime = 0; if (*offset & 0x20) { note->playTime = (*(offset + 4) >> 4) * 118; note->playTime += (*(offset + 4) & 0x0F) * 8; } } } bool Player_AD::processNote(Note *note, const Channel &channel, const byte *offset) { if (++note->state == kNoteStateOff) { return true; } const int instrumentDataOffset = *offset & 0x07; note->bias = _noteBiasTable[instrumentDataOffset]; uint8 instrumentDataValue = 0; if (note->state == kNoteStateAttack) { instrumentDataValue = channel.instrumentData[instrumentDataOffset]; } uint8 noteInstrumentValue = readRegisterSpecial(channel.hardwareChannel, instrumentDataValue, instrumentDataOffset); if (note->bias) { noteInstrumentValue = note->bias - noteInstrumentValue; } note->instrumentValue = noteInstrumentValue; if (note->state == kNoteStateSustain) { note->sustainTimer = _numStepsTable[*(offset + 3) >> 4]; if (*offset & 0x40) { note->sustainTimer = (((getRnd() << 8) * note->sustainTimer) >> 16) + 1; } } else { int timer1, timer2; if (note->state == kNoteStateRelease) { timer1 = *(offset + 3) & 0x0F; timer2 = 0; } else { timer1 = *(offset + note->state + 1) >> 4; timer2 = *(offset + 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 channel, uint8 value, int offset) { if (offset == 6) { return; } uint8 regNum; if (_useOperatorTable[offset]) { regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + channel * 2]; } else { regNum = _channelOffsetTable[channel]; } regNum += _baseRegisterTable[offset]; uint8 regValue = readReg(regNum) & (~_registerMaskTable[offset]); regValue |= value << _registerShiftTable[offset]; writeReg(regNum, regValue); } uint8 Player_AD::readRegisterSpecial(int channel, uint8 defaultValue, int offset) { if (offset == 6) { return 0; } uint8 regNum; if (_useOperatorTable[offset]) { regNum = _operatorOffsetTable[_channelOperatorOffsetTable[offset] + channel * 2]; } else { regNum = _channelOffsetTable[channel]; } 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(Note *note, int steps, int adjust) { note->preIncrease = 0; if (ABS(adjust) > steps) { note->preIncrease = 1; note->adjust = adjust / steps; note->envelope.stepIncrease = ABS(adjust % steps); } else { note->adjust = adjust; note->envelope.stepIncrease = ABS(adjust); } note->envelope.step = steps; note->envelope.stepCounter = 0; note->envelope.timer = steps; } bool Player_AD::processNoteEnvelope(Note *note) { if (note->preIncrease) { note->instrumentValue += note->adjust; } note->envelope.stepCounter += note->envelope.stepIncrease; if (note->envelope.stepCounter >= note->envelope.step) { note->envelope.stepCounter -= note->envelope.step; if (note->adjust < 0) { --note->instrumentValue; } else { ++note->instrumentValue; } } if (--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