diff options
Diffstat (limited to 'engines/scumm/players')
34 files changed, 13693 insertions, 0 deletions
diff --git a/engines/scumm/players/player_ad.cpp b/engines/scumm/players/player_ad.cpp new file mode 100644 index 0000000000..20630e1cb9 --- /dev/null +++ b/engines/scumm/players/player_ad.cpp @@ -0,0 +1,959 @@ +/* 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 diff --git a/engines/scumm/players/player_ad.h b/engines/scumm/players/player_ad.h new file mode 100644 index 0000000000..fbb65fbe24 --- /dev/null +++ b/engines/scumm/players/player_ad.h @@ -0,0 +1,190 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_AD_H +#define SCUMM_PLAYERS_PLAYER_AD_H + +#include "scumm/music.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" + +#include "common/mutex.h" + +namespace OPL { +class OPL; +} + +namespace Scumm { + +class ScummEngine; + +/** + * Sound output for v3/v4 AdLib data. + */ +class Player_AD : public MusicEngine, public Audio::AudioStream { +public: + Player_AD(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_AD(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + virtual void saveLoadWithSerializer(Serializer *ser); + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return false; } + virtual bool endOfData() const { return false; } + virtual int getRate() const { return _rate; } + +private: + ScummEngine *const _vm; + Common::Mutex _mutex; + Audio::Mixer *const _mixer; + const int _rate; + Audio::SoundHandle _soundHandle; + void setupVolume(); + + OPL::OPL *_opl2; + + int _samplesPerCallback; + int _samplesPerCallbackRemainder; + int _samplesTillCallback; + int _samplesTillCallbackRemainder; + + int _soundPlaying; + int _engineMusicTimer; + + // AdLib register utilities + uint8 _registerBackUpTable[256]; + void writeReg(int r, int v); + uint8 readReg(int r) const; + + // Instrument setup + void setupChannel(const uint channel, uint instrOffset) { + setupChannel(channel, _resource + instrOffset); + } + void setupChannel(const uint channel, const byte *instrOffset); + void setupOperator(const uint opr, const byte *&instrOffset); + static const int _operatorOffsetTable[18]; + + // Sound data + const byte *_resource; + + // Music handling + void startMusic(); + void updateMusic(); + void noteOff(uint channel); + int findFreeChannel(); + void setupFrequency(uint channel, int8 frequency); + void setupRhythm(uint rhythmInstr, uint instrOffset); + + uint _timerLimit; + uint _musicTicks; + uint _musicTimer; + uint _internalMusicTimer; + bool _loopFlag; + uint _musicLoopStart; + uint _instrumentOffset[16]; + uint _channelLastEvent[9]; + uint _channelFrequency[9]; + uint _channelB0Reg[9]; + + uint _mdvdrState; + uint _voiceChannels; + + uint _curOffset; + uint _nextEventTimer; + + static const uint _noteFrequencies[12]; + static const uint _mdvdrTable[6]; + static const uint _rhythmOperatorTable[6]; + static const uint _rhythmChannelTable[6]; + + // SFX handling + void startSfx(); + void updateSfx(); + void clearChannel(int channel); + void updateChannel(int channel); + void parseSlot(int channel); + void updateSlot(int channel); + void parseNote(int channel, int num, const byte *offset); + bool processNote(int note, const byte *offset); + void noteOffOn(int channel); + void writeRegisterSpecial(int note, uint8 value, int offset); + uint8 readRegisterSpecial(int note, uint8 defaultValue, int offset); + void setupNoteEnvelopeState(int note, int steps, int adjust); + bool processNoteEnvelope(int note, int &instrumentValue); + + int _sfxTimer; + + int _sfxResource[3]; + int _sfxPriority[3]; + + struct Channel { + int state; + const byte *currentOffset; + const byte *startOffset; + uint8 instrumentData[7]; + } _channels[11]; + + uint8 _rndSeed; + uint8 getRnd(); + + struct Note { + int state; + int playTime; + int sustainTimer; + int instrumentValue; + int bias; + int preIncrease; + int adjust; + + struct Envelope { + int stepIncrease; + int step; + int stepCounter; + int timer; + } envelope; + } _notes[22]; + + static const uint _noteBiasTable[7]; + static const uint _numStepsTable[16]; + static const uint _noteAdjustScaleTable[7]; + static const uint _noteAdjustTable[16]; + static const bool _useOperatorTable[7]; + static const uint _channelOffsetTable[11]; + static const uint _channelOperatorOffsetTable[7]; + static const uint _baseRegisterTable[7]; + static const uint _registerMaskTable[7]; + static const uint _registerShiftTable[7]; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_apple2.cpp b/engines/scumm/players/player_apple2.cpp new file mode 100644 index 0000000000..87b8100f22 --- /dev/null +++ b/engines/scumm/players/player_apple2.cpp @@ -0,0 +1,500 @@ +/* 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 "engines/engine.h" +#include "scumm/players/player_apple2.h" +#include "scumm/scumm.h" + +namespace Scumm { + +/************************************ + * Apple-II sound-resource parsers + ************************************/ + +/* + * SoundFunction1: frequency up/down + */ +class AppleII_SoundFunction1_FreqUpDown : public AppleII_SoundFunction { +public: + virtual void init(Player_AppleII *player, const byte *params) { + _player = player; + _delta = params[0]; + _count = params[1]; + _interval = params[2]; + _limit = params[3]; + _decInterval = (params[4] >= 0x40); + } + + virtual bool update() { // D085 + if (_decInterval) { + do { + _update(_interval, _count); + _interval -= _delta; + } while (_interval >= _limit); + } else { + do { + _update(_interval, _count); + _interval += _delta; + } while (_interval < _limit); + } + return true; + } + +private: + void _update(int interval /*a*/, int count /*y*/) { // D076 + assert(interval > 0); // 0 == 256? + assert(count > 0); // 0 == 256? + + for (; count >= 0; --count) { + _player->speakerToggle(); + _player->generateSamples(17 + 5 * interval); + } + } + +protected: + int _delta; + int _count; + byte _interval; // must be unsigned byte ("interval < delta" possible) + int _limit; + bool _decInterval; +}; + +/* + * SoundFunction2: symmetric wave (~) + */ +class AppleII_SoundFunction2_SymmetricWave : public AppleII_SoundFunction { +public: + virtual void init(Player_AppleII *player, const byte *params) { + _player = player; + _params = params; + _pos = 1; + } + + virtual bool update() { // D0D6 + // while (pos = 1; pos < 256; ++pos) + if (_pos < 256) { + byte interval = _params[_pos]; + if (interval == 0xFF) + return true; + _update(interval, _params[0] /*, LD12F=interval*/); + + ++_pos; + return false; + } + return true; + } + +private: + void _update(int interval /*a*/, int count) { // D0EF + if (interval == 0xFE) { + _player->wait(interval, 10); + } else { + assert(count > 0); // 0 == 256? + assert(interval > 0); // 0 == 256? + + int a = (interval >> 3) + count; + for (int y = a; y > 0; --y) { + _player->generateSamples(1292 - 5*interval); + _player->speakerToggle(); + + _player->generateSamples(1287 - 5*interval); + _player->speakerToggle(); + } + } + } + +protected: + const byte *_params; + int _pos; +}; + +/* + * SoundFunction3: asymmetric wave (__-) + */ +class AppleII_SoundFunction3_AsymmetricWave : public AppleII_SoundFunction { +public: + virtual void init(Player_AppleII *player, const byte *params) { + _player = player; + _params = params; + _pos = 1; + } + + virtual bool update() { // D132 + // while (pos = 1; pos < 256; ++pos) + if (_pos < 256) { + byte interval = _params[_pos]; + if (interval == 0xFF) + return true; + _update(interval, _params[0]); + + ++_pos; + return false; + } + return true; + } + +private: + void _update(int interval /*a*/, int count /*LD12D*/) { // D14B + if (interval == 0xFE) { + _player->wait(interval, 70); + } else { + assert(interval > 0); // 0 == 256? + assert(count > 0); // 0 == 256? + + for (int y = count; y > 0; --y) { + _player->generateSamples(1289 - 5*interval); + _player->speakerToggle(); + } + } + } + +protected: + const byte *_params; + int _pos; +}; + +/* + * SoundFunction4: polyphone (2 voices) + */ +class AppleII_SoundFunction4_Polyphone : public AppleII_SoundFunction { +public: + virtual void init(Player_AppleII *player, const byte *params) { + _player = player; + _params = params; + _updateRemain1 = 80; + _updateRemain2 = 10; + _count = 0; + } + + virtual bool update() { // D170 + // while (_params[0] != 0x01) + if (_params[0] != 0x01) { + if (_count == 0) // prepare next loop + nextLoop(_params[0], _params[1], _params[2]); + if (loopIteration()) // loop finished -> fetch next parameter set + _params += 3; + return false; + } + return true; + } + +private: + /* + * prepare for next parameter set loop + */ + void nextLoop(byte param0, byte param1, byte param2) { // LD182 + _count = (-param2 << 8) | 0x3; + + _bitmask1 = 0x3; + _bitmask2 = 0x3; + + _updateInterval2 = param0; + if (_updateInterval2 == 0) + _bitmask2 = 0x0; + + _updateInterval1 = param1; + if (_updateInterval1 == 0) { + _bitmask1 = 0x0; + if (_bitmask2 != 0) { + _bitmask1 = _bitmask2; + _bitmask2 = 0; + _updateInterval1 = _updateInterval2; + } + } + + _speakerShiftReg = 0; + } + + /* + * perform one loop iteration + * Returns true if loop finished + */ + bool loopIteration() { // D1A2 + --_updateRemain1; + --_updateRemain2; + + if (_updateRemain2 == 0) { + _updateRemain2 = _updateInterval2; + // use only first voice's data (bitmask1) if both voices are triggered + if (_updateRemain1 != 0) { + _speakerShiftReg ^= _bitmask2; + } + } + + if (_updateRemain1 == 0) { + _updateRemain1 = _updateInterval1; + _speakerShiftReg ^= _bitmask1; + } + + if (_speakerShiftReg & 0x1) + _player->speakerToggle(); + _speakerShiftReg >>= 1; + _player->generateSamples(42); /* actually 42.5 */ + + ++_count; + return (_count == 0); + } + +protected: + const byte *_params; + + byte _updateRemain1; + byte _updateRemain2; + + uint16 _count; + byte _bitmask1; + byte _bitmask2; + byte _updateInterval1; + byte _updateInterval2; + byte _speakerShiftReg; +}; + +/* + * SoundFunction5: periodic noise + */ +class AppleII_SoundFunction5_Noise : public AppleII_SoundFunction { +public: + virtual void init(Player_AppleII *player, const byte *params) { + _player = player; + _index = 0; + _param0 = params[0]; + assert(_param0 > 0); + } + + virtual bool update() { // D222 + const byte noiseMask[] = { + 0x3F, 0x3F, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F + }; + + // while (i = 0; i < 10; ++i) + if (_index < 10) { + int count = _param0; + do { + _update(noise() & noiseMask[_index], 1); + --count; + } while (count > 0); + + ++_index; + return false; + } + + return true; + } + +private: + void _update(int interval /*a*/, int count) { // D270 + assert(count > 0); // 0 == 256? + if (interval == 0) + interval = 256; + + for (int i = count; i > 0; --i) { + _player->generateSamples(10 + 5*interval); + _player->speakerToggle(); + + _player->generateSamples(5 + 5*interval); + _player->speakerToggle(); + } + } + + byte /*a*/ noise() { // D261 + static int pos = 0; // initial value? + byte result = _noiseTable[pos]; + pos = (pos + 1) % 256; + return result; + } + +protected: + int _index; + int _param0; + +private: + static const byte _noiseTable[256]; +}; + +// LD000[loc] ^ LD00A[loc] +const byte AppleII_SoundFunction5_Noise::_noiseTable[256] = { + 0x65, 0x1b, 0xda, 0x11, 0x61, 0xe5, 0x77, 0x57, 0x92, 0xc8, 0x51, 0x1c, 0xd4, 0x91, 0x62, 0x63, + 0x00, 0x38, 0x57, 0xd5, 0x18, 0xd8, 0xdc, 0x40, 0x03, 0x86, 0xd3, 0x2f, 0x10, 0x11, 0xd8, 0x3c, + 0xbe, 0x00, 0x19, 0xc5, 0xd2, 0xc3, 0xca, 0x34, 0x00, 0x28, 0xbf, 0xb9, 0x18, 0x20, 0x01, 0xcc, + 0xda, 0x08, 0xbc, 0x75, 0x7c, 0xb0, 0x8d, 0xe0, 0x09, 0x18, 0xbf, 0x5d, 0xe9, 0x8c, 0x75, 0x64, + 0xe5, 0xb5, 0x5d, 0xe0, 0xb7, 0x7d, 0xe9, 0x8c, 0x55, 0x65, 0xc5, 0xb5, 0x5d, 0xd8, 0x09, 0x0d, + 0x64, 0xf0, 0xf0, 0x08, 0x63, 0x03, 0x00, 0x55, 0x35, 0xc0, 0x00, 0x20, 0x74, 0xa5, 0x1e, 0xe3, + 0x00, 0x06, 0x3c, 0x52, 0xd1, 0x70, 0xd0, 0x57, 0x02, 0xf0, 0x00, 0xb6, 0xfc, 0x02, 0x11, 0x9a, + 0x3b, 0xc8, 0x38, 0xdf, 0x1a, 0xb0, 0xd1, 0xb8, 0xd0, 0x18, 0x8a, 0x4a, 0xea, 0x1b, 0x12, 0x5d, + 0x29, 0x58, 0xd8, 0x43, 0xb8, 0x2d, 0xd2, 0x61, 0x10, 0x3c, 0x0c, 0x5d, 0x1b, 0x61, 0x10, 0x3c, + 0x0a, 0x5d, 0x1d, 0x61, 0x10, 0x3c, 0x0b, 0x19, 0x88, 0x21, 0xc0, 0x21, 0x07, 0x00, 0x65, 0x62, + 0x08, 0xe9, 0x36, 0x40, 0x20, 0x41, 0x06, 0x00, 0x20, 0x00, 0x00, 0xed, 0xa3, 0x00, 0x88, 0x06, + 0x98, 0x01, 0x5d, 0x7f, 0x02, 0x1d, 0x78, 0x03, 0x60, 0xcb, 0x3a, 0x01, 0xbd, 0x78, 0x02, 0x5d, + 0x7e, 0x03, 0x1d, 0xf5, 0xa6, 0x40, 0x81, 0xb4, 0xd0, 0x8d, 0xd3, 0xd0, 0x6d, 0xd5, 0x61, 0x48, + 0x61, 0x4d, 0xd1, 0xc8, 0xb1, 0xd8, 0x69, 0xff, 0x61, 0xd9, 0xed, 0xa0, 0xfe, 0x19, 0x91, 0x37, + 0x19, 0x37, 0x00, 0xf1, 0x00, 0x01, 0x1f, 0x00, 0xad, 0xc1, 0x01, 0x01, 0x2e, 0x00, 0x40, 0xc6, + 0x7a, 0x9b, 0x95, 0x43, 0xfc, 0x18, 0xd2, 0x9e, 0x2a, 0x5a, 0x4b, 0x2a, 0xb6, 0x87, 0x30, 0x6c +}; + +/************************************ + * Apple-II player + ************************************/ + +Player_AppleII::Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer) + : _mixer(mixer), _vm(scumm), _soundFunc(0) { + resetState(); + setSampleRate(_mixer->getOutputRate()); + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_AppleII::~Player_AppleII() { + _mixer->stopHandle(_soundHandle); + delete _soundFunc; +} + +void Player_AppleII::resetState() { + _soundNr = 0; + _type = 0; + _loop = 0; + _params = NULL; + _speakerState = 0; + delete _soundFunc; + _soundFunc = 0; + _sampleConverter.reset(); +} + +void Player_AppleII::startSound(int nr) { + Common::StackLock lock(_mutex); + + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + byte *ptr1 = data + 4; + + resetState(); + _soundNr = nr; + _type = ptr1[0]; + _loop = ptr1[1]; + _params = &ptr1[2]; + + switch (_type) { + case 0: // empty (nothing to play) + resetState(); + return; + case 1: + _soundFunc = new AppleII_SoundFunction1_FreqUpDown(); + break; + case 2: + _soundFunc = new AppleII_SoundFunction2_SymmetricWave(); + break; + case 3: + _soundFunc = new AppleII_SoundFunction3_AsymmetricWave(); + break; + case 4: + _soundFunc = new AppleII_SoundFunction4_Polyphone(); + break; + case 5: + _soundFunc = new AppleII_SoundFunction5_Noise(); + break; + } + _soundFunc->init(this, _params); + + assert(_loop > 0); + + debug(4, "startSound %d: type %d, loop %d", + nr, _type, _loop); +} + +bool Player_AppleII::updateSound() { + if (!_soundFunc) + return false; + + if (_soundFunc->update()) { + --_loop; + if (_loop <= 0) { + delete _soundFunc; + _soundFunc = 0; + } else { + // reset function state on each loop + _soundFunc->init(this, _params); + } + } + + return true; +} + +void Player_AppleII::stopAllSounds() { + Common::StackLock lock(_mutex); + resetState(); +} + +void Player_AppleII::stopSound(int nr) { + Common::StackLock lock(_mutex); + if (_soundNr == nr) { + resetState(); + } +} + +int Player_AppleII::getSoundStatus(int nr) const { + Common::StackLock lock(_mutex); + return (_soundNr == nr); +} + +int Player_AppleII::getMusicTimer() { + /* Apple-II sounds are synchronous -> no music timer */ + return 0; +} + +int Player_AppleII::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + if (!_soundNr) + return 0; + + int samplesLeft = numSamples; + do { + int nSamplesRead = _sampleConverter.readSamples(buffer, samplesLeft); + samplesLeft -= nSamplesRead; + buffer += nSamplesRead; + } while ((samplesLeft > 0) && updateSound()); + + // reset state if sound is played completely + if (!_soundFunc && (_sampleConverter.availableSize() == 0)) + resetState(); + + return numSamples - samplesLeft; +} + +/************************************ + * Apple-II sound-resource helpers + ************************************/ + +// toggle speaker on/off +void Player_AppleII::speakerToggle() { + _speakerState ^= 0x1; +} + +void Player_AppleII::generateSamples(int cycles) { + _sampleConverter.addCycles(_speakerState, cycles); +} + +void Player_AppleII::wait(int interval, int count /*y*/) { + assert(count > 0); // 0 == 256? + assert(interval > 0); // 0 == 256? + generateSamples(11 + count*(8 + 5 * interval)); +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_apple2.h b/engines/scumm/players/player_apple2.h new file mode 100644 index 0000000000..9930a4f95d --- /dev/null +++ b/engines/scumm/players/player_apple2.h @@ -0,0 +1,297 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_APPLEII_H +#define SCUMM_PLAYERS_PLAYER_APPLEII_H + +#include "common/mutex.h" +#include "common/scummsys.h" +#include "common/memstream.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/softsynth/sid.h" + +namespace Scumm { + +class ScummEngine; + +/* + * Optimized for use with periodical read/write phases when the buffer + * is filled in a write phase and completely read in a read phase. + * The growing strategy is optimized for repeated small (e.g. 2 bytes) + * single writes resulting in large buffers + * (avg.: 4KB, max: 18KB @ 16bit/22.050kHz (MM sound21)). + */ +class SampleBuffer { +public: + SampleBuffer() : _data(0) { + clear(); + } + + ~SampleBuffer() { + free(_data); + } + + void clear() { + free(_data); + _data = 0; + _capacity = 0; + _writePos = 0; + _readPos = 0; + } + + void ensureFree(uint32 needed) { + // if data was read completely, reset read/write pos to front + if ((_writePos != 0) && (_writePos == _readPos)) { + _writePos = 0; + _readPos = 0; + } + + // check for enough space at end of buffer + uint32 freeEndCnt = _capacity - _writePos; + if (needed <= freeEndCnt) + return; + + uint32 avail = availableSize(); + + // check for enough space at beginning and end of buffer + if (needed <= _readPos + freeEndCnt) { + // move unread data to front of buffer + memmove(_data, _data + _readPos, avail); + _writePos = avail; + _readPos = 0; + } else { // needs a grow + byte *old_data = _data; + uint32 new_len = avail + needed; + + _capacity = new_len + 2048; + _data = (byte *)malloc(_capacity); + + if (old_data) { + // copy old unread data to front of new buffer + memcpy(_data, old_data + _readPos, avail); + free(old_data); + _writePos = avail; + _readPos = 0; + } + } + } + + uint32 availableSize() const { + if (_readPos >= _writePos) + return 0; + return _writePos - _readPos; + } + + uint32 write(const void *dataPtr, uint32 dataSize) { + ensureFree(dataSize); + memcpy(_data + _writePos, dataPtr, dataSize); + _writePos += dataSize; + return dataSize; + } + + uint32 read(byte *dataPtr, uint32 dataSize) { + uint32 avail = availableSize(); + if (avail == 0) + return 0; + if (dataSize > avail) + dataSize = avail; + memcpy(dataPtr, _data + _readPos, dataSize); + _readPos += dataSize; + return dataSize; + } + +private: + uint32 _writePos; + uint32 _readPos; + uint32 _capacity; + byte *_data; +}; + +// CPU_CLOCK according to AppleWin +static const double APPLEII_CPU_CLOCK = 1020484.5; // ~ 1.02 MHz + +/* + * Converts the 1-bit speaker state values into audio samples. + * This is done by aggregation of the speaker states at each + * CPU cycle in a sampling period into an audio sample. + */ +class SampleConverter { +private: + void addSampleToBuffer(int sample) { + int16 value = sample * _volume / _maxVolume; + _buffer.write(&value, sizeof(value)); + } + +public: + SampleConverter() : + _cyclesPerSampleFP(0), + _missingCyclesFP(0), + _sampleCyclesSumFP(0), + _volume(_maxVolume) + {} + + ~SampleConverter() {} + + void reset() { + _missingCyclesFP = 0; + _sampleCyclesSumFP = 0; + _buffer.clear(); + } + + uint32 availableSize() const { + return _buffer.availableSize(); + } + + void setMusicVolume(int vol) { + assert(vol >= 0 && vol <= _maxVolume); + _volume = vol; + } + + void setSampleRate(int rate) { + /* ~46 CPU cycles per sample @ 22.05kHz */ + _cyclesPerSampleFP = int(APPLEII_CPU_CLOCK * (1 << PREC_SHIFT) / rate); + reset(); + } + + void addCycles(byte level, const int cycles) { + /* convert to fixed precision floats */ + int cyclesFP = cycles << PREC_SHIFT; + + // step 1: if cycles are left from the last call, process them first + if (_missingCyclesFP > 0) { + int n = (_missingCyclesFP < cyclesFP) ? _missingCyclesFP : cyclesFP; + if (level) + _sampleCyclesSumFP += n; + cyclesFP -= n; + _missingCyclesFP -= n; + if (_missingCyclesFP == 0) { + addSampleToBuffer(2*32767 * _sampleCyclesSumFP / _cyclesPerSampleFP - 32767); + } else { + return; + } + } + + _sampleCyclesSumFP = 0; + + // step 2: process blocks of cycles fitting into a whole sample + while (cyclesFP >= _cyclesPerSampleFP) { + addSampleToBuffer(level ? 32767 : -32767); + cyclesFP -= _cyclesPerSampleFP; + } + + // step 3: remember cycles left for next call + if (cyclesFP > 0) { + _missingCyclesFP = _cyclesPerSampleFP - cyclesFP; + if (level) + _sampleCyclesSumFP = cyclesFP; + } + } + + uint32 readSamples(void *buffer, int numSamples) { + return _buffer.read((byte *)buffer, numSamples * 2) / 2; + } + +private: + static const int PREC_SHIFT = 7; + +private: + int _cyclesPerSampleFP; /* (fixed precision) */ + int _missingCyclesFP; /* (fixed precision) */ + int _sampleCyclesSumFP; /* (fixed precision) */ + int _volume; /* 0 - 256 */ + static const int _maxVolume = 256; + SampleBuffer _buffer; +}; + +class Player_AppleII; + +class AppleII_SoundFunction { +public: + AppleII_SoundFunction() {} + virtual ~AppleII_SoundFunction() {} + virtual void init(Player_AppleII *player, const byte *params) = 0; + /* returns true if finished */ + virtual bool update() = 0; +protected: + Player_AppleII *_player; +}; + +class Player_AppleII : public Audio::AudioStream, public MusicEngine { +public: + Player_AppleII(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_AppleII(); + + virtual void setMusicVolume(int vol) { _sampleConverter.setMusicVolume(vol); } + void setSampleRate(int rate) { + _sampleRate = rate; + _sampleConverter.setSampleRate(rate); + } + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getSoundStatus(int sound) const; + virtual int getMusicTimer(); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return false; } + bool endOfData() const { return false; } + int getRate() const { return _sampleRate; } + +public: + void speakerToggle(); + void generateSamples(int cycles); + void wait(int interval, int count); + +private: + // sound number + int _soundNr; + // type of sound + int _type; + // number of loops left + int _loop; + // global sound param list + const byte *_params; + // speaker toggle state (0 / 1) + byte _speakerState; + // sound function + AppleII_SoundFunction *_soundFunc; + // cycle to sample converter + SampleConverter _sampleConverter; + +private: + ScummEngine *_vm; + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + int _sampleRate; + Common::Mutex _mutex; + +private: + void resetState(); + bool updateSound(); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_mac.cpp b/engines/scumm/players/player_mac.cpp new file mode 100644 index 0000000000..281eec5336 --- /dev/null +++ b/engines/scumm/players/player_mac.cpp @@ -0,0 +1,419 @@ +/* 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 "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/players/player_mac.h" +#include "scumm/resource.h" +#include "scumm/scumm.h" +#include "scumm/imuse/imuse.h" + +namespace Scumm { + +Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds) + : _vm(scumm), + _mixer(mixer), + _sampleRate(_mixer->getOutputRate()), + _soundPlaying(-1), + _numberOfChannels(numberOfChannels), + _channelMask(channelMask), + _fadeNoteEnds(fadeNoteEnds) { + assert(scumm); + assert(mixer); +} + +void Player_Mac::init() { + _channel = new Player_Mac::Channel[_numberOfChannels]; + + int i; + + for (i = 0; i < _numberOfChannels; i++) { + _channel[i]._looped = false; + _channel[i]._length = 0; + _channel[i]._data = NULL; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + _channel[i]._instrument._data = NULL; + _channel[i]._instrument._size = 0; + _channel[i]._instrument._rate = 0; + _channel[i]._instrument._loopStart = 0; + _channel[i]._instrument._loopEnd = 0; + _channel[i]._instrument._baseFreq = 0; + _channel[i]._instrument._pos = 0; + _channel[i]._instrument._subPos = 0; + } + + _pitchTable[116] = 1664510; + _pitchTable[117] = 1763487; + _pitchTable[118] = 1868350; + _pitchTable[119] = 1979447; + _pitchTable[120] = 2097152; + _pitchTable[121] = 2221855; + _pitchTable[122] = 2353973; + _pitchTable[123] = 2493948; + _pitchTable[124] = 2642246; + _pitchTable[125] = 2799362; + _pitchTable[126] = 2965820; + _pitchTable[127] = 3142177; + for (i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + setMusicVolume(255); + + if (!checkMusicAvailable()) { + return; + } + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_Mac::~Player_Mac() { + Common::StackLock lock(_mutex); + _mixer->stopHandle(_soundHandle); + stopAllSounds_Internal(); + delete[] _channel; +} + +void Player_Mac::saveLoadWithSerializer(Serializer *ser) { + Common::StackLock lock(_mutex); + if (ser->getVersion() < VER(94)) { + if (_vm->_game.id == GID_MONKEY && ser->isLoading()) { + IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL); + dummyImuse->save_or_load(ser, _vm, false); + delete dummyImuse; + } + } else { + static const SaveLoadEntry musicEntries[] = { + MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)), + MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)), + MKEND() + }; + + static const SaveLoadEntry channelEntries[] = { + MKLINE(Channel, _pos, sleUint16, VER(94)), + MKLINE(Channel, _pitchModifier, sleInt32, VER(94)), + MKLINE(Channel, _velocity, sleUint8, VER(94)), + MKLINE(Channel, _remaining, sleUint32, VER(94)), + MKLINE(Channel, _notesLeft, sleUint8, VER(94)), + MKEND() + }; + + static const SaveLoadEntry instrumentEntries[] = { + MKLINE(Instrument, _pos, sleUint32, VER(94)), + MKLINE(Instrument, _subPos, sleUint32, VER(94)), + MKEND() + }; + + uint32 mixerSampleRate = _sampleRate; + int i; + + ser->saveLoadEntries(this, musicEntries); + + if (ser->isLoading() && _soundPlaying != -1) { + const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying); + assert(ptr); + loadMusic(ptr); + } + + ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries); + for (i = 0; i < _numberOfChannels; i++) { + ser->saveLoadEntries(&_channel[i], instrumentEntries); + } + + if (ser->isLoading()) { + // If necessary, adjust the channel data to fit the + // current sample rate. + if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) { + double mult = (double)_sampleRate / (double)mixerSampleRate; + for (i = 0; i < _numberOfChannels; i++) { + _channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult); + _channel[i]._remaining = (int)((double)_channel[i]._remaining / mult); + } + } + _sampleRate = mixerSampleRate; + } + } +} + +void Player_Mac::setMusicVolume(int vol) { + debug(5, "Player_Mac::setMusicVolume(%d)", vol); +} + +void Player_Mac::stopAllSounds_Internal() { + if (_soundPlaying != -1) { + _vm->_res->unlock(rtSound, _soundPlaying); + } + _soundPlaying = -1; + for (int i = 0; i < _numberOfChannels; i++) { + // The channel data is managed by the resource manager, so + // don't delete that. + delete[] _channel[i]._instrument._data; + _channel[i]._instrument._data = NULL; + + _channel[i]._remaining = 0; + _channel[i]._notesLeft = false; + } +} + +void Player_Mac::stopAllSounds() { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::stopAllSounds()"); + stopAllSounds_Internal(); +} + +void Player_Mac::stopSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::stopSound(%d)", nr); + + if (nr == _soundPlaying) { + stopAllSounds(); + } +} + +void Player_Mac::startSound(int nr) { + Common::StackLock lock(_mutex); + debug(5, "Player_Mac::startSound(%d)", nr); + + stopAllSounds_Internal(); + + const byte *ptr = _vm->getResourceAddress(rtSound, nr); + assert(ptr); + + if (!loadMusic(ptr)) { + return; + } + + _vm->_res->lock(rtSound, nr); + _soundPlaying = nr; +} + +bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) { + uint16 soundType = stream->readUint16BE(); + if (soundType != 1) { + warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType); + return false; + } + uint16 typeCount = stream->readUint16BE(); + if (typeCount != 1) { + warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount); + return false; + } + uint16 dataType = stream->readUint16BE(); + if (dataType != 5) { + warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType); + return false; + } + + stream->readUint32BE(); // initialization option + + uint16 cmdCount = stream->readUint16BE(); + if (cmdCount != 1) { + warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount); + return false; + } + uint16 command = stream->readUint16BE(); + if (command != 0x8050 && command != 0x8051) { + warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command); + return false; + } + + stream->readUint16BE(); // 0 + uint32 soundHeaderOffset = stream->readUint32BE(); + + stream->seek(soundHeaderOffset); + + uint32 soundDataOffset = stream->readUint32BE(); + uint32 size = stream->readUint32BE(); + uint32 rate = stream->readUint32BE() >> 16; + uint32 loopStart = stream->readUint32BE(); + uint32 loopEnd = stream->readUint32BE(); + byte encoding = stream->readByte(); + byte baseFreq = stream->readByte(); + + if (encoding != 0) { + warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding); + return false; + } + + stream->skip(soundDataOffset); + + byte *data = new byte[size]; + stream->read(data, size); + + _instrument._data = data; + _instrument._size = size; + _instrument._rate = rate; + _instrument._loopStart = loopStart; + _instrument._loopEnd = loopEnd; + _instrument._baseFreq = baseFreq; + + return true; +} + +int Player_Mac::getMusicTimer() { + return 0; +} + +int Player_Mac::getSoundStatus(int nr) const { + return _soundPlaying == nr; +} + +uint32 Player_Mac::durationToSamples(uint16 duration) { + // The correct formula should be: + // + // (duration * 473 * _sampleRate) / (4 * 480 * 480) + // + // But that's likely to cause integer overflow, so we do it in two + // steps using bitwise operations to perform + // ((duration * 473 * _sampleRate) / 4096) without overflowing, + // then divide this by 225 + // (note that 4 * 480 * 480 == 225 * 4096 == 225 << 12) + // + // The original code is a bit unclear on if it should be 473 or 437, + // but since the comments indicated 473 I'm assuming 437 was a typo. + uint32 samples = (duration * _sampleRate); + samples = (samples >> 12) * 473 + (((samples & 4095) * 473) >> 12); + samples = samples / 225; + return samples; +} + +int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) { + if (note > 0) { + const int pitchIdx = note + 60 - instrument->_baseFreq; + // I don't want to use floating-point arithmetics here, but I + // ran into overflow problems with the church music in Monkey + // Island. It's only once per note, so it should be ok. + double mult = (double)instrument->_rate / (double)_sampleRate; + return (int)(mult * _pitchTable[pitchIdx]); + } else { + return 0; + } +} + +int Player_Mac::readBuffer(int16 *data, const int numSamples) { + Common::StackLock lock(_mutex); + + memset(data, 0, numSamples * 2); + if (_soundPlaying == -1) { + return numSamples; + } + + bool notesLeft = false; + + for (int i = 0; i < _numberOfChannels; i++) { + if (!(_channelMask & (1 << i))) { + continue; + } + + uint samplesLeft = numSamples; + int16 *ptr = data; + + while (samplesLeft > 0) { + int generated; + if (_channel[i]._remaining == 0) { + uint32 samples; + int pitchModifier; + byte velocity; + if (getNextNote(i, samples, pitchModifier, velocity)) { + _channel[i]._remaining = samples; + _channel[i]._pitchModifier = pitchModifier; + _channel[i]._velocity = velocity; + + } else { + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = samplesLeft; + } + } + generated = MIN<uint32>(_channel[i]._remaining, samplesLeft); + if (_channel[i]._velocity != 0) { + _channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds); + } + ptr += generated; + samplesLeft -= generated; + _channel[i]._remaining -= generated; + } + + if (_channel[i]._notesLeft) { + notesLeft = true; + } + } + + if (!notesLeft) { + stopAllSounds_Internal(); + } + + return numSamples; +} + +void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) { + int samplesLeft = numSamples; + while (samplesLeft) { + _subPos += pitchModifier; + while (_subPos >= 0x10000) { + _subPos -= 0x10000; + _pos++; + if (_pos >= _loopEnd) { + _pos = _loopStart; + } + } + + int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255; + + if (fadeNoteEnds) { + // Fade out the last 100 samples on each note. Even at + // low output sample rates this is just a fraction of a + // second, but it gets rid of distracting "pops" at the + // end when the sample would otherwise go abruptly from + // something to nothing. This was particularly + // noticeable on the distaff notes in Loom. + // + // The reason it's conditional is that Monkey Island + // appears to have a "hold current note" command, and + // if we fade out the current note in that case we + // will actually introduce new "pops". + + remainingSamplesOnNote--; + if (remainingSamplesOnNote < 100) { + newSample = (newSample * remainingSamplesOnNote) / 100; + } + } + + int sample = *data + newSample; + if (sample > 32767) { + sample = 32767; + } else if (sample < -32768) { + sample = -32768; + } + + *data++ = sample; + samplesLeft--; + } +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_mac.h b/engines/scumm/players/player_mac.h new file mode 100644 index 0000000000..7f9f42c34e --- /dev/null +++ b/engines/scumm/players/player_mac.h @@ -0,0 +1,133 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_MAC_H +#define SCUMM_PLAYERS_PLAYER_MAC_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/saveload.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +#define RES_SND MKTAG('s', 'n', 'd', ' ') + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm Macintosh music driver, base class. + */ +class Player_Mac : public Audio::AudioStream, public MusicEngine { +public: + Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds); + virtual ~Player_Mac(); + + void init(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return false; } + virtual bool endOfData() const { return false; } + virtual int getRate() const { return _sampleRate; } + + virtual void saveLoadWithSerializer(Serializer *ser); + +private: + Common::Mutex _mutex; + Audio::Mixer *const _mixer; + Audio::SoundHandle _soundHandle; + uint32 _sampleRate; + int _soundPlaying; + + void stopAllSounds_Internal(); + + struct Instrument { + byte *_data; + uint32 _size; + uint32 _rate; + uint32 _loopStart; + uint32 _loopEnd; + byte _baseFreq; + + uint _pos; + uint _subPos; + + void newNote() { + _pos = 0; + _subPos = 0; + } + + void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds); + }; + + int _pitchTable[128]; + int _numberOfChannels; + int _channelMask; + bool _fadeNoteEnds; + + virtual bool checkMusicAvailable() { return false; } + virtual bool loadMusic(const byte *ptr) { return false; } + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { return false; } + +protected: + struct Channel { + virtual ~Channel() {} + + Instrument _instrument; + bool _looped; + uint32 _length; + const byte *_data; + + uint _pos; + int _pitchModifier; + byte _velocity; + uint32 _remaining; + + bool _notesLeft; + + bool loadInstrument(Common::SeekableReadStream *stream); + }; + + ScummEngine *const _vm; + Channel *_channel; + + uint32 durationToSamples(uint16 duration); + int noteToPitchModifier(byte note, Instrument *instrument); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_mod.cpp b/engines/scumm/players/player_mod.cpp new file mode 100644 index 0000000000..abaa8c1fc5 --- /dev/null +++ b/engines/scumm/players/player_mod.cpp @@ -0,0 +1,223 @@ +/* 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_mod.h" +#include "audio/mixer.h" +#include "audio/rate.h" +#include "audio/decoders/raw.h" + +namespace Scumm { + +Player_MOD::Player_MOD(Audio::Mixer *mixer) + : _mixer(mixer), _sampleRate(mixer->getOutputRate()) { + int i; + _mixamt = 0; + _mixpos = 0; + + for (i = 0; i < MOD_MAXCHANS; i++) { + _channels[i].id = 0; + _channels[i].vol = 0; + _channels[i].freq = 0; + _channels[i].input = NULL; + _channels[i].ctr = 0; + _channels[i].pos = 0; + } + + _playproc = NULL; + _playparam = NULL; + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_MOD::~Player_MOD() { + _mixer->stopHandle(_soundHandle); + for (int i = 0; i < MOD_MAXCHANS; i++) { + if (!_channels[i].id) + continue; + delete _channels[i].input; + } +} + +void Player_MOD::setMusicVolume(int vol) { + _maxvol = vol; +} + +void Player_MOD::setUpdateProc(ModUpdateProc *proc, void *param, int freq) { + _playproc = proc; + _playparam = param; + _mixamt = _sampleRate / freq; +} +void Player_MOD::clearUpdateProc() { + _playproc = NULL; + _playparam = NULL; + _mixamt = 0; +} + +void Player_MOD::startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart, int loopEnd, int8 pan) { + int i; + if (id == 0) + error("player_mod - attempted to start channel id 0"); + + for (i = 0; i < MOD_MAXCHANS; i++) { + if (!_channels[i].id) + break; + } + if (i == MOD_MAXCHANS) { + warning("player_mod - too many music channels playing (%i max)",MOD_MAXCHANS); + return; + } + _channels[i].id = id; + _channels[i].vol = vol; + _channels[i].pan = pan; + _channels[i].freq = rate; + _channels[i].ctr = 0; + + Audio::SeekableAudioStream *stream = Audio::makeRawStream((const byte *)data, size, rate, 0); + if (loopStart != loopEnd) { + _channels[i].input = new Audio::SubLoopingAudioStream(stream, 0, Audio::Timestamp(0, loopStart, rate), Audio::Timestamp(0, loopEnd, rate)); + } else { + _channels[i].input = stream; + } + + // read the first sample + _channels[i].input->readBuffer(&_channels[i].pos, 1); +} + +void Player_MOD::stopChannel(int id) { + if (id == 0) + error("player_mod - attempted to stop channel id 0"); + for (int i = 0; i < MOD_MAXCHANS; i++) { + if (_channels[i].id == id) { + delete _channels[i].input; + _channels[i].input = NULL; + _channels[i].id = 0; + _channels[i].vol = 0; + _channels[i].freq = 0; + _channels[i].ctr = 0; + _channels[i].pos = 0; + } + } +} +void Player_MOD::setChannelVol(int id, uint8 vol) { + if (id == 0) + error("player_mod - attempted to set volume for channel id 0"); + for (int i = 0; i < MOD_MAXCHANS; i++) { + if (_channels[i].id == id) { + _channels[i].vol = vol; + break; + } + } +} + +void Player_MOD::setChannelPan(int id, int8 pan) { + if (id == 0) + error("player_mod - attempted to set pan for channel id 0"); + for (int i = 0; i < MOD_MAXCHANS; i++) { + if (_channels[i].id == id) { + _channels[i].pan = pan; + break; + } + } +} + +void Player_MOD::setChannelFreq(int id, int freq) { + if (id == 0) + error("player_mod - attempted to set frequency for channel id 0"); + for (int i = 0; i < MOD_MAXCHANS; i++) { + if (_channels[i].id == id) { + if (freq > 31400) // this is about as high as WinUAE goes + freq = 31400; // can't easily verify on my own Amiga + _channels[i].freq = freq; + break; + } + } +} + +void Player_MOD::do_mix(int16 *data, uint len) { + int i; + int dpos = 0; + uint dlen = 0; + memset(data, 0, 2 * len * sizeof(int16)); + while (len) { + if (_playproc) { + dlen = _mixamt - _mixpos; + if (!_mixpos) + _playproc(_playparam); + if (dlen <= len) { + _mixpos = 0; + len -= dlen; + } else { + _mixpos = len; + dlen = len; + len = 0; + } + } else { + dlen = len; + len = 0; + } + for (i = 0; i < MOD_MAXCHANS; i++) { + if (_channels[i].id) { + Audio::st_volume_t vol_l = (127 - _channels[i].pan) * _channels[i].vol / 127; + Audio::st_volume_t vol_r = (127 + _channels[i].pan) * _channels[i].vol / 127; + for (uint j = 0; j < dlen; j++) { + // simple linear resample, unbuffered + int delta = (uint32)(_channels[i].freq * 0x10000) / _sampleRate; + uint16 cfrac = ~_channels[i].ctr & 0xFFFF; + if (_channels[i].ctr + delta < 0x10000) + cfrac = delta; + _channels[i].ctr += delta; + int32 cpos = _channels[i].pos * cfrac / 0x10000; + while (_channels[i].ctr >= 0x10000) { + if (_channels[i].input->readBuffer(&_channels[i].pos, 1) != 1) { // out of data + stopChannel(_channels[i].id); + goto skipchan; // exit 2 loops at once + } + _channels[i].ctr -= 0x10000; + if (_channels[i].ctr > 0x10000) + cpos += _channels[i].pos; + else + cpos += (int32)(_channels[i].pos * (_channels[i].ctr & 0xFFFF)) / 0x10000; + } + int16 pos = 0; + // if too many samples play in a row, the calculation below will overflow and clip + // so try and split it up into pieces it can manage comfortably + while (cpos < -0x8000) { + pos -= 0x80000000 / delta; + cpos += 0x8000; + } + while (cpos > 0x7FFF) { + pos += 0x7FFF0000 / delta; + cpos -= 0x7FFF; + } + pos += cpos * 0x10000 / delta; + Audio::clampedAdd(data[(dpos + j) * 2 + 0], pos * vol_l / Audio::Mixer::kMaxMixerVolume); + Audio::clampedAdd(data[(dpos + j) * 2 + 1], pos * vol_r / Audio::Mixer::kMaxMixerVolume); + } + } +skipchan: ; // channel ran out of data + } + dpos += dlen; + } +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_mod.h b/engines/scumm/players/player_mod.h new file mode 100644 index 0000000000..d4a5b16fca --- /dev/null +++ b/engines/scumm/players/player_mod.h @@ -0,0 +1,100 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_MOD_H +#define SCUMM_PLAYERS_PLAYER_MOD_H + +#include "scumm/scumm.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +namespace Audio { +class RateConverter; +} + +namespace Scumm { + +/** + * Generic Amiga MOD mixer - provides a 60Hz 'update' routine. + */ +class Player_MOD : public Audio::AudioStream { +public: + Player_MOD(Audio::Mixer *mixer); + virtual ~Player_MOD(); + virtual void setMusicVolume(int vol); + + virtual void startChannel(int id, void *data, int size, int rate, uint8 vol, int loopStart = 0, int loopEnd = 0, int8 pan = 0); + virtual void stopChannel(int id); + virtual void setChannelVol(int id, uint8 vol); + virtual void setChannelPan(int id, int8 pan); + virtual void setChannelFreq(int id, int freq); + + typedef void ModUpdateProc(void *param); + + virtual void setUpdateProc(ModUpdateProc *proc, void *param, int freq); + virtual void clearUpdateProc(); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples) { + do_mix(buffer, numSamples / 2); + return numSamples; + } + bool isStereo() const { return true; } + bool endOfData() const { return false; } + int getRate() const { return _sampleRate; } + +private: + enum { + MOD_MAXCHANS = 24 + }; + + struct soundChan { + int id; + uint8 vol; + int8 pan; + uint16 freq; + + uint32 ctr; + int16 pos; + Audio::AudioStream *input; + }; + + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + + uint32 _mixamt; + uint32 _mixpos; + const int _sampleRate; + + soundChan _channels[MOD_MAXCHANS]; + + uint8 _maxvol; + + virtual void do_mix(int16 *buf, uint len); + + ModUpdateProc *_playproc; + void *_playparam; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_nes.cpp b/engines/scumm/players/player_nes.cpp new file mode 100644 index 0000000000..f55f1f9edd --- /dev/null +++ b/engines/scumm/players/player_nes.cpp @@ -0,0 +1,1067 @@ +/* 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 + * aint32 with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef DISABLE_NES_APU + +#include "engines/engine.h" +#include "scumm/players/player_nes.h" +#include "scumm/scumm.h" +#include "audio/mixer.h" + +namespace Scumm { + +static const byte channelMask[4] = {1, 2, 4, 8}; + +static const uint16 freqTable[64] = { + 0x07F0, 0x077E, 0x0712, 0x06AE, 0x064E, 0x05F3, 0x059E, 0x054D, + 0x0501, 0x04B9, 0x0475, 0x0435, 0x03F8, 0x03BF, 0x0389, 0x0357, + 0x0327, 0x02F9, 0x02CF, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, + 0x01FC, 0x01DF, 0x01C4, 0x01AB, 0x0193, 0x017C, 0x0167, 0x0152, + 0x013F, 0x012D, 0x011C, 0x010C, 0x00FD, 0x00EE, 0x00E1, 0x00D4, + 0x00C8, 0x00BD, 0x00B2, 0x00A8, 0x009F, 0x0096, 0x008D, 0x0085, + 0x007E, 0x0076, 0x0070, 0x0069, 0x0063, 0x005E, 0x0058, 0x0053, + 0x004F, 0x004A, 0x0046, 0x0042, 0x003E, 0x003A, 0x0037, 0x0034 +}; + +static const byte instChannel[16] = { + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 1, 3, 3, 3 +}; +static const byte startCmd[16] = { + 0x05, 0x03, 0x06, 0x08, 0x0B, 0x01, 0x01, 0x1A, + 0x16, 0x06, 0x04, 0x17, 0x02, 0x10, 0x0E, 0x0D +}; +static const byte releaseCmd[16] = { + 0x0F, 0x00, 0x00, 0x09, 0x00, 0x14, 0x15, 0x00, + 0x00, 0x00, 0x1B, 0x1B, 0x0F, 0x0F, 0x0F, 0x0F +}; +static const byte nextCmd[28] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0x07, 0xFF, + 0xFF, 0x0A, 0x09, 0x0C, 0x00, 0x00, 0x00, 0x00, + 0x11, 0x12, 0x11, 0x03, 0xFF, 0xFF, 0x18, 0x00, + 0x19, 0x00, 0x00, 0x00 +}; +static const byte nextDelay[28] = { + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x05, 0x08, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x03, 0x00, 0x00, 0x00 +}; + +namespace APUe { + +static const byte LengthCounts[32] = { + 0x0A,0xFE, + 0x14,0x02, + 0x28,0x04, + 0x50,0x06, + 0xA0,0x08, + 0x3C,0x0A, + 0x0E,0x0C, + 0x1A,0x0E, + + 0x0C,0x10, + 0x18,0x12, + 0x30,0x14, + 0x60,0x16, + 0xC0,0x18, + 0x48,0x1A, + 0x10,0x1C, + 0x20,0x1E +}; + +class SoundGen { +protected: + byte wavehold; + uint32 freq; // short + uint32 CurD; + +public: + byte Timer; + int32 Pos; + uint32 Cycles; // short + + inline byte GetTimer() const { return Timer; } +}; + +class Square : public SoundGen { +protected: + byte volume, envelope, duty, swpspeed, swpdir, swpstep, swpenab; + byte Vol; + byte EnvCtr, Envelope, BendCtr; + bool Enabled, ValidFreq, Active; + bool EnvClk, SwpClk; + + void CheckActive(); + +public: + void Reset(); + void Write(int Reg, byte Val); + void Run(); + void QuarterFrame(); + void HalfFrame(); +}; + +static const int8 Duties[4][8] = { + {-4,+4,-4,-4,-4,-4,-4,-4}, + {-4,+4,+4,-4,-4,-4,-4,-4}, + {-4,+4,+4,+4,+4,-4,-4,-4}, + {+4,-4,-4,+4,+4,+4,+4,+4} +}; + +void Square::Reset() { + memset(this, 0, sizeof(*this)); + Cycles = 1; + EnvCtr = 1; + BendCtr = 1; +} + +void Square::CheckActive() { + ValidFreq = (freq >= 0x8) && ((swpdir) || !((freq + (freq >> swpstep)) & 0x800)); + Active = Timer && ValidFreq; + Pos = Active ? (Duties[duty][CurD] * Vol) : 0; +} + +void Square::Write(int Reg, byte Val) { + switch (Reg) { + case 0: + volume = Val & 0xF; + envelope = Val & 0x10; + wavehold = Val & 0x20; + duty = (Val >> 6) & 0x3; + Vol = envelope ? volume : Envelope; + break; + + case 1: + swpstep = Val & 0x07; + swpdir = Val & 0x08; + swpspeed = (Val >> 4) & 0x7; + swpenab = Val & 0x80; + SwpClk = true; + break; + + case 2: + freq &= 0x700; + freq |= Val; + break; + + case 3: + freq &= 0xFF; + freq |= (Val & 0x7) << 8; + + if (Enabled) + Timer = LengthCounts[(Val >> 3) & 0x1F]; + + CurD = 0; + EnvClk = true; + break; + + case 4: + Enabled = (Val != 0); + if (!Enabled) + Timer = 0; + break; + } + CheckActive(); +} + +void Square::Run() { + Cycles = (freq + 1) << 1; + CurD = (CurD + 1) & 0x7; + + if (Active) + Pos = Duties[duty][CurD] * Vol; +} + +void Square::QuarterFrame() { + if (EnvClk) { + EnvClk = false; + Envelope = 0xF; + EnvCtr = volume + 1; + } else if (!--EnvCtr) { + EnvCtr = volume + 1; + + if (Envelope) + Envelope--; + else + Envelope = wavehold ? 0xF : 0x0; + } + + Vol = envelope ? volume : Envelope; + CheckActive(); +} + +void Square::HalfFrame() { + if (!--BendCtr) { + BendCtr = swpspeed + 1; + + if (swpenab && swpstep && ValidFreq) { + int sweep = freq >> swpstep; + // FIXME: Is -sweep or ~sweep correct??? + freq += swpdir ? -sweep : sweep; + } + } + + if (SwpClk) { + SwpClk = false; + BendCtr = swpspeed + 1; + } + + if (Timer && !wavehold) + Timer--; + + CheckActive(); +} + + +class Triangle : public SoundGen { +protected: + byte linear; + byte LinCtr; + bool Enabled, Active; + bool LinClk; + + void CheckActive(); + +public: + void Reset(); + void Write(int Reg, byte Val); + void Run(); + void QuarterFrame(); + void HalfFrame(); +}; + +static const int8 TriDuty[32] = { + -8,-7,-6,-5,-4,-3,-2,-1, + +0,+1,+2,+3,+4,+5,+6,+7, + +7,+6,+5,+4,+3,+2,+1,+0, + -1,-2,-3,-4,-5,-6,-7,-8 +}; + +void Triangle::Reset() { + memset(this, 0, sizeof(*this)); + Cycles = 1; +} + +void Triangle::CheckActive() { + Active = Timer && LinCtr; + + if (freq < 4) + Pos = 0; // beyond hearing range + else + Pos = TriDuty[CurD] * 8; +} + +void Triangle::Write(int Reg, byte Val) { + switch (Reg) { + case 0: + linear = Val & 0x7F; + wavehold = (Val >> 7) & 0x1; + break; + + case 2: + freq &= 0x700; + freq |= Val; + break; + + case 3: + freq &= 0xFF; + freq |= (Val & 0x7) << 8; + + if (Enabled) + Timer = LengthCounts[(Val >> 3) & 0x1F]; + + LinClk = true; + break; + + case 4: + Enabled = (Val != 0); + if (!Enabled) + Timer = 0; + break; + } + CheckActive(); +} + +void Triangle::Run() { + Cycles = freq + 1; + + if (Active) { + CurD++; + CurD &= 0x1F; + + if (freq < 4) + Pos = 0; // beyond hearing range + else + Pos = TriDuty[CurD] * 8; + } +} + +void Triangle::QuarterFrame() { + if (LinClk) + LinCtr = linear; + else if (LinCtr) + LinCtr--; + + if (!wavehold) + LinClk = false; + + CheckActive(); +} + +void Triangle::HalfFrame() { + if (Timer && !wavehold) + Timer--; + + CheckActive(); +} + +class Noise : public SoundGen { +protected: + byte volume, envelope, datatype; + byte Vol; + byte EnvCtr, Envelope; + bool Enabled; + bool EnvClk; + + void CheckActive(); + +public: + void Reset(); + void Write(int Reg, byte Val); + void Run(); + void QuarterFrame(); + void HalfFrame(); +}; + +static const uint32 NoiseFreq[16] = { + 0x004,0x008,0x010,0x020,0x040,0x060,0x080,0x0A0, + 0x0CA,0x0FE,0x17C,0x1FC,0x2FA,0x3F8,0x7F2,0xFE4 +}; + +void Noise::Reset() { + memset(this, 0, sizeof(*this)); + CurD = 1; + Cycles = 1; + EnvCtr = 1; + +} + +void Noise::Write(int Reg, byte Val) { + switch (Reg) { + case 0: + volume = Val & 0x0F; + envelope = Val & 0x10; + wavehold = Val & 0x20; + Vol = envelope ? volume : Envelope; + + if (Timer) + Pos = ((CurD & 0x4000) ? -2 : 2) * Vol; + break; + + case 2: + freq = Val & 0xF; + datatype = Val & 0x80; + break; + + case 3: + if (Enabled) + Timer = LengthCounts[(Val >> 3) & 0x1F]; + + EnvClk = true; + break; + + case 4: + Enabled = (Val != 0); + if (!Enabled) + Timer = 0; + break; + } +} + +void Noise::Run() { + Cycles = NoiseFreq[freq]; /* no + 1 here */ + + if (datatype) + CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 8)) & 0x1); + else + CurD = (CurD << 1) | (((CurD >> 14) ^ (CurD >> 13)) & 0x1); + + if (Timer) + Pos = ((CurD & 0x4000) ? -2 : 2) * Vol; +} + +void Noise::QuarterFrame() { + if (EnvClk) { + EnvClk = false; + Envelope = 0xF; + EnvCtr = volume + 1; + } else if (!--EnvCtr) { + EnvCtr = volume + 1; + + if (Envelope) + Envelope--; + else + Envelope = wavehold ? 0xF : 0x0; + } + + Vol = envelope ? volume : Envelope; + + if (Timer) + Pos = ((CurD & 0x4000) ? -2 : 2) * Vol; +} + +void Noise::HalfFrame() { + if (Timer && !wavehold) + Timer--; +} + +class APU { +protected: + int BufPos; + int SampleRate; + + Square _square0; + Square _square1; + Triangle _triangle; + Noise _noise; + + struct { + uint32 Cycles; + int Num; + } Frame; + +public: + APU(int rate) : SampleRate(rate) { + Reset(); + } + + void WriteReg(int Addr, byte Val); + byte Read4015(); + void Reset (); + int16 GetSample(); +}; + +void APU::WriteReg(int Addr, byte Val) { + switch (Addr) { + case 0x000: _square0.Write(0,Val); break; + case 0x001: _square0.Write(1,Val); break; + case 0x002: _square0.Write(2,Val); break; + case 0x003: _square0.Write(3,Val); break; + case 0x004: _square1.Write(0,Val); break; + case 0x005: _square1.Write(1,Val); break; + case 0x006: _square1.Write(2,Val); break; + case 0x007: _square1.Write(3,Val); break; + case 0x008: _triangle.Write(0,Val); break; + case 0x009: _triangle.Write(1,Val); break; + case 0x00A: _triangle.Write(2,Val); break; + case 0x00B: _triangle.Write(3,Val); break; + case 0x00C: _noise.Write(0,Val); break; + case 0x00D: _noise.Write(1,Val); break; + case 0x00E: _noise.Write(2,Val); break; + case 0x00F: _noise.Write(3,Val); break; + case 0x015: _square0.Write(4,Val & 0x1); + _square1.Write(4,Val & 0x2); + _triangle.Write(4,Val & 0x4); + _noise.Write(4,Val & 0x8); + break; + } +} + +byte APU::Read4015() { + byte result = + (( _square0.GetTimer()) ? 0x01 : 0) | + (( _square1.GetTimer()) ? 0x02 : 0) | + ((_triangle.GetTimer()) ? 0x04 : 0) | + (( _noise.GetTimer()) ? 0x08 : 0); + return result; +} + +void APU::Reset () { + BufPos = 0; + + _square0.Reset(); + _square1.Reset(); + _triangle.Reset(); + _noise.Reset(); + + Frame.Num = 0; + Frame.Cycles = 1; +} + +template<class T> +int step(T &obj, int sampcycles, uint frame_Cycles, int frame_Num) { + int samppos = 0; + while (sampcycles) { + // Compute the maximal amount we can step ahead before triggering + // an action (i.e. compute the minimum of sampcycles, frame_Cycles + // and obj.Cycles). + uint max_step = sampcycles; + if (max_step > frame_Cycles) + max_step = frame_Cycles; + if (max_step > obj.Cycles) + max_step = obj.Cycles; + + // During all but the last of these steps, we just add the value of obj.Pos + // to samppos -- so we can to that all at once with a simple multiplication: + samppos += obj.Pos * (max_step - 1); + + // Now step ahead... + sampcycles -= max_step; + frame_Cycles -= max_step; + obj.Cycles -= max_step; + + if (!frame_Cycles) { + frame_Cycles = 7457; + + if (frame_Num < 4) { + obj.QuarterFrame(); + + if (frame_Num & 1) + frame_Cycles++; + else + obj.HalfFrame(); + + frame_Num++; + } else + frame_Num = 0; + } + + if (!obj.Cycles) + obj.Run(); + + samppos += obj.Pos; + } + + return samppos; +} + +int16 APU::GetSample() { + int samppos = 0; + + const int sampcycles = 1+(1789773-BufPos-1)/SampleRate; + BufPos = BufPos + sampcycles * SampleRate - 1789773; + + samppos += step( _square0, sampcycles, Frame.Cycles, Frame.Num); + samppos += step( _square1, sampcycles, Frame.Cycles, Frame.Num); + samppos += step(_triangle, sampcycles, Frame.Cycles, Frame.Num); + samppos += step( _noise, sampcycles, Frame.Cycles, Frame.Num); + + uint tmp = sampcycles; + while (tmp >= Frame.Cycles) { + tmp -= Frame.Cycles; + Frame.Cycles = 7457; + + if (Frame.Num < 4) { + if (Frame.Num & 1) + Frame.Cycles++; + Frame.Num++; + } else + Frame.Num = 0; + } + + Frame.Cycles -= tmp; + + return (samppos << 6) / sampcycles; +} + +} // End of namespace APUe + +Player_NES::Player_NES(ScummEngine *scumm, Audio::Mixer *mixer) { + int i; + _vm = scumm; + _mixer = mixer; + _sampleRate = _mixer->getOutputRate(); + _apu = new APUe::APU(_sampleRate); + + _samples_per_frame = _sampleRate / 60; + _current_sample = 0; + + for (i = 0; i < NUMSLOTS; i++) { + _slot[i].id = -1; + _slot[i].framesleft = 0; + _slot[i].type = 0; + _slot[i].offset = 0; + _slot[i].data = NULL; + } + + for (i = 0; i < NUMCHANS; i++) { + _mchan[i].command = 0; + _mchan[i].framedelay = 0; + _mchan[i].pitch = 0; + _mchan[i].volume = 0; + _mchan[i].voldelta = 0; + _mchan[i].envflags = 0; + _mchan[i].cmdlock = 0; + } + isSFXplaying = wasSFXplaying = false; + + auxData1 = auxData2 = NULL; + numNotes = 0; + + APU_writeControl(0); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_NES::~Player_NES() { + _mixer->stopHandle(_soundHandle); + delete _apu; +} + +void Player_NES::setMusicVolume (int vol) { + _maxvol = vol; +} + +int Player_NES::readBuffer(int16 *buffer, const int numSamples) { + for (int n = 0; n < numSamples; n++) { + buffer[n] = _apu->GetSample() * _maxvol / 255; + _current_sample++; + + if (_current_sample == _samples_per_frame) { + _current_sample = 0; + sound_play(); + } + } + return numSamples; +} +void Player_NES::stopAllSounds() { + for (int i = 0; i < NUMSLOTS; i++) { + _slot[i].framesleft = 0; + _slot[i].type = 0; + _slot[i].id = -1; + } + + isSFXplaying = 0; + checkSilenceChannels(0); +} + +void Player_NES::stopSound(int nr) { + if (nr == -1) + return; + + for (int i = 0; i < NUMSLOTS; i++) { + if (_slot[i].id != nr) + continue; + + isSFXplaying = 0; + _slot[i].framesleft = 0; + _slot[i].type = 0; + _slot[i].id = -1; + checkSilenceChannels(i); + } +} + +void Player_NES::startSound(int nr) { + byte *data = _vm->getResourceAddress(rtSound, nr) + 2; + assert(data); + + int soundType = data[1]; + int chan = data[0]; + + if (chan == 4) { + if (_slot[2].framesleft) + return; + chan = 0; + } + + if (soundType < _slot[chan].type) + return; + + _slot[chan].type = soundType; + _slot[chan].id = nr; + _slot[chan].data = data; + _slot[chan].offset = 2; + _slot[chan].framesleft = 1; + checkSilenceChannels(chan); + if (chan == 2) { + numNotes = _slot[chan].data[2]; + auxData1 = _slot[chan].data + 3; + auxData2 = auxData1 + numNotes; + _slot[chan].data = auxData2 + numNotes; + _slot[chan].offset = 0; + + for (int i = 0; i < NUMCHANS; i++) + _mchan[i].cmdlock = 0; + } +} + +void Player_NES::checkSilenceChannels(int chan) { + for (chan--; chan >= 0; chan--) { + if (_slot[chan].framesleft) + return; + } + APU_writeControl(0); +} + +void Player_NES::sound_play() { + if (_slot[0].framesleft) + playSFX(0); + else if (_slot[1].framesleft) + playSFX(1); + + playMusic(); +} + +void Player_NES::playSFX (int nr) { + if (--_slot[nr].framesleft) + return; + + while (1) { + int a = _slot[nr].data[_slot[nr].offset++]; + if (a < 16) { + a >>= 2; + APU_writeControl(APU_readStatus() | channelMask[a]); + isSFXplaying = true; + APU_writeChannel(a, 0, _slot[nr].data[_slot[nr].offset++]); + APU_writeChannel(a, 1, _slot[nr].data[_slot[nr].offset++]); + APU_writeChannel(a, 2, _slot[nr].data[_slot[nr].offset++]); + APU_writeChannel(a, 3, _slot[nr].data[_slot[nr].offset++]); + } else if (a == 0xFE) { + _slot[nr].offset = 2; + } else if (a == 0xFF) { + _slot[nr].id = -1; + _slot[nr].type = 0; + isSFXplaying = false; + APU_writeControl(0); + + if (!nr && _slot[1].framesleft) { + _slot[1].framesleft = 1; + isSFXplaying = true; + } + return; + } else { + _slot[nr].framesleft = _slot[nr].data[_slot[nr].offset++]; + return; + } + } +} + +void Player_NES::playMusic() { + if (!_slot[2].framesleft) + return; + + if (wasSFXplaying && !isSFXplaying) + for (int x = 1; x >= 0; x--) + if (_mchan[x].cmdlock) { + _mchan[x].command = _mchan[x].cmdlock; + _mchan[x].framedelay = 1; + } + + wasSFXplaying = isSFXplaying; + if (!--_slot[2].framesleft) { +top: + int b = _slot[2].data[_slot[2].offset++]; + if (b == 0xFF) { + _slot[2].id = -1; + _slot[2].type = 0; + b = 0; + } else if (b == 0xFE) { + _slot[2].offset = 0; + goto top; + } else { + if (b < numNotes) { + int inst = auxData1[b]; + int ch = instChannel[inst]; + _mchan[ch].pitch = auxData2[b]; + _mchan[ch].cmdlock = startCmd[inst]; + _mchan[ch].command = startCmd[inst]; + _mchan[ch].framedelay = 1; + goto top; + } + b -= numNotes; + if (b < 16) { + int inst = b; + int ch = instChannel[inst]; + _mchan[ch].cmdlock = 0; + _mchan[ch].command = releaseCmd[inst]; + _mchan[ch].framedelay = 1; + goto top; + } + b -= 16; + } + _slot[2].framesleft = b; + } + + for (int x = NUMCHANS - 1; x >= 0; x--) { + if (_slot[0].framesleft || _slot[1].framesleft) { + _mchan[x].volume = 0; + _mchan[x].framedelay = 0; + continue; + } + + if (_mchan[x].framedelay && !--_mchan[x].framedelay) { + switch (_mchan[x].command) { + case 0x00: + case 0x13: + _mchan[x].voldelta = -10; + break; + + case 0x01: + case 0x03: + case 0x08: + case 0x16: + _mchan[x].envflags = 0x30; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x02: + _mchan[x].envflags = 0xB0; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x84); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x04: + _mchan[x].envflags = 0x80; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x05: + _mchan[x].envflags = 0xF0; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = -15; + + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x06: + _mchan[x].pitch += 0x18; + _mchan[x].envflags = 0x80; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x07: + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch - 0x0C] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch - 0x0C] >> 8); + + chainCommand(x); + break; + + case 0x09: + _mchan[x].voldelta = -2; + + APU_writeChannel(x, 1, 0x7F); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x0A: + APU_writeChannel(x, 1, 0x86); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x0B: case 0x1A: + _mchan[x].envflags = 0x70; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x0C: + _mchan[x].envflags = 0xB0; + + chainCommand(x); + break; + + case 0x0D: + _mchan[x].envflags = 0x30; + _mchan[x].volume = 0x5F; + _mchan[x].voldelta = -22; + + APU_writeChannel(x, 0, 0x00); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, _mchan[x].pitch & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x0E: + case 0x10: + _mchan[x].envflags = 0x30; + _mchan[x].volume = 0x5F; + _mchan[x].voldelta = -6; + + APU_writeChannel(x, 0, 0x00); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, _mchan[x].pitch & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x0F: + chainCommand(x); + break; + + case 0x11: + APU_writeChannel(x, 2, _mchan[x].pitch & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x12: + APU_writeChannel(x, 2, (_mchan[x].pitch + 3) & 0xF); + APU_writeChannel(x, 3, 0xFF); + + chainCommand(x); + break; + + case 0x14: + _mchan[x].voldelta = -12; + + APU_writeChannel(x, 1, 0x8C); + + chainCommand(x); + break; + + case 0x15: + _mchan[x].voldelta = -12; + + APU_writeChannel(x, 1, 0x84); + + chainCommand(x); + break; + + case 0x17: + _mchan[x].pitch += 0x0C; + _mchan[x].envflags = 0x80; + _mchan[x].volume = 0x6F; + _mchan[x].voldelta = 0; + + APU_writeChannel(x, 0, 0x00); + APU_writeChannel(x, 1, 0x7F); + APU_writeControl(APU_readStatus() | channelMask[x]); + APU_writeChannel(x, 2, freqTable[_mchan[x].pitch] & 0xFF); + APU_writeChannel(x, 3, freqTable[_mchan[x].pitch] >> 8); + + chainCommand(x); + break; + + case 0x18: + _mchan[x].envflags = 0x70; + + chainCommand(x); + break; + + case 0x19: + _mchan[x].envflags = 0xB0; + + chainCommand(x); + break; + + case 0x1B: + _mchan[x].envflags = 0x00; + _mchan[x].voldelta = -10; + break; + } + } + + _mchan[x].volume += _mchan[x].voldelta; + + if (_mchan[x].volume < 0) + _mchan[x].volume = 0; + if (_mchan[x].volume > MAXVOLUME) + _mchan[x].volume = MAXVOLUME; + + APU_writeChannel(x, 0, (_mchan[x].volume >> 3) | _mchan[x].envflags); + } +} + +void Player_NES::chainCommand(int c) { + int i = _mchan[c].command; + _mchan[c].command = nextCmd[i]; + _mchan[c].framedelay = nextDelay[i]; +} + +int Player_NES::getSoundStatus(int nr) const { + for (int i = 0; i < NUMSLOTS; i++) + if (_slot[i].id == nr) + return 1; + return 0; +} + +void Player_NES::APU_writeChannel(int chan, int offset, byte value) { + _apu->WriteReg(0x000 + 4 * chan + offset, value); +} +void Player_NES::APU_writeControl(byte value) { + _apu->WriteReg(0x015, value); +} +byte Player_NES::APU_readStatus() { + return _apu->Read4015(); +} + +} // End of namespace Scumm + +#endif // DISABLE_NES_APU diff --git a/engines/scumm/players/player_nes.h b/engines/scumm/players/player_nes.h new file mode 100644 index 0000000000..f0b3e79aad --- /dev/null +++ b/engines/scumm/players/player_nes.h @@ -0,0 +1,114 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_NES_H +#define SCUMM_PLAYERS_PLAYER_NES_H + +#include "common/scummsys.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +namespace Scumm { + +class ScummEngine; +namespace APUe { +class APU; +} + +static const int MAXVOLUME = 0x7F; +static const int NUMSLOTS = 3; +static const int NUMCHANS = 4; + +/** + * Scumm NES sound/music driver. + */ +class Player_NES : public Audio::AudioStream, public MusicEngine { +public: + Player_NES(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_NES(); + + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return false; } + bool endOfData() const { return false; } + int getRate() const { return _sampleRate; } + +private: + + void sound_play(); + void playSFX(int nr); + void playMusic(); + byte fetchSoundByte(int nr); + void chainCommand(int chan); + void checkSilenceChannels(int chan); + + void APU_writeChannel(int chan, int offset, byte value); + void APU_writeControl(byte value); + byte APU_readStatus(); + + ScummEngine *_vm; + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + APUe::APU *_apu; + int _sampleRate; + int _samples_per_frame; + int _current_sample; + int _maxvol; + + struct slot { + int framesleft; + int id; + int type; + byte *data; + int offset; + } _slot[NUMSLOTS]; + + struct mchan { + int command; + int framedelay; + int pitch; + int volume; + int voldelta; + int envflags; + int cmdlock; + } _mchan[NUMCHANS]; + + bool isSFXplaying, wasSFXplaying; + + byte *dataStart; + int numNotes; + byte *auxData1; + byte *auxData2; + + byte *soundptr; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_pce.cpp b/engines/scumm/players/player_pce.cpp new file mode 100644 index 0000000000..6d6e2fcde5 --- /dev/null +++ b/engines/scumm/players/player_pce.cpp @@ -0,0 +1,756 @@ +/* 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. + * + */ + +/* + * The PSG_HuC6280 class is based on the HuC6280 sound chip emulator + * by Charles MacDonald (E-mail: cgfm2@hotmail.com, WWW: http://cgfm2.emuviews.com) + * The implementation used here was taken from MESS (http://www.mess.org/) + * the Multiple Emulator Super System (sound/c6280.c). + * LFO and noise channel support have been removed (not used by Loom PCE). + */ + +#include <math.h> +#include "scumm/players/player_pce.h" +#include "common/endian.h" + +// PCE sound engine is only used by Loom, which requires 16bit color support +#ifdef USE_RGB_COLOR + +namespace Scumm { + +// CPU and PSG use the same base clock but with a different divider +const double MASTER_CLOCK = 21477270.0; // ~21.48 MHz +const double CPU_CLOCK = MASTER_CLOCK / 3; // ~7.16 MHz +const double PSG_CLOCK = MASTER_CLOCK / 6; // ~3.58 MHz +const double TIMER_CLOCK = CPU_CLOCK / 1024; // ~6.9 kHz + +// The PSG update routine is originally triggered by the timer IRQ (not by VSYNC) +// approx. 120 times per second (TIML=0x39). But as just every second call is used +// to update the PSG we will call the update routine approx. 60 times per second. +const double UPDATE_FREQ = TIMER_CLOCK / (57 + 1) / 2; // ~60 Hz + +// $AFA5 +static const byte wave_table[7][32] = { + { // sine + 0x10, 0x19, 0x1C, 0x1D, 0x1E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1E, 0x1D, 0x1C, 0x19, + 0x10, 0x05, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x05, + }, { // mw-shaped + 0x10, 0x1C, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, 0x1E, 0x1C, 0x1E, 0x1F, 0x1F, 0x1E, 0x1E, 0x1D, 0x1C, + 0x10, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, + }, { // square + 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + }, { // triangle + 0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x1F, 0x1C, 0x18, 0x14, + 0x10, 0x0C, 0x08, 0x04, 0x01, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x1F, 0x1C, 0x18, 0x14, + }, { // saw-tooth + 0x00, 0x01, 0x02, 0x03, 0x04, 0x06, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + }, { // sigmoid + 0x07, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1F, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, + 0x08, 0x06, 0x05, 0x03, 0x02, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x16, + }, { // MW-shaped + 0x1F, 0x1E, 0x1D, 0x1D, 0x1C, 0x1A, 0x17, 0x0F, 0x0F, 0x17, 0x1A, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, + 0x00, 0x01, 0x02, 0x02, 0x03, 0x05, 0x08, 0x0F, 0x0F, 0x08, 0x05, 0x03, 0x02, 0x02, 0x01, 0x00 + } +}; + +// AEBC +static const int control_offsets[14] = { + 0, 7, 20, 33, 46, 56, 75, 88, 116, 126, 136, 152, 165, 181 +}; + +// AED8 +static const byte control_data[205] = { + /* 0*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0xFF, + /* 7*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x3A, 0x00, 0xFD, 0xFF, + /* 20*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x1D, 0x00, 0xF8, 0xFF, + /* 33*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x1E, 0x00, 0xFC, 0xFF, + /* 46*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x07, 0x00, 0xE0, 0xFF, + /* 56*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x01, 0x00, 0xD8, 0xF0, 0x00, 0xD8, 0x01, 0x00, 0x00, 0x04, 0x00, 0xF0, 0xFF, + /* 75*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF8, 0x6E, 0x00, 0xFF, 0xFF, + /* 88*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x08, 0x00, 0xF0, 0xF0, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x05, 0x00, 0xF0, 0xF0, 0x00, 0xB8, 0xE6, 0x80, 0xFF, 0xE6, 0x80, 0xFF, 0xFF, + /*116*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x05, 0x00, 0xD0, 0xFF, + /*126*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x04, 0x00, 0xF8, 0xFF, + /*136*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x03, 0x00, 0xF4, 0xE6, 0xC0, 0xFF, 0xE6, 0xC0, 0xFF, 0xFF, + /*152*/ 0xF0, 0x00, 0xD0, 0x01, 0x00, 0x00, 0x02, 0x00, 0x10, 0x0E, 0x00, 0xFE, 0xFF, + /*165*/ 0xF0, 0x00, 0xA8, 0x01, 0x00, 0x00, 0x18, 0x00, 0x02, 0xE6, 0x80, 0xFE, 0xE6, 0xC0, 0xFF, 0xFF, + /*181*/ 0xF0, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x02, 0x00, 0xF8, 0x02, 0x00, 0x00, 0x02, 0x00, 0xF0, 0x01, 0x00, 0xF8, 0x02, 0x00, 0x08, 0xF1, 0x99, 0xAF +}; + +static const uint16 lookup_table[87] = { + 0x0D40, 0x0C80, 0x0BC0, 0x0B20, 0x0A80, 0x09E0, 0x0940, 0x08C0, + 0x0840, 0x07E0, 0x0760, 0x0700, 0x06A0, 0x0640, 0x05E0, 0x0590, + 0x0540, 0x04F0, 0x04A0, 0x0460, 0x0420, 0x03F0, 0x03B0, 0x0380, + 0x0350, 0x0320, 0x02F0, 0x02C8, 0x02A0, 0x0278, 0x0250, 0x0230, + 0x0210, 0x01F8, 0x01D8, 0x01C0, 0x01A8, 0x0190, 0x0178, 0x0164, + 0x0150, 0x013C, 0x0128, 0x0118, 0x0108, 0x00FC, 0x00EC, 0x00E0, + 0x00D4, 0x00C8, 0x00BC, 0x00B2, 0x00A8, 0x009E, 0x0094, 0x008C, + 0x0084, 0x007E, 0x0076, 0x0070, 0x006A, 0x0064, 0x005E, 0x0059, + 0x0054, 0x004F, 0x004A, 0x0046, 0x0042, 0x003F, 0x003B, 0x0038, + 0x0035, 0x0032, 0x0030, 0x002D, 0x002A, 0x0028, 0x0026, 0x0024, + 0x0022, 0x0020, 0x001E, 0x001C, 0x001B, 0x8E82, 0xB500 +}; + +// B27B +static const uint16 freq_offset[3] = { + 0, 2, 9 +}; + +static const uint16 freq_table[] = { + 0x0000, 0x0800, + 0xFFB0, 0xFFD1, 0xFFE8, 0xFFF1, 0x0005, 0x0000, 0x0800, + 0xFF9C, 0xFFD8, 0x0000, 0x000F, 0x0005, 0x0000, 0x0800 +}; + +static const int sound_table[13] = { + 0, 2, 3, 4, 5, 6, 7, 8, 9, 11, 1, 10, 11 +}; + +// 0xAE12 +// Note: +// - offsets relative to data_table +// - byte one of each sound was always 0x3F (= use all channels) -> removed from table +static const uint16 sounds[13][6] = { + { 481, 481, 481, 481, 481, 481 }, + { 395, 408, 467, 480, 480, 480 }, + { 85, 96, 109, 109, 109, 109 }, + { 110, 121, 134, 134, 134, 134 }, + { 135, 146, 159, 159, 159, 159 }, + { 160, 171, 184, 184, 184, 184 }, + { 185, 196, 209, 209, 209, 209 }, + { 210, 221, 234, 234, 234, 234 }, + { 235, 246, 259, 259, 259, 259 }, + { 260, 271, 284, 284, 284, 284 }, + { 285, 298, 311, 324, 335, 348 }, + { 349, 360, 361, 362, 373, 384 }, + { 0, 84, 84, 84, 84, 84 } // unused +}; + +// 0xB2A1 +static const byte data_table[482] = { + /* 0*/ 0xE2, 0x0A, 0xE1, 0x0D, 0xE6, 0xED, 0xE0, 0x0F, 0xE2, 0x00, 0xE1, 0x00, + 0xF2, 0xF2, 0xB2, 0xE1, 0x01, 0xF2, 0xF2, 0xB2, 0xE1, 0x02, 0xF2, 0xF2, + 0xB2, 0xE1, 0x03, 0xF2, 0xF2, 0xB2, 0xE1, 0x04, 0xF2, 0xF2, 0xB2, 0xE1, + 0x05, 0xF2, 0xF2, 0xB2, 0xE1, 0x06, 0xF2, 0xF2, 0xB2, 0xE1, 0x07, 0xF2, + 0xF2, 0xB2, 0xE1, 0x08, 0xF2, 0xF2, 0xB2, 0xE1, 0x09, 0xF2, 0xF2, 0xB2, + 0xE1, 0x0A, 0xF2, 0xF2, 0xB2, 0xE1, 0x0B, 0xF2, 0xF2, 0xB2, 0xE1, 0x0C, + 0xF2, 0xF2, 0xB2, 0xE1, 0x0D, 0xF2, 0xF2, 0xB2, 0xFF, 0xD1, 0x03, 0xF3, + /* 84*/ 0xFF, + + /* 85*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x07, 0xFF, + /* 96*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x07, 0xFF, + /*109*/ 0xFF, + + /*110*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x27, 0xFF, + /*121*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x27, 0xFF, + /*134*/ 0xFF, + + /*135*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x47, 0xFF, + /*146*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x47, 0xFF, + /*159*/ 0xFF, + + /*160*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x57, 0xFF, + /*171*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x57, 0xFF, + /*184*/ 0xFF, + + /*185*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x77, 0xFF, + /*196*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x77, 0xFF, + /*209*/ 0xFF, + + /*210*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0x97, 0xFF, + /*221*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x97, 0xFF, + /*234*/ 0xFF, + + /*235*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD3, 0xB7, 0xFF, + /*246*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0xB7, 0xFF, + /*259*/ 0xFF, + + /*260*/ 0xE2, 0x0C, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD4, 0x07, 0xFF, + /*271*/ 0xE2, 0x06, 0xE1, 0x02, 0xE0, 0x0F, 0xE6, 0xED, 0xD5, 0xF0, 0x0C, 0x07, 0xFF, + /*284*/ 0xFF, + + /*285*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x14, 0x0E, 0xFF, + /*298*/ 0xE2, 0x0B, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x32, 0x1E, 0xFF, + /*311*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x50, 0x1E, 0xFF, + /*324*/ 0xE2, 0x0B, 0xE1, 0x0B, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0x0E, 0xFF, + /*335*/ 0xE2, 0x0B, 0xE1, 0x02, 0xE6, 0xED, 0xE0, 0x0A, 0xD0, 0xDB, 0x0A, 0x0E, 0xFF, + /*348*/ 0xFF, + + /*349*/ 0xE2, 0x03, 0xE1, 0x01, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x17, 0xFF, + /*360*/ 0xFF, + /*361*/ 0xFF, + /*362*/ 0xE2, 0x04, 0xE1, 0x04, 0xE6, 0xED, 0xE0, 0x06, 0xD5, 0xA7, 0xFF, + /*373*/ 0xE2, 0x03, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD6, 0x37, 0xFF, + /*384*/ 0xE2, 0x04, 0xE1, 0x06, 0xE6, 0xED, 0xE0, 0x06, 0xD3, 0x87, 0xFF, + + /*395*/ 0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x0B, 0xE8, 0x0B, 0xFF, + /*408*/ 0xE2, 0x0C, 0xE1, 0x03, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0xF0, 0x0C, 0x00, + 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, + 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, + 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, + 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xE8, 0x00, 0xE8, 0x10, 0xFF, + /*467*/ 0xE2, 0x0C, 0xE1, 0x00, 0xE0, 0x04, 0xE6, 0xED, 0xD4, 0x1B, 0xE8, 0x1B, 0xFF, + /*480*/ 0xFF, + + /*481*/ 0xFF +}; + + +/* + * PSG_HuC6280 + */ + +class PSG_HuC6280 { +private: + typedef struct { + uint16 frequency; + uint8 control; + uint8 balance; + uint8 waveform[32]; + uint8 index; + int16 dda; + uint32 counter; + } channel_t; + + double _clock; + double _rate; + uint8 _select; + uint8 _balance; + channel_t _channel[8]; + int16 _volumeTable[32]; + uint32 _noiseFreqTable[32]; + uint32 _waveFreqTable[4096]; + +public: + void init(); + void reset(); + void write(int offset, byte data); + void update(int16* samples, int sampleCnt); + + PSG_HuC6280(double clock, double samplerate); +}; + +PSG_HuC6280::PSG_HuC6280(double clock, double samplerate) { + _clock = clock; + _rate = samplerate; + + // Initialize PSG_HuC6280 emulator + init(); +} + +void PSG_HuC6280::init() { + int i; + double step; + + // Loudest volume level for table + double level = 65535.0 / 6.0 / 32.0; + + // Clear context + reset(); + + // Make waveform frequency table + for (i = 0; i < 4096; i++) { + step = ((_clock / _rate) * 4096) / (i+1); + _waveFreqTable[(1 + i) & 0xFFF] = (uint32)step; + } + + // Make noise frequency table + for (i = 0; i < 32; i++) { + step = ((_clock / _rate) * 32) / (i+1); + _noiseFreqTable[i] = (uint32)step; + } + + // Make volume table + // PSG_HuC6280 has 48dB volume range spread over 32 steps + step = 48.0 / 32.0; + for (i = 0; i < 31; i++) { + _volumeTable[i] = (uint16)level; + level /= pow(10.0, step / 20.0); + } + _volumeTable[31] = 0; +} + +void PSG_HuC6280::reset() { + _select = 0; + _balance = 0xFF; + memset(_channel, 0, sizeof(_channel)); + memset(_volumeTable, 0, sizeof(_volumeTable)); + memset(_noiseFreqTable, 0, sizeof(_noiseFreqTable)); + memset(_waveFreqTable, 0, sizeof(_waveFreqTable)); +} + +void PSG_HuC6280::write(int offset, byte data) { + channel_t *chan = &_channel[_select]; + + switch(offset & 0x0F) { + case 0x00: // Channel select + _select = data & 0x07; + break; + + case 0x01: // Global balance + _balance = data; + break; + + case 0x02: // Channel frequency (LSB) + chan->frequency = (chan->frequency & 0x0F00) | data; + chan->frequency &= 0x0FFF; + break; + + case 0x03: // Channel frequency (MSB) + chan->frequency = (chan->frequency & 0x00FF) | (data << 8); + chan->frequency &= 0x0FFF; + break; + + case 0x04: // Channel control (key-on, DDA mode, volume) + // 1-to-0 transition of DDA bit resets waveform index + if ((chan->control & 0x40) && ((data & 0x40) == 0)) { + chan->index = 0; + } + chan->control = data; + break; + + case 0x05: // Channel balance + chan->balance = data; + break; + + case 0x06: // Channel waveform data + switch(chan->control & 0xC0) { + case 0x00: + chan->waveform[chan->index & 0x1F] = data & 0x1F; + chan->index = (chan->index + 1) & 0x1F; + break; + + case 0x40: + break; + + case 0x80: + chan->waveform[chan->index & 0x1F] = data & 0x1F; + chan->index = (chan->index + 1) & 0x1F; + break; + + case 0xC0: + chan->dda = data & 0x1F; + break; + } + + break; + + case 0x07: // Noise control (enable, frequency) + case 0x08: // LFO frequency + case 0x09: // LFO control (enable, mode) + break; + + default: + break; + } +} + +void PSG_HuC6280::update(int16* samples, int sampleCnt) { + static const int scale_tab[] = { + 0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, + 0x10, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F + }; + int ch; + int i; + + int lmal = (_balance >> 4) & 0x0F; + int rmal = (_balance >> 0) & 0x0F; + int vll, vlr; + + lmal = scale_tab[lmal]; + rmal = scale_tab[rmal]; + + // Clear buffer + memset(samples, 0, 2 * sampleCnt * sizeof(int16)); + + for (ch = 0; ch < 6; ch++) { + // Only look at enabled channels + if (_channel[ch].control & 0x80) { + int lal = (_channel[ch].balance >> 4) & 0x0F; + int ral = (_channel[ch].balance >> 0) & 0x0F; + int al = _channel[ch].control & 0x1F; + + lal = scale_tab[lal]; + ral = scale_tab[ral]; + + // Calculate volume just as the patent says + vll = (0x1F - lal) + (0x1F - al) + (0x1F - lmal); + if (vll > 0x1F) vll = 0x1F; + + vlr = (0x1F - ral) + (0x1F - al) + (0x1F - rmal); + if (vlr > 0x1F) vlr = 0x1F; + + vll = _volumeTable[vll]; + vlr = _volumeTable[vlr]; + + // Check channel mode + if (_channel[ch].control & 0x40) { + /* DDA mode */ + for (i = 0; i < sampleCnt; i++) { + samples[2*i] += (int16)(vll * (_channel[ch].dda - 16)); + samples[2*i + 1] += (int16)(vlr * (_channel[ch].dda - 16)); + } + } else { + /* Waveform mode */ + uint32 step = _waveFreqTable[_channel[ch].frequency]; + for (i = 0; i < sampleCnt; i += 1) { + int offset; + int16 data; + offset = (_channel[ch].counter >> 12) & 0x1F; + _channel[ch].counter += step; + _channel[ch].counter &= 0x1FFFF; + data = _channel[ch].waveform[offset]; + samples[2*i] += (int16)(vll * (data - 16)); + samples[2*i + 1] += (int16)(vlr * (data - 16)); + } + } + } + } +} + + +/* + * Player_PCE + */ + +void Player_PCE::PSG_Write(int reg, byte data) { + _psg->write(reg, data); +} + +void Player_PCE::setupWaveform(byte bank) { + const byte *ptr = wave_table[bank]; + PSG_Write(4, 0x40); + PSG_Write(4, 0x00); + for (int i = 0; i < 32; ++i) { + PSG_Write(6, ptr[i]); + } +} + +// A541 +void Player_PCE::procA541(channel_t *channel) { + channel->soundDataPtr = NULL; + channel->controlVecShort10 = 0; + + channel->controlVecShort03 = 0; + channel->controlVecShort06 = 0; + channel->controlVec8 = 0; + channel->controlVec9 = 0; + channel->controlVec10 = 0; + channel->soundUpdateCounter = 0; + channel->controlVec18 = 0; + channel->controlVec19 = 0; + channel->controlVec23 = false; + channel->controlVec24 = false; + channel->controlVec21 = 0; + + channel->waveformCtrl = 0x80; +} + +// A592 +void Player_PCE::startSound(int sound) { + channel_t *channel; + const uint16 *ptr = sounds[sound_table[sound]]; + + for (int i = 0; i < 6; ++i) { + channel = &channels[i]; + procA541(channel); + + channel->controlVec24 = true; + channel->waveformCtrl = 0; + channel->controlVec0 = 0; + channel->controlVec19 = 0; + channel->controlVec18 = 0; + channel->soundDataPtr = &data_table[*ptr++]; + } +} + +// A64B +void Player_PCE::updateSound() { + for (int i = 0; i < 12; i++) { + channel_t *channel = &channels[i]; + bool cond = true; + if (i < 6) { + channel->controlVec21 ^= 0xFF; + if (!channel->controlVec21) + cond = false; + } + if (cond) { + processSoundData(channel); + procAB7F(channel); + procAC24(channel); + channel->controlVec11 = (channel->controlVecShort10 >> 11) | 0x80; + channel->balance = channel->balance2; + } + } + + for (int i = 0; i < 6; ++i) { + procA731(&channels[i]); + } +} + +int Player_PCE::readBuffer(int16 *buffer, const int numSamples) { + int sampleCopyCnt; + int samplesLeft = numSamples; + + Common::StackLock lock(_mutex); + + while (true) { + // copy samples to output buffer + sampleCopyCnt = (samplesLeft < _sampleBufferCnt) ? samplesLeft : _sampleBufferCnt; + if (sampleCopyCnt > 0) { + memcpy(buffer, _sampleBuffer, sampleCopyCnt * sizeof(int16)); + buffer += sampleCopyCnt; + samplesLeft -= sampleCopyCnt; + _sampleBufferCnt -= sampleCopyCnt; + } + + if (samplesLeft == 0) + break; + + // retrieve samples for one timer period + updateSound(); + _psg->update(_sampleBuffer, _samplesPerPeriod / 2); + _sampleBufferCnt = _samplesPerPeriod; + } + + // copy remaining samples to the front of the buffer + if (_sampleBufferCnt > 0) { + memmove(&_sampleBuffer[0], + &_sampleBuffer[_samplesPerPeriod - _sampleBufferCnt], + _sampleBufferCnt * sizeof(int16)); + } + + return numSamples; +} + +void Player_PCE::procA731(channel_t *channel) { + PSG_Write(0, channel->id); + PSG_Write(2, channel->freq & 0xFF); + PSG_Write(3, (channel->freq >> 8) & 0xFF); + + int tmp = channel->controlVec11; + if ((channel->controlVec11 & 0xC0) == 0x80) { + tmp = channel->controlVec11 & 0x1F; + if (tmp != 0) { + tmp -= channel->controlVec0; + if (tmp >= 0) { + tmp |= 0x80; + } else { + tmp = 0; + } + } + } + + PSG_Write(5, channel->balance); + if ((channel->waveformCtrl & 0x80) == 0) { + channel->waveformCtrl |= 0x80; + PSG_Write(0, channel->id); + setupWaveform(channel->waveformCtrl & 0x7F); + } + + PSG_Write(4, tmp); +} + +// A793 +void Player_PCE::processSoundData(channel_t *channel) { + channel->soundUpdateCounter--; + if (channel->soundUpdateCounter > 0) { + return; + } + + while (true) { + const byte *ptr = channel->soundDataPtr; + byte value = (ptr ? *ptr++ : 0xFF); + if (value < 0xD0) { + int mult = (value & 0x0F) + 1; + channel->soundUpdateCounter = mult * channel->controlVec1; + value >>= 4; + procAA62(channel, value); + channel->soundDataPtr = ptr; + return; + } + + // jump_table (A7F7) + switch (value - 0xD0) { + case 0: /*A85A*/ + case 1: /*A85D*/ + case 2: /*A861*/ + case 3: /*A865*/ + case 4: /*A869*/ + case 5: /*A86D*/ + case 6: /*A871*/ + channel->controlVec2 = (value - 0xD0) * 12; + break; + case 11: /*A8A8*/ + channel->controlVecShort06 = (int8)*ptr++; + break; + case 16: /*A8C2*/ + channel->controlVec1 = *ptr++; + break; + case 17: /*A8CA*/ + channel->waveformCtrl = *ptr++; + break; + case 18: /*A8D2*/ + channel->controlVec10 = *ptr++; + break; + case 22: /*A8F2*/ + value = *ptr; + channel->balance = value; + channel->balance2 = value; + ptr++; + break; + case 24: /*A905*/ + channel->controlVec23 = true; + break; + case 32: /*A921*/ + ptr++; + break; + case 47: + channel->controlVec24 = false; + channel->controlVec10 &= 0x7F; + channel->controlVecShort10 &= 0x00FF; + return; + default: + // unused -> ignore + break; + } + + channel->soundDataPtr = ptr; + } +} + +void Player_PCE::procAA62(channel_t *channel, int a) { + procACEA(channel, a); + if (channel->controlVec23) { + channel->controlVec23 = false; + return; + } + + channel->controlVec18 = 0; + + channel->controlVec10 |= 0x80; + int y = channel->controlVec10 & 0x7F; + channel->controlBufferPos = &control_data[control_offsets[y]]; + channel->controlVec5 = 0; +} + +void Player_PCE::procAB7F(channel_t *channel) { + uint16 freqValue = channel->controlVecShort02; + channel->controlVecShort02 += channel->controlVecShort03; + + int pos = freq_offset[channel->controlVec19] + channel->controlVec18; + freqValue += freq_table[pos]; + if (freq_table[pos + 1] != 0x0800) { + channel->controlVec18++; + } + freqValue += channel->controlVecShort06; + + channel->freq = freqValue; +} + +void Player_PCE::procAC24(channel_t *channel) { + if ((channel->controlVec10 & 0x80) == 0) + return; + + if (channel->controlVec5 == 0) { + const byte *ctrlPtr = channel->controlBufferPos; + byte value = *ctrlPtr++; + while (value >= 0xF0) { + if (value == 0xF0) { + channel->controlVecShort10 = READ_LE_UINT16(ctrlPtr); + ctrlPtr += 2; + } else if (value == 0xFF) { + channel->controlVec10 &= 0x7F; + return; + } else { + // unused + } + value = *ctrlPtr++; + } + channel->controlVec5 = value; + channel->controlVecShort09 = READ_LE_UINT16(ctrlPtr); + ctrlPtr += 2; + channel->controlBufferPos = ctrlPtr; + } + + channel->controlVecShort10 += channel->controlVecShort09; + channel->controlVec5--; +} + +void Player_PCE::procACEA(channel_t *channel, int a) { + int x = a + + channel->controlVec2 + + channel->controlVec8 + + channel->controlVec9; + channel->controlVecShort02 = lookup_table[x]; +} + +Player_PCE::Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer) { + for (int i = 0; i < 12; ++i) { + memset(&channels[i], 0, sizeof(channel_t)); + channels[i].id = i; + } + + _mixer = mixer; + _sampleRate = _mixer->getOutputRate(); + _vm = scumm; + + _samplesPerPeriod = 2 * (int)(_sampleRate / UPDATE_FREQ); + _sampleBuffer = new int16[_samplesPerPeriod]; + _sampleBufferCnt = 0; + + _psg = new PSG_HuC6280(PSG_CLOCK, _sampleRate); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_PCE::~Player_PCE() { + _mixer->stopHandle(_soundHandle); + delete[] _sampleBuffer; + delete _psg; +} + +void Player_PCE::stopSound(int nr) { + // TODO: implement +} + +void Player_PCE::stopAllSounds() { + // TODO: implement +} + +int Player_PCE::getSoundStatus(int nr) const { + // TODO: status for each sound + for (int i = 0; i < 6; ++i) { + if (channels[i].controlVec24) + return 1; + } + return 0; +} + +int Player_PCE::getMusicTimer() { + return 0; +} + +} // End of namespace Scumm + +#endif // USE_RGB_COLOR diff --git a/engines/scumm/players/player_pce.h b/engines/scumm/players/player_pce.h new file mode 100644 index 0000000000..ca2eddf58c --- /dev/null +++ b/engines/scumm/players/player_pce.h @@ -0,0 +1,133 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_PCE_H +#define SCUMM_PLAYERS_PLAYER_PCE_H + +#include "common/scummsys.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +// PCE sound engine is only used by Loom, which requires 16bit color support +#ifdef USE_RGB_COLOR + +namespace Scumm { + +class ScummEngine; +class PSG_HuC6280; + +class Player_PCE : public Audio::AudioStream, public MusicEngine { +private: + struct channel_t { + int id; + + byte controlVec0; + byte controlVec1; + byte controlVec2; + byte controlVec5; + byte balance; + byte balance2; + byte controlVec8; + byte controlVec9; + byte controlVec10; + byte controlVec11; + int16 soundUpdateCounter; + byte controlVec18; + byte controlVec19; + byte waveformCtrl; + byte controlVec21; + bool controlVec23; + bool controlVec24; + + uint16 controlVecShort02; + uint16 controlVecShort03; + int16 controlVecShort06; + uint16 freq; + uint16 controlVecShort09; + uint16 controlVecShort10; + + const byte* soundDataPtr; + const byte* controlBufferPos; + }; + +public: + Player_PCE(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_PCE(); + + virtual void setMusicVolume(int vol) { _maxvol = vol; } + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getSoundStatus(int sound) const; + virtual int getMusicTimer(); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return true; } + bool endOfData() const { return false; } + int getRate() const { return _sampleRate; } + +private: + ScummEngine *_vm; + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + int _sampleRate; + int _maxvol; + +private: + PSG_HuC6280 *_psg; + channel_t channels[12]; + Common::Mutex _mutex; + + // number of samples per timer period + int _samplesPerPeriod; + int16* _sampleBuffer; + int _sampleBufferCnt; + + void init(); + bool isPlaying(); + + void PSG_Write(int reg, byte data); + + void setupWaveform(byte bank); + void procA541(channel_t *channel); + void updateSound(); + void procA731(channel_t *channel); + void processSoundData(channel_t *channel); + void procA9F3(int x); + void procAA62(channel_t *channel, int a); + uint16 procAAF6(int x); + void procAB7F(channel_t *channel); + void procAC24(channel_t *channel); + void procACEA(channel_t *channel, int a); + void procAD21(int a, int x); + void procAD29(int value); + void procAD3D(int a, int x); +}; + +} // End of namespace Scumm + +#endif // USE_RGB_COLOR + +#endif diff --git a/engines/scumm/players/player_sid.cpp b/engines/scumm/players/player_sid.cpp new file mode 100644 index 0000000000..1b97ad16d4 --- /dev/null +++ b/engines/scumm/players/player_sid.cpp @@ -0,0 +1,1384 @@ +/* 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. + * + */ + +#ifndef DISABLE_SID + +#include "engines/engine.h" +#include "scumm/players/player_sid.h" +#include "scumm/scumm.h" +#include "audio/mixer.h" + +namespace Scumm { + +/* + * The player's update() routine is called once per (NTSC/PAL) frame as it is + * called by the VIC Rasterline interrupt handler which is in turn called + * approx. 50 (PAL) or 60 (NTSC) times per second. + * The SCUMM V0/V1 music playback routines or sound data have not been adjusted + * to PAL systems. As a consequence, music is played audibly (-16%) slower + * on PAL systems. + * In addition, the SID oscillator frequency depends on the video clock too. + * As SCUMM games use an NTSC frequency table for both NTSC and PAL versions + * all tone frequencies on PAL systems are slightly (-4%) lower than on NTSC ones. + * + * For more info on the SID chip see: + * - http://www.dopeconnection.net/C64_SID.htm (German) + * For more info on the VIC chip see: + * - http://www.htu.tugraz.at/~herwig/c64/man-vic.php (German) + * - http://www.c64-wiki.de/index.php/VIC (German) + */ + +struct TimingProps { + double clockFreq; + int cyclesPerFrame; +}; + +static const TimingProps timingProps[2] = { + { 17734472.0 / 18, 312 * 63 }, // PAL: 312*63 cycles/frame @ 985248 Hz (~50Hz) + { 14318180.0 / 14, 263 * 65 } // NTSC: 263*65 cycles/frame @ 1022727 Hz (~60Hz) +}; + +static const uint8 BITMASK[7] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40 +}; +static const uint8 BITMASK_INV[7] = { + 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF +}; + +static const int SID_REG_OFFSET[7] = { + 0, 7, 14, 21, 2, 9, 16 +}; + +// NTSC frequency table (also used for PAL versions). +// FREQ_TBL[i] = tone_freq[i] * 2^24 / clockFreq +static const uint16 FREQ_TBL[97] = { + 0x0000, 0x010C, 0x011C, 0x012D, 0x013E, 0x0151, 0x0166, 0x017B, + 0x0191, 0x01A9, 0x01C3, 0x01DD, 0x01FA, 0x0218, 0x0238, 0x025A, + 0x027D, 0x02A3, 0x02CC, 0x02F6, 0x0323, 0x0353, 0x0386, 0x03BB, + 0x03F4, 0x0430, 0x0470, 0x04B4, 0x04FB, 0x0547, 0x0598, 0x05ED, + 0x0647, 0x06A7, 0x070C, 0x0777, 0x07E9, 0x0861, 0x08E1, 0x0968, + 0x09F7, 0x0A8F, 0x0B30, 0x0BDA, 0x0C8F, 0x0D4E, 0x0E18, 0x0EEF, + 0x0FD2, 0x10C3, 0x11C3, 0x12D1, 0x13EF, 0x151F, 0x1660, 0x17B5, + 0x191E, 0x1A9C, 0x1C31, 0x1DDF, 0x1FA5, 0x2187, 0x2386, 0x25A2, + 0x27DF, 0x2A3E, 0x2CC1, 0x2F6B, 0x323C, 0x3539, 0x3863, 0x3BBE, + 0x3F4B, 0x430F, 0x470C, 0x4B45, 0x4FBF, 0x547D, 0x5983, 0x5ED6, + 0x6479, 0x6A73, 0x70C7, 0x777C, 0x7E97, 0x861E, 0x8E18, 0x968B, + 0x9F7E, 0xA8FA, 0xB306, 0xBDAC, 0xC8F3, 0xD4E6, 0xE18F, 0xEEF8, + 0xFD2E +}; + +static const int SONG_CHANNEL_OFFSET[3] = { 6, 8, 10 }; +static const int RES_ID_CHANNEL[3] = { 3, 4, 5 }; + +#define LOBYTE_(a) ((a) & 0xFF) +#define HIBYTE_(a) (((a) >> 8) & 0xFF) + +#define GETBIT(var, pos) ((var) & (1<<(pos))) + +void Player_SID::handleMusicBuffer() { // $33cd + int channel = 2; + while (channel >= 0) { + if ((statusBits1A & BITMASK[channel]) == 0 || + (busyChannelBits & BITMASK[channel]) != 0) { + --channel; + continue; + } + + if (setupSongFileData() == 1) + return; + + uint8* l_chanFileDataPtr = chanFileData[channel]; + + uint16 l_freq = 0; + bool l_keepFreq = false; + + int y = 0; + uint8 curByte = l_chanFileDataPtr[y++]; + + // freq or 0/0xFF + if (curByte == 0) { + func_3674(channel); + if (!isMusicPlaying) + return; + continue; + } else if (curByte == 0xFF) { + l_keepFreq = true; + } else { + l_freq = FREQ_TBL[curByte]; + } + + uint8 local1 = 0; + curByte = l_chanFileDataPtr[y++]; + bool isLastCmdByte = (curByte & 0x80) != 0; + uint16 curStepSum = stepTbl[curByte & 0x7f]; + + for (int i = 0; !isLastCmdByte && (i < 2); ++i) { + curByte = l_chanFileDataPtr[y++]; + isLastCmdByte = (curByte & 0x80) != 0; + if (curByte & 0x40) { + // note: bit used in zak theme (95) only (not used/handled in MM) + _music_timer = curByte & 0x3f; + } else { + local1 = curByte & 0x3f; + } + } + + chanFileData[channel] += y; + chanDataOffset[channel] += y; + + uint8 *l_chanBuf = getResource(RES_ID_CHANNEL[channel]); + + if (local1 != 0) { + // TODO: signed or unsigned? + uint16 offset = READ_LE_UINT16(&actSongFileData[local1*2 + 12]); + l_chanFileDataPtr = actSongFileData + offset; + + // next five bytes: freqDelta, attack, sustain and phase bit + for (int i = 0; i < 5; ++i) { + l_chanBuf[15 + i] = l_chanFileDataPtr[i]; + } + phaseBit[channel] = l_chanFileDataPtr[4]; + + for (int i = 0; i < 17; ++i) { + l_chanBuf[25 + i] = l_chanFileDataPtr[5 + i]; + } + } + + if (l_keepFreq) { + if (!releasePhase[channel]) { + l_chanBuf[10] &= 0xfe; // release phase + } + releasePhase[channel] = true; + } else { + if (releasePhase[channel]) { + l_chanBuf[19] = phaseBit[channel]; + l_chanBuf[10] |= 0x01; // attack phase + } + l_chanBuf[11] = LOBYTE_(l_freq); + l_chanBuf[12] = HIBYTE_(l_freq); + releasePhase[channel] = false; + } + + // set counter value for frequency update (freqDeltaCounter) + l_chanBuf[13] = LOBYTE_(curStepSum); + l_chanBuf[14] = HIBYTE_(curStepSum); + + _soundQueue[channel] = RES_ID_CHANNEL[channel]; + processSongData(channel); + _soundQueue[channel+4] = RES_ID_CHANNEL[channel]; + processSongData(channel+4); + --channel; + } +} + +int Player_SID::setupSongFileData() { // $36cb + // no song playing + // TODO: remove (never NULL) + if (_music == NULL) { + for (int i = 2; i >= 0; --i) { + if (songChannelBits & BITMASK[i]) { + func_3674(i); + } + } + return 1; + } + + // no new song + songFileOrChanBufData = _music; + if (_music == actSongFileData) { + return 0; + } + + // new song selected + actSongFileData = _music; + for (int i = 0; i < 3; ++i) { + chanFileData[i] = _music + chanDataOffset[i]; + } + + return -1; +} + +//x:0..2 +void Player_SID::func_3674(int channel) { // $3674 + statusBits1B &= BITMASK_INV[channel]; + if (statusBits1B == 0) { + isMusicPlaying = false; + unlockCodeLocation(); + safeUnlockResource(resID_song); + for (int i = 0; i < 3; ++i) { + safeUnlockResource(RES_ID_CHANNEL[i]); + } + } + + chanPrio[channel] = 2; + + statusBits1A &= BITMASK_INV[channel]; + phaseBit[channel] = 0; + + func_4F45(channel); +} + +void Player_SID::resetPlayerState() { // $48f7 + for (int i = 6; i >= 0; --i) + releaseChannel(i); + + isMusicPlaying = false; + unlockCodeLocation(); // does nothing + statusBits1B = 0; + statusBits1A = 0; + freeChannelCount = 3; + swapPrepared = false; + filterSwapped = false; + pulseWidthSwapped = false; + //var5163 = 0; +} + +void Player_SID::resetSID() { // $48D8 + SIDReg24 = 0x0f; + + SID_Write( 4, 0); + SID_Write(11, 0); + SID_Write(18, 0); + SID_Write(23, 0); + SID_Write(21, 0); + SID_Write(22, 0); + SID_Write(24, SIDReg24); + + resetPlayerState(); +} + +void Player_SID::update() { // $481B + if (initializing) + return; + + if (_soundInQueue) { + for (int i = 6; i >= 0; --i) { + if (_soundQueue[i] != -1) + processSongData(i); + } + _soundInQueue = false; + } + + // no sound + if (busyChannelBits == 0) + return; + + for (int i = 6; i >= 0; --i) { + if (busyChannelBits & BITMASK[i]) { + updateFreq(i); + } + } + + // seems to be used for background (prio=1?) sounds. + // If a bg sound cannot be played because all SID + // voices are used by higher priority sounds, the + // bg sound's state is updated here so it will be at + // the correct state when a voice is available again. + if (swapPrepared) { + swapVars(0, 0); + swapVarLoaded = true; + updateFreq(0); + swapVars(0, 0); + if (pulseWidthSwapped) { + swapVars(4, 1); + updateFreq(4); + swapVars(4, 1); + } + swapVarLoaded = false; + } + + for (int i = 6; i >= 0; --i) { + if (busyChannelBits & BITMASK[i]) + setSIDWaveCtrlReg(i); + }; + + if (isMusicPlaying) { + handleMusicBuffer(); + } + + return; +} + +// channel: 0..6 +void Player_SID::processSongData(int channel) { // $4939 + // always: _soundQueue[channel] != -1 + // -> channelMap[channel] != -1 + channelMap[channel] = _soundQueue[channel]; + _soundQueue[channel] = -1; + songPosUpdateCounter[channel] = 0; + + isVoiceChannel = (channel < 3); + + songFileOrChanBufOffset[channel] = vec6[channel]; + + setupSongPtr(channel); + + //vec5[channel] = songFileOrChanBufData; // not used + + if (songFileOrChanBufData == NULL) { // chanBuf (4C1C) + /* + // TODO: do we need this? + LOBYTE_(vec20[channel]) = 0; + LOBYTE_(songPosPtr[channel]) = LOBYTE_(songFileOrChanBufOffset[channel]); + */ + releaseResourceUnk(channel); + return; + } + + vec20[channel] = songFileOrChanBufData; // chanBuf (4C1C) + songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel]; // chanBuf (4C1C) + uint8* ptr1 = songPosPtr[channel]; + + int y = -1; + if (channel < 4) { + ++y; + if (channel == 3) { + readSetSIDFilterAndProps(&y, ptr1); + } else if (statusBits1A & BITMASK[channel]) { + ++y; + } else { // channel = 0/1/2 + waveCtrlReg[channel] = ptr1[y]; + + ++y; + if (ptr1[y] & 0x0f) { + // filter on for voice channel + SIDReg23 |= BITMASK[channel]; + } else { + // filter off for voice channel + SIDReg23 &= BITMASK_INV[channel]; + } + SID_Write(23, SIDReg23); + } + } + + saveSongPos(y, channel); + busyChannelBits |= BITMASK[channel]; + readSongChunk(channel); +} + +void Player_SID::readSetSIDFilterAndProps(int *offset, uint8* dataPtr) { // $49e7 + SIDReg23 |= dataPtr[*offset]; + SID_Write(23, SIDReg23); + ++*offset; + SIDReg24 = dataPtr[*offset]; + SID_Write(24, SIDReg24); +} + +void Player_SID::saveSongPos(int y, int channel) { + ++y; + songPosPtr[channel] += y; + songFileOrChanBufOffset[channel] += y; +} + +// channel: 0..6 +void Player_SID::updateFreq(int channel) { + isVoiceChannel = (channel < 3); + + --freqDeltaCounter[channel]; + if (freqDeltaCounter[channel] < 0) { + readSongChunk(channel); + } else { + freqReg[channel] += freqDelta[channel]; + } + setSIDFreqAS(channel); +} + +void Player_SID::resetFreqDelta(int channel) { + freqDeltaCounter[channel] = 0; + freqDelta[channel] = 0; +} + +void Player_SID::readSongChunk(int channel) { // $4a6b + while (true) { + if (setupSongPtr(channel) == 1) { + // do something with code resource + releaseResourceUnk(1); + return; + } + + uint8* ptr1 = songPosPtr[channel]; + + //curChannelActive = true; + + uint8 l_cmdByte = ptr1[0]; + if (l_cmdByte == 0) { + //curChannelActive = false; + songPosUpdateCounter[channel] = 0; + + var481A = -1; + releaseChannel(channel); + return; + } + + //vec19[channel] = l_cmdByte; + + // attack (1) / release (0) phase + if (isVoiceChannel) { + if (GETBIT(l_cmdByte, 0)) + waveCtrlReg[channel] |= 0x01; // start attack phase + else + waveCtrlReg[channel] &= 0xfe; // start release phase + } + + // channel finished bit + if (GETBIT(l_cmdByte, 1)) { + var481A = -1; + releaseChannel(channel); + return; + } + + int y = 0; + + // frequency + if (GETBIT(l_cmdByte, 2)) { + y += 2; + freqReg[channel] = READ_LE_UINT16(&ptr1[y-1]); + if (!GETBIT(l_cmdByte, 6)) { + y += 2; + freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]); + y += 2; + freqDelta[channel] = READ_LE_UINT16(&ptr1[y-1]); + } else { + resetFreqDelta(channel); + } + } else { + resetFreqDelta(channel); + } + + // attack / release + if (isVoiceChannel && GETBIT(l_cmdByte, 3)) { + // start release phase + waveCtrlReg[channel] &= 0xfe; + setSIDWaveCtrlReg(channel); + + ++y; + attackReg[channel] = ptr1[y]; + ++y; + sustainReg[channel] = ptr1[y]; + + // set attack (1) or release (0) phase + waveCtrlReg[channel] |= (l_cmdByte & 0x01); + } + + if (GETBIT(l_cmdByte, 4)) { + ++y; + uint8 curByte = ptr1[y]; + + // pulse width + if (isVoiceChannel && GETBIT(curByte, 0)) { + int reg = SID_REG_OFFSET[channel+4]; + + y += 2; + SID_Write(reg, ptr1[y-1]); + SID_Write(reg+1, ptr1[y]); + } + + if (GETBIT(curByte, 1)) { + ++y; + readSetSIDFilterAndProps(&y, ptr1); + + y += 2; + SID_Write(21, ptr1[y-1]); + SID_Write(22, ptr1[y]); + } + + if (GETBIT(curByte, 2)) { + resetFreqDelta(channel); + + y += 2; + freqDeltaCounter[channel] = READ_LE_UINT16(&ptr1[y-1]); + } + } + + // set waveform (?) + if (GETBIT(l_cmdByte, 5)) { + ++y; + waveCtrlReg[channel] = (waveCtrlReg[channel] & 0x0f) | ptr1[y]; + } + + // song position + if (GETBIT(l_cmdByte, 7)) { + if (songPosUpdateCounter[channel] == 1) { + y += 2; + --songPosUpdateCounter[channel]; + saveSongPos(y, channel); + } else { + // looping / skipping / ... + ++y; + songPosPtr[channel] -= ptr1[y]; + songFileOrChanBufOffset[channel] -= ptr1[y]; + + ++y; + if (songPosUpdateCounter[channel] == 0) { + songPosUpdateCounter[channel] = ptr1[y]; + } else { + --songPosUpdateCounter[channel]; + } + } + } else { + saveSongPos(y, channel); + return; + } + } +} + +/** + * Sets frequency, attack and sustain register + */ +void Player_SID::setSIDFreqAS(int channel) { // $4be6 + if (swapVarLoaded) + return; + int reg = SID_REG_OFFSET[channel]; + SID_Write(reg, LOBYTE_(freqReg[channel])); // freq/pulseWidth voice 1/2/3 + SID_Write(reg+1, HIBYTE_(freqReg[channel])); + if (channel < 3) { + SID_Write(reg+5, attackReg[channel]); // attack + SID_Write(reg+6, sustainReg[channel]); // sustain + } +} + +void Player_SID::setSIDWaveCtrlReg(int channel) { // $4C0D + if (channel < 3) { + int reg = SID_REG_OFFSET[channel]; + SID_Write(reg+4, waveCtrlReg[channel]); + } +} + +// channel: 0..6 +int Player_SID::setupSongPtr(int channel) { // $4C1C + //resID:5,4,3,songid + int resID = channelMap[channel]; + + // TODO: when does this happen, only if resID == 0? + if (getResource(resID) == NULL) { + releaseResourceUnk(resID); + if (resID == bgSoundResID) { + bgSoundResID = 0; + bgSoundActive = false; + swapPrepared = false; + pulseWidthSwapped = false; + } + return 1; + } + + songFileOrChanBufData = getResource(resID); // chanBuf (4C1C) + if (songFileOrChanBufData == vec20[channel]) { + return 0; + } else { + vec20[channel] = songFileOrChanBufData; + songPosPtr[channel] = songFileOrChanBufData + songFileOrChanBufOffset[channel]; + return -1; + } +} + +// ignore: no effect +// chanResIndex: 3,4,5 or 58 +void Player_SID::unlockResource(int chanResIndex) { // $4CDA + if ((resStatus[chanResIndex] & 0x7F) != 0) + --resStatus[chanResIndex]; +} + +void Player_SID::countFreeChannels() { // $4f26 + freeChannelCount = 0; + for (int i = 0; i < 3; ++i) { + if (GETBIT(usedChannelBits, i) == 0) + ++freeChannelCount; + } +} + +void Player_SID::func_4F45(int channel) { // $4F45 + if (swapVarLoaded) { + if (channel == 0) { + swapPrepared = false; + resetSwapVars(); + } + pulseWidthSwapped = false; + } else { + if (channel == 3) { + filterUsed = false; + } + + if (chanPrio[channel] == 1) { + if (var481A == 1) + prepareSwapVars(channel); + else if (channel < 3) + clearSIDWaveform(channel); + } else if (channel < 3 && bgSoundActive && swapPrepared && + !(filterSwapped && filterUsed)) + { + busyChannelBits |= BITMASK[channel]; + useSwapVars(channel); + waveCtrlReg[channel] |= 0x01; + setSIDWaveCtrlReg(channel); + + safeUnlockResource(channelMap[channel]); + return; + } + + chanPrio[channel] = 0; + usedChannelBits &= BITMASK_INV[channel]; + countFreeChannels(); + } + + int resIndex = channelMap[channel]; + channelMap[channel] = 0; + safeUnlockResource(resIndex); +} + +// chanResIndex: 3,4,5 or 58 +void Player_SID::safeUnlockResource(int resIndex) { // $4FEA + if (!isMusicPlaying) { + unlockResource(resIndex); + } +} + +void Player_SID::releaseResource(int resIndex) { // $5031 + releaseResChannels(resIndex); + if (resIndex == bgSoundResID && var481A == -1) { + safeUnlockResource(resIndex); + + bgSoundResID = 0; + bgSoundActive = false; + swapPrepared = false; + pulseWidthSwapped = false; + + resetSwapVars(); + } +} + +void Player_SID::releaseResChannels(int resIndex) { // $5070 + for (int i = 3; i >= 0; --i) { + if (resIndex == channelMap[i]) { + releaseChannel(i); + } + } +} + +void Player_SID::stopSound_intern(int soundResID) { // $5093 + for (int i = 0; i < 7; ++i) { + if (soundResID == _soundQueue[i]) { + _soundQueue[i] = -1; + } + } + var481A = -1; + releaseResource(soundResID); +} + +void Player_SID::stopMusic_intern() { // $4CAA + statusBits1B = 0; + isMusicPlaying = false; + + if (resID_song != 0) { + unlockResource(resID_song); + } + + chanPrio[0] = 2; + chanPrio[1] = 2; + chanPrio[2] = 2; + + statusBits1A = 0; + phaseBit[0] = 0; + phaseBit[1] = 0; + phaseBit[2] = 0; +} + +void Player_SID::releaseResourceUnk(int resIndex) { // $50A4 + var481A = -1; + releaseResource(resIndex); +} + +// a: 0..6 +void Player_SID::releaseChannel(int channel) { + stopChannel(channel); + if (channel >= 4) { + return; + } + if (channel < 3) { + SIDReg23Stuff = SIDReg23; + clearSIDWaveform(channel); + } + func_4F45(channel); + if (channel >= 3) { + return; + } + if ((SIDReg23 != SIDReg23Stuff) && + (SIDReg23 & 0x07) == 0) + { + if (filterUsed) { + func_4F45(3); + stopChannel(3); + } + } + + stopChannel(channel + 4); +} + +void Player_SID::clearSIDWaveform(int channel) { + if (!isMusicPlaying && var481A == -1) { + waveCtrlReg[channel] &= 0x0e; + setSIDWaveCtrlReg(channel); + } +} + +void Player_SID::stopChannel(int channel) { + songPosUpdateCounter[channel] = 0; + // clear "channel" bit + busyChannelBits &= BITMASK_INV[channel]; + if (channel >= 4) { + // pulsewidth = 0 + channelMap[channel] = 0; + } +} + +// channel: 0..6, swapIndex: 0..2 +void Player_SID::swapVars(int channel, int swapIndex) { // $51a5 + if (channel < 3) { + SWAP(attackReg[channel], swapAttack[swapIndex]); + SWAP(sustainReg[channel], swapSustain[swapIndex]); + } + //SWAP(vec5[channel], swapVec5[swapIndex]); // not used + //SWAP(vec19[channel], swapVec19[swapIndex]); // not used + + SWAP(chanPrio[channel], swapSongPrio[swapIndex]); + SWAP(channelMap[channel], swapVec479C[swapIndex]); + SWAP(songPosUpdateCounter[channel], swapSongPosUpdateCounter[swapIndex]); + SWAP(waveCtrlReg[channel], swapWaveCtrlReg[swapIndex]); + SWAP(songPosPtr[channel], swapSongPosPtr[swapIndex]); + SWAP(freqReg[channel], swapFreqReg[swapIndex]); + SWAP(freqDeltaCounter[channel], swapVec11[swapIndex]); + SWAP(freqDelta[channel], swapVec10[swapIndex]); + SWAP(vec20[channel], swapVec20[swapIndex]); + SWAP(songFileOrChanBufOffset[channel], swapVec8[swapIndex]); +} + +void Player_SID::resetSwapVars() { // $52d0 + for (int i = 0; i < 2; ++i) { + swapAttack[i] = 0; + swapSustain[i] = 0; + } + for (int i = 0; i < 3; ++i) { + swapVec5[i] = 0; + swapSongPrio[i] = 0; + swapVec479C[i] = 0; + swapVec19[i] = 0; + swapSongPosUpdateCounter[i] = 0; + swapWaveCtrlReg[i] = 0; + swapSongPosPtr[i] = 0; + swapFreqReg[i] = 0; + swapVec11[i] = 0; + swapVec10[i] = 0; + swapVec20[i] = 0; + swapVec8[i] = 0; + } +} + +void Player_SID::prepareSwapVars(int channel) { // $52E5 + if (channel >= 4) + return; + + if (channel < 3) { + if (!keepSwapVars) { + resetSwapVars(); + } + swapVars(channel, 0); + if (busyChannelBits & BITMASK[channel+4]) { + swapVars(channel+4, 1); + pulseWidthSwapped = true; + } + } else if (channel == 3) { + SIDReg24_HiNibble = SIDReg24 & 0x70; + resetSwapVars(); + keepSwapVars = true; + swapVars(3, 2); + filterSwapped = true; + } + swapPrepared = true; +} + +void Player_SID::useSwapVars(int channel) { // $5342 + if (channel >= 3) + return; + + swapVars(channel, 0); + setSIDFreqAS(channel); + if (pulseWidthSwapped) { + swapVars(channel+4, 1); + setSIDFreqAS(channel+4); + } + if (filterSwapped) { + swapVars(3, 2); + + // resonating filter freq. or voice-to-filter mapping? + SIDReg23 = (SIDReg23Stuff & 0xf0) | BITMASK[channel]; + SID_Write(23, SIDReg23); + + // filter props + SIDReg24 = (SIDReg24 & 0x0f) | SIDReg24_HiNibble; + SID_Write(24, SIDReg24); + + // filter freq. + SID_Write(21, LOBYTE_(freqReg[3])); + SID_Write(22, HIBYTE_(freqReg[3])); + } else { + SIDReg23 = SIDReg23Stuff & BITMASK_INV[channel]; + SID_Write(23, SIDReg23); + } + + swapPrepared = false; + pulseWidthSwapped = false; + keepSwapVars = false; + SIDReg24_HiNibble = 0; + filterSwapped = false; +} + +// ignore: no effect +// resIndex: 3,4,5 or 58 +void Player_SID::lockResource(int resIndex) { // $4ff4 + if (!isMusicPlaying) + ++resStatus[resIndex]; +} + +void Player_SID::reserveChannel(int channel, uint8 prioValue, int chanResIndex) { // $4ffe + if (channel == 3) { + filterUsed = true; + } else if (channel < 3) { + usedChannelBits |= BITMASK[channel]; + countFreeChannels(); + } + + chanPrio[channel] = prioValue; + lockResource(chanResIndex); +} + +// ignore: no effect +void Player_SID::unlockCodeLocation() { // $513e + resStatus[1] &= 0x80; + resStatus[2] &= 0x80; +} + +// ignore: no effect +void Player_SID::lockCodeLocation() { // $514f + resStatus[1] |= 0x01; + resStatus[2] |= 0x01; +} + +void Player_SID::initMusic(int songResIndex) { // $7de6 + unlockResource(resID_song); + + resID_song = songResIndex; + _music = getResource(resID_song); + if (_music == NULL) { + return; + } + + // song base address + uint8* songFileDataPtr = _music; + actSongFileData = _music; + + initializing = true; + _soundInQueue = false; + isMusicPlaying = false; + + unlockCodeLocation(); + resetPlayerState(); + + lockResource(resID_song); + buildStepTbl(songFileDataPtr[5]); + + // fetch sound + songChannelBits = songFileDataPtr[4]; + for (int i = 2; i >= 0; --i) { + if ((songChannelBits & BITMASK[i]) != 0) { + func_7eae(i, songFileDataPtr); + } + } + + isMusicPlaying = true; + lockCodeLocation(); + + SIDReg23 &= 0xf0; + SID_Write(23, SIDReg23); + + handleMusicBuffer(); + + initializing = false; + _soundInQueue = true; +} + +// params: +// channel: channel 0..2 +void Player_SID::func_7eae(int channel, uint8* songFileDataPtr) { + int pos = SONG_CHANNEL_OFFSET[channel]; + chanDataOffset[channel] = READ_LE_UINT16(&songFileDataPtr[pos]); + chanFileData[channel] = songFileDataPtr + chanDataOffset[channel]; + + //vec5[channel+4] = vec5[channel] = CHANNEL_BUFFER_ADDR[RES_ID_CHANNEL[channel]]; // not used + vec6[channel+4] = 0x0019; + vec6[channel] = 0x0008; + + func_819b(channel); + + waveCtrlReg[channel] = 0; +} + +void Player_SID::func_819b(int channel) { + reserveChannel(channel, 127, RES_ID_CHANNEL[channel]); + + statusBits1B |= BITMASK[channel]; + statusBits1A |= BITMASK[channel]; +} + +void Player_SID::buildStepTbl(int step) { // $82B4 + stepTbl[0] = 0; + stepTbl[1] = step - 2; + for (int i = 2; i < 33; ++i) { + stepTbl[i] = stepTbl[i-1] + step; + } +} + +int Player_SID::reserveSoundFilter(uint8 value, uint8 chanResIndex) { // $4ED0 + int channel = 3; + reserveChannel(channel, value, chanResIndex); + return channel; +} + +int Player_SID::reserveSoundVoice(uint8 value, uint8 chanResIndex) { // $4EB8 + for (int i = 2; i >= 0; --i) { + if ((usedChannelBits & BITMASK[i]) == 0) { + reserveChannel(i, value, chanResIndex); + return i; + } + } + return 0; +} + +void Player_SID::findLessPrioChannels(uint8 soundPrio) { // $4ED8 + minChanPrio = 127; + + chansWithLowerPrioCount = 0; + for (int i = 2; i >= 0; --i) { + if (usedChannelBits & BITMASK[i]) { + if (chanPrio[i] < soundPrio) + ++chansWithLowerPrioCount; + if (chanPrio[i] < minChanPrio) { + minChanPrio = chanPrio[i]; + minChanPrioIndex = i; + } + } + } + + if (chansWithLowerPrioCount == 0) + return; + + if (soundPrio >= chanPrio[3]) { + actFilterHasLowerPrio = true; + } else { + /* TODO: is this really a no-op? + if (minChanPrioIndex < chanPrio[3]) + minChanPrioIndex = minChanPrioIndex; + */ + + actFilterHasLowerPrio = false; + } +} + +void Player_SID::releaseResourceBySound(int resID) { // $5088 + var481A = 1; + releaseResource(resID); +} + +void Player_SID::readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID) { // $4E99 + //vec5[x] = songFilePtr; + vec6[x] = songFilePtr[*offset]; + *offset += 2; + _soundQueue[x] = chanResID; +} + +int Player_SID::initSound(int soundResID) { // $4D0A + initializing = true; + + if (isMusicPlaying && (statusBits1A & 0x07) == 0x07) { + initializing = false; + return -2; + } + + uint8 *songFilePtr = getResource(soundResID); + if (songFilePtr == NULL) { + initializing = false; + return 1; + } + + uint8 soundPrio = songFilePtr[4]; + // for (mostly but not always looped) background sounds + if (soundPrio == 1) { + bgSoundResID = soundResID; + bgSoundActive = true; + } + + uint8 requestedChannels = 0; + if ((songFilePtr[5] & 0x40) == 0) { + ++requestedChannels; + if (songFilePtr[5] & 0x02) + ++requestedChannels; + if (songFilePtr[5] & 0x08) + ++requestedChannels; + } + + bool filterNeeded = (songFilePtr[5] & 0x20) != 0; + bool filterBlocked = (filterUsed && filterNeeded); + if (filterBlocked || (freeChannelCount < requestedChannels)) { + findLessPrioChannels(soundPrio); + + if ((freeChannelCount + chansWithLowerPrioCount < requestedChannels) || + (filterBlocked && !actFilterHasLowerPrio)) { + initializing = false; + return -1; + } + + if (filterBlocked) { + if (soundPrio < chanPrio[3]) { + initializing = false; + return -1; + } + + uint8 l_resID = channelMap[3]; + releaseResourceBySound(l_resID); + } + + while ((freeChannelCount < requestedChannels) || (filterNeeded && filterUsed)) { + findLessPrioChannels(soundPrio); + if (minChanPrio >= soundPrio) { + initializing = false; + return -1; + } + + uint8 l_resID = channelMap[minChanPrioIndex]; + releaseResourceBySound(l_resID); + } + } + + int x; + uint8 soundByte5 = songFilePtr[5]; + if (soundByte5 & 0x40) + x = reserveSoundFilter(soundPrio, soundResID); + else + x = reserveSoundVoice(soundPrio, soundResID); + + uint8 var4CF3 = x; + int y = 6; + if (soundByte5 & 0x01) { + x += 4; + readVec6Data(x, &y, songFilePtr, soundResID); + } + if (soundByte5 & 0x02) { + x = reserveSoundVoice(soundPrio, soundResID); + readVec6Data(x, &y, songFilePtr, soundResID); + } + if (soundByte5 & 0x04) { + x += 4; + readVec6Data(x, &y, songFilePtr, soundResID); + } + if (soundByte5 & 0x08) { + x = reserveSoundVoice(soundPrio, soundResID); + readVec6Data(x, &y, songFilePtr, soundResID); + } + if (soundByte5 & 0x10) { + x += 4; + readVec6Data(x, &y, songFilePtr, soundResID); + } + if (soundByte5 & 0x20) { + x = reserveSoundFilter(soundPrio, soundResID); + readVec6Data(x, &y, songFilePtr, soundResID); + } + + //vec5[var4CF3] = songFilePtr; + vec6[var4CF3] = y; + _soundQueue[var4CF3] = soundResID; + + initializing = false; + _soundInQueue = true; + + return soundResID; +} + +void Player_SID::unused1() { // $50AF + var481A = -1; + if (bgSoundResID != 0) { + releaseResourceUnk(bgSoundResID); + } +} + +/////////////////////////// +/////////////////////////// + +#define ZEROMEM(a) memset(a, 0, sizeof(a)) + +Player_SID::Player_SID(ScummEngine *scumm, Audio::Mixer *mixer) { + /* + * clear memory + */ + + resID_song = 0; + statusBits1A = 0; + statusBits1B = 0; + busyChannelBits = 0; + SIDReg23 = 0; + SIDReg23Stuff = 0; + SIDReg24 = 0; + bgSoundResID = 0; + freeChannelCount = 0; + usedChannelBits = 0; + var481A = 0; + songChannelBits = 0; + //var5163 = 0; + SIDReg24_HiNibble = 0; + chansWithLowerPrioCount = 0; + minChanPrio = 0; + minChanPrioIndex = 0; + + _music = NULL; + songFileOrChanBufData = NULL; + actSongFileData = NULL; + + initializing = false; + _soundInQueue = false; + isVoiceChannel = false; + isMusicPlaying = false; + swapVarLoaded = false; + bgSoundActive = false; + filterUsed = false; + pulseWidthSwapped = false; + swapPrepared = false; + filterSwapped = false; + keepSwapVars = false; + actFilterHasLowerPrio = false; + + ZEROMEM(chanFileData); + ZEROMEM(chanDataOffset); + ZEROMEM(songPosPtr); + ZEROMEM(freqReg); + ZEROMEM(vec6); + ZEROMEM(songFileOrChanBufOffset); + ZEROMEM(freqDelta); + ZEROMEM(freqDeltaCounter); + ZEROMEM(swapSongPosPtr); + ZEROMEM(swapVec5); + ZEROMEM(swapVec8); + ZEROMEM(swapVec10); + ZEROMEM(swapFreqReg); + ZEROMEM(swapVec11); + ZEROMEM(vec20); + ZEROMEM(swapVec20); + ZEROMEM(resStatus); + ZEROMEM(attackReg); + ZEROMEM(sustainReg); + ZEROMEM(phaseBit); + ZEROMEM(releasePhase); + ZEROMEM(_soundQueue); + ZEROMEM(channelMap); + ZEROMEM(songPosUpdateCounter); + ZEROMEM(chanPrio); + ZEROMEM(waveCtrlReg); + ZEROMEM(swapAttack); + ZEROMEM(swapSustain); + ZEROMEM(swapSongPrio); + ZEROMEM(swapVec479C); + ZEROMEM(swapVec19); + ZEROMEM(swapSongPosUpdateCounter); + ZEROMEM(swapWaveCtrlReg); + ZEROMEM(stepTbl); + + /* + * initialize data + */ + + const uint8 chanBuffer_const[3][45] = { + { + 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00, + 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0x40,0x10,0x04,0x00,0x00, + 0x00,0x04,0x27,0x03,0xff,0xff,0x01,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00 + }, + { + 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00, + 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00, + 0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00 + }, + { + 0x00,0x00,0x00,0x00,0x7f,0x01,0x19,0x00, + 0x00,0x00,0x2d,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xf0,0x20,0x10,0x04,0x00,0x00, + 0x00,0x04,0x27,0x03,0xff,0xff,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00 + } + }; + memcpy(chanBuffer, chanBuffer_const, sizeof(chanBuffer_const)); + + for (int i = 0; i < 7; ++i) { + _soundQueue[i] = -1; + }; + + _music_timer = 0; + + _mixer = mixer; + _sampleRate = _mixer->getOutputRate(); + _vm = scumm; + + // sound speed is slightly different on NTSC and PAL machines + // as the SID clock depends on the frame rate. + // ScummVM does not distinguish between NTSC and PAL targets + // so we use the NTSC timing here as the music was composed for + // NTSC systems (music on PAL systems is slower). + _videoSystem = NTSC; + _cpuCyclesLeft = 0; + + initSID(); + resetSID(); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_SID::~Player_SID() { + _mixer->stopHandle(_soundHandle); + delete _sid; +} + +uint8 *Player_SID::getResource(int resID) { + switch (resID) { + case 0: + return NULL; + case 3: + case 4: + case 5: + return chanBuffer[resID-3]; + default: + return _vm->getResourceAddress(rtSound, resID); + } +} + +int Player_SID::readBuffer(int16 *buffer, const int numSamples) { + int samplesLeft = numSamples; + + Common::StackLock lock(_mutex); + + while (samplesLeft > 0) { + // update SID status after each frame + if (_cpuCyclesLeft <= 0) { + update(); + _cpuCyclesLeft = timingProps[_videoSystem].cyclesPerFrame; + } + // fetch samples + int sampleCount = _sid->updateClock(_cpuCyclesLeft, (short *)buffer, samplesLeft); + samplesLeft -= sampleCount; + buffer += sampleCount; + } + + return numSamples; +} + +void Player_SID::SID_Write(int reg, uint8 data) { + _sid->write(reg, data); +} + +void Player_SID::initSID() { + _sid = new Resid::SID(); + _sid->set_sampling_parameters( + timingProps[_videoSystem].clockFreq, + _sampleRate); + _sid->enable_filter(true); + + _sid->reset(); + // Synchronize the waveform generators (must occur after reset) + _sid->write( 4, 0x08); + _sid->write(11, 0x08); + _sid->write(18, 0x08); + _sid->write( 4, 0x00); + _sid->write(11, 0x00); + _sid->write(18, 0x00); +} + +void Player_SID::startSound(int nr) { + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + + // WORKAROUND: + // sound[4] contains either a song prio or a music channel usage byte. + // As music channel usage is always 0x07 for all music files and + // prio 7 is never used in any sound file use this byte for auto-detection. + bool isMusic = (data[4] == 0x07); + + Common::StackLock lock(_mutex); + + if (isMusic) { + initMusic(nr); + } else { + stopSound_intern(nr); + initSound(nr); + } +} + +void Player_SID::stopSound(int nr) { + if (nr == -1) + return; + + Common::StackLock lock(_mutex); + stopSound_intern(nr); +} + +void Player_SID::stopAllSounds() { + Common::StackLock lock(_mutex); + resetPlayerState(); +} + +int Player_SID::getSoundStatus(int nr) const { + int result = 0; + + //Common::StackLock lock(_mutex); + + if (resID_song == nr && isMusicPlaying) { + result = 1; + } + + for (int i = 0; (i < 4) && (result == 0); ++i) { + if (nr == _soundQueue[i] || nr == channelMap[i]) { + result = 1; + } + } + + return result; +} + +int Player_SID::getMusicTimer() { + int result = _music_timer; + _music_timer = 0; + return result; +} + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_sid.h b/engines/scumm/players/player_sid.h new file mode 100644 index 0000000000..a48ec793cd --- /dev/null +++ b/engines/scumm/players/player_sid.h @@ -0,0 +1,276 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_SID_H +#define SCUMM_PLAYERS_PLAYER_SID_H + +#include "common/mutex.h" +#include "common/scummsys.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/softsynth/sid.h" + +namespace Scumm { + +// the "channel" parameters seem to be in fact SID register +// offsets. Should be replaced. +enum sid_reg_t { + FREQ_VOICE1, + FREQ_VOICE2, + FREQ_VOICE3, + FREQ_FILTER, + PULSE_VOICE1, + PULSE_VOICE2, + PULSE_VOICE3 +}; + +enum VideoStandard { + PAL, + NTSC +}; + +class ScummEngine; + +class Player_SID : public Audio::AudioStream, public MusicEngine { +public: + Player_SID(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_SID(); + + virtual void setMusicVolume(int vol) { _maxvol = vol; } + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getSoundStatus(int sound) const; + virtual int getMusicTimer(); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return false; } + bool endOfData() const { return false; } + int getRate() const { return _sampleRate; } + +private: + Resid::SID *_sid; + void SID_Write(int reg, uint8 data); + void initSID(); + uint8 *getResource(int resID); + + // number of cpu cycles until next frame update + Resid::cycle_count _cpuCyclesLeft; + + ScummEngine *_vm; + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + int _sampleRate; + int _maxvol; + Common::Mutex _mutex; + + VideoStandard _videoSystem; + + int _music_timer; + uint8* _music; + +private: + void initMusic(int songResIndex); // $7de6 + int initSound(int soundResID); // $4D0A + void stopSound_intern(int soundResID); // $5093 + void stopMusic_intern(); // $4CAA + + void resetSID(); // $48D8 + void update(); // $481B + void handleMusicBuffer(); + int setupSongFileData(); // $36cb + void func_3674(int channel); // $3674 + void resetPlayerState(); // $48f7 + void processSongData(int channel); // $4939 + void readSetSIDFilterAndProps(int *offset, uint8* dataPtr); // $49e7 + void saveSongPos(int y, int channel); + void updateFreq(int channel); + void resetFreqDelta(int channel); + void readSongChunk(int channel); // $4a6b + void setSIDFreqAS(int channel); // $4be6 + void setSIDWaveCtrlReg(int channel); // $4C0D + int setupSongPtr(int channel); // $4C1C + void unlockResource(int chanResIndex); // $4CDA + void countFreeChannels(); // $4f26 + void func_4F45(int channel); // $4F45 + void safeUnlockResource(int resIndex); // $4FEA + void releaseResource(int resIndex); // $5031 + void releaseResChannels(int resIndex); // $5070 + void releaseResourceUnk(int resIndex); // $50A4 + void releaseChannel(int channel); + void clearSIDWaveform(int channel); + void stopChannel(int channel); + void swapVars(int channel, int swapIndex); // $51a5 + void resetSwapVars(); // $52d0 + void prepareSwapVars(int channel); // $52E5 + void useSwapVars(int channel); // $5342 + void lockResource(int resIndex); // $4ff4 + void reserveChannel(int channel, uint8 prioValue, int chanResIndex); // $4ffe + void unlockCodeLocation(); // $513e + void lockCodeLocation(); // $514f + void func_7eae(int channel, uint8* songFileDataPtr); // $7eae + void func_819b(int channel); // $819b + void buildStepTbl(int step); // $82B4 + int reserveSoundFilter(uint8 value, uint8 chanResIndex); // $4ED0 + int reserveSoundVoice(uint8 value, uint8 chanResIndex); // $4EB8 + void findLessPrioChannels(uint8 soundPrio); // $4ED8 + void releaseResourceBySound(int resID); // $5088 + void readVec6Data(int x, int *offset, uint8 *songFilePtr, int chanResID); // $4E99 + + void unused1(); // $50AF + + uint8 chanBuffer[3][45]; + + int resID_song; + + // statusBits1A/1B are always equal + uint8 statusBits1A; + uint8 statusBits1B; + + uint8 busyChannelBits; + + uint8 SIDReg23; + uint8 SIDReg23Stuff; + uint8 SIDReg24; + + uint8* chanFileData[3]; + uint16 chanDataOffset[3]; + uint8* songPosPtr[7]; + + // 0..2: freq value voice1/2/3 + // 3: filter freq + // 4..6: pulse width + uint16 freqReg[7]; + + // start offset[i] for songFileOrChanBufData to obtain songPosPtr[i] + // vec6[0..2] = 0x0008; + // vec6[4..6] = 0x0019; + uint16 vec6[7]; + + // current offset[i] for songFileOrChanBufData to obtain songPosPtr[i] (starts with vec6[i], increased later) + uint16 songFileOrChanBufOffset[7]; + + uint16 freqDelta[7]; + int freqDeltaCounter[7]; + uint8* swapSongPosPtr[3]; + uint8* swapVec5[3]; + uint16 swapVec8[3]; + uint16 swapVec10[3]; + uint16 swapFreqReg[3]; + int swapVec11[3]; + + // never read + //uint8* vec5[7]; + // never read + //uint8 vec19[7]; + // never read (needed by scumm engine?) + //bool curChannelActive; + + uint8* vec20[7]; + + uint8* swapVec20[3]; + + // resource status (never read) + // bit7: some flag + // bit6..0: counter (use-count?), maybe just bit0 as flag (used/unused?) + uint8 resStatus[70]; + + uint8* songFileOrChanBufData; + uint8* actSongFileData; + + uint16 stepTbl[33]; + + bool initializing; + bool _soundInQueue; + bool isVoiceChannel; + + bool isMusicPlaying; + bool swapVarLoaded; + bool bgSoundActive; + bool filterUsed; + + uint8 bgSoundResID; + uint8 freeChannelCount; + + // seems to be used for managing the three voices + // bit[0..2]: 0 -> unused, 1 -> already in use + uint8 usedChannelBits; + uint8 attackReg[3]; + uint8 sustainReg[3]; + + // -1/0/1 + int var481A; + + // bit-array: 00000cba + // a/b/c: channel1/2/3 + uint8 songChannelBits; + + bool pulseWidthSwapped; + bool swapPrepared; + + // never read + //uint8 var5163; + + bool filterSwapped; + uint8 SIDReg24_HiNibble; + bool keepSwapVars; + + uint8 phaseBit[3]; + bool releasePhase[3]; + + // values: a resID or -1 + // resIDs: 3, 4, 5 or song-number + int _soundQueue[7]; + + // values: a resID or 0 + // resIDs: 3, 4, 5 or song-number + int channelMap[7]; + + uint8 songPosUpdateCounter[7]; + + // priortity of channel contents + // MM: 1: lowest .. 120: highest (1,2,A,64,6E,73,78) + // Zak: -???: lowest .. 120: highest (5,32,64,65,66,6E,78, A5,A6,AF,D7) + uint8 chanPrio[7]; + + // only [0..2] used? + uint8 waveCtrlReg[7]; + + uint8 swapAttack[2]; + uint8 swapSustain[2]; + uint8 swapSongPrio[3]; + int swapVec479C[3]; + uint8 swapVec19[3]; + uint8 swapSongPosUpdateCounter[3]; + uint8 swapWaveCtrlReg[3]; + + bool actFilterHasLowerPrio; + uint8 chansWithLowerPrioCount; + uint8 minChanPrio; + uint8 minChanPrioIndex; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_towns.cpp b/engines/scumm/players/player_towns.cpp new file mode 100644 index 0000000000..acbdbc7fb6 --- /dev/null +++ b/engines/scumm/players/player_towns.cpp @@ -0,0 +1,753 @@ +/* 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/sound.h" +#include "scumm/players/player_towns.h" + +namespace Scumm { + +Player_Towns::Player_Towns(ScummEngine *vm, bool isVersion2) : _vm(vm), _v2(isVersion2), _intf(0), _numSoundMax(isVersion2 ? 256 : 200), _unkFlags(0x33) { + memset(_pcmCurrentSound, 0, sizeof(_pcmCurrentSound)); +} + +void Player_Towns::setSfxVolume(int vol) { + if (!_intf) + return; + _intf->setSoundEffectVolume(vol); +} + +int Player_Towns::getSoundStatus(int sound) const { + if (!_intf) + return 0; + for (int i = 1; i < 9; i++) { + if (_pcmCurrentSound[i].index == sound) + return _intf->callback(40, 0x3f + i) ? 1 : 0; + } + return 0; +} + +void Player_Towns::saveLoadWithSerializer(Serializer *ser) { + static const SaveLoadEntry pcmEntries[] = { + MKLINE(PcmCurrentSound, index, sleInt16, VER(81)), + MKLINE(PcmCurrentSound, chan, sleInt16, VER(81)), + MKLINE(PcmCurrentSound, note, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, velo, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, pan, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, paused, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, looping, sleUint8, VER(81)), + MKLINE(PcmCurrentSound, priority, sleUint32, VER(81)), + MKEND() + }; + + for (int i = 1; i < 9; i++) { + if (!_pcmCurrentSound[i].index) + continue; + + if (_intf->callback(40, i + 0x3f)) + continue; + + _intf->callback(39, i + 0x3f); + + _pcmCurrentSound[i].index = 0; + } + + ser->saveLoadArrayOf(_pcmCurrentSound, 9, sizeof(PcmCurrentSound), pcmEntries); +} + +void Player_Towns::restoreAfterLoad() { + Common::Array<uint16> restoredSounds; + + for (int i = 1; i < 9; i++) { + if (!_pcmCurrentSound[i].index || _pcmCurrentSound[i].index == 0xffff) + continue; + + // Don't restart multichannel sounds more than once + if (Common::find(restoredSounds.begin(), restoredSounds.end(), _pcmCurrentSound[i].index) != restoredSounds.end()) + continue; + + if (!_v2) + restoredSounds.push_back(_pcmCurrentSound[i].index); + + uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index); + if (!ptr) + continue; + + if (_vm->_game.version != 3) + ptr += 2; + + if (ptr[13]) + continue; + + playPcmTrack(_pcmCurrentSound[i].index, ptr + 6, _pcmCurrentSound[i].velo, _pcmCurrentSound[i].pan, _pcmCurrentSound[i].note, _pcmCurrentSound[i].priority); + } +} + +void Player_Towns::playPcmTrack(int sound, const uint8 *data, int velo, int pan, int note, int priority) { + if (!_intf) + return; + + const uint8 *sfxData = data + 16; + + int numChan = _v2 ? 1 : data[14]; + for (int i = 0; i < numChan; i++) { + int chan = allocatePcmChannel(sound, i, priority); + if (!chan) + return; + + _intf->callback(70, _unkFlags); + _intf->callback(3, chan + 0x3f, pan); + _intf->callback(37, chan + 0x3f, note, velo, sfxData); + + _pcmCurrentSound[chan].note = note; + _pcmCurrentSound[chan].velo = velo; + _pcmCurrentSound[chan].pan = pan; + _pcmCurrentSound[chan].paused = 0; + _pcmCurrentSound[chan].looping = READ_LE_UINT32(&sfxData[20]) ? 1 : 0; + + sfxData += (READ_LE_UINT32(&sfxData[12]) + 32); + } +} + +void Player_Towns::stopPcmTrack(int sound) { + if (!_intf) + return; + + for (int i = 1; i < 9; i++) { + if (sound == _pcmCurrentSound[i].index || !sound) { + _intf->callback(39, i + 0x3f); + _pcmCurrentSound[i].index = 0; + } + } +} + +int Player_Towns::allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority) { + if (!_intf) + return 0; + + int chan = 0; + + if (_v2 && priority > 255) { + chan = 8; + if (_intf->callback(40, 0x47)) + _intf->callback(39, 0x47); + } else { + for (int i = 8; i; i--) { + if (!_pcmCurrentSound[i].index) { + chan = i; + continue; + } + + if (_intf->callback(40, i + 0x3f)) + continue; + + chan = i; + if (_pcmCurrentSound[chan].index == 0xffff) + _intf->callback(39, chan + 0x3f); + else + _vm->_sound->stopSound(_pcmCurrentSound[chan].index); + } + + if (!chan) { + for (int i = 1; i < 9; i++) { + if (priority >= _pcmCurrentSound[i].priority) + chan = i; + } + if (_pcmCurrentSound[chan].index == 0xffff) + _intf->callback(39, chan + 0x3f); + else + _vm->_sound->stopSound(_pcmCurrentSound[chan].index); + } + } + + if (chan) { + _pcmCurrentSound[chan].index = sound; + _pcmCurrentSound[chan].chan = sfxChanRelIndex; + _pcmCurrentSound[chan].priority = priority; + } + + return chan; +} + +Player_Towns_v1::Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer) : Player_Towns(vm, false) { + _soundOverride = 0; + _cdaCurrentSound = _eupCurrentSound = _cdaNumLoops = 0; + _cdaForceRestart = 0; + _cdaVolLeft = _cdaVolRight = 0; + + _eupVolLeft = _eupVolRight = 0; + _eupLooping = false; + + if (_vm->_game.version == 3) { + _soundOverride = new SoundOvrParameters[_numSoundMax]; + memset(_soundOverride, 0, _numSoundMax * sizeof(SoundOvrParameters)); + } + + _driver = new TownsEuphonyDriver(mixer); +} + +Player_Towns_v1::~Player_Towns_v1() { + delete _driver; + delete[] _soundOverride; +} + +bool Player_Towns_v1::init() { + if (!_driver) + return false; + + if (!_driver->init()) + return false; + + _driver->reserveSoundEffectChannels(8); + _intf = _driver->intf(); + + // Treat all 6 fm channels and all 8 pcm channels as sound effect channels + // since music seems to exist as CD audio only in the games which use this + // MusicEngine implementation. + _intf->setSoundEffectChanMask(-1); + + setVolumeCD(255, 255); + + return true; +} + +void Player_Towns_v1::setMusicVolume(int vol) { + _driver->setMusicVolume(vol); +} + +void Player_Towns_v1::startSound(int sound) { + uint8 *ptr = _vm->getResourceAddress(rtSound, sound); + if (_vm->_game.version != 3) + ptr += 2; + + int type = ptr[13]; + + if (type == 0) { + uint8 velocity = 0; + uint8 note = 0; + + if (_vm->_game.version == 3) { + velocity = (_soundOverride[sound].vLeft + _soundOverride[sound].vRight); + note = _soundOverride[sound].note; + } + + velocity = velocity ? velocity >> 2 : ptr[14] >> 1; + uint16 len = READ_LE_UINT16(ptr) + 2; + playPcmTrack(sound, ptr + 6, velocity, 64, note ? note : (len > 50 ? ptr[50] : 60), READ_LE_UINT16(ptr + 10)); + + } else if (type == 1) { + playEuphonyTrack(sound, ptr + 6); + + } else if (type == 2) { + playCdaTrack(sound, ptr + 6); + } + + if (_vm->_game.version == 3) + _soundOverride[sound].vLeft = _soundOverride[sound].vRight = _soundOverride[sound].note = 0; +} + +void Player_Towns_v1::stopSound(int sound) { + if (sound == 0 || sound == _cdaCurrentSound) { + _cdaCurrentSound = 0; + _vm->_sound->stopCD(); + _vm->_sound->stopCDTimer(); + } + + if (sound != 0 && sound == _eupCurrentSound) { + _eupCurrentSound = 0; + _eupLooping = false; + _driver->stopParser(); + } + + stopPcmTrack(sound); +} + +void Player_Towns_v1::stopAllSounds() { + _cdaCurrentSound = 0; + _vm->_sound->stopCD(); + _vm->_sound->stopCDTimer(); + + _eupCurrentSound = 0; + _eupLooping = false; + _driver->stopParser(); + + stopPcmTrack(0); +} + +int Player_Towns_v1::getSoundStatus(int sound) const { + if (sound == _cdaCurrentSound) + return _vm->_sound->pollCD(); + if (sound == _eupCurrentSound) + return _driver->parserIsPlaying() ? 1 : 0; + return Player_Towns::getSoundStatus(sound); +} + +int32 Player_Towns_v1::doCommand(int numargs, int args[]) { + int32 res = 0; + + switch (args[0]) { + case 2: + _driver->intf()->callback(73, 0); + break; + + case 3: + restartLoopingSounds(); + break; + + case 8: + startSound(args[1]); + break; + + case 9: + _vm->_sound->stopSound(args[1]); + break; + + case 11: + stopPcmTrack(0); + break; + + case 14: + startSoundEx(args[1], args[2], args[3], args[4]); + break; + + case 15: + stopSoundSuspendLooping(args[1]); + break; + + default: + warning("Player_Towns_v1::doCommand: Unknown command %d", args[0]); + break; + } + + return res; +} + +void Player_Towns_v1::setVolumeCD(int left, int right) { + _cdaVolLeft = left & 0xff; + _cdaVolRight = right & 0xff; + _driver->setOutputVolume(1, left >> 1, right >> 1); +} + +void Player_Towns_v1::setSoundVolume(int sound, int left, int right) { + if (_soundOverride && sound > 0 && sound < _numSoundMax) { + _soundOverride[sound].vLeft = left; + _soundOverride[sound].vRight = right; + } +} + +void Player_Towns_v1::setSoundNote(int sound, int note) { + if (_soundOverride && sound > 0 && sound < _numSoundMax) + _soundOverride[sound].note = note; +} + +void Player_Towns_v1::saveLoadWithSerializer(Serializer *ser) { + _cdaCurrentSoundTemp = (_vm->_sound->pollCD() && _cdaNumLoops > 1) ? _cdaCurrentSound & 0xff : 0; + _cdaNumLoopsTemp = _cdaNumLoops & 0xff; + + static const SaveLoadEntry cdEntries[] = { + MKLINE(Player_Towns_v1, _cdaCurrentSoundTemp, sleUint8, VER(81)), + MKLINE(Player_Towns_v1, _cdaNumLoopsTemp, sleUint8, VER(81)), + MKLINE(Player_Towns_v1, _cdaVolLeft, sleUint8, VER(81)), + MKLINE(Player_Towns_v1, _cdaVolRight, sleUint8, VER(81)), + MKEND() + }; + + ser->saveLoadEntries(this, cdEntries); + + if (!_eupLooping && !_driver->parserIsPlaying()) + _eupCurrentSound = 0; + + static const SaveLoadEntry eupEntries[] = { + MKLINE(Player_Towns_v1, _eupCurrentSound, sleUint8, VER(81)), + MKLINE(Player_Towns_v1, _eupLooping, sleUint8, VER(81)), + MKLINE(Player_Towns_v1, _eupVolLeft, sleUint8, VER(81)), + MKLINE(Player_Towns_v1, _eupVolRight, sleUint8, VER(81)), + MKEND() + }; + + ser->saveLoadEntries(this, eupEntries); + + Player_Towns::saveLoadWithSerializer(ser); +} + +void Player_Towns_v1::restoreAfterLoad() { + setVolumeCD(_cdaVolLeft, _cdaVolRight); + + if (_cdaCurrentSoundTemp) { + uint8 *ptr = _vm->getResourceAddress(rtSound, _cdaCurrentSoundTemp) + 6; + if (_vm->_game.version != 3) + ptr += 2; + + if (ptr[7] == 2) { + playCdaTrack(_cdaCurrentSoundTemp, ptr, true); + _cdaCurrentSound = _cdaCurrentSoundTemp; + _cdaNumLoops = _cdaNumLoopsTemp; + } + } + + if (_eupCurrentSound) { + uint8 *ptr = _vm->getResourceAddress(rtSound, _eupCurrentSound) + 6; + if (_vm->_game.version != 3) + ptr += 2; + + if (ptr[7] == 1) { + setSoundVolume(_eupCurrentSound, _eupVolLeft, _eupVolRight); + playEuphonyTrack(_eupCurrentSound, ptr); + } + } + + Player_Towns::restoreAfterLoad(); +} + +void Player_Towns_v1::restartLoopingSounds() { + if (_cdaNumLoops && !_cdaForceRestart) + _cdaForceRestart = 1; + + for (int i = 1; i < 9; i++) { + if (!_pcmCurrentSound[i].paused) + continue; + + _pcmCurrentSound[i].paused = 0; + + uint8 *ptr = _vm->getResourceAddress(rtSound, _pcmCurrentSound[i].index); + if (!ptr) + continue; + ptr += 24; + + int c = 1; + while (_pcmCurrentSound[i].chan != c) { + ptr = ptr + READ_LE_UINT32(&ptr[12]) + 32; + c++; + } + + _driver->playSoundEffect(i + 0x3f, _pcmCurrentSound[i].note, _pcmCurrentSound[i].velo, ptr); + } + + _driver->intf()->callback(73, 1); +} + +void Player_Towns_v1::startSoundEx(int sound, int velo, int pan, int note) { + uint8 *ptr = _vm->getResourceAddress(rtSound, sound) + 2; + + if (pan > 99) + pan = 99; + + velo = velo ? (velo * ptr[14] + 50) / 100 : ptr[14]; + velo = CLIP(velo, 1, 255); + uint16 pri = READ_LE_UINT16(ptr + 10); + + if (ptr[13] == 0) { + velo >>= 1; + + if (!velo) + velo = 1; + + pan = pan ? (((pan << 7) - pan) + 50) / 100 : 64; + + playPcmTrack(sound, ptr + 6, velo ? velo : ptr[14] >> 1, pan, note ? note : ptr[50], pri); + + } else if (ptr[13] == 2) { + int volLeft = velo; + int volRight = velo; + + if (pan < 50) + volRight = ((pan * 2 + 1) * velo + 50) / 100; + else if (pan > 50) + volLeft = (((99 - pan) * 2 + 1) * velo + 50) / 100; + + setVolumeCD(volLeft, volRight); + + if (!_cdaForceRestart && sound == _cdaCurrentSound) + return; + + playCdaTrack(sound, ptr + 6, true); + } +} + +void Player_Towns_v1::stopSoundSuspendLooping(int sound) { + if (!sound) { + return; + } else if (sound == _cdaCurrentSound) { + if (_cdaNumLoops && _cdaForceRestart) + _cdaForceRestart = 1; + } else { + for (int i = 1; i < 9; i++) { + if (sound == _pcmCurrentSound[i].index) { + if (!_driver->soundEffectIsPlaying(i + 0x3f)) + continue; + _driver->stopSoundEffect(i + 0x3f); + if (_pcmCurrentSound[i].looping) + _pcmCurrentSound[i].paused = 1; + else + _pcmCurrentSound[i].index = 0; + } + } + } +} + +void Player_Towns_v1::playEuphonyTrack(int sound, const uint8 *data) { + const uint8 *pos = data + 16; + const uint8 *src = pos + data[14] * 48; + const uint8 *trackData = src + 150; + + for (int i = 0; i < 32; i++) + _driver->configChan_enable(i, *src++); + for (int i = 0; i < 32; i++) + _driver->configChan_setMode(i, 0xff); + for (int i = 0; i < 32; i++) + _driver->configChan_remap(i, *src++); + for (int i = 0; i < 32; i++) + _driver->configChan_adjustVolume(i, *src++); + for (int i = 0; i < 32; i++) + _driver->configChan_setTranspose(i, *src++); + + src += 8; + for (int i = 0; i < 6; i++) + _driver->assignChannel(i, *src++); + + for (int i = 0; i < data[14]; i++) { + _driver->loadInstrument(i, i, pos + i * 48); + _driver->intf()->callback(4, i, i); + } + + _eupVolLeft = _soundOverride[sound].vLeft; + _eupVolRight = _soundOverride[sound].vRight; + int lvl = _soundOverride[sound].vLeft + _soundOverride[sound].vRight; + if (!lvl) + lvl = data[8] + data[9]; + lvl >>= 2; + + for (int i = 0; i < 6; i++) + _driver->chanVolume(i, lvl); + + uint32 trackSize = READ_LE_UINT32(src); + src += 4; + uint8 startTick = *src++; + + _driver->setMusicTempo(*src++); + _driver->startMusicTrack(trackData, trackSize, startTick); + + _eupLooping = (*src != 1) ? 1 : 0; + _driver->setMusicLoop(_eupLooping != 0); + _driver->continueParsing(); + _eupCurrentSound = sound; +} + +void Player_Towns_v1::playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo) { + const uint8 *ptr = data; + + if (!sound) + return; + + if (!skipTrackVelo) { + if (_vm->_game.version == 3) { + if (_soundOverride[sound].vLeft + _soundOverride[sound].vRight) + setVolumeCD(_soundOverride[sound].vLeft, _soundOverride[sound].vRight); + else + setVolumeCD(ptr[8], ptr[9]); + } else { + setVolumeCD(ptr[8], ptr[9]); + } + } + + if (sound == _cdaCurrentSound && _vm->_sound->pollCD() == 1) + return; + + ptr += 16; + + int track = ptr[0]; + _cdaNumLoops = ptr[1]; + int start = (ptr[2] * 60 + ptr[3]) * 75 + ptr[4]; + int end = (ptr[5] * 60 + ptr[6]) * 75 + ptr[7]; + + _vm->_sound->playCDTrack(track, _cdaNumLoops == 0xff ? -1 : _cdaNumLoops, start, end <= start ? 0 : end - start); + _cdaForceRestart = 0; + _cdaCurrentSound = sound; +} + +Player_Towns_v2::Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse) : Player_Towns(vm, true), _imuse(imuse), _imuseDispose(disposeIMuse), _sblData(0) { + _soundOverride = new SoundOvrParameters[_numSoundMax]; + memset(_soundOverride, 0, _numSoundMax * sizeof(SoundOvrParameters)); + _intf = new TownsAudioInterface(mixer, 0); +} + +Player_Towns_v2::~Player_Towns_v2() { + delete _intf; + _intf = 0; + + if (_imuseDispose) + delete _imuse; + + delete[] _sblData; + delete[] _soundOverride; +} + +bool Player_Towns_v2::init() { + if (!_intf) + return false; + + if (!_intf->init()) + return false; + + _intf->callback(33, 8); + _intf->setSoundEffectChanMask(~0x3f); + + return true; +} + +void Player_Towns_v2::setMusicVolume(int vol) { + _imuse->setMusicVolume(vol); +} + +int Player_Towns_v2::getSoundStatus(int sound) const { + if (_soundOverride[sound].type == 7) + return Player_Towns::getSoundStatus(sound); + return _imuse->getSoundStatus(sound); +} + +void Player_Towns_v2::startSound(int sound) { + uint8 *ptr = _vm->getResourceAddress(rtSound, sound); + + if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) { + _soundOverride[sound].type = 7; + uint8 velo = _soundOverride[sound].velo ? _soundOverride[sound].velo - 1: (ptr[10] + ptr[11] + 1) >> 1; + uint8 pan = _soundOverride[sound].pan ? _soundOverride[sound].pan - 1 : 64; + uint8 pri = ptr[9]; + _soundOverride[sound].velo = _soundOverride[sound].pan = 0; + playPcmTrack(sound, ptr + 8, velo, pan, ptr[52], pri); + + } else if (READ_BE_UINT32(ptr) == MKTAG('S','B','L',' ')) { + _soundOverride[sound].type = 5; + playVocTrack(ptr + 27); + + } else { + _soundOverride[sound].type = 3; + _imuse->startSound(sound); + } +} + +void Player_Towns_v2::stopSound(int sound) { + if (_soundOverride[sound].type == 7) { + stopPcmTrack(sound); + } else { + _imuse->stopSound(sound); + } +} + +void Player_Towns_v2::stopAllSounds() { + stopPcmTrack(0); + _imuse->stopAllSounds(); +} + +int32 Player_Towns_v2::doCommand(int numargs, int args[]) { + int32 res = -1; + uint8 *ptr = 0; + + switch (args[0]) { + case 8: + startSound(args[1]); + res = 0; + break; + + case 9: + case 15: + stopSound(args[1]); + res = 0; + break; + + case 11: + stopPcmTrack(0); + break; + + case 13: + res = getSoundStatus(args[1]); + break; + + case 258: + if (_soundOverride[args[1]].type == 0) { + ptr = _vm->getResourceAddress(rtSound, args[1]); + if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) + _soundOverride[args[1]].type = 7; + } + if (_soundOverride[args[1]].type == 7) { + _soundOverride[args[1]].velo = args[2] + 1; + res = 0; + } + break; + + case 259: + if (_soundOverride[args[1]].type == 0) { + ptr = _vm->getResourceAddress(rtSound, args[1]); + if (READ_BE_UINT32(ptr) == MKTAG('T','O','W','S')) + _soundOverride[args[1]].type = 7; + } + if (_soundOverride[args[1]].type == 7) { + _soundOverride[args[1]].pan = 64 - CLIP<int>(args[2], -63, 63); + res = 0; + } + break; + + default: + break; + } + + if (res == -1) + return _imuse->doCommand(numargs, args); + + return res; +} + +void Player_Towns_v2::saveLoadWithSerializer(Serializer *ser) { + if (ser->getVersion() >= 83) + Player_Towns::saveLoadWithSerializer(ser); +} + +void Player_Towns_v2::playVocTrack(const uint8 *data) { + static const uint8 header[] = { + 0x54, 0x61, 0x6C, 0x6B, 0x69, 0x65, 0x20, 0x20, + 0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x36, 0x04, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00 + }; + + uint32 len = (READ_LE_UINT32(data) >> 8) - 2; + + int chan = allocatePcmChannel(0xffff, 0, 0x1000); + if (!chan) + return; + + delete[] _sblData; + _sblData = new uint8[len + 32]; + + memcpy(_sblData, header, 32); + WRITE_LE_UINT32(_sblData + 12, len); + + const uint8 *src = data + 6; + uint8 *dst = _sblData + 32; + for (uint32 i = 0; i < len; i++) + *dst++ = *src & 0x80 ? (*src++ & 0x7f) : -*src++; + + _intf->callback(37, 0x3f + chan, 60, 127, _sblData); + _pcmCurrentSound[chan].paused = 0; +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_towns.h b/engines/scumm/players/player_towns.h new file mode 100644 index 0000000000..2369b7da5f --- /dev/null +++ b/engines/scumm/players/player_towns.h @@ -0,0 +1,180 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_TOWNS_H +#define SCUMM_PLAYERS_PLAYER_TOWNS_H + +#include "scumm/scumm.h" +#include "scumm/imuse/imuse.h" +#include "audio/softsynth/fmtowns_pc98/towns_euphony.h" +#include "audio/softsynth/fmtowns_pc98/towns_midi.h" + +namespace Scumm { + +class Player_Towns : public MusicEngine { +public: + Player_Towns(ScummEngine *vm, bool isVersion2); + virtual ~Player_Towns() {} + + virtual bool init() = 0; + + void setSfxVolume(int vol); + + int getSoundStatus(int sound) const; + + virtual int32 doCommand(int numargs, int args[]) = 0; + + virtual void saveLoadWithSerializer(Serializer *ser); + virtual void restoreAfterLoad(); + + // version 1 specific + virtual int getCurrentCdaSound() { return 0; } + virtual int getCurrentCdaVolume() { return 0; } + virtual void setVolumeCD(int left, int right) {} + virtual void setSoundVolume(int sound, int left, int right) {} + virtual void setSoundNote(int sound, int note) {} + +protected: + void playPcmTrack(int sound, const uint8 *data, int velo = 0, int pan = 64, int note = 0, int priority = 0); + void stopPcmTrack(int sound); + + int allocatePcmChannel(int sound, int sfxChanRelIndex, uint32 priority); + + struct PcmCurrentSound { + uint16 index; + uint16 chan; + uint8 note; + uint8 velo; + uint8 pan; + uint8 paused; + uint8 looping; + uint32 priority; + } _pcmCurrentSound[9]; + + uint8 _unkFlags; + + TownsAudioInterface *_intf; + ScummEngine *_vm; + + const int _numSoundMax; + const bool _v2; +}; + +class Player_Towns_v1 : public Player_Towns { +public: + Player_Towns_v1(ScummEngine *vm, Audio::Mixer *mixer); + ~Player_Towns_v1(); + + bool init(); + + void setMusicVolume(int vol); + void startSound(int sound); + void stopSound(int sound); + void stopAllSounds(); + + int getSoundStatus(int sound) const; + int getCurrentCdaSound() { return _cdaCurrentSound; } + int getCurrentCdaVolume() { return (_cdaVolLeft + _cdaVolRight + 1) >> 1; } + + int32 doCommand(int numargs, int args[]); + + void setVolumeCD(int left, int right); + void setSoundVolume(int sound, int left, int right); + void setSoundNote(int sound, int note); + + void saveLoadWithSerializer(Serializer *ser); + void restoreAfterLoad(); + + TownsEuphonyDriver *driver() { return _driver; } + +private: + void restartLoopingSounds(); + void startSoundEx(int sound, int velo, int pan, int note); + void stopSoundSuspendLooping(int sound); + + void playEuphonyTrack(int sound, const uint8 *data); + void playCdaTrack(int sound, const uint8 *data, bool skipTrackVelo = false); + + struct SoundOvrParameters { + uint8 vLeft; + uint8 vRight; + uint8 note; + }; + + SoundOvrParameters *_soundOverride; + + uint8 _cdaVolLeft; + uint8 _cdaVolRight; + + uint8 _eupCurrentSound; + uint8 _eupLooping; + uint8 _eupVolLeft; + uint8 _eupVolRight; + + uint8 _cdaCurrentSound; + uint8 _cdaNumLoops; + uint8 _cdaForceRestart; + + uint8 _cdaCurrentSoundTemp; + uint8 _cdaNumLoopsTemp; + + TownsEuphonyDriver *_driver; +}; + +class Player_Towns_v2 : public Player_Towns { +public: + Player_Towns_v2(ScummEngine *vm, Audio::Mixer *mixer, IMuse *imuse, bool disposeIMuse); + ~Player_Towns_v2(); + + bool init(); + + void setMusicVolume(int vol); + + int getSoundStatus(int sound) const; + void startSound(int sound); + void stopSound(int sound); + void stopAllSounds(); + + int32 doCommand(int numargs, int args[]); + + void saveLoadWithSerializer(Serializer *ser); + +private: + void playVocTrack(const uint8 *data); + + struct SoundOvrParameters { + uint8 velo; + uint8 pan; + uint8 type; + }; + + SoundOvrParameters *_soundOverride; + + uint8 *_sblData; + + IMuse *_imuse; + const bool _imuseDispose; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v1.cpp b/engines/scumm/players/player_v1.cpp new file mode 100644 index 0000000000..0fa1ee9361 --- /dev/null +++ b/engines/scumm/players/player_v1.cpp @@ -0,0 +1,607 @@ +/* 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 "engines/engine.h" +#include "scumm/players/player_v1.h" +#include "scumm/scumm.h" + +namespace Scumm { + +#define FB_WNOISE 0x12000 /* feedback for white noise */ +#define FB_PNOISE 0x08000 /* feedback for periodic noise */ + +#define TIMER_BASE_FREQ 1193000 +#define FIXP_SHIFT 16 + +Player_V1::Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr) + : Player_V2(scumm, mixer, pcjr) { + // Initialize channel code + for (int i = 0; i < 4; ++i) + clear_channel(i); + + _mplex_step = (_sampleRate << FIXP_SHIFT) / 1193000; + _next_chunk = _repeat_chunk = 0; + _forced_level = 0; + _random_lsr = 0; +} + +Player_V1::~Player_V1() { +} + +void Player_V1::chainSound(int nr, byte *data) { + uint i; + for (i = 0; i < 4; ++i) + clear_channel(i); + + _current_nr = nr; + _current_data = data; + _repeat_chunk = _next_chunk = data + (_pcjr ? 2 : 4); + + debug(4, "Chaining new sound %d", nr); + if (_pcjr) + parsePCjrChunk(); + else + parseSpeakerChunk(); +} + +void Player_V1::startSound(int nr) { + Common::StackLock lock(_mutex); + + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + + int offset = _pcjr ? READ_LE_UINT16(data+4) : 6; + int cprio = _current_data ? *(_current_data) & 0x7f : 0; + int prio = *(data + offset) & 0x7f; + int restartable = *(data + offset) & 0x80; + + debug(4, "startSound %d: prio %d%s, cprio %d", + nr, prio, restartable ? " restartable" : "", cprio); + + if (!_current_nr || cprio <= prio) { + if (_current_data && (*(_current_data) & 0x80)) { + _next_nr = _current_nr; + _next_data = _current_data; + } + + chainSound(nr, data + offset); + } +} + +void Player_V1::stopAllSounds() { + Common::StackLock lock(_mutex); + + for (int i = 0; i < 4; i++) + clear_channel(i); + _repeat_chunk = _next_chunk = 0; + _next_nr = _current_nr = 0; + _next_data = _current_data = 0; +} + +void Player_V1::stopSound(int nr) { + Common::StackLock lock(_mutex); + + if (_next_nr == nr) { + _next_nr = 0; + _next_data = 0; + } + if (_current_nr == nr) { + for (int i = 0; i < 4; i++) { + clear_channel(i); + } + _repeat_chunk = _next_chunk = 0; + _current_nr = 0; + _current_data = 0; + chainNextSound(); + } +} + +void Player_V1::clear_channel(int i) { + _channels[i].freq = 0; + _channels[i].volume = 15; +} + +int Player_V1::getMusicTimer() { + /* Do V1 games have a music timer? */ + return 0; +} + +void Player_V1::parseSpeakerChunk() { + set_mplex(3000); + _forced_level = 0; + + parse_again: + _chunk_type = READ_LE_UINT16(_next_chunk); + debug(6, "parseSpeakerChunk: sound %d, offset %lx, chunk %x", + _current_nr, (long)(_next_chunk - _current_data), _chunk_type); + + _next_chunk += 2; + switch (_chunk_type) { + case 0xffff: + _current_nr = 0; + _current_data = 0; + _channels[0].freq = 0; + _next_chunk = 0; + chainNextSound(); + break; + case 0xfffe: + _repeat_chunk = _next_chunk; + goto parse_again; + + case 0xfffd: + _next_chunk = _repeat_chunk; + goto parse_again; + + case 0xfffc: + /* handle reset. We don't need this do we? */ + goto parse_again; + + case 0: + _time_left = 1; + set_mplex(READ_LE_UINT16(_next_chunk)); + _next_chunk += 2; + break; + case 1: + set_mplex(READ_LE_UINT16(_next_chunk)); + _start = READ_LE_UINT16(_next_chunk + 2); + _end = READ_LE_UINT16(_next_chunk + 4); + _delta = (int16) READ_LE_UINT16(_next_chunk + 6); + _repeat_ctr = READ_LE_UINT16(_next_chunk + 8); + _channels[0].freq = _start; + _next_chunk += 10; + debug(6, "chunk 1: mplex %d, freq %d -> %d step %d x %d", + _mplex, _start, _end, _delta, _repeat_ctr); + break; + case 2: + _start = READ_LE_UINT16(_next_chunk); + _end = READ_LE_UINT16(_next_chunk + 2); + _delta = (int16) READ_LE_UINT16(_next_chunk + 4); + _channels[0].freq = 0; + _next_chunk += 6; + _forced_level = -1; + debug(6, "chunk 2: %d -> %d step %d", + _start, _end, _delta); + break; + case 3: + _start = READ_LE_UINT16(_next_chunk); + _end = READ_LE_UINT16(_next_chunk + 2); + _delta = (int16) READ_LE_UINT16(_next_chunk + 4); + _channels[0].freq = 0; + _next_chunk += 6; + _forced_level = -1; + debug(6, "chunk 3: %d -> %d step %d", + _start, _end, _delta); + break; + } +} + +void Player_V1::nextSpeakerCmd() { + uint16 lsr; + switch (_chunk_type) { + case 0: + if (--_time_left) + return; + _time_left = READ_LE_UINT16(_next_chunk); + _next_chunk += 2; + if (_time_left == 0xfffb) { + /* handle 0xfffb?? */ + _time_left = READ_LE_UINT16(_next_chunk); + _next_chunk += 2; + } + debug(7, "nextSpeakerCmd: chunk %d, offset %4lx: notelen %d", + _chunk_type, (long)(_next_chunk - 2 - _current_data), _time_left); + if (_time_left == 0) { + parseSpeakerChunk(); + } else { + _channels[0].freq = READ_LE_UINT16(_next_chunk); + _next_chunk += 2; + debug(7, "freq_current: %d", _channels[0].freq); + } + break; + + case 1: + _channels[0].freq = (_channels[0].freq + _delta) & 0xffff; + if (_channels[0].freq == _end) { + if (!--_repeat_ctr) { + parseSpeakerChunk(); + return; + } + _channels[0].freq = _start; + } + break; + + case 2: + _start = (_start + _delta) & 0xffff; + if (_start == _end) { + parseSpeakerChunk(); + return; + } + set_mplex(_start); + _forced_level = -_forced_level; + break; + case 3: + _start = (_start + _delta) & 0xffff; + if (_start == _end) { + parseSpeakerChunk(); + return; + } + lsr = _random_lsr + 0x9248; + lsr = (lsr >> 3) | (lsr << 13); + _random_lsr = lsr; + set_mplex((_start & lsr) | 0x180); + _forced_level = -_forced_level; + break; + } +} + +void Player_V1::parsePCjrChunk() { + uint tmp; + uint i; + + set_mplex(3000); + _forced_level = 0; + +parse_again: + + _chunk_type = READ_LE_UINT16(_next_chunk); + debug(6, "parsePCjrChunk: sound %d, offset %4lx, chunk %x", + _current_nr, (long)(_next_chunk - _current_data), _chunk_type); + + _next_chunk += 2; + switch (_chunk_type) { + case 0xffff: + for (i = 0; i < 4; ++i) + clear_channel(i); + _current_nr = 0; + _current_data = 0; + _repeat_chunk = _next_chunk = 0; + chainNextSound(); + break; + + case 0xfffe: + _repeat_chunk = _next_chunk; + goto parse_again; + + case 0xfffd: + _next_chunk = _repeat_chunk; + goto parse_again; + + case 0xfffc: + /* handle reset. We don't need this do we? */ + goto parse_again; + + case 0: + set_mplex(READ_LE_UINT16(_next_chunk)); + _next_chunk += 2; + for (i = 0; i < 4; i++) { + tmp = READ_LE_UINT16(_next_chunk); + _next_chunk += 2; + if (tmp == 0xffff) { + _channels[i].cmd_ptr = 0; + continue; + } + _channels[i].attack = READ_LE_UINT16(_current_data + tmp); + _channels[i].decay = READ_LE_UINT16(_current_data + tmp + 2); + _channels[i].level = READ_LE_UINT16(_current_data + tmp + 4); + _channels[i].sustain_1 = READ_LE_UINT16(_current_data + tmp + 6); + _channels[i].sustain_2 = READ_LE_UINT16(_current_data + tmp + 8); + _channels[i].notelen = 1; + _channels[i].volume = 15; + _channels[i].cmd_ptr = _current_data + tmp + 10; + } + break; + + case 1: + set_mplex(READ_LE_UINT16(_next_chunk)); + tmp = READ_LE_UINT16(_next_chunk + 2); + _channels[0].cmd_ptr = tmp != 0xffff ? _current_data + tmp : NULL; + tmp = READ_LE_UINT16(_next_chunk + 4); + _start = READ_LE_UINT16(_next_chunk + 6); + _delta = (int16) READ_LE_UINT16(_next_chunk + 8); + _time_left = READ_LE_UINT16(_next_chunk + 10); + _next_chunk += 12; + if (tmp >= 0xe0) { + _channels[3].freq = tmp & 0xf; + _value_ptr = &_channels[3].volume; + } else { + assert(!(tmp & 0x10)); + tmp = (tmp & 0x60) >> 5; + _value_ptr = &_channels[tmp].freq; + _channels[tmp].volume = 0; + } + *_value_ptr = _start; + if (_channels[0].cmd_ptr) { + tmp = READ_LE_UINT16(_channels[0].cmd_ptr); + _start_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 2); + _delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 4); + _time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 6); + _channels[0].cmd_ptr += 8; + if (_value_ptr == &_channels[3].volume) { + tmp = (tmp & 0x70) >> 4; + if (tmp & 1) + _value_ptr_2 = &_channels[tmp >> 1].volume; + else + _value_ptr_2 = &_channels[tmp >> 1].freq; + } else { + assert(!(tmp & 0x10)); + tmp = (tmp & 0x60) >> 5; + _value_ptr_2 = &_channels[tmp].freq; + _channels[tmp].volume = 0; + } + *_value_ptr_2 = _start_2; + } + debug(6, "chunk 1: %lu: %d step %d for %d, %lu: %d step %d for %d", + (long)(_value_ptr - (uint *)_channels), _start, _delta, _time_left, + (long)(_value_ptr_2 - (uint *)_channels), _start_2, _delta_2, _time_left_2); + break; + + case 2: + _start = READ_LE_UINT16(_next_chunk); + _end = READ_LE_UINT16(_next_chunk + 2); + _delta = (int16) READ_LE_UINT16(_next_chunk + 4); + _channels[0].freq = 0; + _next_chunk += 6; + _forced_level = -1; + debug(6, "chunk 2: %d -> %d step %d", + _start, _end, _delta); + break; + case 3: + set_mplex(READ_LE_UINT16(_next_chunk)); + tmp = READ_LE_UINT16(_next_chunk + 2); + assert((tmp & 0xf0) == 0xe0); + _channels[3].freq = tmp & 0xf; + if ((tmp & 3) == 3) { + _next_chunk += 2; + _channels[2].freq = READ_LE_UINT16(_next_chunk + 2); + } + _channels[3].volume = READ_LE_UINT16(_next_chunk + 4); + _repeat_ctr = READ_LE_UINT16(_next_chunk + 6); + _delta = (int16) READ_LE_UINT16(_next_chunk + 8); + _next_chunk += 10; + break; + } +} + +void Player_V1::nextPCjrCmd() { + uint i; + int dummy; + switch (_chunk_type) { + case 0: + for (i = 0; i < 4; i++) { + if (!_channels[i].cmd_ptr) + continue; + if (!--_channels[i].notelen) { + dummy = READ_LE_UINT16(_channels[i].cmd_ptr); + if (dummy >= 0xfffe) { + if (dummy == 0xfffe) + _next_chunk = _current_data + 2; + parsePCjrChunk(); + return; + } + _channels[i].notelen = 4 * dummy; + dummy = READ_LE_UINT16(_channels[i].cmd_ptr + 2); + if (dummy == 0) { + _channels[i].hull_counter = 4; + _channels[i].sustctr = _channels[i].sustain_2; + } else { + _channels[i].hull_counter = 1; + _channels[i].freq = dummy; + } + debug(7, "chunk 0: channel %d play %d for %d", + i, dummy, _channels[i].notelen); + _channels[i].cmd_ptr += 4; + } + + + switch (_channels[i].hull_counter) { + case 1: + _channels[i].volume -= _channels[i].attack; + if ((int) _channels[i].volume <= 0) { + _channels[i].volume = 0; + _channels[i].hull_counter++; + } + break; + case 2: + _channels[i].volume += _channels[i].decay; + if (_channels[i].volume >= _channels[i].level) { + _channels[i].volume = _channels[i].level; + _channels[i].hull_counter++; + } + break; + case 4: + if (--_channels[i].sustctr < 0) { + _channels[i].sustctr = _channels[i].sustain_2; + _channels[i].volume += _channels[i].sustain_1; + if ((int) _channels[i].volume >= 15) { + _channels[i].volume = 15; + _channels[i].hull_counter++; + } + } + break; + } + } + break; + + case 1: + _start += _delta; + *_value_ptr = _start; + if (!--_time_left) { + _start = READ_LE_UINT16(_next_chunk); + _next_chunk += 2; + if (_start == 0xffff) { + parsePCjrChunk(); + return; + } + _delta = (int16) READ_LE_UINT16(_next_chunk); + _time_left = READ_LE_UINT16(_next_chunk + 2); + _next_chunk += 4; + *_value_ptr = _start; + } + + if (_channels[0].cmd_ptr) { + _start_2 += _delta_2; + *_value_ptr_2 = _start_2; + if (!--_time_left_2) { + _start_2 = READ_LE_UINT16(_channels[0].cmd_ptr); + if (_start_2 == 0xffff) { + _next_chunk = _channels[0].cmd_ptr + 2; + parsePCjrChunk(); + return; + } + _delta_2 = (int16) READ_LE_UINT16(_channels[0].cmd_ptr + 2); + _time_left_2 = READ_LE_UINT16(_channels[0].cmd_ptr + 4); + _channels[0].cmd_ptr += 6; + } + } + break; + + case 2: + _start += _delta; + if (_start == _end) { + parsePCjrChunk(); + return; + } + set_mplex(_start); + debug(7, "chunk 2: mplex %d curve %d", _start, _forced_level); + _forced_level = -_forced_level; + break; + case 3: + dummy = _channels[3].volume + _delta; + if (dummy >= 15) { + _channels[3].volume = 15; + } else if (dummy <= 0) { + _channels[3].volume = 0; + } else { + _channels[3].volume = dummy; + break; + } + + if (!--_repeat_ctr) { + parsePCjrChunk(); + return; + } + _delta = READ_LE_UINT16(_next_chunk); + _next_chunk += 2; + break; + } +} + +void Player_V1::set_mplex(uint mplex) { + if (mplex == 0) + mplex = 65536; + _mplex = mplex; + _tick_len = _mplex_step * mplex; +} + +void Player_V1::nextTick() { + if (_next_chunk) { + if (_pcjr) + nextPCjrCmd(); + else + nextSpeakerCmd(); + } +} + +void Player_V1::generateSpkSamples(int16 *data, uint len) { + uint i; + + memset(data, 0, 2 * sizeof(int16) * len); + if (_channels[0].freq == 0) { + if (_forced_level) { + int sample = _forced_level * _volumetable[0]; + for (i = 0; i < len; i++) + data[2*i] = data[2*i+1] = sample; + debug(9, "speaker: %8x: forced one", _tick_len); + } else if (!_level) { + return; + } + } else { + squareGenerator(0, _channels[0].freq, 0, 0, data, len); + debug(9, "speaker: %8x: freq %d %.1f", _tick_len, + _channels[0].freq, 1193000.0 / _channels[0].freq); + } + lowPassFilter(data, len); +} + +void Player_V1::generatePCjrSamples(int16 *data, uint len) { + uint i, j; + uint freq, vol; + bool hasdata = false; + + memset(data, 0, 2 * sizeof(int16) * len); + + if (_forced_level) { + int sample = _forced_level * _volumetable[0]; + for (i = 0; i < len; i++) + data[2*i] = data[2*i+1] = sample; + hasdata = true; + debug(9, "channel[4]: %8x: forced one", _tick_len); + } + + for (i = 1; i < 3; i++) { + freq = _channels[i].freq; + if (freq) { + for (j = 0; j < i; j++) { + if (freq == _channels[j].freq) { + /* HACK: this channel is playing at + * the same frequency as another. + * Synchronize it to the same phase to + * prevent interference. + */ + _timer_count[i] = _timer_count[j]; + _timer_output ^= (1 << i) & + (_timer_output ^ _timer_output << (i - j)); + } + } + } + } + + for (i = 0; i < 4; i++) { + freq = _channels[i].freq; + vol = _channels[i].volume; + if (!_volumetable[_channels[i].volume]) { + _timer_count[i] -= len << FIXP_SHIFT; + if (_timer_count[i] < 0) + _timer_count[i] = 0; + } else if (i < 3) { + hasdata = true; + squareGenerator(i, freq, vol, 0, data, len); + debug(9, "channel[%d]: %8x: freq %d %.1f ; volume %d", + i, _tick_len, freq, 111860.0 / freq, vol); + } else { + int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE; + int n = (freq & 3); + + freq = (n == 3) ? 2 * (_channels[2].freq) : 1 << (5 + n); + hasdata = true; + squareGenerator(i, freq, vol, noiseFB, data, len); + debug(9, "channel[%d]: %x: noise freq %d %.1f ; volume %d", + i, _tick_len, freq, 111860.0 / freq, vol); + } + } + + if (_level || hasdata) + lowPassFilter(data, len); +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v1.h b/engines/scumm/players/player_v1.h new file mode 100644 index 0000000000..ccd24c39df --- /dev/null +++ b/engines/scumm/players/player_v1.h @@ -0,0 +1,98 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V1_H +#define SCUMM_PLAYERS_PLAYER_V1_H + +#include "scumm/players/player_v2.h" + +namespace Scumm { + +/** + * Scumm V1 PC-Speaker player. + */ +class Player_V1 : public Player_V2 { +public: + Player_V1(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr); + ~Player_V1(); + + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + +protected: + virtual void nextTick(); + virtual void clear_channel(int i); + virtual void chainSound(int nr, byte *data); + + virtual void generateSpkSamples(int16 *data, uint len); + virtual void generatePCjrSamples(int16 *data, uint len); + + void restartSound(); + + void set_mplex(uint mplex); + void parseSpeakerChunk(); + void nextSpeakerCmd(); + void parsePCjrChunk(); + void nextPCjrCmd(); + +private: + struct channel_data_v1 { + uint freq; + uint volume; + byte *cmd_ptr; + uint notelen; + uint hull_counter; + uint attack; + uint decay; + uint level; + uint sustain_1; + uint sustain_2; + int sustctr; + }; + + channel_data_v1 _channels[4]; + + byte *_next_chunk; + byte *_repeat_chunk; + uint _chunk_type; + uint _mplex_step; + uint _mplex; + uint _repeat_ctr; + uint _freq_current; + int _forced_level; + uint16 _random_lsr; + uint *_value_ptr; + uint _time_left; + uint _start; + uint _end; + int _delta; + uint *_value_ptr_2; + uint _time_left_2; + uint _start_2; + int _delta_2; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v2.cpp b/engines/scumm/players/player_v2.cpp new file mode 100644 index 0000000000..2429af2d8c --- /dev/null +++ b/engines/scumm/players/player_v2.cpp @@ -0,0 +1,327 @@ +/* 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_v2.h" +#include "scumm/scumm.h" + +namespace Scumm { + +#define SPK_DECAY 0xa000 /* Depends on sample rate */ +#define PCJR_DECAY 0xa000 /* Depends on sample rate */ + +#define NG_PRESET 0x0f35 /* noise generator preset */ +#define FB_WNOISE 0x12000 /* feedback for white noise */ +#define FB_PNOISE 0x08000 /* feedback for periodic noise */ + + +Player_V2::Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr) + : Player_V2Base(scumm, mixer, pcjr) { + + int i; + + // Initialize square generator + _level = 0; + + _RNG = NG_PRESET; + + _pcjr = pcjr; + + if (_pcjr) { + _decay = PCJR_DECAY; + _update_step = (_sampleRate << FIXP_SHIFT) / (111860 * 2); + } else { + _decay = SPK_DECAY; + _update_step = (_sampleRate << FIXP_SHIFT) / (1193000 * 2); + } + + // Adapt _decay to sample rate. It must be squared when + // sample rate doubles. + for (i = 0; (_sampleRate << i) < 30000; i++) + _decay = _decay * _decay / 65536; + + _timer_output = 0; + for (i = 0; i < 4; i++) + _timer_count[i] = 0; + + setMusicVolume(255); + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_V2::~Player_V2() { + Common::StackLock lock(_mutex); + _mixer->stopHandle(_soundHandle); +} + +void Player_V2::setMusicVolume (int vol) { + if (vol > 255) + vol = 255; + + /* scale to int16, FIXME: find best value */ + double out = vol * 128 / 3; + + /* build volume table (2dB per step) */ + for (int i = 0; i < 15; i++) { + /* limit volume to avoid clipping */ + if (out > 0xffff) + _volumetable[i] = 0xffff; + else + _volumetable[i] = (int) out; + + out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */ + } + _volumetable[15] = 0; +} + +void Player_V2::stopAllSounds() { + Common::StackLock lock(_mutex); + + for (int i = 0; i < 4; i++) { + clear_channel(i); + } + _next_nr = _current_nr = 0; + _next_data = _current_data = 0; +} + +void Player_V2::stopSound(int nr) { + Common::StackLock lock(_mutex); + + if (_next_nr == nr) { + _next_nr = 0; + _next_data = 0; + } + if (_current_nr == nr) { + for (int i = 0; i < 4; i++) { + clear_channel(i); + } + _current_nr = 0; + _current_data = 0; + chainNextSound(); + } +} + +void Player_V2::startSound(int nr) { + Common::StackLock lock(_mutex); + + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + + int cprio = _current_data ? *(_current_data + _header_len) : 0; + int prio = *(data + _header_len); + int nprio = _next_data ? *(_next_data + _header_len) : 0; + + int restartable = *(data + _header_len + 1); + + if (!_current_nr || cprio <= prio) { + int tnr = _current_nr; + int tprio = cprio; + byte *tdata = _current_data; + + chainSound(nr, data); + nr = tnr; + prio = tprio; + data = tdata; + restartable = data ? *(data + _header_len + 1) : 0; + } + + if (!_current_nr) { + nr = 0; + _next_nr = 0; + _next_data = 0; + } + + if (nr != _current_nr + && restartable + && (!_next_nr + || nprio <= prio)) { + + _next_nr = nr; + _next_data = data; + } +} + +int Player_V2::getSoundStatus(int nr) const { + return _current_nr == nr || _next_nr == nr; +} + +int Player_V2::readBuffer(int16 *data, const int numSamples) { + Common::StackLock lock(_mutex); + + uint step; + uint len = numSamples / 2; + + do { + if (!(_next_tick >> FIXP_SHIFT)) { + _next_tick += _tick_len; + nextTick(); + } + + step = len; + if (step > (_next_tick >> FIXP_SHIFT)) + step = (_next_tick >> FIXP_SHIFT); + if (_pcjr) + generatePCjrSamples(data, step); + else + generateSpkSamples(data, step); + data += 2 * step; + _next_tick -= step << FIXP_SHIFT; + } while (len -= step); + + return numSamples; +} + +void Player_V2::lowPassFilter(int16 *sample, uint len) { + for (uint i = 0; i < len; i++) { + _level = (int) (_level * _decay + + sample[0] * (0x10000 - _decay)) >> 16; + sample[0] = sample[1] = _level; + sample += 2; + } +} + +void Player_V2::squareGenerator(int channel, int freq, int vol, + int noiseFeedback, int16 *sample, uint len) { + int32 period = _update_step * freq; + int32 nsample; + if (period == 0) + period = _update_step; + + for (uint i = 0; i < len; i++) { + uint32 duration = 0; + + if (_timer_output & (1 << channel)) + duration += _timer_count[channel]; + + _timer_count[channel] -= (1 << FIXP_SHIFT); + while (_timer_count[channel] <= 0) { + + if (noiseFeedback) { + if (_RNG & 1) { + _RNG ^= noiseFeedback; + _timer_output ^= (1 << channel); + } + _RNG >>= 1; + } else { + _timer_output ^= (1 << channel); + } + + if (_timer_output & (1 << channel)) + duration += period; + + _timer_count[channel] += period; + } + + if (_timer_output & (1 << channel)) + duration -= _timer_count[channel]; + + nsample = *sample + + (((int32) (duration - (1 << (FIXP_SHIFT - 1))) + * (int32) _volumetable[vol]) >> FIXP_SHIFT); + /* overflow: clip value */ + if (nsample > 0x7fff) + nsample = 0x7fff; + if (nsample < -0x8000) + nsample = -0x8000; + *sample = nsample; + // The following write isn't necessary, because the lowPassFilter does it for us + //sample[1] = sample[0]; + sample += 2; + } +} + +void Player_V2::generateSpkSamples(int16 *data, uint len) { + int winning_channel = -1; + for (int i = 0; i < 4; i++) { + if (winning_channel == -1 + && _channels[i].d.volume + && _channels[i].d.time_left) { + winning_channel = i; + } + } + + memset(data, 0, 2 * sizeof(int16) * len); + if (winning_channel != -1) { + squareGenerator(0, _channels[winning_channel].d.freq, 0, + 0, data, len); + } else if (_level == 0) + /* shortcut: no sound is being played. */ + return; + + lowPassFilter(data, len); +} + +void Player_V2::generatePCjrSamples(int16 *data, uint len) { + int i, j; + int freq, vol; + + memset(data, 0, 2 * sizeof(int16) * len); + bool hasdata = false; + + for (i = 1; i < 3; i++) { + freq = _channels[i].d.freq >> 6; + if (_channels[i].d.volume && _channels[i].d.time_left) { + for (j = 0; j < i; j++) { + if (_channels[j].d.volume + && _channels[j].d.time_left + && freq == (_channels[j].d.freq >> 6)) { + /* HACK: this channel is playing at + * the same frequency as another. + * Synchronize it to the same phase to + * prevent interference. + */ + _timer_count[i] = _timer_count[j]; + _timer_output ^= (1 << i) & + (_timer_output ^ _timer_output << (i - j)); + } + } + } + } + + for (i = 0; i < 4; i++) { + freq = _channels[i].d.freq >> 6; + vol = (65535 - _channels[i].d.volume) >> 12; + if (!_channels[i].d.volume || !_channels[i].d.time_left) { + _timer_count[i] -= len << FIXP_SHIFT; + if (_timer_count[i] < 0) + _timer_count[i] = 0; + } else if (i < 3) { + hasdata = true; + squareGenerator(i, freq, vol, 0, data, len); + } else { + int noiseFB = (freq & 4) ? FB_WNOISE : FB_PNOISE; + int n = (freq & 3); + + freq = (n == 3) ? 2 * (_channels[2].d.freq>>6) : 1 << (5 + n); + hasdata = true; + squareGenerator(i, freq, vol, noiseFB, data, len); + } +#if 0 + debug(9, "channel[%d]: freq %d %.1f ; volume %d", + i, freq, 111860.0 / freq, vol); +#endif + } + + if (_level || hasdata) + lowPassFilter(data, len); +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v2.h b/engines/scumm/players/player_v2.h new file mode 100644 index 0000000000..33878ff08b --- /dev/null +++ b/engines/scumm/players/player_v2.h @@ -0,0 +1,72 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V2_H +#define SCUMM_PLAYERS_PLAYER_V2_H + +#include "scumm/players/player_v2base.h" + +namespace Scumm { + +/** + * Scumm V2 PC-Speaker MIDI driver. + * This simulates the pc speaker sound, which is driven by the 8253 (square + * wave generator) and a low-band filter. + */ +class Player_V2 : public Player_V2Base { +public: + Player_V2(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr); + virtual ~Player_V2(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); +// virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + +protected: + unsigned int _update_step; + unsigned int _decay; + int _level; + unsigned int _RNG; + unsigned int _volumetable[16]; + + int _timer_count[4]; + int _timer_output; + +protected: + virtual void generateSpkSamples(int16 *data, uint len); + virtual void generatePCjrSamples(int16 *data, uint len); + + void lowPassFilter(int16 *data, uint len); + void squareGenerator(int channel, int freq, int vol, + int noiseFeedback, int16 *sample, uint len); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v2a.cpp b/engines/scumm/players/player_v2a.cpp new file mode 100644 index 0000000000..aeccb8b7cb --- /dev/null +++ b/engines/scumm/players/player_v2a.cpp @@ -0,0 +1,1954 @@ +/* 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 "engines/engine.h" +#include "scumm/players/player_v2a.h" +#include "scumm/scumm.h" + +namespace Scumm { + +#define BASE_FREQUENCY 3579545 + +static uint32 CRCtable[256]; + + +static void InitCRC() { + const uint32 poly = 0xEDB88320; + int i, j; + uint32 n; + + for (i = 0; i < 256; i++) { + n = i; + for (j = 0; j < 8; j++) + n = (n & 1) ? ((n >> 1) ^ poly) : (n >> 1); + CRCtable[i] = n; + } +} + +static uint32 GetCRC(byte *data, int len) { + uint32 CRC = 0xFFFFFFFF; + int i; + for (i = 0; i < len; i++) + CRC = (CRC >> 8) ^ CRCtable[(CRC ^ data[i]) & 0xFF]; + return CRC ^ 0xFFFFFFFF; +} + +class V2A_Sound { +public: + V2A_Sound() : _id(0), _mod(NULL) { } + virtual ~V2A_Sound() {} + virtual void start(Player_MOD *mod, int id, const byte *data) = 0; + virtual bool update() = 0; + virtual void stop() = 0; +protected: + int _id; + Player_MOD *_mod; +}; + +// unsupported sound effect, print warning message to console +class V2A_Sound_Unsupported : public V2A_Sound { +public: + V2A_Sound_Unsupported() { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + warning("player_v2a - sound %i not supported", id); + } + virtual bool update() { return false; } + virtual void stop() { } +}; + +// template, automatically stops all channels when a sound is silenced +template<int numChan> +class V2A_Sound_Base : public V2A_Sound { +public: + V2A_Sound_Base() : _offset(0), _size(0), _data(0) { } + V2A_Sound_Base(uint16 offset, uint16 size) : _offset(offset), _size(size), _data(0) { } + virtual void stop() { + assert(_id); + for (int i = 0; i < numChan; i++) + _mod->stopChannel(_id | (i << 8)); + _id = 0; + free(_data); + _data = 0; + } +protected: + const uint16 _offset; + const uint16 _size; + + char *_data; +}; + +// plays a music track +class V2A_Sound_Music : public V2A_Sound { +public: + V2A_Sound_Music(uint16 instoff, uint16 voloff, uint16 chan1off, uint16 chan2off, uint16 chan3off, uint16 chan4off, uint16 sampoff, bool looped) : + _instoff(instoff), _voloff(voloff), _chan1off(chan1off), _chan2off(chan2off), _chan3off(chan3off), _chan4off(chan4off), _sampoff(sampoff), _looped(looped) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _chan[0].dataptr_i = _chan1off; + _chan[1].dataptr_i = _chan2off; + _chan[2].dataptr_i = _chan3off; + _chan[3].dataptr_i = _chan4off; + for (int i = 0; i < 4; i++) { + _chan[i].dataptr = _chan[i].dataptr_i; + _chan[i].volbase = 0; + _chan[i].volptr = 0; + _chan[i].chan = 0; + _chan[i].dur = 0; + _chan[i].ticks = 0; + } + update(); + } + virtual bool update() { + assert(_id); + int i, j = 0; + for (i = 0; i < 4; i++) { + if (_chan[i].dur) { + if (!--_chan[i].dur) { + _mod->stopChannel(_id | (_chan[i].chan << 8)); + } else { + _mod->setChannelVol(_id | (_chan[i].chan << 8), + READ_BE_UINT16(_data + _chan[i].volbase + (_chan[i].volptr++ << 1))); + if (_chan[i].volptr == 0) { + _mod->stopChannel(_id | (_chan[i].chan << 8)); + _chan[i].dur = 0; + } + } + } + if (!_chan[i].dataptr) { + j++; + continue; + } + if (READ_BE_UINT16(_data + _chan[i].dataptr) <= _chan[i].ticks) { + if (READ_BE_UINT16(_data + _chan[i].dataptr + 2) == 0xFFFF) { + if (_looped) { + _chan[i].dataptr = _chan[i].dataptr_i; + _chan[i].ticks = 0; + if (READ_BE_UINT16(_data + _chan[i].dataptr) > 0) { + _chan[i].ticks++; + continue; + } + } else { + _chan[i].dataptr = 0; + j++; + continue; + } + } + int freq = BASE_FREQUENCY / READ_BE_UINT16(_data + _chan[i].dataptr + 2); + int inst = READ_BE_UINT16(_data + _chan[i].dataptr + 8); + _chan[i].volbase = _voloff + (READ_BE_UINT16(_data + _instoff + (inst << 5)) << 9); + _chan[i].volptr = 0; + _chan[i].chan = READ_BE_UINT16(_data + _chan[i].dataptr + 6) & 0x3; + + if (_chan[i].dur) // if there's something playing, stop it + _mod->stopChannel(_id | (_chan[i].chan << 8)); + + _chan[i].dur = READ_BE_UINT16(_data + _chan[i].dataptr + 4); + + int vol = READ_BE_UINT16(_data + _chan[i].volbase + (_chan[i].volptr++ << 1)); + + int pan; + if ((_chan[i].chan == 0) || (_chan[i].chan == 3)) + pan = -127; + else + pan = 127; + int offset = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x14); + int len = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x18); + int loopoffset = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x16); + int looplen = READ_BE_UINT16(_data + _instoff + (inst << 5) + 0x10); + + int size = len + looplen; + char *data = (char *)malloc(size); + memcpy(data, _data + _sampoff + offset, len); + memcpy(data + len, _data + _sampoff + loopoffset, looplen); + + _mod->startChannel(_id | (_chan[i].chan << 8), data, size, freq, vol, len, looplen + len, pan); + _chan[i].dataptr += 16; + } + _chan[i].ticks++; + } + if (j == 4) + return false; + return true; + } + virtual void stop() { + assert(_id); + for (int i = 0; i < 4; i++) { + if (_chan[i].dur) + _mod->stopChannel(_id | (_chan[i].chan << 8)); + } + free(_data); + _id = 0; + } +private: + const uint16 _instoff; + const uint16 _voloff; + const uint16 _chan1off; + const uint16 _chan2off; + const uint16 _chan3off; + const uint16 _chan4off; + const uint16 _sampoff; + const bool _looped; + + char *_data; + struct tchan { + uint16 dataptr_i; + uint16 dataptr; + uint16 volbase; + uint8 volptr; + uint16 chan; + uint16 dur; + uint16 ticks; + } _chan[4]; +}; + +// plays a single waveform +class V2A_Sound_Single : public V2A_Sound_Base<1> { +public: + V2A_Sound_Single(uint16 offset, uint16 size, uint16 freq, uint8 vol) : + V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + int vol = (_vol << 2) | (_vol >> 4); + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, vol, 0, 0); + _ticks = 1 + (60 * _size * _freq) / BASE_FREQUENCY; + } + virtual bool update() { + assert(_id); + _ticks--; + if (!_ticks) { + return false; + } + return true; + } +private: + const uint16 _freq; + const uint8 _vol; + + int _ticks; +}; + +// plays a single looped waveform +class V2A_Sound_SingleLooped : public V2A_Sound_Base<1> { +public: + V2A_Sound_SingleLooped(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint16 loopoffset, uint16 loopsize) : + V2A_Sound_Base<1>(offset, size), _loopoffset(loopoffset), _loopsize(loopsize), _freq(freq), _vol(vol) { } + V2A_Sound_SingleLooped(uint16 offset, uint16 size, uint16 freq, uint8 vol) : + V2A_Sound_Base<1>(offset, size), _loopoffset(0), _loopsize(size), _freq(freq), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + int vol = (_vol << 2) | (_vol >> 4); + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, vol, _loopoffset, _loopoffset + _loopsize); + } + virtual bool update() { + assert(_id); + return true; + } +private: + const uint16 _loopoffset; + const uint16 _loopsize; + const uint16 _freq; + const uint8 _vol; +}; + +// plays two looped waveforms +class V2A_Sound_MultiLooped : public V2A_Sound_Base<2> { +public: + V2A_Sound_MultiLooped(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2) : + V2A_Sound_Base<2>(offset, size), _freq1(freq1), _vol1(vol1), _freq2(freq2), _vol2(vol2) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + memcpy(tmp_data1, data + _offset, _size); + memcpy(tmp_data2, data + _offset, _size); + int vol1 = (_vol1 << 1) | (_vol1 >> 5); + int vol2 = (_vol2 << 1) | (_vol2 >> 5); + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, vol1, 0, _size, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, vol2, 0, _size, 127); + } + virtual bool update() { + assert(_id); + return true; + } +private: + const uint16 _freq1; + const uint8 _vol1; + const uint16 _freq2; + const uint8 _vol2; +}; + +// plays two looped waveforms for a fixed number of frames +class V2A_Sound_MultiLoopedDuration : public V2A_Sound_MultiLooped { +public: + V2A_Sound_MultiLoopedDuration(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2, uint16 numframes) : + V2A_Sound_MultiLooped(offset, size, freq1, vol1, freq2, vol2), _duration(numframes) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + V2A_Sound_MultiLooped::start(mod, id, data); + _ticks = 0; + } + virtual bool update() { + assert(_id); + _ticks++; + if (_ticks >= _duration) + return false; + return true; + } +private: + const uint16 _duration; + + int _ticks; +}; + +// plays a single looped waveform which starts at one frequency and bends to another frequency, where it remains until stopped +class V2A_Sound_SingleLoopedPitchbend : public V2A_Sound_Base<1> { +public: + V2A_Sound_SingleLoopedPitchbend(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 vol, uint8 step) : + V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _vol(vol), _step(step) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + int vol = (_vol << 2) | (_vol >> 4); + _curfreq = _freq1; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, vol, 0, _size); + } + virtual bool update() { + assert(_id); + if (_freq1 < _freq2) { + _curfreq += _step; + if (_curfreq > _freq2) + _curfreq = _freq2; + else + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + } else { + _curfreq -= _step; + if (_curfreq < _freq2) + _curfreq = _freq2; + else + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + } + return true; + } +private: + const uint16 _freq1; + const uint16 _freq2; + const uint8 _vol; + const uint16 _step; + + uint16 _curfreq; +}; + +// plays a single looped waveform starting at a specific frequency/volume, dropping in frequency and fading volume to zero +// used when Maniac Mansion explodes +class V2A_Sound_Special_Maniac69 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Maniac69(uint16 offset, uint16 size, uint16 freq, uint8 vol) : + V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + _curvol = (_vol << 3) | (_vol >> 3); + _curfreq = _freq; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, _curvol >> 1, 0, _size); + } + virtual bool update() { + assert(_id); + _curfreq += 2; + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + _curvol--; + if (_curvol == 0) + return false; + _mod->setChannelVol(_id, _curvol >> 1); + return true; + } +private: + const uint16 _freq; + const uint8 _vol; + + uint16 _curfreq; + uint16 _curvol; +}; + +// plays a single looped waveform, fading the volume from zero to maximum at one rate, then back to zero at another rate +// used when a microwave oven goes 'Ding' +class V2A_Sound_Special_ManiacDing : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_ManiacDing(uint16 offset, uint16 size, uint16 freq, uint8 fadeinrate, uint8 fadeoutrate) : + V2A_Sound_Base<1>(offset, size), _freq(freq), _fade1(fadeinrate), _fade2(fadeoutrate) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + _curvol = 1; + _dir = 0; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, _curvol, 0, _size); + } + virtual bool update() { + assert(_id); + if (_dir == 0) { + _curvol += _fade1; + if (_curvol > 0x3F) { + _curvol = 0x3F; + _dir = 1; + } + } else { + _curvol -= _fade2; + if (_curvol < 1) + return false; + } + _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4)); + return true; + } +private: + const uint16 _freq; + const uint16 _fade1; + const uint16 _fade2; + + int _curvol; + int _dir; +}; + +// plays two looped waveforms, fading the volume from zero to maximum at one rate, then back to zero at another rate +// used in Zak McKracken for several stereo 'Ding' sounds +class V2A_Sound_Special_ZakStereoDing : public V2A_Sound_Base<2> { +public: + V2A_Sound_Special_ZakStereoDing(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 fadeinrate, uint8 fadeoutrate) : + V2A_Sound_Base<2>(offset, size), _freq1(freq1), _freq2(freq2), _fade1(fadeinrate), _fade2(fadeoutrate) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + memcpy(tmp_data1, data + _offset, _size); + memcpy(tmp_data2, data + _offset, _size); + _curvol = 1; + _dir = 0; + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, 1, 0, _size, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, 1, 0, _size, 127); + } + virtual bool update() { + assert(_id); + if (_dir == 0) { + _curvol += _fade1; + if (_curvol > 0x3F) { + _curvol = 0x3F; + _dir = 1; + } + } else { + _curvol -= _fade2; + if (_curvol < 1) + return false; + } + _mod->setChannelVol(_id | 0x000, (_curvol << 1) | (_curvol >> 5)); + _mod->setChannelVol(_id | 0x100, (_curvol << 1) | (_curvol >> 5)); + return true; + } +private: + const uint16 _freq1; + const uint16 _freq2; + const uint16 _fade1; + const uint16 _fade2; + + int _curvol; + int _dir; +}; + +// plays a single looped waveform, starting at one frequency and at full volume, bending down to another frequency, and then fading volume to zero +// used in Maniac Mansion for the tentacle sounds +class V2A_Sound_Special_ManiacTentacle : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_ManiacTentacle(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 step) : + V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _step(step) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + _curfreq = _freq1; + _curvol = 0x3F; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size); + } + virtual bool update() { + assert(_id); + if (_curfreq > _freq2) + _curvol = 0x3F + _freq2 - _curfreq; + if (_curvol < 1) + return false; + _curfreq += _step; + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4)); + return true; + } +private: + const uint16 _freq1; + const uint16 _freq2; + const uint16 _step; + + uint16 _curfreq; + int _curvol; +}; + +// plays a single looped waveform, starting at one frequency, bending down to another frequency, and then back up to the original frequency +// used for electronic noises +class V2A_Sound_Special_Maniac59 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Maniac59(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 step, uint8 vol) : + V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _step(step), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + int vol = (_vol << 2) | (_vol >> 4); + _curfreq = _freq1; + _dir = 2; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, vol, 0, _size); + } + virtual bool update() { + assert(_id); + if (_dir == 2) { + _curfreq += _step; + if (_curfreq > _freq2) { + _curfreq = _freq2; + _dir = 1; + } + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + } else if (_dir == 1) { + _curfreq -= _step; + if (_curfreq < _freq1) { + _curfreq = _freq1; + _dir = 0; + } + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + } + return true; + } +private: + const uint16 _freq1; + const uint16 _freq2; + const uint16 _step; + const uint8 _vol; + + uint16 _curfreq; + int _dir; +}; + +// plays a single looped waveform, simultaneously bending the frequency downward and slowly fading volume to zero +// don't remember where this one is used +class V2A_Sound_Special_Maniac61 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Maniac61(uint16 offset, uint16 size, uint16 freq1, uint16 freq2) : + V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + _curfreq = _freq1; + _curvol = 0x3F; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size); + } + virtual bool update() { + assert(_id); + _curfreq++; + if (!(_curfreq & 3)) + _curvol--; + if ((_curfreq == _freq2) || (_curvol == 0)) + return false; + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4)); + return true; + } +private: + const uint16 _freq1; + const uint16 _freq2; + + uint16 _curfreq; + uint8 _curvol; +}; + +// intermittently plays two looped waveforms for a specific duration +// used for ringing telephones +class V2A_Sound_Special_ManiacPhone : public V2A_Sound_Base<2> { +public: + V2A_Sound_Special_ManiacPhone(uint16 offset, uint16 size, uint16 freq1, uint8 vol1, uint16 freq2, uint8 vol2, uint16 numframes, uint8 playwidth, uint8 loopwidth) : + V2A_Sound_Base<2>(offset, size), _freq1(freq1), _vol1(vol1), _freq2(freq2), _vol2(vol2), _duration(numframes), _playwidth(playwidth), _loopwidth(loopwidth) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + soundon(); + _ticks = 0; + _loop = 0; + } + virtual bool update() { + assert(_id); + if (_loop == _playwidth) { + _mod->stopChannel(_id | 0x000); + _mod->stopChannel(_id | 0x100); + } + if (_loop == _loopwidth) { + _loop = 0; + soundon(); + } + _loop++; + _ticks++; + if (_ticks >= _duration) + return false; + return true; + } +private: + const uint16 _freq1; + const uint8 _vol1; + const uint16 _freq2; + const uint8 _vol2; + const uint16 _duration; + const uint8 _playwidth; + const uint8 _loopwidth; + + int _ticks; + int _loop; + + void soundon() { + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + memcpy(tmp_data1, _data + _offset, _size); + memcpy(tmp_data2, _data + _offset, _size); + int vol1 = (_vol1 << 1) | (_vol1 >> 5); + int vol2 = (_vol2 << 1) | (_vol2 >> 5); + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, vol1, 0, _size, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, vol2, 0, _size, 127); + } +}; + +// intermittently plays a single waveform for a specified duration +// used when applying a wrench to a pipe +class V2A_Sound_Special_Maniac46 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Maniac46(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint8 loopwidth, uint8 numloops) : + V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol), _loopwidth(loopwidth), _numloops(numloops) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + soundon(); + _loop = 0; + _loopctr = 0; + } + virtual bool update() { + assert(_id); + _loop++; + if (_loop == _loopwidth) { + _loop = 0; + _loopctr++; + if (_loopctr == _numloops) + return false; + _mod->stopChannel(_id); + soundon(); + } + return true; + } +private: + const uint16 _freq; + const uint8 _vol; + const uint8 _loopwidth; + const uint8 _numloops; + + int _loop; + int _loopctr; + + void soundon() { + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, _data + _offset, _size); + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, (_vol << 2) | (_vol >> 4), 0, 0); + } +}; + +// plays a single waveform at irregular intervals for a specified number of frames, possibly looped +// used for typewriter noises, as well as tapping on the bus in Zak McKracken +class V2A_Sound_Special_ManiacTypewriter : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_ManiacTypewriter(uint16 offset, uint16 size, uint16 freq, uint8 vol, uint8 numdurs, const uint8 *durations, bool looped) : + V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol), _numdurs(numdurs), _durations(durations), _looped(looped) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + soundon(); + _curdur = 0; + _ticks = _durations[_curdur++]; + } + virtual bool update() { + assert(_id); + _ticks--; + if (!_ticks) { + if (_curdur == _numdurs) { + if (_looped) + _curdur = 0; + else + return false; + } + _mod->stopChannel(_id); + soundon(); + _ticks = _durations[_curdur++]; + } + return true; + } +private: + const uint16 _freq; + const uint8 _vol; + const uint8 _numdurs; + const uint8 *_durations; + const bool _looped; + + int _ticks; + int _curdur; + + void soundon() { + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, _data + _offset, _size); + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, (_vol << 2) | (_vol >> 4), 0, 0); + } +}; + +// plays two looped waveforms pitch bending up at various predefined rates +// used for some sort of siren-like noise in Maniac Mansion +class V2A_Sound_Special_Maniac44 : public V2A_Sound_Base<2> { +public: + V2A_Sound_Special_Maniac44(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq1, uint16 freq2, uint8 vol) : + _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq1(freq1), _freq2(freq2), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _loopnum = 1; + _step = 2; + _curfreq = _freq1; + + soundon(_data + _offset1, _size1); + } + virtual bool update() { + assert(_id); + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _curfreq); + _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / (_curfreq + 3)); + _curfreq -= _step; + if (_loopnum == 7) { + if ((BASE_FREQUENCY / _curfreq) >= 65536) + return false; + else + return true; + } + if (_curfreq >= _freq2) + return true; + const char steps[8] = {0, 2, 2, 3, 4, 8, 15, 2}; + _curfreq = _freq1; + _step = steps[++_loopnum]; + if (_loopnum == 7) { + _mod->stopChannel(_id | 0x000); + _mod->stopChannel(_id | 0x100); + soundon(_data + _offset2, _size2); + } + return true; + } +private: + const uint16 _offset1; + const uint16 _size1; + const uint16 _offset2; + const uint16 _size2; + const uint16 _freq1; + const uint16 _freq2; + const uint8 _vol; + + int _curfreq; + uint16 _loopnum; + uint16 _step; + + void soundon(const char *data, int size) { + char *tmp_data1 = (char *)malloc(size); + char *tmp_data2 = (char *)malloc(size); + memcpy(tmp_data1, data, size); + memcpy(tmp_data2, data, size); + int vol = (_vol << 1) | (_vol >> 5); + _mod->startChannel(_id | 0x000, tmp_data1, size, BASE_FREQUENCY / _curfreq, vol, 0, size, -127); + _mod->startChannel(_id | 0x100, tmp_data2, size, BASE_FREQUENCY / (_curfreq + 3), vol, 0, size, 127); + } +}; + +// plays 4 looped waveforms, each at modulating frequencies +// used for the siren noise in Maniac Mansion +class V2A_Sound_Special_Maniac32 : public V2A_Sound_Base<4> { +public: + V2A_Sound_Special_Maniac32(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint8 vol) : + _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + + _freq1 = 0x02D0; + _step1 = -0x000A; + _freq2 = 0x0122; + _step2 = 0x000A; + _freq3 = 0x02BC; + _step3 = -0x0005; + _freq4 = 0x010E; + _step4 = 0x0007; + + char *tmp_data1 = (char *)malloc(_size1); + char *tmp_data2 = (char *)malloc(_size2); + char *tmp_data3 = (char *)malloc(_size1); + char *tmp_data4 = (char *)malloc(_size2); + memcpy(tmp_data1, data + _offset1, _size1); + memcpy(tmp_data2, data + _offset2, _size2); + memcpy(tmp_data3, data + _offset1, _size1); + memcpy(tmp_data4, data + _offset2, _size2); + _mod->startChannel(_id | 0x000, tmp_data1, _size1, BASE_FREQUENCY / _freq1, _vol, 0, _size1, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size2, BASE_FREQUENCY / _freq2, _vol, 0, _size2, 127); + _mod->startChannel(_id | 0x200, tmp_data3, _size1, BASE_FREQUENCY / _freq3, _vol, 0, _size1, 127); + _mod->startChannel(_id | 0x300, tmp_data4, _size2, BASE_FREQUENCY / _freq4, _vol, 0, _size2, -127); + } + virtual bool update() { + assert(_id); + updatefreq(_freq1, _step1, 0x00AA, 0x00FA); + updatefreq(_freq2, _step2, 0x019A, 0x03B6); + updatefreq(_freq3, _step3, 0x00AA, 0x00FA); + updatefreq(_freq4, _step4, 0x019A, 0x03B6); + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq1); + _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / _freq2); + _mod->setChannelFreq(_id | 0x200, BASE_FREQUENCY / _freq3); + _mod->setChannelFreq(_id | 0x300, BASE_FREQUENCY / _freq4); + return true; + } +private: + const uint16 _offset1; + const uint16 _size1; + const uint16 _offset2; + const uint16 _size2; + const uint8 _vol; + + uint16 _freq1; + int16 _step1; + uint16 _freq2; + int16 _step2; + uint16 _freq3; + int16 _step3; + uint16 _freq4; + int16 _step4; + + void updatefreq(uint16 &freq, int16 &step, uint16 min, uint16 max) { + freq += step; + if (freq <= min) { + freq = min; + step = -step; + } + if (freq >= max) { + freq = max; + step = -step; + } + } +}; + +// plays 4 looped waveforms +// used in the white crystal chamber +class V2A_Sound_Special_Zak70 : public V2A_Sound_Base<4> { +public: + V2A_Sound_Special_Zak70(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 freq3, uint16 freq4, uint8 vol) : + V2A_Sound_Base<4>(offset, size), _freq1(freq1), _freq2(freq2), _freq3(freq3), _freq4(freq4), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + char *tmp_data3 = (char *)malloc(_size); + char *tmp_data4 = (char *)malloc(_size); + memcpy(tmp_data1, data + _offset, _size); + memcpy(tmp_data2, data + _offset, _size); + memcpy(tmp_data3, data + _offset, _size); + memcpy(tmp_data4, data + _offset, _size); + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, _vol, 0, _size, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, _vol, 0, _size, 127); + _mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq3, _vol, 0, _size, 127); + _mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq4, _vol, 0, _size, -127); + } + virtual bool update() { + assert(_id); + return true; + } +protected: + const uint16 _freq1; + const uint16 _freq2; + const uint16 _freq3; + const uint16 _freq4; + const uint8 _vol; +}; + +// plays 4 looped waveforms and fades volume to zero after a specific delay +// used when the Mindbender disappears +class V2A_Sound_Special_Zak101 : public V2A_Sound_Special_Zak70 { +public: + V2A_Sound_Special_Zak101(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint16 freq3, uint16 freq4, uint8 vol, uint16 dur) : + V2A_Sound_Special_Zak70(offset, size, freq1, freq2, freq3, freq4, vol), _dur(dur) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + V2A_Sound_Special_Zak70::start(mod, id, data); + _ticks = _dur; + } + virtual bool update() { + assert(_id); + if (!--_ticks) + return false; + if (_ticks < _vol) { + _mod->setChannelVol(_id | 0x000, _ticks); + _mod->setChannelVol(_id | 0x100, _ticks); + _mod->setChannelVol(_id | 0x200, _ticks); + _mod->setChannelVol(_id | 0x300, _ticks); + } + return true; + } +private: + const uint16 _dur; + + int _ticks; +}; + +// plays a single looped waveform and slowly fades volume to zero +// used when refilling oxygen +class V2A_Sound_Special_Zak37 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Zak37(uint16 offset, uint16 size, uint16 freq, uint8 vol) : + V2A_Sound_Base<1>(offset, size), _freq(freq), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + _curvol = _vol << 2; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _freq, _curvol, 0, _size); + } + virtual bool update() { + assert(_id); + if (!--_curvol) + return false; + _mod->setChannelVol(_id, _curvol); + return true; + } +private: + const uint16 _freq; + const uint8 _vol; + + int _curvol; +}; + +// plays a single looped waveform, slowly bending from one frequency to another and then slowly fading volume from max to zero +// used in Zak for airplane taking off and landing +class V2A_Sound_Special_ZakAirplane : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_ZakAirplane(uint16 offset, uint16 size, uint16 freq1, uint16 freq2) : + V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + _curfreq = _freq1; + _curvol = 0x3F; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_curvol << 2) | (_curvol >> 4), 0, _size); + _ticks = 0; + } + virtual bool update() { + assert(_id); + _ticks++; + if (_ticks < 4) + return true; + _ticks = 0; + if (_curfreq == _freq2) { + _curvol--; + if (_curvol == 0) + return false; + _mod->setChannelVol(_id, (_curvol << 2) | (_curvol >> 4)); + } else { + if (_freq1 < _freq2) + _curfreq++; + else + _curfreq--; + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + } + return true; + } +private: + const uint16 _freq1; + const uint16 _freq2; + + uint16 _curfreq; + int _curvol; + int _ticks; +}; + +// plays 4 looped waveforms, starting at specific frequencies and bending at different rates while fading volume to zero +// used when the white crystal machine turns off +class V2A_Sound_Special_Zak71 : public V2A_Sound_Base<4> { +public: + V2A_Sound_Special_Zak71(uint16 offset, uint16 size) : + _offset(offset), _size(size) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + + _freq1 = 0x00C8; + _freq2 = 0x0190; + _freq3 = 0x0320; + _freq4 = 0x0640; + _vol = 0x78; + + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + char *tmp_data3 = (char *)malloc(_size); + char *tmp_data4 = (char *)malloc(_size); + memcpy(tmp_data1, data + _offset, _size); + memcpy(tmp_data2, data + _offset, _size); + memcpy(tmp_data3, data + _offset, _size); + memcpy(tmp_data4, data + _offset, _size); + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq1, MIN((_vol >> 1) + 3, 0x32), 0, _size, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq2, MIN((_vol >> 1) + 3, 0x32), 0, _size, 127); + _mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq3, MIN((_vol >> 1) + 3, 0x32), 0, _size, 127); + _mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq4, MIN((_vol >> 1) + 3, 0x32), 0, _size, -127); + } + virtual bool update() { + assert(_id); + _freq1 += 0x14; + _freq2 += 0x1E; + _freq3 += 0x32; + _freq4 += 0x50; + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq1); + _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / _freq2); + _mod->setChannelFreq(_id | 0x200, BASE_FREQUENCY / _freq3); + _mod->setChannelFreq(_id | 0x300, BASE_FREQUENCY / _freq4); + _vol--; + if (_vol == 0) + return false; + _mod->setChannelVol(_id | 0x000, MIN((_vol >> 1) + 3, 0x32)); + _mod->setChannelVol(_id | 0x100, MIN((_vol >> 1) + 3, 0x32)); + _mod->setChannelVol(_id | 0x200, MIN((_vol >> 1) + 3, 0x32)); + _mod->setChannelVol(_id | 0x300, MIN((_vol >> 1) + 3, 0x32)); + return true; + } +private: + const uint16 _offset; + const uint16 _size; + + uint16 _freq1; + uint16 _freq2; + uint16 _freq3; + uint16 _freq4; + uint8 _vol; +}; + +// plays a single looped waveform, bending the frequency upward at a varying rate +// used when the Skolarian device activates +class V2A_Sound_Special_Zak99 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Zak99(uint16 offset, uint16 size, uint16 freq1, uint16 freq2, uint8 vol) : + V2A_Sound_Base<1>(offset, size), _freq1(freq1), _freq2(freq2), _vol(vol) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, data + _offset, _size); + _curfreq = _freq1; + _mod->startChannel(_id, tmp_data, _size, BASE_FREQUENCY / _curfreq, (_vol << 2) | (_vol >> 4), 0, _size); + _bendrate = 8; + _bendctr = 100; + _holdctr = 30; + } + virtual bool update() { + assert(_id); + if (_curfreq >= _freq2) { + _mod->setChannelFreq(_id, BASE_FREQUENCY / _curfreq); + _curfreq -= _bendrate; + if (--_bendctr) + return true; + _bendrate--; + if (_bendrate < 2) + _bendrate = 2; + } else { + if (!--_holdctr) + return false; + } + return true; + } +private: + const uint16 _freq1; + const uint16 _freq2; + const uint16 _vol; + + uint16 _curfreq; + uint16 _bendrate; + uint16 _bendctr; + uint16 _holdctr; +}; + +// plays one waveform, then switches to a different looped waveform and slowly fades volume to zero +// used when depressurizing the hostel +class V2A_Sound_Special_Zak54 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Zak54(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq) : + _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq(freq) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + char *tmp_data = (char *)malloc(_size1); + memcpy(tmp_data, data + _offset1, _size1); + _vol = 0xFC; + _mod->startChannel(_id, tmp_data, _size1, BASE_FREQUENCY / _freq, _vol, 0, _size1); + _loop = _size1 * _freq * 60 / BASE_FREQUENCY; + } + virtual bool update() { + assert(_id); + if (!_loop) { + _vol--; + if (_vol) + _mod->setChannelVol(_id, _vol); + else + return false; + } else if (!--_loop) { + _mod->stopChannel(_id); + char *tmp_data = (char *)malloc(_size2); + memcpy(tmp_data, _data + _offset2, _size2); + _mod->startChannel(_id, tmp_data, _size2, BASE_FREQUENCY / _freq, _vol, 0, _size2); + } + return true; + } + +private: + const uint16 _offset1; + const uint16 _offset2; + const uint16 _size1; + const uint16 _size2; + const uint16 _freq; + + int _vol; + int _loop; +}; + +// plays 2 looped waveforms at different frequencies, pulsing at different frequencies and ramping the volume up and down once +// used when abducted at the Bermuda Triangle +class V2A_Sound_Special_Zak110 : public V2A_Sound_Base<2> { +public: + V2A_Sound_Special_Zak110(uint16 offset1, uint16 size1, uint16 offset2, uint16 size2, uint16 freq1, uint16 freq2) : + _offset1(offset1), _size1(size1), _offset2(offset2), _size2(size2), _freq1(freq1), _freq2(freq2) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _loopnum = 0; + _vol = 0x1500; + _beepcount = 0; + } + virtual bool update() { + char *tmp_data; + assert(_id); + + int vol = (((_vol >> 7) & 0x7E) | ((_vol >> 15) & 0x01)); + _beepcount++; + + switch (_beepcount & 0x3) { + case 0: + _mod->stopChannel(_id | 0x000); + break; + case 1: + tmp_data = (char *)malloc(_size1); + memcpy(tmp_data, _data + _offset1, _size1); + _mod->startChannel(_id | 0x000, tmp_data, _size1, BASE_FREQUENCY / _freq1, vol, 0, _size1, -127); + break; + default: + _mod->setChannelVol(_id | 0x000, vol); + break; + } + + switch (_beepcount & 0x7) { + case 0: + _mod->stopChannel(_id | 0x100); + break; + case 1: + tmp_data = (char *)malloc(_size2); + memcpy(tmp_data, _data + _offset2, _size2); + _mod->startChannel(_id | 0x100, tmp_data, _size2, BASE_FREQUENCY / _freq2, vol, 0, _size2, 127); + break; + default: + _mod->setChannelVol(_id | 0x100, vol); + break; + } + + if (_loopnum == 0) { + _vol += 0x80; + if (_vol == 0x4000) { + _vol = 0x3F00; + _loopnum = 1; + } + } else if (_loopnum == 1) { + _vol -= 0x20; + if (_vol == 0x2000) + _loopnum = 2; + } + return true; + } +private: + const uint16 _offset1; + const uint16 _size1; + const uint16 _offset2; + const uint16 _size2; + const uint16 _freq1; + const uint16 _freq2; + + uint16 _loopnum; + uint16 _vol; + uint16 _beepcount; +}; + +// plays a stereo siren, sweeping up and down quickly several times before sweeping up slowly, stopping, and then going silent +// door orb sound in the Mars Face +class V2A_Sound_Special_Zak32 : public V2A_Sound_Base<2> { +public: + V2A_Sound_Special_Zak32(uint16 offset1, uint16 offset2, uint16 size1, uint16 size2) : + _offset1(offset1), _offset2(offset2), _size1(size1), _size2(size2) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _loopnum = 1; + _freqmod = -4; + _freq = 0x00C8; + + char *tmp_data1 = (char *)malloc(_size1); + char *tmp_data2 = (char *)malloc(_size1); + memcpy(tmp_data1, _data + _offset1, _size1); + memcpy(tmp_data2, _data + _offset1, _size1); + _mod->startChannel(_id | 0x000, tmp_data1, _size1, BASE_FREQUENCY / _freq, 0x7F, 0, _size1, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size1, BASE_FREQUENCY / (_freq + 3), 0x7F, 0, _size1, 127); + } + virtual bool update() { + assert(_id); + + if (_loopnum < 7) { + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq); + _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / (_freq + 3)); + _freq += _freqmod; + if (_freq <= 0x80) + _freqmod = -_freqmod; + else if (_freq >= 0xC8) { + _freqmod = -_freqmod; + _loopnum++; + if (_loopnum == 7) { + _freq = 0x00C8; + _freqmod = 2; + } + } + return true; + } else { + if (_loopnum == 7) { + _mod->stopChannel(_id | 0x000); + _mod->stopChannel(_id | 0x100); + + char *tmp_data1 = (char *)malloc(_size2); + char *tmp_data2 = (char *)malloc(_size2); + memcpy(tmp_data1, _data + _offset2, _size2); + memcpy(tmp_data2, _data + _offset2, _size2); + _mod->startChannel(_id | 0x000, tmp_data1, _size2, BASE_FREQUENCY / (_freq), 0x7F, 0, _size2, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size2, BASE_FREQUENCY / (_freq + 3), 0x7F, 0, _size2, 127); + _loopnum++; + } else { + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _freq); + _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / (_freq + 3)); + } + _freq -= _freqmod; + if (_freq > 0) + return true; + else + return false; + } + } +private: + const uint16 _offset1; + const uint16 _offset2; + const uint16 _size1; + const uint16 _size2; + + uint16 _loopnum; + int16 _freqmod; + uint16 _freq; +}; + +// plays a looped waveform, increasing frequency and reducing volume once the frequency reaches a certain point +// probably used for some sort of vehicle sound +class V2A_Sound_Special_Zak52 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Zak52(uint16 offset, uint16 size) : + _offset(offset), _size(size) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _curfreq = 0x0312; + + char *tmp_data = (char *)malloc(_size); + memcpy(tmp_data, _data + _offset, _size); + _mod->startChannel(_id | 0x000, tmp_data, _size, BASE_FREQUENCY / _curfreq, 0xFF, 0, _size, -127); + } + virtual bool update() { + assert(_id); + int vol = (_curfreq - 0xC8) >> 3; + if (vol > 0x3F) + vol = 0x3F; + vol = (vol << 2) | (vol >> 4); + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / _curfreq); + _mod->setChannelVol(_id | 0x000, vol); + _curfreq--; + if (_curfreq >= 0x107) + return true; + else + return false; + } +private: + const uint16 _offset; + const uint16 _size; + + uint16 _curfreq; +}; + +// plays a looped waveform, sweeping the frequency up while modulating it (alternating which channel updates) and fading volume out +// used when teleporting out with the yellow crystal +class V2A_Sound_Special_Zak61 : public V2A_Sound_Base<2> { +public: + V2A_Sound_Special_Zak61(uint16 offset, uint16 size) : + _offset(offset), _size(size) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _loop = 1; + _curfreq = 0x01F4; + + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + memcpy(tmp_data1, _data + _offset, _size); + memcpy(tmp_data2, _data + _offset, _size); + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _curfreq, 0x7F, 0, _size, -127); + // start 2nd channel silent + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _curfreq, 0, 0, _size, 127); + } + virtual bool update() { + assert(_id); + int freq = (_loop << 4) + _curfreq; + int vol = freq - 0x76; + if (vol > 0x3F) + vol = 0x3F; + vol = (vol << 1) | (vol >> 5); + switch (_loop) { + case 0: + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / freq); + _mod->setChannelVol(_id | 0x000, vol); + break; + case 1: + _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / freq); + _mod->setChannelVol(_id | 0x100, vol); + break; + } + _loop = (_loop + 1) & 3; + if (!_loop) { + _curfreq -= 4; + if (_curfreq <= 0x80) + return false; + } + return true; + } +private: + const uint16 _offset; + const uint16 _size; + + uint16 _loop; + uint16 _curfreq; +}; + +// just like Zak61, but sweeps frequency in the other direction +// used when teleporting in with the yellow crystal +class V2A_Sound_Special_Zak62 : public V2A_Sound_Base<2> { +public: + V2A_Sound_Special_Zak62(uint16 offset, uint16 size) : + _offset(offset), _size(size) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _loop = 1; + _curfreq = 0x0080; + + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + memcpy(tmp_data1, _data + _offset, _size); + memcpy(tmp_data2, _data + _offset, _size); + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _curfreq, 0x7F, 0, _size, -127); + // start 2nd channel silent + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _curfreq, 0, 0, _size, 127); + } + virtual bool update() { + assert(_id); + int freq = (_loop << 4) + _curfreq; + int vol = 0x0200 - freq; + if (vol > 0x3F) + vol = 0x3F; + vol = (vol << 1) | (vol >> 5); + switch (_loop) { + case 0: + _mod->setChannelFreq(_id | 0x000, BASE_FREQUENCY / freq); + _mod->setChannelVol(_id | 0x000, vol); + break; + case 1: + _mod->setChannelFreq(_id | 0x100, BASE_FREQUENCY / freq); + _mod->setChannelVol(_id | 0x100, vol); + break; + } + _loop = (_loop + 1) & 3; + if (!_loop) { + _curfreq += 4; + if (_curfreq >= 0x01F4) + return false; + } + return true; + } +private: + const uint16 _offset; + const uint16 _size; + + uint16 _loop; + uint16 _curfreq; +}; + +// plays a series of double-looped sounds at varying frequencies and delays, very specialized +// Guardian of the Sphinx, perhaps? +class V2A_Sound_Special_Zak82 : public V2A_Sound_Base<4> { +public: + V2A_Sound_Special_Zak82(uint16 offset, uint16 size) : + _offset(offset), _size(size) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + // Wait values were to insure playing an integral number of loops on each sample + // and have been adjusted to reflect the actual duration spent playing + _loop = 0; + _playctr = 240; + _wait1 = 76; // was 39, extended to loop twice + _wait2 = 10000; + _wait3 = 10000; + _wait4 = 10000; + + int size = 2000; + int offset = _offset; + assert(offset + size <= _offset + _size); + char *tmp_data = (char *)malloc(size); + memcpy(tmp_data, _data + offset, size); + _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0479, 0xFF, 0, size); + } + virtual bool update() { + assert(_id); + char *tmp_data1, *tmp_data2; + int size, offset = _offset; + + if (!--_wait1) { + _wait1 = 10000; + _mod->stopChannel(_id | 0x000); + } else if (!--_wait2) { + _wait2 = 10000; + _mod->stopChannel(_id | 0x000); + } else if (!--_wait3) { + _wait3 = 10000; + _mod->stopChannel(_id | 0x200); + } else if (!--_wait4) { + _wait4 = 10000; + _mod->stopChannel(_id | 0x100); + _mod->stopChannel(_id | 0x300); + } + if (--_playctr) + return true; + + switch (++_loop) { + case 1: + size = 6300; + offset += 0x07D0; + assert(offset + size <= _offset + _size); + tmp_data1 = (char *)malloc(size); + memcpy(tmp_data1, _data + offset, size); + _mod->startChannel(_id | 0x000, tmp_data1, size, BASE_FREQUENCY / 0x0479, 0x7F, 0, size, -127); + _wait2 = 241; // was 120, extended to loop twice + _playctr = 10; + break; + case 2: + size = 6292; + offset += 0x206C; + assert(offset + size <= _offset + _size); + tmp_data1 = (char *)malloc(size); + memcpy(tmp_data1, _data + offset, size); + _mod->startChannel(_id | 0x200, tmp_data1, size, BASE_FREQUENCY / 0x0384, 0x7F, 0, size, 127); + _wait3 = 189; // was 94, extended to loop twice + _playctr = 20; + break; + case 3: + size = 6300; + offset += 0x07D0; + assert(offset + size <= _offset + _size); + tmp_data1 = (char *)malloc(size); + tmp_data2 = (char *)malloc(size); + memcpy(tmp_data1, _data + offset, size); + memcpy(tmp_data2, _data + offset, size); + _mod->startChannel(_id | 0x100, tmp_data1, size, BASE_FREQUENCY / 0x01E0, 0x7F, 0, size, 127); + _mod->startChannel(_id | 0x300, tmp_data2, size, BASE_FREQUENCY / 0x01E0, 0x7F, 0, size, -127); + _wait4 = 101; // was 50, extended to loop twice + _playctr = 120; + break; + default: + return false; + } + return true; + } +private: + const uint16 _offset; + const uint16 _size; + + uint16 _loop; + uint16 _playctr; + uint16 _wait1; + uint16 _wait2; + uint16 _wait3; + uint16 _wait4; +}; + +// plays a "ding" (volume 0-max-0) followed by a sound sample, a pause, then loops again +// Mars Tram about to depart +class V2A_Sound_Special_Zak86 : public V2A_Sound_Base<1> { +public: + V2A_Sound_Special_Zak86(uint16 offset, uint16 size) : + _offset(offset), _size(size) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _mode = 0; + _vol = 0; + _volmod = 16; + + int size = 32; + int offset = _offset + 0x2B8E; + assert(offset + size <= _offset + _size); + char *tmp_data = (char *)malloc(size); + memcpy(tmp_data, _data + offset, size); + _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0096, 0, 0, size, 0); + } + virtual bool update() { + assert(_id); + int size, offset; + char *tmp_data; + + switch (_mode) { + case 0: + _mod->setChannelVol(_id | 0x000, (_vol << 2) | (_vol >> 4)); + if (_vol + _volmod > 0) { + _vol += _volmod; + if (_vol > 0x3F) { + _vol = 0x3F; + _volmod = -4; + } + return true; + } + _mod->stopChannel(_id | 0x000); + _mode = 1; + + size = 0x2B8E; + offset = _offset; + assert(offset + size <= _offset + _size); + tmp_data = (char *)malloc(size); + memcpy(tmp_data, _data + offset, size); + _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0152, 0x3F); + _volmod = 100; + break; + case 1: + if (!--_volmod) { + size = 32; + offset = _offset + 0x2B8E; + assert(offset + size <= _offset + _size); + tmp_data = (char *)malloc(size); + memcpy(tmp_data, _data + offset, size); + _mod->startChannel(_id | 0x000, tmp_data, size, BASE_FREQUENCY / 0x0096, 0, 0, size, 0); + _mode = 0; + _vol = 0; + _volmod = 16; + } + break; + } + return true; + } +private: + const uint16 _offset; + const uint16 _size; + + uint16 _mode; + uint16 _vol; + int16 _volmod; +}; + +// modulates volume on 4 samples, frequency on only 2 of them +// Skolarian device pedestal activated without any parts +class V2A_Sound_Special_Zak98 : public V2A_Sound_Base<4> { +public: + V2A_Sound_Special_Zak98(uint16 offset, uint16 size) : + _offset(offset), _size(size) { } + virtual void start(Player_MOD *mod, int id, const byte *data) { + _mod = mod; + _id = id; + _data = (char *)malloc(READ_LE_UINT16(data)); + memcpy(_data, data, READ_LE_UINT16(data)); + + _freq[0] = 0x1E0; + _freq[1] = 0x3E8; + _freq[2] = 0x200; + _freq[3] = 0x408; + _vol[0] = 0x3F; + _vol[1] = 0x3F; + _vol[2] = 0x3F; + _vol[3] = 0x3F; + _freqmod = 4; + _volmod[0] = -2; + _volmod[1] = -1; + + char *tmp_data1 = (char *)malloc(_size); + char *tmp_data2 = (char *)malloc(_size); + char *tmp_data3 = (char *)malloc(_size); + char *tmp_data4 = (char *)malloc(_size); + memcpy(tmp_data1, _data + _offset, _size); + memcpy(tmp_data2, _data + _offset, _size); + memcpy(tmp_data3, _data + _offset, _size); + memcpy(tmp_data4, _data + _offset, _size); + _mod->startChannel(_id | 0x000, tmp_data1, _size, BASE_FREQUENCY / _freq[0], _vol[0], 0, _size, -127); + _mod->startChannel(_id | 0x100, tmp_data2, _size, BASE_FREQUENCY / _freq[1], _vol[1], 0, _size, 127); + _mod->startChannel(_id | 0x200, tmp_data3, _size, BASE_FREQUENCY / _freq[2], _vol[2], 0, _size, 127); + _mod->startChannel(_id | 0x300, tmp_data4, _size, BASE_FREQUENCY / _freq[3], _vol[3], 0, _size, -127); + } + virtual bool update() { + assert(_id); + const uint16 _minvol[2] = {0x2E, 0x32}; + int i; + for (i = 0; i < 4; i++) { + _mod->setChannelFreq(_id | (i << 8), BASE_FREQUENCY / _freq[i]); + _mod->setChannelVol(_id | (i << 8), _vol[i]); + } + for (i = 0; i < 2; i++) { + _vol[i] += _volmod[i]; + if (_vol[i] > 0x3F) { + _vol[i] = 0x3F; + _volmod[i] = -_volmod[i]; + } else if (_vol[i] < _minvol[i]) { + _vol[i] = _minvol[i]; + _volmod[i] = -_volmod[i]; + } + _vol[i + 2] = _vol[i]; + } + _freq[0] += _freqmod; + if (_freq[0] > 0x2BC) { + _freq[0] = 0x2BC; + _freqmod = -_freqmod; + } else if (_freq[0] < 0x1E0) { + _freq[0] = 0x1E0; + _freqmod = -_freqmod; + } + _freq[2] = _freq[0] + 0x20; + return true; + } +private: + const uint16 _offset; + const uint16 _size; + + uint16 _freq[4]; + uint16 _vol[4]; + int16 _freqmod; + int16 _volmod[2]; +}; + +#define CRCToSound(CRC, SOUND) \ + if (crc == CRC) \ + return new SOUND + +static V2A_Sound *findSound(unsigned long crc) { + CRCToSound(0x8FAB08C4, V2A_Sound_SingleLooped(0x006C, 0x2B58, 0x016E, 0x3F)); // Maniac 17 + CRCToSound(0xB673160A, V2A_Sound_SingleLooped(0x006C, 0x1E78, 0x01C2, 0x1E)); // Maniac 38 + CRCToSound(0x4DB1D0B2, V2A_Sound_MultiLooped(0x0072, 0x1BC8, 0x023D, 0x3F, 0x0224, 0x3F)); // Maniac 20 + CRCToSound(0x754D75EF, V2A_Sound_Single(0x0076, 0x0738, 0x01FC, 0x3F)); // Maniac 10 + CRCToSound(0x6E3454AF, V2A_Sound_Single(0x0076, 0x050A, 0x017C, 0x3F)); // Maniac 12 + CRCToSound(0x92F0BBB6, V2A_Sound_Single(0x0076, 0x3288, 0x012E, 0x3F)); // Maniac 41 + CRCToSound(0xE1B13982, V2A_Sound_MultiLoopedDuration(0x0078, 0x0040, 0x007C, 0x3F, 0x007B, 0x3F, 0x001E)); // Maniac 21 + CRCToSound(0x288B16CF, V2A_Sound_MultiLoopedDuration(0x007A, 0x0040, 0x007C, 0x3F, 0x007B, 0x3F, 0x000A)); // Maniac 11 + CRCToSound(0xA7565268, V2A_Sound_MultiLoopedDuration(0x007A, 0x0040, 0x00F8, 0x3F, 0x00F7, 0x3F, 0x000A)); // Maniac 19 + CRCToSound(0x7D419BFC, V2A_Sound_MultiLoopedDuration(0x007E, 0x0040, 0x012C, 0x3F, 0x0149, 0x3F, 0x001E)); // Maniac 22 + CRCToSound(0x1B52280C, V2A_Sound_Single(0x0098, 0x0A58, 0x007F, 0x32)); // Maniac 6 + CRCToSound(0x38D4A810, V2A_Sound_Single(0x0098, 0x2F3C, 0x0258, 0x32)); // Maniac 7 + CRCToSound(0x09F98FC2, V2A_Sound_Single(0x0098, 0x0A56, 0x012C, 0x32)); // Maniac 16 + CRCToSound(0x90440A65, V2A_Sound_Single(0x0098, 0x0208, 0x0078, 0x28)); // Maniac 28 + CRCToSound(0x985C76EF, V2A_Sound_Single(0x0098, 0x0D6E, 0x00C8, 0x32)); // Maniac 30 + CRCToSound(0x76156137, V2A_Sound_Single(0x0098, 0x2610, 0x017C, 0x39)); // Maniac 39 + CRCToSound(0x5D95F88C, V2A_Sound_Single(0x0098, 0x0A58, 0x007F, 0x1E)); // Maniac 65 + CRCToSound(0x92D704EA, V2A_Sound_SingleLooped(0x009C, 0x29BC, 0x012C, 0x3F, 0x1BD4, 0x0DE8)); // Maniac 15 + CRCToSound(0x92F5513C, V2A_Sound_Single(0x009E, 0x0DD4, 0x01F4, 0x3F)); // Maniac 13 + CRCToSound(0xCC2F3B5A, V2A_Sound_Single(0x009E, 0x00DE, 0x01AC, 0x3F)); // Maniac 43 + CRCToSound(0x153207D3, V2A_Sound_Single(0x009E, 0x0E06, 0x02A8, 0x3F)); // Maniac 67 + CRCToSound(0xC4F370CE, V2A_Sound_Single(0x00AE, 0x0330, 0x01AC, 0x3F)); // Maniac 8 + CRCToSound(0x928C4BAC, V2A_Sound_Single(0x00AE, 0x08D6, 0x01AC, 0x3F)); // Maniac 9 + CRCToSound(0x62D5B11F, V2A_Sound_Single(0x00AE, 0x165C, 0x01CB, 0x3F)); // Maniac 27 + CRCToSound(0x3AB22CB5, V2A_Sound_Single(0x00AE, 0x294E, 0x012A, 0x3F)); // Maniac 62 + CRCToSound(0x2D70BBE9, V2A_Sound_SingleLoopedPitchbend(0x00B4, 0x1702, 0x03E8, 0x0190, 0x3F, 5)); // Maniac 64 + CRCToSound(0xFA4C1B1C, V2A_Sound_Special_Maniac69(0x00B2, 0x1702, 0x0190, 0x3F)); // Maniac 69 + CRCToSound(0x19D50D67, V2A_Sound_Special_ManiacDing(0x00B6, 0x0020, 0x00C8, 16, 2)); // Maniac 14 + CRCToSound(0x3E6FBE15, V2A_Sound_Special_ManiacTentacle(0x00B2, 0x0010, 0x007C, 0x016D, 1)); // Maniac 25 + CRCToSound(0x5305753C, V2A_Sound_Special_ManiacTentacle(0x00B2, 0x0010, 0x007C, 0x016D, 7)); // Maniac 36 + CRCToSound(0x28895106, V2A_Sound_Special_Maniac59(0x00C0, 0x00FE, 0x00E9, 0x0111, 4, 0x0A)); // Maniac 59 + CRCToSound(0xB641ACF6, V2A_Sound_Special_Maniac61(0x00C8, 0x0100, 0x00C8, 0x01C2)); // Maniac 61 + CRCToSound(0xE1A91583, V2A_Sound_Special_ManiacPhone(0x00D0, 0x0040, 0x007C, 0x3F, 0x007B, 0x3F, 0x3C, 5, 6)); // Maniac 23 + CRCToSound(0x64816ED5, V2A_Sound_Special_ManiacPhone(0x00D0, 0x0040, 0x00BE, 0x37, 0x00BD, 0x37, 0x3C, 5, 6)); // Maniac 24 + CRCToSound(0x639D72C2, V2A_Sound_Special_Maniac46(0x00D0, 0x10A4, 0x0080, 0x3F, 0x28, 3)); // Maniac 46 + CRCToSound(0xE8826D92, V2A_Sound_Special_ManiacTypewriter(0x00EC, 0x025A, 0x023C, 0x3F, 8, (const uint8 *)"\x20\x41\x04\x21\x08\x10\x13\x07", true)); // Maniac 45 + CRCToSound(0xEDFF3D41, V2A_Sound_Single(0x00F8, 0x2ADE, 0x01F8, 0x3F)); // Maniac 42 (this should echo, but it's barely noticeable and I don't feel like doing it) + CRCToSound(0x15606D06, V2A_Sound_Special_Maniac32(0x0148, 0x0020, 0x0168, 0x0020, 0x3F)); // Maniac 32 + CRCToSound(0x753EAFE3, V2A_Sound_Special_Maniac44(0x017C, 0x0010, 0x018C, 0x0020, 0x00C8, 0x0080, 0x3F)); // Maniac 44 + CRCToSound(0xB1AB065C, V2A_Sound_Music(0x0032, 0x00B2, 0x08B2, 0x1222, 0x1A52, 0x23C2, 0x3074, false)); // Maniac 50 + CRCToSound(0x091F5D9C, V2A_Sound_Music(0x0032, 0x0132, 0x0932, 0x1802, 0x23D2, 0x3EA2, 0x4F04, false)); // Maniac 58 + + CRCToSound(0x8E2C8AB3, V2A_Sound_SingleLooped(0x005C, 0x0F26, 0x0168, 0x3C)); // Zak 41 + CRCToSound(0x3792071F, V2A_Sound_SingleLooped(0x0060, 0x1A18, 0x06A4, 0x3F)); // Zak 88 + CRCToSound(0xF192EDE9, V2A_Sound_SingleLooped(0x0062, 0x0054, 0x01FC, 0x1E)); // Zak 68 + CRCToSound(0xC43B0245, V2A_Sound_Special_Zak70(0x006C, 0x166E, 0x00C8, 0x0190, 0x0320, 0x0640, 0x32)); // Zak 70 + CRCToSound(0xCEB51670, V2A_Sound_SingleLooped(0x00AC, 0x26DC, 0x012C, 0x3F)); // Zak 42 + CRCToSound(0x10347B51, V2A_Sound_SingleLooped(0x006C, 0x00E0, 0x0594, 0x3F)); // Zak 18 + CRCToSound(0x9D2FADC0, V2A_Sound_MultiLooped(0x0072, 0x1FC8, 0x016A, 0x3F, 0x01CE, 0x3F)); // Zak 80 + CRCToSound(0xFAD2C676, V2A_Sound_MultiLooped(0x0076, 0x0010, 0x0080, 0x3F, 0x0090, 0x3B)); // Zak 40 + CRCToSound(0x01508B48, V2A_Sound_Single(0x0076, 0x0D8C, 0x017C, 0x3F)); // Zak 90 + CRCToSound(0x9C18DC46, V2A_Sound_Single(0x0076, 0x0D8C, 0x015E, 0x3F)); // Zak 91 + CRCToSound(0xF98F7EAC, V2A_Sound_Single(0x0076, 0x0D8C, 0x0140, 0x3F)); // Zak 92 + CRCToSound(0xC925FBEF, V2A_Sound_MultiLoopedDuration(0x0080, 0x0010, 0x0080, 0x3F, 0x0090, 0x3B, 0x0168)); // Zak 53 + CRCToSound(0xCAB35257, V2A_Sound_Special_Zak101(0x00DA, 0x425C, 0x023C, 0x08F0, 0x0640, 0x0478, 0x3F, 0x012C)); // Zak 101 + CRCToSound(0xA31FE4FD, V2A_Sound_Single(0x0094, 0x036A, 0x00E1, 0x3F)); // Zak 97 + CRCToSound(0x0A1AE0F5, V2A_Sound_Single(0x009E, 0x0876, 0x0168, 0x3F)); // Zak 5 + CRCToSound(0xD01A66CB, V2A_Sound_Single(0x009E, 0x04A8, 0x0168, 0x3F)); // Zak 47 + CRCToSound(0x5497B912, V2A_Sound_Single(0x009E, 0x0198, 0x01F4, 0x3F)); // Zak 39 + CRCToSound(0x2B50362F, V2A_Sound_Single(0x009E, 0x09B6, 0x023D, 0x3F)); // Zak 67 + CRCToSound(0x7BFB6E72, V2A_Sound_Single(0x009E, 0x0D14, 0x0078, 0x3F)); // Zak 69 + CRCToSound(0xB803A792, V2A_Sound_Single(0x009E, 0x2302, 0x02BC, 0x3F)); // Zak 78 + CRCToSound(0x7AB82E39, V2A_Sound_SingleLooped(0x00A0, 0x2A3C, 0x016E, 0x3F, 0x1018, 0x1A24)); // Zak 100 + CRCToSound(0x28057CEC, V2A_Sound_Single(0x0098, 0x0FEC, 0x0140, 0x32)); // Zak 63 + CRCToSound(0x1180A2FC, V2A_Sound_Single(0x0098, 0x0F06, 0x0190, 0x32)); // Zak 64 + CRCToSound(0x12616755, V2A_Sound_Single(0x0098, 0x14C8, 0x023C, 0x14)); // Zak 9 + CRCToSound(0x642723AA, V2A_Sound_Special_Zak37(0x00A2, 0x1702, 0x01F4, 0x3F)); // Zak 37 + CRCToSound(0xDEE56848, V2A_Sound_Single(0x009A, 0x0F86, 0x0100, 0x3F)); // Zak 93 + CRCToSound(0xF9BE27B8, V2A_Sound_Special_Zak37(0x011C, 0x1704, 0x0228, 0x3F)); // Zak 113 + CRCToSound(0xC73487B2, V2A_Sound_Single(0x00B0, 0x18BA, 0x0478, 0x3F)); // Zak 81 + CRCToSound(0x32D8F925, V2A_Sound_Single(0x00B0, 0x2E46, 0x00F0, 0x3F)); // Zak 94 + CRCToSound(0x988C83A5, V2A_Sound_Single(0x00B0, 0x0DE0, 0x025B, 0x3F)); // Zak 106 + CRCToSound(0x8F1E3B3D, V2A_Sound_Single(0x00B0, 0x05FE, 0x04E2, 0x3F)); // Zak 107 + CRCToSound(0x0A2A7646, V2A_Sound_Single(0x00B0, 0x36FE, 0x016E, 0x3F)); // Zak 43 + CRCToSound(0x6F1FC435, V2A_Sound_Single(0x00B0, 0x2808, 0x044C, 0x3F)); // Zak 108 + CRCToSound(0x870EFC29, V2A_Sound_SingleLoopedPitchbend(0x00BA, 0x0100, 0x03E8, 0x00C8, 0x3F, 3)); // Zak 55 + CRCToSound(0xED773699, V2A_Sound_Special_ManiacDing(0x00B4, 0x0020, 0x012C, 8, 4)); // Zak 3 + CRCToSound(0x0BF59774, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x00F8, 0x00F7, 8, 1)); // Zak 72 + CRCToSound(0x656FFEDE, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x00C4, 0x00C3, 8, 1)); // Zak 73 + CRCToSound(0xFC4D41E5, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x00A5, 0x00A4, 8, 1)); // Zak 74 + CRCToSound(0xC0DD2089, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x009C, 0x009B, 8, 1)); // Zak 75 + CRCToSound(0x627DFD92, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x008B, 0x008A, 8, 1)); // Zak 76 + CRCToSound(0x703E05C1, V2A_Sound_Special_ZakStereoDing(0x00BE, 0x0020, 0x007C, 0x007B, 8, 1)); // Zak 77 + CRCToSound(0xB0F77006, V2A_Sound_Special_Zak52(0x00B0, 0x01BC)); // Zak 52 + CRCToSound(0x5AE9D6A7, V2A_Sound_Special_ZakAirplane(0x00CA, 0x22A4, 0x0113, 0x0227)); // Zak 109 + CRCToSound(0xABE0D3B0, V2A_Sound_Special_ZakAirplane(0x00CE, 0x22A4, 0x0227, 0x0113)); // Zak 105 + CRCToSound(0x788CC749, V2A_Sound_Special_Zak71(0x00C8, 0x0B37)); // Zak 71 + CRCToSound(0x2E2AB1FA, V2A_Sound_Special_Zak99(0x00D4, 0x04F0, 0x0FE3, 0x0080, 0x3F)); // Zak 99 + CRCToSound(0x1304CF20, V2A_Sound_Special_ManiacTypewriter(0x00DC, 0x0624, 0x023C, 0x3C, 2, (const uint8 *)"\x14\x11", false)); // Zak 79 + CRCToSound(0xAE68ED91, V2A_Sound_Special_Zak54(0x00D4, 0x1A25, 0x1E1E, 0x0B80, 0x01F4)); // Zak 54 + CRCToSound(0xA4F40F97, V2A_Sound_Special_Zak61(0x00E4, 0x0020)); // Zak 61 + CRCToSound(0x348F85CE, V2A_Sound_Special_Zak62(0x00E4, 0x0020)); // Zak 62 + CRCToSound(0xD473AB86, V2A_Sound_Special_ManiacTypewriter(0x0122, 0x03E8, 0x00BE, 0x3F, 7, (const uint8 *)"\x0F\x0B\x04\x0F\x1E\x0F\x66", false)); // Zak 46 + CRCToSound(0x84A0BA90, V2A_Sound_Special_Zak110(0x0126, 0x0040, 0x0136, 0x0080, 0x007C, 0x0087)); // Zak 110 + CRCToSound(0x92680D9F, V2A_Sound_Special_Zak32(0x0140, 0x0150, 0x0010, 0x0010)); // Zak 32 + CRCToSound(0xABFFDB02, V2A_Sound_Special_Zak86(0x01A2, 0x2BAE)); // Zak 86 + CRCToSound(0x41045447, V2A_Sound_Special_Zak98(0x017A, 0x0020)); // Zak 98 + CRCToSound(0xC8EEBD34, V2A_Sound_Special_Zak82(0x01A6, 0x3900)); // Zak 82 + CRCToSound(0x42F9469F, V2A_Sound_Music(0x05F6, 0x0636, 0x0456, 0x0516, 0x05D6, 0x05E6, 0x0A36, true)); // Zak 96 + CRCToSound(0x038BBD78, V2A_Sound_Music(0x054E, 0x05CE, 0x044E, 0x04BE, 0x052E, 0x053E, 0x0BCE, true)); // Zak 85 + CRCToSound(0x06FFADC5, V2A_Sound_Music(0x0626, 0x0686, 0x0446, 0x04F6, 0x0606, 0x0616, 0x0C86, true)); // Zak 87 + CRCToSound(0xCE20ECF0, V2A_Sound_Music(0x0636, 0x0696, 0x0446, 0x0576, 0x0616, 0x0626, 0x0E96, true)); // Zak 114 + CRCToSound(0xBDA01BB6, V2A_Sound_Music(0x0678, 0x06B8, 0x0458, 0x0648, 0x0658, 0x0668, 0x0EB8, false)); // Zak 33 + CRCToSound(0x59976529, V2A_Sound_Music(0x088E, 0x092E, 0x048E, 0x05EE, 0x074E, 0x07EE, 0x112E, true)); // Zak 49 + CRCToSound(0xED1EED02, V2A_Sound_Music(0x08D0, 0x0950, 0x0440, 0x07E0, 0x08B0, 0x08C0, 0x1350, false)); // Zak 112 + CRCToSound(0x5A16C037, V2A_Sound_Music(0x634A, 0x64CA, 0x049A, 0x18FA, 0x398A, 0x511A, 0x6CCA, false)); // Zak 95 + return NULL; +} + +Player_V2A::Player_V2A(ScummEngine *scumm, Audio::Mixer *mixer) { + int i; + _vm = scumm; + + InitCRC(); + + for (i = 0; i < V2A_MAXSLOTS; i++) { + _slot[i].id = 0; + _slot[i].sound = NULL; + } + + _mod = new Player_MOD(mixer); + _mod->setUpdateProc(update_proc, this, 60); +} + +Player_V2A::~Player_V2A() { + delete _mod; +} + +void Player_V2A::setMusicVolume(int vol) { + _mod->setMusicVolume(vol); +} + +int Player_V2A::getSoundSlot(int id) const { + int i; + for (i = 0; i < V2A_MAXSLOTS; i++) { + if (_slot[i].id == id) + break; + } + if (i == V2A_MAXSLOTS) { + if (id == 0) + warning("player_v2a - out of sound slots"); + return -1; + } + return i; +} + +void Player_V2A::stopAllSounds() { + for (int i = 0; i < V2A_MAXSLOTS; i++) { + if (!_slot[i].id) + continue; + _slot[i].sound->stop(); + delete _slot[i].sound; + _slot[i].sound = NULL; + _slot[i].id = 0; + } +} + +void Player_V2A::stopSound(int nr) { + int i; + if (nr == 0) + return; + i = getSoundSlot(nr); + if (i == -1) + return; + _slot[i].sound->stop(); + delete _slot[i].sound; + _slot[i].sound = NULL; + _slot[i].id = 0; +} + +void Player_V2A::startSound(int nr) { + assert(_vm); + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + uint32 crc = GetCRC(data + 0x0A, READ_BE_UINT16(data + 0x08)); + V2A_Sound *snd = findSound(crc); + if (snd == NULL) { + warning("player_v2a - sound %i not recognized yet (crc %08X)", nr, crc); + return; + } + stopSound(nr); + int i = getSoundSlot(); + if (i == -1) { + delete snd; + return; + } + _slot[i].id = nr; + _slot[i].sound = snd; + _slot[i].sound->start(_mod, nr, data); +} + +void Player_V2A::update_proc(void *param) { + ((Player_V2A *)param)->updateSound(); +} + +void Player_V2A::updateSound() { + int i; + for (i = 0; i < V2A_MAXSLOTS; i++) { + if ((_slot[i].id) && (!_slot[i].sound->update())) { + _slot[i].sound->stop(); + delete _slot[i].sound; + _slot[i].sound = NULL; + _slot[i].id = 0; + } + } +} + +int Player_V2A::getMusicTimer() { + return 0; // FIXME - need to keep track of playing music resources +} + +int Player_V2A::getSoundStatus(int nr) const { + for (int i = 0; i < V2A_MAXSLOTS; i++) { + if (_slot[i].id == nr) + return 1; + } + return 0; +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v2a.h b/engines/scumm/players/player_v2a.h new file mode 100644 index 0000000000..12193635f2 --- /dev/null +++ b/engines/scumm/players/player_v2a.h @@ -0,0 +1,73 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V2A_H +#define SCUMM_PLAYERS_PLAYER_V2A_H + +#include "common/scummsys.h" +#include "scumm/music.h" +#include "scumm/players/player_mod.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; +class V2A_Sound; + +/** + * Scumm V2 Amiga sound/music driver. + */ +class Player_V2A : public MusicEngine { +public: + Player_V2A(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_V2A(); + + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + +private: + enum { + V2A_MAXSLOTS = 8 + }; + + struct soundSlot { + int id; + V2A_Sound *sound; + }; + + ScummEngine *_vm; + Player_MOD *_mod; + soundSlot _slot[V2A_MAXSLOTS]; + + int getSoundSlot(int id = 0) const; + static void update_proc(void *param); + void updateSound(); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v2base.cpp b/engines/scumm/players/player_v2base.cpp new file mode 100644 index 0000000000..75f1518989 --- /dev/null +++ b/engines/scumm/players/player_v2base.cpp @@ -0,0 +1,654 @@ +/* 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_v2base.h" +#include "scumm/scumm.h" + +#define FREQ_HZ 236 // Don't change! + +#define MAX_OUTPUT 0x7fff + +namespace Scumm { + +const uint8 note_lengths[] = { + 0, + 0, 0, 2, + 0, 3, 4, + 5, 6, 8, + 9, 12, 16, + 18, 24, 32, + 36, 48, 64, + 72, 96 +}; + +static const uint16 hull_offsets[] = { + 0, 12, 24, 36, 48, 60, + 72, 88, 104, 120, 136, 256, + 152, 164, 180 +}; + +static const int16 hulls[] = { + // hull 0 + 3, -1, 0, 0, 0, 0, 0, 0, + 0, -1, 0, 0, + // hull 1 (staccato) + 3, -1, 0, 32, 0, -1, 0, 0, + 0, -1, 0, 0, + // hull 2 (legato) + 3, -1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + // hull 3 (staccatissimo) + 3, -1, 0, 2, 0, -1, 0, 0, + 0, -1, 0, 0, + // hull 4 + 3, -1, 0, 6, 0, -1, 0, 0, + 0, -1, 0, 0, + // hull 5 + 3, -1, 0, 16, 0, -1, 0, 0, + 0, -1, 0, 0, + // hull 6 + (int16) 60000, -1, -1000, 20, 0, 0, 0, 0, + (int16) 40000, -1, -5000, 5, 0, -1, 0, 0, + // hull 7 + (int16) 50000, -1, 0, 8, 30000, -1, 0, 0, + 28000, -1, -5000, 5, 0, -1, 0, 0, + // hull 8 + (int16) 60000, -1, -2000, 16, 0, 0, 0, 0, + 28000, -1, -6000, 5, 0, -1, 0, 0, + // hull 9 + (int16) 55000, -1, 0, 8, (int16) 35000, -1, 0, 0, + (int16) 40000, -1, -2000, 10, 0, -1, 0, 0, + // hull 10 + (int16) 60000, -1, 0, 4, -2000, 8, 0, 0, + (int16) 40000, -1, -6000, 5, 0, -1, 0, 0, + // hull 12 + 0, -1, 150, 340, -150, 340, 0, -1, + 0, -1, 0, 0, + // hull 13 == 164 + 20000, -1, 4000, 7, 1000, 15, 0, 0, + (int16) 35000, -1, -2000, 15, 0, -1, 0, 0, + + // hull 14 == 180 + (int16) 35000, -1, 500, 20, 0, 0, 0, 0, + (int16) 45000, -1, -500, 60, 0, -1, 0, 0, + + // hull misc = 196 + (int16) 44000, -1, -4400, 10, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 53000, -1, -5300, 10, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 63000, -1, -6300, 10, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 44000, -1, -1375, 32, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 53000, -1, -1656, 32, 0, -1, 0, 0, + 0, -1, 0, 0, + + // hull 11 == 256 + (int16) 63000, -1, -1968, 32, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 44000, -1, - 733, 60, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 53000, -1, - 883, 60, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 63000, -1, -1050, 60, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 44000, -1, - 488, 90, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 53000, -1, - 588, 90, 0, -1, 0, 0, + 0, -1, 0, 0, + + (int16) 63000, -1, - 700, 90, 0, -1, 0, 0, + 0, -1, 0, 0 +}; + +static const uint16 freqmod_lengths[] = { + 0x1000, 0x1000, 0x20, 0x2000, 0x1000 +}; + +static const uint16 freqmod_offsets[] = { + 0, 0x100, 0x200, 0x302, 0x202 +}; + +static const int8 freqmod_table[0x502] = { + 0, 3, 6, 9, 12, 15, 18, 21, + 24, 27, 30, 33, 36, 39, 42, 45, + 48, 51, 54, 57, 59, 62, 65, 67, + 70, 73, 75, 78, 80, 82, 85, 87, + 89, 91, 94, 96, 98, 100, 102, 103, + 105, 107, 108, 110, 112, 113, 114, 116, + 117, 118, 119, 120, 121, 122, 123, 123, + 124, 125, 125, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 125, 125, + 124, 123, 123, 122, 121, 120, 119, 118, + 117, 116, 114, 113, 112, 110, 108, 107, + 105, 103, 102, 100, 98, 96, 94, 91, + 89, 87, 85, 82, 80, 78, 75, 73, + 70, 67, 65, 62, 59, 57, 54, 51, + 48, 45, 42, 39, 36, 33, 30, 27, + 24, 21, 18, 15, 12, 9, 6, 3, + 0, -3, -6, -9, -12, -15, -18, -21, + -24, -27, -30, -33, -36, -39, -42, -45, + -48, -51, -54, -57, -59, -62, -65, -67, + -70, -73, -75, -78, -80, -82, -85, -87, + -89, -91, -94, -96, -98,-100,-102,-103, + -105,-107,-108,-110,-112,-113,-114,-116, + -117,-118,-119,-120,-121,-122,-123,-123, + -124,-125,-125,-126,-126,-126,-126,-126, + -126,-126,-126,-126,-126,-126,-125,-125, + -124,-123,-123,-122,-121,-120,-119,-118, + -117,-116,-114,-113,-112,-110,-108,-107, + -105,-103,-102,-100, -98, -96, -94, -91, + -89, -87, -85, -82, -80, -78, -75, -73, + -70, -67, -65, -62, -59, -57, -54, -51, + -48, -45, -42, -39, -36, -33, -30, -27, + -24, -21, -18, -15, -12, -9, -6, -3, + + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, + -128,-127,-126,-125,-124,-123,-122,-121, + -120,-119,-118,-117,-116,-115,-114,-113, + -112,-111,-110,-109,-108,-107,-106,-105, + -104,-103,-102,-101,-100, -99, -98, -97, + -96, -95, -94, -93, -92, -91, -90, -89, + -88, -87, -86, -85, -84, -83, -82, -81, + -80, -79, -78, -77, -76, -75, -74, -73, + -72, -71, -70, -69, -68, -67, -66, -65, + -64, -63, -62, -61, -60, -59, -58, -57, + -56, -55, -54, -53, -52, -51, -50, -49, + -48, -47, -46, -45, -44, -43, -42, -41, + -40, -39, -38, -37, -36, -35, -34, -33, + -32, -31, -30, -29, -28, -27, -26, -25, + -24, -23, -22, -21, -20, -19, -18, -17, + -16, -15, -14, -13, -12, -11, -10, -9, + -8, -7, -6, -5, -4, -3, -2, -1, + + -120, 120, + + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + -120,-120,-120,-120,-120,-120,-120,-120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 120, 120, + + 41, 35, -66,-124, -31, 108, -42, -82, + 82,-112, 73, -15, -15, -69, -23, -21, + -77, -90, -37, 60,-121, 12, 62,-103, + 36, 94, 13, 28, 6, -73, 71, -34, + -77, 18, 77, -56, 67, -69,-117, -90, + 31, 3, 90, 125, 9, 56, 37, 31, + 93, -44, -53, -4,-106, -11, 69, 59, + 19, 13,-119, 10, 28, -37, -82, 50, + 32,-102, 80, -18, 64, 120, 54, -3, + 18, 73, 50, -10, -98, 125, 73, -36, + -83, 79, 20, -14, 68, 64, 102, -48, + 107, -60, 48, -73, 50, 59, -95, 34, + -10, 34,-111, -99, -31,-117, 31, -38, + -80, -54,-103, 2, -71, 114, -99, 73, + 44,-128, 126, -59,-103, -43, -23,-128, + -78, -22, -55, -52, 83, -65, 103, -42, + -65, 20, -42, 126, 45, -36,-114, 102, + -125, -17, 87, 73, 97, -1, 105,-113, + 97, -51, -47, 30, -99,-100, 22, 114, + 114, -26, 29, -16,-124, 79, 74, 119, + 2, -41, -24, 57, 44, 83, -53, -55, + 18, 30, 51, 116, -98, 12, -12, -43, + -44, -97, -44, -92, 89, 126, 53, -49, + 50, 34, -12, -52, -49, -45,-112, 45, + 72, -45,-113, 117, -26, -39, 29, 42, + -27, -64, -9, 43, 120,-127,-121, 68, + 14, 95, 80, 0, -44, 97,-115, -66, + 123, 5, 21, 7, 59, 51,-126, 31, + 24, 112,-110, -38, 100, 84, -50, -79, + -123, 62, 105, 21, -8, 70, 106, 4, + -106, 115, 14, -39, 22, 47, 103, 104, + -44, -9, 74, 74, -48, 87, 104, 118, + -6, 22, -69, 17, -83, -82, 36,-120, + 121, -2, 82, -37, 37, 67, -27, 60, + -12, 69, -45, -40, 40, -50, 11, -11, + -59, 96, 89, 61,-105, 39,-118, 89, + 118, 45, -48, -62, -55, -51, 104, -44, + 73, 106, 121, 37, 8, 97, 64, 20, + -79, 59, 106, -91, 17, 40, -63,-116, + -42, -87, 11,-121,-105,-116, 47, -15, + 21, 29,-102,-107, -63,-101, -31, -64, + 126, -23, -88,-102, -89,-122, -62, -75, + 84, -65,-102, -25, -39, 35, -47, 85, + -112, 56, 40, -47, -39, 108, -95, 102, + 94, 78, -31, 48,-100, -2, -39, 113, + -97, -30, -91, -30, 12,-101, -76, 71, + 101, 56, 42, 70,-119, -87,-126, 121, + 122, 118, 120, -62, 99, -79, 38, -33, + -38, 41, 109, 62, 98, -32,-106, 18, + 52, -65, 57, -90, 63,-119, 94, -15, + 109, 14, -29, 108, 40, -95, 30, 32, + 29, -53, -62, 3, 63, 65, 7,-124, + 15, 20, 5, 101, 27, 40, 97, -55, + -59, -25, 44,-114, 70, 54, 8, -36, + -13, -88,-115, -2, -66, -14, -21, 113, + -1, -96, -48, 59, 117, 6,-116, 126, + -121, 120, 115, 77, -48, -66,-126, -66, + -37, -62, 70, 65, 43,-116, -6, 48, + 127, 112, -16, -89, 84,-122, 50,-107, + -86, 91, 104, 19, 11, -26, -4, -11, + -54, -66, 125, -97,-119,-118, 65, 27, + -3, -72, 79, 104, -10, 114, 123, 20, + -103, -51, -45, 13, -16, 68, 58, -76, + -90, 102, 83, 51, 11, -53, -95, 16 +}; + +static const uint16 spk_freq_table[12] = { + 36484, 34436, 32503, 30679, 28957, 27332, + 25798, 24350, 22983, 21693, 20476, 19326 +}; + +static const uint16 pcjr_freq_table[12] = { + 65472, 61760, 58304, 55040, 52032, 49024, + 46272, 43648, 41216, 38912, 36736, 34624 +}; + + +Player_V2Base::Player_V2Base(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr) + : _vm(scumm), + _mixer(mixer), + _pcjr(pcjr), + _sampleRate(_mixer->getOutputRate()) { + + _isV3Game = (scumm->_game.version >= 3); + + _header_len = (scumm->_game.features & GF_OLD_BUNDLE) ? 4 : 6; + + // Initialize sound queue + _current_nr = _next_nr = 0; + _current_data = _next_data = 0; + + // Initialize channel code + for (int i = 0; i < 4; ++i) + clear_channel(i); + + _next_tick = 0; + _tick_len = (_sampleRate << FIXP_SHIFT) / FREQ_HZ; + + // Initialize V3 music timer + _music_timer_ctr = _music_timer = 0; + _ticks_per_music_timer = 65535; + + if (_pcjr) { + _freqs_table = pcjr_freq_table; + } else { + _freqs_table = spk_freq_table; + } +} + +Player_V2Base::~Player_V2Base() { +} + +void Player_V2Base::chainSound(int nr, byte *data) { + int offset = _header_len + (_pcjr ? 10 : 2); + + _current_nr = nr; + _current_data = data; + + for (int i = 0; i < 4; i++) { + clear_channel(i); + + _channels[i].d.music_script_nr = nr; + if (data) { + _channels[i].d.next_cmd = READ_LE_UINT16(data + offset + 2 * i); + if (_channels[i].d.next_cmd) { + _channels[i].d.time_left = 1; + } + } + } + _music_timer = 0; +} + +void Player_V2Base::chainNextSound() { + if (_next_nr) { + chainSound(_next_nr, _next_data); + _next_nr = 0; + _next_data = 0; + } +} + +// TODO: Merge stopAllSounds, stopSound() and startSound(), using some overriding +// perhaps? Player_V2CMS's implementations start like that of Player_V2 +// but then add some MIDI related stuff. + +void Player_V2Base::clear_channel(int i) { + ChannelInfo *channel = &_channels[i]; + memset(channel, 0, sizeof(ChannelInfo)); +} + +int Player_V2Base::getMusicTimer() { + if (_isV3Game) + return _music_timer; + else + return _channels[0].d.music_timer; +} + +void Player_V2Base::execute_cmd(ChannelInfo *channel) { + uint16 value; + int16 offset; + uint8 *script_ptr; + ChannelInfo * current_channel; + ChannelInfo * dest_channel; + + current_channel = channel; + + if (channel->d.next_cmd == 0) + goto check_stopped; + script_ptr = &_current_data[channel->d.next_cmd]; + + for (;;) { + uint8 opcode = *script_ptr++; + if (opcode >= 0xf8) { + switch (opcode) { + case 0xf8: // set hull curve + debug(7, "channels[%d]: hull curve %2d", + (uint)(channel - _channels), *script_ptr); + channel->d.hull_curve = hull_offsets[*script_ptr / 2]; + script_ptr++; + break; + + case 0xf9: // set freqmod curve + debug(7, "channels[%d]: freqmod curve %2d", + (uint)(channel - _channels), *script_ptr); + channel->d.freqmod_table = freqmod_offsets[*script_ptr / 4]; + channel->d.freqmod_modulo = freqmod_lengths[*script_ptr / 4]; + script_ptr++; + break; + + case 0xfd: // clear other channel + value = READ_LE_UINT16 (script_ptr) / sizeof (ChannelInfo); + debug(7, "clear channel %d", value); + script_ptr += 2; + // In Indy3, when traveling to Venice a command is + // issued to clear channel 4. So we introduce a 4th + // channel, which is never used. All OOB accesses are + // mapped to this channel. + // + // The original game had room for 8 channels, but only + // channels 0-3 are read, changes to other channels + // had no effect. + if (value >= ARRAYSIZE (_channels)) + value = 4; + channel = &_channels[value]; + // fall through + + case 0xfa: // clear current channel + if (opcode == 0xfa) + debug(7, "clear channel"); + channel->d.next_cmd = 0; + channel->d.base_freq = 0; + channel->d.freq_delta = 0; + channel->d.freq = 0; + channel->d.volume = 0; + channel->d.volume_delta = 0; + channel->d.inter_note_pause = 0; + channel->d.transpose = 0; + channel->d.hull_curve = 0; + channel->d.hull_offset = 0; + channel->d.hull_counter = 0; + channel->d.freqmod_table = 0; + channel->d.freqmod_offset = 0; + channel->d.freqmod_incr = 0; + channel->d.freqmod_multiplier = 0; + channel->d.freqmod_modulo = 0; + break; + + case 0xfb: // ret from subroutine + debug(7, "ret from sub"); + script_ptr = _retaddr; + break; + + case 0xfc: // call subroutine + offset = READ_LE_UINT16 (script_ptr); + debug(7, "subroutine %d", offset); + script_ptr += 2; + _retaddr = script_ptr; + script_ptr = _current_data + offset; + break; + + case 0xfe: // loop music + opcode = *script_ptr++; + offset = READ_LE_UINT16 (script_ptr); + script_ptr += 2; + debug(7, "loop if %d to %d", opcode, offset); + if (!channel->array[opcode / 2] || --channel->array[opcode/2]) + script_ptr += offset; + break; + + case 0xff: // set parameter + opcode = *script_ptr++; + value = READ_LE_UINT16 (script_ptr); + channel->array[opcode / 2] = value; + debug(7, "channels[%d]: set param %2d = %5d", + (uint)(channel - _channels), opcode, value); + script_ptr += 2; + if (opcode == 14) { + /* tempo var */ + _ticks_per_music_timer = 125; + } + if (opcode == 0) + goto end; + break; + } + } else { // opcode < 0xf8 + for (;;) { + int16 note, octave; + int is_last_note; + dest_channel = &_channels[(opcode >> 5) & 3]; + + if (!(opcode & 0x80)) { + + int tempo = channel->d.tempo; + if (!tempo) + tempo = 1; + channel->d.time_left = tempo * note_lengths[opcode & 0x1f]; + + note = *script_ptr++; + is_last_note = note & 0x80; + note &= 0x7f; + if (note == 0x7f) { + debug(8, "channels[%d]: pause %d", + (uint)(channel - _channels), channel->d.time_left); + goto end; + } + } else { + + channel->d.time_left = ((opcode & 7) << 8) | *script_ptr++; + + if ((opcode & 0x10)) { + debug(8, "channels[%d]: pause %d", + (uint)(channel - _channels), channel->d.time_left); + goto end; + } + + is_last_note = 0; + note = (*script_ptr++) & 0x7f; + } + + debug(8, "channels[%d]: @%04x note: %3d+%d len: %2d hull: %d mod: %d/%d/%d %s", + (uint)(dest_channel - channel), script_ptr ? (uint)(script_ptr - _current_data - 2) : 0, + note, (signed short) dest_channel->d.transpose, channel->d.time_left, + dest_channel->d.hull_curve, dest_channel->d.freqmod_table, + dest_channel->d.freqmod_incr,dest_channel->d.freqmod_multiplier, + is_last_note ? "last":""); + + uint16 myfreq; + dest_channel->d.time_left = channel->d.time_left; + dest_channel->d.note_length = + channel->d.time_left - dest_channel->d.inter_note_pause; + note += dest_channel->d.transpose; + while (note < 0) + note += 12; + octave = note / 12; + note = note % 12; + dest_channel->d.hull_offset = 0; + dest_channel->d.hull_counter = 1; + if (_pcjr && dest_channel == &_channels[3]) { + dest_channel->d.hull_curve = 196 + note * 12; + myfreq = 384 - 64 * octave; + } else { + myfreq = _freqs_table[note] >> octave; + } + dest_channel->d.freq = dest_channel->d.base_freq = myfreq; + if (is_last_note) + goto end; + opcode = *script_ptr++; + } + } + } + +end: + channel = current_channel; + if (channel->d.time_left) { + channel->d.next_cmd = script_ptr - _current_data; + return; + } + + channel->d.next_cmd = 0; + +check_stopped: + int i; + for (i = 0; i < 4; i++) { + if (_channels[i].d.time_left) + return; + } + + _current_nr = 0; + _current_data = 0; + chainNextSound(); +} + +void Player_V2Base::next_freqs(ChannelInfo *channel) { + channel->d.volume += channel->d.volume_delta; + channel->d.base_freq += channel->d.freq_delta; + + channel->d.freqmod_offset += channel->d.freqmod_incr; + if (channel->d.freqmod_offset > channel->d.freqmod_modulo) + channel->d.freqmod_offset -= channel->d.freqmod_modulo; + + channel->d.freq = + (int) (freqmod_table[channel->d.freqmod_table + (channel->d.freqmod_offset >> 4)]) + * (int) channel->d.freqmod_multiplier / 256 + + channel->d.base_freq; + + debug(9, "Freq: %d/%d, %d/%d/%d*%d %d", + channel->d.base_freq, (int16)channel->d.freq_delta, + channel->d.freqmod_table, channel->d.freqmod_offset, + channel->d.freqmod_incr, channel->d.freqmod_multiplier, + channel->d.freq); + + if (channel->d.note_length && !--channel->d.note_length) { + channel->d.hull_offset = 16; + channel->d.hull_counter = 1; + } + + if (!--channel->d.time_left) { + execute_cmd(channel); + } + + if (channel->d.hull_counter && !--channel->d.hull_counter) { + for (;;) { + const int16 *hull_ptr = hulls + + channel->d.hull_curve + channel->d.hull_offset / 2; + if (hull_ptr[1] == -1) { + channel->d.volume = hull_ptr[0]; + if (hull_ptr[0] == 0) + channel->d.volume_delta = 0; + channel->d.hull_offset += 4; + } else { + channel->d.volume_delta = hull_ptr[0]; + channel->d.hull_counter = hull_ptr[1]; + channel->d.hull_offset += 4; + break; + } + } + } +} + +void Player_V2Base::nextTick() { + for (int i = 0; i < 4; i++) { + if (!_channels[i].d.time_left) + continue; + next_freqs(&_channels[i]); + } + if (_music_timer_ctr++ >= _ticks_per_music_timer) { + _music_timer_ctr = 0; + _music_timer++; + } +} + + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v2base.h b/engines/scumm/players/player_v2base.h new file mode 100644 index 0000000000..32bde95ddc --- /dev/null +++ b/engines/scumm/players/player_v2base.h @@ -0,0 +1,145 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V2BASE_H +#define SCUMM_PLAYERS_PLAYER_V2BASE_H + +#include "common/scummsys.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +namespace Scumm { + +class ScummEngine; + + +#include "common/pack-start.h" // START STRUCT PACKING + +struct channel_data { + uint16 time_left; // 00 + uint16 next_cmd; // 02 + uint16 base_freq; // 04 + uint16 freq_delta; // 06 + uint16 freq; // 08 + uint16 volume; // 10 + uint16 volume_delta; // 12 + uint16 tempo; // 14 + uint16 inter_note_pause; // 16 + uint16 transpose; // 18 + uint16 note_length; // 20 + uint16 hull_curve; // 22 + uint16 hull_offset; // 24 + uint16 hull_counter; // 26 + uint16 freqmod_table; // 28 + uint16 freqmod_offset; // 30 + uint16 freqmod_incr; // 32 + uint16 freqmod_multiplier; // 34 + uint16 freqmod_modulo; // 36 + uint16 unknown[4]; // 38 - 44 + uint16 music_timer; // 46 + uint16 music_script_nr; // 48 +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +/** + * Common base class for Player_V2 and Player_V2CMS. + */ +class Player_V2Base : public Audio::AudioStream, public MusicEngine { +public: + Player_V2Base(ScummEngine *scumm, Audio::Mixer *mixer, bool pcjr); + virtual ~Player_V2Base(); + + // MusicEngine API +// virtual void setMusicVolume(int vol); +// virtual void startSound(int sound); +// virtual void stopSound(int sound); +// virtual void stopAllSounds(); + virtual int getMusicTimer(); +// virtual int getSoundStatus(int sound) const; + + // AudioStream API +/* + int readBuffer(int16 *buffer, const int numSamples) { + do_mix(buffer, numSamples / 2); + return numSamples; + } +*/ + virtual bool isStereo() const { return true; } + virtual bool endOfData() const { return false; } + virtual int getRate() const { return _sampleRate; } + +protected: + enum { + FIXP_SHIFT = 16 + }; + + bool _isV3Game; + Audio::Mixer *_mixer; + Audio::SoundHandle _soundHandle; + ScummEngine *_vm; + + bool _pcjr; + int _header_len; + + const uint32 _sampleRate; + uint32 _next_tick; + uint32 _tick_len; + + int _current_nr; + byte *_current_data; + int _next_nr; + byte *_next_data; + byte *_retaddr; + + Common::Mutex _mutex; + + union ChannelInfo { + channel_data d; + uint16 array[sizeof(channel_data)/2]; + }; + + ChannelInfo _channels[5]; + +private: + int _music_timer; + int _music_timer_ctr; + int _ticks_per_music_timer; + + const uint16 *_freqs_table; + +protected: + virtual void nextTick(); + virtual void clear_channel(int i); + virtual void chainSound(int nr, byte *data); + virtual void chainNextSound(); + + void execute_cmd(ChannelInfo *channel); + void next_freqs(ChannelInfo *channel); +}; + + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v2cms.cpp b/engines/scumm/players/player_v2cms.cpp new file mode 100644 index 0000000000..8e903bf9d2 --- /dev/null +++ b/engines/scumm/players/player_v2cms.cpp @@ -0,0 +1,787 @@ +/* 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_v2cms.h" +#include "scumm/scumm.h" +#include "audio/mididrv.h" +#include "audio/mixer.h" +#include "audio/softsynth/cms.h" + +namespace Scumm { + +Player_V2CMS::Player_V2CMS(ScummEngine *scumm, Audio::Mixer *mixer) + : Player_V2Base(scumm, mixer, true), _cmsVoicesBase(), _cmsVoices(), + _cmsChips(), _midiDelay(0), _octaveMask(0), _looping(0), _tempo(0), + _tempoSum(0), _midiData(0), _midiSongBegin(0), _musicTimer(0), + _musicTimerTicks(0), _voiceTimer(0), _loadedMidiSong(0), + _outputTableReady(0), _midiChannel(), _midiChannelUse() { + setMusicVolume(255); + + memset(_sfxFreq, 0xFF, sizeof(_sfxFreq)); + memset(_sfxAmpl, 0x00, sizeof(_sfxAmpl)); + memset(_sfxOctave, 0x66, sizeof(_sfxOctave)); + + _cmsVoices[0].amplitudeOutput = &_cmsChips[0].ampl[0]; + _cmsVoices[0].freqOutput = &_cmsChips[0].freq[0]; + _cmsVoices[0].octaveOutput = &_cmsChips[0].octave[0]; + _cmsVoices[1].amplitudeOutput = &_cmsChips[0].ampl[1]; + _cmsVoices[1].freqOutput = &_cmsChips[0].freq[1]; + _cmsVoices[1].octaveOutput = &_cmsChips[0].octave[0]; + _cmsVoices[2].amplitudeOutput = &_cmsChips[0].ampl[2]; + _cmsVoices[2].freqOutput = &_cmsChips[0].freq[2]; + _cmsVoices[2].octaveOutput = &_cmsChips[0].octave[1]; + _cmsVoices[3].amplitudeOutput = &_cmsChips[0].ampl[3]; + _cmsVoices[3].freqOutput = &_cmsChips[0].freq[3]; + _cmsVoices[3].octaveOutput = &_cmsChips[0].octave[1]; + _cmsVoices[4].amplitudeOutput = &_cmsChips[1].ampl[0]; + _cmsVoices[4].freqOutput = &_cmsChips[1].freq[0]; + _cmsVoices[4].octaveOutput = &_cmsChips[1].octave[0]; + _cmsVoices[5].amplitudeOutput = &_cmsChips[1].ampl[1]; + _cmsVoices[5].freqOutput = &_cmsChips[1].freq[1]; + _cmsVoices[5].octaveOutput = &_cmsChips[1].octave[0]; + _cmsVoices[6].amplitudeOutput = &_cmsChips[1].ampl[2]; + _cmsVoices[6].freqOutput = &_cmsChips[1].freq[2]; + _cmsVoices[6].octaveOutput = &_cmsChips[1].octave[1]; + _cmsVoices[7].amplitudeOutput = &_cmsChips[1].ampl[3]; + _cmsVoices[7].freqOutput = &_cmsChips[1].freq[3]; + _cmsVoices[7].octaveOutput = &_cmsChips[1].octave[1]; + + // inits the CMS Emulator like in the original + _cmsEmu = new CMSEmulator(_sampleRate); + for (int i = 0, cmsPort = 0x220; i < 2; cmsPort += 2, ++i) { + for (int off = 0; off < 13; ++off) { + _cmsEmu->portWrite(cmsPort+1, _cmsInitData[off*2]); + _cmsEmu->portWrite(cmsPort, _cmsInitData[off*2+1]); + } + } + + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_V2CMS::~Player_V2CMS() { + Common::StackLock lock(_mutex); + + _mixer->stopHandle(_soundHandle); + delete _cmsEmu; +} + +void Player_V2CMS::setMusicVolume(int vol) { +} + +int Player_V2CMS::getMusicTimer() { + return _midiData ? _musicTimer : Player_V2Base::getMusicTimer(); +} + +void Player_V2CMS::stopAllSounds() { + Common::StackLock lock(_mutex); + + for (int i = 0; i < 4; i++) { + clear_channel(i); + } + _next_nr = _current_nr = 0; + _next_data = _current_data = 0; + _midiData = 0; + _midiSongBegin = 0; + _midiDelay = 0; + _musicTimer = _musicTimerTicks = 0; + offAllChannels(); +} + +void Player_V2CMS::stopSound(int nr) { + Common::StackLock lock(_mutex); + + if (_next_nr == nr) { + _next_nr = 0; + _next_data = 0; + } + if (_current_nr == nr) { + for (int i = 0; i < 4; i++) { + clear_channel(i); + } + _current_nr = 0; + _current_data = 0; + chainNextSound(); + } + if (_loadedMidiSong == nr) { + _midiData = 0; + _midiSongBegin = 0; + _midiDelay = 0; + offAllChannels(); + } +} + +void Player_V2CMS::startSound(int nr) { + Common::StackLock lock(_mutex); + + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + + if (data[6] == 0x80) { + _musicTimer = _musicTimerTicks = 0; + loadMidiData(data, nr); + } else { + int cprio = _current_data ? *(_current_data + _header_len) : 0; + int prio = *(data + _header_len); + int nprio = _next_data ? *(_next_data + _header_len) : 0; + + int restartable = *(data + _header_len + 1); + + if (!_current_nr || cprio <= prio) { + int tnr = _current_nr; + int tprio = cprio; + byte *tdata = _current_data; + + chainSound(nr, data); + nr = tnr; + prio = tprio; + data = tdata; + restartable = data ? *(data + _header_len + 1) : 0; + } + + if (!_current_nr) { + nr = 0; + _next_nr = 0; + _next_data = 0; + } + + if (nr != _current_nr + && restartable + && (!_next_nr + || nprio <= prio)) { + + _next_nr = nr; + _next_data = data; + } + } +} + +void Player_V2CMS::loadMidiData(byte *data, int sound) { + memset(_midiChannelUse, 0, sizeof(_midiChannelUse)); + memset(_midiChannel, 0, sizeof(_midiChannel)); + + _tempo = data[7]; + _looping = data[8]; + + byte channels = data[14]; + byte curChannel = 0; + byte *voice2 = data + 23; + + for (; channels != 0; ++curChannel, --channels, voice2 += 16) { + if (*(data + 15 + curChannel)) { + byte channel = *(data + 15 + curChannel) - 1; + _midiChannelUse[channel] = 1; + + Voice *voiceDef = &_cmsVoicesBase[channel]; + + byte attackDecay = voice2[10]; + voiceDef->attack = _attackRate[attackDecay >> 4]; + voiceDef->decay = _decayRate[attackDecay & 0x0F]; + byte sustainRelease = voice2[11]; + voiceDef->sustain = _sustainRate[sustainRelease >> 4]; + voiceDef->release = _releaseRate[sustainRelease & 0x0F]; + + if (voice2[3] & 0x40) { + voiceDef->vibrato = 0x0301; + if (voice2[13] & 0x40) { + voiceDef->vibrato = 0x0601; + } + } else { + voiceDef->vibrato = 0; + } + + if (voice2[8] & 0x80) { + voiceDef->vibrato2 = 0x0506; + if (voice2[13] & 0x80) { + voiceDef->vibrato2 = 0x050C; + } + } else { + voiceDef->vibrato2 = 0; + } + + if ((voice2[8] & 0x0F) > 1) { + voiceDef->octadd = 0x01; + } else { + voiceDef->octadd = 0x00; + } + } + } + + for (int i = 0; i < 8; ++i) { + _cmsVoices[i].chanNumber = 0xFF; + _cmsVoices[i].curVolume = 0; + _cmsVoices[i].nextVoice = 0; + } + + _midiDelay = 0; + memset(_cmsChips, 0, sizeof(MusicChip)*2); + _midiData = data + 151; + _midiSongBegin = _midiData + data[9]; + + _loadedMidiSong = sound; +} + +int Player_V2CMS::getSoundStatus(int nr) const { + return _current_nr == nr || _next_nr == nr || _loadedMidiSong == nr; +} + +void Player_V2CMS::processMidiData() { + byte *currentData = _midiData; + byte command = 0x00; + int16 temp = 0; + + ++_musicTimerTicks; + if (_musicTimerTicks > 60) { + _musicTimerTicks = 0; + ++_musicTimer; + } + + if (!_midiDelay) { + while (true) { + if ((command = *currentData++) == 0xFF) { + if ((command = *currentData++) == 0x2F) { + if (_looping == 0) { + currentData = _midiData = _midiSongBegin; + continue; + } + _midiData = _midiSongBegin = 0; + _midiDelay = 0; + _loadedMidiSong = 0; + offAllChannels(); + return; + } else { + if (command == 0x58) { + currentData += 6; + } + } + } else { + _lastMidiCommand = command; + if (command < 0x90) { + clearNote(currentData); + } else { + playNote(currentData); + } + } + + temp = command = *currentData++; + if (command & 0x80) { + temp = (command & 0x7F) << 8; + command = *currentData++; + temp |= (command << 1); + temp >>= 1; + } + temp >>= 1; + int lastBit = temp & 1; + temp >>= 1; + temp += lastBit; + + if (temp) + break; + } + _midiData = currentData; + _midiDelay = temp; + } + + --_midiDelay; + if (_midiDelay < 0) + _midiDelay = 0; + + return; +} + +int Player_V2CMS::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + uint step = 1; + int len = numSamples / 2; + + // maybe this needs a complete rewrite + do { + if (!(_next_tick >> FIXP_SHIFT)) { + if (_midiData) { + --_voiceTimer; + if (!(_voiceTimer & 0x01)) + playVoice(); + + int newTempoSum = _tempo + _tempoSum; + _tempoSum = newTempoSum & 0xFF; + if (newTempoSum > 0xFF) + processMidiData(); + } else { + nextTick(); + play(); + } + _next_tick += _tick_len; + } + + step = len; + if (step > (_next_tick >> FIXP_SHIFT)) + step = (_next_tick >> FIXP_SHIFT); + _cmsEmu->readBuffer(buffer, step); + buffer += 2 * step; + _next_tick -= step << FIXP_SHIFT; + } while (len -= step); + + return numSamples; +} + +void Player_V2CMS::playVoice() { + if (_outputTableReady) { + playMusicChips(_cmsChips); + _outputTableReady = 0; + } + + _octaveMask = 0xF0; + Voice2 *voice = 0; + for (int i = 0; i < 8; ++i) { + voice = &_cmsVoices[i]; + _octaveMask = ~_octaveMask; + + if (voice->chanNumber != 0xFF) { + processChannel(voice); + } else { + if (!voice->curVolume) { + *(voice->amplitudeOutput) = 0; + } + + int volume = voice->curVolume - voice->releaseRate; + if (volume < 0) + volume = 0; + + voice->curVolume = volume; + *(voice->amplitudeOutput) = ((volume >> 4) | (volume & 0xF0)) & voice->channel; + ++_outputTableReady; + } + } +} + +void Player_V2CMS::processChannel(Voice2 *channel) { + ++_outputTableReady; + switch (channel->nextProcessState) { + case Voice2::kEnvelopeAttack: + processAttack(channel); + break; + + case Voice2::kEnvelopeDecay: + processDecay(channel); + break; + + case Voice2::kEnvelopeSustain: + processSustain(channel); + break; + + case Voice2::kEnvelopeRelease: + processRelease(channel); + break; + } +} + +void Player_V2CMS::processRelease(Voice2 *channel) { + int newVolume = channel->curVolume - channel->releaseRate; + if (newVolume < 0) + newVolume = 0; + + channel->curVolume = newVolume; + processVibrato(channel); +} + +void Player_V2CMS::processAttack(Voice2 *channel) { + int newVolume = channel->curVolume + channel->attackRate; + if (newVolume > channel->maxAmpl) { + channel->curVolume = channel->maxAmpl; + channel->nextProcessState = Voice2::kEnvelopeDecay; + } else { + channel->curVolume = newVolume; + } + + processVibrato(channel); +} + +void Player_V2CMS::processDecay(Voice2 *channel) { + int newVolume = channel->curVolume - channel->decayRate; + if (newVolume <= channel->sustainRate) { + channel->curVolume = channel->sustainRate; + channel->nextProcessState = Voice2::kEnvelopeSustain; + } else { + channel->curVolume = newVolume; + } + + processVibrato(channel); +} + +void Player_V2CMS::processSustain(Voice2 *channel) { + if (channel->unkVibratoRate) { + int16 volume = channel->curVolume + channel->unkRate; + if (volume & 0xFF00) { + volume = int8(volume >> 8); + volume = -volume; + } + + channel->curVolume = volume; + --channel->unkCount; + if (!channel->unkCount) { + channel->unkRate = -channel->unkRate; + channel->unkCount = (channel->unkVibratoDepth & 0x0F) << 1; + } + } + processVibrato(channel); +} + +void Player_V2CMS::processVibrato(Voice2 *channel) { + if (channel->vibratoRate) { + int16 temp = channel->curFreq + channel->curVibratoRate; + channel->curOctave += (temp & 0xFF00) >> 8; + channel->curFreq = temp & 0xFF; + + --channel->curVibratoUnk; + if (!channel->curVibratoUnk) { + channel->curVibratoRate = -channel->curVibratoRate; + channel->curVibratoUnk = (channel->vibratoDepth & 0x0F) << 1; + } + } + + byte *output = channel->amplitudeOutput; + *output = ((channel->curVolume >> 4) | (channel->curVolume & 0xF0)) & channel->channel; + output = channel->freqOutput; + *output = channel->curFreq; + output = channel->octaveOutput; + *output = (((channel->curOctave << 4) | (channel->curOctave & 0x0F)) & _octaveMask) | ((~_octaveMask) & *output); +} + +void Player_V2CMS::offAllChannels() { + for (int cmsPort = 0x220, i = 0; i < 2; cmsPort += 2, ++i) { + for (int off = 1; off <= 10; ++off) { + _cmsEmu->portWrite(cmsPort+1, _cmsInitData[off*2]); + _cmsEmu->portWrite(cmsPort, _cmsInitData[off*2+1]); + } + } +} + +Player_V2CMS::Voice2 *Player_V2CMS::getFreeVoice() { + Voice2 *curVoice = 0; + Voice2 *selected = 0; + uint8 volume = 0xFF; + + for (int i = 0; i < 8; ++i) { + curVoice = &_cmsVoices[i]; + + if (curVoice->chanNumber == 0xFF) { + if (!curVoice->curVolume) { + selected = curVoice; + break; + } + + if (curVoice->curVolume < volume) { + selected = curVoice; + volume = selected->curVolume; + } + } + } + + if (selected) { + selected->chanNumber = _lastMidiCommand & 0x0F; + + uint8 channel = selected->chanNumber; + Voice2 *oldChannel = _midiChannel[channel]; + _midiChannel[channel] = selected; + selected->nextVoice = oldChannel; + } + + return selected; +} + +void Player_V2CMS::playNote(byte *&data) { + byte channel = _lastMidiCommand & 0x0F; + if (_midiChannelUse[channel]) { + Voice2 *freeVoice = getFreeVoice(); + if (freeVoice) { + Voice *voice = &_cmsVoicesBase[freeVoice->chanNumber]; + freeVoice->attackRate = voice->attack; + freeVoice->decayRate = voice->decay; + freeVoice->sustainRate = voice->sustain; + freeVoice->releaseRate = voice->release; + freeVoice->octaveAdd = voice->octadd; + freeVoice->vibratoRate = freeVoice->curVibratoRate = voice->vibrato & 0xFF; + freeVoice->vibratoDepth = freeVoice->curVibratoUnk = voice->vibrato >> 8; + freeVoice->unkVibratoRate = freeVoice->unkRate = voice->vibrato2 & 0xFF; + freeVoice->unkVibratoDepth = freeVoice->unkCount = voice->vibrato2 >> 8; + freeVoice->maxAmpl = 0xFF; + + uint8 rate = freeVoice->attackRate; + uint8 volume = freeVoice->curVolume >> 1; + + if (rate < volume) + rate = volume; + + rate -= freeVoice->attackRate; + freeVoice->curVolume = rate; + freeVoice->playingNote = *data; + + int effectiveNote = freeVoice->playingNote + 3; + if (effectiveNote < 0 || effectiveNote >= ARRAYSIZE(_midiNotes)) { + warning("Player_V2CMS::playNote: Note %d out of bounds", effectiveNote); + effectiveNote = CLIP<int>(effectiveNote, 0, ARRAYSIZE(_midiNotes) - 1); + } + + int octave = _midiNotes[effectiveNote].baseOctave + freeVoice->octaveAdd - 3; + if (octave < 0) + octave = 0; + if (octave > 7) + octave = 7; + if (!octave) + ++octave; + freeVoice->curOctave = octave; + freeVoice->curFreq = _midiNotes[effectiveNote].frequency; + freeVoice->curVolume = 0; + freeVoice->nextProcessState = Voice2::kEnvelopeAttack; + if (!(_lastMidiCommand & 1)) + freeVoice->channel = 0xF0; + else + freeVoice->channel = 0x0F; + } + } + data += 2; +} + +Player_V2CMS::Voice2 *Player_V2CMS::getPlayVoice(byte param) { + byte channelNum = _lastMidiCommand & 0x0F; + Voice2 *curVoice = _midiChannel[channelNum]; + + if (curVoice) { + Voice2 *prevVoice = 0; + while (true) { + if (curVoice->playingNote == param) + break; + + prevVoice = curVoice; + curVoice = curVoice->nextVoice; + if (!curVoice) + return 0; + } + + if (prevVoice) + prevVoice->nextVoice = curVoice->nextVoice; + else + _midiChannel[channelNum] = curVoice->nextVoice; + } + + return curVoice; +} + +void Player_V2CMS::clearNote(byte *&data) { + Voice2 *voice = getPlayVoice(*data); + if (voice) { + voice->chanNumber = 0xFF; + voice->nextVoice = 0; + voice->nextProcessState = Voice2::kEnvelopeRelease; + } + data += 2; +} + +void Player_V2CMS::play() { + _octaveMask = 0xF0; + channel_data *chan = &_channels[0].d; + + byte noiseGen = 3; + + for (int i = 1; i <= 4; ++i) { + if (chan->time_left) { + uint16 freq = chan->freq; + + if (i == 4) { + if ((freq >> 8) & 0x40) { + noiseGen = freq & 0xFF; + } else { + noiseGen = 3; + _sfxFreq[0] = _sfxFreq[3]; + _sfxOctave[0] = (_sfxOctave[0] & 0xF0) | ((_sfxOctave[1] & 0xF0) >> 4); + } + } else { + if (freq == 0) { + freq = 0xFFC0; + } + + int cmsOct = 2; + int freqOct = 0x8000; + + while (true) { + if (freq >= freqOct) { + break; + } + freqOct >>= 1; + ++cmsOct; + if (cmsOct == 8) { + --cmsOct; + freq = 1024; + break; + } + } + byte oct = cmsOct << 4; + oct |= cmsOct; + + oct &= _octaveMask; + oct |= (~_octaveMask) & _sfxOctave[(i & 3) >> 1]; + _sfxOctave[(i & 3) >> 1] = oct; + + freq >>= -(cmsOct - 9); + _sfxFreq[i & 3] = (-(freq - 511)) & 0xFF; + } + _sfxAmpl[i & 3] = _volumeTable[chan->volume >> 12]; + } else { + _sfxAmpl[i & 3] = 0; + } + + chan = &_channels[i].d; + _octaveMask ^= 0xFF; + } + + // with the high nibble of the volumeReg value + // the right channels amplitude is set + // with the low value the left channels amplitude + _cmsEmu->portWrite(0x221, 0); + _cmsEmu->portWrite(0x220, _sfxAmpl[0]); + _cmsEmu->portWrite(0x221, 1); + _cmsEmu->portWrite(0x220, _sfxAmpl[1]); + _cmsEmu->portWrite(0x221, 2); + _cmsEmu->portWrite(0x220, _sfxAmpl[2]); + _cmsEmu->portWrite(0x221, 3); + _cmsEmu->portWrite(0x220, _sfxAmpl[3]); + _cmsEmu->portWrite(0x221, 8); + _cmsEmu->portWrite(0x220, _sfxFreq[0]); + _cmsEmu->portWrite(0x221, 9); + _cmsEmu->portWrite(0x220, _sfxFreq[1]); + _cmsEmu->portWrite(0x221, 10); + _cmsEmu->portWrite(0x220, _sfxFreq[2]); + _cmsEmu->portWrite(0x221, 11); + _cmsEmu->portWrite(0x220, _sfxFreq[3]); + _cmsEmu->portWrite(0x221, 0x10); + _cmsEmu->portWrite(0x220, _sfxOctave[0]); + _cmsEmu->portWrite(0x221, 0x11); + _cmsEmu->portWrite(0x220, _sfxOctave[1]); + _cmsEmu->portWrite(0x221, 0x14); + _cmsEmu->portWrite(0x220, 0x3E); + _cmsEmu->portWrite(0x221, 0x15); + _cmsEmu->portWrite(0x220, 0x01); + _cmsEmu->portWrite(0x221, 0x16); + _cmsEmu->portWrite(0x220, noiseGen); +} + +void Player_V2CMS::playMusicChips(const MusicChip *table) { + int cmsPort = 0x21E; + + do { + cmsPort += 2; + _cmsEmu->portWrite(cmsPort+1, 0); + _cmsEmu->portWrite(cmsPort, table->ampl[0]); + _cmsEmu->portWrite(cmsPort+1, 1); + _cmsEmu->portWrite(cmsPort, table->ampl[1]); + _cmsEmu->portWrite(cmsPort+1, 2); + _cmsEmu->portWrite(cmsPort, table->ampl[2]); + _cmsEmu->portWrite(cmsPort+1, 3); + _cmsEmu->portWrite(cmsPort, table->ampl[3]); + _cmsEmu->portWrite(cmsPort+1, 8); + _cmsEmu->portWrite(cmsPort, table->freq[0]); + _cmsEmu->portWrite(cmsPort+1, 9); + _cmsEmu->portWrite(cmsPort, table->freq[1]); + _cmsEmu->portWrite(cmsPort+1, 10); + _cmsEmu->portWrite(cmsPort, table->freq[2]); + _cmsEmu->portWrite(cmsPort+1, 11); + _cmsEmu->portWrite(cmsPort, table->freq[3]); + _cmsEmu->portWrite(cmsPort+1, 0x10); + _cmsEmu->portWrite(cmsPort, table->octave[0]); + _cmsEmu->portWrite(cmsPort+1, 0x11); + _cmsEmu->portWrite(cmsPort, table->octave[1]); + _cmsEmu->portWrite(cmsPort+1, 0x14); + _cmsEmu->portWrite(cmsPort, 0x3F); + _cmsEmu->portWrite(cmsPort+1, 0x15); + _cmsEmu->portWrite(cmsPort, 0x00); + ++table; + } while ((cmsPort & 2) == 0); +} + +const Player_V2CMS::MidiNote Player_V2CMS::_midiNotes[132] = { + { 3, 0 }, { 31, 0 }, { 58, 0 }, { 83, 0 }, + { 107, 0 }, { 130, 0 }, { 151, 0 }, { 172, 0 }, + { 191, 0 }, { 209, 0 }, { 226, 0 }, { 242, 0 }, + { 3, 1 }, { 31, 1 }, { 58, 1 }, { 83, 1 }, + { 107, 1 }, { 130, 1 }, { 151, 1 }, { 172, 1 }, + { 191, 1 }, { 209, 1 }, { 226, 1 }, { 242, 1 }, + { 3, 2 }, { 31, 2 }, { 58, 2 }, { 83, 2 }, + { 107, 2 }, { 130, 2 }, { 151, 2 }, { 172, 2 }, + { 191, 2 }, { 209, 2 }, { 226, 2 }, { 242, 2 }, + { 3, 3 }, { 31, 3 }, { 58, 3 }, { 83, 3 }, + { 107, 3 }, { 130, 3 }, { 151, 3 }, { 172, 3 }, + { 191, 3 }, { 209, 3 }, { 226, 3 }, { 242, 3 }, + { 3, 4 }, { 31, 4 }, { 58, 4 }, { 83, 4 }, + { 107, 4 }, { 130, 4 }, { 151, 4 }, { 172, 4 }, + { 191, 4 }, { 209, 4 }, { 226, 4 }, { 242, 4 }, + { 3, 5 }, { 31, 5 }, { 58, 5 }, { 83, 5 }, + { 107, 5 }, { 130, 5 }, { 151, 5 }, { 172, 5 }, + { 191, 5 }, { 209, 5 }, { 226, 5 }, { 242, 5 }, + { 3, 6 }, { 31, 6 }, { 58, 6 }, { 83, 6 }, + { 107, 6 }, { 130, 6 }, { 151, 6 }, { 172, 6 }, + { 191, 6 }, { 209, 6 }, { 226, 6 }, { 242, 6 }, + { 3, 7 }, { 31, 7 }, { 58, 7 }, { 83, 7 }, + { 107, 7 }, { 130, 7 }, { 151, 7 }, { 172, 7 }, + { 191, 7 }, { 209, 7 }, { 226, 7 }, { 242, 7 }, + { 3, 8 }, { 31, 8 }, { 58, 8 }, { 83, 8 }, + { 107, 8 }, { 130, 8 }, { 151, 8 }, { 172, 8 }, + { 191, 8 }, { 209, 8 }, { 226, 8 }, { 242, 8 }, + { 3, 9 }, { 31, 9 }, { 58, 9 }, { 83, 9 }, + { 107, 9 }, { 130, 9 }, { 151, 9 }, { 172, 9 }, + { 191, 9 }, { 209, 9 }, { 226, 9 }, { 242, 9 }, + { 3, 10 }, { 31, 10 }, { 58, 10 }, { 83, 10 }, + { 107, 10 }, { 130, 10 }, { 151, 10 }, { 172, 10 }, + { 191, 10 }, { 209, 10 }, { 226, 10 }, { 242, 10 } +}; + +const byte Player_V2CMS::_attackRate[16] = { + 0, 2, 4, 7, 14, 26, 48, 82, + 128, 144, 160, 176, 192, 208, 224, 255 +}; + +const byte Player_V2CMS::_decayRate[16] = { + 0, 1, 2, 3, 4, 6, 12, 24, + 48, 96, 192, 215, 255, 255, 255, 255 +}; + +const byte Player_V2CMS::_sustainRate[16] = { + 255, 180, 128, 96, 80, 64, 56, 48, + 42, 36, 32, 28, 24, 20, 16, 0 +}; + +const byte Player_V2CMS::_releaseRate[16] = { + 0, 1, 2, 4, 6, 9, 14, 22, + 36, 56, 80, 100, 120, 140, 160, 255 +}; + +const byte Player_V2CMS::_volumeTable[16] = { + 0x00, 0x10, 0x10, 0x11, 0x11, 0x21, 0x22, 0x22, + 0x33, 0x44, 0x55, 0x66, 0x88, 0xAA, 0xCC, 0xFF +}; + +const byte Player_V2CMS::_cmsInitData[26] = { + 0x1C, 0x02, + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, + 0x14, 0x3F, 0x15, 0x00, 0x16, 0x00, 0x18, 0x00, 0x19, 0x00, 0x1C, 0x01 +}; + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v2cms.h b/engines/scumm/players/player_v2cms.h new file mode 100644 index 0000000000..fe42720215 --- /dev/null +++ b/engines/scumm/players/player_v2cms.h @@ -0,0 +1,177 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V2CMS_H +#define SCUMM_PLAYERS_PLAYER_V2CMS_H + +#include "scumm/players/player_v2base.h" // for channel_data + +class CMSEmulator; + +namespace Scumm { + +/** + * Scumm V2 CMS/Gameblaster MIDI driver. + */ +class Player_V2CMS : public Player_V2Base { +public: + Player_V2CMS(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_V2CMS(); + + // MusicEngine API + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + + // AudioStream API + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return true; } + +private: + struct Voice { + byte attack; + byte decay; + byte sustain; + byte release; + byte octadd; + int16 vibrato; + int16 vibrato2; + int16 noise; + }; + + struct Voice2 { + byte *amplitudeOutput; + byte *freqOutput; + byte *octaveOutput; + + uint8 channel; + int8 sustainLevel; + uint8 attackRate; + uint8 maxAmpl; + uint8 decayRate; + uint8 sustainRate; + uint8 releaseRate; + uint8 releaseTime; + int8 vibratoRate; + int8 vibratoDepth; + + int8 curVibratoRate; + int8 curVibratoUnk; + + int8 unkVibratoRate; + int8 unkVibratoDepth; + + int8 unkRate; + int8 unkCount; + + enum EnvelopeState { + kEnvelopeAttack, + kEnvelopeDecay, + kEnvelopeSustain, + kEnvelopeRelease + }; + + EnvelopeState nextProcessState; + uint8 curVolume; + uint8 curOctave; + uint8 curFreq; + + int8 octaveAdd; + + int8 playingNote; + Voice2 *nextVoice; + + byte chanNumber; + }; + + struct MusicChip { + byte ampl[4]; + byte freq[4]; + byte octave[2]; + }; + + Voice _cmsVoicesBase[16]; + Voice2 _cmsVoices[8]; + MusicChip _cmsChips[2]; + + uint8 _tempo; + uint8 _tempoSum; + byte _looping; + byte _octaveMask; + int16 _midiDelay; + Voice2 *_midiChannel[16]; + byte _midiChannelUse[16]; + byte *_midiData; + byte *_midiSongBegin; + + int _loadedMidiSong; + + byte _sfxFreq[4], _sfxAmpl[4], _sfxOctave[2]; + + byte _lastMidiCommand; + uint _outputTableReady; + byte _voiceTimer; + + int _musicTimer, _musicTimerTicks; + + void loadMidiData(byte *data, int sound); + void play(); + + void processChannel(Voice2 *channel); + void processRelease(Voice2 *channel); + void processAttack(Voice2 *channel); + void processDecay(Voice2 *channel); + void processSustain(Voice2 *channel); + void processVibrato(Voice2 *channel); + + void playMusicChips(const MusicChip *table); + void playNote(byte *&data); + void clearNote(byte *&data); + void offAllChannels(); + void playVoice(); + void processMidiData(); + + Voice2 *getFreeVoice(); + Voice2 *getPlayVoice(byte param); + + struct MidiNote { + byte frequency; + byte baseOctave; + }; + + static const MidiNote _midiNotes[132]; + static const byte _attackRate[16]; + static const byte _decayRate[16]; + static const byte _sustainRate[16]; + static const byte _releaseRate[16]; + static const byte _volumeTable[16]; + static const byte _cmsInitData[26]; + + CMSEmulator *_cmsEmu; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v3a.cpp b/engines/scumm/players/player_v3a.cpp new file mode 100644 index 0000000000..ca0eedc90a --- /dev/null +++ b/engines/scumm/players/player_v3a.cpp @@ -0,0 +1,357 @@ +/* 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 "engines/engine.h" +#include "scumm/players/player_v3a.h" +#include "scumm/scumm.h" + +namespace Scumm { + +static const uint16 note_freqs[4][12] = { + {0x06B0, 0x0650, 0x05F4, 0x05A0, 0x054C, 0x0500, 0x04B8, 0x0474, 0x0434, 0x03F8, 0x03C0, 0x0388}, + {0x0358, 0x0328, 0x02FA, 0x02D0, 0x02A6, 0x0280, 0x025C, 0x023A, 0x021A, 0x01FC, 0x01E0, 0x01C4}, + {0x01AC, 0x0194, 0x017D, 0x0168, 0x0153, 0x0140, 0x012E, 0x011D, 0x010D, 0x00FE, 0x00F0, 0x00E2}, + {0x00D6, 0x00CA, 0x00BE, 0x00B4, 0x00A9, 0x00A0, 0x0097, 0x008E, 0x0086, 0x007F, 0x00F0, 0x00E2} +}; + +Player_V3A::Player_V3A(ScummEngine *scumm, Audio::Mixer *mixer) { + int i; + _vm = scumm; + for (i = 0; i < V3A_MAXMUS; i++) { + _mus[i].id = 0; + _mus[i].dur = 0; + } + for (i = 0; i < V3A_MAXSFX; i++) { + _sfx[i].id = 0; + _sfx[i].dur = 0; + } + + _curSong = 0; + _songData = NULL; + _songPtr = 0; + _songDelay = 0; + + _music_timer = 0; + + _isinit = false; + + _mod = new Player_MOD(mixer); + _mod->setUpdateProc(update_proc, this, 60); +} + +Player_V3A::~Player_V3A() { + int i; + delete _mod; + if (_isinit) { + for (i = 0; _wavetable[i] != NULL; i++) { + for (int j = 0; j < 6; j++) { + free(_wavetable[i]->_idat[j]); + free(_wavetable[i]->_ldat[j]); + } + free(_wavetable[i]); + } + free(_wavetable); + } +} + +void Player_V3A::setMusicVolume (int vol) { + _mod->setMusicVolume(vol); +} + +int Player_V3A::getMusChan (int id) const { + int i; + for (i = 0; i < V3A_MAXMUS; i++) { + if (_mus[i].id == id) + break; + } + if (i == V3A_MAXMUS) { + if (id == 0) + warning("player_v3a - out of music channels"); + return -1; + } + return i; +} +int Player_V3A::getSfxChan (int id) const { + int i; + for (i = 0; i < V3A_MAXSFX; i++) { + if (_sfx[i].id == id) + break; + } + if (i == V3A_MAXSFX) { + if (id == 0) + warning("player_v3a - out of sfx channels"); + return -1; + } + return i; +} + +void Player_V3A::stopAllSounds() { + int i; + for (i = 0; i < V3A_MAXMUS; i++) { + if (_mus[i].id) + _mod->stopChannel(_mus[i].id); + _mus[i].id = 0; + _mus[i].dur = 0; + } + _curSong = 0; + _songPtr = 0; + _songDelay = 0; + _songData = NULL; + for (i = 0; i < V3A_MAXSFX; i++) { + if (_sfx[i].id) + _mod->stopChannel(_sfx[i].id | 0x100); + _sfx[i].id = 0; + _sfx[i].dur = 0; + } +} + +void Player_V3A::stopSound(int nr) { + int i; + if (nr == 0) { // Amiga Loom does this near the end, when Chaos casts SILENCE on Hetchel + stopAllSounds(); + return; + } + if (nr == _curSong) { + for (i = 0; i < V3A_MAXMUS; i++) { + if (_mus[i].id) + _mod->stopChannel(_mus[i].id); + _mus[i].id = 0; + _mus[i].dur = 0; + } + _curSong = 0; + _songPtr = 0; + _songDelay = 0; + _songData = NULL; + } else { + i = getSfxChan(nr); + if (i != -1) { + _mod->stopChannel(nr | 0x100); + _sfx[i].id = 0; + _sfx[i].dur = 0; + } + } +} + +void Player_V3A::startSound(int nr) { + assert(_vm); + byte *data = _vm->getResourceAddress(rtSound, nr); + assert(data); + + if ((_vm->_game.id != GID_INDY3) && (_vm->_game.id != GID_LOOM)) + error("player_v3a - unknown game"); + + if (!_isinit) { + int i; + unsigned char *ptr; + int offset = 4; + int numInstruments; + + if (_vm->_game.id == GID_INDY3) { + ptr = _vm->getResourceAddress(rtSound, 83); + numInstruments = 12; + } else { + ptr = _vm->getResourceAddress(rtSound, 79); + numInstruments = 9; + } + assert(ptr); + _wavetable = (instData **)malloc((numInstruments + 1) * sizeof(void *)); + for (i = 0; i < numInstruments; i++) { + _wavetable[i] = (instData *)malloc(sizeof(instData)); + for (int j = 0; j < 6; j++) { + int off, len; + off = READ_BE_UINT16(ptr + offset + 0); + _wavetable[i]->_ilen[j] = len = READ_BE_UINT16(ptr + offset + 2); + if (len) { + _wavetable[i]->_idat[j] = (char *)malloc(len); + memcpy(_wavetable[i]->_idat[j],ptr + off,len); + } else _wavetable[i]->_idat[j] = NULL; + off = READ_BE_UINT16(ptr + offset + 4); + _wavetable[i]->_llen[j] = len = READ_BE_UINT16(ptr + offset + 6); + if (len) { + _wavetable[i]->_ldat[j] = (char *)malloc(len); + memcpy(_wavetable[i]->_ldat[j],ptr + off,len); + } else _wavetable[i]->_ldat[j] = NULL; + _wavetable[i]->_oct[j] = READ_BE_UINT16(ptr + offset + 8); + offset += 10; + } + if (_vm->_game.id == GID_INDY3) { + _wavetable[i]->_pitadjust = 0; + offset += 2; + } else { + _wavetable[i]->_pitadjust = READ_BE_UINT16(ptr + offset + 2); + offset += 4; + } + } + _wavetable[i] = NULL; + _isinit = true; + } + + if (getSoundStatus(nr)) + stopSound(nr); // if a sound is playing, restart it + + if (data[26]) { + if (_curSong) + stopSound(_curSong); + _curSong = nr; + _songData = data; + _songPtr = 0x1C; + _songDelay = 1; + _music_timer = 0; + } else { + int size = READ_BE_UINT16(data + 12); + int rate = 3579545 / READ_BE_UINT16(data + 20); + char *sound = (char *)malloc(size); + int vol = (data[24] << 1) | (data[24] >> 5); // if I boost this to 0-255, it gets too loud and starts to clip + memcpy(sound, data + READ_BE_UINT16(data + 8), size); + int loopStart = 0, loopEnd = 0; + int loopcount = data[27]; + if (loopcount > 1) { + loopStart = READ_BE_UINT16(data + 10) - READ_BE_UINT16(data + 8); + loopEnd = READ_BE_UINT16(data + 14); + } + int i = getSfxChan(); + if (i == -1) { + free(sound); + return; + } + _sfx[i].id = nr; + _sfx[i].dur = 1 + loopcount * 60 * size / rate; + if (READ_BE_UINT16(data + 16)) { + _sfx[i].rate = READ_BE_UINT16(data + 20) << 16; + _sfx[i].delta = (int32)READ_BE_UINT32(data + 32); + _sfx[i].dur = READ_BE_UINT32(data + 40); + } else { + _sfx[i].delta = 0; + } + _mod->startChannel(nr | 0x100, sound, size, rate, vol, loopStart, loopEnd); + } +} + +void Player_V3A::update_proc(void *param) { + ((Player_V3A *)param)->playMusic(); +} + +void Player_V3A::playMusic() { + int i; + for (i = 0; i < V3A_MAXMUS; i++) { + if (_mus[i].id) { + _mus[i].dur--; + if (_mus[i].dur) + continue; + _mod->stopChannel(_mus[i].id); + _mus[i].id = 0; + } + } + for (i = 0; i < V3A_MAXSFX; i++) { + if (_sfx[i].id) { + if (_sfx[i].delta) { + uint16 oldrate = _sfx[i].rate >> 16; + _sfx[i].rate += _sfx[i].delta; + if (_sfx[i].rate < (55 << 16)) + _sfx[i].rate = 55 << 16; // at rates below 55, frequency + uint16 newrate = _sfx[i].rate >> 16; // exceeds 65536, which is bad + if (oldrate != newrate) + _mod->setChannelFreq(_sfx[i].id | 0x100, 3579545 / newrate); + } + _sfx[i].dur--; + if (_sfx[i].dur) + continue; + _mod->stopChannel(_sfx[i].id | 0x100); + _sfx[i].id = 0; + } + } + + _music_timer++; + if (!_curSong) + return; + if (_songDelay && --_songDelay) + return; + if (_songPtr == 0) { + // at the end of the song, and it wasn't looped - kill it + _curSong = 0; + return; + } + while (1) { + int inst, pit, vol, dur, oct; + inst = _songData[_songPtr++]; + if ((inst & 0xF0) != 0x80) { + // tune is at the end - figure out what's still playing + // and see how long we have to wait until we stop/restart + for (i = 0; i < V3A_MAXMUS; i++) { + if (_songDelay < _mus[i].dur) + _songDelay = _mus[i].dur; + } + if (inst == 0xFB) // it's a looped song, restart it afterwards + _songPtr = 0x1C; + else _songPtr = 0; // otherwise, terminate it + break; + } + inst &= 0xF; + pit = _songData[_songPtr++]; + vol = _songData[_songPtr++] & 0x7F; // if I boost this to 0-255, it gets too loud and starts to clip + dur = _songData[_songPtr++]; + if (pit == 0) { + _songDelay = dur; + break; + } + pit += _wavetable[inst]->_pitadjust; + oct = (pit / 12) - 2; + pit = pit % 12; + if (oct < 0) + oct = 0; + if (oct > 5) + oct = 5; + int rate = 3579545 / note_freqs[_wavetable[inst]->_oct[oct]][pit]; + if (!_wavetable[inst]->_llen[oct]) + dur = _wavetable[inst]->_ilen[oct] * 60 / rate; + char *data = (char *)malloc(_wavetable[inst]->_ilen[oct] + _wavetable[inst]->_llen[oct]); + if (_wavetable[inst]->_idat[oct]) + memcpy(data, _wavetable[inst]->_idat[oct], _wavetable[inst]->_ilen[oct]); + if (_wavetable[inst]->_ldat[oct]) + memcpy(data + _wavetable[inst]->_ilen[oct], _wavetable[inst]->_ldat[oct], _wavetable[inst]->_llen[oct]); + + i = getMusChan(); + if (i == -1) { + free(data); + return; + } + _mus[i].id = i + 1; + _mus[i].dur = dur + 1; + _mod->startChannel(_mus[i].id, data, _wavetable[inst]->_ilen[oct] + _wavetable[inst]->_llen[oct], rate, vol, + _wavetable[inst]->_ilen[oct], _wavetable[inst]->_ilen[oct] + _wavetable[inst]->_llen[oct]); + } +} + +int Player_V3A::getMusicTimer() { + return _music_timer / 30; +} + +int Player_V3A::getSoundStatus(int nr) const { + if (nr == _curSong) + return 1; + if (getSfxChan(nr) != -1) + return 1; + return 0; +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v3a.h b/engines/scumm/players/player_v3a.h new file mode 100644 index 0000000000..3f84a74e51 --- /dev/null +++ b/engines/scumm/players/player_v3a.h @@ -0,0 +1,101 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V3A_H +#define SCUMM_PLAYERS_PLAYER_V3A_H + +#include "common/scummsys.h" +#include "scumm/music.h" +#include "scumm/players/player_mod.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V3 Amiga sound/music driver. + */ +class Player_V3A : public MusicEngine { +public: + Player_V3A(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_V3A(); + + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + +private: + enum { + V3A_MAXMUS = 24, + V3A_MAXSFX = 16 + }; + + struct musChan { + int id; + int dur; + }; + + struct sfxChan { + int id; + int dur; + uint32 rate; + int32 delta; + }; + + struct instData { + char *_idat[6]; + uint16 _ilen[6]; + char *_ldat[6]; + uint16 _llen[6]; + uint16 _oct[6]; + int16 _pitadjust; + }; + + ScummEngine *_vm; + Player_MOD *_mod; + + musChan _mus[V3A_MAXMUS]; + sfxChan _sfx[V3A_MAXSFX]; + + int _curSong; + uint8 *_songData; + uint16 _songPtr; + uint16 _songDelay; + int _music_timer; + bool _isinit; + + instData **_wavetable; + + int getMusChan (int id = 0) const; + int getSfxChan (int id = 0) const; + static void update_proc(void *param); + void playMusic(); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v3m.cpp b/engines/scumm/players/player_v3m.cpp new file mode 100644 index 0000000000..e30e31aff9 --- /dev/null +++ b/engines/scumm/players/player_v3m.cpp @@ -0,0 +1,214 @@ +/* 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. + * + */ + +/* + We have the following information from Lars Christensen (lechimp) and + Jamieson Christian (jamieson630): + + RESOURCE DATA + LE 2 bytes Resource size + 2 bytes Unknown + 2 bytes 'so' + 14 bytes Unknown + BE 2 bytes Instrument for Stream 1 + BE 2 bytes Instrument for Stream 2 + BE 2 bytes Instrument for Stream 3 + BE 2 bytes Instrument for Stream 4 + BE 2 bytes Instrument for Stream 5 + BE 2 bytes Offset to Stream 1 + BE 2 bytes Offset to Stream 2 + BE 2 bytes Offset to Stream 3 + BE 2 bytes Offset to Stream 4 + BE 2 bytes Offset to Stream 5 + ? bytes The streams + + STREAM DATA + BE 2 bytes Unknown (always 1?) + 2 bytes Unknown (always 0?) + BE 2 bytes Number of events in stream + ? bytes Stream data + + Each stream event is exactly 3 bytes, therefore one can + assert that numEvents == (streamSize - 6) / 3. The + polyphony of a stream appears to be 1; in other words, only + one note at a time can be playing in each stream. The next + event is not executed until the current note (or rest) is + finished playing; therefore, note duration also serves as the + time delta between events. + + FOR EACH EVENTS + BE 2 bytes Note duration + 1 byte Note number to play (0 = rest/silent) + + Oh, and quick speculation -- Stream 1 may be used for a + single-voice interleaved version of the music, where Stream 2- + 5 represent a version of the music in up to 4-voice + polyphony, one voice per stream. I postulate thus because + the first stream of the Mac Loom theme music contains + interleaved voices, whereas the second stream seemed to + contain only the pizzicato bottom-end harp. Stream 5, in this + example, is empty, so if my speculation is correct, this + particular musical number supports 3-voice polyphony at + most. I must check out Streams 3 and 4 to see what they + contain. + + ========== + + The instruments appear to be identified by their resource IDs: + + 1000 Dual Harp + 10895 harp1 + 11445 strings1 + 11548 silent + 13811 staff1 + 15703 brass1 + 16324 flute1 + 25614 accordian 1 + 28110 f horn1 + 29042 bassoon1 +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/players/player_v3m.h" +#include "scumm/scumm.h" + +namespace Scumm { + +Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) + : Player_Mac(scumm, mixer, 5, 0x1E, true) { + assert(_vm->_game.id == GID_LOOM); + + // Channel 0 seems to be what was played on low-end macs, that couldn't + // handle multi-channel music and play the game at the same time. I'm + // not sure if stream 4 is ever used, but let's use it just in case. +} + +// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows +// version, the UTF-8 version, and just plain without in case the file system +// can't handle exotic characters like that. + +static const char *loomFileNames[] = { + "Loom\xAA", + "Loom\x99", + "Loom\xE2\x84\xA2", + "Loom" +}; + +bool Player_V3M::checkMusicAvailable() { + Common::MacResManager resource; + + for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { + if (resource.exists(loomFileNames[i])) { + return true; + } + } + + GUI::MessageDialog dialog(_( + "Could not find the 'Loom' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return false; +} + +bool Player_V3M::loadMusic(const byte *ptr) { + Common::MacResManager resource; + bool found = false; + + for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { + if (resource.open(loomFileNames[i])) { + found = true; + break; + } + } + + if (!found) { + return false; + } + + if (ptr[4] != 's' || ptr[5] != 'o') { + // Like the original we ignore all sound resources which do not have + // a 'so' tag in them. + // See bug #3602239 ("Mac Loom crashes using opening spell on + // gravestone") for a case where this is required. Loom Mac tries to + // play resource 11 here. This resource is no Mac sound resource + // though, it is a PC Speaker resource. A test with the original + // interpreter also has shown that no sound is played while the + // screen is shaking. + debug(5, "Player_V3M::loadMusic: Skipping unknown music type %02X%02X", ptr[4], ptr[5]); + resource.close(); + return false; + } + + uint i; + for (i = 0; i < 5; i++) { + int instrument = READ_BE_UINT16(ptr + 20 + 2 * i); + int offset = READ_BE_UINT16(ptr + 30 + 2 * i); + + _channel[i]._looped = false; + _channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3; + _channel[i]._data = ptr + offset + 6; + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = true; + + Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument); + if (_channel[i].loadInstrument(stream)) { + debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str()); + } else { + resource.close(); + return false; + } + } + + resource.close(); + return true; +} + +bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { + _channel[ch]._instrument.newNote(); + if (_channel[ch]._pos >= _channel[ch]._length) { + if (!_channel[ch]._looped) { + _channel[ch]._notesLeft = false; + return false; + } + _channel[ch]._pos = 0; + } + uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + byte note = _channel[ch]._data[_channel[ch]._pos + 2]; + samples = durationToSamples(duration); + if (note > 0) { + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); + velocity = 127; + } else { + pitchModifier = 0; + velocity = 0; + } + _channel[ch]._pos += 3; + return true; +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v3m.h b/engines/scumm/players/player_v3m.h new file mode 100644 index 0000000000..615d736035 --- /dev/null +++ b/engines/scumm/players/player_v3m.h @@ -0,0 +1,54 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V3M_H +#define SCUMM_PLAYERS_PLAYER_V3M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/players/player_mac.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V3 Macintosh music driver. + */ +class Player_V3M : public Player_Mac { +public: + Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer); + + virtual bool checkMusicAvailable(); + virtual bool loadMusic(const byte *ptr); + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v4a.cpp b/engines/scumm/players/player_v4a.cpp new file mode 100644 index 0000000000..59f49625c3 --- /dev/null +++ b/engines/scumm/players/player_v4a.cpp @@ -0,0 +1,190 @@ +/* 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 "engines/engine.h" +#include "scumm/players/player_v4a.h" +#include "scumm/scumm.h" + +#include "common/file.h" + +namespace Scumm { + +Player_V4A::Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer) + : _vm(scumm), + _mixer(mixer), + _tfmxMusic(_mixer->getOutputRate(), true), + _tfmxSfx(_mixer->getOutputRate(), true), + _musicHandle(), + _sfxHandle(), + _musicId(), + _sfxSlots(), + _initState(0), + _signal(0) { + + assert(scumm); + assert(mixer); + assert(_vm->_game.id == GID_MONKEY_VGA); + _tfmxMusic.setSignalPtr(&_signal, 1); +} + +bool Player_V4A::init() { + if (_vm->_game.id != GID_MONKEY_VGA) + error("player_v4a - unknown game"); + + Common::File fileMdat, fileSample; + + if (fileMdat.open("music.dat") && fileSample.open("sample.dat")) { + // explicitly request that no instance delets the resources automatically + if (_tfmxMusic.load(fileMdat, fileSample, false)) { + _tfmxSfx.setModuleData(_tfmxMusic); + return true; + } + } else + warning("player_v4a: couldnt load one of the music resources: music.dat, sample.dat"); + + return false; +} + +Player_V4A::~Player_V4A() { + _mixer->stopHandle(_musicHandle); + _mixer->stopHandle(_sfxHandle); + _tfmxMusic.freeResources(); +} + +void Player_V4A::setMusicVolume(int vol) { + debug(5, "player_v4a: setMusicVolume %i", vol); +} + +void Player_V4A::stopAllSounds() { + debug(5, "player_v4a: stopAllSounds"); + if (_initState > 0) { + _tfmxMusic.stopSong(); + _signal = 0; + _musicId = 0; + + _tfmxSfx.stopSong(); + clearSfxSlots(); + } else + _mixer->stopHandle(_musicHandle); +} + +void Player_V4A::stopSound(int nr) { + debug(5, "player_v4a: stopSound %d", nr); + if (nr == 0) + return; + if (nr == _musicId) { + _musicId = 0; + if (_initState > 0) + _tfmxMusic.stopSong(); + else + _mixer->stopHandle(_musicHandle); + _signal = 0; + } else { + const int chan = getSfxChan(nr); + if (chan != -1) { + setSfxSlot(chan, 0); + _tfmxSfx.stopMacroEffect(chan); + } + } +} + +void Player_V4A::startSound(int nr) { + static const int8 monkeyCommands[52] = { + -1, -2, -3, -4, -5, -6, -7, -8, + -9, -10, -11, -12, -13, -14, 18, 17, + -17, -18, -19, -20, -21, -22, -23, -24, + -25, -26, -27, -28, -29, -30, -31, -32, + -33, 16, -35, 0, 1, 2, 3, 7, + 8, 10, 11, 4, 5, 14, 15, 12, + 6, 13, 9, 19 + }; + + const byte *ptr = _vm->getResourceAddress(rtSound, nr); + assert(ptr); + + const int val = ptr[9]; + if (val < 0 || val >= ARRAYSIZE(monkeyCommands)) { + warning("player_v4a: illegal Songnumber %i", val); + return; + } + + if (!_initState) + _initState = init() ? 1 : -1; + + if (_initState < 0) + return; + + int index = monkeyCommands[val]; + const byte type = ptr[6]; + if (index < 0) { // SoundFX + index = -index - 1; + debug(3, "player_v4a: play %d: custom %i - %02X", nr, index, type); + + // start an empty Song so timing is setup + if (_tfmxSfx.getSongIndex() < 0) + _tfmxSfx.doSong(0x18); + + const int chan = _tfmxSfx.doSfx((uint16)index); + if (chan >= 0 && chan < ARRAYSIZE(_sfxSlots)) + setSfxSlot(chan, nr, type); + else + warning("player_v4a: custom %i is not of required type", index); + + // the Tfmx-player never "ends" the output by itself, so this should be threadsafe + if (!_mixer->isSoundHandleActive(_sfxHandle)) + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, &_tfmxSfx, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); + + } else { // Song + debug(3, "player_v4a: play %d: song %i - %02X", nr, index, type); + if (ptr[6] != 0x7F) + warning("player_v4a: Song has wrong type"); + + _tfmxMusic.doSong(index); + _signal = 2; + + // the Tfmx-player never "ends" the output by itself, so this should be threadsafe + if (!_mixer->isSoundHandleActive(_musicHandle)) + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, &_tfmxMusic, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO); + _musicId = nr; + } +} + +int Player_V4A::getMusicTimer() { + // A workaround if the modplayer couldnt load the datafiles - just return a number big enough to pass all tests + if (_initState < 0) + return 2000; + if (_musicId) { + // The titlesong (and a few others) is running with ~70 ticks per second and the scale seems to be based on that. + // The Game itself doesnt get the timing from the Tfmx Player however, so we just use the elapsed time + // 357 ~ 1000 * 25 * (1 / 70) + return _mixer->getSoundElapsedTime(_musicHandle) / 357; + } + return 0; +} + +int Player_V4A::getSoundStatus(int nr) const { + // For music the game queues a variable the Tfmx Player sets through a special command. + // For sfx there seems to be no way to queue them, and the game doesnt try to. + return (nr == _musicId) ? _signal : 0; +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v4a.h b/engines/scumm/players/player_v4a.h new file mode 100644 index 0000000000..c8c7b63ed2 --- /dev/null +++ b/engines/scumm/players/player_v4a.h @@ -0,0 +1,96 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V4A_H +#define SCUMM_PLAYERS_PLAYER_V4A_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "scumm/music.h" +#include "audio/mixer.h" +#include "audio/mods/tfmx.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V4 Amiga sound/music driver. + */ +class Player_V4A : public MusicEngine { +public: + Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer); + virtual ~Player_V4A(); + + virtual void setMusicVolume(int vol); + virtual void startSound(int sound); + virtual void stopSound(int sound); + virtual void stopAllSounds(); + virtual int getMusicTimer(); + virtual int getSoundStatus(int sound) const; + +private: + ScummEngine *const _vm; + Audio::Mixer *const _mixer; + + Audio::Tfmx _tfmxMusic; + Audio::Tfmx _tfmxSfx; + Audio::SoundHandle _musicHandle; + Audio::SoundHandle _sfxHandle; + + int _musicId; + uint16 _signal; + + struct SfxChan { + int id; +// byte type; + } _sfxSlots[4]; + + int8 _initState; // < 0: failed, 0: uninitialized, > 0: initialized + + int getSfxChan(int id) const { + for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i) + if (_sfxSlots[i].id == id) + return i; + return -1; + } + + void setSfxSlot(int channel, int id, byte type = 0) { + _sfxSlots[channel].id = id; +// _sfxSlots[channel].type = type; + } + + void clearSfxSlots() { + for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i){ + _sfxSlots[i].id = 0; +// _sfxSlots[i].type = 0; + } + } + + bool init(); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/players/player_v5m.cpp b/engines/scumm/players/player_v5m.cpp new file mode 100644 index 0000000000..77b6d52a3c --- /dev/null +++ b/engines/scumm/players/player_v5m.cpp @@ -0,0 +1,246 @@ +/* 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. + * + */ + +/* + From Markus Magnuson (superqult) we got this information: + Mac0 + --- + 4 bytes - 'SOUN' + BE 4 bytes - block length + + 4 bytes - 'Mac0' + BE 4 bytes - (blockLength - 27) + 28 bytes - ??? + + do this three times (once for each channel): + 4 bytes - 'Chan' + BE 4 bytes - channel length + 4 bytes - instrument name (e.g. 'MARI') + + do this for ((chanLength-24)/4) times: + 2 bytes - note duration + 1 byte - note value + 1 byte - note velocity + + 4 bytes - ??? + 4 bytes - 'Loop'/'Done' + 4 bytes - ??? + + 1 byte - 0x09 + --- + + The instruments presumably correspond to the snd resource names in the + Monkey Island executable: + + Instruments + "MARI" - MARIMBA + "PLUC" - PLUCK + "HARM" - HARMONIC + "PIPE" - PIPEORGAN + "TROM" - TROMBONE + "STRI" - STRINGS + "HORN" - HORN + "VIBE" - VIBES + "SHAK" - SHAKUHACHI + "PANP" - PANPIPE + "WHIS" - WHISTLE + "ORGA" - ORGAN3 + "BONG" - BONGO + "BASS" - BASS + + --- + + Note values <= 1 are silent. +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/players/player_v5m.h" +#include "scumm/scumm.h" + +namespace Scumm { + +Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) + : Player_Mac(scumm, mixer, 3, 0x07, false) { + assert(_vm->_game.id == GID_MONKEY); +} + +// Try both with and without underscore in the filename, because hfsutils may +// turn the space into an underscore. At least, it did for me. + +static const char *monkeyIslandFileNames[] = { + "Monkey Island", + "Monkey_Island" +}; + +bool Player_V5M::checkMusicAvailable() { + Common::MacResManager resource; + + for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { + if (resource.exists(monkeyIslandFileNames[i])) { + return true; + } + } + + GUI::MessageDialog dialog(_( + "Could not find the 'Monkey Island' Macintosh executable to read the\n" + "instruments from. Music will be disabled."), _("OK")); + dialog.runModal(); + return false; +} + +bool Player_V5M::loadMusic(const byte *ptr) { + Common::MacResManager resource; + bool found = false; + uint i; + + for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { + if (resource.open(monkeyIslandFileNames[i])) { + found = true; + break; + } + } + + if (!found) { + return false; + } + + ptr += 8; + // TODO: Decipher the unknown bytes in the header. For now, skip 'em + ptr += 28; + + Common::MacResIDArray idArray = resource.getResIDArray(RES_SND); + + // Load the three channels and their instruments + for (i = 0; i < 3; i++) { + assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n')); + uint32 len = READ_BE_UINT32(ptr + 4); + uint32 instrument = READ_BE_UINT32(ptr + 8); + + _channel[i]._length = len - 20; + _channel[i]._data = ptr + 12; + _channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p')); + _channel[i]._pos = 0; + _channel[i]._pitchModifier = 0; + _channel[i]._velocity = 0; + _channel[i]._remaining = 0; + _channel[i]._notesLeft = true; + + for (uint j = 0; j < idArray.size(); j++) { + Common::String name = resource.getResName(RES_SND, idArray[j]); + if (instrument == READ_BE_UINT32(name.c_str())) { + debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str()); + Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]); + + if (!_channel[i].loadInstrument(stream)) { + resource.close(); + return false; + } + + break; + } + } + + ptr += len; + } + + resource.close(); + + // The last note of each channel is just zeroes. We will adjust this + // note so that all the channels end at the same time. + + uint32 samples[3]; + uint32 maxSamples = 0; + for (i = 0; i < 3; i++) { + samples[i] = 0; + for (uint j = 0; j < _channel[i]._length; j += 4) { + samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j])); + } + if (samples[i] > maxSamples) { + maxSamples = samples[i]; + } + } + + for (i = 0; i < 3; i++) { + _lastNoteSamples[i] = maxSamples - samples[i]; + } + + return true; +} + +bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { + if (_channel[ch]._pos >= _channel[ch]._length) { + if (!_channel[ch]._looped) { + _channel[ch]._notesLeft = false; + return false; + } + // FIXME: Jamieson630: The jump seems to be happening + // too quickly! There should maybe be a pause after + // the last Note Off? But I couldn't find one in the + // MI1 Lookout music, where I was hearing problems. + _channel[ch]._pos = 0; + } + uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); + byte note = _channel[ch]._data[_channel[ch]._pos + 2]; + samples = durationToSamples(duration); + + if (note != 1) { + _channel[ch]._instrument.newNote(); + } + + if (note > 1) { + pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); + velocity = _channel[ch]._data[_channel[ch]._pos + 3]; + } else if (note == 1) { + // This is guesswork, but Monkey Island uses two different + // "special" note values: 0, which is clearly a rest, and 1 + // which is... I thought at first it was a "soft" key off, to + // fade out the note, but listening to the music in a Mac + // emulator (which unfortunately doesn't work all that well), + // I hear no trace of fading out. + // + // It could mean "change the volume on the current note", but + // I can't hear that either, and it always seems to use the + // exact same velocity on this note. + // + // So it appears it really just is a "hold the current note", + // but why? Couldn't they just have made the original note + // longer? + + pitchModifier = _channel[ch]._pitchModifier; + velocity = _channel[ch]._velocity; + } else { + pitchModifier = 0; + velocity = 0; + } + + _channel[ch]._pos += 4; + + if (_channel[ch]._pos >= _channel[ch]._length) { + samples = _lastNoteSamples[ch]; + } + return true; +} + +} // End of namespace Scumm diff --git a/engines/scumm/players/player_v5m.h b/engines/scumm/players/player_v5m.h new file mode 100644 index 0000000000..e3a457f8af --- /dev/null +++ b/engines/scumm/players/player_v5m.h @@ -0,0 +1,57 @@ +/* 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. + * + */ + +#ifndef SCUMM_PLAYERS_PLAYER_V5M_H +#define SCUMM_PLAYERS_PLAYER_V5M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/players/player_mac.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V5 Macintosh music driver. + */ +class Player_V5M : public Player_Mac { +public: + Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer); + + virtual bool checkMusicAvailable(); + virtual bool loadMusic(const byte *ptr); + virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); + +private: + uint32 _lastNoteSamples[3]; +}; + +} // End of namespace Scumm + +#endif |