From 651bf899399de2b5c2d8e62a5bb4964b037950d2 Mon Sep 17 00:00:00 2001 From: Filippos Karapetis Date: Fri, 1 Nov 2013 06:57:20 +0200 Subject: SCUMM: Move all players to a separate "player" directory There are 34 player .cpp/.h player files, so they have been placed in their own directory, to logically separate them from the rest of the engine --- engines/scumm/module.mk | 34 +- engines/scumm/player/ad.cpp | 959 +++++++++++++++++++ engines/scumm/player/ad.h | 190 ++++ engines/scumm/player/apple2.cpp | 500 ++++++++++ engines/scumm/player/apple2.h | 297 ++++++ engines/scumm/player/mac.cpp | 419 +++++++++ engines/scumm/player/mac.h | 133 +++ engines/scumm/player/mod.cpp | 223 +++++ engines/scumm/player/mod.h | 100 ++ engines/scumm/player/nes.cpp | 1067 +++++++++++++++++++++ engines/scumm/player/nes.h | 114 +++ engines/scumm/player/pce.cpp | 756 +++++++++++++++ engines/scumm/player/pce.h | 133 +++ engines/scumm/player/sid.cpp | 1384 +++++++++++++++++++++++++++ engines/scumm/player/sid.h | 276 ++++++ engines/scumm/player/towns.cpp | 753 +++++++++++++++ engines/scumm/player/towns.h | 180 ++++ engines/scumm/player/v1.cpp | 607 ++++++++++++ engines/scumm/player/v1.h | 98 ++ engines/scumm/player/v2.cpp | 327 +++++++ engines/scumm/player/v2.h | 72 ++ engines/scumm/player/v2a.cpp | 1954 +++++++++++++++++++++++++++++++++++++++ engines/scumm/player/v2a.h | 73 ++ engines/scumm/player/v2base.cpp | 654 +++++++++++++ engines/scumm/player/v2base.h | 145 +++ engines/scumm/player/v2cms.cpp | 787 ++++++++++++++++ engines/scumm/player/v2cms.h | 177 ++++ engines/scumm/player/v3a.cpp | 357 +++++++ engines/scumm/player/v3a.h | 101 ++ engines/scumm/player/v3m.cpp | 214 +++++ engines/scumm/player/v3m.h | 54 ++ engines/scumm/player/v4a.cpp | 190 ++++ engines/scumm/player/v4a.h | 96 ++ engines/scumm/player/v5m.cpp | 246 +++++ engines/scumm/player/v5m.h | 57 ++ engines/scumm/player_ad.cpp | 959 ------------------- engines/scumm/player_ad.h | 190 ---- engines/scumm/player_apple2.cpp | 500 ---------- engines/scumm/player_apple2.h | 297 ------ engines/scumm/player_mac.cpp | 419 --------- engines/scumm/player_mac.h | 133 --- engines/scumm/player_mod.cpp | 223 ----- engines/scumm/player_mod.h | 100 -- engines/scumm/player_nes.cpp | 1067 --------------------- engines/scumm/player_nes.h | 114 --- engines/scumm/player_pce.cpp | 756 --------------- engines/scumm/player_pce.h | 133 --- engines/scumm/player_sid.cpp | 1384 --------------------------- engines/scumm/player_sid.h | 276 ------ engines/scumm/player_towns.cpp | 753 --------------- engines/scumm/player_towns.h | 180 ---- engines/scumm/player_v1.cpp | 607 ------------ engines/scumm/player_v1.h | 98 -- engines/scumm/player_v2.cpp | 327 ------- engines/scumm/player_v2.h | 72 -- engines/scumm/player_v2a.cpp | 1954 --------------------------------------- engines/scumm/player_v2a.h | 73 -- engines/scumm/player_v2base.cpp | 654 ------------- engines/scumm/player_v2base.h | 145 --- engines/scumm/player_v2cms.cpp | 787 ---------------- engines/scumm/player_v2cms.h | 177 ---- engines/scumm/player_v3a.cpp | 357 ------- engines/scumm/player_v3a.h | 101 -- engines/scumm/player_v3m.cpp | 214 ----- engines/scumm/player_v3m.h | 54 -- engines/scumm/player_v4a.cpp | 190 ---- engines/scumm/player_v4a.h | 96 -- engines/scumm/player_v5m.cpp | 246 ----- engines/scumm/player_v5m.h | 57 -- engines/scumm/saveload.cpp | 2 +- engines/scumm/script_v5.cpp | 2 +- engines/scumm/scumm.cpp | 28 +- engines/scumm/sound.cpp | 2 +- 73 files changed, 13727 insertions(+), 13727 deletions(-) create mode 100644 engines/scumm/player/ad.cpp create mode 100644 engines/scumm/player/ad.h create mode 100644 engines/scumm/player/apple2.cpp create mode 100644 engines/scumm/player/apple2.h create mode 100644 engines/scumm/player/mac.cpp create mode 100644 engines/scumm/player/mac.h create mode 100644 engines/scumm/player/mod.cpp create mode 100644 engines/scumm/player/mod.h create mode 100644 engines/scumm/player/nes.cpp create mode 100644 engines/scumm/player/nes.h create mode 100644 engines/scumm/player/pce.cpp create mode 100644 engines/scumm/player/pce.h create mode 100644 engines/scumm/player/sid.cpp create mode 100644 engines/scumm/player/sid.h create mode 100644 engines/scumm/player/towns.cpp create mode 100644 engines/scumm/player/towns.h create mode 100644 engines/scumm/player/v1.cpp create mode 100644 engines/scumm/player/v1.h create mode 100644 engines/scumm/player/v2.cpp create mode 100644 engines/scumm/player/v2.h create mode 100644 engines/scumm/player/v2a.cpp create mode 100644 engines/scumm/player/v2a.h create mode 100644 engines/scumm/player/v2base.cpp create mode 100644 engines/scumm/player/v2base.h create mode 100644 engines/scumm/player/v2cms.cpp create mode 100644 engines/scumm/player/v2cms.h create mode 100644 engines/scumm/player/v3a.cpp create mode 100644 engines/scumm/player/v3a.h create mode 100644 engines/scumm/player/v3m.cpp create mode 100644 engines/scumm/player/v3m.h create mode 100644 engines/scumm/player/v4a.cpp create mode 100644 engines/scumm/player/v4a.h create mode 100644 engines/scumm/player/v5m.cpp create mode 100644 engines/scumm/player/v5m.h delete mode 100644 engines/scumm/player_ad.cpp delete mode 100644 engines/scumm/player_ad.h delete mode 100644 engines/scumm/player_apple2.cpp delete mode 100644 engines/scumm/player_apple2.h delete mode 100644 engines/scumm/player_mac.cpp delete mode 100644 engines/scumm/player_mac.h delete mode 100644 engines/scumm/player_mod.cpp delete mode 100644 engines/scumm/player_mod.h delete mode 100644 engines/scumm/player_nes.cpp delete mode 100644 engines/scumm/player_nes.h delete mode 100644 engines/scumm/player_pce.cpp delete mode 100644 engines/scumm/player_pce.h delete mode 100644 engines/scumm/player_sid.cpp delete mode 100644 engines/scumm/player_sid.h delete mode 100644 engines/scumm/player_towns.cpp delete mode 100644 engines/scumm/player_towns.h delete mode 100644 engines/scumm/player_v1.cpp delete mode 100644 engines/scumm/player_v1.h delete mode 100644 engines/scumm/player_v2.cpp delete mode 100644 engines/scumm/player_v2.h delete mode 100644 engines/scumm/player_v2a.cpp delete mode 100644 engines/scumm/player_v2a.h delete mode 100644 engines/scumm/player_v2base.cpp delete mode 100644 engines/scumm/player_v2base.h delete mode 100644 engines/scumm/player_v2cms.cpp delete mode 100644 engines/scumm/player_v2cms.h delete mode 100644 engines/scumm/player_v3a.cpp delete mode 100644 engines/scumm/player_v3a.h delete mode 100644 engines/scumm/player_v3m.cpp delete mode 100644 engines/scumm/player_v3m.h delete mode 100644 engines/scumm/player_v4a.cpp delete mode 100644 engines/scumm/player_v4a.h delete mode 100644 engines/scumm/player_v5m.cpp delete mode 100644 engines/scumm/player_v5m.h diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index a377ad3dc4..3c0671043e 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -35,23 +35,23 @@ MODULE_OBJS := \ midiparser_ro.o \ object.o \ palette.o \ - player_ad.o \ - player_apple2.o \ - player_mac.o \ - player_mod.o \ - player_nes.o \ - player_pce.o \ - player_sid.o \ - player_towns.o \ - player_v1.o \ - player_v2.o \ - player_v2a.o \ - player_v2base.o \ - player_v2cms.o \ - player_v3a.o \ - player_v3m.o \ - player_v4a.o \ - player_v5m.o \ + player/ad.o \ + player/apple2.o \ + player/mac.o \ + player/mod.o \ + player/nes.o \ + player/pce.o \ + player/sid.o \ + player/towns.o \ + player/v1.o \ + player/v2.o \ + player/v2a.o \ + player/v2base.o \ + player/v2cms.o \ + player/v3a.o \ + player/v3m.o \ + player/v4a.o \ + player/v5m.o \ resource_v2.o \ resource_v3.o \ resource_v4.o \ diff --git a/engines/scumm/player/ad.cpp b/engines/scumm/player/ad.cpp new file mode 100644 index 0000000000..d3cd0483b6 --- /dev/null +++ b/engines/scumm/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/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(ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxChannelVolume); + int soundVolumeSfx = CLIP(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/player/ad.h b/engines/scumm/player/ad.h new file mode 100644 index 0000000000..da6c7177ef --- /dev/null +++ b/engines/scumm/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_PLAYER_AD_H +#define SCUMM_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/player/apple2.cpp b/engines/scumm/player/apple2.cpp new file mode 100644 index 0000000000..671cbf9580 --- /dev/null +++ b/engines/scumm/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/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/player/apple2.h b/engines/scumm/player/apple2.h new file mode 100644 index 0000000000..e1ec9d8946 --- /dev/null +++ b/engines/scumm/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_PLAYER_APPLEII_H +#define SCUMM_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/player/mac.cpp b/engines/scumm/player/mac.cpp new file mode 100644 index 0000000000..1fba266311 --- /dev/null +++ b/engines/scumm/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/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(_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/player/mac.h b/engines/scumm/player/mac.h new file mode 100644 index 0000000000..09307b4e57 --- /dev/null +++ b/engines/scumm/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_PLAYER_MAC_H +#define SCUMM_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/player/mod.cpp b/engines/scumm/player/mod.cpp new file mode 100644 index 0000000000..afa8967343 --- /dev/null +++ b/engines/scumm/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/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/player/mod.h b/engines/scumm/player/mod.h new file mode 100644 index 0000000000..619d83541d --- /dev/null +++ b/engines/scumm/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_PLAYER_MOD_H +#define SCUMM_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/player/nes.cpp b/engines/scumm/player/nes.cpp new file mode 100644 index 0000000000..072f46dd5a --- /dev/null +++ b/engines/scumm/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/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 +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/player/nes.h b/engines/scumm/player/nes.h new file mode 100644 index 0000000000..be1617e0f6 --- /dev/null +++ b/engines/scumm/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_PLAYER_NES_H +#define SCUMM_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/player/pce.cpp b/engines/scumm/player/pce.cpp new file mode 100644 index 0000000000..0a9cbaa315 --- /dev/null +++ b/engines/scumm/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 +#include "scumm/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/player/pce.h b/engines/scumm/player/pce.h new file mode 100644 index 0000000000..427fb1ace6 --- /dev/null +++ b/engines/scumm/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_PLAYER_PCE_H +#define SCUMM_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/player/sid.cpp b/engines/scumm/player/sid.cpp new file mode 100644 index 0000000000..ae5346f18f --- /dev/null +++ b/engines/scumm/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/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/player/sid.h b/engines/scumm/player/sid.h new file mode 100644 index 0000000000..12e3573575 --- /dev/null +++ b/engines/scumm/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_PLAYER_SID_H +#define SCUMM_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/player/towns.cpp b/engines/scumm/player/towns.cpp new file mode 100644 index 0000000000..4adb11b5a3 --- /dev/null +++ b/engines/scumm/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/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 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(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/player/towns.h b/engines/scumm/player/towns.h new file mode 100644 index 0000000000..5c76d7c6c7 --- /dev/null +++ b/engines/scumm/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_PLAYER_TOWNS_H +#define SCUMM_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/player/v1.cpp b/engines/scumm/player/v1.cpp new file mode 100644 index 0000000000..25f42f143c --- /dev/null +++ b/engines/scumm/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/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/player/v1.h b/engines/scumm/player/v1.h new file mode 100644 index 0000000000..d6e08bd7ab --- /dev/null +++ b/engines/scumm/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_PLAYER_V1_H +#define SCUMM_PLAYER_V1_H + +#include "scumm/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/player/v2.cpp b/engines/scumm/player/v2.cpp new file mode 100644 index 0000000000..e3b3c978dd --- /dev/null +++ b/engines/scumm/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/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/player/v2.h b/engines/scumm/player/v2.h new file mode 100644 index 0000000000..e23f74cb31 --- /dev/null +++ b/engines/scumm/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_PLAYER_V2_H +#define SCUMM_PLAYER_V2_H + +#include "scumm/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/player/v2a.cpp b/engines/scumm/player/v2a.cpp new file mode 100644 index 0000000000..2350d7e048 --- /dev/null +++ b/engines/scumm/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/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 +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/player/v2a.h b/engines/scumm/player/v2a.h new file mode 100644 index 0000000000..cc2f4a7362 --- /dev/null +++ b/engines/scumm/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_PLAYER_V2A_H +#define SCUMM_PLAYER_V2A_H + +#include "common/scummsys.h" +#include "scumm/music.h" +#include "scumm/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/player/v2base.cpp b/engines/scumm/player/v2base.cpp new file mode 100644 index 0000000000..4b20cfc910 --- /dev/null +++ b/engines/scumm/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/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/player/v2base.h b/engines/scumm/player/v2base.h new file mode 100644 index 0000000000..eb9ed941ca --- /dev/null +++ b/engines/scumm/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_PLAYER_V2BASE_H +#define SCUMM_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/player/v2cms.cpp b/engines/scumm/player/v2cms.cpp new file mode 100644 index 0000000000..30ca238860 --- /dev/null +++ b/engines/scumm/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/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(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/player/v2cms.h b/engines/scumm/player/v2cms.h new file mode 100644 index 0000000000..d022a64a5f --- /dev/null +++ b/engines/scumm/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_PLAYER_V2CMS_H +#define SCUMM_PLAYER_V2CMS_H + +#include "scumm/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/player/v3a.cpp b/engines/scumm/player/v3a.cpp new file mode 100644 index 0000000000..b91bd49e6e --- /dev/null +++ b/engines/scumm/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/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/player/v3a.h b/engines/scumm/player/v3a.h new file mode 100644 index 0000000000..0869c3398c --- /dev/null +++ b/engines/scumm/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_PLAYER_V3A_H +#define SCUMM_PLAYER_V3A_H + +#include "common/scummsys.h" +#include "scumm/music.h" +#include "scumm/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/player/v3m.cpp b/engines/scumm/player/v3m.cpp new file mode 100644 index 0000000000..b910b36a18 --- /dev/null +++ b/engines/scumm/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/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/player/v3m.h b/engines/scumm/player/v3m.h new file mode 100644 index 0000000000..609ab8a9d7 --- /dev/null +++ b/engines/scumm/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_PLAYER_V3M_H +#define SCUMM_PLAYER_V3M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/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/player/v4a.cpp b/engines/scumm/player/v4a.cpp new file mode 100644 index 0000000000..de1301ee4c --- /dev/null +++ b/engines/scumm/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/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/player/v4a.h b/engines/scumm/player/v4a.h new file mode 100644 index 0000000000..d01c70f295 --- /dev/null +++ b/engines/scumm/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_PLAYER_V4A_H +#define SCUMM_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/player/v5m.cpp b/engines/scumm/player/v5m.cpp new file mode 100644 index 0000000000..8a2b7f6814 --- /dev/null +++ b/engines/scumm/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/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/player/v5m.h b/engines/scumm/player/v5m.h new file mode 100644 index 0000000000..ed51943226 --- /dev/null +++ b/engines/scumm/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_PLAYER_V5M_H +#define SCUMM_PLAYER_V5M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/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 diff --git a/engines/scumm/player_ad.cpp b/engines/scumm/player_ad.cpp deleted file mode 100644 index ed368afbf6..0000000000 --- a/engines/scumm/player_ad.cpp +++ /dev/null @@ -1,959 +0,0 @@ -/* 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/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(ConfMan.getInt("music_volume"), 0, Audio::Mixer::kMaxChannelVolume); - int soundVolumeSfx = CLIP(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/player_ad.h b/engines/scumm/player_ad.h deleted file mode 100644 index da6c7177ef..0000000000 --- a/engines/scumm/player_ad.h +++ /dev/null @@ -1,190 +0,0 @@ -/* 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_PLAYER_AD_H -#define SCUMM_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/player_apple2.cpp b/engines/scumm/player_apple2.cpp deleted file mode 100644 index 58e4f78a94..0000000000 --- a/engines/scumm/player_apple2.cpp +++ /dev/null @@ -1,500 +0,0 @@ -/* 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/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/player_apple2.h b/engines/scumm/player_apple2.h deleted file mode 100644 index e1ec9d8946..0000000000 --- a/engines/scumm/player_apple2.h +++ /dev/null @@ -1,297 +0,0 @@ -/* 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_PLAYER_APPLEII_H -#define SCUMM_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/player_mac.cpp b/engines/scumm/player_mac.cpp deleted file mode 100644 index a60736df5e..0000000000 --- a/engines/scumm/player_mac.cpp +++ /dev/null @@ -1,419 +0,0 @@ -/* 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/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(_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/player_mac.h b/engines/scumm/player_mac.h deleted file mode 100644 index 09307b4e57..0000000000 --- a/engines/scumm/player_mac.h +++ /dev/null @@ -1,133 +0,0 @@ -/* 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_PLAYER_MAC_H -#define SCUMM_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/player_mod.cpp b/engines/scumm/player_mod.cpp deleted file mode 100644 index 6411f0a17a..0000000000 --- a/engines/scumm/player_mod.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/* 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/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/player_mod.h b/engines/scumm/player_mod.h deleted file mode 100644 index 619d83541d..0000000000 --- a/engines/scumm/player_mod.h +++ /dev/null @@ -1,100 +0,0 @@ -/* 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_PLAYER_MOD_H -#define SCUMM_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/player_nes.cpp b/engines/scumm/player_nes.cpp deleted file mode 100644 index a6ffc9ed86..0000000000 --- a/engines/scumm/player_nes.cpp +++ /dev/null @@ -1,1067 +0,0 @@ -/* 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/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 -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/player_nes.h b/engines/scumm/player_nes.h deleted file mode 100644 index be1617e0f6..0000000000 --- a/engines/scumm/player_nes.h +++ /dev/null @@ -1,114 +0,0 @@ -/* 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_PLAYER_NES_H -#define SCUMM_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/player_pce.cpp b/engines/scumm/player_pce.cpp deleted file mode 100644 index 8d886ee008..0000000000 --- a/engines/scumm/player_pce.cpp +++ /dev/null @@ -1,756 +0,0 @@ -/* 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 -#include "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/player_pce.h b/engines/scumm/player_pce.h deleted file mode 100644 index 427fb1ace6..0000000000 --- a/engines/scumm/player_pce.h +++ /dev/null @@ -1,133 +0,0 @@ -/* 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_PLAYER_PCE_H -#define SCUMM_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/player_sid.cpp b/engines/scumm/player_sid.cpp deleted file mode 100644 index 7a609364e5..0000000000 --- a/engines/scumm/player_sid.cpp +++ /dev/null @@ -1,1384 +0,0 @@ -/* 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/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/player_sid.h b/engines/scumm/player_sid.h deleted file mode 100644 index 12e3573575..0000000000 --- a/engines/scumm/player_sid.h +++ /dev/null @@ -1,276 +0,0 @@ -/* 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_PLAYER_SID_H -#define SCUMM_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/player_towns.cpp b/engines/scumm/player_towns.cpp deleted file mode 100644 index 33e3e40e39..0000000000 --- a/engines/scumm/player_towns.cpp +++ /dev/null @@ -1,753 +0,0 @@ -/* 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/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 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(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/player_towns.h b/engines/scumm/player_towns.h deleted file mode 100644 index 5c76d7c6c7..0000000000 --- a/engines/scumm/player_towns.h +++ /dev/null @@ -1,180 +0,0 @@ -/* 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_PLAYER_TOWNS_H -#define SCUMM_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/player_v1.cpp b/engines/scumm/player_v1.cpp deleted file mode 100644 index 8e784e9866..0000000000 --- a/engines/scumm/player_v1.cpp +++ /dev/null @@ -1,607 +0,0 @@ -/* 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/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/player_v1.h b/engines/scumm/player_v1.h deleted file mode 100644 index 9e6063adc9..0000000000 --- a/engines/scumm/player_v1.h +++ /dev/null @@ -1,98 +0,0 @@ -/* 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_PLAYER_V1_H -#define SCUMM_PLAYER_V1_H - -#include "scumm/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/player_v2.cpp b/engines/scumm/player_v2.cpp deleted file mode 100644 index 6910f5e0db..0000000000 --- a/engines/scumm/player_v2.cpp +++ /dev/null @@ -1,327 +0,0 @@ -/* 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/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/player_v2.h b/engines/scumm/player_v2.h deleted file mode 100644 index d932585b8e..0000000000 --- a/engines/scumm/player_v2.h +++ /dev/null @@ -1,72 +0,0 @@ -/* 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_PLAYER_V2_H -#define SCUMM_PLAYER_V2_H - -#include "scumm/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/player_v2a.cpp b/engines/scumm/player_v2a.cpp deleted file mode 100644 index 07fc77b301..0000000000 --- a/engines/scumm/player_v2a.cpp +++ /dev/null @@ -1,1954 +0,0 @@ -/* 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/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 -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/player_v2a.h b/engines/scumm/player_v2a.h deleted file mode 100644 index fe20b43846..0000000000 --- a/engines/scumm/player_v2a.h +++ /dev/null @@ -1,73 +0,0 @@ -/* 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_PLAYER_V2A_H -#define SCUMM_PLAYER_V2A_H - -#include "common/scummsys.h" -#include "scumm/music.h" -#include "scumm/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/player_v2base.cpp b/engines/scumm/player_v2base.cpp deleted file mode 100644 index 0d3ad4b1b3..0000000000 --- a/engines/scumm/player_v2base.cpp +++ /dev/null @@ -1,654 +0,0 @@ -/* 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/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/player_v2base.h b/engines/scumm/player_v2base.h deleted file mode 100644 index eb9ed941ca..0000000000 --- a/engines/scumm/player_v2base.h +++ /dev/null @@ -1,145 +0,0 @@ -/* 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_PLAYER_V2BASE_H -#define SCUMM_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/player_v2cms.cpp b/engines/scumm/player_v2cms.cpp deleted file mode 100644 index c1242e0645..0000000000 --- a/engines/scumm/player_v2cms.cpp +++ /dev/null @@ -1,787 +0,0 @@ -/* 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/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(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/player_v2cms.h b/engines/scumm/player_v2cms.h deleted file mode 100644 index 905c7c141e..0000000000 --- a/engines/scumm/player_v2cms.h +++ /dev/null @@ -1,177 +0,0 @@ -/* 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_PLAYER_V2CMS_H -#define SCUMM_PLAYER_V2CMS_H - -#include "scumm/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/player_v3a.cpp b/engines/scumm/player_v3a.cpp deleted file mode 100644 index 472cd1252b..0000000000 --- a/engines/scumm/player_v3a.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/* 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/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/player_v3a.h b/engines/scumm/player_v3a.h deleted file mode 100644 index 9449664e9b..0000000000 --- a/engines/scumm/player_v3a.h +++ /dev/null @@ -1,101 +0,0 @@ -/* 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_PLAYER_V3A_H -#define SCUMM_PLAYER_V3A_H - -#include "common/scummsys.h" -#include "scumm/music.h" -#include "scumm/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/player_v3m.cpp b/engines/scumm/player_v3m.cpp deleted file mode 100644 index bf65ec797f..0000000000 --- a/engines/scumm/player_v3m.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* 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/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/player_v3m.h b/engines/scumm/player_v3m.h deleted file mode 100644 index 359bab32a9..0000000000 --- a/engines/scumm/player_v3m.h +++ /dev/null @@ -1,54 +0,0 @@ -/* 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_PLAYER_V3M_H -#define SCUMM_PLAYER_V3M_H - -#include "common/scummsys.h" -#include "common/util.h" -#include "common/mutex.h" -#include "scumm/music.h" -#include "scumm/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/player_v4a.cpp b/engines/scumm/player_v4a.cpp deleted file mode 100644 index e791736f0e..0000000000 --- a/engines/scumm/player_v4a.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* 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/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/player_v4a.h b/engines/scumm/player_v4a.h deleted file mode 100644 index d01c70f295..0000000000 --- a/engines/scumm/player_v4a.h +++ /dev/null @@ -1,96 +0,0 @@ -/* 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_PLAYER_V4A_H -#define SCUMM_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/player_v5m.cpp b/engines/scumm/player_v5m.cpp deleted file mode 100644 index 500f3bbc40..0000000000 --- a/engines/scumm/player_v5m.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/* 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/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/player_v5m.h b/engines/scumm/player_v5m.h deleted file mode 100644 index b2079ee331..0000000000 --- a/engines/scumm/player_v5m.h +++ /dev/null @@ -1,57 +0,0 @@ -/* 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_PLAYER_V5M_H -#define SCUMM_PLAYER_V5M_H - -#include "common/scummsys.h" -#include "common/util.h" -#include "common/mutex.h" -#include "scumm/music.h" -#include "scumm/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 diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 3453e53a18..8081afd317 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -30,7 +30,7 @@ #include "scumm/charset.h" #include "scumm/imuse_digi/dimuse.h" #include "scumm/imuse/imuse.h" -#include "scumm/player_towns.h" +#include "scumm/player/towns.h" #include "scumm/he/intern_he.h" #include "scumm/object.h" #include "scumm/resource.h" diff --git a/engines/scumm/script_v5.cpp b/engines/scumm/script_v5.cpp index 2d4c326b68..8a15656b4b 100644 --- a/engines/scumm/script_v5.cpp +++ b/engines/scumm/script_v5.cpp @@ -27,7 +27,7 @@ #include "scumm/scumm_v3.h" #include "scumm/scumm_v5.h" #include "scumm/sound.h" -#include "scumm/player_towns.h" +#include "scumm/player/towns.h" #include "scumm/util.h" #include "scumm/verbs.h" diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index d1a3de94b8..84d74032b8 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -45,26 +45,26 @@ #include "scumm/imuse_digi/dimuse.h" #include "scumm/smush/smush_mixer.h" #include "scumm/smush/smush_player.h" -#include "scumm/player_towns.h" +#include "scumm/player/towns.h" #include "scumm/insane/insane.h" #include "scumm/he/animation_he.h" #include "scumm/he/intern_he.h" #include "scumm/he/logic_he.h" #include "scumm/he/sound_he.h" #include "scumm/object.h" -#include "scumm/player_ad.h" -#include "scumm/player_nes.h" -#include "scumm/player_sid.h" -#include "scumm/player_pce.h" -#include "scumm/player_apple2.h" -#include "scumm/player_v1.h" -#include "scumm/player_v2.h" -#include "scumm/player_v2cms.h" -#include "scumm/player_v2a.h" -#include "scumm/player_v3a.h" -#include "scumm/player_v3m.h" -#include "scumm/player_v4a.h" -#include "scumm/player_v5m.h" +#include "scumm/player/ad.h" +#include "scumm/player/nes.h" +#include "scumm/player/sid.h" +#include "scumm/player/pce.h" +#include "scumm/player/apple2.h" +#include "scumm/player/v1.h" +#include "scumm/player/v2.h" +#include "scumm/player/v2cms.h" +#include "scumm/player/v2a.h" +#include "scumm/player/v3a.h" +#include "scumm/player/v3m.h" +#include "scumm/player/v4a.h" +#include "scumm/player/v5m.h" #include "scumm/resource.h" #include "scumm/he/resource_he.h" #include "scumm/scumm_v0.h" diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index aaf7f90aca..ad4625e28c 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -30,7 +30,7 @@ #include "scumm/file.h" #include "scumm/imuse/imuse.h" #include "scumm/imuse_digi/dimuse.h" -#include "scumm/player_towns.h" +#include "scumm/player/towns.h" #include "scumm/resource.h" #include "scumm/scumm.h" #include "scumm/sound.h" -- cgit v1.2.3